/libraries/classes/Util.php
PHP | 2753 lines | 1839 code | 266 blank | 648 comment | 242 complexity | 31c3ddcdbd5993f7de68d7823478336c MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?php
- declare(strict_types=1);
- namespace PhpMyAdmin;
- use PhpMyAdmin\Html\Generator;
- use PhpMyAdmin\Html\MySQLDocumentation;
- use PhpMyAdmin\Query\Utilities;
- use PhpMyAdmin\SqlParser\Components\Expression;
- use PhpMyAdmin\SqlParser\Context;
- use PhpMyAdmin\SqlParser\Token;
- use PhpMyAdmin\Utils\SessionCache;
- use phpseclib3\Crypt\Random;
- use function __;
- use function _pgettext;
- use function abs;
- use function array_key_exists;
- use function array_map;
- use function array_merge;
- use function array_shift;
- use function array_unique;
- use function basename;
- use function bin2hex;
- use function chr;
- use function class_exists;
- use function count;
- use function ctype_digit;
- use function date;
- use function decbin;
- use function explode;
- use function extension_loaded;
- use function fclose;
- use function floatval;
- use function floor;
- use function fread;
- use function function_exists;
- use function html_entity_decode;
- use function htmlentities;
- use function htmlspecialchars;
- use function htmlspecialchars_decode;
- use function implode;
- use function in_array;
- use function ini_get;
- use function is_array;
- use function is_callable;
- use function is_object;
- use function is_scalar;
- use function is_string;
- use function log10;
- use function mb_detect_encoding;
- use function mb_strlen;
- use function mb_strpos;
- use function mb_strrpos;
- use function mb_strstr;
- use function mb_strtolower;
- use function mb_substr;
- use function number_format;
- use function ord;
- use function parse_url;
- use function preg_match;
- use function preg_quote;
- use function preg_replace;
- use function range;
- use function reset;
- use function round;
- use function rtrim;
- use function set_time_limit;
- use function sort;
- use function sprintf;
- use function str_contains;
- use function str_pad;
- use function str_replace;
- use function strcasecmp;
- use function strftime;
- use function strlen;
- use function strrev;
- use function strtolower;
- use function strtr;
- use function substr;
- use function time;
- use function trim;
- use function uksort;
- use const ENT_COMPAT;
- use const ENT_QUOTES;
- use const PHP_INT_SIZE;
- use const PHP_MAJOR_VERSION;
- use const STR_PAD_LEFT;
- /**
- * Misc functions used all over the scripts.
- */
- class Util
- {
- /**
- * Checks whether configuration value tells to show icons.
- *
- * @param string $value Configuration option name
- */
- public static function showIcons($value): bool
- {
- return in_array($GLOBALS['cfg'][$value], ['icons', 'both']);
- }
- /**
- * Checks whether configuration value tells to show text.
- *
- * @param string $value Configuration option name
- */
- public static function showText($value): bool
- {
- return in_array($GLOBALS['cfg'][$value], ['text', 'both']);
- }
- /**
- * Returns the formatted maximum size for an upload
- *
- * @param int|float|string $maxUploadSize the size
- *
- * @return string the message
- *
- * @access public
- */
- public static function getFormattedMaximumUploadSize($maxUploadSize): string
- {
- // I have to reduce the second parameter (sensitiveness) from 6 to 4
- // to avoid weird results like 512 kKib
- [$maxSize, $maxUnit] = self::formatByteDown($maxUploadSize, 4);
- return '(' . sprintf(__('Max: %s%s'), $maxSize, $maxUnit) . ')';
- }
- /**
- * Add slashes before "_" and "%" characters for using them in MySQL
- * database, table and field names.
- * Note: This function does not escape backslashes!
- *
- * @param string $name the string to escape
- *
- * @return string the escaped string
- *
- * @access public
- */
- public static function escapeMysqlWildcards($name): string
- {
- return strtr($name, ['_' => '\\_', '%' => '\\%']);
- }
- /**
- * removes slashes before "_" and "%" characters
- * Note: This function does not unescape backslashes!
- *
- * @param string $name the string to escape
- *
- * @return string the escaped string
- *
- * @access public
- */
- public static function unescapeMysqlWildcards($name): string
- {
- return strtr($name, ['\\_' => '_', '\\%' => '%']);
- }
- /**
- * removes quotes (',",`) from a quoted string
- *
- * checks if the string is quoted and removes this quotes
- *
- * @param string $quotedString string to remove quotes from
- * @param string $quote type of quote to remove
- *
- * @return string unquoted string
- */
- public static function unQuote(string $quotedString, ?string $quote = null): string
- {
- $quotes = [];
- if ($quote === null) {
- $quotes[] = '`';
- $quotes[] = '"';
- $quotes[] = "'";
- } else {
- $quotes[] = $quote;
- }
- foreach ($quotes as $quote) {
- if (mb_substr($quotedString, 0, 1) === $quote && mb_substr($quotedString, -1, 1) === $quote) {
- $unquotedString = mb_substr($quotedString, 1, -1);
- // replace escaped quotes
- $unquotedString = str_replace($quote . $quote, $quote, $unquotedString);
- return $unquotedString;
- }
- }
- return $quotedString;
- }
- /**
- * Get a URL link to the official MySQL documentation
- *
- * @param string $link contains name of page/anchor that is being linked
- * @param string $anchor anchor to page part
- *
- * @return string the URL link
- *
- * @access public
- */
- public static function getMySQLDocuURL(string $link, string $anchor = ''): string
- {
- global $dbi;
- // Fixup for newly used names:
- $link = str_replace('_', '-', mb_strtolower($link));
- if (empty($link)) {
- $link = 'index';
- }
- $mysql = '5.5';
- $lang = 'en';
- if (isset($dbi)) {
- $serverVersion = $dbi->getVersion();
- if ($serverVersion >= 80000) {
- $mysql = '8.0';
- } elseif ($serverVersion >= 50700) {
- $mysql = '5.7';
- } elseif ($serverVersion >= 50600) {
- $mysql = '5.6';
- } elseif ($serverVersion >= 50500) {
- $mysql = '5.5';
- }
- }
- $url = 'https://dev.mysql.com/doc/refman/'
- . $mysql . '/' . $lang . '/' . $link . '.html';
- if (! empty($anchor)) {
- $url .= '#' . $anchor;
- }
- return Core::linkURL($url);
- }
- /**
- * Get a URL link to the official documentation page of either MySQL
- * or MariaDB depending on the database server
- * of the user.
- *
- * @param bool $isMariaDB if the database server is MariaDB
- *
- * @return string The URL link
- */
- public static function getDocuURL(bool $isMariaDB = false): string
- {
- if ($isMariaDB) {
- $url = 'https://mariadb.com/kb/en/documentation/';
- return Core::linkURL($url);
- }
- return self::getMySQLDocuURL('');
- }
- /**
- * Check the correct row count
- *
- * @param string $db the db name
- * @param array $table the table infos
- *
- * @return int the possibly modified row count
- */
- private static function checkRowCount($db, array $table)
- {
- global $dbi;
- $rowCount = 0;
- if ($table['Rows'] === null) {
- // Do not check exact row count here,
- // if row count is invalid possibly the table is defect
- // and this would break the navigation panel;
- // but we can check row count if this is a view or the
- // information_schema database
- // since Table::countRecords() returns a limited row count
- // in this case.
- // set this because Table::countRecords() can use it
- $tableIsView = $table['TABLE_TYPE'] === 'VIEW';
- if ($tableIsView || Utilities::isSystemSchema($db)) {
- $rowCount = $dbi
- ->getTable($db, $table['Name'])
- ->countRecords();
- }
- }
- return $rowCount;
- }
- /**
- * returns array with tables of given db with extended information and grouped
- *
- * @param string $db
- *
- * @return array (recursive) grouped table list
- */
- public static function getTableList($db): array
- {
- global $dbi;
- $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
- $tables = $dbi->getTablesFull($db);
- if ($GLOBALS['cfg']['NaturalOrder']) {
- uksort($tables, 'strnatcasecmp');
- }
- if (count($tables) < 1) {
- return $tables;
- }
- $default = [
- 'Name' => '',
- 'Rows' => 0,
- 'Comment' => '',
- 'disp_name' => '',
- ];
- $tableGroups = [];
- foreach ($tables as $tableName => $table) {
- $table['Rows'] = self::checkRowCount($db, $table);
- // in $group we save the reference to the place in $table_groups
- // where to store the table info
- if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] && $sep && mb_strstr($tableName, $sep)) {
- $parts = explode($sep, $tableName);
- $group =& $tableGroups;
- $i = 0;
- $groupNameFull = '';
- $partsCount = count($parts) - 1;
- while (($i < $partsCount) && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel'])) {
- $groupName = $parts[$i] . $sep;
- $groupNameFull .= $groupName;
- if (! isset($group[$groupName])) {
- $group[$groupName] = [];
- $group[$groupName]['is' . $sep . 'group'] = true;
- $group[$groupName]['tab' . $sep . 'count'] = 1;
- $group[$groupName]['tab' . $sep . 'group'] = $groupNameFull;
- } elseif (! isset($group[$groupName]['is' . $sep . 'group'])) {
- $table = $group[$groupName];
- $group[$groupName] = [];
- $group[$groupName][$groupName] = $table;
- $group[$groupName]['is' . $sep . 'group'] = true;
- $group[$groupName]['tab' . $sep . 'count'] = 1;
- $group[$groupName]['tab' . $sep . 'group'] = $groupNameFull;
- } else {
- $group[$groupName]['tab' . $sep . 'count']++;
- }
- $group =& $group[$groupName];
- $i++;
- }
- } else {
- if (! isset($tableGroups[$tableName])) {
- $tableGroups[$tableName] = [];
- }
- $group =& $tableGroups;
- }
- $table['disp_name'] = $table['Name'];
- $group[$tableName] = array_merge($default, $table);
- }
- return $tableGroups;
- }
- /* ----------------------- Set of misc functions ----------------------- */
- /**
- * Adds backquotes on both sides of a database, table or field name.
- * and escapes backquotes inside the name with another backquote
- *
- * example:
- * <code>
- * echo backquote('owner`s db'); // `owner``s db`
- *
- * </code>
- *
- * @param array|string $aName the database, table or field name to "backquote" or array of it
- *
- * @return mixed the "backquoted" database, table or field name
- *
- * @access public
- */
- public static function backquote($aName)
- {
- return static::backquoteCompat($aName, 'NONE', true);
- }
- /**
- * Adds backquotes on both sides of a database, table or field name.
- * in compatibility mode
- *
- * example:
- * <code>
- * echo backquoteCompat('owner`s db'); // `owner``s db`
- *
- * </code>
- *
- * @param array|string $aName the database, table or field name to "backquote" or array of it
- * @param string $compatibility string compatibility mode (used by dump functions)
- * @param bool $doIt a flag to bypass this function (used by dump functions)
- *
- * @return mixed the "backquoted" database, table or field name
- *
- * @access public
- */
- public static function backquoteCompat(
- $aName,
- string $compatibility = 'MSSQL',
- $doIt = true
- ) {
- if (is_array($aName)) {
- foreach ($aName as &$data) {
- $data = self::backquoteCompat($data, $compatibility, $doIt);
- }
- return $aName;
- }
- if (! $doIt) {
- if (! (Context::isKeyword($aName) & Token::FLAG_KEYWORD_RESERVED)) {
- return $aName;
- }
- }
- // @todo add more compatibility cases (ORACLE for example)
- switch ($compatibility) {
- case 'MSSQL':
- $quote = '"';
- $escapeChar = '\\';
- break;
- default:
- $quote = '`';
- $escapeChar = '`';
- break;
- }
- // '0' is also empty for php :-(
- if (strlen((string) $aName) > 0 && $aName !== '*') {
- return $quote . str_replace($quote, $escapeChar . $quote, (string) $aName) . $quote;
- }
- return $aName;
- }
- /**
- * Formats $value to byte view
- *
- * @param float|int|string|null $value the value to format
- * @param int $limes the sensitiveness
- * @param int $comma the number of decimals to retain
- *
- * @return array|null the formatted value and its unit
- *
- * @access public
- */
- public static function formatByteDown($value, $limes = 6, $comma = 0): ?array
- {
- if ($value === null) {
- return null;
- }
- if (is_string($value)) {
- $value = (float) $value;
- }
- $byteUnits = [
- /* l10n: shortcuts for Byte */
- __('B'),
- /* l10n: shortcuts for Kilobyte */
- __('KiB'),
- /* l10n: shortcuts for Megabyte */
- __('MiB'),
- /* l10n: shortcuts for Gigabyte */
- __('GiB'),
- /* l10n: shortcuts for Terabyte */
- __('TiB'),
- /* l10n: shortcuts for Petabyte */
- __('PiB'),
- /* l10n: shortcuts for Exabyte */
- __('EiB'),
- ];
- $dh = 10 ** $comma;
- $li = 10 ** $limes;
- $unit = $byteUnits[0];
- for ($d = 6, $ex = 15; $d >= 1; $d--, $ex -= 3) {
- $unitSize = $li * 10 ** $ex;
- if (isset($byteUnits[$d]) && $value >= $unitSize) {
- // use 1024.0 to avoid integer overflow on 64-bit machines
- $value = round($value / (1024 ** $d / $dh)) / $dh;
- $unit = $byteUnits[$d];
- break 1;
- }
- }
- if ($unit != $byteUnits[0]) {
- // if the unit is not bytes (as represented in current language)
- // reformat with max length of 5
- // 4th parameter=true means do not reformat if value < 1
- $returnValue = self::formatNumber($value, 5, $comma, true, false);
- } else {
- // do not reformat, just handle the locale
- $returnValue = self::formatNumber($value, 0);
- }
- return [
- trim($returnValue),
- $unit,
- ];
- }
- /**
- * Formats $value to the given length and appends SI prefixes
- * with a $length of 0 no truncation occurs, number is only formatted
- * to the current locale
- *
- * examples:
- * <code>
- * echo formatNumber(123456789, 6); // 123,457 k
- * echo formatNumber(-123456789, 4, 2); // -123.46 M
- * echo formatNumber(-0.003, 6); // -3 m
- * echo formatNumber(0.003, 3, 3); // 0.003
- * echo formatNumber(0.00003, 3, 2); // 0.03 m
- * echo formatNumber(0, 6); // 0
- * </code>
- *
- * @param float|int|string $value the value to format
- * @param int $digitsLeft number of digits left of the comma
- * @param int $digitsRight number of digits right of the comma
- * @param bool $onlyDown do not reformat numbers below 1
- * @param bool $noTrailingZero removes trailing zeros right of the comma (default: true)
- *
- * @return string the formatted value and its unit
- *
- * @access public
- */
- public static function formatNumber(
- $value,
- $digitsLeft = 3,
- $digitsRight = 0,
- $onlyDown = false,
- $noTrailingZero = true
- ) {
- if ($value == 0) {
- return '0';
- }
- if (is_string($value)) {
- $value = (float) $value;
- }
- $originalValue = $value;
- //number_format is not multibyte safe, str_replace is safe
- if ($digitsLeft === 0) {
- $value = number_format(
- (float) $value,
- $digitsRight,
- /* l10n: Decimal separator */
- __('.'),
- /* l10n: Thousands separator */
- __(',')
- );
- if (($originalValue != 0) && (floatval($value) == 0)) {
- $value = ' <' . (1 / 10 ** $digitsRight);
- }
- return $value;
- }
- // this units needs no translation, ISO
- $units = [
- -8 => 'y',
- -7 => 'z',
- -6 => 'a',
- -5 => 'f',
- -4 => 'p',
- -3 => 'n',
- -2 => 'µ',
- -1 => 'm',
- 0 => ' ',
- 1 => 'k',
- 2 => 'M',
- 3 => 'G',
- 4 => 'T',
- 5 => 'P',
- 6 => 'E',
- 7 => 'Z',
- 8 => 'Y',
- ];
- /* l10n: Decimal separator */
- $decimalSep = __('.');
- /* l10n: Thousands separator */
- $thousandsSep = __(',');
- // check for negative value to retain sign
- if ($value < 0) {
- $sign = '-';
- $value = abs($value);
- } else {
- $sign = '';
- }
- $dh = 10 ** $digitsRight;
- /*
- * This gives us the right SI prefix already,
- * but $digits_left parameter not incorporated
- */
- $d = floor(log10((float) $value) / 3);
- /*
- * Lowering the SI prefix by 1 gives us an additional 3 zeros
- * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits)
- * to use, then lower the SI prefix
- */
- $curDigits = floor(log10($value / 1000 ** $d) + 1);
- if ($digitsLeft > $curDigits) {
- $d -= floor(($digitsLeft - $curDigits) / 3);
- }
- if ($d < 0 && $onlyDown) {
- $d = 0;
- }
- $value = round($value / (1000 ** $d / $dh)) / $dh;
- $unit = $units[$d];
- // number_format is not multibyte safe, str_replace is safe
- $formattedValue = number_format($value, $digitsRight, $decimalSep, $thousandsSep);
- // If we don't want any zeros, remove them now
- if ($noTrailingZero && str_contains($formattedValue, $decimalSep)) {
- $formattedValue = preg_replace('/' . preg_quote($decimalSep, '/') . '?0+$/', '', $formattedValue);
- }
- if ($originalValue != 0 && floatval($value) == 0) {
- return ' <' . number_format(1 / 10 ** $digitsRight, $digitsRight, $decimalSep, $thousandsSep) . ' ' . $unit;
- }
- return $sign . $formattedValue . ' ' . $unit;
- }
- /**
- * Returns the number of bytes when a formatted size is given
- *
- * @param string|int $formattedSize the size expression (for example 8MB)
- *
- * @return int|float The numerical part of the expression (for example 8)
- */
- public static function extractValueFromFormattedSize($formattedSize)
- {
- $returnValue = -1;
- $formattedSize = (string) $formattedSize;
- if (preg_match('/^[0-9]+GB$/', $formattedSize)) {
- $returnValue = (int) mb_substr($formattedSize, 0, -2) * 1024 ** 3;
- } elseif (preg_match('/^[0-9]+MB$/', $formattedSize)) {
- $returnValue = (int) mb_substr($formattedSize, 0, -2) * 1024 ** 2;
- } elseif (preg_match('/^[0-9]+K$/', $formattedSize)) {
- $returnValue = (int) mb_substr($formattedSize, 0, -1) * 1024 ** 1;
- }
- return $returnValue;
- }
- /**
- * Writes localised date
- *
- * @param int $timestamp the current timestamp
- * @param string $format format
- *
- * @return string the formatted date
- *
- * @access public
- */
- public static function localisedDate($timestamp = -1, $format = '')
- {
- $month = [
- /* l10n: Short month name */
- __('Jan'),
- /* l10n: Short month name */
- __('Feb'),
- /* l10n: Short month name */
- __('Mar'),
- /* l10n: Short month name */
- __('Apr'),
- /* l10n: Short month name */
- _pgettext('Short month name', 'May'),
- /* l10n: Short month name */
- __('Jun'),
- /* l10n: Short month name */
- __('Jul'),
- /* l10n: Short month name */
- __('Aug'),
- /* l10n: Short month name */
- __('Sep'),
- /* l10n: Short month name */
- __('Oct'),
- /* l10n: Short month name */
- __('Nov'),
- /* l10n: Short month name */
- __('Dec'),
- ];
- $dayOfWeek = [
- /* l10n: Short week day name for Sunday */
- _pgettext('Short week day name for Sunday', 'Sun'),
- /* l10n: Short week day name for Monday */
- __('Mon'),
- /* l10n: Short week day name for Tuesday */
- __('Tue'),
- /* l10n: Short week day name for Wednesday */
- __('Wed'),
- /* l10n: Short week day name for Thursday */
- __('Thu'),
- /* l10n: Short week day name for Friday */
- __('Fri'),
- /* l10n: Short week day name for Saturday */
- __('Sat'),
- ];
- if ($format == '') {
- /* l10n: See https://www.php.net/manual/en/function.strftime.php */
- $format = __('%B %d, %Y at %I:%M %p');
- }
- if ($timestamp == -1) {
- $timestamp = time();
- }
- $date = (string) preg_replace(
- '@%[aA]@',
- $dayOfWeek[(int) @strftime('%w', (int) $timestamp)],
- $format
- );
- $date = (string) preg_replace(
- '@%[bB]@',
- $month[(int) @strftime('%m', (int) $timestamp) - 1],
- $date
- );
- /* Fill in AM/PM */
- $hours = (int) date('H', (int) $timestamp);
- if ($hours >= 12) {
- $amPm = _pgettext('AM/PM indication in time', 'PM');
- } else {
- $amPm = _pgettext('AM/PM indication in time', 'AM');
- }
- $date = (string) preg_replace('@%[pP]@', $amPm, $date);
- // Can return false on windows for Japanese language
- // See https://github.com/phpmyadmin/phpmyadmin/issues/15830
- $ret = @strftime($date, (int) $timestamp);
- // Some OSes such as Win8.1 Traditional Chinese version did not produce UTF-8
- // output here. See https://github.com/phpmyadmin/phpmyadmin/issues/10598
- if ($ret === false || mb_detect_encoding($ret, 'UTF-8', true) !== 'UTF-8') {
- $ret = date('Y-m-d H:i:s', (int) $timestamp);
- }
- return $ret;
- }
- /**
- * Splits a URL string by parameter
- *
- * @param string $url the URL
- *
- * @return array<int, string> the parameter/value pairs, for example [0] db=sakila
- */
- public static function splitURLQuery($url): array
- {
- // decode encoded url separators
- $separator = Url::getArgSeparator();
- // on most places separator is still hard coded ...
- if ($separator !== '&') {
- // ... so always replace & with $separator
- $url = str_replace([htmlentities('&'), '&'], [$separator, $separator], $url);
- }
- $url = str_replace(htmlentities($separator), $separator, $url);
- // end decode
- $urlParts = parse_url($url);
- if (is_array($urlParts) && isset($urlParts['query']) && strlen($separator) > 0) {
- return explode($separator, $urlParts['query']);
- }
- return [];
- }
- /**
- * Returns a given timespan value in a readable format.
- *
- * @param int $seconds the timespan
- *
- * @return string the formatted value
- */
- public static function timespanFormat($seconds): string
- {
- $days = floor($seconds / 86400);
- if ($days > 0) {
- $seconds -= $days * 86400;
- }
- $hours = floor($seconds / 3600);
- if ($days > 0 || $hours > 0) {
- $seconds -= $hours * 3600;
- }
- $minutes = floor($seconds / 60);
- if ($days > 0 || $hours > 0 || $minutes > 0) {
- $seconds -= $minutes * 60;
- }
- return sprintf(
- __('%s days, %s hours, %s minutes and %s seconds'),
- (string) $days,
- (string) $hours,
- (string) $minutes,
- (string) $seconds
- );
- }
- /**
- * Function added to avoid path disclosures.
- * Called by each script that needs parameters, it displays
- * an error message and, by default, stops the execution.
- *
- * @param string[] $params The names of the parameters needed by the calling
- * script
- * @param bool $request Check parameters in request
- *
- * @access public
- */
- public static function checkParameters($params, $request = false): void
- {
- $reportedScriptName = basename($GLOBALS['PMA_PHP_SELF']);
- $foundError = false;
- $errorMessage = '';
- if ($request) {
- $array = $_REQUEST;
- } else {
- $array = $GLOBALS;
- }
- foreach ($params as $param) {
- if (isset($array[$param])) {
- continue;
- }
- $errorMessage .= $reportedScriptName
- . ': ' . __('Missing parameter:') . ' '
- . $param
- . MySQLDocumentation::showDocumentation('faq', 'faqmissingparameters', true)
- . '[br]';
- $foundError = true;
- }
- if (! $foundError) {
- return;
- }
- Core::fatalError($errorMessage);
- }
- /**
- * Build a condition and with a value
- *
- * @param string|int|float|null $row The row value
- * @param FieldMetadata $meta The field metadata
- * @param int $fieldsCount A number of fields
- * @param string $conditionKey A key used for BINARY fields functions
- * @param string $condition The condition
- *
- * @return array<int,string|null>
- */
- private static function getConditionValue(
- $row,
- FieldMetadata $meta,
- int $fieldsCount,
- string $conditionKey,
- string $condition
- ): array {
- global $dbi;
- if ($row === null) {
- return ['IS NULL', $condition];
- }
- $conditionValue = '';
- $isBinaryString = $meta->isType(FieldMetadata::TYPE_STRING) && $meta->isBinary();
- // 63 is the binary charset, see: https://dev.mysql.com/doc/internals/en/charsets.html
- $isBlobAndIsBinaryCharset = $meta->isType(FieldMetadata::TYPE_BLOB) && $meta->charsetnr === 63;
- // timestamp is numeric on some MySQL 4.1
- // for real we use CONCAT above and it should compare to string
- // See commit: 049fc7fef7548c2ba603196937c6dcaf9ff9bf00
- // See bug: https://sourceforge.net/p/phpmyadmin/bugs/3064/
- if ($meta->isNumeric && ! $meta->isMappedTypeTimestamp && $meta->isNotType(FieldMetadata::TYPE_REAL)) {
- $conditionValue = '= ' . $row;
- } elseif ($isBlobAndIsBinaryCharset || (! empty($row) && $isBinaryString)) {
- // hexify only if this is a true not empty BLOB or a BINARY
- // do not waste memory building a too big condition
- $rowLength = mb_strlen((string) $row);
- if ($rowLength > 0 && $rowLength < 1000) {
- // use a CAST if possible, to avoid problems
- // if the field contains wildcard characters % or _
- $conditionValue = '= CAST(0x' . bin2hex((string) $row) . ' AS BINARY)';
- } elseif ($fieldsCount === 1) {
- // when this blob is the only field present
- // try settling with length comparison
- $condition = ' CHAR_LENGTH(' . $conditionKey . ') ';
- $conditionValue = ' = ' . $rowLength;
- } else {
- // this blob won't be part of the final condition
- $conditionValue = null;
- }
- } elseif ($meta->isMappedTypeGeometry && ! empty($row)) {
- // do not build a too big condition
- if (mb_strlen((string) $row) < 5000) {
- $condition .= '=0x' . bin2hex((string) $row) . ' AND';
- } else {
- $condition = '';
- }
- } elseif ($meta->isMappedTypeBit) {
- $conditionValue = "= b'"
- . self::printableBitValue((int) $row, (int) $meta->length) . "'";
- } else {
- $conditionValue = '= \''
- . $dbi->escapeString($row) . '\'';
- }
- return [$conditionValue, $condition];
- }
- /**
- * Function to generate unique condition for specified row.
- *
- * @param resource|int $handle current query result
- * @param int $fieldsCount number of fields
- * @param FieldMetadata[] $fieldsMeta meta information about fields
- * @param array $row current row
- * @param bool $forceUnique generate condition only on pk or unique
- * @param string|bool $restrictToTable restrict the unique condition to this table or false if none
- * @param Expression[] $expressions An array of Expression instances.
- *
- * @return array the calculated condition and whether condition is unique
- */
- public static function getUniqueCondition(
- $handle,
- $fieldsCount,
- array $fieldsMeta,
- array $row,
- $forceUnique = false,
- $restrictToTable = false,
- array $expressions = []
- ): array {
- global $dbi;
- $primaryKey = '';
- $uniqueKey = '';
- $nonPrimaryCondition = '';
- $preferredCondition = '';
- $primaryKeyArray = [];
- $uniqueKeyArray = [];
- $nonPrimaryConditionArray = [];
- $conditionArray = [];
- for ($i = 0; $i < $fieldsCount; ++$i) {
- $meta = $fieldsMeta[$i];
- // do not use a column alias in a condition
- if (! isset($meta->orgname) || strlen($meta->orgname) === 0) {
- $meta->orgname = $meta->name;
- foreach ($expressions as $expression) {
- if (empty($expression->alias) || empty($expression->column)) {
- continue;
- }
- if (strcasecmp($meta->name, $expression->alias) == 0) {
- $meta->orgname = $expression->column;
- break;
- }
- }
- }
- // Do not use a table alias in a condition.
- // Test case is:
- // select * from galerie x WHERE
- //(select count(*) from galerie y where y.datum=x.datum)>1
- //
- // But orgtable is present only with mysqli extension so the
- // fix is only for mysqli.
- // Also, do not use the original table name if we are dealing with
- // a view because this view might be updatable.
- // (The isView() verification should not be costly in most cases
- // because there is some caching in the function).
- if (
- isset($meta->orgtable)
- && ($meta->table != $meta->orgtable)
- && ! $dbi->getTable($GLOBALS['db'], $meta->table)->isView()
- ) {
- $meta->table = $meta->orgtable;
- }
- // If this field is not from the table which the unique clause needs
- // to be restricted to.
- if ($restrictToTable && $restrictToTable != $meta->table) {
- continue;
- }
- // to fix the bug where float fields (primary or not)
- // can't be matched because of the imprecision of
- // floating comparison, use CONCAT
- // (also, the syntax "CONCAT(field) IS NULL"
- // that we need on the next "if" will work)
- if ($meta->isType(FieldMetadata::TYPE_REAL)) {
- $conKey = 'CONCAT(' . self::backquote($meta->table) . '.'
- . self::backquote($meta->orgname) . ')';
- } else {
- $conKey = self::backquote($meta->table) . '.'
- . self::backquote($meta->orgname);
- }
- $condition = ' ' . $conKey . ' ';
- [$conVal, $condition] = self::getConditionValue($row[$i] ?? null, $meta, $fieldsCount, $conKey, $condition);
- if ($conVal === null) {
- continue;
- }
- $condition .= $conVal . ' AND';
- if ($meta->isPrimaryKey()) {
- $primaryKey .= $condition;
- $primaryKeyArray[$conKey] = $conVal;
- } elseif ($meta->isUniqueKey()) {
- $uniqueKey .= $condition;
- $uniqueKeyArray[$conKey] = $conVal;
- }
- $nonPrimaryCondition .= $condition;
- $nonPrimaryConditionArray[$conKey] = $conVal;
- }
- // Correction University of Virginia 19991216:
- // prefer primary or unique keys for condition,
- // but use conjunction of all values if no primary key
- $clauseIsUnique = true;
- if ($primaryKey) {
- $preferredCondition = $primaryKey;
- $conditionArray = $primaryKeyArray;
- } elseif ($uniqueKey) {
- $preferredCondition = $uniqueKey;
- $conditionArray = $uniqueKeyArray;
- } elseif (! $forceUnique) {
- $preferredCondition = $nonPrimaryCondition;
- $conditionArray = $nonPrimaryConditionArray;
- $clauseIsUnique = false;
- }
- $whereClause = trim((string) preg_replace('|\s?AND$|', '', $preferredCondition));
- return [
- $whereClause,
- $clauseIsUnique,
- $conditionArray,
- ];
- }
- /**
- * Generate the charset query part
- *
- * @param string $collation Collation
- * @param bool $override (optional) force 'CHARACTER SET' keyword
- */
- public static function getCharsetQueryPart(string $collation, bool $override = false): string
- {
- [$charset] = explode('_', $collation);
- $keyword = ' CHARSET=';
- if ($override) {
- $keyword = ' CHARACTER SET ';
- }
- return $keyword . $charset
- . ($charset == $collation ? '' : ' COLLATE ' . $collation);
- }
- /**
- * Generate a pagination selector for browsing resultsets
- *
- * @param string $name The name for the request parameter
- * @param int $rows Number of rows in the pagination set
- * @param int $pageNow current page number
- * @param int $nbTotalPage number of total pages
- * @param int $showAll If the number of pages is lower than this
- * variable, no pages will be omitted in pagination
- * @param int $sliceStart How many rows at the beginning should always
- * be shown?
- * @param int $sliceEnd How many rows at the end should always be shown?
- * @param int $percent Percentage of calculation page offsets to hop to a
- * next page
- * @param int $range Near the current page, how many pages should
- * be considered "nearby" and displayed as well?
- * @param string $prompt The prompt to display (sometimes empty)
- *
- * @access public
- */
- public static function pageselector(
- $name,
- $rows,
- $pageNow = 1,
- $nbTotalPage = 1,
- $showAll = 200,
- $sliceStart = 5,
- $sliceEnd = 5,
- $percent = 20,
- $range = 10,
- $prompt = ''
- ): string {
- $increment = floor($nbTotalPage / $percent);
- $pageNowMinusRange = $pageNow - $range;
- $pageNowPlusRange = $pageNow + $range;
- $gotoPage = $prompt . ' <select class="pageselector ajax"';
- $gotoPage .= ' name="' . $name . '" >';
- if ($nbTotalPage < $showAll) {
- $pages = range(1, $nbTotalPage);
- } else {
- $pages = [];
- // Always show first X pages
- for ($i = 1; $i <= $sliceStart; $i++) {
- $pages[] = $i;
- }
- // Always show last X pages
- for ($i = $nbTotalPage - $sliceEnd; $i <= $nbTotalPage; $i++) {
- $pages[] = $i;
- }
- // Based on the number of results we add the specified
- // $percent percentage to each page number,
- // so that we have a representing page number every now and then to
- // immediately jump to specific pages.
- // As soon as we get near our currently chosen page ($pageNow -
- // $range), every page number will be shown.
- $i = $sliceStart;
- $x = $nbTotalPage - $sliceEnd;
- $metBoundary = false;
- while ($i <= $x) {
- if ($i >= $pageNowMinusRange && $i <= $pageNowPlusRange) {
- // If our pageselector comes near the current page, we use 1
- // counter increments
- $i++;
- $metBoundary = true;
- } else {
- // We add the percentage increment to our current page to
- // hop to the next one in range
- $i += $increment;
- // Make sure that we do not cross our boundaries.
- if ($i > $pageNowMinusRange && ! $metBoundary) {
- $i = $pageNowMinusRange;
- }
- }
- if ($i <= 0 || $i > $x) {
- continue;
- }
- $pages[] = $i;
- }
- /*
- Add page numbers with "geometrically increasing" distances.
- This helps me a lot when navigating through giant tables.
- Test case: table with 2.28 million sets, 76190 pages. Page of interest
- is between 72376 and 76190.
- Selecting page 72376.
- Now, old version enumerated only +/- 10 pages around 72376 and the
- percentage increment produced steps of about 3000.
- The following code adds page numbers +/- 2,4,8,16,32,64,128,256 etc.
- around the current page.
- */
- $i = $pageNow;
- $dist = 1;
- while ($i < $x) {
- $dist = 2 * $dist;
- $i = $pageNow + $dist;
- if ($i <= 0 || $i > $x) {
- continue;
- }
- $pages[] = $i;
- }
- $i = $pageNow;
- $dist = 1;
- while ($i > 0) {
- $dist = 2 * $dist;
- $i = $pageNow - $dist;
- if ($i <= 0 || $i > $x) {
- continue;
- }
- $pages[] = $i;
- }
- // Since because of ellipsing of the current page some numbers may be
- // double, we unify our array:
- sort($pages);
- $pages = array_unique($pages);
- }
- if ($pageNow > $nbTotalPage) {
- $pages[] = $pageNow;
- }
- foreach ($pages as $i) {
- if ($i == $pageNow) {
- $selected = 'selected="selected" style="font-weight: bold"';
- } else {
- $selected = '';
- }
- $gotoPage .= ' <option ' . $selected
- . ' value="' . (($i - 1) * $rows) . '">' . $i . '</option>' . "\n";
- }
- $gotoPage .= ' </select>';
- return $gotoPage;
- }
- /**
- * Calculate page number through position
- *
- * @param int $pos position of first item
- * @param int $maxCount number of items per page
- *
- * @return int $page_num
- *
- * @access public
- */
- public static function getPageFromPosition($pos, $maxCount)
- {
- return (int) floor($pos / $maxCount) + 1;
- }
- /**
- * replaces %u in given path with current user name
- *
- * example:
- * <code>
- * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/'
- *
- * </code>
- *
- * @param string $dir with wildcard for user
- *
- * @return string per user directory
- */
- public static function userDir(string $dir): string
- {
- // add trailing slash
- if (mb_substr($dir, -1) !== '/') {
- $dir .= '/';
- }
- return str_replace('%u', Core::securePath($GLOBALS['cfg']['Server']['user']), $dir);
- }
- /**
- * Clears cache content which needs to be refreshed on user change.
- */
- public static function clearUserCache(): void
- {
- SessionCache::remove('is_superuser');
- SessionCache::remove('is_createuser');
- SessionCache::remove('is_grantuser');
- }
- /**
- * Converts a bit value to printable format;
- * in MySQL a BIT field can be from 1 to 64 bits so we need this
- * function because in PHP, decbin() supports only 32 bits
- * on 32-bit servers
- *
- * @param int $value coming from a BIT field
- * @param int $length length
- *
- * @return string the printable value
- */
- public static function printableBitValue(int $value, int $length): string
- {
- // if running on a 64-bit server or the length is safe for decbin()
- if (PHP_INT_SIZE == 8 || $length < 33) {
- $printable = decbin($value);
- } else {
- // FIXME: does not work for the leftmost bit of a 64-bit value
- $i = 0;
- $printable = '';
- while ($value >= 2 ** $i) {
- ++$i;
- }
- if ($i != 0) {
- --$i;
- }
- while ($i >= 0) {
- if ($value - 2 ** $i < 0) {
- $printable = '0' . $printable;
- } else {
- $printable = '1' . $printable;
- $value -= 2 ** $i;
- }
- --$i;
- }
- $printable = strrev($printable);
- }
- $printable = str_pad($printable, $length, '0', STR_PAD_LEFT);
- return $printable;
- }
- /**
- * Converts a BIT type default value
- * for example, b'010' becomes 010
- *
- * @param string|null $bitDefaultValue value
- *
- * @return string the converted value
- */
- public static function convertBitDefaultValue(?string $bitDefaultValue): string
- {
- return (string) preg_replace(
- "/^b'(\d*)'?$/",
- '$1',
- htmlspecialchars_decode((string) $bitDefaultValue, ENT_QUOTES),
- 1
- );
- }
- /**
- * Extracts the various parts from a column spec
- *
- * @param string $columnSpecification Column specification
- *
- * @return array associative array containing type, spec_in_brackets
- * and possibly enum_set_values (another array)
- */
- public static function extractColumnSpec($columnSpecification)
- {
- $firstBracketPos = mb_strpos($columnSpecification, '(');
- if ($firstBracketPos) {
- $specInBrackets = rtrim(
- mb_substr(
- $columnSpecification,
- $firstBracketPos + 1,
- mb_strrpos($columnSpecification, ')') - $firstBracketPos - 1
- )
- );
- // convert to lowercase just to be sure
- $type = mb_strtolower(
- rtrim(mb_substr($columnSpecification, 0, $firstBracketPos))
- );
- } else {
- // Split trailing attributes such as unsigned,
- // binary, zerofill and get data type name
- $typeParts = explode(' ', $columnSpecification);
- $type = mb_strtolower($typeParts[0]);
- $specInBrackets = '';
- }
- if ($type === 'enum' || $type === 'set') {
- // Define our working vars
- $enumSetValues = self::parseEnumSetValues($columnSpecification, false);
- $printType = $type
- . '(' . str_replace("','", "', '", $specInBrackets) . ')';
- $binary = false;
- $unsigned = false;
- $zerofill = false;
- } else {
- $enumSetValues = [];
- /* Create printable type name */
- $printType = mb_strtolower($columnSpecification);
- // Strip the "BINARY" attribute, except if we find "BINARY(" because
- // this would be a BINARY or VARBINARY column type;
- // by the way, a BLOB should not show the BINARY attribute
- // because this is not accepted in MySQL syntax.
- if (str_contains($printType, 'binary') && ! preg_match('@binary[\(]@', $printType)) {
- $printType = str_replace('binary', '', $printType);
- $binary = true;
- } else {
- $binary = false;
- }
- $printType = (string) preg_replace('@zerofill@', '', $printType, -1, $zerofillCount);
- $zerofill = ($zerofillCount > 0);
- $printType = (string) preg_replace('@unsigned@', '', $printType, -1, $unsignedCount);
- $unsigned = ($unsignedCount > 0);
- $printType = trim($printType);
- }
- $attribute = ' ';
- if ($binary) {
- $attribute = 'BINARY';
- }
- if ($unsigned) {
- $attribute = 'UNSIGNED';
- }
- if ($zerofill) {
- $attribute = 'UNSIGNED ZEROFILL';
- }
- $canContainCollation = false;
- if (! $binary && preg_match('@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@', $type)) {
- $canContainCollation = true;
- }
- // for the case ENUM('–','“')
- $displayedType = htmlspecialchars($printType, ENT_COMPAT);
- if (mb_strlen($printType) > $GLOBALS['cfg']['LimitChars']) {
- $displayedType = '<abbr title="' . htmlspecialchars($printType) . '">';
- $displayedType .= htmlspecialchars(
- mb_substr(
- $printType,
- 0,
- (int) $GLOBALS['cfg']['LimitChars']
- ) . '...',
- ENT_COMPAT
- );
- $displayedType .= '</abbr>';
- }
- return [
- 'type' => $type,
- 'spec_in_brackets' => $specInBrackets,
- 'enum_set_values' => $enumSetValues,
- 'print_type' => $printType,
- 'binary' => $binary,
- 'unsigned' => $unsigned,
- 'zerofill' => $zerofill,
- 'attribute' => $attribute,
- 'can_contain_collation' => $canContainCollation,
- 'displayed_type' => $displayedType,
- ];
- }
- /**
- * If the string starts with a \r\n pair (0x0d0a) add an extra \n
- *
- * @param string $string string
- *
- * @return string with the chars replaced
- */
- public static function duplicateFirstNewline(string $string): string
- {
- $firstOccurrence = mb_strpos($string, "\r\n");
- if ($firstOccurrence === 0) {
- $string = "\n" . $string;
- }
- return $string;
- }
- /**
- * Get the action word corresponding to a script name
- * in order to display it as a title in navigation panel
- *
- * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'],
- * $cfg['NavigationTreeDefaultTabTable2'],
- * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase']
- *
- * @return string|bool Title for the $cfg value
- */
- public static function getTitleForTarget($target)
- {
- $mapping = [
- 'structure' => __('Structure'),
- 'sql' => __('SQL'),
- 'search' => __('Search'),
- 'insert' => __('Insert'),
- 'browse' => __('Browse'),
- 'operations' => __('Operations'),
- ];
- return $mapping[$target] ?? false;
- }
- /**
- * Get the script name corresponding to a plain English config word
- * in order to append in links on navigation and main panel
- *
- * @param string $target a valid value for
- * $cfg['NavigationTreeDefaultTabTable'],
- * $cfg['NavigationTreeDefaultTabTable2'],
- * $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or
- * $cfg['DefaultTabServer']
- * @param string $location one out of 'server', 'table', 'database'
- *
- * @return string script name corresponding to the config word
- */
- public static function getScriptNameForO…
Large files files are truncated, but you can click here to view the full file