Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
34.41% |
32 / 93 |
|
35.71% |
5 / 14 |
CRAP | |
0.00% |
0 / 1 |
MWException | |
34.41% |
32 / 93 |
|
35.71% |
5 / 14 |
360.21 | |
0.00% |
0 / 1 |
useOutputPage | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
isLoggable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
useMessageCache | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
4.25 | |||
msg | |
50.00% |
5 / 10 |
|
0.00% |
0 / 1 |
6.00 | |||
getHTML | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
getText | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getPageTitle | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
reportHTML | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
report | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
3.71 | |||
hasOverriddenHandler | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
writeToCommandLine | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
isCommandLine | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
header | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
statusHeader | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 |
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 | use MediaWiki\Html\Html; |
22 | use MediaWiki\Request\WebRequest; |
23 | |
24 | /** |
25 | * MediaWiki exception |
26 | * |
27 | * @newable |
28 | * @stable to extend |
29 | * |
30 | * @ingroup Exception |
31 | * @deprecated since 1.40, use native exceptions instead (either directly, or defining subclasses when appropriate) |
32 | */ |
33 | class MWException extends Exception { |
34 | /** |
35 | * Should the exception use $wgOut to output the error? |
36 | * |
37 | * @return bool |
38 | */ |
39 | private function useOutputPage() { |
40 | // NOTE: keep in sync with MWExceptionRenderer::useOutputPage |
41 | return $this->useMessageCache() && |
42 | !empty( $GLOBALS['wgFullyInitialised'] ) && |
43 | !empty( $GLOBALS['wgOut'] ) && |
44 | !defined( 'MEDIAWIKI_INSTALL' ) && |
45 | // Don't send a skinned HTTP 500 page to API clients. |
46 | !defined( 'MW_API' ); |
47 | } |
48 | |
49 | /** |
50 | * Whether to log this exception in the exception debug log. |
51 | * |
52 | * @stable to override |
53 | * |
54 | * @since 1.23 |
55 | * @return bool |
56 | */ |
57 | public function isLoggable() { |
58 | return true; |
59 | } |
60 | |
61 | /** |
62 | * Can the extension use the Message class/wfMessage to get i18n-ed messages? |
63 | * |
64 | * @stable to override |
65 | * |
66 | * @return bool |
67 | */ |
68 | public function useMessageCache() { |
69 | foreach ( $this->getTrace() as $frame ) { |
70 | if ( isset( $frame['class'] ) && $frame['class'] === LocalisationCache::class ) { |
71 | return false; |
72 | } |
73 | } |
74 | return true; |
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 | * @param mixed ...$params To pass to wfMessage() |
84 | * @return string Message with arguments replaced |
85 | */ |
86 | public function msg( $key, $fallback, ...$params ) { |
87 | // NOTE: Keep logic in sync with MWExceptionRenderer::msg. |
88 | $res = false; |
89 | if ( $this->useMessageCache() ) { |
90 | try { |
91 | $res = wfMessage( $key, ...$params )->text(); |
92 | } catch ( Exception $e ) { |
93 | } |
94 | } |
95 | if ( $res === false ) { |
96 | // Fallback to static message text and generic sitename. |
97 | // Avoid live config as this must work before Setup/MediaWikiServices finish. |
98 | $res = wfMsgReplaceArgs( $fallback, $params ); |
99 | $res = strtr( $res, [ |
100 | '{{SITENAME}}' => 'MediaWiki', |
101 | ] ); |
102 | } |
103 | return $res; |
104 | } |
105 | |
106 | /** |
107 | * Format an HTML message for the current exception object. |
108 | * |
109 | * @deprecated since 1.42 Provide the error message when constructing the Exception instead. |
110 | * If you need a whole custom error page, use ErrorPageError instead. |
111 | * @return string HTML to output |
112 | */ |
113 | public function getHTML() { |
114 | wfDeprecated( __METHOD__, '1.42' ); |
115 | if ( MWExceptionRenderer::shouldShowExceptionDetails() ) { |
116 | return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) . |
117 | '</p><p>Backtrace:</p><p>' . |
118 | nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) . |
119 | "</p>\n"; |
120 | } else { |
121 | $logId = WebRequest::getRequestId(); |
122 | $type = static::class; |
123 | return Html::errorBox( |
124 | htmlspecialchars( |
125 | '[' . $logId . '] ' . |
126 | gmdate( 'Y-m-d H:i:s' ) . ": " . |
127 | $this->msg( "internalerror-fatal-exception", |
128 | "Fatal exception of type $1", |
129 | $type, |
130 | $logId, |
131 | MWExceptionHandler::getURL() |
132 | ) |
133 | ) ) . |
134 | "<!-- Set \$wgShowExceptionDetails = true; " . |
135 | "at the bottom of LocalSettings.php to show detailed " . |
136 | "debugging information. -->"; |
137 | } |
138 | } |
139 | |
140 | /** |
141 | * Format plain text message for the current exception object. |
142 | * |
143 | * @deprecated since 1.42 Provide the error message when constructing the Exception instead. |
144 | * If you need a whole custom error page, use ErrorPageError instead. |
145 | * @return string |
146 | */ |
147 | public function getText() { |
148 | wfDeprecated( __METHOD__, '1.42' ); |
149 | if ( MWExceptionRenderer::shouldShowExceptionDetails() ) { |
150 | return MWExceptionHandler::getLogMessage( $this ) . |
151 | "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n"; |
152 | } else { |
153 | return "Set \$wgShowExceptionDetails = true; " . |
154 | "in LocalSettings.php to show detailed debugging information.\n"; |
155 | } |
156 | } |
157 | |
158 | /** |
159 | * Return the title of the page when reporting this error in a HTTP response. |
160 | * |
161 | * @deprecated since 1.42 Provide the error message when constructing the Exception instead. |
162 | * If you need a whole custom error page, use ErrorPageError instead. |
163 | * @return string |
164 | */ |
165 | public function getPageTitle() { |
166 | wfDeprecated( __METHOD__, '1.42' ); |
167 | return $this->msg( 'internalerror', 'Internal error' ); |
168 | } |
169 | |
170 | /** |
171 | * Output the exception report using HTML. |
172 | * @deprecated since 1.42 Provide the error message when constructing the Exception instead. |
173 | * If you need a whole custom error page, use ErrorPageError instead. |
174 | */ |
175 | public function reportHTML() { |
176 | wfDeprecated( __METHOD__, '1.42' ); |
177 | global $wgOut; |
178 | |
179 | if ( $this->useOutputPage() ) { |
180 | $wgOut->prepareErrorPage(); |
181 | $wgOut->setPageTitle( $this->getPageTitle() ); |
182 | // Manually set the html title, since sometimes |
183 | // {{SITENAME}} does not get replaced for exceptions |
184 | // happening inside message rendering. |
185 | $wgOut->setHTMLTitle( |
186 | $this->msg( 'pagetitle', '$1 - MediaWiki', $this->getPageTitle() ) |
187 | ); |
188 | |
189 | $wgOut->addHTML( $this->getHTML() ); |
190 | // Content-Type is set by OutputPage::output |
191 | $wgOut->output(); |
192 | } else { |
193 | self::header( 'Content-Type: text/html; charset=UTF-8' ); |
194 | echo "<!DOCTYPE html>\n" . |
195 | '<html><head>' . |
196 | // Mimic OutputPage::setPageTitle behaviour |
197 | '<title>' . |
198 | htmlspecialchars( $this->msg( 'pagetitle', '$1 - MediaWiki', $this->getPageTitle() ) ) . |
199 | '</title>' . |
200 | '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' . |
201 | "</head><body>\n"; |
202 | |
203 | echo $this->getHTML(); |
204 | |
205 | echo "</body></html>\n"; |
206 | } |
207 | } |
208 | |
209 | /** |
210 | * Output a report about the exception and takes care of formatting. |
211 | * It will be either HTML or plain text based on isCommandLine(). |
212 | * |
213 | * @stable to override |
214 | */ |
215 | public function report() { |
216 | if ( defined( 'MW_API' ) ) { |
217 | self::header( 'MediaWiki-API-Error: internal_api_error_' . static::class ); |
218 | } |
219 | |
220 | if ( self::isCommandLine() ) { |
221 | $message = $this->getText(); |
222 | $this->writeToCommandLine( $message ); |
223 | } else { |
224 | self::statusHeader( 500 ); |
225 | $this->reportHTML(); |
226 | } |
227 | } |
228 | |
229 | /** |
230 | * @internal |
231 | */ |
232 | final public function hasOverriddenHandler(): bool { |
233 | // No deprecation warning - report() is not deprecated, only the other methods |
234 | if ( MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'report' ) ) { |
235 | return true; |
236 | } |
237 | |
238 | // Check them all here to avoid short-circuiting and report all deprecations, |
239 | // even if the function is not called in this request |
240 | $detectedOverrides = [ |
241 | 'getHTML' => MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'getHTML', '1.42' ), |
242 | 'getText' => MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'getText', '1.42' ), |
243 | 'getPageTitle' => MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'getPageTitle', '1.42' ), |
244 | 'reportHTML' => MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'reportHTML', '1.42' ), |
245 | ]; |
246 | |
247 | return (bool)array_filter( $detectedOverrides ); |
248 | } |
249 | |
250 | /** |
251 | * Write a message to stderr falling back to stdout if stderr unavailable |
252 | * |
253 | * @param string $message |
254 | * @suppress SecurityCheck-XSS |
255 | */ |
256 | private function writeToCommandLine( $message ) { |
257 | // T17602: STDERR may not be available |
258 | if ( !defined( 'MW_PHPUNIT_TEST' ) && defined( 'STDERR' ) ) { |
259 | fwrite( STDERR, $message ); |
260 | } else { |
261 | echo $message; |
262 | } |
263 | } |
264 | |
265 | /** |
266 | * Check whether we are in command line mode or not to report the exception |
267 | * in the correct format. |
268 | * |
269 | * @return bool |
270 | */ |
271 | public static function isCommandLine() { |
272 | return MW_ENTRY_POINT === 'cli'; |
273 | } |
274 | |
275 | /** |
276 | * Send a header, if we haven't already sent them. We shouldn't, |
277 | * but sometimes we might in a weird case like Export |
278 | * @param string $header |
279 | */ |
280 | private static function header( $header ) { |
281 | if ( !headers_sent() ) { |
282 | header( $header ); |
283 | } |
284 | } |
285 | |
286 | private static function statusHeader( $code ) { |
287 | if ( !headers_sent() ) { |
288 | HttpStatus::header( $code ); |
289 | } |
290 | } |
291 | } |