Response.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Manages the rendering of pages in PMA
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin;
  9. use PhpMyAdmin\Core;
  10. use PhpMyAdmin\Footer;
  11. use PhpMyAdmin\Header;
  12. use PhpMyAdmin\Message;
  13. use PhpMyAdmin\OutputBuffering;
  14. /**
  15. * Singleton class used to manage the rendering of pages in PMA
  16. *
  17. * @package PhpMyAdmin
  18. */
  19. class Response
  20. {
  21. /**
  22. * Response instance
  23. *
  24. * @access private
  25. * @static
  26. * @var Response
  27. */
  28. private static $_instance;
  29. /**
  30. * Header instance
  31. *
  32. * @access private
  33. * @var Header
  34. */
  35. private $_header;
  36. /**
  37. * HTML data to be used in the response
  38. *
  39. * @access private
  40. * @var string
  41. */
  42. private $_HTML;
  43. /**
  44. * An array of JSON key-value pairs
  45. * to be sent back for ajax requests
  46. *
  47. * @access private
  48. * @var array
  49. */
  50. private $_JSON;
  51. /**
  52. * PhpMyAdmin\Footer instance
  53. *
  54. * @access private
  55. * @var Footer
  56. */
  57. private $_footer;
  58. /**
  59. * Whether we are servicing an ajax request.
  60. *
  61. * @access private
  62. * @var bool
  63. */
  64. private $_isAjax;
  65. /**
  66. * Whether response object is disabled
  67. *
  68. * @access private
  69. * @var bool
  70. */
  71. private $_isDisabled;
  72. /**
  73. * Whether there were any errors during the processing of the request
  74. * Only used for ajax responses
  75. *
  76. * @access private
  77. * @var bool
  78. */
  79. private $_isSuccess;
  80. /**
  81. * Workaround for PHP bug
  82. *
  83. * @access private
  84. * @var string|bool
  85. */
  86. private $_CWD;
  87. /**
  88. * Creates a new class instance
  89. */
  90. private function __construct()
  91. {
  92. if (! defined('TESTSUITE')) {
  93. $buffer = OutputBuffering::getInstance();
  94. $buffer->start();
  95. register_shutdown_function(array($this, 'response'));
  96. }
  97. $this->_header = new Header();
  98. $this->_HTML = '';
  99. $this->_JSON = array();
  100. $this->_footer = new Footer();
  101. $this->_isSuccess = true;
  102. $this->_isDisabled = false;
  103. $this->setAjax(! empty($_REQUEST['ajax_request']));
  104. $this->_CWD = getcwd();
  105. }
  106. /**
  107. * Set the ajax flag to indicate whether
  108. * we are servicing an ajax request
  109. *
  110. * @param bool $isAjax Whether we are servicing an ajax request
  111. *
  112. * @return void
  113. */
  114. public function setAjax($isAjax)
  115. {
  116. $this->_isAjax = (boolean) $isAjax;
  117. $this->_header->setAjax($this->_isAjax);
  118. $this->_footer->setAjax($this->_isAjax);
  119. }
  120. /**
  121. * Returns the singleton Response object
  122. *
  123. * @return Response object
  124. */
  125. public static function getInstance()
  126. {
  127. if (empty(self::$_instance)) {
  128. self::$_instance = new Response();
  129. }
  130. return self::$_instance;
  131. }
  132. /**
  133. * Set the status of an ajax response,
  134. * whether it is a success or an error
  135. *
  136. * @param bool $state Whether the request was successfully processed
  137. *
  138. * @return void
  139. */
  140. public function setRequestStatus($state)
  141. {
  142. $this->_isSuccess = ($state == true);
  143. }
  144. /**
  145. * Returns true or false depending on whether
  146. * we are servicing an ajax request
  147. *
  148. * @return bool
  149. */
  150. public function isAjax()
  151. {
  152. return $this->_isAjax;
  153. }
  154. /**
  155. * Returns the path to the current working directory
  156. * Necessary to work around a PHP bug where the CWD is
  157. * reset after the initial script exits
  158. *
  159. * @return string
  160. */
  161. public function getCWD()
  162. {
  163. return $this->_CWD;
  164. }
  165. /**
  166. * Disables the rendering of the header
  167. * and the footer in responses
  168. *
  169. * @return void
  170. */
  171. public function disable()
  172. {
  173. $this->_header->disable();
  174. $this->_footer->disable();
  175. $this->_isDisabled = true;
  176. }
  177. /**
  178. * Returns a PhpMyAdmin\Header object
  179. *
  180. * @return Header
  181. */
  182. public function getHeader()
  183. {
  184. return $this->_header;
  185. }
  186. /**
  187. * Returns a PhpMyAdmin\Footer object
  188. *
  189. * @return Footer
  190. */
  191. public function getFooter()
  192. {
  193. return $this->_footer;
  194. }
  195. /**
  196. * Add HTML code to the response
  197. *
  198. * @param string $content A string to be appended to
  199. * the current output buffer
  200. *
  201. * @return void
  202. */
  203. public function addHTML($content)
  204. {
  205. if (is_array($content)) {
  206. foreach ($content as $msg) {
  207. $this->addHTML($msg);
  208. }
  209. } elseif ($content instanceof Message) {
  210. $this->_HTML .= $content->getDisplay();
  211. } else {
  212. $this->_HTML .= $content;
  213. }
  214. }
  215. /**
  216. * Add JSON code to the response
  217. *
  218. * @param mixed $json Either a key (string) or an
  219. * array or key-value pairs
  220. * @param mixed $value Null, if passing an array in $json otherwise
  221. * it's a string value to the key
  222. *
  223. * @return void
  224. */
  225. public function addJSON($json, $value = null)
  226. {
  227. if (is_array($json)) {
  228. foreach ($json as $key => $value) {
  229. $this->addJSON($key, $value);
  230. }
  231. } else {
  232. if ($value instanceof Message) {
  233. $this->_JSON[$json] = $value->getDisplay();
  234. } else {
  235. $this->_JSON[$json] = $value;
  236. }
  237. }
  238. }
  239. /**
  240. * Renders the HTML response text
  241. *
  242. * @return string
  243. */
  244. private function _getDisplay()
  245. {
  246. // The header may contain nothing at all,
  247. // if its content was already rendered
  248. // and, in this case, the header will be
  249. // in the content part of the request
  250. $retval = $this->_header->getDisplay();
  251. $retval .= $this->_HTML;
  252. $retval .= $this->_footer->getDisplay();
  253. return $retval;
  254. }
  255. /**
  256. * Sends an HTML response to the browser
  257. *
  258. * @return void
  259. */
  260. private function _htmlResponse()
  261. {
  262. echo $this->_getDisplay();
  263. }
  264. /**
  265. * Sends a JSON response to the browser
  266. *
  267. * @return void
  268. */
  269. private function _ajaxResponse()
  270. {
  271. /* Avoid wrapping in case we're disabled */
  272. if ($this->_isDisabled) {
  273. echo $this->_getDisplay();
  274. return;
  275. }
  276. if (! isset($this->_JSON['message'])) {
  277. $this->_JSON['message'] = $this->_getDisplay();
  278. } elseif ($this->_JSON['message'] instanceof Message) {
  279. $this->_JSON['message'] = $this->_JSON['message']->getDisplay();
  280. }
  281. if ($this->_isSuccess) {
  282. $this->_JSON['success'] = true;
  283. } else {
  284. $this->_JSON['success'] = false;
  285. $this->_JSON['error'] = $this->_JSON['message'];
  286. unset($this->_JSON['message']);
  287. }
  288. if ($this->_isSuccess) {
  289. $this->addJSON('_title', $this->getHeader()->getTitleTag());
  290. if (isset($GLOBALS['dbi'])) {
  291. $menuHash = $this->getHeader()->getMenu()->getHash();
  292. $this->addJSON('_menuHash', $menuHash);
  293. $hashes = array();
  294. if (isset($_REQUEST['menuHashes'])) {
  295. $hashes = explode('-', $_REQUEST['menuHashes']);
  296. }
  297. if (! in_array($menuHash, $hashes)) {
  298. $this->addJSON(
  299. '_menu',
  300. $this->getHeader()
  301. ->getMenu()
  302. ->getDisplay()
  303. );
  304. }
  305. }
  306. $this->addJSON('_scripts', $this->getHeader()->getScripts()->getFiles());
  307. $this->addJSON('_selflink', $this->getFooter()->getSelfUrl());
  308. $this->addJSON('_displayMessage', $this->getHeader()->getMessage());
  309. $debug = $this->_footer->getDebugMessage();
  310. if (empty($_REQUEST['no_debug'])
  311. && strlen($debug) > 0
  312. ) {
  313. $this->addJSON('_debug', $debug);
  314. }
  315. $errors = $this->_footer->getErrorMessages();
  316. if (strlen($errors) > 0) {
  317. $this->addJSON('_errors', $errors);
  318. }
  319. $promptPhpErrors = $GLOBALS['error_handler']->hasErrorsForPrompt();
  320. $this->addJSON('_promptPhpErrors', $promptPhpErrors);
  321. if (empty($GLOBALS['error_message'])) {
  322. // set current db, table and sql query in the querywindow
  323. // (this is for the bottom console)
  324. $query = '';
  325. $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
  326. if (isset($GLOBALS['sql_query'])
  327. && mb_strlen($GLOBALS['sql_query']) < $maxChars
  328. ) {
  329. $query = $GLOBALS['sql_query'];
  330. }
  331. $this->addJSON(
  332. '_reloadQuerywindow',
  333. array(
  334. 'db' => Core::ifSetOr($GLOBALS['db'], ''),
  335. 'table' => Core::ifSetOr($GLOBALS['table'], ''),
  336. 'sql_query' => $query
  337. )
  338. );
  339. if (! empty($GLOBALS['focus_querywindow'])) {
  340. $this->addJSON('_focusQuerywindow', $query);
  341. }
  342. if (! empty($GLOBALS['reload'])) {
  343. $this->addJSON('_reloadNavigation', 1);
  344. }
  345. $this->addJSON('_params', $this->getHeader()->getJsParams());
  346. }
  347. }
  348. // Set the Content-Type header to JSON so that jQuery parses the
  349. // response correctly.
  350. Core::headerJSON();
  351. $result = json_encode($this->_JSON);
  352. if ($result === false) {
  353. switch (json_last_error()) {
  354. case JSON_ERROR_NONE:
  355. $error = 'No errors';
  356. break;
  357. case JSON_ERROR_DEPTH:
  358. $error = 'Maximum stack depth exceeded';
  359. break;
  360. case JSON_ERROR_STATE_MISMATCH:
  361. $error = 'Underflow or the modes mismatch';
  362. break;
  363. case JSON_ERROR_CTRL_CHAR:
  364. $error = 'Unexpected control character found';
  365. break;
  366. case JSON_ERROR_SYNTAX:
  367. $error = 'Syntax error, malformed JSON';
  368. break;
  369. case JSON_ERROR_UTF8:
  370. $error = 'Malformed UTF-8 characters, possibly incorrectly encoded';
  371. break;
  372. case JSON_ERROR_RECURSION:
  373. $error = 'One or more recursive references in the value to be encoded';
  374. break;
  375. case JSON_ERROR_INF_OR_NAN:
  376. $error = 'One or more NAN or INF values in the value to be encoded';
  377. break;
  378. case JSON_ERROR_UNSUPPORTED_TYPE:
  379. $error = 'A value of a type that cannot be encoded was given';
  380. default:
  381. $error = 'Unknown error';
  382. break;
  383. }
  384. echo json_encode(
  385. array(
  386. 'success' => false,
  387. 'error' => 'JSON encoding failed: ' . $error,
  388. )
  389. );
  390. } else {
  391. echo $result;
  392. }
  393. }
  394. /**
  395. * Sends an HTML response to the browser
  396. *
  397. * @return void
  398. */
  399. public function response()
  400. {
  401. chdir($this->getCWD());
  402. $buffer = OutputBuffering::getInstance();
  403. if (empty($this->_HTML)) {
  404. $this->_HTML = $buffer->getContents();
  405. }
  406. if ($this->isAjax()) {
  407. $this->_ajaxResponse();
  408. } else {
  409. $this->_htmlResponse();
  410. }
  411. $buffer->flush();
  412. exit;
  413. }
  414. /**
  415. * Wrapper around PHP's header() function.
  416. *
  417. * @param string $text header string
  418. *
  419. * @return void
  420. */
  421. public function header($text)
  422. {
  423. header($text);
  424. }
  425. /**
  426. * Wrapper around PHP's headers_sent() function.
  427. *
  428. * @return bool
  429. */
  430. public function headersSent()
  431. {
  432. return headers_sent();
  433. }
  434. /**
  435. * Wrapper around PHP's http_response_code() function.
  436. *
  437. * @param int $response_code will set the response code.
  438. *
  439. * @return void
  440. */
  441. public function httpResponseCode($response_code)
  442. {
  443. http_response_code($response_code);
  444. }
  445. /**
  446. * Sets http response code.
  447. *
  448. * @param int $response_code will set the response code.
  449. *
  450. * @return void
  451. */
  452. public function setHttpResponseCode($response_code)
  453. {
  454. $this->httpResponseCode($response_code);
  455. switch ($response_code) {
  456. case 100: $httpStatusMsg = ' Continue'; break;
  457. case 101: $httpStatusMsg = ' Switching Protocols'; break;
  458. case 200: $httpStatusMsg = ' OK'; break;
  459. case 201: $httpStatusMsg = ' Created'; break;
  460. case 202: $httpStatusMsg = ' Accepted'; break;
  461. case 203: $httpStatusMsg = ' Non-Authoritative Information'; break;
  462. case 204: $httpStatusMsg = ' No Content'; break;
  463. case 205: $httpStatusMsg = ' Reset Content'; break;
  464. case 206: $httpStatusMsg = ' Partial Content'; break;
  465. case 300: $httpStatusMsg = ' Multiple Choices'; break;
  466. case 301: $httpStatusMsg = ' Moved Permanently'; break;
  467. case 302: $httpStatusMsg = ' Moved Temporarily'; break;
  468. case 303: $httpStatusMsg = ' See Other'; break;
  469. case 304: $httpStatusMsg = ' Not Modified'; break;
  470. case 305: $httpStatusMsg = ' Use Proxy'; break;
  471. case 400: $httpStatusMsg = ' Bad Request'; break;
  472. case 401: $httpStatusMsg = ' Unauthorized'; break;
  473. case 402: $httpStatusMsg = ' Payment Required'; break;
  474. case 403: $httpStatusMsg = ' Forbidden'; break;
  475. case 404: $httpStatusMsg = ' Not Found'; break;
  476. case 405: $httpStatusMsg = ' Method Not Allowed'; break;
  477. case 406: $httpStatusMsg = ' Not Acceptable'; break;
  478. case 407: $httpStatusMsg = ' Proxy Authentication Required'; break;
  479. case 408: $httpStatusMsg = ' Request Time-out'; break;
  480. case 409: $httpStatusMsg = ' Conflict'; break;
  481. case 410: $httpStatusMsg = ' Gone'; break;
  482. case 411: $httpStatusMsg = ' Length Required'; break;
  483. case 412: $httpStatusMsg = ' Precondition Failed'; break;
  484. case 413: $httpStatusMsg = ' Request Entity Too Large'; break;
  485. case 414: $httpStatusMsg = ' Request-URI Too Large'; break;
  486. case 415: $httpStatusMsg = ' Unsupported Media Type'; break;
  487. case 500: $httpStatusMsg = ' Internal Server Error'; break;
  488. case 501: $httpStatusMsg = ' Not Implemented'; break;
  489. case 502: $httpStatusMsg = ' Bad Gateway'; break;
  490. case 503: $httpStatusMsg = ' Service Unavailable'; break;
  491. case 504: $httpStatusMsg = ' Gateway Time-out'; break;
  492. case 505: $httpStatusMsg = ' HTTP Version not supported'; break;
  493. default: $httpStatusMsg = ' Web server is down'; break;
  494. }
  495. if (php_sapi_name() !== 'cgi-fcgi') {
  496. $this->header('status: ' . $response_code . $httpStatusMsg);
  497. }
  498. }
  499. /**
  500. * Generate header for 303
  501. *
  502. * @param string $location will set location to redirect.
  503. *
  504. * @return void
  505. */
  506. public function generateHeader303($location)
  507. {
  508. $this->setHttpResponseCode(303);
  509. $this->header('Location: '.$location);
  510. if (!defined('TESTSUITE')) {
  511. exit;
  512. }
  513. }
  514. /**
  515. * Configures response for the login page
  516. *
  517. * @return bool Whether caller should exit
  518. */
  519. public function loginPage()
  520. {
  521. /* Handle AJAX redirection */
  522. if ($this->isAjax()) {
  523. $this->setRequestStatus(false);
  524. // redirect_flag redirects to the login page
  525. $this->addJSON('redirect_flag', '1');
  526. return true;
  527. }
  528. $this->getFooter()->setMinimal();
  529. $header = $this->getHeader();
  530. $header->setBodyId('loginform');
  531. $header->setTitle('phpMyAdmin');
  532. $header->disableMenuAndConsole();
  533. $header->disableWarnings();
  534. return false;
  535. }
  536. }