PageRenderTime 76ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/phpmyadmin/libraries/Util.class.php

https://github.com/Linaida/Projet_mabox
PHP | 4393 lines | 2758 code | 383 blank | 1252 comment | 574 complexity | 3a55ca9a84a478ec5843c058f4250811 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, Apache-2.0
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Hold the PMA_Util class
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * Misc functions used all over the scripts.
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class PMA_Util
  17. {
  18. /**
  19. * Detects which function to use for pow.
  20. *
  21. * @return string Function name.
  22. */
  23. public static function detectPow()
  24. {
  25. if (function_exists('bcpow')) {
  26. // BCMath Arbitrary Precision Mathematics Function
  27. return 'bcpow';
  28. } elseif (function_exists('gmp_pow')) {
  29. // GMP Function
  30. return 'gmp_pow';
  31. } else {
  32. // PHP function
  33. return 'pow';
  34. }
  35. }
  36. /**
  37. * Exponential expression / raise number into power
  38. *
  39. * @param string $base base to raise
  40. * @param string $exp exponent to use
  41. * @param mixed $use_function pow function to use, or false for auto-detect
  42. *
  43. * @return mixed string or float
  44. */
  45. public static function pow($base, $exp, $use_function = false)
  46. {
  47. static $pow_function = null;
  48. if ($pow_function == null) {
  49. $pow_function = self::detectPow();
  50. }
  51. if (! $use_function) {
  52. if ($exp < 0) {
  53. $use_function = 'pow';
  54. } else {
  55. $use_function = $pow_function;
  56. }
  57. }
  58. if (($exp < 0) && ($use_function != 'pow')) {
  59. return false;
  60. }
  61. switch ($use_function) {
  62. case 'bcpow' :
  63. // bcscale() needed for testing pow() with base values < 1
  64. bcscale(10);
  65. $pow = bcpow($base, $exp);
  66. break;
  67. case 'gmp_pow' :
  68. $pow = gmp_strval(gmp_pow($base, $exp));
  69. break;
  70. case 'pow' :
  71. $base = (float) $base;
  72. $exp = (int) $exp;
  73. $pow = pow($base, $exp);
  74. break;
  75. default:
  76. $pow = $use_function($base, $exp);
  77. }
  78. return $pow;
  79. }
  80. /**
  81. * Checks whether configuration value tells to show icons.
  82. *
  83. * @param string $value Configuration option name
  84. *
  85. * @return boolean Whether to show icons.
  86. */
  87. public static function showIcons($value)
  88. {
  89. return in_array($GLOBALS['cfg'][$value], array('icons', 'both'));
  90. }
  91. /**
  92. * Checks whether configuration value tells to show text.
  93. *
  94. * @param string $value Configuration option name
  95. *
  96. * @return boolean Whether to show text.
  97. */
  98. public static function showText($value)
  99. {
  100. return in_array($GLOBALS['cfg'][$value], array('text', 'both'));
  101. }
  102. /**
  103. * Returns an HTML IMG tag for a particular icon from a theme,
  104. * which may be an actual file or an icon from a sprite.
  105. * This function takes into account the ActionLinksMode
  106. * configuration setting and wraps the image tag in a span tag.
  107. *
  108. * @param string $icon name of icon file
  109. * @param string $alternate alternate text
  110. * @param boolean $force_text whether to force alternate text to be displayed
  111. * @param boolean $menu_icon whether this icon is for the menu bar or not
  112. * @param string $control_param which directive controls the display
  113. *
  114. * @return string an html snippet
  115. */
  116. public static function getIcon(
  117. $icon, $alternate = '', $force_text = false,
  118. $menu_icon = false, $control_param = 'ActionLinksMode'
  119. ) {
  120. $include_icon = $include_text = false;
  121. if (self::showIcons($control_param)) {
  122. $include_icon = true;
  123. }
  124. if ($force_text
  125. || self::showText($control_param)
  126. ) {
  127. $include_text = true;
  128. }
  129. // Sometimes use a span (we rely on this in js/sql.js). But for menu bar
  130. // we don't need a span
  131. $button = $menu_icon ? '' : '<span class="nowrap">';
  132. if ($include_icon) {
  133. $button .= self::getImage($icon, $alternate);
  134. }
  135. if ($include_icon && $include_text) {
  136. $button .= ' ';
  137. }
  138. if ($include_text) {
  139. $button .= $alternate;
  140. }
  141. $button .= $menu_icon ? '' : '</span>';
  142. return $button;
  143. }
  144. /**
  145. * Returns an HTML IMG tag for a particular image from a theme,
  146. * which may be an actual file or an icon from a sprite
  147. *
  148. * @param string $image The name of the file to get
  149. * @param string $alternate Used to set 'alt' and 'title' attributes
  150. * of the image
  151. * @param array $attributes An associative array of other attributes
  152. *
  153. * @return string an html IMG tag
  154. */
  155. public static function getImage($image, $alternate = '', $attributes = array())
  156. {
  157. static $sprites; // cached list of available sprites (if any)
  158. if (defined('TESTSUITE')) {
  159. // prevent caching in testsuite
  160. unset($sprites);
  161. }
  162. $url = '';
  163. $is_sprite = false;
  164. $alternate = htmlspecialchars($alternate);
  165. // If it's the first time this function is called
  166. if (! isset($sprites)) {
  167. // Try to load the list of sprites
  168. $sprite_file = $_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php';
  169. if (is_readable($sprite_file)) {
  170. include_once $sprite_file;
  171. $sprites = PMA_sprites();
  172. } else {
  173. // No sprites are available for this theme
  174. $sprites = array();
  175. }
  176. }
  177. // Check if we have the requested image as a sprite
  178. // and set $url accordingly
  179. $class = str_replace(array('.gif','.png'), '', $image);
  180. if (array_key_exists($class, $sprites)) {
  181. $is_sprite = true;
  182. $url = (defined('PMA_TEST_THEME') ? '../' : '') . 'themes/dot.gif';
  183. } else {
  184. $url = $GLOBALS['pmaThemeImage'] . $image;
  185. }
  186. // set class attribute
  187. if ($is_sprite) {
  188. if (isset($attributes['class'])) {
  189. $attributes['class'] = "icon ic_$class " . $attributes['class'];
  190. } else {
  191. $attributes['class'] = "icon ic_$class";
  192. }
  193. }
  194. // set all other attributes
  195. $attr_str = '';
  196. foreach ($attributes as $key => $value) {
  197. if (! in_array($key, array('alt', 'title'))) {
  198. $attr_str .= " $key=\"$value\"";
  199. }
  200. }
  201. // override the alt attribute
  202. if (isset($attributes['alt'])) {
  203. $alt = $attributes['alt'];
  204. } else {
  205. $alt = $alternate;
  206. }
  207. // override the title attribute
  208. if (isset($attributes['title'])) {
  209. $title = $attributes['title'];
  210. } else {
  211. $title = $alternate;
  212. }
  213. // generate the IMG tag
  214. $template = '<img src="%s" title="%s" alt="%s"%s />';
  215. $retval = sprintf($template, $url, $title, $alt, $attr_str);
  216. return $retval;
  217. }
  218. /**
  219. * Returns the formatted maximum size for an upload
  220. *
  221. * @param integer $max_upload_size the size
  222. *
  223. * @return string the message
  224. *
  225. * @access public
  226. */
  227. public static function getFormattedMaximumUploadSize($max_upload_size)
  228. {
  229. // I have to reduce the second parameter (sensitiveness) from 6 to 4
  230. // to avoid weird results like 512 kKib
  231. list($max_size, $max_unit) = self::formatByteDown($max_upload_size, 4);
  232. return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')';
  233. }
  234. /**
  235. * Generates a hidden field which should indicate to the browser
  236. * the maximum size for upload
  237. *
  238. * @param integer $max_size the size
  239. *
  240. * @return string the INPUT field
  241. *
  242. * @access public
  243. */
  244. public static function generateHiddenMaxFileSize($max_size)
  245. {
  246. return '<input type="hidden" name="MAX_FILE_SIZE" value="'
  247. . $max_size . '" />';
  248. }
  249. /**
  250. * Add slashes before "'" and "\" characters so a value containing them can
  251. * be used in a sql comparison.
  252. *
  253. * @param string $a_string the string to slash
  254. * @param bool $is_like whether the string will be used in a 'LIKE' clause
  255. * (it then requires two more escaped sequences) or not
  256. * @param bool $crlf whether to treat cr/lfs as escape-worthy entities
  257. * (converts \n to \\n, \r to \\r)
  258. * @param bool $php_code whether this function is used as part of the
  259. * "Create PHP code" dialog
  260. *
  261. * @return string the slashed string
  262. *
  263. * @access public
  264. */
  265. public static function sqlAddSlashes(
  266. $a_string = '', $is_like = false, $crlf = false, $php_code = false
  267. ) {
  268. if ($is_like) {
  269. $a_string = str_replace('\\', '\\\\\\\\', $a_string);
  270. } else {
  271. $a_string = str_replace('\\', '\\\\', $a_string);
  272. }
  273. if ($crlf) {
  274. $a_string = strtr(
  275. $a_string,
  276. array("\n" => '\n', "\r" => '\r', "\t" => '\t')
  277. );
  278. }
  279. if ($php_code) {
  280. $a_string = str_replace('\'', '\\\'', $a_string);
  281. } else {
  282. $a_string = str_replace('\'', '\'\'', $a_string);
  283. }
  284. return $a_string;
  285. } // end of the 'sqlAddSlashes()' function
  286. /**
  287. * Add slashes before "_" and "%" characters for using them in MySQL
  288. * database, table and field names.
  289. * Note: This function does not escape backslashes!
  290. *
  291. * @param string $name the string to escape
  292. *
  293. * @return string the escaped string
  294. *
  295. * @access public
  296. */
  297. public static function escapeMysqlWildcards($name)
  298. {
  299. return strtr($name, array('_' => '\\_', '%' => '\\%'));
  300. } // end of the 'escapeMysqlWildcards()' function
  301. /**
  302. * removes slashes before "_" and "%" characters
  303. * Note: This function does not unescape backslashes!
  304. *
  305. * @param string $name the string to escape
  306. *
  307. * @return string the escaped string
  308. *
  309. * @access public
  310. */
  311. public static function unescapeMysqlWildcards($name)
  312. {
  313. return strtr($name, array('\\_' => '_', '\\%' => '%'));
  314. } // end of the 'unescapeMysqlWildcards()' function
  315. /**
  316. * removes quotes (',",`) from a quoted string
  317. *
  318. * checks if the string is quoted and removes this quotes
  319. *
  320. * @param string $quoted_string string to remove quotes from
  321. * @param string $quote type of quote to remove
  322. *
  323. * @return string unqoted string
  324. */
  325. public static function unQuote($quoted_string, $quote = null)
  326. {
  327. $quotes = array();
  328. if ($quote === null) {
  329. $quotes[] = '`';
  330. $quotes[] = '"';
  331. $quotes[] = "'";
  332. } else {
  333. $quotes[] = $quote;
  334. }
  335. foreach ($quotes as $quote) {
  336. if (substr($quoted_string, 0, 1) === $quote
  337. && substr($quoted_string, -1, 1) === $quote
  338. ) {
  339. $unquoted_string = substr($quoted_string, 1, -1);
  340. // replace escaped quotes
  341. $unquoted_string = str_replace(
  342. $quote . $quote,
  343. $quote,
  344. $unquoted_string
  345. );
  346. return $unquoted_string;
  347. }
  348. }
  349. return $quoted_string;
  350. }
  351. /**
  352. * format sql strings
  353. *
  354. * @param string $sql_query raw SQL string
  355. * @param boolean $truncate truncate the query if it is too long
  356. *
  357. * @return string the formatted sql
  358. *
  359. * @global array $cfg the configuration array
  360. *
  361. * @access public
  362. * @todo move into PMA_Sql
  363. */
  364. public static function formatSql($sql_query, $truncate = false)
  365. {
  366. global $cfg;
  367. if ($truncate
  368. && strlen($sql_query) > $cfg['MaxCharactersInDisplayedSQL']
  369. ) {
  370. $sql_query = $GLOBALS['PMA_String']->substr(
  371. $sql_query,
  372. 0,
  373. $cfg['MaxCharactersInDisplayedSQL']
  374. ) . '[...]';
  375. }
  376. return '<code class="sql"><pre>' . "\n"
  377. . htmlspecialchars($sql_query) . "\n"
  378. . '</pre></code>';
  379. } // end of the "formatSql()" function
  380. /**
  381. * Displays a link to the documentation as an icon
  382. *
  383. * @param string $link documentation link
  384. * @param string $target optional link target
  385. *
  386. * @return string the html link
  387. *
  388. * @access public
  389. */
  390. public static function showDocLink($link, $target = 'documentation')
  391. {
  392. return '<a href="' . $link . '" target="' . $target . '">'
  393. . self::getImage('b_help.png', __('Documentation'))
  394. . '</a>';
  395. } // end of the 'showDocLink()' function
  396. /**
  397. * Get a URL link to the official MySQL documentation
  398. *
  399. * @param string $link contains name of page/anchor that is being linked
  400. * @param string $anchor anchor to page part
  401. *
  402. * @return string the URL link
  403. *
  404. * @access public
  405. */
  406. public static function getMySQLDocuURL($link, $anchor = '')
  407. {
  408. // Fixup for newly used names:
  409. $link = str_replace('_', '-', strtolower($link));
  410. if (empty($link)) {
  411. $link = 'index';
  412. }
  413. $mysql = '5.5';
  414. $lang = 'en';
  415. if (defined('PMA_MYSQL_INT_VERSION')) {
  416. if (PMA_MYSQL_INT_VERSION >= 50600) {
  417. $mysql = '5.6';
  418. } else if (PMA_MYSQL_INT_VERSION >= 50500) {
  419. $mysql = '5.5';
  420. } else if (PMA_MYSQL_INT_VERSION >= 50100) {
  421. $mysql = '5.1';
  422. } else {
  423. $mysql = '5.0';
  424. }
  425. }
  426. $url = 'http://dev.mysql.com/doc/refman/'
  427. . $mysql . '/' . $lang . '/' . $link . '.html';
  428. if (! empty($anchor)) {
  429. $url .= '#' . $anchor;
  430. }
  431. return PMA_linkURL($url);
  432. }
  433. /**
  434. * Displays a link to the official MySQL documentation
  435. *
  436. * @param string $link contains name of page/anchor that is being linked
  437. * @param bool $big_icon whether to use big icon (like in left frame)
  438. * @param string $anchor anchor to page part
  439. * @param bool $just_open whether only the opening <a> tag should be returned
  440. *
  441. * @return string the html link
  442. *
  443. * @access public
  444. */
  445. public static function showMySQLDocu(
  446. $link, $big_icon = false, $anchor = '', $just_open = false
  447. ) {
  448. $url = self::getMySQLDocuURL($link, $anchor);
  449. $open_link = '<a href="' . $url . '" target="mysql_doc">';
  450. if ($just_open) {
  451. return $open_link;
  452. } elseif ($big_icon) {
  453. return $open_link
  454. . self::getImage('b_sqlhelp.png', __('Documentation')) . '</a>';
  455. } else {
  456. return self::showDocLink($url, 'mysql_doc');
  457. }
  458. } // end of the 'showMySQLDocu()' function
  459. /**
  460. * Returns link to documentation.
  461. *
  462. * @param string $page Page in documentation
  463. * @param string $anchor Optional anchor in page
  464. *
  465. * @return string URL
  466. */
  467. public static function getDocuLink($page, $anchor = '')
  468. {
  469. /* Construct base URL */
  470. $url = $page . '.html';
  471. if (!empty($anchor)) {
  472. $url .= '#' . $anchor;
  473. }
  474. /* Check if we have built local documentation */
  475. if (defined('TESTSUITE')) {
  476. /* Provide consistent URL for testsuite */
  477. return PMA_linkURL('http://docs.phpmyadmin.net/en/latest/' . $url);
  478. } else if (file_exists('doc/html/index.html')) {
  479. if (defined('PMA_SETUP')) {
  480. return '../doc/html/' . $url;
  481. } else {
  482. return './doc/html/' . $url;
  483. }
  484. } else {
  485. /* TODO: Should link to correct branch for released versions */
  486. return PMA_linkURL('http://docs.phpmyadmin.net/en/latest/' . $url);
  487. }
  488. }
  489. /**
  490. * Displays a link to the phpMyAdmin documentation
  491. *
  492. * @param string $page Page in documentation
  493. * @param string $anchor Optional anchor in page
  494. *
  495. * @return string the html link
  496. *
  497. * @access public
  498. */
  499. public static function showDocu($page, $anchor = '')
  500. {
  501. return self::showDocLink(self::getDocuLink($page, $anchor));
  502. } // end of the 'showDocu()' function
  503. /**
  504. * Displays a link to the PHP documentation
  505. *
  506. * @param string $target anchor in documentation
  507. *
  508. * @return string the html link
  509. *
  510. * @access public
  511. */
  512. public static function showPHPDocu($target)
  513. {
  514. $url = PMA_getPHPDocLink($target);
  515. return self::showDocLink($url);
  516. } // end of the 'showPHPDocu()' function
  517. /**
  518. * Returns HTML code for a tooltip
  519. *
  520. * @param string $message the message for the tooltip
  521. *
  522. * @return string
  523. *
  524. * @access public
  525. */
  526. public static function showHint($message)
  527. {
  528. if ($GLOBALS['cfg']['ShowHint']) {
  529. $classClause = ' class="pma_hint"';
  530. } else {
  531. $classClause = '';
  532. }
  533. return '<span' . $classClause . '>'
  534. . self::getImage('b_help.png')
  535. . '<span class="hide">' . $message . '</span>'
  536. . '</span>';
  537. }
  538. /**
  539. * Displays a MySQL error message in the main panel when $exit is true.
  540. * Returns the error message otherwise.
  541. *
  542. * @param string $error_message the error message
  543. * @param string $the_query the sql query that failed
  544. * @param bool $is_modify_link whether to show a "modify" link or not
  545. * @param string $back_url the "back" link url (full path is not required)
  546. * @param bool $exit EXIT the page?
  547. *
  548. * @return mixed
  549. *
  550. * @global string $table the curent table
  551. * @global string $db the current db
  552. *
  553. * @access public
  554. */
  555. public static function mysqlDie(
  556. $error_message = '', $the_query = '',
  557. $is_modify_link = true, $back_url = '', $exit = true
  558. ) {
  559. global $table, $db;
  560. $error_msg = '';
  561. if (! $error_message) {
  562. $error_message = $GLOBALS['dbi']->getError();
  563. }
  564. if (! $the_query && ! empty($GLOBALS['sql_query'])) {
  565. $the_query = $GLOBALS['sql_query'];
  566. }
  567. // --- Added to solve bug #641765
  568. if (! function_exists('PMA_SQP_isError') || PMA_SQP_isError()) {
  569. $formatted_sql = htmlspecialchars($the_query);
  570. } elseif (empty($the_query) || (trim($the_query) == '')) {
  571. $formatted_sql = '';
  572. } else {
  573. $formatted_sql = self::formatSql($the_query, true);
  574. }
  575. // ---
  576. $error_msg .= "\n" . '<!-- PMA-SQL-ERROR -->' . "\n";
  577. $error_msg .= ' <div class="error"><h1>' . __('Error')
  578. . '</h1>' . "\n";
  579. // if the config password is wrong, or the MySQL server does not
  580. // respond, do not show the query that would reveal the
  581. // username/password
  582. if (! empty($the_query) && ! strstr($the_query, 'connect')) {
  583. // --- Added to solve bug #641765
  584. if (function_exists('PMA_SQP_isError') && PMA_SQP_isError()) {
  585. $error_msg .= PMA_SQP_getErrorString() . "\n";
  586. $error_msg .= '<br />' . "\n";
  587. }
  588. // ---
  589. // modified to show the help on sql errors
  590. $error_msg .= '<p><strong>' . __('SQL query:') . '</strong>' . "\n";
  591. if (strstr(strtolower($formatted_sql), 'select')) {
  592. // please show me help to the error on select
  593. $error_msg .= self::showMySQLDocu('SELECT');
  594. }
  595. if ($is_modify_link) {
  596. $_url_params = array(
  597. 'sql_query' => $the_query,
  598. 'show_query' => 1,
  599. );
  600. if (strlen($table)) {
  601. $_url_params['db'] = $db;
  602. $_url_params['table'] = $table;
  603. $doedit_goto = '<a href="tbl_sql.php'
  604. . PMA_URL_getCommon($_url_params) . '">';
  605. } elseif (strlen($db)) {
  606. $_url_params['db'] = $db;
  607. $doedit_goto = '<a href="db_sql.php'
  608. . PMA_URL_getCommon($_url_params) . '">';
  609. } else {
  610. $doedit_goto = '<a href="server_sql.php'
  611. . PMA_URL_getCommon($_url_params) . '">';
  612. }
  613. $error_msg .= $doedit_goto
  614. . self::getIcon('b_edit.png', __('Edit'))
  615. . '</a>';
  616. } // end if
  617. $error_msg .= ' </p>' . "\n"
  618. . '<p>' . "\n"
  619. . $formatted_sql . "\n"
  620. . '</p>' . "\n";
  621. } // end if
  622. if (! empty($error_message)) {
  623. $error_message = preg_replace(
  624. "@((\015\012)|(\015)|(\012)){3,}@",
  625. "\n\n",
  626. $error_message
  627. );
  628. }
  629. // modified to show the help on error-returns
  630. // (now error-messages-server)
  631. $error_msg .= '<p>' . "\n"
  632. . ' <strong>' . __('MySQL said: ') . '</strong>'
  633. . self::showMySQLDocu('Error-messages-server')
  634. . "\n"
  635. . '</p>' . "\n";
  636. // The error message will be displayed within a CODE segment.
  637. // To preserve original formatting, but allow wordwrapping,
  638. // we do a couple of replacements
  639. // Replace all non-single blanks with their HTML-counterpart
  640. $error_message = str_replace(' ', '&nbsp;&nbsp;', $error_message);
  641. // Replace TAB-characters with their HTML-counterpart
  642. $error_message = str_replace(
  643. "\t", '&nbsp;&nbsp;&nbsp;&nbsp;', $error_message
  644. );
  645. // Replace linebreaks
  646. $error_message = nl2br($error_message);
  647. $error_msg .= '<code>' . "\n"
  648. . $error_message . "\n"
  649. . '</code><br />' . "\n";
  650. $error_msg .= '</div>';
  651. $_SESSION['Import_message']['message'] = $error_msg;
  652. if ($exit) {
  653. /**
  654. * If in an Ajax request
  655. * - avoid displaying a Back link
  656. * - use PMA_Response() to transmit the message and exit
  657. */
  658. if (isset($GLOBALS['is_ajax_request'])
  659. && $GLOBALS['is_ajax_request'] == true
  660. ) {
  661. $response = PMA_Response::getInstance();
  662. $response->isSuccess(false);
  663. $response->addJSON('message', $error_msg);
  664. exit;
  665. }
  666. if (! empty($back_url)) {
  667. if (strstr($back_url, '?')) {
  668. $back_url .= '&amp;no_history=true';
  669. } else {
  670. $back_url .= '?no_history=true';
  671. }
  672. $_SESSION['Import_message']['go_back_url'] = $back_url;
  673. $error_msg .= '<fieldset class="tblFooters">'
  674. . '[ <a href="' . $back_url . '">' . __('Back') . '</a> ]'
  675. . '</fieldset>' . "\n\n";
  676. }
  677. echo $error_msg;
  678. exit;
  679. } else {
  680. return $error_msg;
  681. }
  682. } // end of the 'mysqlDie()' function
  683. /**
  684. * returns array with tables of given db with extended information and grouped
  685. *
  686. * @param string $db name of db
  687. * @param string $tables name of tables
  688. * @param integer $limit_offset list offset
  689. * @param int|bool $limit_count max tables to return
  690. *
  691. * @return array (recursive) grouped table list
  692. */
  693. public static function getTableList(
  694. $db, $tables = null, $limit_offset = 0, $limit_count = false
  695. ) {
  696. $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
  697. if ($tables === null) {
  698. $tables = $GLOBALS['dbi']->getTablesFull(
  699. $db, false, false, null, $limit_offset, $limit_count
  700. );
  701. if ($GLOBALS['cfg']['NaturalOrder']) {
  702. uksort($tables, 'strnatcasecmp');
  703. }
  704. }
  705. if (count($tables) < 1) {
  706. return $tables;
  707. }
  708. $default = array(
  709. 'Name' => '',
  710. 'Rows' => 0,
  711. 'Comment' => '',
  712. 'disp_name' => '',
  713. );
  714. $table_groups = array();
  715. foreach ($tables as $table_name => $table) {
  716. // check for correct row count
  717. if ($table['Rows'] === null) {
  718. // Do not check exact row count here,
  719. // if row count is invalid possibly the table is defect
  720. // and this would break left frame;
  721. // but we can check row count if this is a view or the
  722. // information_schema database
  723. // since PMA_Table::countRecords() returns a limited row count
  724. // in this case.
  725. // set this because PMA_Table::countRecords() can use it
  726. $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW';
  727. if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) {
  728. $table['Rows'] = PMA_Table::countRecords(
  729. $db,
  730. $table['Name'],
  731. false,
  732. true
  733. );
  734. }
  735. }
  736. // in $group we save the reference to the place in $table_groups
  737. // where to store the table info
  738. if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
  739. && $sep && strstr($table_name, $sep)
  740. ) {
  741. $parts = explode($sep, $table_name);
  742. $group =& $table_groups;
  743. $i = 0;
  744. $group_name_full = '';
  745. $parts_cnt = count($parts) - 1;
  746. while (($i < $parts_cnt)
  747. && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel'])
  748. ) {
  749. $group_name = $parts[$i] . $sep;
  750. $group_name_full .= $group_name;
  751. if (! isset($group[$group_name])) {
  752. $group[$group_name] = array();
  753. $group[$group_name]['is' . $sep . 'group'] = true;
  754. $group[$group_name]['tab' . $sep . 'count'] = 1;
  755. $group[$group_name]['tab' . $sep . 'group']
  756. = $group_name_full;
  757. } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) {
  758. $table = $group[$group_name];
  759. $group[$group_name] = array();
  760. $group[$group_name][$group_name] = $table;
  761. unset($table);
  762. $group[$group_name]['is' . $sep . 'group'] = true;
  763. $group[$group_name]['tab' . $sep . 'count'] = 1;
  764. $group[$group_name]['tab' . $sep . 'group']
  765. = $group_name_full;
  766. } else {
  767. $group[$group_name]['tab' . $sep . 'count']++;
  768. }
  769. $group =& $group[$group_name];
  770. $i++;
  771. }
  772. } else {
  773. if (! isset($table_groups[$table_name])) {
  774. $table_groups[$table_name] = array();
  775. }
  776. $group =& $table_groups;
  777. }
  778. $table['disp_name'] = $table['Name'];
  779. $group[$table_name] = array_merge($default, $table);
  780. }
  781. return $table_groups;
  782. }
  783. /* ----------------------- Set of misc functions ----------------------- */
  784. /**
  785. * Adds backquotes on both sides of a database, table or field name.
  786. * and escapes backquotes inside the name with another backquote
  787. *
  788. * example:
  789. * <code>
  790. * echo backquote('owner`s db'); // `owner``s db`
  791. *
  792. * </code>
  793. *
  794. * @param mixed $a_name the database, table or field name to "backquote"
  795. * or array of it
  796. * @param boolean $do_it a flag to bypass this function (used by dump
  797. * functions)
  798. *
  799. * @return mixed the "backquoted" database, table or field name
  800. *
  801. * @access public
  802. */
  803. public static function backquote($a_name, $do_it = true)
  804. {
  805. if (is_array($a_name)) {
  806. foreach ($a_name as &$data) {
  807. $data = self::backquote($data, $do_it);
  808. }
  809. return $a_name;
  810. }
  811. if (! $do_it) {
  812. global $PMA_SQPdata_forbidden_word;
  813. if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) {
  814. return $a_name;
  815. }
  816. }
  817. // '0' is also empty for php :-(
  818. if (strlen($a_name) && $a_name !== '*') {
  819. return '`' . str_replace('`', '``', $a_name) . '`';
  820. } else {
  821. return $a_name;
  822. }
  823. } // end of the 'backquote()' function
  824. /**
  825. * Adds quotes on both sides of a database, table or field name.
  826. * in compatibility mode
  827. *
  828. * example:
  829. * <code>
  830. * echo backquote('owner`s db'); // `owner``s db`
  831. *
  832. * </code>
  833. *
  834. * @param mixed $a_name the database, table or field name to
  835. * "backquote" or array of it
  836. * @param string $compatibility string compatibility mode (used by dump
  837. * functions)
  838. * @param boolean $do_it a flag to bypass this function (used by dump
  839. * functions)
  840. *
  841. * @return mixed the "backquoted" database, table or field name
  842. *
  843. * @access public
  844. */
  845. public static function backquoteCompat(
  846. $a_name, $compatibility = 'MSSQL', $do_it = true
  847. ) {
  848. if (is_array($a_name)) {
  849. foreach ($a_name as &$data) {
  850. $data = self::backquoteCompat($data, $compatibility, $do_it);
  851. }
  852. return $a_name;
  853. }
  854. if (! $do_it) {
  855. global $PMA_SQPdata_forbidden_word;
  856. if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) {
  857. return $a_name;
  858. }
  859. }
  860. // @todo add more compatibility cases (ORACLE for example)
  861. switch ($compatibility) {
  862. case 'MSSQL':
  863. $quote = '"';
  864. break;
  865. default:
  866. (isset($GLOBALS['sql_backquotes'])) ? $quote = "`" : $quote = '';
  867. break;
  868. }
  869. // '0' is also empty for php :-(
  870. if (strlen($a_name) && $a_name !== '*') {
  871. return $quote . $a_name . $quote;
  872. } else {
  873. return $a_name;
  874. }
  875. } // end of the 'backquoteCompat()' function
  876. /**
  877. * Defines the <CR><LF> value depending on the user OS.
  878. *
  879. * @return string the <CR><LF> value to use
  880. *
  881. * @access public
  882. */
  883. public static function whichCrlf()
  884. {
  885. // The 'PMA_USR_OS' constant is defined in "libraries/Config.class.php"
  886. // Win case
  887. if (PMA_USR_OS == 'Win') {
  888. $the_crlf = "\r\n";
  889. } else {
  890. // Others
  891. $the_crlf = "\n";
  892. }
  893. return $the_crlf;
  894. } // end of the 'whichCrlf()' function
  895. /**
  896. * Prepare the message and the query
  897. * usually the message is the result of the query executed
  898. *
  899. * @param string $message the message to display
  900. * @param string $sql_query the query to display
  901. * @param string $type the type (level) of the message
  902. * @param boolean $is_view is this a message after a VIEW operation?
  903. *
  904. * @return string
  905. *
  906. * @access public
  907. */
  908. public static function getMessage(
  909. $message, $sql_query = null, $type = 'notice', $is_view = false
  910. ) {
  911. global $cfg;
  912. $retval = '';
  913. if (null === $sql_query) {
  914. if (! empty($GLOBALS['display_query'])) {
  915. $sql_query = $GLOBALS['display_query'];
  916. } elseif (! empty($GLOBALS['unparsed_sql'])) {
  917. $sql_query = $GLOBALS['unparsed_sql'];
  918. } elseif (! empty($GLOBALS['sql_query'])) {
  919. $sql_query = $GLOBALS['sql_query'];
  920. } else {
  921. $sql_query = '';
  922. }
  923. }
  924. if (isset($GLOBALS['using_bookmark_message'])) {
  925. $retval .= $GLOBALS['using_bookmark_message']->getDisplay();
  926. unset($GLOBALS['using_bookmark_message']);
  927. }
  928. // In an Ajax request, $GLOBALS['cell_align_left'] may not be defined. Hence,
  929. // check for it's presence before using it
  930. $retval .= '<div id="result_query"'
  931. . ( isset($GLOBALS['cell_align_left'])
  932. ? ' style="text-align: ' . $GLOBALS['cell_align_left'] . '"'
  933. : '' )
  934. . '>' . "\n";
  935. if ($message instanceof PMA_Message) {
  936. if (isset($GLOBALS['special_message'])) {
  937. $message->addMessage($GLOBALS['special_message']);
  938. unset($GLOBALS['special_message']);
  939. }
  940. $retval .= $message->getDisplay();
  941. } else {
  942. $retval .= '<div class="' . $type . '">';
  943. $retval .= PMA_sanitize($message);
  944. if (isset($GLOBALS['special_message'])) {
  945. $retval .= PMA_sanitize($GLOBALS['special_message']);
  946. unset($GLOBALS['special_message']);
  947. }
  948. $retval .= '</div>';
  949. }
  950. if ($cfg['ShowSQL'] == true && ! empty($sql_query)) {
  951. // Html format the query to be displayed
  952. // If we want to show some sql code it is easiest to create it here
  953. /* SQL-Parser-Analyzer */
  954. if (! empty($GLOBALS['show_as_php'])) {
  955. $new_line = '\\n"<br />' . "\n"
  956. . '&nbsp;&nbsp;&nbsp;&nbsp;. "';
  957. $query_base = htmlspecialchars(addslashes($sql_query));
  958. $query_base = preg_replace(
  959. '/((\015\012)|(\015)|(\012))/', $new_line, $query_base
  960. );
  961. } else {
  962. $query_base = $sql_query;
  963. }
  964. $query_too_big = false;
  965. if (strlen($query_base) > $cfg['MaxCharactersInDisplayedSQL']) {
  966. // when the query is large (for example an INSERT of binary
  967. // data), the parser chokes; so avoid parsing the query
  968. $query_too_big = true;
  969. $shortened_query_base = nl2br(
  970. htmlspecialchars(
  971. substr($sql_query, 0, $cfg['MaxCharactersInDisplayedSQL'])
  972. . '[...]'
  973. )
  974. );
  975. } elseif (! empty($GLOBALS['parsed_sql'])
  976. && $query_base == $GLOBALS['parsed_sql']['raw']
  977. ) {
  978. // (here, use "! empty" because when deleting a bookmark,
  979. // $GLOBALS['parsed_sql'] is set but empty
  980. $parsed_sql = $GLOBALS['parsed_sql'];
  981. } else {
  982. // Parse SQL if needed
  983. $parsed_sql = PMA_SQP_parse($query_base);
  984. }
  985. // Analyze it
  986. if (isset($parsed_sql) && ! PMA_SQP_isError()) {
  987. $analyzed_display_query = PMA_SQP_analyze($parsed_sql);
  988. // Same as below (append LIMIT), append the remembered ORDER BY
  989. if ($GLOBALS['cfg']['RememberSorting']
  990. && isset($analyzed_display_query[0]['queryflags']['select_from'])
  991. && isset($GLOBALS['sql_order_to_append'])
  992. ) {
  993. $query_base = $analyzed_display_query[0]['section_before_limit']
  994. . "\n" . $GLOBALS['sql_order_to_append']
  995. . $analyzed_display_query[0]['limit_clause'] . ' '
  996. . $analyzed_display_query[0]['section_after_limit'];
  997. // Need to reparse query
  998. $parsed_sql = PMA_SQP_parse($query_base);
  999. // update the $analyzed_display_query
  1000. $analyzed_display_query[0]['section_before_limit']
  1001. .= $GLOBALS['sql_order_to_append'];
  1002. $analyzed_display_query[0]['order_by_clause']
  1003. = $GLOBALS['sorted_col'];
  1004. }
  1005. // Here we append the LIMIT added for navigation, to
  1006. // enable its display. Adding it higher in the code
  1007. // to $sql_query would create a problem when
  1008. // using the Refresh or Edit links.
  1009. // Only append it on SELECTs.
  1010. /**
  1011. * @todo what would be the best to do when someone hits Refresh:
  1012. * use the current LIMITs ?
  1013. */
  1014. if (isset($analyzed_display_query[0]['queryflags']['select_from'])
  1015. && ! empty($GLOBALS['sql_limit_to_append'])
  1016. ) {
  1017. $query_base = $analyzed_display_query[0]['section_before_limit']
  1018. . "\n" . $GLOBALS['sql_limit_to_append']
  1019. . $analyzed_display_query[0]['section_after_limit'];
  1020. // Need to reparse query
  1021. $parsed_sql = PMA_SQP_parse($query_base);
  1022. }
  1023. }
  1024. if (! empty($GLOBALS['show_as_php'])) {
  1025. $query_base = '$sql = "' . $query_base;
  1026. } elseif (isset($query_base)) {
  1027. $query_base = self::formatSql($query_base);
  1028. }
  1029. // Prepares links that may be displayed to edit/explain the query
  1030. // (don't go to default pages, we must go to the page
  1031. // where the query box is available)
  1032. // Basic url query part
  1033. $url_params = array();
  1034. if (! isset($GLOBALS['db'])) {
  1035. $GLOBALS['db'] = '';
  1036. }
  1037. if (strlen($GLOBALS['db'])) {
  1038. $url_params['db'] = $GLOBALS['db'];
  1039. if (strlen($GLOBALS['table'])) {
  1040. $url_params['table'] = $GLOBALS['table'];
  1041. $edit_link = 'tbl_sql.php';
  1042. } else {
  1043. $edit_link = 'db_sql.php';
  1044. }
  1045. } else {
  1046. $edit_link = 'server_sql.php';
  1047. }
  1048. // Want to have the query explained
  1049. // but only explain a SELECT (that has not been explained)
  1050. /* SQL-Parser-Analyzer */
  1051. $explain_link = '';
  1052. $is_select = preg_match('@^SELECT[[:space:]]+@i', $sql_query);
  1053. if (! empty($cfg['SQLQuery']['Explain']) && ! $query_too_big) {
  1054. $explain_params = $url_params;
  1055. if ($is_select) {
  1056. $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query;
  1057. $_message = __('Explain SQL');
  1058. } elseif (preg_match(
  1059. '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i', $sql_query
  1060. )) {
  1061. $explain_params['sql_query'] = substr($sql_query, 8);
  1062. $_message = __('Skip Explain SQL');
  1063. }
  1064. if (isset($explain_params['sql_query'])) {
  1065. $explain_link = 'import.php'
  1066. . PMA_URL_getCommon($explain_params);
  1067. $explain_link = ' ['
  1068. . self::linkOrButton($explain_link, $_message) . ']';
  1069. }
  1070. } //show explain
  1071. $url_params['sql_query'] = $sql_query;
  1072. $url_params['show_query'] = 1;
  1073. // even if the query is big and was truncated, offer the chance
  1074. // to edit it (unless it's enormous, see linkOrButton() )
  1075. if (! empty($cfg['SQLQuery']['Edit'])) {
  1076. if ($cfg['EditInWindow'] == true) {
  1077. $onclick = 'PMA_querywindow.focus(\''
  1078. . PMA_jsFormat($sql_query, false) . '\'); return false;';
  1079. } else {
  1080. $onclick = '';
  1081. }
  1082. $edit_link .= PMA_URL_getCommon($url_params) . '#querybox';
  1083. $edit_link = ' ['
  1084. . self::linkOrButton(
  1085. $edit_link, __('Edit'),
  1086. array('onclick' => $onclick, 'class' => 'disableAjax')
  1087. )
  1088. . ']';
  1089. } else {
  1090. $edit_link = '';
  1091. }
  1092. // Also we would like to get the SQL formed in some nice
  1093. // php-code
  1094. if (! empty($cfg['SQLQuery']['ShowAsPHP']) && ! $query_too_big) {
  1095. $php_params = $url_params;
  1096. if (! empty($GLOBALS['show_as_php'])) {
  1097. $_message = __('Without PHP Code');
  1098. } else {
  1099. $php_params['show_as_php'] = 1;
  1100. $_message = __('Create PHP Code');
  1101. }
  1102. $php_link = 'import.php' . PMA_URL_getCommon($php_params);
  1103. $php_link = ' [' . self::linkOrButton($php_link, $_message) . ']';
  1104. if (isset($GLOBALS['show_as_php'])) {
  1105. $runquery_link = 'import.php'
  1106. . PMA_URL_getCommon($url_params);
  1107. $php_link .= ' ['
  1108. . self::linkOrButton($runquery_link, __('Submit Query'))
  1109. . ']';
  1110. }
  1111. } else {
  1112. $php_link = '';
  1113. } //show as php
  1114. // Refresh query
  1115. if (! empty($cfg['SQLQuery']['Refresh'])
  1116. && ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same
  1117. && preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query)
  1118. ) {
  1119. $refresh_link = 'import.php' . PMA_URL_getCommon($url_params);
  1120. $refresh_link = ' ['
  1121. . self::linkOrButton($refresh_link, __('Refresh')) . ']';
  1122. } else {
  1123. $refresh_link = '';
  1124. } //refresh
  1125. $retval .= '<div class="sqlOuter">';
  1126. if ($query_too_big) {
  1127. $retval .= $shortened_query_base;
  1128. } else {
  1129. $retval .= $query_base;
  1130. }
  1131. //Clean up the end of the PHP
  1132. if (! empty($GLOBALS['show_as_php'])) {
  1133. $retval .= '";';
  1134. }
  1135. $retval .= '</div>';
  1136. $retval .= '<div class="tools">';
  1137. $retval .= '<form action="sql.php" method="post">';
  1138. $retval .= PMA_URL_getHiddenInputs(
  1139. $GLOBALS['db'], $GLOBALS['table']
  1140. );
  1141. $retval .= '<input type="hidden" name="sql_query" value="'
  1142. . htmlspecialchars($sql_query) . '" />';
  1143. // avoid displaying a Profiling checkbox that could
  1144. // be checked, which would reexecute an INSERT, for example
  1145. if (! empty($refresh_link) && self::profilingSupported()) {
  1146. $retval .= '<input type="hidden" name="profiling_form" value="1" />';
  1147. $retval .= self::getCheckbox(
  1148. 'profiling', __('Profiling'), isset($_SESSION['profiling']), true
  1149. );
  1150. }
  1151. $retval .= '</form>';
  1152. /**
  1153. * TODO: Should we have $cfg['SQLQuery']['InlineEdit']?
  1154. */
  1155. if (! empty($cfg['SQLQuery']['Edit']) && ! $query_too_big) {
  1156. $inline_edit_link = ' ['
  1157. . self::linkOrButton(
  1158. '#',
  1159. _pgettext('Inline edit query', 'Inline'),
  1160. array('class' => 'inline_edit_sql')
  1161. )
  1162. . ']';
  1163. } else {
  1164. $inline_edit_link = '';
  1165. }
  1166. $retval .= $inline_edit_link . $edit_link . $explain_link . $php_link
  1167. . $refresh_link;
  1168. $retval .= '</div>';
  1169. }
  1170. $retval .= '</div>';
  1171. if ($GLOBALS['is_ajax_request'] === false) {
  1172. $retval .= '<br class="clearfloat" />';
  1173. }
  1174. return $retval;
  1175. } // end of the 'getMessage()' function
  1176. /**
  1177. * Verifies if current MySQL server supports profiling
  1178. *
  1179. * @access public
  1180. *
  1181. * @return boolean whether profiling is supported
  1182. */
  1183. public static function profilingSupported()
  1184. {
  1185. if (!self::cacheExists('profiling_supported', true)) {
  1186. // 5.0.37 has profiling but for example, 5.1.20 does not
  1187. // (avoid a trip to the server for MySQL before 5.0.37)
  1188. // and do not set a constant as we might be switching servers
  1189. if (defined('PMA_MYSQL_INT_VERSION')
  1190. && (PMA_MYSQL_INT_VERSION >= 50037)
  1191. && $GLOBALS['dbi']->fetchValue("SHOW VARIABLES LIKE 'profiling'")
  1192. ) {
  1193. self::cacheSet('profiling_supported', true, true);
  1194. } else {
  1195. self::cacheSet('profiling_supported', false, true);
  1196. }
  1197. }
  1198. return self::cacheGet('profiling_supported', true);
  1199. }
  1200. /**
  1201. * Formats $value to byte view
  1202. *
  1203. * @param double $value the value to format
  1204. * @param int $limes the sensitiveness
  1205. * @param int $comma the number of decimals to retain
  1206. *
  1207. * @return array the formatted value and its unit
  1208. *
  1209. * @access public
  1210. */
  1211. public static function formatByteDown($value, $limes = 6, $comma = 0)
  1212. {
  1213. if ($value === null) {
  1214. return null;
  1215. }
  1216. $byteUnits = array(
  1217. /* l10n: shortcuts for Byte */
  1218. __('B'),
  1219. /* l10n: shortcuts for Kilobyte */
  1220. __('KiB'),
  1221. /* l10n: shortcuts for Megabyte */
  1222. __('MiB'),
  1223. /* l10n: shortcuts for Gigabyte */
  1224. __('GiB'),
  1225. /* l10n: shortcuts for Terabyte */
  1226. __('TiB'),
  1227. /* l10n: shortcuts for Petabyte */
  1228. __('PiB'),
  1229. /* l10n: shortcuts for Exabyte */
  1230. __('EiB')
  1231. );
  1232. $dh = self::pow(10, $comma);
  1233. $li = self::pow(10, $limes);
  1234. $unit = $byteUnits[0];
  1235. for ($d = 6, $ex = 15; $d >= 1; $d--, $ex-=3) {
  1236. // cast to float to avoid overflow
  1237. $unitSize = (float) $li * self::pow(10, $ex);
  1238. if (isset($byteUnits[$d]) && $value >= $unitSize) {
  1239. // use 1024.0 to avoid integer overflow on 64-bit machines
  1240. $value = round($value / (self::pow(1024, $d) / $dh)) /$dh;
  1241. $unit = $byteUnits[$d];
  1242. break 1;
  1243. } // end if
  1244. } // end for
  1245. if ($unit != $byteUnits[0]) {
  1246. // if the unit is not bytes (as represented in current language)
  1247. // reformat with max length of 5
  1248. // 4th parameter=true means do not reformat if value < 1
  1249. $return_value = self::formatNumber($value, 5, $comma, true);
  1250. } else {
  1251. // do not reformat, just handle the locale
  1252. $return_value = self::formatNumber($value, 0);
  1253. }
  1254. return array(trim($return_value), $unit);
  1255. } // end of the 'formatByteDown' function
  1256. /**
  1257. * Changes thousands and decimal separators to locale specific values.
  1258. *
  1259. * @param string $value the value
  1260. *
  1261. * @return string
  1262. */
  1263. public static function localizeNumber($value)
  1264. {
  1265. return str_replace(
  1266. array(',', '.'),
  1267. array(
  1268. /* l10n: Thousands separator */
  1269. __(','),
  1270. /* l10n: Decimal separator */
  1271. __('.'),
  1272. ),
  1273. $value
  1274. );
  1275. }
  1276. /**
  1277. * Formats $value to the given length and appends SI prefixes
  1278. * with a $length of 0 no truncation occurs, number is only formated
  1279. * to the current locale
  1280. *
  1281. * examples:
  1282. * <code>
  1283. * echo formatNumber(123456789, 6); // 123,457 k
  1284. * echo formatNumber(-123456789, 4, 2); // -123.46 M
  1285. * echo formatNumber(-0.003, 6); // -3 m
  1286. * echo formatNumber(0.003, 3, 3); // 0.003
  1287. * echo formatNumber(0.00003, 3, 2); // 0.03 m
  1288. * echo formatNumber(0, 6); // 0
  1289. * </code>
  1290. *
  1291. * @param double $value the value to format
  1292. * @param integer $digits_left number of digits left of the comma
  1293. * @param integer $digits_right number of digits right of the comma
  1294. * @param boolean $only_down do not reformat numbers below 1
  1295. * @param boolean $noTrailingZero removes trailing zeros right of the comma
  1296. * (default: true)
  1297. *
  1298. * @return string the formatted value and its unit
  1299. *
  1300. * @access public
  1301. */
  1302. public static function formatNumber(
  1303. $value, $digits_left = 3, $digits_right = 0,
  1304. $only_down = false, $noTrailingZero = true
  1305. ) {
  1306. if ($value == 0) {
  1307. return '0';
  1308. }
  1309. $originalValue = $value;
  1310. //number_format is not multibyte safe, str_replace is safe
  1311. if ($digits_left === 0) {
  1312. $value = number_format($value, $digits_right);
  1313. if (($originalValue != 0) && (floatval($value) == 0)) {
  1314. $value = ' <' . (1 / self::pow(10, $digits_right));
  1315. }
  1316. return self::localizeNumber($value);
  1317. }
  1318. // this units needs no translation, ISO
  1319. $units = array(
  1320. -8 => 'y',
  1321. -7 => 'z',
  1322. -6 => 'a',
  1323. -5 => 'f',
  1324. -4 => 'p',
  1325. -3 => 'n',
  1326. -2 => '&micro;',
  1327. -1 => 'm',
  1328. 0 => ' ',
  1329. 1 => 'k',
  1330. 2 => 'M',
  1331. 3 => 'G',
  1332. 4 => 'T',
  1333. 5 => 'P',
  1334. 6 => 'E',
  1335. 7 => 'Z',
  1336. 8 => 'Y'
  1337. );
  1338. // check for negative value to retain sign
  1339. if ($value < 0) {
  1340. $sign = '-';
  1341. $value = abs($value);
  1342. } else {
  1343. $sign = '';
  1344. }
  1345. $dh = self::pow(10, $digits_right);
  1346. /*
  1347. * This gives us the right SI prefix already,
  1348. * but $digits_left parameter not incorporated
  1349. */
  1350. $d = floor(log10($value) / 3);
  1351. /*
  1352. * Lowering the SI prefix by 1 gives us an additional 3 zeros
  1353. * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits)
  1354. * to use, then lower the SI prefix
  1355. */
  1356. $cur_digits = floor(log10($value / self::pow(1000, $d, 'pow'))+1);
  1357. if ($digits_left > $cur_digits) {
  1358. $d -= floor(($digits_left - $cur_digits)/3);
  1359. }
  1360. if ($d < 0 && $only_down) {
  1361. $d = 0;
  1362. }
  1363. $value = round($value / (self::pow(1000, $d, 'pow') / $dh)) /$dh;
  1364. $unit = $units[$d];
  1365. // If we dont want any zeros after the comma just add the thousand separator
  1366. if ($noTrailingZero) {
  1367. $value = self::localizeNumber(
  1368. preg_replace('/(?<=\d)(?=(\d{3})+(?!\d))/', ',', $value)
  1369. );
  1370. } else {
  1371. //number_format is not multibyte safe, str_replace is safe
  1372. $value = self::localizeNumber(number_format($value, $digits_right));
  1373. }
  1374. if ($originalValue != 0 && floatval($value) == 0) {
  1375. return ' <' . (1 / self::pow(10, $digits_right)) . ' ' . $unit;
  1376. }
  1377. return $sign . $value . ' ' . $unit;
  1378. } // end of the 'formatNumber' function
  1379. /**
  1380. * Returns the number of bytes when a formatted size is given
  1381. *
  1382. * @param string $formatted_size the size expression (for example 8MB)
  1383. *
  1384. * @return integer The numerical part of the expression (for example 8)
  1385. */
  1386. public static function extractValueFromFormattedSize($formatted_size)
  1387. {
  1388. $return_value = -1;
  1389. if (preg_match('/^[0-9]+GB$/', $formatted_size)) {
  1390. $return_value = substr($formatted_size, 0, -2) * self::pow(1024, 3);
  1391. } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) {
  1392. $return_value = substr($formatted_size, 0, -2) * self::pow(1024, 2);
  1393. } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) {
  1394. $return_value = substr($formatted_size, 0, -1) * self::pow(1024, 1);
  1395. }
  1396. return $return_value;
  1397. }// end of the 'extractValueFromFormattedSize' function
  1398. /**
  1399. * Writes localised date
  1400. *
  1401. * @param string $timestamp the current timestamp
  1402. * @param string $format format
  1403. *
  1404. * @return string the formatted date
  1405. *
  1406. * @access public
  1407. */
  1408. public static function localisedDate($timestamp = -1, $format = '')
  1409. {
  1410. $month = array(
  1411. /* l10n: Short month name */
  1412. __('Jan'),
  1413. /* l10n: Short month name */
  1414. __('Feb'),
  1415. /* l10n: Short month name */
  1416. __('Mar'),
  1417. /* l10n: Short month name */
  1418. __('Apr'),
  1419. /* l10n: Short month name */
  1420. _pgettext('Short month name', 'May'),
  1421. /* l10n: Short month name */
  1422. __('Jun'),
  1423. /* l10n: Short month name */
  1424. __('Jul'),
  1425. /* l10n: Short month name */
  1426. __('Aug'),
  1427. /* l10n: Short month name */
  1428. __('Sep'),
  1429. /* l10n: Short month name */
  1430. __('Oct'),
  1431. /* l10n: Short month name */
  1432. __('Nov'),
  1433. /* l10n: Short month name */
  1434. __('Dec'));
  1435. $day_of_week = array(
  1436. /* l10n: Short week day name */
  1437. _pgettext('Short week day name', 'Sun'),
  1438. /* l10n: Short week day name */
  1439. __('Mon'),
  1440. /* l10n: Short week day name */
  1441. __('Tue'),
  1442. /* l10n: Short week day name */
  1443. __('Wed'),
  1444. /* l10n: Short week day name */
  1445. __('Thu'),
  1446. /* l10n: Short week day name */
  1447. __('Fri'),
  1448. /* l10n: Short week day name */
  1449. __('Sat'));
  1450. if ($format == '') {
  1451. /* l10n: See http://www.php.net/manual/en/function.strftime.php */
  1452. $format = __('%B %d, %Y at %I:%M %p');
  1453. }
  1454. if ($timestamp == -1) {
  1455. $timestamp = time();
  1456. }
  1457. $date = preg_replace(
  1458. '@%[aA]@',
  1459. $day_of_week[(int)strftime('%w', $timestamp)],
  1460. $format
  1461. );
  1462. $date = preg_replace(
  1463. '@%[bB]@',
  1464. $month[(int)strftime('%m', $timestamp)-1],
  1465. $date
  1466. );
  1467. return strftime($date, $timestamp);
  1468. } // end of the 'localisedDate()' function
  1469. /**
  1470. * returns a tab for tabbed navigation.
  1471. * If the variables $link and $args ar left empty, an inactive tab is created
  1472. *
  1473. * @param array $tab array with all options
  1474. * @param array $url_params tab specific URL parameters
  1475. *
  1476. * @return string html code for one tab, a link if valid otherwise a span
  1477. *
  1478. * @access public
  1479. */
  1480. public static function getHtmlTab($tab, $url_params = array())
  1481. {
  1482. // default values
  1483. $defaults = array(
  1484. 'text' => '',
  1485. 'class' => '',
  1486. 'active' => null,
  1487. 'link' => '',
  1488. 'sep' => '?',
  1489. 'attr' => '',
  1490. 'args' => '',
  1491. 'warning' => '',
  1492. 'fragment' => '',
  1493. 'id' => '',
  1494. );
  1495. $tab = array_merge($defaults, $tab);
  1496. // determine additionnal style-class
  1497. if (empty($tab['class'])) {
  1498. if (! empty($tab['active'])
  1499. || PMA_isValid($GLOBALS['active_page'], 'identical', $tab['link'])
  1500. ) {
  1501. $tab['class'] = 'active';
  1502. } elseif (is_null($tab['active']) && empty($GLOBALS['active_page'])
  1503. && (basename($GLOBALS['PMA_PHP_SELF']) == $tab['link'])
  1504. ) {
  1505. $tab['class'] = 'active';
  1506. }
  1507. }
  1508. // If there are any tab specific URL parameters, merge those with
  1509. // the general URL parameters
  1510. if (! empty($tab['url_params']) && is_array($tab['url_params'])) {
  1511. $url_params = array_merge($url_params, $tab['url_params']);
  1512. }
  1513. // build the link
  1514. if (! empty($tab['link'])) {
  1515. $tab['link'] = htmlentities($tab['link']);
  1516. $tab['link'] = $tab['link'] . PMA_URL_getCommon($url_params);
  1517. if (! empty($tab['args'])) {
  1518. foreach ($tab['args'] as $param => $value) {
  1519. $tab['link'] .= PMA_URL_getArgSeparator('html')
  1520. . urlencode($param) . '=' . urlencode($value);
  1521. }
  1522. }
  1523. }
  1524. if (! empty($tab['fragment'])) {
  1525. $tab['link'] .= $tab['fragment'];
  1526. }
  1527. // display icon
  1528. if (isset($tab['icon'])) {
  1529. // avoid generating an alt tag, because it only illustrates
  1530. // the text that follows and if browser does not display
  1531. // images, the text is duplicated
  1532. $tab['text'] = self::getIcon(
  1533. $tab['icon'],
  1534. $tab['text'],
  1535. false,
  1536. true,
  1537. 'TabsMode'
  1538. );
  1539. } elseif (empty($tab['text'])) {
  1540. // check to not display an empty link-text
  1541. $tab['text'] = '?';
  1542. trigger_error(
  1543. 'empty linktext in function ' . __FUNCTION__ . '()',
  1544. E_USER_NOTICE
  1545. );
  1546. }
  1547. //Set the id for the tab, if set in the params
  1548. $id_string = ( empty($tab['id']) ? '' : ' id="' . $tab['id'] . '" ' );
  1549. $out = '<li' . ($tab['class'] == 'active' ? ' class="active"' : '') . '>';
  1550. if (! empty($tab['link'])) {
  1551. $out .= '<a class="tab' . htmlentities($tab['class']) . '"'
  1552. . $id_string
  1553. . ' href="' . $tab['link'] . '" ' . $tab['attr'] . '>'
  1554. . $tab['text'] . '</a>';
  1555. } else {
  1556. $out .= '<span class="tab' . htmlentities($tab['class']) . '"'
  1557. . $id_string . '>' . $tab['text'] . '</span>';
  1558. }
  1559. $out .= '</li>';
  1560. return $out;
  1561. } // end of the 'getHtmlTab()' function
  1562. /**
  1563. * returns html-code for a tab navigation
  1564. *
  1565. * @param array $tabs one element per tab
  1566. * @param string $url_params additional URL parameters
  1567. * @param string $menu_id HTML id attribute for the menu container
  1568. * @param bool $resizable whether to add a "resizable" class
  1569. *
  1570. * @return string html-code for tab-navigation
  1571. */
  1572. public static function getHtmlTabs($tabs, $url_params, $menu_id,
  1573. $resizable = false
  1574. ) {
  1575. $class = '';
  1576. if ($resizable) {
  1577. $class = ' class="resizable-menu"';
  1578. }
  1579. $tab_navigation = '<div id="' . htmlentities($menu_id)
  1580. . 'container" class="menucontainer">'
  1581. . '<ul id="' . htmlentities($menu_id) . '" ' . $class . '>';
  1582. foreach ($tabs as $tab) {
  1583. $tab_navigation .= self::getHtmlTab($tab, $url_params);
  1584. }
  1585. $tab_navigation .=
  1586. '<div class="clearfloat"></div>'
  1587. . '</ul>' . "\n"
  1588. . '</div>' . "\n";
  1589. return $tab_navigation;
  1590. }
  1591. /**
  1592. * Displays a link, or a button if the link's URL is too large, to
  1593. * accommodate some browsers' limitations
  1594. *
  1595. * @param string $url the URL
  1596. * @param string $message the link message
  1597. * @param mixed $tag_params string: js confirmation
  1598. * array: additional tag params (f.e. style="")
  1599. * @param boolean $new_form we set this to false when we are already in
  1600. * a form, to avoid generating nested forms
  1601. * @param boolean $strip_img whether to strip the image
  1602. * @param string $target target
  1603. *
  1604. * @return string the results to be echoed or saved in an array
  1605. */
  1606. public static function linkOrButton(
  1607. $url, $message, $tag_params = array(),
  1608. $new_form = true, $strip_img = false, $target = ''
  1609. ) {
  1610. $url_length = strlen($url);
  1611. // with this we should be able to catch case of image upload
  1612. // into a (MEDIUM) BLOB; not worth generating even a form for these
  1613. if ($url_length > $GLOBALS['cfg']['LinkLengthLimit'] * 100) {
  1614. return '';
  1615. }
  1616. if (! is_array($tag_params)) {
  1617. $tmp = $tag_params;
  1618. $tag_params = array();
  1619. if (! empty($tmp)) {
  1620. $tag_params['onclick'] = 'return confirmLink(this, \''
  1621. . PMA_escapeJsString($tmp) . '\')';
  1622. }
  1623. unset($tmp);
  1624. }
  1625. if (! empty($target)) {
  1626. $tag_params['target'] = htmlentities($target);
  1627. }
  1628. $tag_params_strings = array();
  1629. foreach ($tag_params as $par_name => $par_value) {
  1630. // htmlspecialchars() only on non javascript
  1631. $par_value = substr($par_name, 0, 2) == 'on'
  1632. ? $par_value
  1633. : htmlspecialchars($par_value);
  1634. $tag_params_strings[] = $par_name . '="' . $par_value . '"';
  1635. }
  1636. $displayed_message = '';
  1637. // Add text if not already added
  1638. if (stristr($message, '<img')
  1639. && (! $strip_img || ($GLOBALS['cfg']['ActionLinksMode'] == 'icons'))
  1640. && (strip_tags($message) == $message)
  1641. ) {
  1642. $displayed_message = '<span>'
  1643. . htmlspecialchars(
  1644. preg_replace('/^.*\salt="([^"]*)".*$/si', '\1', $message)
  1645. )
  1646. . '</span>';
  1647. }
  1648. // Suhosin: Check that each query parameter is not above maximum
  1649. $in_suhosin_limits = true;
  1650. if ($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) {
  1651. $suhosin_get_MaxValueLength = ini_get('suhosin.get.max_value_length');
  1652. if ($suhosin_get_MaxValueLength) {
  1653. $query_parts = self::splitURLQuery($url);
  1654. foreach ($query_parts as $query_pair) {
  1655. list($eachvar, $eachval) = explode('=', $query_pair);
  1656. if (strlen($eachval) > $suhosin_get_MaxValueLength) {
  1657. $in_suhosin_limits = false;
  1658. break;
  1659. }
  1660. }
  1661. }
  1662. }
  1663. if (($url_length <= $GLOBALS['cfg']['LinkLengthLimit'])
  1664. && $in_suhosin_limits
  1665. ) {
  1666. // no whitespace within an <a> else Safari will make it part of the link
  1667. $ret = "\n" . '<a href="' . $url . '" '
  1668. . implode(' ', $tag_params_strings) . '>'
  1669. . $message . $displayed_message . '</a>' . "\n";
  1670. } else {
  1671. // no spaces (linebreaks) at all
  1672. // or after the hidden fields
  1673. // IE will display them all
  1674. // add class=link to submit button
  1675. if (empty($tag_params['class'])) {
  1676. $tag_params['class'] = 'link';
  1677. }
  1678. if (! isset($query_parts)) {
  1679. $query_parts = self::splitURLQuery($url);
  1680. }
  1681. $url_parts = parse_url($url);
  1682. if ($new_form) {
  1683. $ret = '<form action="' . $url_parts['path'] . '" class="link"'
  1684. . ' method="post"' . $target . ' style="display: inline;">';
  1685. $subname_open = '';
  1686. $subname_close = '';
  1687. $submit_link = '#';
  1688. } else {
  1689. $query_parts[] = 'redirect=' . $url_parts['path'];
  1690. if (empty($GLOBALS['subform_counter'])) {
  1691. $GLOBALS['subform_counter'] = 0;
  1692. }
  1693. $GLOBALS['subform_counter']++;
  1694. $ret = '';
  1695. $subname_open = 'subform[' . $GLOBALS['subform_counter'] . '][';
  1696. $subname_close = ']';
  1697. $submit_link = '#usesubform[' . $GLOBALS['subform_counter']
  1698. . ']=1';
  1699. }
  1700. foreach ($query_parts as $query_pair) {
  1701. list($eachvar, $eachval) = explode('=', $query_pair);
  1702. $ret .= '<input type="hidden" name="' . $subname_open . $eachvar
  1703. . $subname_close . '" value="'
  1704. . htmlspecialchars(urldecode($eachval)) . '" />';
  1705. } // end while
  1706. $ret .= "\n" . '<a href="' . $submit_link . '" class="formLinkSubmit" '
  1707. . implode(' ', $tag_params_strings) . '>'
  1708. . $message . ' ' . $displayed_message . '</a>' . "\n";
  1709. if ($new_form) {
  1710. $ret .= '</form>';
  1711. }
  1712. } // end if... else...
  1713. return $ret;
  1714. } // end of the 'linkOrButton()' function
  1715. /**
  1716. * Splits a URL string by parameter
  1717. *
  1718. * @param string $url the URL
  1719. *
  1720. * @return array the parameter/value pairs, for example [0] db=sakila
  1721. */
  1722. public static function splitURLQuery($url)
  1723. {
  1724. // decode encoded url separators
  1725. $separator = PMA_URL_getArgSeparator();
  1726. // on most places separator is still hard coded ...
  1727. if ($separator !== '&') {
  1728. // ... so always replace & with $separator
  1729. $url = str_replace(htmlentities('&'), $separator, $url);
  1730. $url = str_replace('&', $separator, $url);
  1731. }
  1732. $url = str_replace(htmlentities($separator), $separator, $url);
  1733. // end decode
  1734. $url_parts = parse_url($url);
  1735. return explode($separator, $url_parts['query']);
  1736. }
  1737. /**
  1738. * Returns a given timespan value in a readable format.
  1739. *
  1740. * @param int $seconds the timespan
  1741. *
  1742. * @return string the formatted value
  1743. */
  1744. public static function timespanFormat($seconds)
  1745. {
  1746. $days = floor($seconds / 86400);
  1747. if ($days > 0) {
  1748. $seconds -= $days * 86400;
  1749. }
  1750. $hours = floor($seconds / 3600);
  1751. if ($days > 0 || $hours > 0) {
  1752. $seconds -= $hours * 3600;
  1753. }
  1754. $minutes = floor($seconds / 60);
  1755. if ($days > 0 || $hours > 0 || $minutes > 0) {
  1756. $seconds -= $minutes * 60;
  1757. }
  1758. return sprintf(
  1759. __('%s days, %s hours, %s minutes and %s seconds'),
  1760. (string)$days, (string)$hours, (string)$minutes, (string)$seconds
  1761. );
  1762. }
  1763. /**
  1764. * Takes a string and outputs each character on a line for itself. Used
  1765. * mainly for horizontalflipped display mode.
  1766. * Takes care of special html-characters.
  1767. * Fulfills https://sourceforge.net/p/phpmyadmin/feature-requests/164/
  1768. *
  1769. * @param string $string The string
  1770. * @param string $Separator The Separator (defaults to "<br />\n")
  1771. *
  1772. * @access public
  1773. * @todo add a multibyte safe function $GLOBALS['PMA_String']->split()
  1774. *
  1775. * @return string The flipped string
  1776. */
  1777. public static function flipstring($string, $Separator = "<br />\n")
  1778. {
  1779. $format_string = '';
  1780. $charbuff = false;
  1781. for ($i = 0, $str_len = strlen($string); $i < $str_len; $i++) {
  1782. $char = $string{$i};
  1783. $append = false;
  1784. if ($char == '&') {
  1785. $format_string .= $charbuff;
  1786. $charbuff = $char;
  1787. } elseif ($char == ';' && ! empty($charbuff)) {
  1788. $format_string .= $charbuff . $char;
  1789. $charbuff = false;
  1790. $append = true;
  1791. } elseif (! empty($charbuff)) {
  1792. $charbuff .= $char;
  1793. } else {
  1794. $format_string .= $char;
  1795. $append = true;
  1796. }
  1797. // do not add separator after the last character
  1798. if ($append && ($i != $str_len - 1)) {
  1799. $format_string .= $Separator;
  1800. }
  1801. }
  1802. return $format_string;
  1803. }
  1804. /**
  1805. * Function added to avoid path disclosures.
  1806. * Called by each script that needs parameters, it displays
  1807. * an error message and, by default, stops the execution.
  1808. *
  1809. * Not sure we could use a strMissingParameter message here,
  1810. * would have to check if the error message file is always available
  1811. *
  1812. * @param array $params The names of the parameters needed by the calling script
  1813. * @param bool $request Whether to include this list in checking for
  1814. * special params
  1815. *
  1816. * @return void
  1817. *
  1818. * @global boolean $checked_special flag whether any special variable
  1819. * was required
  1820. *
  1821. * @access public
  1822. */
  1823. public static function checkParameters($params, $request = true)
  1824. {
  1825. global $checked_special;
  1826. if (! isset($checked_special)) {
  1827. $checked_special = false;
  1828. }
  1829. $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']);
  1830. $found_error = false;
  1831. $error_message = '';
  1832. foreach ($params as $param) {
  1833. if ($request && ($param != 'db') && ($param != 'table')) {
  1834. $checked_special = true;
  1835. }
  1836. if (! isset($GLOBALS[$param])) {
  1837. $error_message .= $reported_script_name
  1838. . ': ' . __('Missing parameter:') . ' '
  1839. . $param
  1840. . self::showDocu('faq', 'faqmissingparameters')
  1841. . '<br />';
  1842. $found_error = true;
  1843. }
  1844. }
  1845. if ($found_error) {
  1846. PMA_fatalError($error_message, null, false);
  1847. }
  1848. } // end function
  1849. /**
  1850. * Function to generate unique condition for specified row.
  1851. *
  1852. * @param resource $handle current query result
  1853. * @param integer $fields_cnt number of fields
  1854. * @param array $fields_meta meta information about fields
  1855. * @param array $row current row
  1856. * @param boolean $force_unique generate condition only on pk or unique
  1857. *
  1858. * @access public
  1859. *
  1860. * @return array the calculated condition and whether condition is unique
  1861. */
  1862. public static function getUniqueCondition(
  1863. $handle, $fields_cnt, $fields_meta, $row, $force_unique = false
  1864. ) {
  1865. $primary_key = '';
  1866. $unique_key = '';
  1867. $nonprimary_condition = '';
  1868. $preferred_condition = '';
  1869. $primary_key_array = array();
  1870. $unique_key_array = array();
  1871. $nonprimary_condition_array = array();
  1872. $condition_array = array();
  1873. for ($i = 0; $i < $fields_cnt; ++$i) {
  1874. $condition = '';
  1875. $con_key = '';
  1876. $con_val = '';
  1877. $field_flags = $GLOBALS['dbi']->fieldFlags($handle, $i);
  1878. $meta = $fields_meta[$i];
  1879. // do not use a column alias in a condition
  1880. if (! isset($meta->orgname) || ! strlen($meta->orgname)) {
  1881. $meta->orgname = $meta->name;
  1882. if (isset($GLOBALS['analyzed_sql'][0]['select_expr'])
  1883. && is_array($GLOBALS['analyzed_sql'][0]['select_expr'])
  1884. ) {
  1885. foreach (
  1886. $GLOBALS['analyzed_sql'][0]['select_expr'] as $select_expr
  1887. ) {
  1888. // need (string) === (string)
  1889. // '' !== 0 but '' == 0
  1890. if ((string)$select_expr['alias'] === (string)$meta->name) {
  1891. $meta->orgname = $select_expr['column'];
  1892. break;
  1893. } // end if
  1894. } // end foreach
  1895. }
  1896. }
  1897. // Do not use a table alias in a condition.
  1898. // Test case is:
  1899. // select * from galerie x WHERE
  1900. //(select count(*) from galerie y where y.datum=x.datum)>1
  1901. //
  1902. // But orgtable is present only with mysqli extension so the
  1903. // fix is only for mysqli.
  1904. // Also, do not use the original table name if we are dealing with
  1905. // a view because this view might be updatable.
  1906. // (The isView() verification should not be costly in most cases
  1907. // because there is some caching in the function).
  1908. if (isset($meta->orgtable)
  1909. && ($meta->table != $meta->orgtable)
  1910. && ! PMA_Table::isView($GLOBALS['db'], $meta->table)
  1911. ) {
  1912. $meta->table = $meta->orgtable;
  1913. }
  1914. // to fix the bug where float fields (primary or not)
  1915. // can't be matched because of the imprecision of
  1916. // floating comparison, use CONCAT
  1917. // (also, the syntax "CONCAT(field) IS NULL"
  1918. // that we need on the next "if" will work)
  1919. if ($meta->type == 'real') {
  1920. $con_key = 'CONCAT(' . self::backquote($meta->table) . '.'
  1921. . self::backquote($meta->orgname) . ')';
  1922. } else {
  1923. $con_key = self::backquote($meta->table) . '.'
  1924. . self::backquote($meta->orgname);
  1925. } // end if... else...
  1926. $condition = ' ' . $con_key . ' ';
  1927. if (! isset($row[$i]) || is_null($row[$i])) {
  1928. $con_val = 'IS NULL';
  1929. } else {
  1930. // timestamp is numeric on some MySQL 4.1
  1931. // for real we use CONCAT above and it should compare to string
  1932. if ($meta->numeric
  1933. && ($meta->type != 'timestamp')
  1934. && ($meta->type != 'real')
  1935. ) {
  1936. $con_val = '= ' . $row[$i];
  1937. } elseif ((($meta->type == 'blob') || ($meta->type == 'string'))
  1938. && stristr($field_flags, 'BINARY')
  1939. && ! empty($row[$i])
  1940. ) {
  1941. // hexify only if this is a true not empty BLOB or a BINARY
  1942. // do not waste memory building a too big condition
  1943. if (strlen($row[$i]) < 1000) {
  1944. // use a CAST if possible, to avoid problems
  1945. // if the field contains wildcard characters % or _
  1946. $con_val = '= CAST(0x' . bin2hex($row[$i]) . ' AS BINARY)';
  1947. } else if ($fields_cnt == 1) {
  1948. // when this blob is the only field present
  1949. // try settling with length comparison
  1950. $condition = ' CHAR_LENGTH(' . $con_key . ') ';
  1951. $con_val = ' = ' . strlen($row[$i]);
  1952. } else {
  1953. // this blob won't be part of the final condition
  1954. $con_val = null;
  1955. }
  1956. } elseif (in_array($meta->type, self::getGISDatatypes())
  1957. && ! empty($row[$i])
  1958. ) {
  1959. // do not build a too big condition
  1960. if (strlen($row[$i]) < 5000) {
  1961. $condition .= '=0x' . bin2hex($row[$i]) . ' AND';
  1962. } else {
  1963. $condition = '';
  1964. }
  1965. } elseif ($meta->type == 'bit') {
  1966. $con_val = "= b'"
  1967. . self::printableBitValue($row[$i], $meta->length) . "'";
  1968. } else {
  1969. $con_val = '= \''
  1970. . self::sqlAddSlashes($row[$i], false, true) . '\'';
  1971. }
  1972. }
  1973. if ($con_val != null) {
  1974. $condition .= $con_val . ' AND';
  1975. if ($meta->primary_key > 0) {
  1976. $primary_key .= $condition;
  1977. $primary_key_array[$con_key] = $con_val;
  1978. } elseif ($meta->unique_key > 0) {
  1979. $unique_key .= $condition;
  1980. $unique_key_array[$con_key] = $con_val;
  1981. }
  1982. $nonprimary_condition .= $condition;
  1983. $nonprimary_condition_array[$con_key] = $con_val;
  1984. }
  1985. } // end for
  1986. // Correction University of Virginia 19991216:
  1987. // prefer primary or unique keys for condition,
  1988. // but use conjunction of all values if no primary key
  1989. $clause_is_unique = true;
  1990. if ($primary_key) {
  1991. $preferred_condition = $primary_key;
  1992. $condition_array = $primary_key_array;
  1993. } elseif ($unique_key) {
  1994. $preferred_condition = $unique_key;
  1995. $condition_array = $unique_key_array;
  1996. } elseif (! $force_unique) {
  1997. $preferred_condition = $nonprimary_condition;
  1998. $condition_array = $nonprimary_condition_array;
  1999. $clause_is_unique = false;
  2000. }
  2001. $where_clause = trim(preg_replace('|\s?AND$|', '', $preferred_condition));
  2002. return(array($where_clause, $clause_is_unique, $condition_array));
  2003. } // end function
  2004. /**
  2005. * Generate a button or image tag
  2006. *
  2007. * @param string $button_name name of button element
  2008. * @param string $button_class class of button or image element
  2009. * @param string $image_name name of image element
  2010. * @param string $text text to display
  2011. * @param string $image image to display
  2012. * @param string $value value
  2013. *
  2014. * @return string html content
  2015. *
  2016. * @access public
  2017. */
  2018. public static function getButtonOrImage(
  2019. $button_name, $button_class, $image_name, $text, $image, $value = ''
  2020. ) {
  2021. if ($value == '') {
  2022. $value = $text;
  2023. }
  2024. if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') {
  2025. return ' <input type="submit" name="' . $button_name . '"'
  2026. . ' value="' . htmlspecialchars($value) . '"'
  2027. . ' title="' . htmlspecialchars($text) . '" />' . "\n";
  2028. }
  2029. /* Opera has trouble with <input type="image"> */
  2030. /* IE (before version 9) has trouble with <button> */
  2031. if (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER < 9) {
  2032. return '<input type="image" name="' . $image_name
  2033. . '" class="' . $button_class
  2034. . '" value="' . htmlspecialchars($value)
  2035. . '" title="' . htmlspecialchars($text)
  2036. . '" src="' . $GLOBALS['pmaThemeImage'] . $image . '" />'
  2037. . ($GLOBALS['cfg']['ActionLinksMode'] == 'both'
  2038. ? '&nbsp;' . htmlspecialchars($text)
  2039. : '') . "\n";
  2040. } else {
  2041. return '<button class="' . $button_class . '" type="submit"'
  2042. . ' name="' . $button_name . '" value="' . htmlspecialchars($value)
  2043. . '" title="' . htmlspecialchars($text) . '">' . "\n"
  2044. . self::getIcon($image, $text)
  2045. . '</button>' . "\n";
  2046. }
  2047. } // end function
  2048. /**
  2049. * Generate a pagination selector for browsing resultsets
  2050. *
  2051. * @param string $name The name for the request parameter
  2052. * @param int $rows Number of rows in the pagination set
  2053. * @param int $pageNow current page number
  2054. * @param int $nbTotalPage number of total pages
  2055. * @param int $showAll If the number of pages is lower than this
  2056. * variable, no pages will be omitted in pagination
  2057. * @param int $sliceStart How many rows at the beginning should always
  2058. * be shown?
  2059. * @param int $sliceEnd How many rows at the end should always be shown?
  2060. * @param int $percent Percentage of calculation page offsets to hop to a
  2061. * next page
  2062. * @param int $range Near the current page, how many pages should
  2063. * be considered "nearby" and displayed as well?
  2064. * @param string $prompt The prompt to display (sometimes empty)
  2065. *
  2066. * @return string
  2067. *
  2068. * @access public
  2069. */
  2070. public static function pageselector(
  2071. $name, $rows, $pageNow = 1, $nbTotalPage = 1, $showAll = 200,
  2072. $sliceStart = 5,
  2073. $sliceEnd = 5, $percent = 20, $range = 10, $prompt = ''
  2074. ) {
  2075. $increment = floor($nbTotalPage / $percent);
  2076. $pageNowMinusRange = ($pageNow - $range);
  2077. $pageNowPlusRange = ($pageNow + $range);
  2078. $gotopage = $prompt . ' <select class="pageselector ';
  2079. $gotopage .= ' ajax';
  2080. $gotopage .= '" name="' . $name . '" >';
  2081. if ($nbTotalPage < $showAll) {
  2082. $pages = range(1, $nbTotalPage);
  2083. } else {
  2084. $pages = array();
  2085. // Always show first X pages
  2086. for ($i = 1; $i <= $sliceStart; $i++) {
  2087. $pages[] = $i;
  2088. }
  2089. // Always show last X pages
  2090. for ($i = $nbTotalPage - $sliceEnd; $i <= $nbTotalPage; $i++) {
  2091. $pages[] = $i;
  2092. }
  2093. // Based on the number of results we add the specified
  2094. // $percent percentage to each page number,
  2095. // so that we have a representing page number every now and then to
  2096. // immediately jump to specific pages.
  2097. // As soon as we get near our currently chosen page ($pageNow -
  2098. // $range), every page number will be shown.
  2099. $i = $sliceStart;
  2100. $x = $nbTotalPage - $sliceEnd;
  2101. $met_boundary = false;
  2102. while ($i <= $x) {
  2103. if ($i >= $pageNowMinusRange && $i <= $pageNowPlusRange) {
  2104. // If our pageselector comes near the current page, we use 1
  2105. // counter increments
  2106. $i++;
  2107. $met_boundary = true;
  2108. } else {
  2109. // We add the percentage increment to our current page to
  2110. // hop to the next one in range
  2111. $i += $increment;
  2112. // Make sure that we do not cross our boundaries.
  2113. if ($i > $pageNowMinusRange && ! $met_boundary) {
  2114. $i = $pageNowMinusRange;
  2115. }
  2116. }
  2117. if ($i > 0 && $i <= $x) {
  2118. $pages[] = $i;
  2119. }
  2120. }
  2121. /*
  2122. Add page numbers with "geometrically increasing" distances.
  2123. This helps me a lot when navigating through giant tables.
  2124. Test case: table with 2.28 million sets, 76190 pages. Page of interest
  2125. is between 72376 and 76190.
  2126. Selecting page 72376.
  2127. Now, old version enumerated only +/- 10 pages around 72376 and the
  2128. percentage increment produced steps of about 3000.
  2129. The following code adds page numbers +/- 2,4,8,16,32,64,128,256 etc.
  2130. around the current page.
  2131. */
  2132. $i = $pageNow;
  2133. $dist = 1;
  2134. while ($i < $x) {
  2135. $dist = 2 * $dist;
  2136. $i = $pageNow + $dist;
  2137. if ($i > 0 && $i <= $x) {
  2138. $pages[] = $i;
  2139. }
  2140. }
  2141. $i = $pageNow;
  2142. $dist = 1;
  2143. while ($i >0) {
  2144. $dist = 2 * $dist;
  2145. $i = $pageNow - $dist;
  2146. if ($i > 0 && $i <= $x) {
  2147. $pages[] = $i;
  2148. }
  2149. }
  2150. // Since because of ellipsing of the current page some numbers may be
  2151. // double, we unify our array:
  2152. sort($pages);
  2153. $pages = array_unique($pages);
  2154. }
  2155. foreach ($pages as $i) {
  2156. if ($i == $pageNow) {
  2157. $selected = 'selected="selected" style="font-weight: bold"';
  2158. } else {
  2159. $selected = '';
  2160. }
  2161. $gotopage .= ' <option ' . $selected
  2162. . ' value="' . (($i - 1) * $rows) . '">' . $i . '</option>' . "\n";
  2163. }
  2164. $gotopage .= ' </select>';
  2165. return $gotopage;
  2166. } // end function
  2167. /**
  2168. * Prepare navigation for a list
  2169. *
  2170. * @param int $count number of elements in the list
  2171. * @param int $pos current position in the list
  2172. * @param array $_url_params url parameters
  2173. * @param string $script script name for form target
  2174. * @param string $frame target frame
  2175. * @param int $max_count maximum number of elements to display from the list
  2176. * @param string $name the name for the request parameter
  2177. * @param array $classes additional classes for the container
  2178. *
  2179. * @return string $list_navigator_html the html content
  2180. *
  2181. * @access public
  2182. *
  2183. * @todo use $pos from $_url_params
  2184. */
  2185. public static function getListNavigator(
  2186. $count, $pos, $_url_params, $script, $frame, $max_count, $name = 'pos',
  2187. $classes = array()
  2188. ) {
  2189. $class = $frame == 'frame_navigation' ? ' class="ajax"' : '';
  2190. $list_navigator_html = '';
  2191. if ($max_count < $count) {
  2192. $classes[] = 'pageselector';
  2193. $list_navigator_html .= '<div class="' . implode(' ', $classes) . '">';
  2194. if ($frame != 'frame_navigation') {
  2195. $list_navigator_html .= __('Page number:');
  2196. }
  2197. // Move to the beginning or to the previous page
  2198. if ($pos > 0) {
  2199. if (self::showIcons('TableNavigationLinksMode')) {
  2200. $caption1 = '&lt;&lt;';
  2201. $caption2 = ' &lt; ';
  2202. $title1 = ' title="' . _pgettext('First page', 'Begin') . '"';
  2203. $title2 = ' title="'
  2204. . _pgettext('Previous page', 'Previous') . '"';
  2205. } else {
  2206. $caption1 = _pgettext('First page', 'Begin') . ' &lt;&lt;';
  2207. $caption2 = _pgettext('Previous page', 'Previous') . ' &lt;';
  2208. $title1 = '';
  2209. $title2 = '';
  2210. } // end if... else...
  2211. $_url_params[$name] = 0;
  2212. $list_navigator_html .= '<a' . $class . $title1 . ' href="' . $script
  2213. . PMA_URL_getCommon($_url_params) . '">' . $caption1
  2214. . '</a>';
  2215. $_url_params[$name] = $pos - $max_count;
  2216. $list_navigator_html .= '<a' . $class . $title2 . ' href="' . $script
  2217. . PMA_URL_getCommon($_url_params) . '">' . $caption2
  2218. . '</a>';
  2219. }
  2220. $list_navigator_html .= '<form action="' . basename($script)
  2221. . '" method="post">';
  2222. $list_navigator_html .= PMA_URL_getHiddenInputs($_url_params);
  2223. $list_navigator_html .= self::pageselector(
  2224. $name,
  2225. $max_count,
  2226. floor(($pos + 1) / $max_count) + 1,
  2227. ceil($count / $max_count)
  2228. );
  2229. $list_navigator_html .= '</form>';
  2230. if ($pos + $max_count < $count) {
  2231. if ( self::showIcons('TableNavigationLinksMode')) {
  2232. $caption3 = ' &gt; ';
  2233. $caption4 = '&gt;&gt;';
  2234. $title3 = ' title="' . _pgettext('Next page', 'Next') . '"';
  2235. $title4 = ' title="' . _pgettext('Last page', 'End') . '"';
  2236. } else {
  2237. $caption3 = '&gt; ' . _pgettext('Next page', 'Next');
  2238. $caption4 = '&gt;&gt; ' . _pgettext('Last page', 'End');
  2239. $title3 = '';
  2240. $title4 = '';
  2241. } // end if... else...
  2242. $_url_params[$name] = $pos + $max_count;
  2243. $list_navigator_html .= '<a' . $class . $title3 . ' href="' . $script
  2244. . PMA_URL_getCommon($_url_params) . '" >' . $caption3
  2245. . '</a>';
  2246. $_url_params[$name] = floor($count / $max_count) * $max_count;
  2247. if ($_url_params[$name] == $count) {
  2248. $_url_params[$name] = $count - $max_count;
  2249. }
  2250. $list_navigator_html .= '<a' . $class . $title4 . ' href="' . $script
  2251. . PMA_URL_getCommon($_url_params) . '" >' . $caption4
  2252. . '</a>';
  2253. }
  2254. $list_navigator_html .= '</div>' . "\n";
  2255. }
  2256. return $list_navigator_html;
  2257. }
  2258. /**
  2259. * replaces %u in given path with current user name
  2260. *
  2261. * example:
  2262. * <code>
  2263. * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/'
  2264. *
  2265. * </code>
  2266. *
  2267. * @param string $dir with wildcard for user
  2268. *
  2269. * @return string per user directory
  2270. */
  2271. public static function userDir($dir)
  2272. {
  2273. // add trailing slash
  2274. if (substr($dir, -1) != '/') {
  2275. $dir .= '/';
  2276. }
  2277. return str_replace('%u', $GLOBALS['cfg']['Server']['user'], $dir);
  2278. }
  2279. /**
  2280. * returns html code for db link to default db page
  2281. *
  2282. * @param string $database database
  2283. *
  2284. * @return string html link to default db page
  2285. */
  2286. public static function getDbLink($database = null)
  2287. {
  2288. if (! strlen($database)) {
  2289. if (! strlen($GLOBALS['db'])) {
  2290. return '';
  2291. }
  2292. $database = $GLOBALS['db'];
  2293. } else {
  2294. $database = self::unescapeMysqlWildcards($database);
  2295. }
  2296. return '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase'] . '?'
  2297. . PMA_URL_getCommon($database) . '" title="'
  2298. . htmlspecialchars(
  2299. sprintf(
  2300. __('Jump to database "%s".'),
  2301. $database
  2302. )
  2303. )
  2304. . '">' . htmlspecialchars($database) . '</a>';
  2305. }
  2306. /**
  2307. * Prepare a lightbulb hint explaining a known external bug
  2308. * that affects a functionality
  2309. *
  2310. * @param string $functionality localized message explaining the func.
  2311. * @param string $component 'mysql' (eventually, 'php')
  2312. * @param string $minimum_version of this component
  2313. * @param string $bugref bug reference for this component
  2314. *
  2315. * @return String
  2316. */
  2317. public static function getExternalBug(
  2318. $functionality, $component, $minimum_version, $bugref
  2319. ) {
  2320. $ext_but_html = '';
  2321. if (($component == 'mysql') && (PMA_MYSQL_INT_VERSION < $minimum_version)) {
  2322. $ext_but_html .= self::showHint(
  2323. sprintf(
  2324. __('The %s functionality is affected by a known bug, see %s'),
  2325. $functionality,
  2326. PMA_linkURL('http://bugs.mysql.com/') . $bugref
  2327. )
  2328. );
  2329. }
  2330. return $ext_but_html;
  2331. }
  2332. /**
  2333. * Returns a HTML checkbox
  2334. *
  2335. * @param string $html_field_name the checkbox HTML field
  2336. * @param string $label label for checkbox
  2337. * @param boolean $checked is it initially checked?
  2338. * @param boolean $onclick should it submit the form on click?
  2339. *
  2340. * @return string HTML for the checkbox
  2341. */
  2342. public static function getCheckbox($html_field_name, $label, $checked, $onclick)
  2343. {
  2344. return '<input type="checkbox" name="' . $html_field_name . '" id="'
  2345. . $html_field_name . '"' . ($checked ? ' checked="checked"' : '')
  2346. . ($onclick ? ' class="autosubmit"' : '') . ' /><label for="'
  2347. . $html_field_name . '">' . $label . '</label>';
  2348. }
  2349. /**
  2350. * Generates a set of radio HTML fields
  2351. *
  2352. * @param string $html_field_name the radio HTML field
  2353. * @param array $choices the choices values and labels
  2354. * @param string $checked_choice the choice to check by default
  2355. * @param boolean $line_break whether to add HTML line break after a choice
  2356. * @param boolean $escape_label whether to use htmlspecialchars() on label
  2357. * @param string $class enclose each choice with a div of this class
  2358. *
  2359. * @return string set of html radio fiels
  2360. */
  2361. public static function getRadioFields(
  2362. $html_field_name, $choices, $checked_choice = '',
  2363. $line_break = true, $escape_label = true, $class = ''
  2364. ) {
  2365. $radio_html = '';
  2366. foreach ($choices as $choice_value => $choice_label) {
  2367. if (! empty($class)) {
  2368. $radio_html .= '<div class="' . $class . '">';
  2369. }
  2370. $html_field_id = $html_field_name . '_' . $choice_value;
  2371. $radio_html .= '<input type="radio" name="' . $html_field_name . '" id="'
  2372. . $html_field_id . '" value="'
  2373. . htmlspecialchars($choice_value) . '"';
  2374. if ($choice_value == $checked_choice) {
  2375. $radio_html .= ' checked="checked"';
  2376. }
  2377. $radio_html .= ' />' . "\n"
  2378. . '<label for="' . $html_field_id . '">'
  2379. . ($escape_label
  2380. ? htmlspecialchars($choice_label)
  2381. : $choice_label)
  2382. . '</label>';
  2383. if ($line_break) {
  2384. $radio_html .= '<br />';
  2385. }
  2386. if (! empty($class)) {
  2387. $radio_html .= '</div>';
  2388. }
  2389. $radio_html .= "\n";
  2390. }
  2391. return $radio_html;
  2392. }
  2393. /**
  2394. * Generates and returns an HTML dropdown
  2395. *
  2396. * @param string $select_name name for the select element
  2397. * @param array $choices choices values
  2398. * @param string $active_choice the choice to select by default
  2399. * @param string $id id of the select element; can be different in
  2400. * case the dropdown is present more than once
  2401. * on the page
  2402. * @param string $class class for the select element
  2403. *
  2404. * @return string html content
  2405. *
  2406. * @todo support titles
  2407. */
  2408. public static function getDropdown(
  2409. $select_name, $choices, $active_choice, $id, $class = ''
  2410. ) {
  2411. $result = '<select'
  2412. . ' name="' . htmlspecialchars($select_name) . '"'
  2413. . ' id="' . htmlspecialchars($id) . '"'
  2414. . (! empty($class) ? ' class="' . htmlspecialchars($class) . '"' : '')
  2415. . '>';
  2416. foreach ($choices as $one_choice_value => $one_choice_label) {
  2417. $result .= '<option value="' . htmlspecialchars($one_choice_value) . '"';
  2418. if ($one_choice_value == $active_choice) {
  2419. $result .= ' selected="selected"';
  2420. }
  2421. $result .= '>' . htmlspecialchars($one_choice_label) . '</option>';
  2422. }
  2423. $result .= '</select>';
  2424. return $result;
  2425. }
  2426. /**
  2427. * Generates a slider effect (jQjuery)
  2428. * Takes care of generating the initial <div> and the link
  2429. * controlling the slider; you have to generate the </div> yourself
  2430. * after the sliding section.
  2431. *
  2432. * @param string $id the id of the <div> on which to apply the effect
  2433. * @param string $message the message to show as a link
  2434. *
  2435. * @return string html div element
  2436. *
  2437. */
  2438. public static function getDivForSliderEffect($id, $message)
  2439. {
  2440. if ($GLOBALS['cfg']['InitialSlidersState'] == 'disabled') {
  2441. return '<div id="' . $id . '">';
  2442. }
  2443. /**
  2444. * Bad hack on the next line. document.write() conflicts with jQuery,
  2445. * hence, opening the <div> with PHP itself instead of JavaScript.
  2446. *
  2447. * @todo find a better solution that uses $.append(), the recommended
  2448. * method maybe by using an additional param, the id of the div to
  2449. * append to
  2450. */
  2451. return '<div id="' . $id . '"'
  2452. . (($GLOBALS['cfg']['InitialSlidersState'] == 'closed')
  2453. ? ' style="display: none; overflow:auto;"'
  2454. : '')
  2455. . ' class="pma_auto_slider" title="' . htmlspecialchars($message) . '">';
  2456. }
  2457. /**
  2458. * Creates an AJAX sliding toggle button
  2459. * (or and equivalent form when AJAX is disabled)
  2460. *
  2461. * @param string $action The URL for the request to be executed
  2462. * @param string $select_name The name for the dropdown box
  2463. * @param array $options An array of options (see rte_footer.lib.php)
  2464. * @param string $callback A JS snippet to execute when the request is
  2465. * successfully processed
  2466. *
  2467. * @return string HTML code for the toggle button
  2468. */
  2469. public static function toggleButton($action, $select_name, $options, $callback)
  2470. {
  2471. // Do the logic first
  2472. $link = "$action&amp;" . urlencode($select_name) . "=";
  2473. $link_on = $link . urlencode($options[1]['value']);
  2474. $link_off = $link . urlencode($options[0]['value']);
  2475. if ($options[1]['selected'] == true) {
  2476. $state = 'on';
  2477. } else if ($options[0]['selected'] == true) {
  2478. $state = 'off';
  2479. } else {
  2480. $state = 'on';
  2481. }
  2482. // Generate output
  2483. return "<!-- TOGGLE START -->\n"
  2484. . "<div class='wrapper toggleAjax hide'>\n"
  2485. . " <div class='toggleButton'>\n"
  2486. . " <div title='" . __('Click to toggle')
  2487. . "' class='container $state'>\n"
  2488. . " <img src='" . htmlspecialchars($GLOBALS['pmaThemeImage'])
  2489. . "toggle-" . htmlspecialchars($GLOBALS['text_dir']) . ".png'\n"
  2490. . " alt='' />\n"
  2491. . " <table class='nospacing nopadding'>\n"
  2492. . " <tbody>\n"
  2493. . " <tr>\n"
  2494. . " <td class='toggleOn'>\n"
  2495. . " <span class='hide'>$link_on</span>\n"
  2496. . " <div>"
  2497. . str_replace(' ', '&nbsp;', htmlspecialchars($options[1]['label']))
  2498. . "\n" . " </div>\n"
  2499. . " </td>\n"
  2500. . " <td><div>&nbsp;</div></td>\n"
  2501. . " <td class='toggleOff'>\n"
  2502. . " <span class='hide'>$link_off</span>\n"
  2503. . " <div>"
  2504. . str_replace(' ', '&nbsp;', htmlspecialchars($options[0]['label']))
  2505. . "\n" . " </div>\n"
  2506. . " </tr>\n"
  2507. . " </tbody>\n"
  2508. . " </table>\n"
  2509. . " <span class='hide callback'>"
  2510. . htmlspecialchars($callback) . "</span>\n"
  2511. . " <span class='hide text_direction'>"
  2512. . htmlspecialchars($GLOBALS['text_dir']) . "</span>\n"
  2513. . " </div>\n"
  2514. . " </div>\n"
  2515. . "</div>\n"
  2516. . "<!-- TOGGLE END -->";
  2517. } // end toggleButton()
  2518. /**
  2519. * Clears cache content which needs to be refreshed on user change.
  2520. *
  2521. * @return void
  2522. */
  2523. public static function clearUserCache()
  2524. {
  2525. self::cacheUnset('is_superuser', true);
  2526. }
  2527. /**
  2528. * Verifies if something is cached in the session
  2529. *
  2530. * @param string $var variable name
  2531. * @param int|true $server server
  2532. *
  2533. * @return boolean
  2534. */
  2535. public static function cacheExists($var, $server = 0)
  2536. {
  2537. if ($server === true) {
  2538. $server = $GLOBALS['server'];
  2539. }
  2540. return isset($_SESSION['cache']['server_' . $server][$var]);
  2541. }
  2542. /**
  2543. * Gets cached information from the session
  2544. *
  2545. * @param string $var varibale name
  2546. * @param int|true $server server
  2547. *
  2548. * @return mixed
  2549. */
  2550. public static function cacheGet($var, $server = 0)
  2551. {
  2552. if ($server === true) {
  2553. $server = $GLOBALS['server'];
  2554. }
  2555. if (isset($_SESSION['cache']['server_' . $server][$var])) {
  2556. return $_SESSION['cache']['server_' . $server][$var];
  2557. } else {
  2558. return null;
  2559. }
  2560. }
  2561. /**
  2562. * Caches information in the session
  2563. *
  2564. * @param string $var variable name
  2565. * @param mixed $val value
  2566. * @param int|true $server server
  2567. *
  2568. * @return mixed
  2569. */
  2570. public static function cacheSet($var, $val = null, $server = 0)
  2571. {
  2572. if ($server === true) {
  2573. $server = $GLOBALS['server'];
  2574. }
  2575. $_SESSION['cache']['server_' . $server][$var] = $val;
  2576. }
  2577. /**
  2578. * Removes cached information from the session
  2579. *
  2580. * @param string $var variable name
  2581. * @param int|true $server server
  2582. *
  2583. * @return void
  2584. */
  2585. public static function cacheUnset($var, $server = 0)
  2586. {
  2587. if ($server === true) {
  2588. $server = $GLOBALS['server'];
  2589. }
  2590. unset($_SESSION['cache']['server_' . $server][$var]);
  2591. }
  2592. /**
  2593. * Converts a bit value to printable format;
  2594. * in MySQL a BIT field can be from 1 to 64 bits so we need this
  2595. * function because in PHP, decbin() supports only 32 bits
  2596. * on 32-bit servers
  2597. *
  2598. * @param number $value coming from a BIT field
  2599. * @param integer $length length
  2600. *
  2601. * @return string the printable value
  2602. */
  2603. public static function printableBitValue($value, $length)
  2604. {
  2605. // if running on a 64-bit server or the length is safe for decbin()
  2606. if (PHP_INT_SIZE == 8 || $length < 33) {
  2607. $printable = decbin($value);
  2608. } else {
  2609. // FIXME: does not work for the leftmost bit of a 64-bit value
  2610. $i = 0;
  2611. $printable = '';
  2612. while ($value >= pow(2, $i)) {
  2613. $i++;
  2614. }
  2615. if ($i != 0) {
  2616. $i = $i - 1;
  2617. }
  2618. while ($i >= 0) {
  2619. if ($value - pow(2, $i) < 0) {
  2620. $printable = '0' . $printable;
  2621. } else {
  2622. $printable = '1' . $printable;
  2623. $value = $value - pow(2, $i);
  2624. }
  2625. $i--;
  2626. }
  2627. $printable = strrev($printable);
  2628. }
  2629. $printable = str_pad($printable, $length, '0', STR_PAD_LEFT);
  2630. return $printable;
  2631. }
  2632. /**
  2633. * Verifies whether the value contains a non-printable character
  2634. *
  2635. * @param string $value value
  2636. *
  2637. * @return boolean
  2638. */
  2639. public static function containsNonPrintableAscii($value)
  2640. {
  2641. return preg_match('@[^[:print:]]@', $value);
  2642. }
  2643. /**
  2644. * Converts a BIT type default value
  2645. * for example, b'010' becomes 010
  2646. *
  2647. * @param string $bit_default_value value
  2648. *
  2649. * @return string the converted value
  2650. */
  2651. public static function convertBitDefaultValue($bit_default_value)
  2652. {
  2653. return strtr($bit_default_value, array("b" => "", "'" => ""));
  2654. }
  2655. /**
  2656. * Extracts the various parts from a column spec
  2657. *
  2658. * @param string $columnspec Column specification
  2659. *
  2660. * @return array associative array containing type, spec_in_brackets
  2661. * and possibly enum_set_values (another array)
  2662. */
  2663. public static function extractColumnSpec($columnspec)
  2664. {
  2665. $first_bracket_pos = strpos($columnspec, '(');
  2666. if ($first_bracket_pos) {
  2667. $spec_in_brackets = chop(
  2668. substr(
  2669. $columnspec,
  2670. $first_bracket_pos + 1,
  2671. (strrpos($columnspec, ')') - $first_bracket_pos - 1)
  2672. )
  2673. );
  2674. // convert to lowercase just to be sure
  2675. $type = strtolower(chop(substr($columnspec, 0, $first_bracket_pos)));
  2676. } else {
  2677. // Split trailing attributes such as unsigned, binary, zerofill and get data type name
  2678. $type_parts = explode(' ',$columnspec);
  2679. $type = strtolower($type_parts[0]);
  2680. $spec_in_brackets = '';
  2681. }
  2682. if ('enum' == $type || 'set' == $type) {
  2683. // Define our working vars
  2684. $enum_set_values = self::parseEnumSetValues($columnspec, false);
  2685. $printtype = $type
  2686. . '(' . str_replace("','", "', '", $spec_in_brackets) . ')';
  2687. $binary = false;
  2688. $unsigned = false;
  2689. $zerofill = false;
  2690. } else {
  2691. $enum_set_values = array();
  2692. /* Create printable type name */
  2693. $printtype = strtolower($columnspec);
  2694. // Strip the "BINARY" attribute, except if we find "BINARY(" because
  2695. // this would be a BINARY or VARBINARY column type;
  2696. // by the way, a BLOB should not show the BINARY attribute
  2697. // because this is not accepted in MySQL syntax.
  2698. if (preg_match('@binary@', $printtype)
  2699. && ! preg_match('@binary[\(]@', $printtype)
  2700. ) {
  2701. $printtype = preg_replace('@binary@', '', $printtype);
  2702. $binary = true;
  2703. } else {
  2704. $binary = false;
  2705. }
  2706. $printtype = preg_replace(
  2707. '@zerofill@', '', $printtype, -1, $zerofill_cnt
  2708. );
  2709. $zerofill = ($zerofill_cnt > 0);
  2710. $printtype = preg_replace(
  2711. '@unsigned@', '', $printtype, -1, $unsigned_cnt
  2712. );
  2713. $unsigned = ($unsigned_cnt > 0);
  2714. $printtype = trim($printtype);
  2715. }
  2716. $attribute = ' ';
  2717. if ($binary) {
  2718. $attribute = 'BINARY';
  2719. }
  2720. if ($unsigned) {
  2721. $attribute = 'UNSIGNED';
  2722. }
  2723. if ($zerofill) {
  2724. $attribute = 'UNSIGNED ZEROFILL';
  2725. }
  2726. $can_contain_collation = false;
  2727. if (! $binary
  2728. && preg_match(
  2729. "@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@", $type
  2730. )
  2731. ) {
  2732. $can_contain_collation = true;
  2733. }
  2734. // for the case ENUM('&#8211;','&ldquo;')
  2735. $displayed_type = htmlspecialchars($printtype);
  2736. if (strlen($printtype) > $GLOBALS['cfg']['LimitChars']) {
  2737. $displayed_type = '<abbr title="' . $printtype . '">';
  2738. $displayed_type .= $GLOBALS['PMA_String']->substr(
  2739. $printtype, 0, $GLOBALS['cfg']['LimitChars']
  2740. );
  2741. $displayed_type .= '</abbr>';
  2742. }
  2743. return array(
  2744. 'type' => $type,
  2745. 'spec_in_brackets' => $spec_in_brackets,
  2746. 'enum_set_values' => $enum_set_values,
  2747. 'print_type' => $printtype,
  2748. 'binary' => $binary,
  2749. 'unsigned' => $unsigned,
  2750. 'zerofill' => $zerofill,
  2751. 'attribute' => $attribute,
  2752. 'can_contain_collation' => $can_contain_collation,
  2753. 'displayed_type' => $displayed_type
  2754. );
  2755. }
  2756. /**
  2757. * Verifies if this table's engine supports foreign keys
  2758. *
  2759. * @param string $engine engine
  2760. *
  2761. * @return boolean
  2762. */
  2763. public static function isForeignKeySupported($engine)
  2764. {
  2765. $engine = strtoupper($engine);
  2766. if (($engine == 'INNODB') || ($engine == 'PBXT')) {
  2767. return true;
  2768. } elseif ($engine == 'NDBCLUSTER' || $engine == 'NDB') {
  2769. $ndbver = $GLOBALS['dbi']->fetchValue(
  2770. "SHOW VARIABLES LIKE 'ndb_version_string'"
  2771. );
  2772. return ($ndbver >= 7.3);
  2773. } else {
  2774. return false;
  2775. }
  2776. }
  2777. /**
  2778. * Replaces some characters by a displayable equivalent
  2779. *
  2780. * @param string $content content
  2781. *
  2782. * @return string the content with characters replaced
  2783. */
  2784. public static function replaceBinaryContents($content)
  2785. {
  2786. $result = str_replace("\x00", '\0', $content);
  2787. $result = str_replace("\x08", '\b', $result);
  2788. $result = str_replace("\x0a", '\n', $result);
  2789. $result = str_replace("\x0d", '\r', $result);
  2790. $result = str_replace("\x1a", '\Z', $result);
  2791. return $result;
  2792. }
  2793. /**
  2794. * Converts GIS data to Well Known Text format
  2795. *
  2796. * @param binary $data GIS data
  2797. * @param bool $includeSRID Add SRID to the WKT
  2798. *
  2799. * @return string GIS data in Well Know Text format
  2800. */
  2801. public static function asWKT($data, $includeSRID = false)
  2802. {
  2803. // Convert to WKT format
  2804. $hex = bin2hex($data);
  2805. $wktsql = "SELECT ASTEXT(x'" . $hex . "')";
  2806. if ($includeSRID) {
  2807. $wktsql .= ", SRID(x'" . $hex . "')";
  2808. }
  2809. $wktresult = $GLOBALS['dbi']->tryQuery(
  2810. $wktsql, null, PMA_DatabaseInterface::QUERY_STORE
  2811. );
  2812. $wktarr = $GLOBALS['dbi']->fetchRow($wktresult, 0);
  2813. $wktval = $wktarr[0];
  2814. if ($includeSRID) {
  2815. $srid = $wktarr[1];
  2816. $wktval = "'" . $wktval . "'," . $srid;
  2817. }
  2818. @$GLOBALS['dbi']->freeResult($wktresult);
  2819. return $wktval;
  2820. }
  2821. /**
  2822. * If the string starts with a \r\n pair (0x0d0a) add an extra \n
  2823. *
  2824. * @param string $string string
  2825. *
  2826. * @return string with the chars replaced
  2827. */
  2828. public static function duplicateFirstNewline($string)
  2829. {
  2830. $first_occurence = strpos($string, "\r\n");
  2831. if ($first_occurence === 0) {
  2832. $string = "\n" . $string;
  2833. }
  2834. return $string;
  2835. }
  2836. /**
  2837. * Get the action word corresponding to a script name
  2838. * in order to display it as a title in navigation panel
  2839. *
  2840. * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'],
  2841. * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase']
  2842. *
  2843. * @return array
  2844. */
  2845. public static function getTitleForTarget($target)
  2846. {
  2847. $mapping = array(
  2848. // Values for $cfg['DefaultTabTable']
  2849. 'tbl_structure.php' => __('Structure'),
  2850. 'tbl_sql.php' => __('SQL'),
  2851. 'tbl_select.php' =>__('Search'),
  2852. 'tbl_change.php' =>__('Insert'),
  2853. 'sql.php' => __('Browse'),
  2854. // Values for $cfg['DefaultTabDatabase']
  2855. 'db_structure.php' => __('Structure'),
  2856. 'db_sql.php' => __('SQL'),
  2857. 'db_search.php' => __('Search'),
  2858. 'db_operations.php' => __('Operations'),
  2859. );
  2860. return $mapping[$target];
  2861. }
  2862. /**
  2863. * Formats user string, expanding @VARIABLES@, accepting strftime format
  2864. * string.
  2865. *
  2866. * @param string $string Text where to do expansion.
  2867. * @param array|string $escape Function to call for escaping variable values.
  2868. * Can also be an array of:
  2869. * - the escape method name
  2870. * - the class that contains the method
  2871. * - location of the class (for inclusion)
  2872. * @param array $updates Array with overrides for default parameters
  2873. * (obtained from GLOBALS).
  2874. *
  2875. * @return string
  2876. */
  2877. public static function expandUserString(
  2878. $string, $escape = null, $updates = array()
  2879. ) {
  2880. $vars = array();
  2881. /* Content */
  2882. $vars['http_host'] = PMA_getenv('HTTP_HOST');
  2883. $vars['server_name'] = $GLOBALS['cfg']['Server']['host'];
  2884. $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose'];
  2885. if (empty($GLOBALS['cfg']['Server']['verbose'])) {
  2886. $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host'];
  2887. } else {
  2888. $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose'];
  2889. }
  2890. $vars['database'] = $GLOBALS['db'];
  2891. $vars['table'] = $GLOBALS['table'];
  2892. $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION;
  2893. /* Update forced variables */
  2894. foreach ($updates as $key => $val) {
  2895. $vars[$key] = $val;
  2896. }
  2897. /* Replacement mapping */
  2898. /*
  2899. * The __VAR__ ones are for backward compatibility, because user
  2900. * might still have it in cookies.
  2901. */
  2902. $replace = array(
  2903. '@HTTP_HOST@' => $vars['http_host'],
  2904. '@SERVER@' => $vars['server_name'],
  2905. '__SERVER__' => $vars['server_name'],
  2906. '@VERBOSE@' => $vars['server_verbose'],
  2907. '@VSERVER@' => $vars['server_verbose_or_name'],
  2908. '@DATABASE@' => $vars['database'],
  2909. '__DB__' => $vars['database'],
  2910. '@TABLE@' => $vars['table'],
  2911. '__TABLE__' => $vars['table'],
  2912. '@PHPMYADMIN@' => $vars['phpmyadmin_version'],
  2913. );
  2914. /* Optional escaping */
  2915. if (! is_null($escape)) {
  2916. if (is_array($escape)) {
  2917. include_once $escape[2];
  2918. $escape_class = new $escape[1];
  2919. $escape_method = $escape[0];
  2920. }
  2921. foreach ($replace as $key => $val) {
  2922. if (is_array($escape)) {
  2923. $replace[$key] = $escape_class->$escape_method($val);
  2924. } else {
  2925. $replace[$key] = ($escape == 'backquote')
  2926. ? self::$escape($val)
  2927. : $escape($val);
  2928. }
  2929. }
  2930. }
  2931. /* Backward compatibility in 3.5.x */
  2932. if (strpos($string, '@FIELDS@') !== false) {
  2933. $string = strtr($string, array('@FIELDS@' => '@COLUMNS@'));
  2934. }
  2935. /* Fetch columns list if required */
  2936. if (strpos($string, '@COLUMNS@') !== false) {
  2937. $columns_list = $GLOBALS['dbi']->getColumns(
  2938. $GLOBALS['db'], $GLOBALS['table']
  2939. );
  2940. // sometimes the table no longer exists at this point
  2941. if (! is_null($columns_list)) {
  2942. $column_names = array();
  2943. foreach ($columns_list as $column) {
  2944. if (! is_null($escape)) {
  2945. $column_names[] = self::$escape($column['Field']);
  2946. } else {
  2947. $column_names[] = $column['Field'];
  2948. }
  2949. }
  2950. $replace['@COLUMNS@'] = implode(',', $column_names);
  2951. } else {
  2952. $replace['@COLUMNS@'] = '*';
  2953. }
  2954. }
  2955. /* Do the replacement */
  2956. return strtr(strftime($string), $replace);
  2957. }
  2958. /**
  2959. * Prepare the form used to browse anywhere on the local server for a file to
  2960. * import
  2961. *
  2962. * @param string $max_upload_size maximum upload size
  2963. *
  2964. * @return String
  2965. */
  2966. public static function getBrowseUploadFileBlock($max_upload_size)
  2967. {
  2968. $block_html = '';
  2969. if ($GLOBALS['is_upload'] && ! empty($GLOBALS['cfg']['UploadDir'])) {
  2970. $block_html .= '<label for="radio_import_file">';
  2971. } else {
  2972. $block_html .= '<label for="input_import_file">';
  2973. }
  2974. $block_html .= __("Browse your computer:") . '</label>'
  2975. . '<div id="upload_form_status" style="display: none;"></div>'
  2976. . '<div id="upload_form_status_info" style="display: none;"></div>'
  2977. . '<input type="file" name="import_file" id="input_import_file" />'
  2978. . self::getFormattedMaximumUploadSize($max_upload_size) . "\n"
  2979. // some browsers should respect this :)
  2980. . self::generateHiddenMaxFileSize($max_upload_size) . "\n";
  2981. return $block_html;
  2982. }
  2983. /**
  2984. * Prepare the form used to select a file to import from the server upload
  2985. * directory
  2986. *
  2987. * @param array $import_list array of import plugins
  2988. * @param string $uploaddir upload directory
  2989. *
  2990. * @return String
  2991. */
  2992. public static function getSelectUploadFileBlock($import_list, $uploaddir)
  2993. {
  2994. $block_html = '';
  2995. $block_html .= '<label for="radio_local_import_file">'
  2996. . sprintf(
  2997. __("Select from the web server upload directory <b>%s</b>:"),
  2998. htmlspecialchars(self::userDir($uploaddir))
  2999. )
  3000. . '</label>';
  3001. $extensions = '';
  3002. foreach ($import_list as $import_plugin) {
  3003. if (! empty($extensions)) {
  3004. $extensions .= '|';
  3005. }
  3006. $extensions .= $import_plugin->getProperties()->getExtension();
  3007. }
  3008. $matcher = '@\.(' . $extensions . ')(\.('
  3009. . PMA_supportedDecompressions() . '))?$@';
  3010. $active = (isset($GLOBALS['timeout_passed']) && $GLOBALS['timeout_passed']
  3011. && isset($local_import_file))
  3012. ? $local_import_file
  3013. : '';
  3014. $files = PMA_getFileSelectOptions(
  3015. self::userDir($uploaddir),
  3016. $matcher,
  3017. $active
  3018. );
  3019. if ($files === false) {
  3020. PMA_Message::error(
  3021. __('The directory you set for upload work cannot be reached.')
  3022. )->display();
  3023. } elseif (! empty($files)) {
  3024. $block_html .= "\n"
  3025. . ' <select style="margin: 5px" size="1" '
  3026. . 'name="local_import_file" '
  3027. . 'id="select_local_import_file">' . "\n"
  3028. . ' <option value="">&nbsp;</option>' . "\n"
  3029. . $files
  3030. . ' </select>' . "\n";
  3031. } elseif (empty ($files)) {
  3032. $block_html .= '<i>' . __('There are no files to upload!') . '</i>';
  3033. }
  3034. return $block_html;
  3035. }
  3036. /**
  3037. * Build titles and icons for action links
  3038. *
  3039. * @return array the action titles
  3040. */
  3041. public static function buildActionTitles()
  3042. {
  3043. $titles = array();
  3044. $titles['Browse'] = self::getIcon('b_browse.png', __('Browse'));
  3045. $titles['NoBrowse'] = self::getIcon('bd_browse.png', __('Browse'));
  3046. $titles['Search'] = self::getIcon('b_select.png', __('Search'));
  3047. $titles['NoSearch'] = self::getIcon('bd_select.png', __('Search'));
  3048. $titles['Insert'] = self::getIcon('b_insrow.png', __('Insert'));
  3049. $titles['NoInsert'] = self::getIcon('bd_insrow.png', __('Insert'));
  3050. $titles['Structure'] = self::getIcon('b_props.png', __('Structure'));
  3051. $titles['Drop'] = self::getIcon('b_drop.png', __('Drop'));
  3052. $titles['NoDrop'] = self::getIcon('bd_drop.png', __('Drop'));
  3053. $titles['Empty'] = self::getIcon('b_empty.png', __('Empty'));
  3054. $titles['NoEmpty'] = self::getIcon('bd_empty.png', __('Empty'));
  3055. $titles['Edit'] = self::getIcon('b_edit.png', __('Edit'));
  3056. $titles['NoEdit'] = self::getIcon('bd_edit.png', __('Edit'));
  3057. $titles['Export'] = self::getIcon('b_export.png', __('Export'));
  3058. $titles['NoExport'] = self::getIcon('bd_export.png', __('Export'));
  3059. $titles['Execute'] = self::getIcon('b_nextpage.png', __('Execute'));
  3060. $titles['NoExecute'] = self::getIcon('bd_nextpage.png', __('Execute'));
  3061. // For Favorite/NoFavorite, we need icon only.
  3062. $titles['Favorite'] = self::getIcon('b_favorite.png', '');
  3063. $titles['NoFavorite']= self::getIcon('b_no_favorite.png', '');
  3064. return $titles;
  3065. }
  3066. /**
  3067. * This function processes the datatypes supported by the DB,
  3068. * as specified in PMA_Types->getColumns() and either returns an array
  3069. * (useful for quickly checking if a datatype is supported)
  3070. * or an HTML snippet that creates a drop-down list.
  3071. *
  3072. * @param bool $html Whether to generate an html snippet or an array
  3073. * @param string $selected The value to mark as selected in HTML mode
  3074. *
  3075. * @return mixed An HTML snippet or an array of datatypes.
  3076. *
  3077. */
  3078. public static function getSupportedDatatypes($html = false, $selected = '')
  3079. {
  3080. if ($html) {
  3081. // NOTE: the SELECT tag in not included in this snippet.
  3082. $retval = '';
  3083. foreach ($GLOBALS['PMA_Types']->getColumns() as $key => $value) {
  3084. if (is_array($value)) {
  3085. $retval .= "<optgroup label='" . htmlspecialchars($key) . "'>";
  3086. foreach ($value as $subvalue) {
  3087. if ($subvalue == $selected) {
  3088. $retval .= sprintf(
  3089. '<option selected="selected" title="%s">%s</option>',
  3090. $GLOBALS['PMA_Types']->getTypeDescription($subvalue),
  3091. $subvalue
  3092. );
  3093. } else if ($subvalue === '-') {
  3094. $retval .= '<option disabled="disabled">';
  3095. $retval .= $subvalue;
  3096. $retval .= '</option>';
  3097. } else {
  3098. $retval .= sprintf(
  3099. '<option title="%s">%s</option>',
  3100. $GLOBALS['PMA_Types']->getTypeDescription($subvalue),
  3101. $subvalue
  3102. );
  3103. }
  3104. }
  3105. $retval .= '</optgroup>';
  3106. } else {
  3107. if ($selected == $value) {
  3108. $retval .= sprintf(
  3109. '<option selected="selected" title="%s">%s</option>',
  3110. $GLOBALS['PMA_Types']->getTypeDescription($value),
  3111. $value
  3112. );
  3113. } else {
  3114. $retval .= sprintf(
  3115. '<option title="%s">%s</option>',
  3116. $GLOBALS['PMA_Types']->getTypeDescription($value),
  3117. $value
  3118. );
  3119. }
  3120. }
  3121. }
  3122. } else {
  3123. $retval = array();
  3124. foreach ($GLOBALS['PMA_Types']->getColumns() as $value) {
  3125. if (is_array($value)) {
  3126. foreach ($value as $subvalue) {
  3127. if ($subvalue !== '-') {
  3128. $retval[] = $subvalue;
  3129. }
  3130. }
  3131. } else {
  3132. if ($value !== '-') {
  3133. $retval[] = $value;
  3134. }
  3135. }
  3136. }
  3137. }
  3138. return $retval;
  3139. } // end getSupportedDatatypes()
  3140. /**
  3141. * Returns a list of datatypes that are not (yet) handled by PMA.
  3142. * Used by: tbl_change.php and libraries/db_routines.inc.php
  3143. *
  3144. * @return array list of datatypes
  3145. */
  3146. public static function unsupportedDatatypes()
  3147. {
  3148. $no_support_types = array();
  3149. return $no_support_types;
  3150. }
  3151. /**
  3152. * Return GIS data types
  3153. *
  3154. * @param bool $upper_case whether to return values in upper case
  3155. *
  3156. * @return array GIS data types
  3157. */
  3158. public static function getGISDatatypes($upper_case = false)
  3159. {
  3160. $gis_data_types = array(
  3161. 'geometry',
  3162. 'point',
  3163. 'linestring',
  3164. 'polygon',
  3165. 'multipoint',
  3166. 'multilinestring',
  3167. 'multipolygon',
  3168. 'geometrycollection'
  3169. );
  3170. if ($upper_case) {
  3171. for ($i = 0, $nb = count($gis_data_types); $i < $nb; $i++) {
  3172. $gis_data_types[$i] = strtoupper($gis_data_types[$i]);
  3173. }
  3174. }
  3175. return $gis_data_types;
  3176. }
  3177. /**
  3178. * Generates GIS data based on the string passed.
  3179. *
  3180. * @param string $gis_string GIS string
  3181. *
  3182. * @return string GIS data enclosed in 'GeomFromText' function
  3183. */
  3184. public static function createGISData($gis_string)
  3185. {
  3186. $gis_string = trim($gis_string);
  3187. $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING|'
  3188. . 'POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)';
  3189. if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $gis_string)) {
  3190. return 'GeomFromText(' . $gis_string . ')';
  3191. } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $gis_string)) {
  3192. return "GeomFromText('" . $gis_string . "')";
  3193. } else {
  3194. return $gis_string;
  3195. }
  3196. }
  3197. /**
  3198. * Returns the names and details of the functions
  3199. * that can be applied on geometry data typess.
  3200. *
  3201. * @param string $geom_type if provided the output is limited to the functions
  3202. * that are applicable to the provided geometry type.
  3203. * @param bool $binary if set to false functions that take two geometries
  3204. * as arguments will not be included.
  3205. * @param bool $display if set to true separators will be added to the
  3206. * output array.
  3207. *
  3208. * @return array names and details of the functions that can be applied on
  3209. * geometry data typess.
  3210. */
  3211. public static function getGISFunctions(
  3212. $geom_type = null, $binary = true, $display = false
  3213. ) {
  3214. $funcs = array();
  3215. if ($display) {
  3216. $funcs[] = array('display' => ' ');
  3217. }
  3218. // Unary functions common to all geomety types
  3219. $funcs['Dimension'] = array('params' => 1, 'type' => 'int');
  3220. $funcs['Envelope'] = array('params' => 1, 'type' => 'Polygon');
  3221. $funcs['GeometryType'] = array('params' => 1, 'type' => 'text');
  3222. $funcs['SRID'] = array('params' => 1, 'type' => 'int');
  3223. $funcs['IsEmpty'] = array('params' => 1, 'type' => 'int');
  3224. $funcs['IsSimple'] = array('params' => 1, 'type' => 'int');
  3225. $geom_type = trim(strtolower($geom_type));
  3226. if ($display && $geom_type != 'geometry' && $geom_type != 'multipoint') {
  3227. $funcs[] = array('display' => '--------');
  3228. }
  3229. // Unary functions that are specific to each geomety type
  3230. if ($geom_type == 'point') {
  3231. $funcs['X'] = array('params' => 1, 'type' => 'float');
  3232. $funcs['Y'] = array('params' => 1, 'type' => 'float');
  3233. } elseif ($geom_type == 'multipoint') {
  3234. // no fucntions here
  3235. } elseif ($geom_type == 'linestring') {
  3236. $funcs['EndPoint'] = array('params' => 1, 'type' => 'point');
  3237. $funcs['GLength'] = array('params' => 1, 'type' => 'float');
  3238. $funcs['NumPoints'] = array('params' => 1, 'type' => 'int');
  3239. $funcs['StartPoint'] = array('params' => 1, 'type' => 'point');
  3240. $funcs['IsRing'] = array('params' => 1, 'type' => 'int');
  3241. } elseif ($geom_type == 'multilinestring') {
  3242. $funcs['GLength'] = array('params' => 1, 'type' => 'float');
  3243. $funcs['IsClosed'] = array('params' => 1, 'type' => 'int');
  3244. } elseif ($geom_type == 'polygon') {
  3245. $funcs['Area'] = array('params' => 1, 'type' => 'float');
  3246. $funcs['ExteriorRing'] = array('params' => 1, 'type' => 'linestring');
  3247. $funcs['NumInteriorRings'] = array('params' => 1, 'type' => 'int');
  3248. } elseif ($geom_type == 'multipolygon') {
  3249. $funcs['Area'] = array('params' => 1, 'type' => 'float');
  3250. $funcs['Centroid'] = array('params' => 1, 'type' => 'point');
  3251. // Not yet implemented in MySQL
  3252. //$funcs['PointOnSurface'] = array('params' => 1, 'type' => 'point');
  3253. } elseif ($geom_type == 'geometrycollection') {
  3254. $funcs['NumGeometries'] = array('params' => 1, 'type' => 'int');
  3255. }
  3256. // If we are asked for binary functions as well
  3257. if ($binary) {
  3258. // section separator
  3259. if ($display) {
  3260. $funcs[] = array('display' => '--------');
  3261. }
  3262. if (PMA_MYSQL_INT_VERSION < 50601) {
  3263. $funcs['Crosses'] = array('params' => 2, 'type' => 'int');
  3264. $funcs['Contains'] = array('params' => 2, 'type' => 'int');
  3265. $funcs['Disjoint'] = array('params' => 2, 'type' => 'int');
  3266. $funcs['Equals'] = array('params' => 2, 'type' => 'int');
  3267. $funcs['Intersects'] = array('params' => 2, 'type' => 'int');
  3268. $funcs['Overlaps'] = array('params' => 2, 'type' => 'int');
  3269. $funcs['Touches'] = array('params' => 2, 'type' => 'int');
  3270. $funcs['Within'] = array('params' => 2, 'type' => 'int');
  3271. } else {
  3272. // If MySQl version is greaeter than or equal 5.6.1,
  3273. // use the ST_ prefix.
  3274. $funcs['ST_Crosses'] = array('params' => 2, 'type' => 'int');
  3275. $funcs['ST_Contains'] = array('params' => 2, 'type' => 'int');
  3276. $funcs['ST_Disjoint'] = array('params' => 2, 'type' => 'int');
  3277. $funcs['ST_Equals'] = array('params' => 2, 'type' => 'int');
  3278. $funcs['ST_Intersects'] = array('params' => 2, 'type' => 'int');
  3279. $funcs['ST_Overlaps'] = array('params' => 2, 'type' => 'int');
  3280. $funcs['ST_Touches'] = array('params' => 2, 'type' => 'int');
  3281. $funcs['ST_Within'] = array('params' => 2, 'type' => 'int');
  3282. }
  3283. if ($display) {
  3284. $funcs[] = array('display' => '--------');
  3285. }
  3286. // Minimum bounding rectangle functions
  3287. $funcs['MBRContains'] = array('params' => 2, 'type' => 'int');
  3288. $funcs['MBRDisjoint'] = array('params' => 2, 'type' => 'int');
  3289. $funcs['MBREquals'] = array('params' => 2, 'type' => 'int');
  3290. $funcs['MBRIntersects'] = array('params' => 2, 'type' => 'int');
  3291. $funcs['MBROverlaps'] = array('params' => 2, 'type' => 'int');
  3292. $funcs['MBRTouches'] = array('params' => 2, 'type' => 'int');
  3293. $funcs['MBRWithin'] = array('params' => 2, 'type' => 'int');
  3294. }
  3295. return $funcs;
  3296. }
  3297. /**
  3298. * Returns default function for a particular column.
  3299. *
  3300. * @param array $field Data about the column for which
  3301. * to generate the dropdown
  3302. * @param bool $insert_mode Whether the operation is 'insert'
  3303. *
  3304. * @global array $cfg PMA configuration
  3305. * @global array $analyzed_sql Analyzed SQL query
  3306. * @global mixed $data data of currently edited row
  3307. * (used to detect whether to choose defaults)
  3308. *
  3309. * @return string An HTML snippet of a dropdown list with function
  3310. * names appropriate for the requested column.
  3311. */
  3312. public static function getDefaultFunctionForField($field, $insert_mode)
  3313. {
  3314. /*
  3315. * @todo Except for $cfg, no longer use globals but pass as parameters
  3316. * from higher levels
  3317. */
  3318. global $cfg, $analyzed_sql, $data;
  3319. $default_function = '';
  3320. // Can we get field class based values?
  3321. $current_class = $GLOBALS['PMA_Types']->getTypeClass($field['True_Type']);
  3322. if (! empty($current_class)) {
  3323. if (isset($cfg['DefaultFunctions']['FUNC_' . $current_class])) {
  3324. $default_function
  3325. = $cfg['DefaultFunctions']['FUNC_' . $current_class];
  3326. }
  3327. }
  3328. $analyzed_sql_field_array = $analyzed_sql[0]['create_table_fields']
  3329. [$field['Field']];
  3330. // what function defined as default?
  3331. // for the first timestamp we don't set the default function
  3332. // if there is a default value for the timestamp
  3333. // (not including CURRENT_TIMESTAMP)
  3334. // and the column does not have the
  3335. // ON UPDATE DEFAULT TIMESTAMP attribute.
  3336. if (($field['True_Type'] == 'timestamp')
  3337. && $field['first_timestamp']
  3338. && empty($field['Default'])
  3339. && empty($data)
  3340. && ! isset($analyzed_sql_field_array['on_update_current_timestamp'])
  3341. && ($analyzed_sql_field_array['default_value'] != 'NULL')
  3342. ) {
  3343. $default_function = $cfg['DefaultFunctions']['first_timestamp'];
  3344. }
  3345. // For primary keys of type char(36) or varchar(36) UUID if the default
  3346. // function
  3347. // Only applies to insert mode, as it would silently trash data on updates.
  3348. if ($insert_mode
  3349. && $field['Key'] == 'PRI'
  3350. && ($field['Type'] == 'char(36)' || $field['Type'] == 'varchar(36)')
  3351. ) {
  3352. $default_function = $cfg['DefaultFunctions']['FUNC_UUID'];
  3353. }
  3354. // this is set only when appropriate and is always true
  3355. if (isset($field['display_binary_as_hex'])) {
  3356. $default_function = 'UNHEX';
  3357. }
  3358. return $default_function;
  3359. }
  3360. /**
  3361. * Creates a dropdown box with MySQL functions for a particular column.
  3362. *
  3363. * @param array $field Data about the column for which
  3364. * to generate the dropdown
  3365. * @param bool $insert_mode Whether the operation is 'insert'
  3366. *
  3367. * @return string An HTML snippet of a dropdown list with function
  3368. * names appropriate for the requested column.
  3369. */
  3370. public static function getFunctionsForField($field, $insert_mode)
  3371. {
  3372. $default_function = self::getDefaultFunctionForField($field, $insert_mode);
  3373. $dropdown_built = array();
  3374. // Create the output
  3375. $retval = '<option></option>' . "\n";
  3376. // loop on the dropdown array and print all available options for that
  3377. // field.
  3378. $functions = $GLOBALS['PMA_Types']->getFunctions($field['True_Type']);
  3379. foreach ($functions as $function) {
  3380. $retval .= '<option';
  3381. if ($default_function === $function) {
  3382. $retval .= ' selected="selected"';
  3383. }
  3384. $retval .= '>' . $function . '</option>' . "\n";
  3385. $dropdown_built[$function] = true;
  3386. }
  3387. // Create separator before all functions list
  3388. if (count($functions) > 0) {
  3389. $retval .= '<option value="" disabled="disabled">--------</option>'
  3390. . "\n";
  3391. }
  3392. // For compatibility's sake, do not let out all other functions. Instead
  3393. // print a separator (blank) and then show ALL functions which weren't
  3394. // shown yet.
  3395. $functions = $GLOBALS['PMA_Types']->getAllFunctions();
  3396. foreach ($functions as $function) {
  3397. // Skip already included functions
  3398. if (isset($dropdown_built[$function])) {
  3399. continue;
  3400. }
  3401. $retval .= '<option';
  3402. if ($default_function === $function) {
  3403. $retval .= ' selected="selected"';
  3404. }
  3405. $retval .= '>' . $function . '</option>' . "\n";
  3406. } // end for
  3407. return $retval;
  3408. } // end getFunctionsForField()
  3409. /**
  3410. * Checks if the current user has a specific privilege and returns true if the
  3411. * user indeed has that privilege or false if (s)he doesn't. This function must
  3412. * only be used for features that are available since MySQL 5, because it
  3413. * relies on the INFORMATION_SCHEMA database to be present.
  3414. *
  3415. * Example: currentUserHasPrivilege('CREATE ROUTINE', 'mydb');
  3416. * // Checks if the currently logged in user has the global
  3417. * // 'CREATE ROUTINE' privilege or, if not, checks if the
  3418. * // user has this privilege on database 'mydb'.
  3419. *
  3420. * @param string $priv The privilege to check
  3421. * @param mixed $db null, to only check global privileges
  3422. * string, db name where to also check for privileges
  3423. * @param mixed $tbl null, to only check global/db privileges
  3424. * string, table name where to also check for privileges
  3425. *
  3426. * @return bool
  3427. */
  3428. public static function currentUserHasPrivilege($priv, $db = null, $tbl = null)
  3429. {
  3430. // TRIGGER privilege was added in MySQL 5.1.6.
  3431. // Before MySQL 5.1.6, the SUPER privilege was required to create i
  3432. // or drop triggers.
  3433. if ($priv == "TRIGGER" && PMA_MYSQL_INT_VERSION < 50160) {
  3434. $priv = "SUPER";
  3435. }
  3436. // Get the username for the current user in the format
  3437. // required to use in the information schema database.
  3438. $user = $GLOBALS['dbi']->fetchValue("SELECT CURRENT_USER();");
  3439. if ($user === false) {
  3440. return false;
  3441. }
  3442. $user = explode('@', $user);
  3443. $username = "''";
  3444. $username .= str_replace("'", "''", $user[0]);
  3445. $username .= "''@''";
  3446. $username .= str_replace("'", "''", $user[1]);
  3447. $username .= "''";
  3448. // Prepare the query
  3449. $query = "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`%s` "
  3450. . "WHERE GRANTEE='%s' AND PRIVILEGE_TYPE='%s'";
  3451. // Check global privileges first.
  3452. $user_privileges = $GLOBALS['dbi']->fetchValue(
  3453. sprintf(
  3454. $query,
  3455. 'USER_PRIVILEGES',
  3456. $username,
  3457. $priv
  3458. )
  3459. );
  3460. if ($user_privileges) {
  3461. return true;
  3462. }
  3463. // If a database name was provided and user does not have the
  3464. // required global privilege, try database-wise permissions.
  3465. if ($db !== null) {
  3466. // need to escape wildcards in db and table names, see bug #3566
  3467. // (wildcard characters appear as being quoted with a backslash
  3468. // when querying TABLE_SCHEMA.SCHEMA_PRIVILEGES)
  3469. $db = str_replace(array('%', '_'), array('\%', '\_'), $db);
  3470. /*
  3471. * This is to take into account a wildcard db privilege
  3472. * so we replace % by .* and _ by . to be able to compare
  3473. * with REGEXP.
  3474. *
  3475. * Also, we need to double the inner % to please sprintf().
  3476. */
  3477. $query .= " AND '%s' REGEXP"
  3478. . " REPLACE(REPLACE(TABLE_SCHEMA, '_', '.'), '%%', '.*')";
  3479. $schema_privileges = $GLOBALS['dbi']->fetchValue(
  3480. sprintf(
  3481. $query,
  3482. 'SCHEMA_PRIVILEGES',
  3483. $username,
  3484. $priv,
  3485. self::sqlAddSlashes($db)
  3486. )
  3487. );
  3488. if ($schema_privileges) {
  3489. return true;
  3490. }
  3491. } else {
  3492. // There was no database name provided and the user
  3493. // does not have the correct global privilege.
  3494. return false;
  3495. }
  3496. // If a table name was also provided and we still didn't
  3497. // find any valid privileges, try table-wise privileges.
  3498. if ($tbl !== null) {
  3499. // need to escape wildcards in db and table names, see bug #3518484
  3500. $tbl = str_replace(array('%', '_'), array('\%', '\_'), $tbl);
  3501. $query .= " AND TABLE_NAME='%s'";
  3502. $table_privileges = $GLOBALS['dbi']->fetchValue(
  3503. sprintf(
  3504. $query,
  3505. 'TABLE_PRIVILEGES',
  3506. $username,
  3507. $priv,
  3508. self::sqlAddSlashes($db),
  3509. self::sqlAddSlashes($tbl)
  3510. )
  3511. );
  3512. if ($table_privileges) {
  3513. return true;
  3514. }
  3515. }
  3516. // If we reached this point, the user does not
  3517. // have even valid table-wise privileges.
  3518. return false;
  3519. }
  3520. /**
  3521. * Returns server type for current connection
  3522. *
  3523. * Known types are: Drizzle, MariaDB and MySQL (default)
  3524. *
  3525. * @return string
  3526. */
  3527. public static function getServerType()
  3528. {
  3529. $server_type = 'MySQL';
  3530. if (PMA_DRIZZLE) {
  3531. $server_type = 'Drizzle';
  3532. } else if (stripos(PMA_MYSQL_STR_VERSION, 'mariadb') !== false) {
  3533. $server_type = 'MariaDB';
  3534. } else if (stripos(PMA_MYSQL_VERSION_COMMENT, 'percona') !== false) {
  3535. $server_type = 'Percona Server';
  3536. }
  3537. return $server_type;
  3538. }
  3539. /**
  3540. * Analyzes the limit clause and return the start and length attributes of it.
  3541. *
  3542. * @param string $limit_clause limit clause
  3543. *
  3544. * @return array|void Start and length attributes of the limit clause
  3545. */
  3546. public static function analyzeLimitClause($limit_clause)
  3547. {
  3548. $start_and_length = explode(',', str_ireplace('LIMIT', '', $limit_clause));
  3549. $size = count($start_and_length);
  3550. if ($size == 1) {
  3551. return array(
  3552. 'start' => '0',
  3553. 'length' => trim($start_and_length[0])
  3554. );
  3555. } elseif ($size == 2) {
  3556. return array(
  3557. 'start' => trim($start_and_length[0]),
  3558. 'length' => trim($start_and_length[1])
  3559. );
  3560. }
  3561. }
  3562. /**
  3563. * Prepare HTML code for display button.
  3564. *
  3565. * @return String
  3566. */
  3567. public static function getButton()
  3568. {
  3569. return '<p class="print_ignore">'
  3570. . '<input type="button" class="button" id="print" value="'
  3571. . __('Print') . '" />'
  3572. . '</p>';
  3573. }
  3574. /**
  3575. * Parses ENUM/SET values
  3576. *
  3577. * @param string $definition The definition of the column
  3578. * for which to parse the values
  3579. * @param bool $escapeHtml Whether to escape html entitites
  3580. *
  3581. * @return array
  3582. */
  3583. public static function parseEnumSetValues($definition, $escapeHtml = true)
  3584. {
  3585. $values_string = htmlentities($definition, ENT_COMPAT, "UTF-8");
  3586. // There is a JS port of the below parser in functions.js
  3587. // If you are fixing something here,
  3588. // you need to also update the JS port.
  3589. $values = array();
  3590. $in_string = false;
  3591. $buffer = '';
  3592. for ($i=0, $length = strlen($values_string); $i < $length; $i++) {
  3593. $curr = $values_string[$i];
  3594. $next = ($i == strlen($values_string)-1) ? '' : $values_string[$i+1];
  3595. if (! $in_string && $curr == "'") {
  3596. $in_string = true;
  3597. } else if (($in_string && $curr == "\\") && $next == "\\") {
  3598. $buffer .= "&#92;";
  3599. $i++;
  3600. } else if (($in_string && $next == "'")
  3601. && ($curr == "'" || $curr == "\\")
  3602. ) {
  3603. $buffer .= "&#39;";
  3604. $i++;
  3605. } else if ($in_string && $curr == "'") {
  3606. $in_string = false;
  3607. $values[] = $buffer;
  3608. $buffer = '';
  3609. } else if ($in_string) {
  3610. $buffer .= $curr;
  3611. }
  3612. }
  3613. if (strlen($buffer) > 0) {
  3614. // The leftovers in the buffer are the last value (if any)
  3615. $values[] = $buffer;
  3616. }
  3617. if (! $escapeHtml) {
  3618. foreach ($values as $key => $value) {
  3619. $values[$key] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
  3620. }
  3621. }
  3622. return $values;
  3623. }
  3624. /**
  3625. * fills given tooltip arrays
  3626. *
  3627. * @param array &$tooltip_truename tooltip data
  3628. * @param array &$tooltip_aliasname tooltip data
  3629. * @param array $table tabledata
  3630. *
  3631. * @return void
  3632. */
  3633. public static function fillTooltip(
  3634. &$tooltip_truename, &$tooltip_aliasname, $table
  3635. ) {
  3636. if (strstr($table['Comment'], '; InnoDB free') === false) {
  3637. if (!strstr($table['Comment'], 'InnoDB free') === false) {
  3638. // here we have just InnoDB generated part
  3639. $table['Comment'] = '';
  3640. }
  3641. } else {
  3642. // remove InnoDB comment from end, just the minimal part
  3643. // (*? is non greedy)
  3644. $table['Comment'] = preg_replace(
  3645. '@; InnoDB free:.*?$@', '', $table['Comment']
  3646. );
  3647. }
  3648. // views have VIEW as comment so it's not a real comment put by a user
  3649. if ('VIEW' == $table['Comment']) {
  3650. $table['Comment'] = '';
  3651. }
  3652. if (empty($table['Comment'])) {
  3653. $table['Comment'] = $table['Name'];
  3654. } else {
  3655. // todo: why?
  3656. $table['Comment'] .= ' ';
  3657. }
  3658. $tooltip_truename[$table['Name']] = $table['Name'];
  3659. $tooltip_aliasname[$table['Name']] = $table['Comment'];
  3660. if (isset($table['Create_time']) && !empty($table['Create_time'])) {
  3661. $tooltip_aliasname[$table['Name']] .= ', ' . __('Creation')
  3662. . ': '
  3663. . PMA_Util::localisedDate(strtotime($table['Create_time']));
  3664. }
  3665. if (! empty($table['Update_time'])) {
  3666. $tooltip_aliasname[$table['Name']] .= ', ' . __('Last update')
  3667. . ': '
  3668. . PMA_Util::localisedDate(strtotime($table['Update_time']));
  3669. }
  3670. if (! empty($table['Check_time'])) {
  3671. $tooltip_aliasname[$table['Name']] .= ', ' . __('Last check')
  3672. . ': '
  3673. . PMA_Util::localisedDate(strtotime($table['Check_time']));
  3674. }
  3675. }
  3676. /**
  3677. * Get regular expression which occur first inside the given sql query.
  3678. *
  3679. * @param Array $regex_array Comparing regular expressions.
  3680. * @param String $query SQL query to be checked.
  3681. *
  3682. * @return String Matching regular expression.
  3683. */
  3684. public static function getFirstOccurringRegularExpression($regex_array, $query)
  3685. {
  3686. $minimum_first_occurence_index = null;
  3687. $regex = null;
  3688. foreach ($regex_array as $test_regex) {
  3689. if (preg_match($test_regex, $query, $matches, PREG_OFFSET_CAPTURE)) {
  3690. if (is_null($minimum_first_occurence_index)
  3691. || ($matches[0][1] < $minimum_first_occurence_index)
  3692. ) {
  3693. $regex = $test_regex;
  3694. $minimum_first_occurence_index = $matches[0][1];
  3695. }
  3696. }
  3697. }
  3698. return $regex;
  3699. }
  3700. /**
  3701. * Return the list of tabs for the menu with corresponding names
  3702. *
  3703. * @param string $level 'server', 'db' or 'table' level
  3704. *
  3705. * @return array list of tabs for the menu
  3706. */
  3707. public static function getMenuTabList($level = null)
  3708. {
  3709. $tabList = array(
  3710. 'server' => array(
  3711. 'databases' => __('Databases'),
  3712. 'sql' => __('SQL'),
  3713. 'status' => __('Status'),
  3714. 'rights' => __('Users'),
  3715. 'export' => __('Export'),
  3716. 'import' => __('Import'),
  3717. 'settings' => __('Settings'),
  3718. 'binlog' => __('Binary log'),
  3719. 'replication' => __('Replication'),
  3720. 'vars' => __('Variables'),
  3721. 'charset' => __('Charsets'),
  3722. 'plugins' => __('Plugins'),
  3723. 'engine' => __('Engines')
  3724. ),
  3725. 'db' => array(
  3726. 'structure' => __('Structure'),
  3727. 'sql' => __('SQL'),
  3728. 'search' => __('Search'),
  3729. 'qbe' => __('Query'),
  3730. 'export' => __('Export'),
  3731. 'import' => __('Import'),
  3732. 'operation' => __('Operations'),
  3733. 'privileges' => __('Privileges'),
  3734. 'routines' => __('Routines'),
  3735. 'events' => __('Events'),
  3736. 'triggers' => __('Triggers'),
  3737. 'tracking' => __('Tracking'),
  3738. 'designer' => __('Designer')
  3739. ),
  3740. 'table' => array(
  3741. 'browse' => __('Browse'),
  3742. 'structure' => __('Structure'),
  3743. 'sql' => __('SQL'),
  3744. 'search' => __('Search'),
  3745. 'insert' => __('Insert'),
  3746. 'export' => __('Export'),
  3747. 'import' => __('Import'),
  3748. 'privileges' => __('Privileges'),
  3749. 'operation' => __('Operations'),
  3750. 'tracking' => __('Tracking'),
  3751. 'triggers' => __('Triggers'),
  3752. )
  3753. );
  3754. if ($level == null) {
  3755. return $tabList;
  3756. } else if (array_key_exists($level, $tabList)) {
  3757. return $tabList[$level];
  3758. } else {
  3759. return null;
  3760. }
  3761. }
  3762. /**
  3763. * Returns information with regards to handling the http request
  3764. *
  3765. * @param array $context Data about the context for which
  3766. * to http request is sent
  3767. *
  3768. * @return array of updated context information
  3769. */
  3770. public static function handleContext(array $context)
  3771. {
  3772. if (strlen($GLOBALS['cfg']['ProxyUrl'])) {
  3773. $context['http'] = array(
  3774. 'proxy' => $GLOBALS['cfg']['ProxyUrl'],
  3775. 'request_fulluri' => true
  3776. );
  3777. if (strlen($GLOBALS['cfg']['ProxyUser'])) {
  3778. $auth = base64_encode(
  3779. $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass']
  3780. );
  3781. $context['http']['header'] .= 'Proxy-Authorization: Basic '
  3782. . $auth . "\r\n";
  3783. }
  3784. }
  3785. return $context;
  3786. }
  3787. /**
  3788. * Updates an existing curl as necessary
  3789. *
  3790. * @param resource $curl_handle A curl_handle resource
  3791. * created by curl_init which should
  3792. * have several options set
  3793. *
  3794. * @return resource curl_handle with updated options
  3795. */
  3796. public static function configureCurl(resource $curl_handle)
  3797. {
  3798. if (strlen($GLOBALS['cfg']['ProxyUrl'])) {
  3799. curl_setopt($curl_handle, CURLOPT_PROXY, $GLOBALS['cfg']['ProxyUrl']);
  3800. if (strlen($GLOBALS['cfg']['ProxyUser'])) {
  3801. curl_setopt(
  3802. $curl_handle,
  3803. CURLOPT_PROXYUSERPWD,
  3804. $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass']
  3805. );
  3806. }
  3807. }
  3808. return $curl_handle;
  3809. }
  3810. /**
  3811. * Returns information with latest version from phpmyadmin.net
  3812. *
  3813. * @return String JSON decoded object with the data
  3814. */
  3815. public static function getLatestVersion()
  3816. {
  3817. // wait 3s at most for server response, it's enough to get information
  3818. // from a working server
  3819. $connection_timeout = 3;
  3820. $response = '{}';
  3821. // Get response text from phpmyadmin.net or from the session
  3822. // Update cache every 6 hours
  3823. if (isset($_SESSION['cache']['version_check'])
  3824. && time() < $_SESSION['cache']['version_check']['timestamp'] + 3600 * 6
  3825. ) {
  3826. $save = false;
  3827. $response = $_SESSION['cache']['version_check']['response'];
  3828. } else {
  3829. $save = true;
  3830. $file = 'http://www.phpmyadmin.net/home_page/version.json';
  3831. if (ini_get('allow_url_fopen')) {
  3832. $context = array(
  3833. 'http' => array(
  3834. 'request_fulluri' => true,
  3835. 'timeout' => $connection_timeout,
  3836. )
  3837. );
  3838. $context = PMA_Util::handleContext($context);
  3839. if (! defined('TESTSUITE')) {
  3840. session_write_close();
  3841. }
  3842. $response = file_get_contents(
  3843. $file,
  3844. false,
  3845. stream_context_create($context)
  3846. );
  3847. } else if (function_exists('curl_init')) {
  3848. $curl_handle = curl_init($file);
  3849. $curl_handle = PMA_Util::configureCurl($curl_handle);
  3850. curl_setopt(
  3851. $curl_handle,
  3852. CURLOPT_HEADER,
  3853. false
  3854. );
  3855. curl_setopt(
  3856. $curl_handle,
  3857. CURLOPT_RETURNTRANSFER,
  3858. true
  3859. );
  3860. curl_setopt(
  3861. $curl_handle,
  3862. CURLOPT_TIMEOUT,
  3863. $connection_timeout
  3864. );
  3865. if (! defined('TESTSUITE')) {
  3866. session_write_close();
  3867. }
  3868. $response = curl_exec($curl_handle);
  3869. }
  3870. }
  3871. $data = json_decode($response);
  3872. if (is_object($data)
  3873. && strlen($data->version)
  3874. && strlen($data->date)
  3875. && $save
  3876. ) {
  3877. if (! isset($_SESSION) && ! defined('TESTSUITE')) {
  3878. ini_set('session.use_only_cookies', false);
  3879. ini_set('session.use_cookies', false);
  3880. ini_set('session.use_trans_sid', false);
  3881. ini_set('session.cache_limiter', null);
  3882. session_start();
  3883. }
  3884. $_SESSION['cache']['version_check'] = array(
  3885. 'response' => $response,
  3886. 'timestamp' => time()
  3887. );
  3888. }
  3889. return $data;
  3890. }
  3891. /**
  3892. * Calculates numerical equivalent of phpMyAdmin version string
  3893. *
  3894. * @param string $version version
  3895. *
  3896. * @return mixed false on failure, integer on success
  3897. */
  3898. public static function versionToInt($version)
  3899. {
  3900. $parts = explode('-', $version);
  3901. if (count($parts) > 1) {
  3902. $suffix = $parts[1];
  3903. } else {
  3904. $suffix = '';
  3905. }
  3906. $parts = explode('.', $parts[0]);
  3907. $result = 0;
  3908. if (count($parts) >= 1 && is_numeric($parts[0])) {
  3909. $result += 1000000 * $parts[0];
  3910. }
  3911. if (count($parts) >= 2 && is_numeric($parts[1])) {
  3912. $result += 10000 * $parts[1];
  3913. }
  3914. if (count($parts) >= 3 && is_numeric($parts[2])) {
  3915. $result += 100 * $parts[2];
  3916. }
  3917. if (count($parts) >= 4 && is_numeric($parts[3])) {
  3918. $result += 1 * $parts[3];
  3919. }
  3920. if (!empty($suffix)) {
  3921. $matches = array();
  3922. if (preg_match('/^(\D+)(\d+)$/', $suffix, $matches)) {
  3923. $suffix = $matches[1];
  3924. $result += intval($matches[2]);
  3925. }
  3926. switch ($suffix) {
  3927. case 'pl':
  3928. $result += 60;
  3929. break;
  3930. case 'rc':
  3931. $result += 30;
  3932. break;
  3933. case 'beta':
  3934. $result += 20;
  3935. break;
  3936. case 'alpha':
  3937. $result += 10;
  3938. break;
  3939. case 'dev':
  3940. $result += 0;
  3941. break;
  3942. }
  3943. } else {
  3944. $result += 50; // for final
  3945. }
  3946. return $result;
  3947. }
  3948. /**
  3949. * Add fractional seconds to time, datetime and timestamp strings.
  3950. * If the string contains fractional seconds,
  3951. * pads it with 0s up to 6 decimal places.
  3952. *
  3953. * @param string $value time, datetime or timestamp strings
  3954. *
  3955. * @return string time, datetime or timestamp strings with fractional seconds
  3956. */
  3957. public static function addMicroseconds($value)
  3958. {
  3959. if (empty($value) || $value == 'CURRENT_TIMESTAMP') {
  3960. return $value;
  3961. } elseif (strpos($value, '.')) {
  3962. $value .= '000000';
  3963. return substr($value, 0, strpos($value, '.') + 7);
  3964. } else {
  3965. return $value . '.000000';
  3966. }
  3967. }
  3968. /**
  3969. * Reads the file, detects the compression MIME type, closes the file
  3970. * and returns the MIME type
  3971. *
  3972. * @param resource $file the file handle
  3973. *
  3974. * @return string the MIME type for compression, or 'none'
  3975. */
  3976. public static function getCompressionMimeType($file)
  3977. {
  3978. $test = fread($file, 4);
  3979. $len = strlen($test);
  3980. fclose($file);
  3981. if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
  3982. return 'application/gzip';
  3983. }
  3984. if ($len >= 3 && substr($test, 0, 3) == 'BZh') {
  3985. return 'application/bzip2';
  3986. }
  3987. if ($len >= 4 && $test == "PK\003\004") {
  3988. return 'application/zip';
  3989. }
  3990. return 'none';
  3991. }
  3992. }
  3993. ?>