Translated using Weblate (Slovenian)
[phpmyadmin.git] / libraries / classes / Response.php
blobf2f3933afbf4962bdc46a450b38e49948f8d8338
1 <?php
2 /**
3 * Manages the rendering of pages in PMA
4 */
6 declare(strict_types=1);
8 namespace PhpMyAdmin;
10 use const JSON_ERROR_CTRL_CHAR;
11 use const JSON_ERROR_DEPTH;
12 use const JSON_ERROR_INF_OR_NAN;
13 use const JSON_ERROR_NONE;
14 use const JSON_ERROR_RECURSION;
15 use const JSON_ERROR_STATE_MISMATCH;
16 use const JSON_ERROR_SYNTAX;
17 use const JSON_ERROR_UNSUPPORTED_TYPE;
18 use const JSON_ERROR_UTF8;
19 use const PHP_SAPI;
20 use function defined;
21 use function explode;
22 use function headers_sent;
23 use function http_response_code;
24 use function in_array;
25 use function is_array;
26 use function json_encode;
27 use function json_last_error;
28 use function mb_strlen;
29 use function register_shutdown_function;
30 use function strlen;
32 /**
33 * Singleton class used to manage the rendering of pages in PMA
35 class Response
37 /**
38 * Response instance
40 * @access private
41 * @static
42 * @var Response
44 private static $instance;
45 /**
46 * Header instance
48 * @access private
49 * @var Header
51 private $header;
52 /**
53 * HTML data to be used in the response
55 * @access private
56 * @var string
58 private $HTML;
59 /**
60 * An array of JSON key-value pairs
61 * to be sent back for ajax requests
63 * @access private
64 * @var array
66 private $JSON;
67 /**
68 * PhpMyAdmin\Footer instance
70 * @access private
71 * @var Footer
73 private $footer;
74 /**
75 * Whether we are servicing an ajax request.
77 * @access private
78 * @var bool
80 private $isAjax;
81 /**
82 * Whether response object is disabled
84 * @access private
85 * @var bool
87 private $isDisabled;
88 /**
89 * Whether there were any errors during the processing of the request
90 * Only used for ajax responses
92 * @access private
93 * @var bool
95 private $isSuccess;
97 /**
98 * @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
100 * @var array<int, string>
102 protected static $httpStatusMessages = [
103 // Informational
104 100 => 'Continue',
105 101 => 'Switching Protocols',
106 102 => 'Processing',
107 103 => 'Early Hints',
108 // Success
109 200 => 'OK',
110 201 => 'Created',
111 202 => 'Accepted',
112 203 => 'Non-Authoritative Information',
113 204 => 'No Content',
114 205 => 'Reset Content',
115 206 => 'Partial Content',
116 207 => 'Multi-Status',
117 208 => 'Already Reported',
118 226 => 'IM Used',
119 // Redirection
120 300 => 'Multiple Choices',
121 301 => 'Moved Permanently',
122 302 => 'Found',
123 303 => 'See Other',
124 304 => 'Not Modified',
125 305 => 'Use Proxy',
126 307 => 'Temporary Redirect',
127 308 => 'Permanent Redirect',
128 // Client Error
129 400 => 'Bad Request',
130 401 => 'Unauthorized',
131 402 => 'Payment Required',
132 403 => 'Forbidden',
133 404 => 'Not Found',
134 405 => 'Method Not Allowed',
135 406 => 'Not Acceptable',
136 407 => 'Proxy Authentication Required',
137 408 => 'Request Timeout',
138 409 => 'Conflict',
139 410 => 'Gone',
140 411 => 'Length Required',
141 412 => 'Precondition Failed',
142 413 => 'Payload Too Large',
143 414 => 'URI Too Long',
144 415 => 'Unsupported Media Type',
145 416 => 'Range Not Satisfiable',
146 417 => 'Expectation Failed',
147 421 => 'Misdirected Request',
148 422 => 'Unprocessable Entity',
149 423 => 'Locked',
150 424 => 'Failed Dependency',
151 425 => 'Too Early',
152 426 => 'Upgrade Required',
153 427 => 'Unassigned',
154 428 => 'Precondition Required',
155 429 => 'Too Many Requests',
156 430 => 'Unassigned',
157 431 => 'Request Header Fields Too Large',
158 451 => 'Unavailable For Legal Reasons',
159 // Server Error
160 500 => 'Internal Server Error',
161 501 => 'Not Implemented',
162 502 => 'Bad Gateway',
163 503 => 'Service Unavailable',
164 504 => 'Gateway Timeout',
165 505 => 'HTTP Version Not Supported',
166 506 => 'Variant Also Negotiates',
167 507 => 'Insufficient Storage',
168 508 => 'Loop Detected',
169 509 => 'Unassigned',
170 510 => 'Not Extended',
171 511 => 'Network Authentication Required',
175 * Creates a new class instance
177 private function __construct()
179 if (! defined('TESTSUITE')) {
180 $buffer = OutputBuffering::getInstance();
181 $buffer->start();
182 register_shutdown_function([$this, 'response']);
184 $this->header = new Header();
185 $this->HTML = '';
186 $this->JSON = [];
187 $this->footer = new Footer();
189 $this->isSuccess = true;
190 $this->isDisabled = false;
191 $this->setAjax(! empty($_REQUEST['ajax_request']));
195 * Set the ajax flag to indicate whether
196 * we are servicing an ajax request
198 * @param bool $isAjax Whether we are servicing an ajax request
200 public function setAjax(bool $isAjax): void
202 $this->isAjax = $isAjax;
203 $this->header->setAjax($this->isAjax);
204 $this->footer->setAjax($this->isAjax);
208 * Returns the singleton Response object
210 * @return Response object
212 public static function getInstance()
214 if (empty(self::$instance)) {
215 self::$instance = new Response();
218 return self::$instance;
222 * Set the status of an ajax response,
223 * whether it is a success or an error
225 * @param bool $state Whether the request was successfully processed
227 public function setRequestStatus(bool $state): void
229 $this->isSuccess = ($state === true);
233 * Returns true or false depending on whether
234 * we are servicing an ajax request
236 public function isAjax(): bool
238 return $this->isAjax;
242 * Disables the rendering of the header
243 * and the footer in responses
245 * @return void
247 public function disable()
249 $this->header->disable();
250 $this->footer->disable();
251 $this->isDisabled = true;
255 * Returns a PhpMyAdmin\Header object
257 * @return Header
259 public function getHeader()
261 return $this->header;
265 * Returns a PhpMyAdmin\Footer object
267 * @return Footer
269 public function getFooter()
271 return $this->footer;
275 * Add HTML code to the response
277 * @param string $content A string to be appended to
278 * the current output buffer
280 * @return void
282 public function addHTML($content)
284 if (is_array($content)) {
285 foreach ($content as $msg) {
286 $this->addHTML($msg);
288 } elseif ($content instanceof Message) {
289 $this->HTML .= $content->getDisplay();
290 } else {
291 $this->HTML .= $content;
296 * Add JSON code to the response
298 * @param mixed $json Either a key (string) or an
299 * array or key-value pairs
300 * @param mixed $value Null, if passing an array in $json otherwise
301 * it's a string value to the key
303 * @return void
305 public function addJSON($json, $value = null)
307 if (is_array($json)) {
308 foreach ($json as $key => $value) {
309 $this->addJSON($key, $value);
311 } else {
312 if ($value instanceof Message) {
313 $this->JSON[$json] = $value->getDisplay();
314 } else {
315 $this->JSON[$json] = $value;
321 * Renders the HTML response text
323 * @return string
325 private function getDisplay()
327 // The header may contain nothing at all,
328 // if its content was already rendered
329 // and, in this case, the header will be
330 // in the content part of the request
331 $retval = $this->header->getDisplay();
332 $retval .= $this->HTML;
333 $retval .= $this->footer->getDisplay();
335 return $retval;
339 * Sends an HTML response to the browser
341 * @return void
343 private function htmlResponse()
345 echo $this->getDisplay();
349 * Sends a JSON response to the browser
351 * @return void
353 private function ajaxResponse()
355 global $dbi;
357 /* Avoid wrapping in case we're disabled */
358 if ($this->isDisabled) {
359 echo $this->getDisplay();
361 return;
364 if (! isset($this->JSON['message'])) {
365 $this->JSON['message'] = $this->getDisplay();
366 } elseif ($this->JSON['message'] instanceof Message) {
367 $this->JSON['message'] = $this->JSON['message']->getDisplay();
370 if ($this->isSuccess) {
371 $this->JSON['success'] = true;
372 } else {
373 $this->JSON['success'] = false;
374 $this->JSON['error'] = $this->JSON['message'];
375 unset($this->JSON['message']);
378 if ($this->isSuccess) {
379 if (! isset($this->JSON['title'])) {
380 $this->addJSON('title', '<title>' . $this->getHeader()->getPageTitle() . '</title>');
383 if (isset($dbi)) {
384 $menuHash = $this->getHeader()->getMenu()->getHash();
385 $this->addJSON('menuHash', $menuHash);
386 $hashes = [];
387 if (isset($_REQUEST['menuHashes'])) {
388 $hashes = explode('-', $_REQUEST['menuHashes']);
390 if (! in_array($menuHash, $hashes)) {
391 $this->addJSON(
392 'menu',
393 $this->getHeader()
394 ->getMenu()
395 ->getDisplay()
400 $this->addJSON('scripts', $this->getHeader()->getScripts()->getFiles());
401 $this->addJSON('selflink', $this->getFooter()->getSelfUrl());
402 $this->addJSON('displayMessage', $this->getHeader()->getMessage());
404 $debug = $this->footer->getDebugMessage();
405 if (empty($_REQUEST['no_debug'])
406 && strlen($debug) > 0
408 $this->addJSON('debug', $debug);
411 $errors = $this->footer->getErrorMessages();
412 if (strlen($errors) > 0) {
413 $this->addJSON('errors', $errors);
415 $promptPhpErrors = $GLOBALS['error_handler']->hasErrorsForPrompt();
416 $this->addJSON('promptPhpErrors', $promptPhpErrors);
418 if (empty($GLOBALS['error_message'])) {
419 // set current db, table and sql query in the querywindow
420 // (this is for the bottom console)
421 $query = '';
422 $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
423 if (isset($GLOBALS['sql_query'])
424 && mb_strlen($GLOBALS['sql_query']) < $maxChars
426 $query = $GLOBALS['sql_query'];
428 $this->addJSON(
429 'reloadQuerywindow',
431 'db' => Core::ifSetOr($GLOBALS['db'], ''),
432 'table' => Core::ifSetOr($GLOBALS['table'], ''),
433 'sql_query' => $query,
436 if (! empty($GLOBALS['focus_querywindow'])) {
437 $this->addJSON('_focusQuerywindow', $query);
439 if (! empty($GLOBALS['reload'])) {
440 $this->addJSON('reloadNavigation', 1);
442 $this->addJSON('params', $this->getHeader()->getJsParams());
446 // Set the Content-Type header to JSON so that jQuery parses the
447 // response correctly.
448 Core::headerJSON();
450 $result = json_encode($this->JSON);
451 if ($result === false) {
452 switch (json_last_error()) {
453 case JSON_ERROR_NONE:
454 $error = 'No errors';
455 break;
456 case JSON_ERROR_DEPTH:
457 $error = 'Maximum stack depth exceeded';
458 break;
459 case JSON_ERROR_STATE_MISMATCH:
460 $error = 'Underflow or the modes mismatch';
461 break;
462 case JSON_ERROR_CTRL_CHAR:
463 $error = 'Unexpected control character found';
464 break;
465 case JSON_ERROR_SYNTAX:
466 $error = 'Syntax error, malformed JSON';
467 break;
468 case JSON_ERROR_UTF8:
469 $error = 'Malformed UTF-8 characters, possibly incorrectly encoded';
470 break;
471 case JSON_ERROR_RECURSION:
472 $error = 'One or more recursive references in the value to be encoded';
473 break;
474 case JSON_ERROR_INF_OR_NAN:
475 $error = 'One or more NAN or INF values in the value to be encoded';
476 break;
477 case JSON_ERROR_UNSUPPORTED_TYPE:
478 $error = 'A value of a type that cannot be encoded was given';
479 break;
480 default:
481 $error = 'Unknown error';
482 break;
484 echo json_encode([
485 'success' => false,
486 'error' => 'JSON encoding failed: ' . $error,
488 } else {
489 echo $result;
494 * Sends an HTML response to the browser
496 * @return void
498 public function response()
500 $buffer = OutputBuffering::getInstance();
501 if (empty($this->HTML)) {
502 $this->HTML = $buffer->getContents();
504 if ($this->isAjax()) {
505 $this->ajaxResponse();
506 } else {
507 $this->htmlResponse();
509 $buffer->flush();
510 exit;
514 * Wrapper around PHP's header() function.
516 * @param string $text header string
518 * @return void
520 public function header($text)
522 // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
523 header($text);
527 * Wrapper around PHP's headers_sent() function.
529 * @return bool
531 public function headersSent()
533 return headers_sent();
537 * Wrapper around PHP's http_response_code() function.
539 * @param int $response_code will set the response code.
541 * @return void
543 public function httpResponseCode($response_code)
545 http_response_code($response_code);
549 * Sets http response code.
551 * @param int $responseCode will set the response code.
553 public function setHttpResponseCode(int $responseCode): void
555 $this->httpResponseCode($responseCode);
556 $header = 'status: ' . $responseCode . ' ';
557 if (isset(static::$httpStatusMessages[$responseCode])) {
558 $header .= static::$httpStatusMessages[$responseCode];
559 } else {
560 $header .= 'Web server is down';
562 if (PHP_SAPI === 'cgi-fcgi') {
563 return;
566 $this->header($header);
570 * Generate header for 303
572 * @param string $location will set location to redirect.
574 * @return void
576 public function generateHeader303($location)
578 $this->setHttpResponseCode(303);
579 $this->header('Location: ' . $location);
580 if (! defined('TESTSUITE')) {
581 exit;
586 * Configures response for the login page
588 * @return bool Whether caller should exit
590 public function loginPage()
592 /* Handle AJAX redirection */
593 if ($this->isAjax()) {
594 $this->setRequestStatus(false);
595 // redirect_flag redirects to the login page
596 $this->addJSON('redirect_flag', '1');
598 return true;
601 $this->getFooter()->setMinimal();
602 $header = $this->getHeader();
603 $header->setBodyId('loginform');
604 $header->setTitle('phpMyAdmin');
605 $header->disableMenuAndConsole();
606 $header->disableWarnings();
608 return false;