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 | } |