phpmyadmin/libraries/Charsets.php
Michal Čihař 031d379bdc Rewite collation naming code
Rewritten to use level based parser instead of hardcoding ordering. This
is necessary as MySQL charset naming got even more complex with MySQL
8.0.

Fixes #13216

Signed-off-by: Michal Čihař <michal@cihar.com>
2017-04-28 16:04:27 +02:00

597 lines
21 KiB
PHP

<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* MySQL charset metadata and manipulations
*
* @package PhpMyAdmin
*/
namespace PMA\libraries;
use PMA\libraries\Util;
/**
* Class used to manage MySQL charsets
*
* @package PhpMyAdmin
*/
class Charsets
{
/**
* MySQL charsets map
*
* @var array
*/
public static $mysql_charset_map = array(
'big5' => 'big5',
'cp-866' => 'cp866',
'euc-jp' => 'ujis',
'euc-kr' => 'euckr',
'gb2312' => 'gb2312',
'gbk' => 'gbk',
'iso-8859-1' => 'latin1',
'iso-8859-2' => 'latin2',
'iso-8859-7' => 'greek',
'iso-8859-8' => 'hebrew',
'iso-8859-8-i' => 'hebrew',
'iso-8859-9' => 'latin5',
'iso-8859-13' => 'latin7',
'iso-8859-15' => 'latin1',
'koi8-r' => 'koi8r',
'shift_jis' => 'sjis',
'tis-620' => 'tis620',
'utf-8' => 'utf8',
'windows-1250' => 'cp1250',
'windows-1251' => 'cp1251',
'windows-1252' => 'latin1',
'windows-1256' => 'cp1256',
'windows-1257' => 'cp1257',
);
private static $_charsets = array();
private static $_charsets_descriptions = array();
private static $_collations = array();
private static $_default_collations = array();
/**
* Loads charset data from the MySQL server.
*
* @return void
*/
public static function loadCharsets()
{
/* Data already loaded */
if (count(self::$_charsets) > 0) {
return;
}
$sql = 'SELECT * FROM information_schema.CHARACTER_SETS';
$res = $GLOBALS['dbi']->query($sql);
self::$_charsets = array();
while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
$name = $row['CHARACTER_SET_NAME'];
self::$_charsets[] = $name;
self::$_charsets_descriptions[$name] = $row['DESCRIPTION'];
}
$GLOBALS['dbi']->freeResult($res);
sort(self::$_charsets, SORT_STRING);
}
/**
* Loads collation data from the MySQL server.
*
* @return void
*/
public static function loadCollations()
{
/* Data already loaded */
if (count(self::$_collations) > 0) {
return;
}
$sql = 'SELECT * FROM information_schema.COLLATIONS';
$res = $GLOBALS['dbi']->query($sql);
while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
$char_set_name = $row['CHARACTER_SET_NAME'];
$name = $row['COLLATION_NAME'];
self::$_collations[$char_set_name][] = $name;
if ($row['IS_DEFAULT'] == 'Yes' || $row['IS_DEFAULT'] == '1') {
self::$_default_collations[$char_set_name] = $name;
}
}
$GLOBALS['dbi']->freeResult($res);
foreach (self::$_collations as $key => $value) {
sort(self::$_collations[$key], SORT_STRING);
}
}
public static function getMySQLCharsets()
{
self::loadCharsets();
return self::$_charsets;
}
public static function getMySQLCharsetsDescriptions()
{
self::loadCharsets();
return self::$_charsets_descriptions;
}
public static function getMySQLCollations()
{
self::loadCollations();
return self::$_collations;
}
public static function getMySQLCollationsDefault()
{
self::loadCollations();
return self::$_default_collations;
}
/**
* Generate charset dropdown box
*
* @param string $name Element name
* @param string $id Element id
* @param null|string $default Default value
* @param bool $label Label
* @param bool $submitOnChange Submit on change
*
* @return string
*/
public static function getCharsetDropdownBox(
$name = null, $id = null, $default = null, $label = true,
$submitOnChange = false
) {
self::loadCharsets();
if (empty($name)) {
$name = 'character_set';
}
$return_str = '<select lang="en" dir="ltr" name="'
. htmlspecialchars($name) . '"'
. (empty($id) ? '' : ' id="' . htmlspecialchars($id) . '"')
. ($submitOnChange ? ' class="autosubmit"' : '') . '>' . "\n";
if ($label) {
$return_str .= '<option value="">'
. __('Charset')
. '</option>' . "\n";
}
$return_str .= '<option value=""></option>' . "\n";
foreach (self::$_charsets as $current_charset) {
$current_cs_descr
= empty(self::$_charsets_descriptions[$current_charset])
? $current_charset
: self::$_charsets_descriptions[$current_charset];
$return_str .= '<option value="' . $current_charset
. '" title="' . $current_cs_descr . '"'
. ($default == $current_charset ? ' selected="selected"' : '') . '>'
. $current_charset . '</option>' . "\n";
}
$return_str .= '</select>' . "\n";
return $return_str;
}
/**
* Generate collation dropdown box
*
* @param string $name Element name
* @param string $id Element id
* @param null|string $default Default value
* @param bool $label Label
* @param bool $submitOnChange Submit on change
*
* @return string
*/
public static function getCollationDropdownBox(
$name = null, $id = null, $default = null, $label = true,
$submitOnChange = false
) {
self::loadCharsets();
self::loadCollations();
if (empty($name)) {
$name = 'collation';
}
$return_str = '<select lang="en" dir="ltr" name="'
. htmlspecialchars($name) . '"'
. (empty($id) ? '' : ' id="' . htmlspecialchars($id) . '"')
. ($submitOnChange ? ' class="autosubmit"' : '') . '>' . "\n";
if ($label) {
$return_str .= '<option value="">'
. __('Collation')
. '</option>' . "\n";
}
$return_str .= '<option value=""></option>' . "\n";
foreach (self::$_charsets as $current_charset) {
$current_cs_descr
= empty(self::$_charsets_descriptions[$current_charset])
? $current_charset
: self::$_charsets_descriptions[$current_charset];
$return_str .= '<optgroup label="' . $current_charset
. '" title="' . $current_cs_descr . '">' . "\n";
foreach (self::$_collations[$current_charset] as $current_collation) {
$return_str .= '<option value="' . $current_collation
. '" title="' . self::getCollationDescr($current_collation) . '"'
. ($default == $current_collation ? ' selected="selected"' : '')
. '>'
. $current_collation . '</option>' . "\n";
}
$return_str .= '</optgroup>' . "\n";
}
$return_str .= '</select>' . "\n";
return $return_str;
}
/**
* returns description for given collation
*
* @param string $collation MySQL collation string
*
* @return string collation description
*/
public static function getCollationDescr($collation)
{
$parts = explode('_', $collation);
$name = __('Unknonwn');
$variant = null;
$suffixes = array();
$unicode = false;
$unknown = false;
$level = 0;
foreach ($parts as $part) {
if ($level == 0) {
/* Next will be language */
$level = 1;
/* First should be charset */
switch ($part) {
case 'binary':
$name = _pgettext('Collation', 'Binary');
break;
// Unicode charsets
case 'utf8mb4':
$variant = 'UCA 4.0.0';
// Fall through to other unicode
case 'ucs2':
case 'utf8':
case 'utf16':
case 'utf16le':
case 'utf16be':
case 'utf32':
$name = _pgettext('Collation', 'Unicode');
$unicode = true;
break;
// West European charsets
case 'ascii':
case 'cp850':
case 'dec8':
case 'hp8':
case 'latin1':
case 'macroman':
$name = _pgettext('Collation', 'West European');
break;
// Central European charsets
case 'cp1250':
case 'cp852':
case 'latin2':
case 'macce':
$name = _pgettext('Collation', 'Central European');
break;
// Russian charsets
case 'cp866':
case 'koi8r':
$name = _pgettext('Collation', 'Russian');
break;
// Simplified Chinese charsets
case 'gb2312':
case 'gbk':
$name = _pgettext('Collation', 'Simplified Chinese');
break;
// Japanese charsets
case 'sjis':
case 'ujis':
case 'cp932':
case 'eucjpms':
$name = _pgettext('Collation', 'Japanese');
break;
// Baltic charsets
case 'cp1257':
case 'latin7':
$name = _pgettext('Collation', 'Baltic');
break;
// Other
case 'armscii8':
case 'armscii':
$name = _pgettext('Collation', 'Armenian');
break;
case 'big5':
$name = _pgettext('Collation', 'Traditional Chinese');
break;
case 'cp1251':
$name = _pgettext('Collation', 'Cyrillic');
break;
case 'cp1256':
$name = _pgettext('Collation', 'Arabic');
break;
case 'euckr':
$name = _pgettext('Collation', 'Korean');
break;
case 'hebrew':
$name = _pgettext('Collation', 'Hebrew');
break;
case 'geostd8':
$name = _pgettext('Collation', 'Georgian');
break;
case 'greek':
$name = _pgettext('Collation', 'Greek');
break;
case 'keybcs2':
$name = _pgettext('Collation', 'Czech-Slovak');
break;
case 'koi8u':
$name = _pgettext('Collation', 'Ukrainian');
break;
case 'latin5':
$name = _pgettext('Collation', 'Turkish');
break;
case 'swe7':
$name = _pgettext('Collation', 'Swedish');
break;
case 'tis620':
$name = _pgettext('Collation', 'Thai');
break;
default:
$name = _pgettext('Collation', 'Unknown');
$unknown = true;
break;
}
continue;
}
if ($level == 1) {
/* Next will be variant unless changed later */
$level = 4;
/* Locale name or code */
$found = true;
switch ($part) {
case 'general':
break;
case 'bulgarian':
case 'bg':
$name = _pgettext('Collation', 'Bulgarian');
break;
case 'chinese':
case 'cn':
if ($unicode) {
$name = _pgettext('Collation', 'Chinese');
}
break;
case 'croatian':
case 'hr':
$name = _pgettext('Collation', 'Croatian');
break;
case 'czech':
case 'cs':
$name = _pgettext('Collation', 'Czech');
break;
case 'danish':
case 'da':
$name = _pgettext('Collation', 'Danish');
break;
case 'english':
case 'en':
$name = _pgettext('Collation', 'English');
break;
case 'esperanto':
case 'eo':
$name = _pgettext('Collation', 'Esperanto');
break;
case 'estonian':
case 'et':
$name = _pgettext('Collation', 'Estonian');
break;
case 'german1':
$name = _pgettext('Collation', 'German (dictionary order)');
break;
case 'german2':
$name = _pgettext('Collation', 'German (phone book order)');
break;
case 'german':
case 'de':
/* Name is set later */
$level = 2;
break;
case 'hungarian':
case 'hu':
$name = _pgettext('Collation', 'Hungarian');
break;
case 'icelandic':
case 'is':
$name = _pgettext('Collation', 'Icelandic');
break;
case 'japanese':
case 'ja':
$name = _pgettext('Collation', 'Japanese');
break;
case 'la':
$name = _pgettext('Collation', 'Classical Latin');
break;
case 'latvian':
case 'lv':
$name = _pgettext('Collation', 'Latvian');
break;
case 'lithuanian':
case 'lt':
$name = _pgettext('Collation', 'Lithuanian');
break;
case 'korean':
case 'ko':
$name = _pgettext('Collation', 'Korean');
break;
case 'myanmar':
case 'my':
$name = _pgettext('Collation', 'Burmese');
break;
case 'persian':
$name = _pgettext('Collation', 'Persian');
break;
case 'polish':
case 'pl':
$name = _pgettext('Collation', 'Polish');
break;
case 'roman':
$name = _pgettext('Collation', 'West European');
break;
case 'romanian':
case 'ro':
$name = _pgettext('Collation', 'Romanian');
break;
case 'si':
case 'sinhala':
$name = _pgettext('Collation', 'Sinhalese');
break;
case 'slovak':
case 'sl':
$name = _pgettext('Collation', 'Slovak');
break;
case 'slovenian':
case 'sl':
$name = _pgettext('Collation', 'Slovenian');
break;
case 'spanish':
$name = _pgettext('Collation', 'Spanish (modern)');
break;
case 'es':
/* Name is set later */
$level = 3;
break;
case 'spanish2':
$name = _pgettext('Collation', 'Spanish (traditional)');
break;
case 'swedish':
$name = _pgettext('Collation', 'Swedish');
break;
case 'thai':
case 'th':
$name = _pgettext('Collation', 'Thai');
break;
case 'turkish':
case 'tr':
$name = _pgettext('Collation', 'Turkish');
break;
case 'ukrainian':
case 'uk':
$name = _pgettext('Collation', 'Ukrainian');
break;
case 'vietnamese':
case 'vi':
$name = _pgettext('Collation', 'Vietnamese');
break;
case 'unicode':
if ($unknown) {
$name = _pgettext('Collation', 'Unicode');
}
break;
default:
$found = false;
}
if ($found) {
continue;
}
// Not parsed token, fall to next level
}
if ($level == 2) {
/* Next will be variant */
$level = 4;
/* Germal variant */
if ($part == 'pb') {
$name = _pgettext('Collation', 'German (phone book order)');
continue;
}
$name = _pgettext('Collation', 'German (dictionary order)');
// Not parsed token, fall to next level
}
if ($level == 3) {
/* Next will be variant */
$level = 4;
/* Spanish variant */
if ($part == 'trad') {
$name = _pgettext('Collation', 'Spanish (traditional)');
continue;
}
$name = _pgettext('Collation', 'Spanish (modern)');
// Not parsed token, fall to next level
}
if ($level == 4) {
/* Next will be suffix */
$level = 5;
/* Variant */
$found = true;
switch ($part) {
case '0900':
$variant = 'UCA 9.0.0';
break;
case '520':
$variant = 'UCA 5.2.0';
break;
case 'mysql561':
$variant = 'MySQL 5.6.1';
break;
case 'mysql500':
$variant = 'MySQL 5.0.0';
break;
default:
$found = false;
}
if ($found) {
continue;
}
// Not parsed token, fall to next level
}
if ($level == 5) {
/* Suffixes */
switch ($part) {
case 'ci':
$suffixes[] = _pgettext('Collation variant', 'case-insensitive');
break;
case 'cs':
$suffixes[] = _pgettext('Collation variant', 'case-sensitive');
break;
case 'ai':
$suffixes[] = _pgettext('Collation variant', 'accent-insensitive');
break;
case 'as':
$suffixes[] = _pgettext('Collation variant', 'accent-sensitive');
break;
case 'w2':
$suffixes[] = _pgettext('Collation variant', 'weight=2');
break;
case 'l2':
$suffixes[] = _pgettext('Collation variant', 'level=2');
break;
case 'bin':
$suffixes[] = _pgettext('Collation variant', 'binary');
break;
}
}
}
$result = $name;
if (! is_null($variant)) {
$result .= ' (' . $variant . ')';
}
if (count($suffixes) > 0) {
$result .= ', ' . implode(', ', $suffixes);
}
return $result;
}
}