Completed
Branch master (939199)
by
unknown
39:35
created

includes/exception/MWException.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 */
20
21
/**
22
 * MediaWiki exception
23
 *
24
 * @ingroup Exception
25
 */
26
class MWException extends Exception {
27
	/**
28
	 * Should the exception use $wgOut to output the error?
29
	 *
30
	 * @return bool
31
	 */
32
	public function useOutputPage() {
33
		return $this->useMessageCache() &&
34
		!empty( $GLOBALS['wgFullyInitialised'] ) &&
35
		!empty( $GLOBALS['wgOut'] ) &&
36
		!defined( 'MEDIAWIKI_INSTALL' );
37
	}
38
39
	/**
40
	 * Whether to log this exception in the exception debug log.
41
	 *
42
	 * @since 1.23
43
	 * @return bool
44
	 */
45
	public function isLoggable() {
46
		return true;
47
	}
48
49
	/**
50
	 * Can the extension use the Message class/wfMessage to get i18n-ed messages?
51
	 *
52
	 * @return bool
53
	 */
54
	public function useMessageCache() {
55
		global $wgLang;
56
57 View Code Duplication
		foreach ( $this->getTrace() as $frame ) {
58
			if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
59
				return false;
60
			}
61
		}
62
63
		return $wgLang instanceof Language;
64
	}
65
66
	/**
67
	 * Run hook to allow extensions to modify the text of the exception
68
	 *
69
	 * @param string $name Class name of the exception
70
	 * @param array $args Arguments to pass to the callback functions
71
	 * @return string|null String to output or null if any hook has been called
72
	 */
73
	public function runHooks( $name, $args = [] ) {
74
		return MWExceptionRenderer::runHooks( $this, $name, $args );
75
	}
76
77
	/**
78
	 * Get a message from i18n
79
	 *
80
	 * @param string $key Message name
81
	 * @param string $fallback Default message if the message cache can't be
82
	 *                  called by the exception
83
	 * The function also has other parameters that are arguments for the message
84
	 * @return string Message with arguments replaced
85
	 */
86 View Code Duplication
	public function msg( $key, $fallback /*[, params...] */ ) {
87
		$args = array_slice( func_get_args(), 2 );
88
89
		if ( $this->useMessageCache() ) {
90
			try {
91
				return wfMessage( $key, $args )->text();
92
			} catch ( Exception $e ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
93
			}
94
		}
95
		return wfMsgReplaceArgs( $fallback, $args );
96
	}
97
98
	/**
99
	 * If $wgShowExceptionDetails is true, return a HTML message with a
100
	 * backtrace to the error, otherwise show a message to ask to set it to true
101
	 * to show that information.
102
	 *
103
	 * @return string Html to output
104
	 */
105
	public function getHTML() {
106
		global $wgShowExceptionDetails;
107
108
		if ( $wgShowExceptionDetails ) {
109
			return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
110
			'</p><p>Backtrace:</p><p>' .
111
			nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
112
			"</p>\n";
113
		} else {
114
			$logId = WebRequest::getRequestId();
115
			$type = get_class( $this );
116
			return "<div class=\"errorbox\">" .
117
			'[' . $logId . '] ' .
118
			gmdate( 'Y-m-d H:i:s' ) . ": " .
119
			$this->msg( "internalerror-fatal-exception",
120
				"Fatal exception of type $1",
121
				$type,
122
				$logId,
123
				MWExceptionHandler::getURL( $this )
124
			) . "</div>\n" .
125
			"<!-- Set \$wgShowExceptionDetails = true; " .
126
			"at the bottom of LocalSettings.php to show detailed " .
127
			"debugging information. -->";
128
		}
129
	}
130
131
	/**
132
	 * Get the text to display when reporting the error on the command line.
133
	 * If $wgShowExceptionDetails is true, return a text message with a
134
	 * backtrace to the error.
135
	 *
136
	 * @return string
137
	 */
138 View Code Duplication
	public function getText() {
139
		global $wgShowExceptionDetails;
140
141
		if ( $wgShowExceptionDetails ) {
142
			return MWExceptionHandler::getLogMessage( $this ) .
143
			"\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
144
		} else {
145
			return "Set \$wgShowExceptionDetails = true; " .
146
			"in LocalSettings.php to show detailed debugging information.\n";
147
		}
148
	}
149
150
	/**
151
	 * Return the title of the page when reporting this error in a HTTP response.
152
	 *
153
	 * @return string
154
	 */
155
	public function getPageTitle() {
156
		return $this->msg( 'internalerror', 'Internal error' );
157
	}
158
159
	/**
160
	 * Output the exception report using HTML.
161
	 */
162
	public function reportHTML() {
163
		global $wgOut, $wgSitename;
164
		if ( $this->useOutputPage() ) {
165
			$wgOut->prepareErrorPage( $this->getPageTitle() );
166
167
			$hookResult = $this->runHooks( get_class( $this ) );
168
			if ( $hookResult ) {
169
				$wgOut->addHTML( $hookResult );
170
			} else {
171
				$wgOut->addHTML( $this->getHTML() );
172
			}
173
174
			$wgOut->output();
175
		} else {
176
			self::header( 'Content-Type: text/html; charset=utf-8' );
177
			echo "<!DOCTYPE html>\n" .
178
				'<html><head>' .
179
				// Mimick OutputPage::setPageTitle behaviour
180
				'<title>' .
181
				htmlspecialchars( $this->msg( 'pagetitle', "$1 - $wgSitename", $this->getPageTitle() ) ) .
182
				'</title>' .
183
				'<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
184
				"</head><body>\n";
185
186
			$hookResult = $this->runHooks( get_class( $this ) . 'Raw' );
187
			if ( $hookResult ) {
188
				echo $hookResult;
189
			} else {
190
				echo $this->getHTML();
191
			}
192
193
			echo "</body></html>\n";
194
		}
195
	}
196
197
	/**
198
	 * Output a report about the exception and takes care of formatting.
199
	 * It will be either HTML or plain text based on isCommandLine().
200
	 */
201
	public function report() {
202
		global $wgMimeType;
203
204
		if ( defined( 'MW_API' ) ) {
205
			// Unhandled API exception, we can't be sure that format printer is alive
206
			self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
207
			wfHttpError( 500, 'Internal Server Error', $this->getText() );
208
		} elseif ( self::isCommandLine() ) {
209
			$message = $this->getText();
210
			// T17602: STDERR may not be available
211
			if ( defined( 'STDERR' ) ) {
212
				fwrite( STDERR, $message );
213
			} else {
214
				echo $message;
215
			}
216
		} else {
217
			self::statusHeader( 500 );
218
			self::header( "Content-Type: $wgMimeType; charset=utf-8" );
219
220
			$this->reportHTML();
221
		}
222
	}
223
224
	/**
225
	 * Check whether we are in command line mode or not to report the exception
226
	 * in the correct format.
227
	 *
228
	 * @return bool
229
	 */
230
	public static function isCommandLine() {
231
		return !empty( $GLOBALS['wgCommandLineMode'] );
232
	}
233
234
	/**
235
	 * Send a header, if we haven't already sent them. We shouldn't,
236
	 * but sometimes we might in a weird case like Export
237
	 * @param string $header
238
	 */
239
	private static function header( $header ) {
240
		if ( !headers_sent() ) {
241
			header( $header );
242
		}
243
	}
244
	private static function statusHeader( $code ) {
245
		if ( !headers_sent() ) {
246
			HttpStatus::header( $code );
247
		}
248
	}
249
}
250