Version: current

      1 <?php
      2 /**
      3  * Supports debugging and core error handling.
      4  *
      5  * Attaches custom methods to the default error handling hooks
      6  * in PHP. Currently, two levels of error are supported:
      7  *
      8  * - Notice
      9  * - Warning
     10  * - Error
     11  *
     12  * Uncaught exceptions are currently passed to the debug
     13  * reporter as standard PHP errors.
     14  *
     15  * Errors handled by this class are passed along to {@link SS_Log}.
     16  * For configuration information, see the {@link SS_Log}
     17  * class documentation.
     18  *
     19  * @todo add support for user defined config: Debug::die_on_notice(true | false)
     20  * @todo better way of figuring out the error context to display in highlighted source
     21  *
     22  * @package sapphire
     23  * @subpackage dev
     24  */
     25 class Debug {
     26
     27     /**
     28      * @var $custom_smtp_server string Custom mailserver for sending mails.
     29      */
     30     protected static $custom_smtp_server = '';
     31
     32     /**
     33      * @var $send_errors_to string Email address to send error notifications
     34      */
     35     protected static $send_errors_to;
     36
     37     /**
     38      * @var $send_warnings_to string Email address to send warning notifications
     39      */
     40     protected static $send_warnings_to;
     41
     42     /**
     43      * String indicating the file where errors are logged.
     44      * Filename is relative to the site root.
     45      * The named file will have a terse log sent to it, and the full log (an
     46      * encoded file containing backtraces and things) will go to a file of a similar
     47      * name, but with the suffix ".full" added.
     48      */
     49     protected static $log_errors_to = null;
     50
     51     /**
     52      * The header of the message shown to users on the live site when a fatal error occurs.
     53      */
     54     public static $friendly_error_header = 'There has been an error';
     55
     56     /**
     57      * The body of the message shown to users on the live site when a fatal error occurs.
     58      */
     59     public static $friendly_error_detail = 'The website server has not been able to respond to your request.';
     60
     61     /**
     62      * Show the contents of val in a debug-friendly way.
     63      * Debug::show() is intended to be equivalent to dprintr()
     64      */
     65     static function show($val, $showHeader = true) {
     66         if(!Director::isLive()) {
     67             if($showHeader) {
     68                 $caller = Debug::caller();
     69                 if(Director::is_ajax() || Director::is_cli())
     70                     echo "Debug ($caller[class]$caller[type]$caller[function]() in line $caller[line] of " . basename($caller['file']) . ")\n";
     71                 else
     72                     echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]() \n<span style=\"font-weight:normal\">in line</span> $caller[line] \n<span style=\"font-weight:normal\">of</span> " . basename($caller['file']) . ")</span>\n</h3>\n";
     73             }
     74
     75             echo Debug::text($val);
     76
     77             if(!Director::is_ajax() && !Director::is_cli()) echo "</div>";
     78             else echo "\n\n";
     79         }
     80
     81     }
     82
     83     /**
     84      * Close out the show dumper
     85      *
     86      * @param mixed $val
     87      */
     88     static function endshow($val) {
     89         if(!Director::isLive()) {
     90             $caller = Debug::caller();
     91             echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]() \n<span style=\"font-weight:normal\">in line</span> $caller[line] \n<span style=\"font-weight:normal\">of</span> " . basename($caller['file']) . ")</span>\n</h3>\n";
     92             echo Debug::text($val);
     93             die();
     94         }
     95     }
     96
     97     /**
     98      * Quick dump of a variable.
     99      *
    100      * @param mixed $val
    101      */
    102     static function dump($val) {
    103         echo '<pre style="background-color:#ccc;padding:5px;font-size:14px;line-height:18px;">';
    104         $caller = Debug::caller();
    105         echo "<span style=\"font-size: 12px;color:#666;\">Line $caller[line] of " . basename($caller['file']) . ":</span>\n";
    106         if (is_string($val)) print_r(wordwrap($val, 100));
    107         else print_r($val);
    108         echo '</pre>';
    109     }
    110
    111     /**
    112      * ??
    113      *
    114      * @param unknown_type $val
    115      * @return unknown
    116      */
    117     static function text($val) {
    118         if(is_object($val)) {
    119             if(method_exists($val, 'hasMethod')) {
    120                 $hasDebugMethod = $val->hasMethod('debug');
    121             } else {
    122                 $hasDebugMethod = method_exists($val, 'debug');
    123             }
    124
    125             if($hasDebugMethod) {
    126                 return $val->debug();
    127             }
    128         }
    129
    130         if(is_array($val)) {
    131             $result = "<ul>\n";
    132             foreach($val as $k => $v) {
    133                 $result .= "<li>$k = " . Debug::text($v) . "</li>\n";
    134             }
    135             $val = $result . "</ul>\n";
    136
    137         } else if (is_object($val)) {
    138             $val = var_export($val, true);
    139         } else {
    140             if(!Director::is_cli() && !Director::is_ajax()) {
    141                 $val = "<pre style=\"font-family: Courier new\">" . htmlentities($val) . "</pre>\n";
    142             }
    143         }
    144
    145         return $val;
    146     }
    147
    148     /**
    149      * Show a debugging message
    150      */
    151     static function message($message, $showHeader = true) {
    152         if(!Director::isLive()) {
    153             $caller = Debug::caller();
    154             $file = basename($caller['file']);
    155             if(Director::is_cli()) {
    156                 if($showHeader) echo "Debug (line $caller[line] of $file):\n ";
    157                 echo $message . "\n";
    158             } else {
    159                 echo "<p style=\"background-color: white; color: black; width: 95%; margin: 0.5em; padding: 0.3em; border: 1px #CCC solid\">\n";
    160                 if($showHeader) echo "<b>Debug (line $caller[line] of $file):</b>\n ";
    161                 echo Convert::raw2xml($message) . "</p>\n";
    162             }
    163         }
    164     }
    165
    166     // Keep track of how many headers have been sent
    167     static $headerCount = 0;
    168
    169     /**
    170      * Send a debug message in an HTTP header. Only works if you are
    171      * on Dev, and headers have not yet been sent.
    172      *
    173      * @param string $msg
    174      * @param string $prefix (optional)
    175      * @return void
    176      */
    177     static function header($msg, $prefix = null) {
    178         if (Director::isDev() && !headers_sent()) {
    179             self::$headerCount++;
    180             header('SS-'.self::$headerCount.($prefix?'-'.$prefix:'').': '.$msg);
    181         }
    182     }
    183
    184     /**
    185      * Log to a standard text file output.
    186      *
    187      * @param $message string to output
    188      */
    189     static function log($message) {
    190         $file = Director::baseFolder().'/debug.log';
    191         $now = date('r');
    192         $oldcontent = (file_exists($file)) ? file_get_contents($file) : '';
    193         $content = $oldcontent . "\n\n== $now ==\n$message\n";
    194         file_put_contents($file, $content);
    195     }
    196
    197     /**
    198      * Load error handlers into environment.
    199      * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php).
    200      */
    201     static function loadErrorHandlers() {
    202         set_error_handler('errorHandler', error_reporting());
    203         set_exception_handler('exceptionHandler');
    204     }
    205
    206     static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) {
    207         if(error_reporting() == 0) return;
    208
    209         // Send out the error details to the logger for writing
    210         SS_Log::log(
    211             array(
    212                 'errno' => $errno,
    213                 'errstr' => $errstr,
    214                 'errfile' => $errfile,
    215                 'errline' => $errline,
    216                 'errcontext' => $errcontext
    217             ),
    218             SS_Log::NOTICE
    219         );
    220
    221         if(Director::isDev()) {
    222             self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Notice");
    223         }
    224     }
    225
    226     /**
    227      * Handle a non-fatal warning error thrown by PHP interpreter.
    228      *
    229      * @param unknown_type $errno
    230      * @param unknown_type $errstr
    231      * @param unknown_type $errfile
    232      * @param unknown_type $errline
    233      * @param unknown_type $errcontext
    234      */
    235     static function warningHandler($errno, $errstr, $errfile, $errline, $errcontext) {
    236         if(error_reporting() == 0) return;
    237         if(self::$send_warnings_to) {
    238             self::emailError(self::$send_warnings_to, $errno, $errstr, $errfile, $errline, $errcontext, "Warning");
    239         }
    240
    241         // Send out the error details to the logger for writing
    242         SS_Log::log(
    243             array(
    244                 'errno' => $errno,
    245                 'errstr' => $errstr,
    246                 'errfile' => $errfile,
    247                 'errline' => $errline,
    248                 'errcontext' => $errcontext
    249             ),
    250             SS_Log::WARN
    251         );
    252
    253         if(self::$log_errors_to) {
    254             self::log_error_if_necessary( $errno, $errstr, $errfile, $errline, $errcontext, "Warning");
    255         }
    256
    257         if(Director::isDev()) {
    258             self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning");
    259         }
    260     }
    261
    262     /**
    263      * Handle a fatal error, depending on the mode of the site (ie: Dev, Test, or Live).
    264      *
    265      * Runtime execution dies immediately once the error is generated.
    266      *
    267      * @param unknown_type $errno
    268      * @param unknown_type $errstr
    269      * @param unknown_type $errfile
    270      * @param unknown_type $errline
    271      * @param unknown_type $errcontext
    272      */
    273     static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) {
    274         if(self::$send_errors_to) {
    275             self::emailError(self::$send_errors_to, $errno, $errstr, $errfile, $errline, $errcontext, "Error");
    276         }
    277
    278         // Send out the error details to the logger for writing
    279         SS_Log::log(
    280             array(
    281                 'errno' => $errno,
    282                 'errstr' => $errstr,
    283                 'errfile' => $errfile,
    284                 'errline' => $errline,
    285                 'errcontext' => $errcontext
    286             ),
    287             SS_Log::ERR
    288         );
    289
    290         if(self::$log_errors_to) {
    291             self::log_error_if_necessary( $errno, $errstr, $errfile, $errline, $errcontext, "Error");
    292         }
    293
    294         if(Director::isDev() || Director::is_cli()) {
    295             self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error");
    296         } else {
    297             self::friendlyError();
    298         }
    299         exit(1);
    300     }
    301
    302     /**
    303      * Render a user-facing error page, using the default HTML error template
    304      * rendered by {@link ErrorPage} if it exists. Doesn't use the standard {@link SS_HTTPResponse} class
    305      * the keep dependencies minimal.
    306      *
    307      * @uses ErrorPage
    308      *
    309      * @param int $statusCode HTTP Status Code (Default: 500)
    310      * @param string $friendlyErrorMessage User-focused error message. Should not contain code pointers or "tech-speak".
    311      *    Used in the HTTP Header and ajax responses.
    312      * @param string $friendlyErrorDetail Detailed user-focused message. Is just used if no {@link ErrorPage} is found
    313      *    for this specific status code.
    314      * @return string HTML error message for non-ajax requests, plaintext for ajax-request.
    315      */
    316     static function friendlyError($statusCode = 500, $friendlyErrorMessage = null, $friendlyErrorDetail = null) {
    317         if(!$friendlyErrorMessage) $friendlyErrorMessage = self::$friendly_error_header;
    318         if(!$friendlyErrorDetail) $friendlyErrorDetail = self::$friendly_error_detail;
    319
    320         if(!headers_sent()) header($_SERVER['SERVER_PROTOCOL'] . " $statusCode $friendlyErrorMessage");
    321
    322         if(Director::is_ajax()) {
    323             echo $friendlyErrorMessage;
    324         } else {
    325             $errorFilePath = ErrorPage::get_filepath_for_errorcode($statusCode, i18n::get_locale());
    326             if(file_exists($errorFilePath)) {
    327                 $content = file_get_contents(ASSETS_PATH . "/error-$statusCode.html");
    328                 // $BaseURL is left dynamic in error-###.html, so that multi-domain sites don't get broken
    329                 echo str_replace('$BaseURL', Director::absoluteBaseURL(), $content);
    330
    331             } else {
    332                 $renderer = new DebugView();
    333                 $renderer->writeHeader();
    334                 $renderer->writeInfo("Website Error", $friendlyErrorMessage, $friendlyErrorDetail);
    335
    336                 if(Email::getAdminEmail()) {
    337                     $mailto = Email::obfuscate(Email::getAdminEmail());
    338                     $renderer->writeParagraph('Contact an administrator: ' . $mailto . '');
    339                 }
    340
    341                 $renderer->writeFooter();
    342             }
    343         }
    344     }
    345
    346     /**
    347      * Create an instance of an appropriate DebugView object.
    348      */
    349     static function create_debug_view() {
    350         if(Director::is_cli() || Director::is_ajax()) return new CliDebugView();
    351         else return new DebugView();
    352     }
    353
    354     /**
    355      * Render a developer facing error page, showing the stack trace and details
    356      * of the code where the error occured.
    357      *
    358      * @param unknown_type $errno
    359      * @param unknown_type $errstr
    360      * @param unknown_type $errfile
    361      * @param unknown_type $errline
    362      * @param unknown_type $errcontext
    363      */
    364     static function showError($errno, $errstr, $errfile, $errline, $errcontext, $errtype) {
    365         if(!headers_sent()) {
    366             $errText = "$errtype: \"$errstr\" at line $errline of $errfile";
    367             $errText = str_replace(array("\n","\r")," ",$errText);
    368             if(!headers_sent()) header($_SERVER['SERVER_PROTOCOL'] . " 500 $errText");
    369
    370             // if error is displayed through ajax with CliDebugView, use plaintext output
    371             if(Director::is_ajax()) header('Content-Type: text/plain');
    372         }
    373
    374         // Legacy error handling for customized prototype.js Ajax.Base.responseIsSuccess()
    375         // if(Director::is_ajax()) echo "ERROR:\n";
    376
    377         $reporter = self::create_debug_view();
    378
    379         // Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved.
    380         $httpRequest = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : @$_REQUEST['url'];
    381         if(isset($_SERVER['REQUEST_METHOD'])) $httpRequest = $_SERVER['REQUEST_METHOD'] . ' ' . $httpRequest;
    382
    383         $reporter->writeHeader($httpRequest);
    384         $reporter->writeError($httpRequest, $errno, $errstr, $errfile, $errline, $errcontext);
    385
    386         if(file_exists($errfile)) {
    387             $lines = file($errfile);
    388
    389             // Make the array 1-based
    390             array_unshift($lines,"");
    391             unset($lines[0]);
    392
    393             $offset = $errline-10;
    394             $lines = array_slice($lines, $offset, 16, true);
    395             $reporter->writeSourceFragment($lines, $errline);
    396         }
    397         $reporter->writeTrace(($errcontext ? $errcontext : debug_backtrace()));
    398         $reporter->writeFooter();
    399         exit(1);
    400     }
    401
    402     /**
    403      * Utility method to render a snippet of PHP source code, from selected file
    404      * and highlighting the given line number.
    405      *
    406      * @param string $errfile
    407      * @param int $errline
    408      */
    409     static function showLines($errfile, $errline) {
    410         $lines = file($errfile);
    411         $offset = $errline-10;
    412         $lines = array_slice($lines, $offset, 16);
    413         echo '<pre>';
    414         $offset++;
    415         foreach($lines as $line) {
    416             $line = htmlentities($line);
    417             if ($offset == $errline) {
    418                 echo "<span>$offset</span> <span class=\"error\">$line</span>";
    419             } else {
    420                 echo "<span>$offset</span> $line";
    421             }
    422             $offset++;
    423         }
    424         echo '</pre>';
    425     }
    426
    427     /**
    428      * Dispatch an email notification message when an error is triggered.
    429      * @deprecated 2.5
    430      * To create error logs by email, use this code instead:
    431      * <code>
    432      * $emailWriter = new SS_LogEmailWriter('my@email.com');
    433      * SS_Log::add_writer($emailWriter, SS_Log::ERR);
    434      * </code>
    435      *
    436      * @param string $emailAddress
    437      * @param string $errno
    438      * @param string $errstr
    439      * @param string $errfile
    440      * @param int $errline
    441      * @param string $errcontext
    442      * @param string $errorType "warning" or "error"
    443      * @return boolean
    444      */
    445     static function emailError($emailAddress, $errno, $errstr, $errfile, $errline, $errcontext, $errorType = "Error") {
    446         user_error('Debug::send_errors_to() and Debug::emailError() is deprecated. Please use SS_Log instead.
    447             See the class documentation in SS_Log.php for more information.', E_USER_NOTICE);
    448         $priority = ($errorType == 'Error') ? SS_Log::ERR : SS_Log::WARN;
    449         $writer = new SS_LogEmailWriter($emailAddress);
    450         SS_Log::add_writer($writer, $priority);
    451         SS_Log::log(
    452             array(
    453                 'errno' => $errno,
    454                 'errstr' => $errstr,
    455                 'errfile' => $errfile,
    456                 'errline' => $errline,
    457                 'errcontext' => $errcontext
    458             ),
    459             $priority
    460         );
    461         SS_Log::remove_writer($writer);
    462     }
    463
    464     /**
    465      * Log the given error, if self::$log_errors is set.
    466      * Uses the native error_log() funtion in PHP.
    467      *
    468      * Format: [d-M-Y h:i:s] <type> at <file> line <line>: <errormessage> <url>
    469      *
    470      * @todo Detect script path for CLI errors
    471      * @todo Log detailed errors to full file
    472      * @deprecated 2.5 See SS_Log on setting up error file logging
    473      */
    474     protected static function log_error_if_necessary($errno, $errstr, $errfile, $errline, $errcontext, $errtype) {
    475         user_error('Debug::log_error_if_necessary() and Debug::log_errors_to() are deprecated. Please use SS_Log instead.
    476             See the class documentation in SS_Log.php for more information.', E_USER_NOTICE);
    477         $priority = ($errtype == 'Error') ? SS_Log::ERR : SS_Log::WARN;
    478         $writer = new SS_LogFileWriter(Director::baseFolder() .'/'. self::$log_errors_to);
    479         SS_Log::add_writer($writer, $priority);
    480         SS_Log::log(
    481             array(
    482                 'errno' => $errno,
    483                 'errstr' => $errstr,
    484                 'errfile' => $errfile,
    485                 'errline' => $errline,
    486                 'errcontext' => $errcontext
    487             ),
    488             $priority
    489         );
    490         SS_Log::remove_writer($writer);
    491     }
    492
    493     /**
    494      * @param string $server IP-Address or domain
    495      * @deprecated 2.5 See SS_Log on setting up error email notification
    496      */
    497     static function set_custom_smtp_server($server) {
    498         self::$custom_smtp_server = $server;
    499     }
    500
    501     /**
    502      * @return string
    503      * @deprecated 2.5 See SS_Log on setting up error email notification
    504      */
    505     static function get_custom_smtp_server() {
    506         return self::$custom_smtp_server;
    507     }
    508
    509     /**
    510      * Send errors to the given email address.
    511      * Can be used like so:
    512      * if(Director::isLive()) Debug::send_errors_to("sam@silverstripe.com");
    513      *
    514      * @deprecated 2.5 See SS_Log on setting up error email notification
    515      *
    516      * @param string $emailAddress The email address to send errors to
    517      * @param string $sendWarnings Set to true to send warnings as well as errors (Default: false)
    518      */
    519     static function send_errors_to($emailAddress, $sendWarnings = false) {
    520         self::$send_errors_to = $emailAddress;
    521         self::$send_warnings_to = $sendWarnings ? $emailAddress : null;
    522     }
    523
    524     /**
    525      * @return string
    526      * @deprecated 2.5 See SS_Log on setting up error email notification
    527      */
    528     static function get_send_errors_to() {
    529         return self::$send_errors_to;
    530     }
    531
    532     /**
    533      * @param string $emailAddress
    534      * @deprecated 2.5 See SS_Log on setting up error email notification
    535      */
    536     static function send_warnings_to($emailAddress) {
    537         self::$send_warnings_to = $emailAddress;
    538     }
    539
    540     /**
    541      * @return string
    542      * @deprecated 2.5 See SS_Log on setting up error email notification
    543      */
    544     static function get_send_warnings_to() {
    545         return self::$send_warnings_to;
    546     }
    547
    548     /**
    549      * Call this to enable logging of errors.
    550      * @deprecated 2.5 See SS_Log on setting up error file logging
    551      */
    552     static function log_errors_to($logFile = ".sserrors") {
    553         self::$log_errors_to = $logFile;
    554     }
    555
    556     static function caller() {
    557         $bt = debug_backtrace();
    558         $caller = $bt[2];
    559         $caller['line'] = $bt[1]['line'];
    560         $caller['file'] = $bt[1]['file'];
    561         if(!isset($caller['class'])) $caller['class'] = '';
    562         if(!isset($caller['type'])) $caller['type'] = '';
    563         return $caller;
    564     }
    565
    566     /**
    567      * @deprecated 2.5 Please use {@link SS_Backtrace::backtrace()}
    568      */
    569     static function backtrace($returnVal = false, $ignoreAjax = false) {
    570         user_error('Debug::backtrace() is deprecated. Please use SS_Backtrace::backtrace() instead', E_USER_NOTICE);
    571         return SS_Backtrace::backtrace($returnVal, $ignoreAjax);
    572     }
    573
    574     /**
    575      * @deprecated 2.5 Please use {@link SS_Backtrace::get_rendered_backtrace()}
    576      */
    577     static function get_rendered_backtrace($bt, $plainText = false) {
    578         user_error('Debug::get_rendered_backtrace() is deprecated. Please use SS_Backtrace::get_rendered_backtrace() instead', E_USER_NOTICE);
    579         return SS_Backtrace::get_rendered_backtrace($bt, $plainText);
    580     }
    581
    582     /**
    583      * Check if the user has permissions to run URL debug tools,
    584      * else redirect them to log in.
    585      */
    586     static function require_developer_login() {
    587         if(Director::isDev())   {
    588             return;
    589         }
    590         if(isset($_SESSION['loggedInAs'])) {
    591             // We have to do some raw SQL here, because this method is called in Object::defineMethods().
    592             // This means we have to be careful about what objects we create, as we don't want Object::defineMethods()
    593             // being called again.
    594             // This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN');
    595
    596             $memberID = $_SESSION['loggedInAs'];
    597
    598             $groups = DB::query("SELECT \"GroupID\" from \"Group_Members\" WHERE \"MemberID\" = " . $memberID);
    599             $groupCSV = implode($groups->column(), ',');
    600
    601             $permission = DB::query("
    602                 SELECT \"ID\"
    603                 FROM \"Permission\"
    604                 WHERE (
    605                     \"Code\" = 'ADMIN'
    606                     AND \"Type\" = " . Permission::GRANT_PERMISSION . "
    607                     AND \"GroupID\" IN ($groupCSV)
    608                 )
    609             ")->value();
    610
    611             if($permission) {
    612                 return;
    613             }
    614         }
    615
    616         // This basically does the same as
    617         // Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.");
    618         // We have to do this because of how early this method is called in execution.
    619         $_SESSION['Security']['Message']['message'] = "You need to login with developer access to make use of debugging tools.";
    620         $_SESSION['Security']['Message']['type'] =  'warning';
    621         $_SESSION['BackURL'] = $_SERVER['REQUEST_URI'];
    622         header($_SERVER['SERVER_PROTOCOL'] . " 302 Found");
    623         header("Location: " . Director::baseURL() . "Security/login");
    624         die();
    625     }
    626 }
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637 /**
    638  * Generic callback, to catch uncaught exceptions when they bubble up to the top of the call chain.
    639  *
    640  * @ignore
    641  * @param Exception $exception
    642  */
    643 function exceptionHandler($exception) {
    644     $errno = E_USER_ERROR;
    645     $type = get_class($exception);
    646     $message = "Uncaught " . $type . ": " . $exception->getMessage();
    647     $file = $exception->getFile();
    648     $line = $exception->getLine();
    649     $context = $exception->getTrace();
    650     Debug::fatalHandler($errno, $message, $file, $line, $context);
    651 }
    652
    653 /**
    654  * Generic callback to catch standard PHP runtime errors thrown by the interpreter
    655  * or manually triggered with the user_error function.
    656  * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php).
    657  *
    658  * @ignore
    659  * @param int $errno
    660  * @param string $errstr
    661  * @param string $errfile
    662  * @param int $errline
    663  */
    664 function errorHandler($errno, $errstr, $errfile, $errline) {
    665     switch($errno) {
    666         case E_ERROR:
    667         case E_CORE_ERROR:
    668         case E_USER_ERROR:
    669             Debug::fatalHandler($errno, $errstr, $errfile, $errline, null);
    670             break;
    671
    672         case E_WARNING:
    673         case E_CORE_WARNING:
    674         case E_USER_WARNING:
    675             Debug::warningHandler($errno, $errstr, $errfile, $errline, null);
    676             break;
    677
    678         case E_NOTICE:
    679         case E_USER_NOTICE:
    680             Debug::noticeHandler($errno, $errstr, $errfile, $errline, null);
    681             break;
    682     }
    683 }
    684

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -