1 <?php
  2 
  3 /**
  4  * @package redaxo\core
  5  */
  6 abstract class rex_error_handler
  7 {
  8     private static $registered;
  9 
 10     /**
 11      * Registers the class as php-error/exception handler.
 12      */
 13     public static function register()
 14     {
 15         if (self::$registered) {
 16             return;
 17         }
 18 
 19         self::$registered = true;
 20 
 21         set_error_handler([self::class, 'handleError']);
 22         set_exception_handler([self::class, 'handleException']);
 23         register_shutdown_function([self::class, 'shutdown']);
 24     }
 25 
 26     /**
 27      * Unregisters the logger instance as php-error/exception handler.
 28      */
 29     public static function unregister()
 30     {
 31         if (!self::$registered) {
 32             return;
 33         }
 34 
 35         self::$registered = false;
 36 
 37         restore_error_handler();
 38         restore_exception_handler();
 39         // unregister of shutdown function is not possible
 40     }
 41 
 42     /**
 43      * Handles the given Exception.
 44      *
 45      * @param Throwable|Exception $exception The Exception to handle
 46      */
 47     public static function handleException($exception)
 48     {
 49         try {
 50             rex_logger::logException($exception);
 51 
 52             // in case exceptions happen early - before symfony-console doRun()
 53             if ('cli' === PHP_SAPI) {
 54                 echo $exception->__toString();
 55                 exit(1);
 56             }
 57 
 58             while (ob_get_level()) {
 59                 ob_end_clean();
 60             }
 61 
 62             $status = rex_response::HTTP_INTERNAL_ERROR;
 63             if ($exception instanceof rex_http_exception && $exception->getHttpCode()) {
 64                 $status = $exception->getHttpCode();
 65             }
 66             rex_response::setStatus($status);
 67 
 68             if (rex::isSetup() || rex::isDebugMode() || ($user = rex_backend_login::createUser()) && $user->isAdmin()) {
 69                 list($errPage, $contentType) = self::renderWhoops($exception);
 70                 rex_response::sendContent($errPage, $contentType);
 71                 exit(1);
 72             }
 73         } catch (Throwable $e) {
 74             // fallback to the less feature rich error pages, when whoops rendering fails
 75         } catch (Exception $e) {
 76             // fallback to the less feature rich error pages, when whoops rendering fails
 77         }
 78 
 79         try {
 80             $fragment = new rex_fragment();
 81             if (rex::isBackend()) {
 82                 $errorPage = $fragment->parse('core/be_ooops.php');
 83             } else {
 84                 $errorPage = $fragment->parse('core/fe_ooops.php');
 85             }
 86         } catch (Throwable $e) {
 87             // we werent even able to render the error page, without an error
 88             $errorPage = 'Oooops, an internal error occured!';
 89         } catch (Exception $e) {
 90             // we werent even able to render the error page, without an error
 91             $errorPage = 'Oooops, an internal error occured!';
 92         }
 93         rex_response::sendContent($errorPage);
 94         exit(1);
 95     }
 96 
 97     private static function renderWhoops($exception)
 98     {
 99         $whoops = new \Whoops\Run();
100         $whoops->writeToOutput(false);
101         $whoops->allowQuit(false);
102 
103         $handler = new \Whoops\Handler\PrettyPageHandler();
104         $handler->setApplicationRootPath(rtrim(rex_path::base(), '/\\'));
105 
106         $handler->setEditor([rex_editor::factory(), 'getUrl']);
107 
108         $whoops->pushHandler($handler);
109 
110         $errPage = $whoops->handleException($exception);
111 
112         $logo = '
113                 <svg xmlns="http://www.w3.org/2000/svg" width="250px" height="30px" viewBox="0 0 250 30">
114                     <path fill="#FFFFFF" d="M147.414,8.221c-0.344,0-5.73,0.003-5.73,0.003c-2.375,0-3.768,2.152-4.281,7.117c-0.571,5.528,1.038,6.839,3.428,6.787c0,0,4.383-0.018,4.983-0.018c2.578,0,3.868-1.401,4.424-6.75C150.776,9.795,150.006,8.221,147.414,8.221z M147.576,1.085c6.356,0,10.716,4.8,9.78,14.12c-0.93,9.264-4.338,14.051-11.097,14.039l-5.618,0.018c-7.919-0.022-11.169-6.19-10.311-14.756c0.813-8.112,4.823-13.417,10.842-13.417L147.576,1.085z M88.31,14.004h11.837l0.239-2.326c0.263-2.215-0.569-3.44-2.919-3.449h-5.445c-2.204,0.074-3.232,1.037-3.548,4.148L88.31,14.004z M99.5,20.268H87.678l-0.888,8.813h-7.095l1.755-17.412c0.727-6.39,4.402-10.456,10.553-10.456h5.471c6.285,0.021,10.614,4.443,9.936,11.176l-1.723,16.691H98.59L99.5,20.268L99.5,20.268z"/>
115                     <path fill="#FFFFFF" d="M69.432,8.269h-6.758l-1.384,13.73h6.813c2.196,0,4.068-2.377,4.554-7.492C73.008,10.791,71.503,8.269,69.432,8.269z M69.713,1.213c14.65,0,12.27,27.844-1.457,27.844l-14.773,0.001l2.811-27.845H69.713L69.713,1.213z M53.957,8.239L40.618,8.237c-1.725,0-2.939,0.975-3.532,3.49h14.123l-0.631,6.263H36.413c0.11,2.896,1.056,3.845,3.481,3.854c2.037,0.009,8.634,0.106,12.677,0.127l-0.726,7.088H39.672c-7.177,0-11.146-5.596-10.233-14.52C30.402,5.116,34.176,1.21,40.609,1.21l14.05,0.003L53.957,8.239L53.957,8.239z M9.655,13.577h8.266c2.082,0,3.532-1.039,3.532-2.756c0-1.301-1.005-2.543-2.819-2.543c-1.406,0-5.36-0.021-8.44-0.038L9.655,13.577z M23.032,19.573l4.207,9.485h-7.722l-3.735-8.423H8.944l-0.85,8.423H1L3.802,1.215l14.655-0.006c6.585,0,10.478,5.11,10.075,9.686C28.215,14.502,26.893,17.467,23.032,19.573z"/>
116                     <path fill="#FFFFFF" d="M114.552,1.279l5.167,8.52l5.461-8.52h8.835l-10.004,15.089l8.675,12.667h-8.309l-4.2-6.607l-4.398,6.607h-9.147l9.056-13.276l-9.043-14.479H114.552L114.552,1.279z"/>
117                     <path fill="#FFFFFF" d="M167.358,21.674l-3.909-0.001c-0.938,0-1.487,0.334-1.746,2.553c-0.229,1.97,0.399,2.474,1.338,2.474c0.732,0,2.414,0.016,3.81,0.008l-0.26,2.581c-1.424,0.008-3.024-0.009-3.726-0.009c-2.691,0-4.008-2.053-3.729-5.312c0.258-3.015,1.581-4.874,4.075-4.874l4.408,0.001L167.358,21.674z M188.539,21.634l-4.229,0.002c-1.084,0-1.028,1.299-0.163,1.299l1.786,0.003c4.288,0,3.99,6.319-0.339,6.319l-5.103-0.008l0.261-2.581l4.935,0.008c0.994,0,0.983-1.383,0.154-1.383l-1.694-0.002c-4.316,0-4.096-6.236,0.33-6.236l4.321-0.002L188.539,21.634L188.539,21.634z M175.543,21.67l-0.769,7.624h-2.595l0.77-7.624l-1.809-0.017l-0.712,7.641h-2.591l0.954-10.255l7.51,0.051c2.297,0.008,3.88,1.641,3.632,4.102l-0.629,6.103h-2.595l0.657-6.363c0.093-0.923-0.276-1.256-1.067-1.26L175.543,21.67L175.543,21.67z"/>
118                     <path fill="#FFFFFF" d="M165.465,1c2.893,0,5.236,2.345,5.236,5.25c0,2.891-2.344,5.236-5.236,5.236c-2.891,0-5.236-2.345-5.236-5.236C160.229,3.345,162.574,1,165.465,1z M162.703,9.082c1.515,1.553,4.011,1.554,5.539,0c1.557-1.57,1.557-4.109,0-5.679c-1.528-1.554-4.024-1.553-5.539,0c-0.752,0.752-1.175,1.773-1.175,2.846C161.528,7.308,161.95,8.329,162.703,9.082z M166.391,6.713l1.448,2.647h-1.78l-1.122-2.222l-0.272,2.222h-1.517l0.779-6.33h1.903c1.558,0,2.373,0.925,1.917,2.369C167.545,6.075,167.067,6.516,166.391,6.713L166.391,6.713z M165.318,5.761c0.533,0,0.822-0.219,0.935-0.577c0.188-0.528-0.034-0.854-0.652-0.854h-0.318l-0.175,1.432H165.318L165.318,5.761z"/>
119                     <text fill="#FFFFFF" x="193" y="29" font-size="10">'. rex::getVersion() .'</text>
120                 </svg>';
121         $styles = '
122                 <style>
123                     .Whoops {
124                         padding-top: 70px;
125                     }
126                     .panel {
127                         top: 70px;
128                         bottom: 0;
129                         height: auto;
130                     }
131                     .exc-message {
132                         vertical-align: middle;
133                     }
134                     .search-for-help {
135                         width: auto;
136                     }
137                     .rex-whoops-header {
138                         position: fixed;
139                         top: 0;
140                         left: 0;
141                         right: 0;
142                         height: 70px;
143                         background: #b00;                            
144                     }
145                     .rex-logo {
146                         padding-left: 40px;
147                         line-height: 70px;
148                     }
149                     .rex-logo > svg {
150                         vertical-align: middle;
151                     }
152                     .rex-safemode {
153                         position: absolute;
154                         top: 17px;
155                         right: 40px;
156                         display: inline-block;
157                         padding: 10px;
158                         background-color: #f90;
159                         border-radius: 4px;
160                         color: #754600;
161                         font-size: .875rem;
162                         font-weight: 700;
163                         transition: 0.2s ease-out;
164                     }
165                     .rex-safemode:hover {
166                         background-color: #754600;
167                         color: #f90;
168                     }
169                     button.clipboard {
170                         margin: 10px 5px;
171                         padding: 0 10px;
172                         border: 0;
173                         box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.2);
174                         color: #fff;
175                         font-weight: bold;
176                         line-height: 24px;
177                         vertical-align: top;
178                         cursor: pointer;
179                         transition: 0.2s ease-out;
180                     } 
181                     button.clipboard:hover {
182                         box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 1);
183                         color: #fff;
184                     }
185                 </style>';
186 
187         $saveModeLink = '';
188         if (!rex::isSetup() && rex::isBackend() && !rex::isSafeMode()) {
189             $saveModeLink = '<a class="rex-safemode" href="' . rex_url::backendPage('packages', ['safemode' => 1]) . '">activate safe mode</a>';
190         }
191 
192         $errPage = str_replace(
193             [
194                 '</head>',
195                 '</body>',
196             ], [
197                 $styles . '</head>',
198                 '<div class="rex-whoops-header"><div class="rex-logo">' . $logo . '</div>' . $saveModeLink . '</div></body>',
199             ],
200             $errPage
201         );
202 
203         $errPage = str_replace(
204             '<ul class="search-for-help">',
205             '<ul class="search-for-help">
206                             <li>
207                                 <a rel="noopener noreferrer" target="_blank" href="https://redaxo.org/doku/master" title="Search for help in the REDAXO Docs.">
208                                     <svg version="1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 700 700"><path d="M27.9 85.7c0 .4-.2 1.5-.3 2.3-.2.8-.7 4.9-1.1 9-.4 4.1-.8 8.2-1 9-.1.8-.6 5.1-.9 9.5-.4 4.4-.9 9.6-1.1 11.5-.2 1.9-.6 6-.9 9-1.2 12.5-2.7 26.7-3.1 30-.2 1.9-.7 6.9-1 11-.4 4.1-.8 8.6-1 10-.4 2.4-.7 5.2-2 19.5-.3 3.3-.8 7.6-1 9.5-.2 1.9-.7 6.4-1 10-.3 3.6-.8 8.1-1 10-.2 1.9-.6 6.4-1 10-.3 3.6-.8 8.1-1 10-.9 7.8-1.8 16.6-3 29.5-.3 3.3-.7 7.6-1 9.5-.2 1.9-.7 6.2-1 9.5-.3 3.3-.8 8-1 10.5-.3 2.5-.7 6.9-1 9.7l-.6 5.3H34c19.9 0 31-.4 31.1-1 .1-.6.2-1.3.3-1.8.1-.4.6-5 1-10.2.5-5.2 1-10.2 1.1-11 .4-2 1.5-12.3 2-18.5.2-2.8.6-6.8.9-9 .3-2.2.8-7.3 1.1-11.2.4-4 .8-8.1.9-9 .3-1.7 2.4-1.8 30.6-1.8h30.3l5.9 13.5c3.2 7.4 6.5 14.8 7.3 16.5.7 1.6 3.4 7.7 6 13.5s5.3 11.8 6 13.5c.8 1.6 2.7 6 4.4 9.7l3 6.8H200c18.8 0 33.9-.3 33.7-.8-.3-.4-4.2-9-8.7-19.2-4.4-10.2-8.7-19.9-9.5-21.5-8.4-18.4-18.6-41.8-18.3-42 .2-.1 3.5-2.2 7.3-4.7 7.3-4.6 19.3-15.7 23.7-21.9 13.9-19.1 20.4-49.6 15.3-71.5-3.1-13.5-12.8-30-22.8-39-12.6-11.3-22.9-16.8-40.7-21.7-7.2-2-10.4-2.1-79.7-2.4-39.8-.2-72.3-.1-72.4.4zm139.9 63.5c8.8 3.3 14.2 10.2 14.7 19 .7 11.8-6.6 20.8-20 24.6-1.1.3-20.3.6-42.8.8l-40.7.2.5-4.1c.3-2.3.8-7.1 1.1-10.7.3-3.6.7-7.5.9-8.7.2-1.2.7-6.1 1-10.8.6-7.3 1-10.4 1.4-12.3.1-.2 17.8-.2 39.4-.1 36.1.1 39.7.3 44.5 2.1zM337 85.6c-2 .3-12.7 2.4-15 3-.3 0-1.2.3-2 .6s-3.5 1.3-6 2.2c-11.4 4-24.5 14.1-33.1 25.4-11.7 15.5-22.5 44.2-25.4 67.7-.4 2.7-.8 5.7-1 6.5-1.1 4.5-2 17.3-2 31 0 19.7 1 28.3 4.6 41.5 5.2 18.8 11.8 30.6 23.8 42.5 11.9 11.8 23.7 18 42 22 7.6 1.7 14.1 1.9 67.7 2 32.6 0 59.4-.3 59.7-.7.3-.4.8-4.9 1.2-9.8.4-5 .8-9.2 1-9.5.1-.3.6-4.1.9-8.5.4-4.4.8-9.1 1.1-10.5.2-1.4.6-5.4 1-9 .3-3.6.7-8.2 1-10.3.3-2.5.1-3.7-.8-3.8-2.2-.2-39.3-.7-76.7-1-20.1-.2-38.9-.7-41.8-1.1-7-1.1-12.4-3.9-15.8-8.3-3-4-6.3-14.9-6.4-21v-4h62.2c61.3 0 62.1 0 62.4-2 .2-1.1.6-5.2 1-9 .3-3.9.7-8.1.9-9.5.2-2.5 1.5-15.3 2-20 .1-1.4.6-5.2.9-8.5l.7-6h-62c-34.2 0-62.1-.4-62.1-.8 0-1.7 4.2-13.1 5.7-15.6 4.4-7.1 11-12 18.2-13.6 1.8-.4 30.5-.7 63.7-.7 44.1 0 60.4-.3 60.5-1.2 0-.6.2-2.7.4-4.6.1-1.9.5-5.8.8-8.5.4-2.8.9-7.9 1.2-11.5.3-3.6.8-8.8 1.1-11.5.3-2.8.7-7.3.9-10 .3-2.8.7-7.1 1-9.8l.6-4.7-67.8.1c-37.3.1-68.9.3-70.3.5zm152.2.1c.2.8-1.1 14.5-1.7 18.8-.2 1.1-.6 5.1-.9 9-.4 3.8-.8 8.3-1 10-.3 1.6-.7 6.4-1.1 10.5-.4 4.1-.8 8.6-1 10-.1 1.4-.6 5.9-.9 10-.4 4.1-.9 8.6-1.1 10-.4 3.1-1.3 11.3-2 20-.3 3.6-.8 8.1-1 10-.9 8.2-1.5 13.9-2 20-.3 3.6-.8 8.1-1 10-.2 1.9-.9 8.2-1.5 14-1 10.1-1.4 13.7-2.6 26-.8 8.4-1.4 14.1-1.9 18.5-.2 2.2-.7 6.7-1 10-.3 3.3-.8 7.8-1 10-.7 5.7-1.3 12-2.1 20.3l-.6 7.3 71.3-.4c39.3-.1 72.8-.6 74.4-1 20.2-5 37.1-15 49-29.3 2.8-3.2 5.2-6.1 5.5-6.4 2.1-2.1 8.7-13.2 12.8-21.5 7.9-15.8 13.9-35.9 15.7-52 .4-3.3.9-6.9 1.1-8 .6-2.7.5-28.3 0-31.5-.2-1.4-.7-5.2-1.1-8.5-1.3-12.4-8.8-35.9-13.5-42.5-1.1-1.6-2-3.2-2-3.7 0-1.5-7.6-11.4-13.3-17.2-8.1-8.3-17.8-14.5-28.6-18.3-12.5-4.3-17.4-4.6-83.9-4.7-34.8-.1-63.1.2-63 .6zm123.4 62.8c8.6 3.6 15.2 12.2 18.8 24.7 2.1 7.3 2.4 10 2.1 21.8-.5 28.4-7.9 51.2-20.6 64-8.7 8.6-9.4 8.8-47.3 8.8l-31.8.1.7-8.7c.3-4.8.8-9.4 1-10.2.2-.8.6-5.1 1-9.5.3-4.4.8-8.7 1-9.5.2-.8.6-5.3 1-10 .3-4.7.8-9.4 1-10.5.2-1.1.7-5.4 1-9.5.3-4.1 1-11.6 1.6-16.5 1.6-14.4 1.8-16.6 2.4-25 .4-4.4.9-8.8 1.1-9.8.5-1.6 2.9-1.7 32-1.7 25.6 0 32.1.3 35 1.5zm-72.8 223c-.1.1-2.1.6-4.3.9-4.3.6-5.1.8-10.4 2-5.2 1.2-15.5 6.1-21.9 10.3-11.7 7.8-25.6 24.2-33.2 39.2-7.1 14.2-14.5 39.1-16.6 56.2-.3 3-.8 6.7-1 8.4-.9 6.6-1.1 36.4-.3 42 2.1 14.5 3.3 20.5 6 28.7 6.8 20.8 17.9 36 34 46.4 3.7 2.4 7 4.4 7.3 4.4.4 0 2.5.9 4.9 2 2.4 1.1 9 3.1 14.7 4.4 10.1 2.3 12 2.4 47 2.4 40.4 0 47.6-.7 61.3-6 22.5-8.7 40.1-28.5 50.3-56.8 4-11.1 8.5-29.3 9.8-39 .3-2.5.8-5.5 1-6.7.3-1.2.8-5.3 1.2-9 .4-3.7.8-8.2 1-9.8.6-5.4.5-28.8-.2-35-3.1-30.9-16.9-57.6-36.7-71-8.1-5.5-21.2-11-31.7-13.1-3.4-.7-81.5-1.6-82.2-.9zm69.2 62.9c9 1.2 14 5.5 17.5 15.1 3.3 9 2.8 38.6-.9 63.1-1.3 8.3-6.1 23.2-9.3 28.6-5.1 8.6-11.8 13.1-21.7 14.2-7.8.9-55.8.7-59.8-.3-8.8-2.1-15-8.5-18.4-18.7-1.5-4.8-1.9-8.8-1.9-21.9 0-18.1.7-26.1 3.6-39 6.1-27.5 15.5-39.7 31.7-41.2 3.1-.3 5.8-.6 5.9-.7.6-.4 49.2.3 53.3.8zm-504.1-62c-.2.2-3.1.7-6.3 1.1-12.8 1.4-29.5 8.3-39.6 16.3-15.5 12.2-27 30.7-32.8 52.7-1.1 4.4-2.3 9.6-2.6 11.5-.3 1.9-.8 5.1-1.1 7-.5 3.8-1.1 9.6-2 20-.3 3.6-.8 7.6-1 9-.2 1.4-.7 5.9-1 10-.4 4.1-.8 8.6-1 10-.2 1.4-.7 5.9-1 10-.3 4.1-.8 8.6-1 10-.2 1.4-.6 5.9-1 10-.3 4.1-.8 8.6-1 10-.3 1.4-.7 5.9-1 10-.3 4.1-.7 8.6-1 10-.2 1.4-.7 5.8-1 9.8-.4 4.1-.8 8.1-1 9-.5 2.8-1.8 19.4-1.9 24.1l-.1 4.5h30.3c16.7.1 30.6-.2 30.9-.6.7-1.3 1.2-4.8 1.9-12.7.3-4.2.7-8.2.9-9.1.2-.9.6-5.6 1-10.6.4-4.9.9-9.6 1-10.4.2-.8.6-5.1 1-9.5s.8-9.1 1-10.5c.2-1.4.7-5.1 1-8.3l.7-5.7h4.7c2.5-.1 25.9-.2 51.9-.3l47.4-.2-.7 7c-.4 3.8-.8 8.1-1 9.5-.2 1.4-.6 5.9-1 10s-.8 8.6-1 10c-.2 1.4-.7 5.4-1 9-.3 3.6-.8 7.6-1 9-.2 1.4-.7 5.9-1 10-.3 4.1-.8 8.8-1.1 10.5l-.4 3 31-.1c17 0 31.2-.2 31.4-.5.2-.2.7-4.2 1.1-8.8.4-4.6.8-9.1 1-10 .1-.9.6-5 1-9.1s.9-8.2 1-9.2c.2-.9.6-5.2 1-9.5.3-4.3.8-8.9 1-10.3.2-1.4.7-5.9 1.1-10 1.1-11.7 1.1-12.1 1.8-19 .4-3.6.9-8.5 1.1-11 .4-3.9 2-18.5 3-27.5.2-1.7.6-6.6 1-11s.9-8.9 1.1-10c.8-4 .5-21.2-.5-28-4.1-28.4-20.7-51-46.1-62.7-5.8-2.7-13.4-5.3-18.5-6.2-1.6-.4-4.1-.9-5.5-1.3-2.7-.7-74.4-1.6-75.1-.9zM173 435c8 2.2 13.4 7.3 15.5 14.8.8 2.7.8 13 .1 18.2-.3 1.9-.8 6.2-1.1 9.5-.4 3.3-.9 6.2-1.2 6.5-.2.3-23.7.5-52 .6H82.8l.6-7.9c.4-4.4 1.3-11.4 2.2-15.6 3.8-18.4 10.6-25.4 26.4-26.9 11.1-1.2 56.1-.5 61 .8z"/><path d="M249.1 381.2c2.9 4.6 9.7 15.5 15.2 24.3s11 17.6 12.2 19.5c17.4 27.1 46.5 74.2 46.5 75.2 0 .8-16.4 25.3-43.5 64.8-2.8 4.1-8.7 12.7-13 19-4.3 6.3-11.2 16.3-15.2 22.3l-7.4 10.7h80.6l19.3-29.1 19.4-29 12.5 19.8c6.9 10.9 14 22 15.8 24.8 1.8 2.7 4.4 6.9 5.9 9.3l2.6 4.2h73l-8-11.8c-4.4-6.4-9.3-13.5-10.8-15.7-1.5-2.2-8-11.7-14.4-21-17.7-26-30.1-44.1-36.8-53.7-3.8-5.5-5.8-9.3-5.4-10 2-3.3 26-39.6 54.4-82.3 17.2-25.9 31.4-47.6 31.7-48.3.4-1-7.4-1.2-38.3-1l-38.8.3-13.7 21.5c-7.6 11.8-17.7 27.6-22.4 35-9.6 15-11.3 17.5-11.8 16.9-.2-.2-6.4-10.3-13.7-22.4-7.3-12.1-17.3-28.6-22.3-36.8l-8.9-14.7H244l5.1 8.2z"/></svg>
209                                 </a>
210                             </li>',
211             $errPage
212         );
213 
214         $errPage = preg_replace('@<button id="copy-button" .*?</button>@s', '$0<button class="clipboard" data-clipboard-text="'.rex_escape(self::getMarkdownReport($exception)).'" title="Copy exception details and system report as markdown to clipboard">
215       COPY MARKDOWN
216     </button>', $errPage);
217         $errPage = str_replace('<button id="copy-button"', '<button ', $errPage);
218 
219         return [$errPage, $handler->contentType()];
220     }
221 
222     /**
223      * Handles a error message.
224      *
225      * @param int    $errno   The error code to handle
226      * @param string $errstr  The error message
227      * @param string $errfile The file in which the error occured
228      * @param int    $errline The line of the file in which the error occured
229      *
230      * @throws ErrorException
231      */
232     public static function handleError($errno, $errstr, $errfile, $errline)
233     {
234         if (in_array($errno, [E_USER_ERROR, E_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR, E_PARSE])) {
235             throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
236         }
237 
238         // silenced errors ("@" operator)
239         if (0 === error_reporting()) {
240             return;
241         }
242 
243         $debug = rex::getDebugFlags();
244 
245         if (
246             isset($debug['throw_always_exception']) &&
247             (true === $debug['throw_always_exception'] || $errno === ($errno & $debug['throw_always_exception']))
248         ) {
249             throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
250         }
251 
252         if ((error_reporting() & $errno) !== $errno) {
253             return;
254         }
255 
256         if (ini_get('display_errors') && (rex::isSetup() || rex::isDebugMode() || ($user = rex_backend_login::createUser()) && $user->isAdmin())) {
257             $file = rex_path::relative($errfile);
258             if ('cli' === PHP_SAPI) {
259                 echo self::getErrorType($errno) . ": $errstr in $file on line $errline";
260             } else {
261                 $file = rex_escape($file);
262                 if ($url = rex_editor::factory()->getUrl($errfile, $errline)) {
263                     $file = '<a href="'.rex_escape($url).'">'.$file.'</a>';
264                 }
265                 echo '<div><b>' . self::getErrorType($errno) . '</b>: '.rex_escape($errstr)." in <b>$file</b> on line <b>$errline</b></div>";
266             }
267         }
268 
269         rex_logger::logError($errno, $errstr, $errfile, $errline);
270     }
271 
272     /**
273      * Shutdown-handler which is called at the very end of the request.
274      */
275     public static function shutdown()
276     {
277         // catch fatal/parse errors
278         if (self::$registered) {
279             $error = error_get_last();
280             if (is_array($error) && in_array($error['type'], [E_USER_ERROR, E_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR, E_PARSE])) {
281                 self::handleException(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
282             }
283         }
284     }
285 
286     /**
287      * Get a human readable string representing the given php error code.
288      *
289      * @param int $errno a php error code, e.g. E_ERROR
290      *
291      * @return string
292      */
293     public static function getErrorType($errno)
294     {
295         switch ($errno) {
296             case E_USER_ERROR:
297             case E_ERROR:
298             case E_COMPILE_ERROR:
299             case E_RECOVERABLE_ERROR:
300                 return 'Fatal error';
301 
302             case E_PARSE:
303                 return 'Parse error';
304 
305             case E_USER_WARNING:
306             case E_WARNING:
307             case E_COMPILE_WARNING:
308                 return 'Warning';
309 
310             case E_USER_NOTICE:
311             case E_NOTICE:
312                 return 'Notice';
313 
314             case E_USER_DEPRECATED:
315             case E_DEPRECATED:
316                 return 'Deprecated';
317 
318             case E_STRICT:
319                 return 'Strict';
320 
321             default:
322                 return 'Unknown';
323         }
324     }
325 
326     /**
327      * @param Throwable|Exception $exception
328      */
329     private static function getMarkdownReport($exception)
330     {
331         $file = rex_path::relative($exception->getFile());
332         $markdown = '**'.get_class($exception).":** {$exception->getMessage()}\n";
333         $markdown .= "**File:** {$file}\n";
334         $markdown .= "**Line:** {$exception->getLine()}\n\n";
335 
336         $trace = [];
337 
338         $headers = ['Function', 'File', 'Line'];
339         $widths = [mb_strlen($headers[0]), mb_strlen($headers[0]), mb_strlen($headers[0])];
340 
341         foreach ($exception->getTrace() as $frame) {
342             $function = $frame['function'];
343             if (isset($frame['class'])) {
344                 $function = $frame['class'].$frame['type'].$function;
345             }
346 
347             $file = isset($frame['file']) ? rex_path::relative($frame['file']) : '';
348             $line = isset($frame['line']) ? $frame['line'] : '';
349 
350             $trace[] = [$function, $file, $line];
351 
352             $widths[0] = max($widths[0], mb_strlen($function));
353             $widths[1] = max($widths[1], mb_strlen($file));
354             $widths[2] = max($widths[2], mb_strlen($line));
355         }
356 
357         $table = '| '.str_pad($headers[0], $widths[0]).' | '.str_pad($headers[1], $widths[1]).' | '.str_pad($headers[2], $widths[2])." |\n";
358         $table .= '| '.str_repeat('-', $widths[0]).' | '.str_repeat('-', $widths[1]).' | '.str_repeat('-', $widths[2])." |\n";
359 
360         foreach ($trace as $row) {
361             $table .= '| '.str_pad($row[0], $widths[0]).' | '.str_pad($row[1], $widths[1]).' | '.str_pad($row[2], $widths[2])." |\n";
362         }
363 
364         $markdown .= <<<OUTPUT
365 <details>
366 <summary>Stacktrace</summary>
367 
368 $table
369 </details>
370 
371 OUTPUT;
372         $markdown .= rex_system_report::factory()->asMarkdown();
373 
374         return $markdown;
375     }
376 }
377