Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
61 / 61 |
|
100.00% |
10 / 10 |
CRAP | |
100.00% |
1 / 1 |
| DeveloperToolbar | |
100.00% |
61 / 61 |
|
100.00% |
10 / 10 |
22 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| render | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| addJsonToolbar | |
100.00% |
45 / 45 |
|
100.00% |
1 / 1 |
12 | |||
| addHtmlToolbar | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
| getResponseCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getExitCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setResponseCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| addHeader | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setExitCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getHeaders | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Miniframe\Toolbar\Response; |
| 4 | |
| 5 | use Miniframe\Core\Response; |
| 6 | use Miniframe\Response\InternalServerErrorResponse; |
| 7 | use Miniframe\Response\PhpResponse; |
| 8 | use Miniframe\Toolbar\Service\DeveloperToolbar as DeveloperToolbarService; |
| 9 | |
| 10 | class DeveloperToolbar extends Response |
| 11 | { |
| 12 | /** |
| 13 | * The parent response. |
| 14 | * |
| 15 | * @var Response |
| 16 | */ |
| 17 | private $parentResponse; |
| 18 | /** |
| 19 | * URL for this debugging session. |
| 20 | * |
| 21 | * @var string |
| 22 | */ |
| 23 | private $debugUrl; |
| 24 | |
| 25 | /** |
| 26 | * Response that extends another response and adds the development toolbar to the output HTML |
| 27 | * |
| 28 | * @param Response $parentResponse The parent response. |
| 29 | * @param string $debugUrl URL for this debugging session. |
| 30 | */ |
| 31 | public function __construct(Response $parentResponse, string $debugUrl) |
| 32 | { |
| 33 | $this->parentResponse = $parentResponse; |
| 34 | $this->debugUrl = $debugUrl; |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Returns the response text |
| 39 | * |
| 40 | * @return string |
| 41 | */ |
| 42 | public function render(): string |
| 43 | { |
| 44 | return $this->addJsonToolbar($this->addHtmlToolbar($this->parentResponse->render())); |
| 45 | } |
| 46 | |
| 47 | /** |
| 48 | * Replaces the toolbar in existing json code |
| 49 | * |
| 50 | * @param string $jsonData The json code. |
| 51 | * |
| 52 | * @return string |
| 53 | */ |
| 54 | private function addJsonToolbar(string $jsonData): string |
| 55 | { |
| 56 | // Validate if we're dealing with json data |
| 57 | if ( |
| 58 | substr($jsonData, 0, 1) !== '{' |
| 59 | || substr($jsonData, -1) !== '}' |
| 60 | || !is_array(json_decode($jsonData, true, 255, JSON_INVALID_UTF8_IGNORE)) |
| 61 | ) { |
| 62 | return $jsonData; |
| 63 | } |
| 64 | $service = DeveloperToolbarService::getInstance(); |
| 65 | |
| 66 | // Detect how the json is pretty-printed |
| 67 | preg_match('/^\{([\r\n]*)([\s]*)/', $jsonData, $matches); |
| 68 | $eol = $matches[1] ?? ''; |
| 69 | $ws = $matches[2] ?? ''; |
| 70 | $parts = preg_split('/[\s]*\}$/', $jsonData); |
| 71 | |
| 72 | // Start adding a debugging element |
| 73 | $jsonData = $parts[0] . ',' . $eol; |
| 74 | $jsonData .= $ws . '"_debugging_' . array_reverse(explode('/', $this->debugUrl))[0] . '": {' . $eol; |
| 75 | $jsonData .= $ws . $ws . '"debug_url": "' . addslashes($this->debugUrl) . '"'; |
| 76 | |
| 77 | // Adding dumps |
| 78 | if (count($service->getDumps())) { |
| 79 | $jsonData .= ',' . $eol . $ws . $ws . '"dumps": ['; |
| 80 | foreach ($service->getDumps() as $iterator => $dump) { |
| 81 | if ($iterator !== 0) { |
| 82 | $jsonData .= ','; |
| 83 | } |
| 84 | $jsonData .= $eol . $ws . $ws . $ws . '{' . $eol; |
| 85 | $jsonData .= $ws . $ws . $ws . $ws . '"file": ' . json_encode($dump['file']) . ',' . $eol; |
| 86 | $jsonData .= $ws . $ws . $ws . $ws . '"line": ' . intval($dump['line']) . ',' . $eol; |
| 87 | $jsonData .= $ws . $ws . $ws . $ws . '"data": ' . json_encode($dump['data']) . $eol; |
| 88 | $jsonData .= $ws . $ws . $ws . '}'; |
| 89 | } |
| 90 | $jsonData .= $eol . $ws . $ws . ']'; |
| 91 | } |
| 92 | |
| 93 | // Adding errors |
| 94 | if (count($service->getErrors())) { |
| 95 | $jsonData .= ',' . $eol . $ws . $ws . '"errors": ['; |
| 96 | foreach ($service->getErrors() as $iterator => $error) { |
| 97 | if ($iterator !== 0) { |
| 98 | $jsonData .= ','; |
| 99 | } |
| 100 | $jsonData .= $eol . $ws . $ws . $ws . '{' . $eol; |
| 101 | $jsonData .= $ws . $ws . $ws . $ws . '"errtype": "' . addslashes($error['errtype']) . '",' . $eol; |
| 102 | $jsonData .= $ws . $ws . $ws . $ws . '"file": "' . addslashes($error['file']) . '",' . $eol; |
| 103 | $jsonData .= $ws . $ws . $ws . $ws . '"line": ' . intval($error['line']) . ',' . $eol; |
| 104 | $jsonData .= $ws . $ws . $ws . $ws . '"message": ' . json_encode($error['message']) . $eol; |
| 105 | $jsonData .= $ws . $ws . $ws . '}'; |
| 106 | } |
| 107 | $jsonData .= $eol . $ws . $ws . ']'; |
| 108 | } |
| 109 | |
| 110 | // Add backtrace if available |
| 111 | if ($this->parentResponse instanceof InternalServerErrorResponse || $this->parentResponse->getPrevious()) { |
| 112 | $exception = $this->parentResponse->getPrevious() ?? $this->parentResponse; |
| 113 | $jsonData .= ',' . $eol . $ws . $ws . '"exception": ' . json_encode(get_class($exception)); |
| 114 | $jsonData .= ',' . $eol . $ws . $ws . '"message": ' . json_encode($exception->getMessage()); |
| 115 | $jsonData .= ',' . $eol . $ws . $ws . '"code": ' . $exception->getCode(); |
| 116 | $jsonData .= ',' . $eol . $ws . $ws . '"file": ' . json_encode($exception->getFile()); |
| 117 | $jsonData .= ',' . $eol . $ws . $ws . '"line": ' . $exception->getLine(); |
| 118 | $jsonData .= ',' . $eol . $ws . $ws . '"backtrace": ' . json_encode($exception->getTraceAsString()); |
| 119 | } |
| 120 | |
| 121 | // Close debugging element and json |
| 122 | $jsonData .= $eol . $ws . '}' . $eol . '}'; |
| 123 | |
| 124 | return $jsonData; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Replaces the toolbar in existing HTML code |
| 129 | * |
| 130 | * @param string $htmlData The HTML code. |
| 131 | * |
| 132 | * @return string |
| 133 | */ |
| 134 | private function addHtmlToolbar(string $htmlData): string |
| 135 | { |
| 136 | // Look for HTML data, that contains a body |
| 137 | if (!preg_match('/<html.*>.*?<\/body>/is', $htmlData)) { |
| 138 | return $htmlData; |
| 139 | } |
| 140 | |
| 141 | // Renders the toolbar HTML |
| 142 | $insertResponse = new PhpResponse(__DIR__ . '/../../templates/toolbar_insert.html.php', [ |
| 143 | 'debugUrl' => $this->debugUrl, |
| 144 | 'bugiconUrl' => substr($this->debugUrl, 0, strrpos($this->debugUrl, '/')) . '/bugicon.svg', |
| 145 | ]); |
| 146 | $html = $insertResponse->render(); |
| 147 | |
| 148 | return preg_replace('/<\/body>/i', $html . '$0', $htmlData, 1); |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Returns the current response code (used for HTTP responses) |
| 153 | * |
| 154 | * @return integer |
| 155 | */ |
| 156 | public function getResponseCode(): int |
| 157 | { |
| 158 | return $this->parentResponse->getResponseCode(); |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Returns the current exit code (used for shell error codes) |
| 163 | * |
| 164 | * @return integer |
| 165 | */ |
| 166 | public function getExitCode(): int |
| 167 | { |
| 168 | return $this->parentResponse->getExitCode(); |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Sets a new response code (used for HTTP responses) |
| 173 | * |
| 174 | * @param integer $responseCode The new response code. |
| 175 | * |
| 176 | * @return void |
| 177 | */ |
| 178 | public function setResponseCode(int $responseCode): void |
| 179 | { |
| 180 | $this->parentResponse->setResponseCode($responseCode); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Adds a response header |
| 185 | * |
| 186 | * @param string $header The header string. |
| 187 | * @param boolean $replace When set to false, a header can be sent back multiple times. |
| 188 | * |
| 189 | * @return void |
| 190 | */ |
| 191 | public function addHeader(string $header, bool $replace = true): void |
| 192 | { |
| 193 | $this->parentResponse->addHeader($header, $replace); |
| 194 | } |
| 195 | |
| 196 | /** |
| 197 | * Sets a new exit code (used for shell error codes) |
| 198 | * |
| 199 | * @param integer $exitCode Error code. |
| 200 | * |
| 201 | * @return void |
| 202 | */ |
| 203 | public function setExitCode(int $exitCode): void |
| 204 | { |
| 205 | $this->parentResponse->setExitCode($exitCode); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Returns a list of all headers to set (each record has two keys: 'header' and 'replace') |
| 210 | * |
| 211 | * @return array[] |
| 212 | */ |
| 213 | public function getHeaders(): array |
| 214 | { |
| 215 | return $this->parentResponse->getHeaders(); |
| 216 | } |
| 217 | } |