PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/Exception.php

https://github.com/daevid/MWFork
PHP | 500 lines | 303 code | 66 blank | 131 comment | 50 complexity | eccb1d74b47aff901de5ed5bb3369cf4 MD5 | raw file
  1. <?php
  2. /**
  3. * Exception class and handler
  4. *
  5. * @file
  6. */
  7. /**
  8. * @defgroup Exception Exception
  9. */
  10. /**
  11. * MediaWiki exception
  12. *
  13. * @ingroup Exception
  14. */
  15. class MWException extends Exception {
  16. /**
  17. * Should the exception use $wgOut to output the error ?
  18. * @return bool
  19. */
  20. function useOutputPage() {
  21. return $this->useMessageCache() &&
  22. !empty( $GLOBALS['wgFullyInitialised'] ) &&
  23. !empty( $GLOBALS['wgOut'] ) &&
  24. !empty( $GLOBALS['wgTitle'] );
  25. }
  26. /**
  27. * Can the extension use wfMsg() to get i18n messages ?
  28. * @return bool
  29. */
  30. function useMessageCache() {
  31. global $wgLang;
  32. foreach ( $this->getTrace() as $frame ) {
  33. if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
  34. return false;
  35. }
  36. }
  37. return $wgLang instanceof Language;
  38. }
  39. /**
  40. * Run hook to allow extensions to modify the text of the exception
  41. *
  42. * @param $name String: class name of the exception
  43. * @param $args Array: arguments to pass to the callback functions
  44. * @return Mixed: string to output or null if any hook has been called
  45. */
  46. function runHooks( $name, $args = array() ) {
  47. global $wgExceptionHooks;
  48. if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
  49. return; // Just silently ignore
  50. }
  51. if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
  52. return;
  53. }
  54. $hooks = $wgExceptionHooks[ $name ];
  55. $callargs = array_merge( array( $this ), $args );
  56. foreach ( $hooks as $hook ) {
  57. if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
  58. $result = call_user_func_array( $hook, $callargs );
  59. } else {
  60. $result = null;
  61. }
  62. if ( is_string( $result ) )
  63. return $result;
  64. }
  65. }
  66. /**
  67. * Get a message from i18n
  68. *
  69. * @param $key String: message name
  70. * @param $fallback String: default message if the message cache can't be
  71. * called by the exception
  72. * The function also has other parameters that are arguments for the message
  73. * @return String message with arguments replaced
  74. */
  75. function msg( $key, $fallback /*[, params...] */ ) {
  76. $args = array_slice( func_get_args(), 2 );
  77. if ( $this->useMessageCache() ) {
  78. return wfMsgNoTrans( $key, $args );
  79. } else {
  80. return wfMsgReplaceArgs( $fallback, $args );
  81. }
  82. }
  83. /**
  84. * If $wgShowExceptionDetails is true, return a HTML message with a
  85. * backtrace to the error, otherwise show a message to ask to set it to true
  86. * to show that information.
  87. *
  88. * @return String html to output
  89. */
  90. function getHTML() {
  91. global $wgShowExceptionDetails;
  92. if ( $wgShowExceptionDetails ) {
  93. return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
  94. '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
  95. "</p>\n";
  96. } else {
  97. return "<p>Set <b><tt>\$wgShowExceptionDetails = true;</tt></b> " .
  98. "at the bottom of LocalSettings.php to show detailed " .
  99. "debugging information.</p>";
  100. }
  101. }
  102. /**
  103. * If $wgShowExceptionDetails is true, return a text message with a
  104. * backtrace to the error.
  105. */
  106. function getText() {
  107. global $wgShowExceptionDetails;
  108. if ( $wgShowExceptionDetails ) {
  109. return $this->getMessage() .
  110. "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
  111. } else {
  112. return "Set \$wgShowExceptionDetails = true; " .
  113. "in LocalSettings.php to show detailed debugging information.\n";
  114. }
  115. }
  116. /* Return titles of this error page */
  117. function getPageTitle() {
  118. global $wgSitename;
  119. return $this->msg( 'internalerror', "$wgSitename error" );
  120. }
  121. /**
  122. * Return the requested URL and point to file and line number from which the
  123. * exception occured
  124. *
  125. * @return String
  126. */
  127. function getLogMessage() {
  128. global $wgRequest;
  129. $file = $this->getFile();
  130. $line = $this->getLine();
  131. $message = $this->getMessage();
  132. if ( isset( $wgRequest ) ) {
  133. $url = $wgRequest->getRequestURL();
  134. if ( !$url ) {
  135. $url = '[no URL]';
  136. }
  137. } else {
  138. $url = '[no req]';
  139. }
  140. return "$url Exception from line $line of $file: $message";
  141. }
  142. /** Output the exception report using HTML */
  143. function reportHTML() {
  144. global $wgOut;
  145. if ( $this->useOutputPage() ) {
  146. $wgOut->setPageTitle( $this->getPageTitle() );
  147. $wgOut->setRobotPolicy( "noindex,nofollow" );
  148. $wgOut->setArticleRelated( false );
  149. $wgOut->enableClientCache( false );
  150. $wgOut->redirect( '' );
  151. $wgOut->clearHTML();
  152. $hookResult = $this->runHooks( get_class( $this ) );
  153. if ( $hookResult ) {
  154. $wgOut->addHTML( $hookResult );
  155. } else {
  156. $wgOut->addHTML( $this->getHTML() );
  157. }
  158. $wgOut->output();
  159. } else {
  160. $hookResult = $this->runHooks( get_class( $this ) . "Raw" );
  161. if ( $hookResult ) {
  162. die( $hookResult );
  163. }
  164. echo $this->getHTML();
  165. die(1);
  166. }
  167. }
  168. /**
  169. * Output a report about the exception and takes care of formatting.
  170. * It will be either HTML or plain text based on isCommandLine().
  171. */
  172. function report() {
  173. $log = $this->getLogMessage();
  174. if ( $log ) {
  175. wfDebugLog( 'exception', $log );
  176. }
  177. if ( self::isCommandLine() ) {
  178. MWExceptionHandler::printError( $this->getText() );
  179. } else {
  180. $this->reportHTML();
  181. }
  182. }
  183. static function isCommandLine() {
  184. return !empty( $GLOBALS['wgCommandLineMode'] );
  185. }
  186. }
  187. /**
  188. * Exception class which takes an HTML error message, and does not
  189. * produce a backtrace. Replacement for OutputPage::fatalError().
  190. * @ingroup Exception
  191. */
  192. class FatalError extends MWException {
  193. function getHTML() {
  194. return $this->getMessage();
  195. }
  196. function getText() {
  197. return $this->getMessage();
  198. }
  199. }
  200. /**
  201. * An error page which can definitely be safely rendered using the OutputPage
  202. * @ingroup Exception
  203. */
  204. class ErrorPageError extends MWException {
  205. public $title, $msg, $params;
  206. /**
  207. * Note: these arguments are keys into wfMsg(), not text!
  208. */
  209. function __construct( $title, $msg, $params = null ) {
  210. $this->title = $title;
  211. $this->msg = $msg;
  212. $this->params = $params;
  213. if( $msg instanceof Message ){
  214. parent::__construct( $msg );
  215. } else {
  216. parent::__construct( wfMsg( $msg ) );
  217. }
  218. }
  219. function report() {
  220. global $wgOut;
  221. if ( $wgOut->getTitle() ) {
  222. $wgOut->debug( 'Original title: ' . $wgOut->getTitle()->getPrefixedText() . "\n" );
  223. }
  224. $wgOut->setPageTitle( wfMsg( $this->title ) );
  225. $wgOut->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
  226. $wgOut->setRobotPolicy( 'noindex,nofollow' );
  227. $wgOut->setArticleRelated( false );
  228. $wgOut->enableClientCache( false );
  229. $wgOut->mRedirect = '';
  230. $wgOut->clearHTML();
  231. if( $this->msg instanceof Message ){
  232. $wgOut->addHTML( $this->msg->parse() );
  233. } else {
  234. $wgOut->addWikiMsgArray( $this->msg, $this->params );
  235. }
  236. $wgOut->returnToMain();
  237. $wgOut->output();
  238. }
  239. }
  240. /**
  241. * Show an error when a user tries to do something they do not have the necessary
  242. * permissions for.
  243. * @ingroup Exception
  244. */
  245. class PermissionsError extends ErrorPageError {
  246. public $permission;
  247. function __construct( $permission ) {
  248. global $wgLang;
  249. $this->permission = $permission;
  250. $groups = array_map(
  251. array( 'User', 'makeGroupLinkWiki' ),
  252. User::getGroupsWithPermission( $this->permission )
  253. );
  254. if( $groups ) {
  255. parent::__construct(
  256. 'badaccess',
  257. 'badaccess-groups',
  258. array(
  259. $wgLang->commaList( $groups ),
  260. count( $groups )
  261. )
  262. );
  263. } else {
  264. parent::__construct(
  265. 'badaccess',
  266. 'badaccess-group0'
  267. );
  268. }
  269. }
  270. }
  271. /**
  272. * Show an error when the wiki is locked/read-only and the user tries to do
  273. * something that requires write access
  274. * @ingroup Exception
  275. */
  276. class ReadOnlyError extends ErrorPageError {
  277. public function __construct(){
  278. parent::__construct(
  279. 'readonly',
  280. 'readonlytext',
  281. wfReadOnlyReason()
  282. );
  283. }
  284. }
  285. /**
  286. * Show an error when the user hits a rate limit
  287. * @ingroup Exception
  288. */
  289. class ThrottledError extends ErrorPageError {
  290. public function __construct(){
  291. parent::__construct(
  292. 'actionthrottled',
  293. 'actionthrottledtext'
  294. );
  295. }
  296. public function report(){
  297. global $wgOut;
  298. $wgOut->setStatusCode( 503 );
  299. return parent::report();
  300. }
  301. }
  302. /**
  303. * Show an error when the user tries to do something whilst blocked
  304. * @ingroup Exception
  305. */
  306. class UserBlockedError extends ErrorPageError {
  307. public function __construct( Block $block ){
  308. global $wgLang;
  309. $blockerUserpage = $block->getBlocker()->getUserPage();
  310. $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
  311. $reason = $block->mReason;
  312. if( $reason == '' ) {
  313. $reason = wfMsg( 'blockednoreason' );
  314. }
  315. /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
  316. * This could be a username, an IP range, or a single IP. */
  317. $intended = $block->getTarget();
  318. parent::__construct(
  319. 'blockedtitle',
  320. $block->mAuto ? 'autoblockedtext' : 'blockedtext',
  321. array(
  322. $link,
  323. $reason,
  324. wfGetIP(),
  325. $block->getBlocker()->getName(),
  326. $block->getId(),
  327. $wgLang->formatExpiry( $block->mExpiry ),
  328. $intended,
  329. $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true )
  330. )
  331. );
  332. }
  333. }
  334. /**
  335. * Handler class for MWExceptions
  336. * @ingroup Exception
  337. */
  338. class MWExceptionHandler {
  339. /**
  340. * Install an exception handler for MediaWiki exception types.
  341. */
  342. public static function installHandler() {
  343. set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
  344. }
  345. /**
  346. * Report an exception to the user
  347. */
  348. protected static function report( Exception $e ) {
  349. global $wgShowExceptionDetails;
  350. $cmdLine = MWException::isCommandLine();
  351. if ( $e instanceof MWException ) {
  352. try {
  353. // Try and show the exception prettily, with the normal skin infrastructure
  354. $e->report();
  355. } catch ( Exception $e2 ) {
  356. // Exception occurred from within exception handler
  357. // Show a simpler error message for the original exception,
  358. // don't try to invoke report()
  359. $message = "MediaWiki internal error.\n\n";
  360. if ( $wgShowExceptionDetails ) {
  361. $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
  362. 'Exception caught inside exception handler: ' . $e2->__toString();
  363. } else {
  364. $message .= "Exception caught inside exception handler.\n\n" .
  365. "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
  366. "to show detailed debugging information.";
  367. }
  368. $message .= "\n";
  369. if ( $cmdLine ) {
  370. self::printError( $message );
  371. } else {
  372. self::escapeEchoAndDie( $message );
  373. }
  374. }
  375. } else {
  376. $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
  377. $e->__toString() . "\n";
  378. if ( $wgShowExceptionDetails ) {
  379. $message .= "\n" . $e->getTraceAsString() . "\n";
  380. }
  381. if ( $cmdLine ) {
  382. self::printError( $message );
  383. } else {
  384. self::escapeEchoAndDie( $message );
  385. }
  386. }
  387. }
  388. /**
  389. * Print a message, if possible to STDERR.
  390. * Use this in command line mode only (see isCommandLine)
  391. * @param $message String Failure text
  392. */
  393. public static function printError( $message ) {
  394. # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
  395. # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
  396. if ( defined( 'STDERR' ) ) {
  397. fwrite( STDERR, $message );
  398. } else {
  399. echo( $message );
  400. }
  401. }
  402. /**
  403. * Print a message after escaping it and converting newlines to <br>
  404. * Use this for non-command line failures
  405. * @param $message String Failure text
  406. */
  407. private static function escapeEchoAndDie( $message ) {
  408. echo nl2br( htmlspecialchars( $message ) ) . "\n";
  409. die(1);
  410. }
  411. /**
  412. * Exception handler which simulates the appropriate catch() handling:
  413. *
  414. * try {
  415. * ...
  416. * } catch ( MWException $e ) {
  417. * $e->report();
  418. * } catch ( Exception $e ) {
  419. * echo $e->__toString();
  420. * }
  421. */
  422. public static function handle( $e ) {
  423. global $wgFullyInitialised;
  424. self::report( $e );
  425. // Final cleanup
  426. if ( $wgFullyInitialised ) {
  427. try {
  428. wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
  429. } catch ( Exception $e ) {}
  430. }
  431. // Exit value should be nonzero for the benefit of shell jobs
  432. exit( 1 );
  433. }
  434. }