MediaWiki  REL1_20
Exception.php
Go to the documentation of this file.
00001 <?php
00032 class MWException extends Exception {
00033         var $logId;
00034 
00040         function useOutputPage() {
00041                 return $this->useMessageCache() &&
00042                         !empty( $GLOBALS['wgFullyInitialised'] ) &&
00043                         !empty( $GLOBALS['wgOut'] ) &&
00044                         !empty( $GLOBALS['wgTitle'] );
00045         }
00046 
00052         function useMessageCache() {
00053                 global $wgLang;
00054 
00055                 foreach ( $this->getTrace() as $frame ) {
00056                         if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
00057                                 return false;
00058                         }
00059                 }
00060 
00061                 return $wgLang instanceof Language;
00062         }
00063 
00071         function runHooks( $name, $args = array() ) {
00072                 global $wgExceptionHooks;
00073 
00074                 if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
00075                         return null; // Just silently ignore
00076                 }
00077 
00078                 if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
00079                         return null;
00080                 }
00081 
00082                 $hooks = $wgExceptionHooks[ $name ];
00083                 $callargs = array_merge( array( $this ), $args );
00084 
00085                 foreach ( $hooks as $hook ) {
00086                         if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) {    // 'function' or array( 'class', hook' )
00087                                 $result = call_user_func_array( $hook, $callargs );
00088                         } else {
00089                                 $result = null;
00090                         }
00091 
00092                         if ( is_string( $result ) ) {
00093                                 return $result;
00094                         }
00095                 }
00096                 return null;
00097         }
00098 
00108         function msg( $key, $fallback /*[, params...] */ ) {
00109                 $args = array_slice( func_get_args(), 2 );
00110 
00111                 if ( $this->useMessageCache() ) {
00112                         return wfMessage( $key, $args )->plain();
00113                 } else {
00114                         return wfMsgReplaceArgs( $fallback, $args );
00115                 }
00116         }
00117 
00125         function getHTML() {
00126                 global $wgShowExceptionDetails;
00127 
00128                 if ( $wgShowExceptionDetails ) {
00129                         return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
00130                                 '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
00131                                 "</p>\n";
00132                 } else {
00133                         return
00134                                 "<div class=\"errorbox\">" .
00135                                 '[' . $this->getLogId() . '] ' .
00136                                 gmdate( 'Y-m-d H:i:s' ) .
00137                                 ": Fatal exception of type " . get_class( $this ) . "</div>\n" .
00138                                 "<!-- Set \$wgShowExceptionDetails = true; " .
00139                                 "at the bottom of LocalSettings.php to show detailed " .
00140                                 "debugging information. -->";
00141                 }
00142         }
00143 
00151         function getText() {
00152                 global $wgShowExceptionDetails;
00153 
00154                 if ( $wgShowExceptionDetails ) {
00155                         return $this->getMessage() .
00156                                 "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
00157                 } else {
00158                         return "Set \$wgShowExceptionDetails = true; " .
00159                                 "in LocalSettings.php to show detailed debugging information.\n";
00160                 }
00161         }
00162 
00168         function getPageTitle() {
00169                 return $this->msg( 'internalerror', "Internal error" );
00170         }
00171 
00179         function getLogId() {
00180                 if ( $this->logId === null ) {
00181                         $this->logId = wfRandomString( 8 );
00182                 }
00183                 return $this->logId;
00184         }
00185 
00192         function getLogMessage() {
00193                 global $wgRequest;
00194 
00195                 $id = $this->getLogId();
00196                 $file = $this->getFile();
00197                 $line = $this->getLine();
00198                 $message = $this->getMessage();
00199 
00200                 if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
00201                         $url = $wgRequest->getRequestURL();
00202                         if ( !$url ) {
00203                                 $url = '[no URL]';
00204                         }
00205                 } else {
00206                         $url = '[no req]';
00207                 }
00208 
00209                 return "[$id] $url   Exception from line $line of $file: $message";
00210         }
00211 
00215         function reportHTML() {
00216                 global $wgOut;
00217                 if ( $this->useOutputPage() ) {
00218                         $wgOut->prepareErrorPage( $this->getPageTitle() );
00219 
00220                         $hookResult = $this->runHooks( get_class( $this ) );
00221                         if ( $hookResult ) {
00222                                 $wgOut->addHTML( $hookResult );
00223                         } else {
00224                                 $wgOut->addHTML( $this->getHTML() );
00225                         }
00226 
00227                         $wgOut->output();
00228                 } else {
00229                         header( "Content-Type: text/html; charset=utf-8" );
00230                         echo "<!doctype html>\n" .
00231                                 '<html><head>' .
00232                                 '<title>' . htmlspecialchars( $this->getPageTitle() ) . '</title>' .
00233                                 "</head><body>\n";
00234 
00235                         $hookResult = $this->runHooks( get_class( $this ) . "Raw" );
00236                         if ( $hookResult ) {
00237                                 echo $hookResult;
00238                         } else {
00239                                 echo $this->getHTML();
00240                         }
00241 
00242                         echo "</body></html>\n";
00243                 }
00244         }
00245 
00250         function report() {
00251                 global $wgLogExceptionBacktrace;
00252                 $log = $this->getLogMessage();
00253 
00254                 if ( $log ) {
00255                         if ( $wgLogExceptionBacktrace ) {
00256                                 wfDebugLog( 'exception', $log . "\n" . $this->getTraceAsString() . "\n" );
00257                         } else {
00258                                 wfDebugLog( 'exception', $log );
00259                         }
00260                 }
00261 
00262                 if ( defined( 'MW_API' ) ) {
00263                         // Unhandled API exception, we can't be sure that format printer is alive
00264                         header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
00265                         wfHttpError(500, 'Internal Server Error', $this->getText() );
00266                 } elseif ( self::isCommandLine() ) {
00267                         MWExceptionHandler::printError( $this->getText() );
00268                 } else {
00269                         header( "HTTP/1.1 500 MediaWiki exception" );
00270                         header( "Status: 500 MediaWiki exception", true );
00271 
00272                         $this->reportHTML();
00273                 }
00274         }
00275 
00282         static function isCommandLine() {
00283                 return !empty( $GLOBALS['wgCommandLineMode'] );
00284         }
00285 }
00286 
00294 class FatalError extends MWException {
00295 
00299         function getHTML() {
00300                 return $this->getMessage();
00301         }
00302 
00306         function getText() {
00307                 return $this->getMessage();
00308         }
00309 }
00310 
00317 class ErrorPageError extends MWException {
00318         public $title, $msg, $params;
00319 
00327         function __construct( $title, $msg, $params = null ) {
00328                 $this->title = $title;
00329                 $this->msg = $msg;
00330                 $this->params = $params;
00331 
00332                 if( $msg instanceof Message ){
00333                         parent::__construct( $msg );
00334                 } else {
00335                         parent::__construct( wfMessage( $msg )->text() );
00336                 }
00337         }
00338 
00339         function report() {
00340                 global $wgOut;
00341 
00342                 $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
00343                 $wgOut->output();
00344         }
00345 }
00346 
00355 class BadTitleError extends ErrorPageError {
00360         function __construct( $msg = 'badtitletext', $params = null ) {
00361                 parent::__construct( 'badtitle', $msg, $params );
00362         }
00363 
00368         function report() {
00369                 global $wgOut;
00370 
00371                 // bug 33646: a badtitle error page need to return an error code
00372                 // to let mobile browser now that it is not a normal page.
00373                 $wgOut->setStatusCode( 400 );
00374                 parent::report();
00375         }
00376 
00377 }
00378 
00386 class PermissionsError extends ErrorPageError {
00387         public $permission, $errors;
00388 
00389         function __construct( $permission, $errors = array() ) {
00390                 global $wgLang;
00391 
00392                 $this->permission = $permission;
00393 
00394                 if ( !count( $errors ) ) {
00395                         $groups = array_map(
00396                                 array( 'User', 'makeGroupLinkWiki' ),
00397                                 User::getGroupsWithPermission( $this->permission )
00398                         );
00399 
00400                         if ( $groups ) {
00401                                 $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
00402                         } else {
00403                                 $errors[] = array( 'badaccess-group0' );
00404                         }
00405                 }
00406 
00407                 $this->errors = $errors;
00408         }
00409 
00410         function report() {
00411                 global $wgOut;
00412 
00413                 $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
00414                 $wgOut->output();
00415         }
00416 }
00417 
00425 class ReadOnlyError extends ErrorPageError {
00426         public function __construct(){
00427                 parent::__construct(
00428                         'readonly',
00429                         'readonlytext',
00430                         wfReadOnlyReason()
00431                 );
00432         }
00433 }
00434 
00441 class ThrottledError extends ErrorPageError {
00442         public function __construct(){
00443                 parent::__construct(
00444                         'actionthrottled',
00445                         'actionthrottledtext'
00446                 );
00447         }
00448 
00449         public function report(){
00450                 global $wgOut;
00451                 $wgOut->setStatusCode( 503 );
00452                 parent::report();
00453         }
00454 }
00455 
00462 class UserBlockedError extends ErrorPageError {
00463         public function __construct( Block $block ){
00464                 global $wgLang, $wgRequest;
00465 
00466                 $blocker = $block->getBlocker();
00467                 if ( $blocker instanceof User ) { // local user
00468                         $blockerUserpage = $block->getBlocker()->getUserPage();
00469                         $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
00470                 } else { // foreign user
00471                         $link = $blocker;
00472                 }
00473 
00474                 $reason = $block->mReason;
00475                 if( $reason == '' ) {
00476                         $reason = wfMessage( 'blockednoreason' )->text();
00477                 }
00478 
00479                 /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
00480                  * This could be a username, an IP range, or a single IP. */
00481                 $intended = $block->getTarget();
00482 
00483                 parent::__construct(
00484                         'blockedtitle',
00485                         $block->mAuto ? 'autoblockedtext' : 'blockedtext',
00486                         array(
00487                                 $link,
00488                                 $reason,
00489                                 $wgRequest->getIP(),
00490                                 $block->getByName(),
00491                                 $block->getId(),
00492                                 $wgLang->formatExpiry( $block->mExpiry ),
00493                                 $intended,
00494                                 $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true )
00495                         )
00496                 );
00497         }
00498 }
00499 
00528 class UserNotLoggedIn extends ErrorPageError {
00529 
00538         public function __construct(
00539                 $reasonMsg = 'exception-nologin-text',
00540                 $titleMsg  = 'exception-nologin',
00541                 $params = null
00542         ) {
00543                 parent::__construct( $titleMsg, $reasonMsg, $params );
00544         }
00545 }
00546 
00554 class HttpError extends MWException {
00555         private $httpCode, $header, $content;
00556 
00564         public function __construct( $httpCode, $content, $header = null ){
00565                 parent::__construct( $content );
00566                 $this->httpCode = (int)$httpCode;
00567                 $this->header = $header;
00568                 $this->content = $content;
00569         }
00570 
00571         public function report() {
00572                 $httpMessage = HttpStatus::getMessage( $this->httpCode );
00573 
00574                 header( "Status: {$this->httpCode} {$httpMessage}" );
00575                 header( 'Content-type: text/html; charset=utf-8' );
00576 
00577                 if ( $this->header === null ) {
00578                         $header = $httpMessage;
00579                 } elseif ( $this->header instanceof Message ) {
00580                         $header = $this->header->escaped();
00581                 } else {
00582                         $header = htmlspecialchars( $this->header );
00583                 }
00584 
00585                 if ( $this->content instanceof Message ) {
00586                         $content = $this->content->escaped();
00587                 } else {
00588                         $content = htmlspecialchars( $this->content );
00589                 }
00590 
00591                 print "<!DOCTYPE html>\n".
00592                         "<html><head><title>$header</title></head>\n" .
00593                         "<body><h1>$header</h1><p>$content</p></body></html>\n";
00594         }
00595 }
00596 
00601 class MWExceptionHandler {
00605         public static function installHandler() {
00606                 set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
00607         }
00608 
00612         protected static function report( Exception $e ) {
00613                 global $wgShowExceptionDetails;
00614 
00615                 $cmdLine = MWException::isCommandLine();
00616 
00617                 if ( $e instanceof MWException ) {
00618                         try {
00619                                 // Try and show the exception prettily, with the normal skin infrastructure
00620                                 $e->report();
00621                         } catch ( Exception $e2 ) {
00622                                 // Exception occurred from within exception handler
00623                                 // Show a simpler error message for the original exception,
00624                                 // don't try to invoke report()
00625                                 $message = "MediaWiki internal error.\n\n";
00626 
00627                                 if ( $wgShowExceptionDetails ) {
00628                                         $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
00629                                                 'Exception caught inside exception handler: ' . $e2->__toString();
00630                                 } else {
00631                                         $message .= "Exception caught inside exception handler.\n\n" .
00632                                                 "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
00633                                                 "to show detailed debugging information.";
00634                                 }
00635 
00636                                 $message .= "\n";
00637 
00638                                 if ( $cmdLine ) {
00639                                         self::printError( $message );
00640                                 } else {
00641                                         echo nl2br( htmlspecialchars( $message ) ) . "\n";
00642                                 }
00643                         }
00644                 } else {
00645                         $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
00646                                 $e->__toString() . "\n";
00647 
00648                         if ( $wgShowExceptionDetails ) {
00649                                 $message .= "\n" . $e->getTraceAsString() . "\n";
00650                         }
00651 
00652                         if ( $cmdLine ) {
00653                                 self::printError( $message );
00654                         } else {
00655                                 echo nl2br( htmlspecialchars( $message ) ) . "\n";
00656                         }
00657                 }
00658         }
00659 
00666         public static function printError( $message ) {
00667                 # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
00668                 #      Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
00669                 if ( defined( 'STDERR' ) ) {
00670                         fwrite( STDERR, $message );
00671                 } else {
00672                         echo( $message );
00673                 }
00674         }
00675 
00687         public static function handle( $e ) {
00688                 global $wgFullyInitialised;
00689 
00690                 self::report( $e );
00691 
00692                 // Final cleanup
00693                 if ( $wgFullyInitialised ) {
00694                         try {
00695                                 wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
00696                         } catch ( Exception $e ) {}
00697                 }
00698 
00699                 // Exit value should be nonzero for the benefit of shell jobs
00700                 exit( 1 );
00701         }
00702 }