3 * FusionForge extradebug feature
5 * Copyright 2011, Fusionforge Team
6 * http://fusionforge.org
8 * This file is part of FusionForge. FusionForge is free software;
9 * you can redistribute it and/or modify it under the terms of the
10 * GNU General Public License as published by the Free Software
11 * Foundation; either version 2 of the Licence, or (at your option)
14 * FusionForge is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 // initialise globals used by the debugging code
25 $sysdebug_dberrors = forge_get_config('sysdebug_dberrors');
26 $sysdebug_dbquery = forge_get_config('sysdebug_dbquery');
27 $sysdebug_ignored = forge_get_config('sysdebug_ignored');
28 if (!isset($ffErrors)) {
32 // error handler function
33 function ffErrorHandler($errno, $errstr, $errfile, $errline) {
34 global $ffErrors, $sysdebug_ignored, $sysdebug__aborted;
35 /* Debian-specific MediaWiki patch */
36 global $wf__warnings_suppressed;
38 if ($sysdebug__aborted) {
39 /* inside the exception handler, ignore everything */
43 if (isset($wf__warnings_suppressed) && $wf__warnings_suppressed) {
45 * MediaWiki makes use of surrounding sloppily written,
46 * unclean, unsafe code with wfSuppressWarnings(); and
47 * wfRestoreWarnings(); calls, we never want to see them
52 if (!$sysdebug_ignored && error_reporting() == 0) {
53 /* prepended @ to statement => ignore */
57 $msg = "[$errno] $errstr ($errfile at $errline)";
59 // Display messages only once.
60 foreach ($ffErrors as $m) {
61 if ($m['message'] == $msg) {
83 case E_USER_DEPRECATED:
93 if (forge_get_config('sysdebug_backtraces')) {
95 '<pre style="font-weight:normal; font-size:90%; color:#000066; line-height:100%;">' .
96 htmlentities(debug_string_backtrace()) . "</pre>";
103 /* Don't execute PHP internal error handler */
107 // output buffer finaliser function
108 function ffOutputHandler($buffer) {
109 global $ffErrors, $sysdebug_enable, $sysdebug__aborted,
110 $sysdebug_lazymode_on, $sysdebug_doframe, $gfcommon,
111 $sysDTDs, $sysXMLNSs, $HTML;
113 if ($sysdebug__aborted) {
114 /* called from exception handler, discard */
115 $p = strrpos($buffer, "\r\n");
116 return (($p === false) ? "" : substr($buffer, $p + 2));
119 if (!getenv('SERVER_SOFTWARE')) {
123 /* in case we’re aborted */
124 if (!$sysdebug_enable) {
128 /* if content-type != text/html* assume abortion */
129 if ($sysdebug_lazymode_on) {
130 $thdr = 'content-type:';
131 $tstr = 'content-type: text/html';
132 foreach (headers_list() as $h) {
133 if (strncasecmp($h, $thdr, strlen($thdr))) {
136 if (strncasecmp($h, $tstr, strlen($tstr))) {
137 /* application/something, maybe */
143 /* stop calling ffErrorHandler */
144 restore_error_handler();
146 $dtdpath = $gfcommon . 'include/';
147 // this is, sadly, necessary (especially in ff-plugin-mediawiki)
148 $pre_tag = "<pre style=\"margin:0; padding:0; border:0; line-height:125%;\">";
150 $divstring = "\n\n" . '<script type="text/javascript">//<![CDATA[
151 function toggle_ffErrors() {
152 var errorsblock = document.getElementById("ffErrorsBlock");
153 var errorsgroup = document.getElementById("ffErrors");
154 if (errorsblock.style.display == "none") {
155 errorsblock.style.display = "block";
156 errorsgroup.style.right = "10px";
158 errorsblock.style.display = "none";
159 errorsgroup.style.right = "300px";
161 }' . "\n//]]></script>\n<div id=\"ffErrors\">\n" .
162 '<a href="javascript:toggle_ffErrors();">Click to toggle</a>' .
163 "\n<div id=\"ffErrorsBlock\">";
165 if (isset($HTML->doctype)) {
166 $doctype = $HTML->doctype;
168 $doctype = 'transitional';
171 if ($sysdebug_doframe) {
172 $initial = '<?xml version="1.0" encoding="utf-8"?>' .
173 $sysDTDs[$doctype]['doctype'] .
174 '<html xml:lang="en" ' . $sysXMLNSs .
175 "><head><title>AJAX frame</title></head><body>\n";
176 $bufferstrip = strlen($initial);
177 $buffer = $initial . $buffer . '</body></html>';
180 /* cut off </body></html> (hopefully only) at the end */
181 $buffer = rtrim($buffer); /* spaces, newlines, etc. */
182 $bufend = array(false, substr($buffer, -100));
183 if (substr($buffer, -strlen("</html>")) != "</html>") {
187 'message' => htmlentities("does not end with </html> tag"),
189 $buffer = str_ireplace("</html>", "", $buffer);
191 $buffer = substr($buffer, 0, -strlen("</html>"));
193 $buffer = rtrim($buffer); /* spaces, newlines, etc. */
194 if (substr($buffer, -strlen("</body>")) != "</body>") {
198 'message' => htmlentities("does not end with </body> tag"),
200 $buffer = str_ireplace("</body>", "", $buffer);
202 $buffer = substr($buffer, 0, -strlen("</body>"));
204 $buffer = rtrim($buffer); /* spaces, newlines, etc. */
209 'message' => "The output has ended thus: " .
210 htmlentities($bufend[1]),
214 /* append errors, if any */
216 foreach ($ffErrors as $msg) {
218 $buffer .= $divstring;
221 $buffer .= "\n <div class=\"" . $msg['type'] . '">' .
222 $msg['message'] . "</div>";
225 /* generate buffer for checking */
226 $cbuf = str_ireplace('http://www.w3.org/TR/xhtml1/DTD/',
227 'file://' . $dtdpath, str_ireplace('http://evolvis.org/DTD/',
228 'file://' . $dtdpath, $buffer));
230 $cbuf .= "\n</div></div>";
232 $cbuf .= "\n</body></html>\n";
234 /* now check XHTML validity… two means */
238 if (forge_get_config('sysdebug_xmlstarlet')) {
239 /* xmlstarlet (well-formed, DTD and DOCTYPE, encoding */
241 0 => array("pipe", "r"),
242 1 => array("pipe", "w"),
243 2 => array("pipe", "w"),
245 $xmlstarlet = proc_open("xmlstarlet val -d " .
246 escapeshellarg($dtdpath . $sysDTDs[$doctype]['dtdfile']) .
247 " -e -", $dspec, $pipes);
249 if (is_resource($xmlstarlet)) {
250 fwrite($pipes[0], $cbuf);
252 $sout = stream_get_contents($pipes[1]);
253 $serr = stream_get_contents($pipes[2]);
256 $rv = proc_close($xmlstarlet);
257 /* work around Debian #627158 */
258 $serr = join("\n", preg_grep(
259 '/^-:[0-9]*: Entity'." 'nbsp' ".'not defined$/',
260 explode("\n", $serr), PREG_GREP_INVERT));
263 'msg' => "could not run xmlstarlet",
268 'msg' => "xmlstarlet found that this document is not valid (errorlevel $rv)!",
269 'extra' => $pre_tag .
270 htmlspecialchars(trim($serr .
271 "\n\n" . $sout)) . "</pre>",
278 /* append XHTML source code, if validation failed */
280 $vbuf = "<ol><li>" . $pre_tag .
281 join(" </pre></li>\n<li>" . $pre_tag,
282 explode("\n", htmlentities(rtrim($cbuf)))) .
285 'msg' => "Since XHTML validation failed, here’s the checked document for you to look at:",
291 /* append error messages from the validators */
292 foreach ($valck as $msg) {
294 $buffer .= $divstring;
297 if (!isset($msg['type']) || !$msg['type']) {
298 $msg['type'] = 'unknown';
300 $buffer .= "\n <div class=\"" . $msg['type'] . '">' .
302 if (isset($msg['extra'])) {
303 $buffer .= "\n <div style=\"font-weight:normal; font-size:90%; color:#333333;\">" .
304 $msg['extra'] . "</div>\n ";
309 /* return final buffer */
311 $buffer .= "\n</div></div>";
313 if ($sysdebug_doframe) {
314 return substr($buffer, $bufferstrip);
316 return $buffer . "\n</body></html>\n";
320 // exception handler function
321 function ffExceptionHandler($e) {
322 global $sysdebug__aborted;
324 /* drop output buffers and error handler */
325 $sysdebug__aborted = true;
326 while (ob_get_length() > 0 && ob_end_clean()) {
329 restore_error_handler();
331 /* issue exception information */
332 header('HTTP/1.0 500 Exception not handled');
333 header('Content-type: text/plain');
334 echo "\r\nUncaught exception:\n" . str_replace("\r", "",
335 $e->getMessage() . "\n\nBacktrace:\n" . $e->getTraceAsString()) .
340 if (forge_get_config('sysdebug_phphandler')) {
341 // set to the user defined error handler
342 set_error_handler("ffErrorHandler");
345 set_exception_handler("ffExceptionHandler");
347 $sysdebug_lazymode_on = false;
348 $sysdebug_doframe = false;
349 $sysdebug__aborted = false;
350 ob_start("ffOutputHandler", 0, false);
352 function sysdebug_ajaxbody($enable=true) {
353 global $sysdebug_doframe;
355 $sysdebug_doframe = $enable;
358 function sysdebug_off($hdr=false, $replace=true, $resp=false) {
359 global $ffErrors, $sysdebug_enable;
361 if ($sysdebug_enable) {
362 $sysdebug_enable = false;
363 $buf = @ob_get_flush();
365 if ($buf === false) {
369 /* if we had any old errors, log them */
371 foreach ($ffErrors as $msg) {
372 $olderrors .= "\n(" . $msg['type'] . ") " .
376 if (!forge_get_config('sysdebug_backtraces')) {
377 $olderrors .= "\n" . debug_string_backtrace();
379 $olderrors = rtrim($olderrors);
381 foreach (explode("\n",
382 "sysdebug_off: previous errors found:" . $olderrors)
384 error_log($pfx . $olderrorline);
385 /* followup lines get indented */
393 if ($hdr !== false) {
394 if ($resp === false) {
395 header($hdr, $replace);
397 header($hdr, $replace, $resp);
404 function sysdebug_lazymode($enable) {
405 global $sysdebug_lazymode_on;
407 $sysdebug_lazymode_on = $enable ? true : false;
410 function ffDebug($type, $intro, $pretext=false) {
418 $text .= htmlentities($intro);
421 $text .= '<pre style="font-weight:normal; font-size:90%; color:#000066;">' .
422 htmlentities($pretext) . "</pre>";