Header.class.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Used to render the header of PMA's pages
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. require_once 'libraries/Scripts.class.php';
  12. require_once 'libraries/RecentTable.class.php';
  13. require_once 'libraries/Menu.class.php';
  14. require_once 'libraries/navigation/Navigation.class.php';
  15. require_once 'libraries/url_generating.lib.php';
  16. /**
  17. * Class used to output the HTTP and HTML headers
  18. *
  19. * @package PhpMyAdmin
  20. */
  21. class PMA_Header
  22. {
  23. /**
  24. * PMA_Scripts instance
  25. *
  26. * @access private
  27. * @var object
  28. */
  29. private $_scripts;
  30. /**
  31. * PMA_Menu instance
  32. *
  33. * @access private
  34. * @var object
  35. */
  36. private $_menu;
  37. /**
  38. * Whether to offer the option of importing user settings
  39. *
  40. * @access private
  41. * @var bool
  42. */
  43. private $_userprefsOfferImport;
  44. /**
  45. * The page title
  46. *
  47. * @access private
  48. * @var string
  49. */
  50. private $_title;
  51. /**
  52. * The value for the id attribute for the body tag
  53. *
  54. * @access private
  55. * @var string
  56. */
  57. private $_bodyId;
  58. /**
  59. * Whether to show the top menu
  60. *
  61. * @access private
  62. * @var bool
  63. */
  64. private $_menuEnabled;
  65. /**
  66. * Whether to show the warnings
  67. *
  68. * @access private
  69. * @var bool
  70. */
  71. private $_warningsEnabled;
  72. /**
  73. * Whether the page is in 'print view' mode
  74. *
  75. * @access private
  76. * @var bool
  77. */
  78. private $_isPrintView;
  79. /**
  80. * Whether we are servicing an ajax request.
  81. * We can't simply use $GLOBALS['is_ajax_request']
  82. * here since it may have not been initialised yet.
  83. *
  84. * @access private
  85. * @var bool
  86. */
  87. private $_isAjax;
  88. /**
  89. * Whether to display anything
  90. *
  91. * @access private
  92. * @var bool
  93. */
  94. private $_isEnabled;
  95. /**
  96. * Whether the HTTP headers (and possibly some HTML)
  97. * have already been sent to the browser
  98. *
  99. * @access private
  100. * @var bool
  101. */
  102. private $_headerIsSent;
  103. /**
  104. * Creates a new class instance
  105. *
  106. * @return new PMA_Header object
  107. */
  108. public function __construct()
  109. {
  110. $this->_isEnabled = true;
  111. $this->_isAjax = false;
  112. $this->_bodyId = '';
  113. $this->_title = '';
  114. $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : '';
  115. $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : '';
  116. $this->_menu = new PMA_Menu(
  117. $GLOBALS['server'],
  118. $db,
  119. $table
  120. );
  121. $this->_menuEnabled = true;
  122. $this->_warningsEnabled = true;
  123. $this->_isPrintView = false;
  124. $this->_scripts = new PMA_Scripts();
  125. $this->_addDefaultScripts();
  126. $this->_headerIsSent = false;
  127. // if database storage for user preferences is transient,
  128. // offer to load exported settings from localStorage
  129. // (detection will be done in JavaScript)
  130. $this->_userprefsOfferImport = false;
  131. if ($GLOBALS['PMA_Config']->get('user_preferences') == 'session'
  132. && ! isset($_SESSION['userprefs_autoload'])
  133. ) {
  134. $this->_userprefsOfferImport = true;
  135. }
  136. }
  137. /**
  138. * Loads common scripts
  139. *
  140. * @return void
  141. */
  142. private function _addDefaultScripts()
  143. {
  144. // Localised strings
  145. $params = array('lang' => $GLOBALS['lang']);
  146. if (isset($GLOBALS['db'])) {
  147. $params['db'] = $GLOBALS['db'];
  148. }
  149. $this->_scripts->addFile('jquery/jquery-1.8.3.min.js');
  150. $this->_scripts->addFile(
  151. 'whitelist.php' . PMA_generate_common_url($params), false, true
  152. );
  153. $this->_scripts->addFile('ajax.js');
  154. $this->_scripts->addFile('keyhandler.js');
  155. $this->_scripts->addFile('jquery/jquery-ui-1.9.2.custom.min.js');
  156. $this->_scripts->addFile('jquery/jquery.sprintf.js');
  157. $this->_scripts->addFile('jquery/jquery.cookie.js');
  158. $this->_scripts->addFile('jquery/jquery.mousewheel.js');
  159. $this->_scripts->addFile('jquery/jquery.event.drag-2.2.js');
  160. $this->_scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
  161. $this->_scripts->addFile('jquery/jquery.ba-hashchange-1.3.js');
  162. $this->_scripts->addFile('jquery/jquery.debounce-1.0.5.js');
  163. $this->_scripts->addFile('jquery/jquery.menuResizer-1.0.js');
  164. // Cross-framing protection
  165. if ($GLOBALS['cfg']['AllowThirdPartyFraming'] === false) {
  166. $this->_scripts->addFile('cross_framing_protection.js');
  167. }
  168. $this->_scripts->addFile('rte.js');
  169. // Here would not be a good place to add CodeMirror because
  170. // the user preferences have not been merged at this point
  171. $this->_scripts->addFile('messages.php' . PMA_generate_common_url($params));
  172. // Append the theme id to this url to invalidate
  173. // the cache on a theme change. Though this might be
  174. // unavailable for fatal errors.
  175. if (isset($_SESSION['PMA_Theme'])) {
  176. $theme_id = urlencode($_SESSION['PMA_Theme']->getId());
  177. } else {
  178. $theme_id = 'default';
  179. }
  180. $this->_scripts->addFile(
  181. 'get_image.js.php?theme=' . $theme_id
  182. );
  183. $this->_scripts->addFile('functions.js');
  184. $this->_scripts->addFile('navigation.js');
  185. $this->_scripts->addFile('indexes.js');
  186. $this->_scripts->addFile('common.js');
  187. $this->_scripts->addCode($this->getJsParamsCode());
  188. }
  189. /**
  190. * Returns, as an array, a list of parameters
  191. * used on the client side
  192. *
  193. * @return array
  194. */
  195. public function getJsParams()
  196. {
  197. $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : '';
  198. $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : '';
  199. return array(
  200. 'common_query' => PMA_generate_common_url('', '', '&'),
  201. 'opendb_url' => $GLOBALS['cfg']['DefaultTabDatabase'],
  202. 'safari_browser' => PMA_USR_BROWSER_AGENT == 'SAFARI' ? 1 : 0,
  203. 'querywindow_height' => $GLOBALS['cfg']['QueryWindowHeight'],
  204. 'querywindow_width' => $GLOBALS['cfg']['QueryWindowWidth'],
  205. 'collation_connection' => $GLOBALS['collation_connection'],
  206. 'lang' => $GLOBALS['lang'],
  207. 'server' => $GLOBALS['server'],
  208. 'table' => $table,
  209. 'db' => $db,
  210. 'token' => $_SESSION[' PMA_token '],
  211. 'text_dir' => $GLOBALS['text_dir'],
  212. 'pma_absolute_uri' => $GLOBALS['cfg']['PmaAbsoluteUri'],
  213. 'pma_text_default_tab' => PMA_Util::getTitleForTarget(
  214. $GLOBALS['cfg']['DefaultTabTable']
  215. ),
  216. 'pma_text_left_default_tab' => PMA_Util::getTitleForTarget(
  217. $GLOBALS['cfg']['NavigationTreeDefaultTabTable']
  218. ),
  219. 'confirm' => $GLOBALS['cfg']['Confirm']
  220. );
  221. }
  222. /**
  223. * Returns, as a string, a list of parameters
  224. * used on the client side
  225. *
  226. * @return string
  227. */
  228. public function getJsParamsCode()
  229. {
  230. $params = $this->getJsParams();
  231. foreach ($params as $key => $value) {
  232. $params[$key] = $key . ':"' . PMA_escapeJsString($value) . '"';
  233. }
  234. return 'PMA_commonParams.setAll({' . implode(',', $params) . '});';
  235. }
  236. /**
  237. * Disables the rendering of the header
  238. *
  239. * @return void
  240. */
  241. public function disable()
  242. {
  243. $this->_isEnabled = false;
  244. }
  245. /**
  246. * Set the ajax flag to indicate whether
  247. * we are sevicing an ajax request
  248. *
  249. * @param bool $isAjax Whether we are sevicing an ajax request
  250. *
  251. * @return void
  252. */
  253. public function setAjax($isAjax)
  254. {
  255. $this->_isAjax = ($isAjax == true);
  256. }
  257. /**
  258. * Returns the PMA_Scripts object
  259. *
  260. * @return PMA_Scripts object
  261. */
  262. public function getScripts()
  263. {
  264. return $this->_scripts;
  265. }
  266. /**
  267. * Returns the PMA_Menu object
  268. *
  269. * @return PMA_Menu object
  270. */
  271. public function getMenu()
  272. {
  273. return $this->_menu;
  274. }
  275. /**
  276. * Setter for the ID attribute in the BODY tag
  277. *
  278. * @param string $id Value for the ID attribute
  279. *
  280. * @return void
  281. */
  282. public function setBodyId($id)
  283. {
  284. $this->_bodyId = htmlspecialchars($id);
  285. }
  286. /**
  287. * Setter for the title of the page
  288. *
  289. * @param string $title New title
  290. *
  291. * @return void
  292. */
  293. public function setTitle($title)
  294. {
  295. $this->_title = htmlspecialchars($title);
  296. }
  297. /**
  298. * Disables the display of the top menu
  299. *
  300. * @return void
  301. */
  302. public function disableMenu()
  303. {
  304. $this->_menuEnabled = false;
  305. }
  306. /**
  307. * Disables the display of the top menu
  308. *
  309. * @return void
  310. */
  311. public function disableWarnings()
  312. {
  313. $this->_warningsEnabled = false;
  314. }
  315. /**
  316. * Turns on 'print view' mode
  317. *
  318. * @return void
  319. */
  320. public function enablePrintView()
  321. {
  322. $this->disableMenu();
  323. $this->setTitle(__('Print view') . ' - phpMyAdmin ' . PMA_VERSION);
  324. $this->_isPrintView = true;
  325. }
  326. /**
  327. * Generates the header
  328. *
  329. * @return string The header
  330. */
  331. public function getDisplay()
  332. {
  333. $retval = '';
  334. if (! $this->_headerIsSent) {
  335. if (! $this->_isAjax && $this->_isEnabled) {
  336. $this->sendHttpHeaders();
  337. $retval .= $this->_getHtmlStart();
  338. $retval .= $this->_getMetaTags();
  339. $retval .= $this->_getLinkTags();
  340. $retval .= $this->getTitleTag();
  341. // The user preferences have been merged at this point
  342. // so we can conditionally add CodeMirror
  343. if ($GLOBALS['cfg']['CodemirrorEnable']) {
  344. $this->_scripts->addFile('codemirror/lib/codemirror.js');
  345. $this->_scripts->addFile('codemirror/mode/mysql/mysql.js');
  346. }
  347. if ($this->_userprefsOfferImport) {
  348. $this->_scripts->addFile('config.js');
  349. }
  350. $retval .= $this->_scripts->getDisplay();
  351. $retval .= $this->_getBodyStart();
  352. if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
  353. $nav = new PMA_Navigation();
  354. $retval .= $nav->getDisplay();
  355. }
  356. // Include possible custom headers
  357. if (file_exists(CUSTOM_HEADER_FILE)) {
  358. ob_start();
  359. include CUSTOM_HEADER_FILE;
  360. $retval .= ob_get_contents();
  361. ob_end_clean();
  362. }
  363. // offer to load user preferences from localStorage
  364. if ($this->_userprefsOfferImport) {
  365. include_once './libraries/user_preferences.lib.php';
  366. $retval .= PMA_userprefsAutoloadGetHeader();
  367. }
  368. // pass configuration for hint tooltip display
  369. // (to be used by PMA_tooltip() in js/functions.js)
  370. if (! $GLOBALS['cfg']['ShowHint']) {
  371. $retval .= '<span id="no_hint" class="hide"></span>';
  372. }
  373. $retval .= $this->_getWarnings();
  374. if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
  375. $retval .= $this->_menu->getDisplay();
  376. $pagetop_link = '<a id="goto_pagetop" href="#" title="%s">%s</a>';
  377. $retval .= sprintf(
  378. $pagetop_link,
  379. __('Click on the bar to scroll to top of page'),
  380. PMA_Util::getImage('s_top.png')
  381. );
  382. }
  383. $retval .= '<div id="page_content">';
  384. $retval .= $this->getMessage();
  385. }
  386. if ($this->_isEnabled && empty($_REQUEST['recent_table'])) {
  387. $retval .= $this->_addRecentTable(
  388. $GLOBALS['db'],
  389. $GLOBALS['table']
  390. );
  391. }
  392. }
  393. return $retval;
  394. }
  395. /**
  396. * Returns the message to be displayed at the top of
  397. * the page, including the executed SQL query, if any.
  398. *
  399. * @return string
  400. */
  401. public function getMessage()
  402. {
  403. $retval = '';
  404. $message = '';
  405. if (! empty($GLOBALS['message'])) {
  406. $message = $GLOBALS['message'];
  407. unset($GLOBALS['message']);
  408. } else if (! empty($_REQUEST['message'])) {
  409. $message = $_REQUEST['message'];
  410. }
  411. if (! empty($message)) {
  412. if (isset($GLOBALS['buffer_message'])) {
  413. $buffer_message = $GLOBALS['buffer_message'];
  414. }
  415. $retval .= PMA_Util::getMessage($message);
  416. if (isset($buffer_message)) {
  417. $GLOBALS['buffer_message'] = $buffer_message;
  418. }
  419. }
  420. return $retval;
  421. }
  422. /**
  423. * Sends out the HTTP headers
  424. *
  425. * @return void
  426. */
  427. public function sendHttpHeaders()
  428. {
  429. $https = $GLOBALS['PMA_Config']->isHttps();
  430. $mapTilesUrls = ' *.tile.openstreetmap.org *.tile.opencyclemap.org';
  431. /**
  432. * Sends http headers
  433. */
  434. $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT';
  435. if (! defined('TESTSUITE')) {
  436. /* Prevent against ClickJacking by disabling framing */
  437. if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) {
  438. header(
  439. 'X-Frame-Options: DENY'
  440. );
  441. }
  442. header(
  443. "Content-Security-Policy: default-src 'self' "
  444. . $GLOBALS['cfg']['CSPAllow'] . ';'
  445. . "script-src 'self' 'unsafe-inline' 'unsafe-eval' "
  446. . $GLOBALS['cfg']['CSPAllow'] . ';'
  447. . ";"
  448. . "style-src 'self' 'unsafe-inline' "
  449. . $GLOBALS['cfg']['CSPAllow']
  450. . ";"
  451. . "referrer no-referrer;"
  452. . "img-src 'self' data: "
  453. . $GLOBALS['cfg']['CSPAllow']
  454. . ($https ? "" : $mapTilesUrls)
  455. . ";"
  456. );
  457. header(
  458. "X-Content-Security-Policy: default-src 'self' "
  459. . $GLOBALS['cfg']['CSPAllow'] . ';'
  460. . "options inline-script eval-script;"
  461. . "img-src 'self' data: "
  462. . "referrer no-referrer;"
  463. . $GLOBALS['cfg']['CSPAllow']
  464. . ($https ? "" : $mapTilesUrls)
  465. . ";"
  466. );
  467. header(
  468. "X-WebKit-CSP: default-src 'self' "
  469. . $GLOBALS['cfg']['CSPAllow'] . ';'
  470. . "script-src 'self' "
  471. . $GLOBALS['cfg']['CSPAllow']
  472. . " 'unsafe-inline' 'unsafe-eval';"
  473. . "style-src 'self' 'unsafe-inline' "
  474. . ';'
  475. . "referrer no-referrer;"
  476. . "img-src 'self' data: "
  477. . $GLOBALS['cfg']['CSPAllow']
  478. . ($https ? "" : $mapTilesUrls)
  479. . ";"
  480. );
  481. header(
  482. "X-Content-Security-Policy: default-src 'self' "
  483. . $GLOBALS['cfg']['CSPAllow'] . ';'
  484. . "options inline-script eval-script;"
  485. . "referrer no-referrer;"
  486. . "img-src 'self' data: "
  487. . $GLOBALS['cfg']['CSPAllow']
  488. . ($https ? "" : $mapTilesUrls)
  489. . ";"
  490. );
  491. }
  492. PMA_noCacheHeader();
  493. if (! defined('IS_TRANSFORMATION_WRAPPER') && ! defined('TESTSUITE')) {
  494. // Define the charset to be used
  495. header('Content-Type: text/html; charset=utf-8');
  496. }
  497. $this->_headerIsSent = true;
  498. }
  499. /**
  500. * Returns the DOCTYPE and the start HTML tag
  501. *
  502. * @return string DOCTYPE and HTML tags
  503. */
  504. private function _getHtmlStart()
  505. {
  506. $lang = $GLOBALS['available_languages'][$GLOBALS['lang']][1];
  507. $dir = $GLOBALS['text_dir'];
  508. $retval = "<!DOCTYPE HTML>";
  509. $retval .= "<html lang='$lang' dir='$dir'>";
  510. return $retval;
  511. }
  512. /**
  513. * Returns the META tags
  514. *
  515. * @return string the META tags
  516. */
  517. private function _getMetaTags()
  518. {
  519. $retval = '<meta charset="utf-8" />';
  520. $retval .= '<meta name="referrer" content="no-referrer" />';
  521. $retval .= '<meta name="robots" content="noindex,nofollow" />';
  522. $retval .= '<meta http-equiv="X-UA-Compatible" content="IE=Edge">';
  523. if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) {
  524. $retval .= '<style>html{display: none;}</style>';
  525. }
  526. return $retval;
  527. }
  528. /**
  529. * Returns the LINK tags for the favicon and the stylesheets
  530. *
  531. * @return string the LINK tags
  532. */
  533. private function _getLinkTags()
  534. {
  535. $retval = '<link rel="icon" href="favicon.ico" '
  536. . 'type="image/x-icon" />'
  537. . '<link rel="shortcut icon" href="favicon.ico" '
  538. . 'type="image/x-icon" />';
  539. // stylesheets
  540. $basedir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
  541. $common_url = PMA_generate_common_url(array('server' => $GLOBALS['server']));
  542. $theme_id = $GLOBALS['PMA_Config']->getThemeUniqueValue();
  543. $theme_path = $GLOBALS['pmaThemePath'];
  544. if ($this->_isPrintView) {
  545. $retval .= '<link rel="stylesheet" type="text/css" href="'
  546. . $basedir . 'print.css" />';
  547. } else {
  548. $retval .= '<link rel="stylesheet" type="text/css" href="'
  549. . $basedir . 'phpmyadmin.css.php'
  550. . $common_url . '&amp;nocache='
  551. . $theme_id . $GLOBALS['text_dir'] . '" />';
  552. $retval .= '<link rel="stylesheet" type="text/css" href="'
  553. . $theme_path . '/jquery/jquery-ui-1.9.2.custom.css" />';
  554. }
  555. return $retval;
  556. }
  557. /**
  558. * Returns the TITLE tag
  559. *
  560. * @return string the TITLE tag
  561. */
  562. public function getTitleTag()
  563. {
  564. $retval = "<title>";
  565. $retval .= $this->_getPageTitle();
  566. $retval .= "</title>";
  567. return $retval;
  568. }
  569. /**
  570. * If the page is missing the title, this function
  571. * will set it to something reasonable
  572. *
  573. * @return string
  574. */
  575. private function _getPageTitle()
  576. {
  577. if (empty($this->_title)) {
  578. if ($GLOBALS['server'] > 0) {
  579. if (! empty($GLOBALS['table'])) {
  580. $temp_title = $GLOBALS['cfg']['TitleTable'];
  581. } else if (! empty($GLOBALS['db'])) {
  582. $temp_title = $GLOBALS['cfg']['TitleDatabase'];
  583. } elseif (! empty($GLOBALS['cfg']['Server']['host'])) {
  584. $temp_title = $GLOBALS['cfg']['TitleServer'];
  585. } else {
  586. $temp_title = $GLOBALS['cfg']['TitleDefault'];
  587. }
  588. $this->_title = htmlspecialchars(
  589. PMA_Util::expandUserString($temp_title)
  590. );
  591. } else {
  592. $this->_title = 'phpMyAdmin';
  593. }
  594. }
  595. return $this->_title;
  596. }
  597. /**
  598. * Returns the close tag to the HEAD
  599. * and the start tag for the BODY
  600. *
  601. * @return string HEAD and BODY tags
  602. */
  603. private function _getBodyStart()
  604. {
  605. $retval = "</head><body";
  606. if (! empty($this->_bodyId)) {
  607. $retval .= " id='" . $this->_bodyId . "'";
  608. }
  609. $retval .= ">";
  610. return $retval;
  611. }
  612. /**
  613. * Returns some warnings to be displayed at the top of the page
  614. *
  615. * @return string The warnings
  616. */
  617. private function _getWarnings()
  618. {
  619. $retval = '';
  620. if ($this->_warningsEnabled) {
  621. $retval .= "<noscript>";
  622. $retval .= PMA_message::error(
  623. __("Javascript must be enabled past this point")
  624. )->getDisplay();
  625. $retval .= "</noscript>";
  626. }
  627. return $retval;
  628. }
  629. /**
  630. * Add recently used table and reload the navigation.
  631. *
  632. * @param string $db Database name where the table is located.
  633. * @param string $table The table name
  634. *
  635. * @return string
  636. */
  637. private function _addRecentTable($db, $table)
  638. {
  639. $retval = '';
  640. if ($this->_menuEnabled && strlen($table) && $GLOBALS['cfg']['NumRecentTables'] > 0) {
  641. $tmp_result = PMA_RecentTable::getInstance()->add($db, $table);
  642. if ($tmp_result === true) {
  643. $params = array('ajax_request' => true, 'recent_table' => true);
  644. $url = 'index.php' . PMA_generate_common_url($params);
  645. $retval = '<a class="hide" id="update_recent_tables"';
  646. $retval .= ' href="' . $url . '"></a>';
  647. } else {
  648. $error = $tmp_result;
  649. $retval = $error->getDisplay();
  650. }
  651. }
  652. return $retval;
  653. }
  654. }
  655. ?>