PageRenderTime 41ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/phpmyadmin/libraries/Util.class.php

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