1<?php
2/**
3 * Provides methods used to handle error reporting.
4 *
5 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
6 *
7 * See the enclosed file COPYING for license information (LGPL). If you
8 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
9 *
10 * @author   Michael Slusarz <slusarz@horde.org>
11 * @category Horde
12 * @license  http://www.horde.org/licenses/lgpl21 LGPL-2.1
13 * @package  Core
14 */
15class Horde_ErrorHandler
16{
17    /**
18     * Aborts with a fatal error, displaying debug information to the user.
19     *
20     * @param mixed $error  Either a string or an object with a getMessage()
21     *                      method (e.g. PEAR_Error, Exception).
22     */
23    public static function fatal($error)
24    {
25        global $registry;
26
27        if (is_object($error)) {
28            switch (get_class($error)) {
29            case 'Horde_Exception_AuthenticationFailure':
30                $auth_app = !$registry->clearAuthApp($error->application);
31
32                if ($auth_app &&
33                    $registry->isAuthenticated(array('app' => $error->application, 'notransparent' => true))) {
34                    break;
35                }
36
37                try {
38                    Horde::log($error, 'NOTICE');
39                } catch (Exception $e) {}
40
41                if (Horde_Cli::runningFromCLI()) {
42                    $cli = new Horde_Cli();
43                    $cli->fatal($error);
44                }
45
46                $params = array();
47
48                if ($registry->getAuth()) {
49                    $params['app'] = $error->application;
50                }
51
52                switch ($error->getCode()) {
53                case Horde_Auth::REASON_MESSAGE:
54                    $params['msg'] = $error->getMessage();
55                    $params['reason'] = $error->getCode();
56                    break;
57                }
58
59                $logout_url = $registry->getLogoutUrl($params);
60
61                /* Clear authentication here. Otherwise, there might be
62                 * issues on the login page since we would otherwise need
63                 * to do session token checking (which might not be
64                 * available, so logout won't happen, etc...) */
65                if ($auth_app && array_key_exists('app', $params)) {
66                    $registry->clearAuth();
67                }
68
69                $logout_url->redirect();
70            }
71        }
72
73        try {
74            Horde::log($error, 'EMERG');
75        } catch (Exception $e) {}
76
77        try {
78            $cli = Horde_Cli::runningFromCLI();
79        } catch (Exception $e) {
80            die($e);
81        }
82
83        if ($cli) {
84            $cli = new Horde_Cli();
85            $cli->fatal($error);
86        }
87
88        if (!headers_sent()) {
89            header('Content-type: text/html; charset=UTF-8');
90        }
91        echo <<< HTML
92<html>
93<head><title>Horde :: Fatal Error</title></head>
94<body style="background:#fff; color:#000">
95HTML;
96
97        ob_start();
98        try {
99            $admin = (isset($registry) && $registry->isAdmin());
100
101            echo '<h1>' . Horde_Core_Translation::t("A fatal error has occurred") . '</h1>';
102
103            if (is_object($error) && method_exists($error, 'getMessage')) {
104                echo '<h3>' . htmlspecialchars($error->getMessage()) . '</h3>';
105            } elseif (is_string($error)) {
106                echo '<h3>' . htmlspecialchars($error) . '</h3>';
107            }
108
109            if ($admin) {
110                if ($error instanceof Throwable ||
111                    $error instanceof Exception) {
112                    $trace = $error;
113                    $file = $error->getFile();
114                    $line = $error->getLine();
115                } else {
116                    $trace = debug_backtrace();
117                    $calling = array_shift($trace);
118                    $file = $calling['file'];
119                    $line = $calling['line'];
120                }
121                printf(Horde_Core_Translation::t("in %s:%d"), $file, $line);
122                echo '<div id="backtrace"><pre>' .
123                    strval(new Horde_Support_Backtrace($trace)) .
124                    '</pre></div>';
125                if (is_object($error)) {
126                    echo '<h3>' . Horde_Core_Translation::t("Details") . '</h3>';
127                    echo '<h4>' . Horde_Core_Translation::t("The full error message is logged in Horde's log file, and is shown below only to administrators. Non-administrative users will not see error details.") . '</h4>';
128                    ob_flush();
129                    flush();
130                    echo '<div id="details"><pre>' . htmlspecialchars(print_r($error, true)) . '</pre></div>';
131                }
132            } else {
133                echo '<h3>' . Horde_Core_Translation::t("Details have been logged for the administrator.") . '</h3>';
134            }
135        } catch (Exception $e) {
136            die($e);
137        }
138
139        ob_end_flush();
140        echo '</body></html>';
141        exit(1);
142    }
143
144    /**
145     * PHP legacy error handling (non-Exceptions).
146     *
147     * @param integer $errno     See set_error_handler().
148     * @param string $errstr     See set_error_handler().
149     * @param string $errfile    See set_error_handler().
150     * @param integer $errline   See set_error_handler().
151     * @param array $errcontext  See set_error_handler().
152     */
153    public static function errorHandler($errno, $errstr, $errfile, $errline,
154                                        $errcontext)
155    {
156        $er = error_reporting();
157
158        // Calls prefixed with '@'.
159        if ($er == 0) {
160            // Must return false to populate $php_errormsg (as of PHP 5.2).
161            return false;
162        }
163
164        if (!($er & $errno) || !class_exists('Horde_Log')) {
165            return;
166        }
167
168        $options = array();
169
170        try {
171            switch ($errno) {
172            case E_WARNING:
173            case E_USER_WARNING:
174            case E_RECOVERABLE_ERROR:
175                $priority = Horde_Log::WARN;
176                break;
177
178            case E_NOTICE:
179            case E_USER_NOTICE:
180                $priority = Horde_Log::NOTICE;
181                break;
182
183            case E_STRICT:
184                $options['notracelog'] = true;
185                $priority = Horde_Log::DEBUG;
186                break;
187
188            default:
189                $priority = Horde_Log::DEBUG;
190                break;
191            }
192
193            Horde::log(new ErrorException('PHP ERROR: ' . $errstr, 0, $errno, $errfile, $errline), $priority, $options);
194        } catch (Exception $e) {}
195    }
196
197    /**
198     * Catch fatal errors.
199     */
200    public static function catchFatalError()
201    {
202        $error = error_get_last();
203        if ($error['type'] == E_ERROR) {
204            self::fatal(new ErrorException(
205                $error['message'],
206                0,
207                $error['type'],
208                $error['file'],
209                $error['line']
210            ));
211        }
212    }
213
214}
215