Collation.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. <?php
  2. /**
  3. * Value object class for a collation
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Charsets;
  7. use function count;
  8. use function explode;
  9. use function implode;
  10. /**
  11. * Value object class for a collation
  12. */
  13. final class Collation
  14. {
  15. /**
  16. * The collation name
  17. *
  18. * @var string
  19. */
  20. private $name;
  21. /**
  22. * A description of the collation
  23. *
  24. * @var string
  25. */
  26. private $description;
  27. /**
  28. * The name of the character set with which the collation is associated
  29. *
  30. * @var string
  31. */
  32. private $charset;
  33. /**
  34. * The collation ID
  35. *
  36. * @var int
  37. */
  38. private $id;
  39. /**
  40. * Whether the collation is the default for its character set
  41. *
  42. * @var bool
  43. */
  44. private $isDefault;
  45. /**
  46. * Whether the character set is compiled into the server
  47. *
  48. * @var bool
  49. */
  50. private $isCompiled;
  51. /**
  52. * Used for determining the memory used to sort strings in this collation
  53. *
  54. * @var int
  55. */
  56. private $sortLength;
  57. /**
  58. * The collation pad attribute
  59. *
  60. * @var string
  61. */
  62. private $padAttribute;
  63. /**
  64. * @param string $name Collation name
  65. * @param string $charset Related charset
  66. * @param int $id Collation ID
  67. * @param bool $isDefault Whether is the default
  68. * @param bool $isCompiled Whether the charset is compiled
  69. * @param int $sortLength Sort length
  70. * @param string $padAttribute Pad attribute
  71. */
  72. private function __construct(
  73. string $name,
  74. string $charset,
  75. int $id,
  76. bool $isDefault,
  77. bool $isCompiled,
  78. int $sortLength,
  79. string $padAttribute
  80. ) {
  81. $this->name = $name;
  82. $this->charset = $charset;
  83. $this->id = $id;
  84. $this->isDefault = $isDefault;
  85. $this->isCompiled = $isCompiled;
  86. $this->sortLength = $sortLength;
  87. $this->padAttribute = $padAttribute;
  88. $this->description = $this->buildDescription();
  89. }
  90. /**
  91. * @param array $state State obtained from the database server
  92. */
  93. public static function fromServer(array $state): self
  94. {
  95. return new self(
  96. $state['Collation'] ?? '',
  97. $state['Charset'] ?? '',
  98. (int) ($state['Id'] ?? 0),
  99. isset($state['Default']) && ($state['Default'] === 'Yes' || $state['Default'] === '1'),
  100. isset($state['Compiled']) && ($state['Compiled'] === 'Yes' || $state['Compiled'] === '1'),
  101. (int) ($state['Sortlen'] ?? 0),
  102. $state['Pad_attribute'] ?? ''
  103. );
  104. }
  105. public function getName(): string
  106. {
  107. return $this->name;
  108. }
  109. public function getDescription(): string
  110. {
  111. return $this->description;
  112. }
  113. public function getCharset(): string
  114. {
  115. return $this->charset;
  116. }
  117. public function getId(): int
  118. {
  119. return $this->id;
  120. }
  121. public function isDefault(): bool
  122. {
  123. return $this->isDefault;
  124. }
  125. public function isCompiled(): bool
  126. {
  127. return $this->isCompiled;
  128. }
  129. public function getSortLength(): int
  130. {
  131. return $this->sortLength;
  132. }
  133. public function getPadAttribute(): string
  134. {
  135. return $this->padAttribute;
  136. }
  137. /**
  138. * Returns description for given collation
  139. *
  140. * @return string collation description
  141. */
  142. private function buildDescription(): string
  143. {
  144. $parts = explode('_', $this->getName());
  145. $name = __('Unknown');
  146. $variant = null;
  147. $suffixes = [];
  148. $unicode = false;
  149. $unknown = false;
  150. $level = 0;
  151. foreach ($parts as $part) {
  152. if ($level === 0) {
  153. /* Next will be language */
  154. $level = 1;
  155. /* First should be charset */
  156. $this->getNameForLevel0(
  157. $unicode, // By reference
  158. $unknown, // Same
  159. $part,
  160. $name, // By reference
  161. $variant// Same
  162. );
  163. continue;
  164. }
  165. if ($level === 1) {
  166. /* Next will be variant unless changed later */
  167. $level = 4;
  168. /* Locale name or code */
  169. $found = true;
  170. $this->getNameForLevel1(
  171. $unicode,
  172. $unknown,
  173. $part,
  174. $name, // By reference, will be changed
  175. $level, // Also
  176. $found// Same
  177. );
  178. if ($found) {
  179. continue;
  180. }
  181. // Not parsed token, fall to next level
  182. }
  183. if ($level === 2) {
  184. /* Next will be variant */
  185. $level = 4;
  186. /* Germal variant */
  187. if ($part === 'pb') {
  188. $name = _pgettext('Collation', 'German (phone book order)');
  189. continue;
  190. }
  191. $name = _pgettext('Collation', 'German (dictionary order)');
  192. // Not parsed token, fall to next level
  193. }
  194. if ($level === 3) {
  195. /* Next will be variant */
  196. $level = 4;
  197. /* Spanish variant */
  198. if ($part === 'trad') {
  199. $name = _pgettext('Collation', 'Spanish (traditional)');
  200. continue;
  201. }
  202. $name = _pgettext('Collation', 'Spanish (modern)');
  203. // Not parsed token, fall to next level
  204. }
  205. if ($level === 4) {
  206. /* Next will be suffix */
  207. $level = 5;
  208. /* Variant */
  209. $found = true;
  210. $variantFound = $this->getVariant($part);
  211. if ($variantFound === null) {
  212. $found = false;
  213. } else {
  214. $variant = $variantFound;
  215. }
  216. if ($found) {
  217. continue;
  218. }
  219. // Not parsed token, fall to next level
  220. }
  221. if ($level < 5) {
  222. continue;
  223. }
  224. /* Suffixes */
  225. $suffixes = $this->addSuffixes($suffixes, $part);
  226. }
  227. return $this->buildName($name, $variant, $suffixes);
  228. }
  229. private function buildName(string $result, ?string $variant, array $suffixes): string
  230. {
  231. if ($variant !== null) {
  232. $result .= ' (' . $variant . ')';
  233. }
  234. if (count($suffixes) > 0) {
  235. $result .= ', ' . implode(', ', $suffixes);
  236. }
  237. return $result;
  238. }
  239. private function getVariant(string $part): ?string
  240. {
  241. switch ($part) {
  242. case '0900':
  243. return 'UCA 9.0.0';
  244. case '520':
  245. return 'UCA 5.2.0';
  246. case 'mysql561':
  247. return 'MySQL 5.6.1';
  248. case 'mysql500':
  249. return 'MySQL 5.0.0';
  250. default:
  251. return null;
  252. }
  253. }
  254. private function addSuffixes(array $suffixes, string $part): array
  255. {
  256. switch ($part) {
  257. case 'ci':
  258. $suffixes[] = _pgettext('Collation variant', 'case-insensitive');
  259. break;
  260. case 'cs':
  261. $suffixes[] = _pgettext('Collation variant', 'case-sensitive');
  262. break;
  263. case 'ai':
  264. $suffixes[] = _pgettext('Collation variant', 'accent-insensitive');
  265. break;
  266. case 'as':
  267. $suffixes[] = _pgettext('Collation variant', 'accent-sensitive');
  268. break;
  269. case 'ks':
  270. $suffixes[] = _pgettext('Collation variant', 'kana-sensitive');
  271. break;
  272. case 'w2':
  273. case 'l2':
  274. $suffixes[] = _pgettext('Collation variant', 'multi-level');
  275. break;
  276. case 'bin':
  277. $suffixes[] = _pgettext('Collation variant', 'binary');
  278. break;
  279. case 'nopad':
  280. $suffixes[] = _pgettext('Collation variant', 'no-pad');
  281. break;
  282. }
  283. return $suffixes;
  284. }
  285. private function getNameForLevel0(
  286. bool &$unicode,
  287. bool &$unknown,
  288. string $part,
  289. ?string &$name,
  290. ?string &$variant
  291. ): void {
  292. switch ($part) {
  293. case 'binary':
  294. $name = _pgettext('Collation', 'Binary');
  295. break;
  296. // Unicode charsets
  297. case 'utf8mb4':
  298. $variant = 'UCA 4.0.0';
  299. // Fall through to other unicode
  300. case 'ucs2':
  301. case 'utf8':
  302. case 'utf16':
  303. case 'utf16le':
  304. case 'utf16be':
  305. case 'utf32':
  306. $name = _pgettext('Collation', 'Unicode');
  307. $unicode = true;
  308. break;
  309. // West European charsets
  310. case 'ascii':
  311. case 'cp850':
  312. case 'dec8':
  313. case 'hp8':
  314. case 'latin1':
  315. case 'macroman':
  316. $name = _pgettext('Collation', 'West European');
  317. break;
  318. // Central European charsets
  319. case 'cp1250':
  320. case 'cp852':
  321. case 'latin2':
  322. case 'macce':
  323. $name = _pgettext('Collation', 'Central European');
  324. break;
  325. // Russian charsets
  326. case 'cp866':
  327. case 'koi8r':
  328. $name = _pgettext('Collation', 'Russian');
  329. break;
  330. // Chinese charsets
  331. case 'gb2312':
  332. case 'gbk':
  333. $name = _pgettext('Collation', 'Simplified Chinese');
  334. break;
  335. case 'big5':
  336. $name = _pgettext('Collation', 'Traditional Chinese');
  337. break;
  338. case 'gb18030':
  339. $name = _pgettext('Collation', 'Chinese');
  340. $unicode = true;
  341. break;
  342. // Japanese charsets
  343. case 'sjis':
  344. case 'ujis':
  345. case 'cp932':
  346. case 'eucjpms':
  347. $name = _pgettext('Collation', 'Japanese');
  348. break;
  349. // Baltic charsets
  350. case 'cp1257':
  351. case 'latin7':
  352. $name = _pgettext('Collation', 'Baltic');
  353. break;
  354. // Other
  355. case 'armscii8':
  356. case 'armscii':
  357. $name = _pgettext('Collation', 'Armenian');
  358. break;
  359. case 'cp1251':
  360. $name = _pgettext('Collation', 'Cyrillic');
  361. break;
  362. case 'cp1256':
  363. $name = _pgettext('Collation', 'Arabic');
  364. break;
  365. case 'euckr':
  366. $name = _pgettext('Collation', 'Korean');
  367. break;
  368. case 'hebrew':
  369. $name = _pgettext('Collation', 'Hebrew');
  370. break;
  371. case 'geostd8':
  372. $name = _pgettext('Collation', 'Georgian');
  373. break;
  374. case 'greek':
  375. $name = _pgettext('Collation', 'Greek');
  376. break;
  377. case 'keybcs2':
  378. $name = _pgettext('Collation', 'Czech-Slovak');
  379. break;
  380. case 'koi8u':
  381. $name = _pgettext('Collation', 'Ukrainian');
  382. break;
  383. case 'latin5':
  384. $name = _pgettext('Collation', 'Turkish');
  385. break;
  386. case 'swe7':
  387. $name = _pgettext('Collation', 'Swedish');
  388. break;
  389. case 'tis620':
  390. $name = _pgettext('Collation', 'Thai');
  391. break;
  392. default:
  393. $name = _pgettext('Collation', 'Unknown');
  394. $unknown = true;
  395. break;
  396. }
  397. }
  398. private function getNameForLevel1(
  399. bool $unicode,
  400. bool $unknown,
  401. string $part,
  402. ?string &$name,
  403. int &$level,
  404. bool &$found
  405. ): void {
  406. switch ($part) {
  407. case 'general':
  408. break;
  409. case 'bulgarian':
  410. case 'bg':
  411. $name = _pgettext('Collation', 'Bulgarian');
  412. break;
  413. case 'chinese':
  414. case 'cn':
  415. case 'zh':
  416. if ($unicode) {
  417. $name = _pgettext('Collation', 'Chinese');
  418. }
  419. break;
  420. case 'croatian':
  421. case 'hr':
  422. $name = _pgettext('Collation', 'Croatian');
  423. break;
  424. case 'czech':
  425. case 'cs':
  426. $name = _pgettext('Collation', 'Czech');
  427. break;
  428. case 'danish':
  429. case 'da':
  430. $name = _pgettext('Collation', 'Danish');
  431. break;
  432. case 'english':
  433. case 'en':
  434. $name = _pgettext('Collation', 'English');
  435. break;
  436. case 'esperanto':
  437. case 'eo':
  438. $name = _pgettext('Collation', 'Esperanto');
  439. break;
  440. case 'estonian':
  441. case 'et':
  442. $name = _pgettext('Collation', 'Estonian');
  443. break;
  444. case 'german1':
  445. $name = _pgettext('Collation', 'German (dictionary order)');
  446. break;
  447. case 'german2':
  448. $name = _pgettext('Collation', 'German (phone book order)');
  449. break;
  450. case 'german':
  451. case 'de':
  452. /* Name is set later */
  453. $level = 2;
  454. break;
  455. case 'hungarian':
  456. case 'hu':
  457. $name = _pgettext('Collation', 'Hungarian');
  458. break;
  459. case 'icelandic':
  460. case 'is':
  461. $name = _pgettext('Collation', 'Icelandic');
  462. break;
  463. case 'japanese':
  464. case 'ja':
  465. $name = _pgettext('Collation', 'Japanese');
  466. break;
  467. case 'la':
  468. $name = _pgettext('Collation', 'Classical Latin');
  469. break;
  470. case 'latvian':
  471. case 'lv':
  472. $name = _pgettext('Collation', 'Latvian');
  473. break;
  474. case 'lithuanian':
  475. case 'lt':
  476. $name = _pgettext('Collation', 'Lithuanian');
  477. break;
  478. case 'korean':
  479. case 'ko':
  480. $name = _pgettext('Collation', 'Korean');
  481. break;
  482. case 'myanmar':
  483. case 'my':
  484. $name = _pgettext('Collation', 'Burmese');
  485. break;
  486. case 'persian':
  487. $name = _pgettext('Collation', 'Persian');
  488. break;
  489. case 'polish':
  490. case 'pl':
  491. $name = _pgettext('Collation', 'Polish');
  492. break;
  493. case 'roman':
  494. $name = _pgettext('Collation', 'West European');
  495. break;
  496. case 'romanian':
  497. case 'ro':
  498. $name = _pgettext('Collation', 'Romanian');
  499. break;
  500. case 'ru':
  501. $name = _pgettext('Collation', 'Russian');
  502. break;
  503. case 'si':
  504. case 'sinhala':
  505. $name = _pgettext('Collation', 'Sinhalese');
  506. break;
  507. case 'slovak':
  508. case 'sk':
  509. $name = _pgettext('Collation', 'Slovak');
  510. break;
  511. case 'slovenian':
  512. case 'sl':
  513. $name = _pgettext('Collation', 'Slovenian');
  514. break;
  515. case 'spanish':
  516. $name = _pgettext('Collation', 'Spanish (modern)');
  517. break;
  518. case 'es':
  519. /* Name is set later */
  520. $level = 3;
  521. break;
  522. case 'spanish2':
  523. $name = _pgettext('Collation', 'Spanish (traditional)');
  524. break;
  525. case 'swedish':
  526. case 'sv':
  527. $name = _pgettext('Collation', 'Swedish');
  528. break;
  529. case 'thai':
  530. case 'th':
  531. $name = _pgettext('Collation', 'Thai');
  532. break;
  533. case 'turkish':
  534. case 'tr':
  535. $name = _pgettext('Collation', 'Turkish');
  536. break;
  537. case 'ukrainian':
  538. case 'uk':
  539. $name = _pgettext('Collation', 'Ukrainian');
  540. break;
  541. case 'vietnamese':
  542. case 'vi':
  543. $name = _pgettext('Collation', 'Vietnamese');
  544. break;
  545. case 'unicode':
  546. if ($unknown) {
  547. $name = _pgettext('Collation', 'Unicode');
  548. }
  549. break;
  550. default:
  551. $found = false;
  552. }
  553. }
  554. }