Util.php 102 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\Html\Generator;
  5. use PhpMyAdmin\Html\MySQLDocumentation;
  6. use PhpMyAdmin\Query\Utilities;
  7. use PhpMyAdmin\SqlParser\Components\Expression;
  8. use PhpMyAdmin\SqlParser\Context;
  9. use PhpMyAdmin\SqlParser\Token;
  10. use PhpMyAdmin\Utils\SessionCache;
  11. use phpseclib\Crypt\Random;
  12. use stdClass;
  13. use const ENT_COMPAT;
  14. use const ENT_QUOTES;
  15. use const PHP_INT_SIZE;
  16. use const PHP_MAJOR_VERSION;
  17. use const PREG_OFFSET_CAPTURE;
  18. use const STR_PAD_LEFT;
  19. use function abs;
  20. use function array_key_exists;
  21. use function array_map;
  22. use function array_merge;
  23. use function array_shift;
  24. use function array_unique;
  25. use function basename;
  26. use function bin2hex;
  27. use function chr;
  28. use function class_exists;
  29. use function count;
  30. use function ctype_digit;
  31. use function date;
  32. use function decbin;
  33. use function defined;
  34. use function explode;
  35. use function extension_loaded;
  36. use function fclose;
  37. use function floatval;
  38. use function floor;
  39. use function fread;
  40. use function function_exists;
  41. use function html_entity_decode;
  42. use function htmlentities;
  43. use function htmlspecialchars;
  44. use function htmlspecialchars_decode;
  45. use function implode;
  46. use function in_array;
  47. use function ini_get;
  48. use function is_array;
  49. use function is_callable;
  50. use function is_object;
  51. use function is_string;
  52. use function log10;
  53. use function mb_detect_encoding;
  54. use function mb_strlen;
  55. use function mb_strpos;
  56. use function mb_strrpos;
  57. use function mb_strstr;
  58. use function mb_strtolower;
  59. use function mb_substr;
  60. use function number_format;
  61. use function ord;
  62. use function parse_url;
  63. use function pow;
  64. use function preg_match;
  65. use function preg_quote;
  66. use function preg_replace;
  67. use function range;
  68. use function reset;
  69. use function round;
  70. use function rtrim;
  71. use function set_time_limit;
  72. use function sort;
  73. use function sprintf;
  74. use function str_pad;
  75. use function str_replace;
  76. use function strcasecmp;
  77. use function strftime;
  78. use function stripos;
  79. use function strlen;
  80. use function strpos;
  81. use function strrev;
  82. use function strtolower;
  83. use function strtoupper;
  84. use function strtr;
  85. use function substr;
  86. use function time;
  87. use function trim;
  88. use function uksort;
  89. use function version_compare;
  90. /**
  91. * Misc functions used all over the scripts.
  92. */
  93. class Util
  94. {
  95. /**
  96. * Checks whether configuration value tells to show icons.
  97. *
  98. * @param string $value Configuration option name
  99. *
  100. * @return bool Whether to show icons.
  101. */
  102. public static function showIcons($value): bool
  103. {
  104. return in_array($GLOBALS['cfg'][$value], ['icons', 'both']);
  105. }
  106. /**
  107. * Checks whether configuration value tells to show text.
  108. *
  109. * @param string $value Configuration option name
  110. *
  111. * @return bool Whether to show text.
  112. */
  113. public static function showText($value): bool
  114. {
  115. return in_array($GLOBALS['cfg'][$value], ['text', 'both']);
  116. }
  117. /**
  118. * Returns the formatted maximum size for an upload
  119. *
  120. * @param int|float|string $max_upload_size the size
  121. *
  122. * @return string the message
  123. *
  124. * @access public
  125. */
  126. public static function getFormattedMaximumUploadSize($max_upload_size): string
  127. {
  128. // I have to reduce the second parameter (sensitiveness) from 6 to 4
  129. // to avoid weird results like 512 kKib
  130. [$max_size, $max_unit] = self::formatByteDown($max_upload_size, 4);
  131. return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')';
  132. }
  133. /**
  134. * Add slashes before "_" and "%" characters for using them in MySQL
  135. * database, table and field names.
  136. * Note: This function does not escape backslashes!
  137. *
  138. * @param string $name the string to escape
  139. *
  140. * @return string the escaped string
  141. *
  142. * @access public
  143. */
  144. public static function escapeMysqlWildcards($name): string
  145. {
  146. return strtr($name, ['_' => '\\_', '%' => '\\%']);
  147. }
  148. /**
  149. * removes slashes before "_" and "%" characters
  150. * Note: This function does not unescape backslashes!
  151. *
  152. * @param string $name the string to escape
  153. *
  154. * @return string the escaped string
  155. *
  156. * @access public
  157. */
  158. public static function unescapeMysqlWildcards($name): string
  159. {
  160. return strtr($name, ['\\_' => '_', '\\%' => '%']);
  161. }
  162. /**
  163. * removes quotes (',",`) from a quoted string
  164. *
  165. * checks if the string is quoted and removes this quotes
  166. *
  167. * @param string $quoted_string string to remove quotes from
  168. * @param string $quote type of quote to remove
  169. *
  170. * @return string unquoted string
  171. */
  172. public static function unQuote(string $quoted_string, ?string $quote = null): string
  173. {
  174. $quotes = [];
  175. if ($quote === null) {
  176. $quotes[] = '`';
  177. $quotes[] = '"';
  178. $quotes[] = "'";
  179. } else {
  180. $quotes[] = $quote;
  181. }
  182. foreach ($quotes as $quote) {
  183. if (mb_substr($quoted_string, 0, 1) === $quote
  184. && mb_substr($quoted_string, -1, 1) === $quote
  185. ) {
  186. $unquoted_string = mb_substr($quoted_string, 1, -1);
  187. // replace escaped quotes
  188. $unquoted_string = str_replace(
  189. $quote . $quote,
  190. $quote,
  191. $unquoted_string
  192. );
  193. return $unquoted_string;
  194. }
  195. }
  196. return $quoted_string;
  197. }
  198. /**
  199. * Get a URL link to the official MySQL documentation
  200. *
  201. * @param string $link contains name of page/anchor that is being linked
  202. * @param string $anchor anchor to page part
  203. *
  204. * @return string the URL link
  205. *
  206. * @access public
  207. */
  208. public static function getMySQLDocuURL(string $link, string $anchor = ''): string
  209. {
  210. global $dbi;
  211. // Fixup for newly used names:
  212. $link = str_replace('_', '-', mb_strtolower($link));
  213. if (empty($link)) {
  214. $link = 'index';
  215. }
  216. $mysql = '5.5';
  217. $lang = 'en';
  218. if (isset($dbi)) {
  219. $serverVersion = $dbi->getVersion();
  220. if ($serverVersion >= 80000) {
  221. $mysql = '8.0';
  222. } elseif ($serverVersion >= 50700) {
  223. $mysql = '5.7';
  224. } elseif ($serverVersion >= 50600) {
  225. $mysql = '5.6';
  226. } elseif ($serverVersion >= 50500) {
  227. $mysql = '5.5';
  228. }
  229. }
  230. $url = 'https://dev.mysql.com/doc/refman/'
  231. . $mysql . '/' . $lang . '/' . $link . '.html';
  232. if (! empty($anchor)) {
  233. $url .= '#' . $anchor;
  234. }
  235. return Core::linkURL($url);
  236. }
  237. /**
  238. * Get a URL link to the official documentation page of either MySQL
  239. * or MariaDB depending on the database server
  240. * of the user.
  241. *
  242. * @param bool $isMariaDB if the database server is MariaDB
  243. *
  244. * @return string The URL link
  245. */
  246. public static function getDocuURL(bool $isMariaDB = false): string
  247. {
  248. if ($isMariaDB) {
  249. $url = 'https://mariadb.com/kb/en/documentation/';
  250. return Core::linkURL($url);
  251. }
  252. return self::getMySQLDocuURL('');
  253. }
  254. /**
  255. * Check the correct row count
  256. *
  257. * @param string $db the db name
  258. * @param array $table the table infos
  259. *
  260. * @return int the possibly modified row count
  261. */
  262. private static function checkRowCount($db, array $table)
  263. {
  264. global $dbi;
  265. $rowCount = 0;
  266. if ($table['Rows'] === null) {
  267. // Do not check exact row count here,
  268. // if row count is invalid possibly the table is defect
  269. // and this would break the navigation panel;
  270. // but we can check row count if this is a view or the
  271. // information_schema database
  272. // since Table::countRecords() returns a limited row count
  273. // in this case.
  274. // set this because Table::countRecords() can use it
  275. $tbl_is_view = $table['TABLE_TYPE'] === 'VIEW';
  276. if ($tbl_is_view || Utilities::isSystemSchema($db)) {
  277. $rowCount = $dbi
  278. ->getTable($db, $table['Name'])
  279. ->countRecords();
  280. }
  281. }
  282. return $rowCount;
  283. }
  284. /**
  285. * returns array with tables of given db with extended information and grouped
  286. *
  287. * @param string $db
  288. *
  289. * @return array (recursive) grouped table list
  290. */
  291. public static function getTableList($db): array
  292. {
  293. global $dbi;
  294. $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
  295. $tables = $dbi->getTablesFull($db);
  296. if ($GLOBALS['cfg']['NaturalOrder']) {
  297. uksort($tables, 'strnatcasecmp');
  298. }
  299. if (count($tables) < 1) {
  300. return $tables;
  301. }
  302. $default = [
  303. 'Name' => '',
  304. 'Rows' => 0,
  305. 'Comment' => '',
  306. 'disp_name' => '',
  307. ];
  308. $table_groups = [];
  309. foreach ($tables as $table_name => $table) {
  310. $table['Rows'] = self::checkRowCount($db, $table);
  311. // in $group we save the reference to the place in $table_groups
  312. // where to store the table info
  313. if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
  314. && $sep && mb_strstr($table_name, $sep)
  315. ) {
  316. $parts = explode($sep, $table_name);
  317. $group =& $table_groups;
  318. $i = 0;
  319. $group_name_full = '';
  320. $parts_cnt = count($parts) - 1;
  321. while (($i < $parts_cnt)
  322. && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel'])
  323. ) {
  324. $group_name = $parts[$i] . $sep;
  325. $group_name_full .= $group_name;
  326. if (! isset($group[$group_name])) {
  327. $group[$group_name] = [];
  328. $group[$group_name]['is' . $sep . 'group'] = true;
  329. $group[$group_name]['tab' . $sep . 'count'] = 1;
  330. $group[$group_name]['tab' . $sep . 'group']
  331. = $group_name_full;
  332. } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) {
  333. $table = $group[$group_name];
  334. $group[$group_name] = [];
  335. $group[$group_name][$group_name] = $table;
  336. $group[$group_name]['is' . $sep . 'group'] = true;
  337. $group[$group_name]['tab' . $sep . 'count'] = 1;
  338. $group[$group_name]['tab' . $sep . 'group']
  339. = $group_name_full;
  340. } else {
  341. $group[$group_name]['tab' . $sep . 'count']++;
  342. }
  343. $group =& $group[$group_name];
  344. $i++;
  345. }
  346. } else {
  347. if (! isset($table_groups[$table_name])) {
  348. $table_groups[$table_name] = [];
  349. }
  350. $group =& $table_groups;
  351. }
  352. $table['disp_name'] = $table['Name'];
  353. $group[$table_name] = array_merge($default, $table);
  354. }
  355. return $table_groups;
  356. }
  357. /* ----------------------- Set of misc functions ----------------------- */
  358. /**
  359. * Adds backquotes on both sides of a database, table or field name.
  360. * and escapes backquotes inside the name with another backquote
  361. *
  362. * example:
  363. * <code>
  364. * echo backquote('owner`s db'); // `owner``s db`
  365. *
  366. * </code>
  367. *
  368. * @param array|string $a_name the database, table or field name to "backquote"
  369. * or array of it
  370. * @param bool $do_it a flag to bypass this function (used by dump
  371. * functions)
  372. *
  373. * @return mixed the "backquoted" database, table or field name
  374. *
  375. * @access public
  376. */
  377. public static function backquote($a_name, $do_it = true)
  378. {
  379. return static::backquoteCompat($a_name, 'NONE', $do_it);
  380. }
  381. /**
  382. * Adds backquotes on both sides of a database, table or field name.
  383. * in compatibility mode
  384. *
  385. * example:
  386. * <code>
  387. * echo backquoteCompat('owner`s db'); // `owner``s db`
  388. *
  389. * </code>
  390. *
  391. * @param array|string $a_name the database, table or field name to
  392. * "backquote" or array of it
  393. * @param string $compatibility string compatibility mode (used by dump
  394. * functions)
  395. * @param bool $do_it a flag to bypass this function (used by dump
  396. * functions)
  397. *
  398. * @return mixed the "backquoted" database, table or field name
  399. *
  400. * @access public
  401. */
  402. public static function backquoteCompat(
  403. $a_name,
  404. string $compatibility = 'MSSQL',
  405. $do_it = true
  406. ) {
  407. if (is_array($a_name)) {
  408. foreach ($a_name as &$data) {
  409. $data = self::backquoteCompat($data, $compatibility, $do_it);
  410. }
  411. return $a_name;
  412. }
  413. if (! $do_it) {
  414. if (! (Context::isKeyword($a_name) & Token::FLAG_KEYWORD_RESERVED)) {
  415. return $a_name;
  416. }
  417. }
  418. // @todo add more compatibility cases (ORACLE for example)
  419. switch ($compatibility) {
  420. case 'MSSQL':
  421. $quote = '"';
  422. $escapeChar = '\\';
  423. break;
  424. default:
  425. $quote = '`';
  426. $escapeChar = '`';
  427. break;
  428. }
  429. // '0' is also empty for php :-(
  430. if (strlen((string) $a_name) > 0 && $a_name !== '*') {
  431. return $quote . str_replace($quote, $escapeChar . $quote, (string) $a_name) . $quote;
  432. }
  433. return $a_name;
  434. }
  435. /**
  436. * Formats $value to byte view
  437. *
  438. * @param float|int|string|null $value the value to format
  439. * @param int $limes the sensitiveness
  440. * @param int $comma the number of decimals to retain
  441. *
  442. * @return array|null the formatted value and its unit
  443. *
  444. * @access public
  445. */
  446. public static function formatByteDown($value, $limes = 6, $comma = 0): ?array
  447. {
  448. if ($value === null) {
  449. return null;
  450. }
  451. if (is_string($value)) {
  452. $value = (float) $value;
  453. }
  454. $byteUnits = [
  455. /* l10n: shortcuts for Byte */
  456. __('B'),
  457. /* l10n: shortcuts for Kilobyte */
  458. __('KiB'),
  459. /* l10n: shortcuts for Megabyte */
  460. __('MiB'),
  461. /* l10n: shortcuts for Gigabyte */
  462. __('GiB'),
  463. /* l10n: shortcuts for Terabyte */
  464. __('TiB'),
  465. /* l10n: shortcuts for Petabyte */
  466. __('PiB'),
  467. /* l10n: shortcuts for Exabyte */
  468. __('EiB'),
  469. ];
  470. $dh = pow(10, $comma);
  471. $li = pow(10, $limes);
  472. $unit = $byteUnits[0];
  473. for ($d = 6, $ex = 15; $d >= 1; $d--, $ex -= 3) {
  474. $unitSize = $li * pow(10, $ex);
  475. if (isset($byteUnits[$d]) && $value >= $unitSize) {
  476. // use 1024.0 to avoid integer overflow on 64-bit machines
  477. $value = round($value / (pow(1024, $d) / $dh)) / $dh;
  478. $unit = $byteUnits[$d];
  479. break 1;
  480. }
  481. }
  482. if ($unit != $byteUnits[0]) {
  483. // if the unit is not bytes (as represented in current language)
  484. // reformat with max length of 5
  485. // 4th parameter=true means do not reformat if value < 1
  486. $return_value = self::formatNumber($value, 5, $comma, true, false);
  487. } else {
  488. // do not reformat, just handle the locale
  489. $return_value = self::formatNumber($value, 0);
  490. }
  491. return [
  492. trim($return_value),
  493. $unit,
  494. ];
  495. }
  496. /**
  497. * Formats $value to the given length and appends SI prefixes
  498. * with a $length of 0 no truncation occurs, number is only formatted
  499. * to the current locale
  500. *
  501. * examples:
  502. * <code>
  503. * echo formatNumber(123456789, 6); // 123,457 k
  504. * echo formatNumber(-123456789, 4, 2); // -123.46 M
  505. * echo formatNumber(-0.003, 6); // -3 m
  506. * echo formatNumber(0.003, 3, 3); // 0.003
  507. * echo formatNumber(0.00003, 3, 2); // 0.03 m
  508. * echo formatNumber(0, 6); // 0
  509. * </code>
  510. *
  511. * @param float|int|string $value the value to format
  512. * @param int $digits_left number of digits left of the comma
  513. * @param int $digits_right number of digits right of the comma
  514. * @param bool $only_down do not reformat numbers below 1
  515. * @param bool $noTrailingZero removes trailing zeros right of the comma (default: true)
  516. *
  517. * @return string the formatted value and its unit
  518. *
  519. * @access public
  520. */
  521. public static function formatNumber(
  522. $value,
  523. $digits_left = 3,
  524. $digits_right = 0,
  525. $only_down = false,
  526. $noTrailingZero = true
  527. ) {
  528. if ($value == 0) {
  529. return '0';
  530. }
  531. if (is_string($value)) {
  532. $value = (float) $value;
  533. }
  534. $originalValue = $value;
  535. //number_format is not multibyte safe, str_replace is safe
  536. if ($digits_left === 0) {
  537. $value = number_format(
  538. (float) $value,
  539. $digits_right,
  540. /* l10n: Decimal separator */
  541. __('.'),
  542. /* l10n: Thousands separator */
  543. __(',')
  544. );
  545. if (($originalValue != 0) && (floatval($value) == 0)) {
  546. $value = ' <' . (1 / pow(10, $digits_right));
  547. }
  548. return $value;
  549. }
  550. // this units needs no translation, ISO
  551. $units = [
  552. -8 => 'y',
  553. -7 => 'z',
  554. -6 => 'a',
  555. -5 => 'f',
  556. -4 => 'p',
  557. -3 => 'n',
  558. -2 => 'µ',
  559. -1 => 'm',
  560. 0 => ' ',
  561. 1 => 'k',
  562. 2 => 'M',
  563. 3 => 'G',
  564. 4 => 'T',
  565. 5 => 'P',
  566. 6 => 'E',
  567. 7 => 'Z',
  568. 8 => 'Y',
  569. ];
  570. /* l10n: Decimal separator */
  571. $decimal_sep = __('.');
  572. /* l10n: Thousands separator */
  573. $thousands_sep = __(',');
  574. // check for negative value to retain sign
  575. if ($value < 0) {
  576. $sign = '-';
  577. $value = abs($value);
  578. } else {
  579. $sign = '';
  580. }
  581. $dh = pow(10, $digits_right);
  582. /*
  583. * This gives us the right SI prefix already,
  584. * but $digits_left parameter not incorporated
  585. */
  586. $d = floor(log10((float) $value) / 3);
  587. /*
  588. * Lowering the SI prefix by 1 gives us an additional 3 zeros
  589. * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits)
  590. * to use, then lower the SI prefix
  591. */
  592. $cur_digits = floor(log10($value / pow(1000, $d)) + 1);
  593. if ($digits_left > $cur_digits) {
  594. $d -= floor(($digits_left - $cur_digits) / 3);
  595. }
  596. if ($d < 0 && $only_down) {
  597. $d = 0;
  598. }
  599. $value = round($value / (pow(1000, $d) / $dh)) / $dh;
  600. $unit = $units[$d];
  601. // number_format is not multibyte safe, str_replace is safe
  602. $formattedValue = number_format(
  603. $value,
  604. $digits_right,
  605. $decimal_sep,
  606. $thousands_sep
  607. );
  608. // If we don't want any zeros, remove them now
  609. if ($noTrailingZero && strpos($formattedValue, $decimal_sep) !== false) {
  610. $formattedValue = preg_replace('/' . preg_quote($decimal_sep, '/') . '?0+$/', '', $formattedValue);
  611. }
  612. if ($originalValue != 0 && floatval($value) == 0) {
  613. return ' <' . number_format(
  614. 1 / pow(10, $digits_right),
  615. $digits_right,
  616. $decimal_sep,
  617. $thousands_sep
  618. )
  619. . ' ' . $unit;
  620. }
  621. return $sign . $formattedValue . ' ' . $unit;
  622. }
  623. /**
  624. * Returns the number of bytes when a formatted size is given
  625. *
  626. * @param string|int $formatted_size the size expression (for example 8MB)
  627. *
  628. * @return int|float The numerical part of the expression (for example 8)
  629. */
  630. public static function extractValueFromFormattedSize($formatted_size)
  631. {
  632. $return_value = -1;
  633. $formatted_size = (string) $formatted_size;
  634. if (preg_match('/^[0-9]+GB$/', $formatted_size)) {
  635. $return_value = (int) mb_substr(
  636. $formatted_size,
  637. 0,
  638. -2
  639. ) * pow(1024, 3);
  640. } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) {
  641. $return_value = (int) mb_substr(
  642. $formatted_size,
  643. 0,
  644. -2
  645. ) * pow(1024, 2);
  646. } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) {
  647. $return_value = (int) mb_substr(
  648. $formatted_size,
  649. 0,
  650. -1
  651. ) * pow(1024, 1);
  652. }
  653. return $return_value;
  654. }
  655. /**
  656. * Writes localised date
  657. *
  658. * @param int $timestamp the current timestamp
  659. * @param string $format format
  660. *
  661. * @return string the formatted date
  662. *
  663. * @access public
  664. */
  665. public static function localisedDate($timestamp = -1, $format = '')
  666. {
  667. $month = [
  668. /* l10n: Short month name */
  669. __('Jan'),
  670. /* l10n: Short month name */
  671. __('Feb'),
  672. /* l10n: Short month name */
  673. __('Mar'),
  674. /* l10n: Short month name */
  675. __('Apr'),
  676. /* l10n: Short month name */
  677. _pgettext('Short month name', 'May'),
  678. /* l10n: Short month name */
  679. __('Jun'),
  680. /* l10n: Short month name */
  681. __('Jul'),
  682. /* l10n: Short month name */
  683. __('Aug'),
  684. /* l10n: Short month name */
  685. __('Sep'),
  686. /* l10n: Short month name */
  687. __('Oct'),
  688. /* l10n: Short month name */
  689. __('Nov'),
  690. /* l10n: Short month name */
  691. __('Dec'),
  692. ];
  693. $day_of_week = [
  694. /* l10n: Short week day name for Sunday */
  695. _pgettext('Short week day name for Sunday', 'Sun'),
  696. /* l10n: Short week day name for Monday */
  697. __('Mon'),
  698. /* l10n: Short week day name for Tuesday */
  699. __('Tue'),
  700. /* l10n: Short week day name for Wednesday */
  701. __('Wed'),
  702. /* l10n: Short week day name for Thursday */
  703. __('Thu'),
  704. /* l10n: Short week day name for Friday */
  705. __('Fri'),
  706. /* l10n: Short week day name for Saturday */
  707. __('Sat'),
  708. ];
  709. if ($format == '') {
  710. /* l10n: See https://www.php.net/manual/en/function.strftime.php */
  711. $format = __('%B %d, %Y at %I:%M %p');
  712. }
  713. if ($timestamp == -1) {
  714. $timestamp = time();
  715. }
  716. $date = (string) preg_replace(
  717. '@%[aA]@',
  718. $day_of_week[(int) strftime('%w', (int) $timestamp)],
  719. $format
  720. );
  721. $date = (string) preg_replace(
  722. '@%[bB]@',
  723. $month[(int) strftime('%m', (int) $timestamp) - 1],
  724. $date
  725. );
  726. /* Fill in AM/PM */
  727. $hours = (int) date('H', (int) $timestamp);
  728. if ($hours >= 12) {
  729. $am_pm = _pgettext('AM/PM indication in time', 'PM');
  730. } else {
  731. $am_pm = _pgettext('AM/PM indication in time', 'AM');
  732. }
  733. $date = (string) preg_replace('@%[pP]@', $am_pm, $date);
  734. // Can return false on windows for Japanese language
  735. // See https://github.com/phpmyadmin/phpmyadmin/issues/15830
  736. $ret = strftime($date, (int) $timestamp);
  737. // Some OSes such as Win8.1 Traditional Chinese version did not produce UTF-8
  738. // output here. See https://github.com/phpmyadmin/phpmyadmin/issues/10598
  739. if ($ret === false
  740. || mb_detect_encoding($ret, 'UTF-8', true) !== 'UTF-8'
  741. ) {
  742. $ret = date('Y-m-d H:i:s', (int) $timestamp);
  743. }
  744. return $ret;
  745. }
  746. /**
  747. * Splits a URL string by parameter
  748. *
  749. * @param string $url the URL
  750. *
  751. * @return array<int, string> the parameter/value pairs, for example [0] db=sakila
  752. */
  753. public static function splitURLQuery($url): array
  754. {
  755. // decode encoded url separators
  756. $separator = Url::getArgSeparator();
  757. // on most places separator is still hard coded ...
  758. if ($separator !== '&') {
  759. // ... so always replace & with $separator
  760. $url = str_replace([htmlentities('&'), '&'], [$separator, $separator], $url);
  761. }
  762. $url = str_replace(htmlentities($separator), $separator, $url);
  763. // end decode
  764. $url_parts = parse_url($url);
  765. if (is_array($url_parts) && isset($url_parts['query'])) {
  766. $array = explode($separator, $url_parts['query']);
  767. return is_array($array) ? $array : [];
  768. }
  769. return [];
  770. }
  771. /**
  772. * Returns a given timespan value in a readable format.
  773. *
  774. * @param int $seconds the timespan
  775. *
  776. * @return string the formatted value
  777. */
  778. public static function timespanFormat($seconds): string
  779. {
  780. $days = floor($seconds / 86400);
  781. if ($days > 0) {
  782. $seconds -= $days * 86400;
  783. }
  784. $hours = floor($seconds / 3600);
  785. if ($days > 0 || $hours > 0) {
  786. $seconds -= $hours * 3600;
  787. }
  788. $minutes = floor($seconds / 60);
  789. if ($days > 0 || $hours > 0 || $minutes > 0) {
  790. $seconds -= $minutes * 60;
  791. }
  792. return sprintf(
  793. __('%s days, %s hours, %s minutes and %s seconds'),
  794. (string) $days,
  795. (string) $hours,
  796. (string) $minutes,
  797. (string) $seconds
  798. );
  799. }
  800. /**
  801. * Function added to avoid path disclosures.
  802. * Called by each script that needs parameters, it displays
  803. * an error message and, by default, stops the execution.
  804. *
  805. * @param string[] $params The names of the parameters needed by the calling
  806. * script
  807. * @param bool $request Check parameters in request
  808. *
  809. * @access public
  810. */
  811. public static function checkParameters($params, $request = false): void
  812. {
  813. $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']);
  814. $found_error = false;
  815. $error_message = '';
  816. if ($request) {
  817. $array = $_REQUEST;
  818. } else {
  819. $array = $GLOBALS;
  820. }
  821. foreach ($params as $param) {
  822. if (isset($array[$param])) {
  823. continue;
  824. }
  825. $error_message .= $reported_script_name
  826. . ': ' . __('Missing parameter:') . ' '
  827. . $param
  828. . MySQLDocumentation::showDocumentation('faq', 'faqmissingparameters', true)
  829. . '[br]';
  830. $found_error = true;
  831. }
  832. if (! $found_error) {
  833. return;
  834. }
  835. Core::fatalError($error_message);
  836. }
  837. /**
  838. * Build a condition and with a value
  839. *
  840. * @param string|int|float|null $row The row value
  841. * @param stdClass $meta The field metadata
  842. * @param string $fieldFlags The field flags
  843. * @param int $fieldsCount A number of fields
  844. * @param string $conditionKey A key used for BINARY fields functions
  845. * @param string $condition The condition
  846. *
  847. * @return array<int,string|null>
  848. */
  849. private static function getConditionValue(
  850. $row,
  851. stdClass $meta,
  852. string $fieldFlags,
  853. int $fieldsCount,
  854. string $conditionKey,
  855. string $condition
  856. ): array {
  857. global $dbi;
  858. if ($row === null) {
  859. return ['IS NULL', $condition];
  860. }
  861. $conditionValue = '';
  862. $isBinaryString = $meta->type === 'string' && stripos($fieldFlags, 'BINARY') !== false;
  863. // 63 is the binary charset, see: https://dev.mysql.com/doc/internals/en/charsets.html
  864. $isBlobAndIsBinaryCharset = $meta->type === 'blob' && $meta->charsetnr === 63;
  865. // timestamp is numeric on some MySQL 4.1
  866. // for real we use CONCAT above and it should compare to string
  867. if ($meta->numeric
  868. && ($meta->type !== 'timestamp')
  869. && ($meta->type !== 'real')
  870. ) {
  871. $conditionValue = '= ' . $row;
  872. } elseif ($isBlobAndIsBinaryCharset || (! empty($row) && $isBinaryString)) {
  873. // hexify only if this is a true not empty BLOB or a BINARY
  874. // do not waste memory building a too big condition
  875. $rowLength = mb_strlen((string) $row);
  876. if ($rowLength > 0 && $rowLength < 1000) {
  877. // use a CAST if possible, to avoid problems
  878. // if the field contains wildcard characters % or _
  879. $conditionValue = '= CAST(0x' . bin2hex((string) $row) . ' AS BINARY)';
  880. } elseif ($fieldsCount === 1) {
  881. // when this blob is the only field present
  882. // try settling with length comparison
  883. $condition = ' CHAR_LENGTH(' . $conditionKey . ') ';
  884. $conditionValue = ' = ' . $rowLength;
  885. } else {
  886. // this blob won't be part of the final condition
  887. $conditionValue = null;
  888. }
  889. } elseif (in_array($meta->type, self::getGISDatatypes())
  890. && ! empty($row)
  891. ) {
  892. // do not build a too big condition
  893. if (mb_strlen((string) $row) < 5000) {
  894. $condition .= '=0x' . bin2hex((string) $row) . ' AND';
  895. } else {
  896. $condition = '';
  897. }
  898. } elseif ($meta->type === 'bit') {
  899. $conditionValue = "= b'"
  900. . self::printableBitValue((int) $row, (int) $meta->length) . "'";
  901. } else {
  902. $conditionValue = '= \''
  903. . $dbi->escapeString($row) . '\'';
  904. }
  905. return [$conditionValue, $condition];
  906. }
  907. /**
  908. * Function to generate unique condition for specified row.
  909. *
  910. * @param resource|int $handle current query result
  911. * @param int $fields_cnt number of fields
  912. * @param stdClass[] $fields_meta meta information about fields
  913. * @param array $row current row
  914. * @param bool $force_unique generate condition only on pk or unique
  915. * @param string|bool $restrict_to_table restrict the unique condition to this table or false if none
  916. * @param Expression[] $expressions An array of Expression instances.
  917. *
  918. * @return array the calculated condition and whether condition is unique
  919. */
  920. public static function getUniqueCondition(
  921. $handle,
  922. $fields_cnt,
  923. array $fields_meta,
  924. array $row,
  925. $force_unique = false,
  926. $restrict_to_table = false,
  927. array $expressions = []
  928. ): array {
  929. global $dbi;
  930. $primary_key = '';
  931. $unique_key = '';
  932. $nonprimary_condition = '';
  933. $preferred_condition = '';
  934. $primary_key_array = [];
  935. $unique_key_array = [];
  936. $nonprimary_condition_array = [];
  937. $condition_array = [];
  938. for ($i = 0; $i < $fields_cnt; ++$i) {
  939. $meta = $fields_meta[$i];
  940. // do not use a column alias in a condition
  941. if (! isset($meta->orgname) || strlen($meta->orgname) === 0) {
  942. $meta->orgname = $meta->name;
  943. foreach ($expressions as $expression) {
  944. if (empty($expression->alias) || empty($expression->column)) {
  945. continue;
  946. }
  947. if (strcasecmp($meta->name, $expression->alias) == 0) {
  948. $meta->orgname = $expression->column;
  949. break;
  950. }
  951. }
  952. }
  953. // Do not use a table alias in a condition.
  954. // Test case is:
  955. // select * from galerie x WHERE
  956. //(select count(*) from galerie y where y.datum=x.datum)>1
  957. //
  958. // But orgtable is present only with mysqli extension so the
  959. // fix is only for mysqli.
  960. // Also, do not use the original table name if we are dealing with
  961. // a view because this view might be updatable.
  962. // (The isView() verification should not be costly in most cases
  963. // because there is some caching in the function).
  964. if (isset($meta->orgtable)
  965. && ($meta->table != $meta->orgtable)
  966. && ! $dbi->getTable($GLOBALS['db'], $meta->table)->isView()
  967. ) {
  968. $meta->table = $meta->orgtable;
  969. }
  970. // If this field is not from the table which the unique clause needs
  971. // to be restricted to.
  972. if ($restrict_to_table && $restrict_to_table != $meta->table) {
  973. continue;
  974. }
  975. // to fix the bug where float fields (primary or not)
  976. // can't be matched because of the imprecision of
  977. // floating comparison, use CONCAT
  978. // (also, the syntax "CONCAT(field) IS NULL"
  979. // that we need on the next "if" will work)
  980. if ($meta->type === 'real') {
  981. $con_key = 'CONCAT(' . self::backquote($meta->table) . '.'
  982. . self::backquote($meta->orgname) . ')';
  983. } else {
  984. $con_key = self::backquote($meta->table) . '.'
  985. . self::backquote($meta->orgname);
  986. }
  987. $condition = ' ' . $con_key . ' ';
  988. [$con_val, $condition] = self::getConditionValue(
  989. $row[$i] ?? null,
  990. $meta,
  991. $dbi->fieldFlags($handle, $i),
  992. $fields_cnt,
  993. $con_key,
  994. $condition
  995. );
  996. if ($con_val === null) {
  997. continue;
  998. }
  999. $condition .= $con_val . ' AND';
  1000. if ($meta->primary_key > 0) {
  1001. $primary_key .= $condition;
  1002. $primary_key_array[$con_key] = $con_val;
  1003. } elseif ($meta->unique_key > 0) {
  1004. $unique_key .= $condition;
  1005. $unique_key_array[$con_key] = $con_val;
  1006. }
  1007. $nonprimary_condition .= $condition;
  1008. $nonprimary_condition_array[$con_key] = $con_val;
  1009. }
  1010. // Correction University of Virginia 19991216:
  1011. // prefer primary or unique keys for condition,
  1012. // but use conjunction of all values if no primary key
  1013. $clause_is_unique = true;
  1014. if ($primary_key) {
  1015. $preferred_condition = $primary_key;
  1016. $condition_array = $primary_key_array;
  1017. } elseif ($unique_key) {
  1018. $preferred_condition = $unique_key;
  1019. $condition_array = $unique_key_array;
  1020. } elseif (! $force_unique) {
  1021. $preferred_condition = $nonprimary_condition;
  1022. $condition_array = $nonprimary_condition_array;
  1023. $clause_is_unique = false;
  1024. }
  1025. $where_clause = trim((string) preg_replace('|\s?AND$|', '', $preferred_condition));
  1026. return [
  1027. $where_clause,
  1028. $clause_is_unique,
  1029. $condition_array,
  1030. ];
  1031. }
  1032. /**
  1033. * Generate the charset query part
  1034. *
  1035. * @param string $collation Collation
  1036. * @param bool $override (optional) force 'CHARACTER SET' keyword
  1037. */
  1038. public static function getCharsetQueryPart(string $collation, bool $override = false): string
  1039. {
  1040. [$charset] = explode('_', $collation);
  1041. $keyword = ' CHARSET=';
  1042. if ($override) {
  1043. $keyword = ' CHARACTER SET ';
  1044. }
  1045. return $keyword . $charset
  1046. . ($charset == $collation ? '' : ' COLLATE ' . $collation);
  1047. }
  1048. /**
  1049. * Generate a pagination selector for browsing resultsets
  1050. *
  1051. * @param string $name The name for the request parameter
  1052. * @param int $rows Number of rows in the pagination set
  1053. * @param int $pageNow current page number
  1054. * @param int $nbTotalPage number of total pages
  1055. * @param int $showAll If the number of pages is lower than this
  1056. * variable, no pages will be omitted in pagination
  1057. * @param int $sliceStart How many rows at the beginning should always
  1058. * be shown?
  1059. * @param int $sliceEnd How many rows at the end should always be shown?
  1060. * @param int $percent Percentage of calculation page offsets to hop to a
  1061. * next page
  1062. * @param int $range Near the current page, how many pages should
  1063. * be considered "nearby" and displayed as well?
  1064. * @param string $prompt The prompt to display (sometimes empty)
  1065. *
  1066. * @access public
  1067. */
  1068. public static function pageselector(
  1069. $name,
  1070. $rows,
  1071. $pageNow = 1,
  1072. $nbTotalPage = 1,
  1073. $showAll = 200,
  1074. $sliceStart = 5,
  1075. $sliceEnd = 5,
  1076. $percent = 20,
  1077. $range = 10,
  1078. $prompt = ''
  1079. ): string {
  1080. $increment = floor($nbTotalPage / $percent);
  1081. $pageNowMinusRange = $pageNow - $range;
  1082. $pageNowPlusRange = $pageNow + $range;
  1083. $gotopage = $prompt . ' <select class="pageselector ajax"';
  1084. $gotopage .= ' name="' . $name . '" >';
  1085. if ($nbTotalPage < $showAll) {
  1086. $pages = range(1, $nbTotalPage);
  1087. } else {
  1088. $pages = [];
  1089. // Always show first X pages
  1090. for ($i = 1; $i <= $sliceStart; $i++) {
  1091. $pages[] = $i;
  1092. }
  1093. // Always show last X pages
  1094. for ($i = $nbTotalPage - $sliceEnd; $i <= $nbTotalPage; $i++) {
  1095. $pages[] = $i;
  1096. }
  1097. // Based on the number of results we add the specified
  1098. // $percent percentage to each page number,
  1099. // so that we have a representing page number every now and then to
  1100. // immediately jump to specific pages.
  1101. // As soon as we get near our currently chosen page ($pageNow -
  1102. // $range), every page number will be shown.
  1103. $i = $sliceStart;
  1104. $x = $nbTotalPage - $sliceEnd;
  1105. $met_boundary = false;
  1106. while ($i <= $x) {
  1107. if ($i >= $pageNowMinusRange && $i <= $pageNowPlusRange) {
  1108. // If our pageselector comes near the current page, we use 1
  1109. // counter increments
  1110. $i++;
  1111. $met_boundary = true;
  1112. } else {
  1113. // We add the percentage increment to our current page to
  1114. // hop to the next one in range
  1115. $i += $increment;
  1116. // Make sure that we do not cross our boundaries.
  1117. if ($i > $pageNowMinusRange && ! $met_boundary) {
  1118. $i = $pageNowMinusRange;
  1119. }
  1120. }
  1121. if ($i <= 0 || $i > $x) {
  1122. continue;
  1123. }
  1124. $pages[] = $i;
  1125. }
  1126. /*
  1127. Add page numbers with "geometrically increasing" distances.
  1128. This helps me a lot when navigating through giant tables.
  1129. Test case: table with 2.28 million sets, 76190 pages. Page of interest
  1130. is between 72376 and 76190.
  1131. Selecting page 72376.
  1132. Now, old version enumerated only +/- 10 pages around 72376 and the
  1133. percentage increment produced steps of about 3000.
  1134. The following code adds page numbers +/- 2,4,8,16,32,64,128,256 etc.
  1135. around the current page.
  1136. */
  1137. $i = $pageNow;
  1138. $dist = 1;
  1139. while ($i < $x) {
  1140. $dist = 2 * $dist;
  1141. $i = $pageNow + $dist;
  1142. if ($i <= 0 || $i > $x) {
  1143. continue;
  1144. }
  1145. $pages[] = $i;
  1146. }
  1147. $i = $pageNow;
  1148. $dist = 1;
  1149. while ($i > 0) {
  1150. $dist = 2 * $dist;
  1151. $i = $pageNow - $dist;
  1152. if ($i <= 0 || $i > $x) {
  1153. continue;
  1154. }
  1155. $pages[] = $i;
  1156. }
  1157. // Since because of ellipsing of the current page some numbers may be
  1158. // double, we unify our array:
  1159. sort($pages);
  1160. $pages = array_unique($pages);
  1161. }
  1162. if ($pageNow > $nbTotalPage) {
  1163. $pages[] = $pageNow;
  1164. }
  1165. foreach ($pages as $i) {
  1166. if ($i == $pageNow) {
  1167. $selected = 'selected="selected" style="font-weight: bold"';
  1168. } else {
  1169. $selected = '';
  1170. }
  1171. $gotopage .= ' <option ' . $selected
  1172. . ' value="' . (($i - 1) * $rows) . '">' . $i . '</option>' . "\n";
  1173. }
  1174. $gotopage .= ' </select>';
  1175. return $gotopage;
  1176. }
  1177. /**
  1178. * Calculate page number through position
  1179. *
  1180. * @param int $pos position of first item
  1181. * @param int $max_count number of items per page
  1182. *
  1183. * @return int $page_num
  1184. *
  1185. * @access public
  1186. */
  1187. public static function getPageFromPosition($pos, $max_count)
  1188. {
  1189. return (int) floor($pos / $max_count) + 1;
  1190. }
  1191. /**
  1192. * replaces %u in given path with current user name
  1193. *
  1194. * example:
  1195. * <code>
  1196. * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/'
  1197. *
  1198. * </code>
  1199. *
  1200. * @param string $dir with wildcard for user
  1201. *
  1202. * @return string per user directory
  1203. */
  1204. public static function userDir($dir): string
  1205. {
  1206. // add trailing slash
  1207. if (mb_substr($dir, -1) !== '/') {
  1208. $dir .= '/';
  1209. }
  1210. return str_replace('%u', Core::securePath($GLOBALS['cfg']['Server']['user']), $dir);
  1211. }
  1212. /**
  1213. * Clears cache content which needs to be refreshed on user change.
  1214. */
  1215. public static function clearUserCache(): void
  1216. {
  1217. SessionCache::remove('is_superuser');
  1218. SessionCache::remove('is_createuser');
  1219. SessionCache::remove('is_grantuser');
  1220. }
  1221. /**
  1222. * Converts a bit value to printable format;
  1223. * in MySQL a BIT field can be from 1 to 64 bits so we need this
  1224. * function because in PHP, decbin() supports only 32 bits
  1225. * on 32-bit servers
  1226. *
  1227. * @param int $value coming from a BIT field
  1228. * @param int $length length
  1229. *
  1230. * @return string the printable value
  1231. */
  1232. public static function printableBitValue(int $value, int $length): string
  1233. {
  1234. // if running on a 64-bit server or the length is safe for decbin()
  1235. if (PHP_INT_SIZE == 8 || $length < 33) {
  1236. $printable = decbin($value);
  1237. } else {
  1238. // FIXME: does not work for the leftmost bit of a 64-bit value
  1239. $i = 0;
  1240. $printable = '';
  1241. while ($value >= pow(2, $i)) {
  1242. ++$i;
  1243. }
  1244. if ($i != 0) {
  1245. --$i;
  1246. }
  1247. while ($i >= 0) {
  1248. if ($value - pow(2, $i) < 0) {
  1249. $printable = '0' . $printable;
  1250. } else {
  1251. $printable = '1' . $printable;
  1252. $value -= pow(2, $i);
  1253. }
  1254. --$i;
  1255. }
  1256. $printable = strrev($printable);
  1257. }
  1258. $printable = str_pad($printable, $length, '0', STR_PAD_LEFT);
  1259. return $printable;
  1260. }
  1261. /**
  1262. * Converts a BIT type default value
  1263. * for example, b'010' becomes 010
  1264. *
  1265. * @param string|null $bitDefaultValue value
  1266. *
  1267. * @return string the converted value
  1268. */
  1269. public static function convertBitDefaultValue(?string $bitDefaultValue): string
  1270. {
  1271. return (string) preg_replace(
  1272. "/^b'(\d*)'?$/",
  1273. '$1',
  1274. htmlspecialchars_decode((string) $bitDefaultValue, ENT_QUOTES),
  1275. 1
  1276. );
  1277. }
  1278. /**
  1279. * Extracts the various parts from a column spec
  1280. *
  1281. * @param string $columnspec Column specification
  1282. *
  1283. * @return array associative array containing type, spec_in_brackets
  1284. * and possibly enum_set_values (another array)
  1285. */
  1286. public static function extractColumnSpec($columnspec)
  1287. {
  1288. $first_bracket_pos = mb_strpos($columnspec, '(');
  1289. if ($first_bracket_pos) {
  1290. $spec_in_brackets = rtrim(
  1291. mb_substr(
  1292. $columnspec,
  1293. $first_bracket_pos + 1,
  1294. mb_strrpos($columnspec, ')') - $first_bracket_pos - 1
  1295. )
  1296. );
  1297. // convert to lowercase just to be sure
  1298. $type = mb_strtolower(
  1299. rtrim(mb_substr($columnspec, 0, $first_bracket_pos))
  1300. );
  1301. } else {
  1302. // Split trailing attributes such as unsigned,
  1303. // binary, zerofill and get data type name
  1304. $type_parts = explode(' ', $columnspec);
  1305. $type = mb_strtolower($type_parts[0]);
  1306. $spec_in_brackets = '';
  1307. }
  1308. if ($type === 'enum' || $type === 'set') {
  1309. // Define our working vars
  1310. $enum_set_values = self::parseEnumSetValues($columnspec, false);
  1311. $printtype = $type
  1312. . '(' . str_replace("','", "', '", $spec_in_brackets) . ')';
  1313. $binary = false;
  1314. $unsigned = false;
  1315. $zerofill = false;
  1316. } else {
  1317. $enum_set_values = [];
  1318. /* Create printable type name */
  1319. $printtype = mb_strtolower($columnspec);
  1320. // Strip the "BINARY" attribute, except if we find "BINARY(" because
  1321. // this would be a BINARY or VARBINARY column type;
  1322. // by the way, a BLOB should not show the BINARY attribute
  1323. // because this is not accepted in MySQL syntax.
  1324. if (strpos($printtype, 'binary') !== false
  1325. && ! preg_match('@binary[\(]@', $printtype)
  1326. ) {
  1327. $printtype = str_replace('binary', '', $printtype);
  1328. $binary = true;
  1329. } else {
  1330. $binary = false;
  1331. }
  1332. $printtype = (string) preg_replace(
  1333. '@zerofill@',
  1334. '',
  1335. $printtype,
  1336. -1,
  1337. $zerofill_cnt
  1338. );
  1339. $zerofill = ($zerofill_cnt > 0);
  1340. $printtype = (string) preg_replace(
  1341. '@unsigned@',
  1342. '',
  1343. $printtype,
  1344. -1,
  1345. $unsigned_cnt
  1346. );
  1347. $unsigned = ($unsigned_cnt > 0);
  1348. $printtype = trim($printtype);
  1349. }
  1350. $attribute = ' ';
  1351. if ($binary) {
  1352. $attribute = 'BINARY';
  1353. }
  1354. if ($unsigned) {
  1355. $attribute = 'UNSIGNED';
  1356. }
  1357. if ($zerofill) {
  1358. $attribute = 'UNSIGNED ZEROFILL';
  1359. }
  1360. $can_contain_collation = false;
  1361. if (! $binary
  1362. && preg_match(
  1363. '@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@',
  1364. $type
  1365. )
  1366. ) {
  1367. $can_contain_collation = true;
  1368. }
  1369. // for the case ENUM('&#8211;','&ldquo;')
  1370. $displayed_type = htmlspecialchars($printtype, ENT_COMPAT);
  1371. if (mb_strlen($printtype) > $GLOBALS['cfg']['LimitChars']) {
  1372. $displayed_type = '<abbr title="' . htmlspecialchars($printtype) . '">';
  1373. $displayed_type .= htmlspecialchars(
  1374. mb_substr(
  1375. $printtype,
  1376. 0,
  1377. (int) $GLOBALS['cfg']['LimitChars']
  1378. ) . '...',
  1379. ENT_COMPAT
  1380. );
  1381. $displayed_type .= '</abbr>';
  1382. }
  1383. return [
  1384. 'type' => $type,
  1385. 'spec_in_brackets' => $spec_in_brackets,
  1386. 'enum_set_values' => $enum_set_values,
  1387. 'print_type' => $printtype,
  1388. 'binary' => $binary,
  1389. 'unsigned' => $unsigned,
  1390. 'zerofill' => $zerofill,
  1391. 'attribute' => $attribute,
  1392. 'can_contain_collation' => $can_contain_collation,
  1393. 'displayed_type' => $displayed_type,
  1394. ];
  1395. }
  1396. /**
  1397. * Verifies if this table's engine supports foreign keys
  1398. *
  1399. * @param string $engine engine
  1400. */
  1401. public static function isForeignKeySupported($engine): bool
  1402. {
  1403. global $dbi;
  1404. $engine = strtoupper((string) $engine);
  1405. if (($engine === 'INNODB') || ($engine === 'PBXT')) {
  1406. return true;
  1407. }
  1408. if ($engine === 'NDBCLUSTER' || $engine === 'NDB') {
  1409. $ndbver = strtolower(
  1410. $dbi->fetchValue('SELECT @@ndb_version_string')
  1411. );
  1412. if (substr($ndbver, 0, 4) === 'ndb-') {
  1413. $ndbver = substr($ndbver, 4);
  1414. }
  1415. return version_compare($ndbver, '7.3', '>=');
  1416. }
  1417. return false;
  1418. }
  1419. /**
  1420. * Is Foreign key check enabled?
  1421. */
  1422. public static function isForeignKeyCheck(): bool
  1423. {
  1424. global $dbi;
  1425. if ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'enable') {
  1426. return true;
  1427. }
  1428. if ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'disable') {
  1429. return false;
  1430. }
  1431. return $dbi->getVariable('FOREIGN_KEY_CHECKS') === 'ON';
  1432. }
  1433. /**
  1434. * Handle foreign key check request
  1435. *
  1436. * @return bool Default foreign key checks value
  1437. */
  1438. public static function handleDisableFKCheckInit(): bool
  1439. {
  1440. /** @var DatabaseInterface $dbi */
  1441. global $dbi;
  1442. $default_fk_check_value = $dbi->getVariable('FOREIGN_KEY_CHECKS') === 'ON';
  1443. if (isset($_REQUEST['fk_checks'])) {
  1444. if (empty($_REQUEST['fk_checks'])) {
  1445. // Disable foreign key checks
  1446. $dbi->setVariable('FOREIGN_KEY_CHECKS', 'OFF');
  1447. } else {
  1448. // Enable foreign key checks
  1449. $dbi->setVariable('FOREIGN_KEY_CHECKS', 'ON');
  1450. }
  1451. }
  1452. return $default_fk_check_value;
  1453. }
  1454. /**
  1455. * Cleanup changes done for foreign key check
  1456. *
  1457. * @param bool $default_fk_check_value original value for 'FOREIGN_KEY_CHECKS'
  1458. */
  1459. public static function handleDisableFKCheckCleanup(bool $default_fk_check_value): void
  1460. {
  1461. /** @var DatabaseInterface $dbi */
  1462. global $dbi;
  1463. $dbi->setVariable(
  1464. 'FOREIGN_KEY_CHECKS',
  1465. $default_fk_check_value ? 'ON' : 'OFF'
  1466. );
  1467. }
  1468. /**
  1469. * Converts GIS data to Well Known Text format
  1470. *
  1471. * @param string $data GIS data
  1472. * @param bool $includeSRID Add SRID to the WKT
  1473. *
  1474. * @return string GIS data in Well Know Text format
  1475. */
  1476. public static function asWKT($data, $includeSRID = false)
  1477. {
  1478. global $dbi;
  1479. // Convert to WKT format
  1480. $hex = bin2hex($data);
  1481. $spatialAsText = 'ASTEXT';
  1482. $spatialSrid = 'SRID';
  1483. $axisOrder = '';
  1484. $mysqlVersionInt = $dbi->getVersion();
  1485. if ($mysqlVersionInt >= 50600) {
  1486. $spatialAsText = 'ST_ASTEXT';
  1487. $spatialSrid = 'ST_SRID';
  1488. }
  1489. if ($mysqlVersionInt >= 80010 && ! $dbi->isMariaDb()) {
  1490. $axisOrder = ', \'axis-order=long-lat\'';
  1491. }
  1492. $wktsql = 'SELECT ' . $spatialAsText . "(x'" . $hex . "'" . $axisOrder . ')';
  1493. if ($includeSRID) {
  1494. $wktsql .= ', ' . $spatialSrid . "(x'" . $hex . "')";
  1495. }
  1496. $wktresult = $dbi->tryQuery(
  1497. $wktsql
  1498. );
  1499. $wktarr = $dbi->fetchRow($wktresult, 0);
  1500. $wktval = $wktarr[0] ?? null;
  1501. if ($includeSRID) {
  1502. $srid = $wktarr[1] ?? null;
  1503. $wktval = "'" . $wktval . "'," . $srid;
  1504. }
  1505. @$dbi->freeResult($wktresult);
  1506. return $wktval;
  1507. }
  1508. /**
  1509. * If the string starts with a \r\n pair (0x0d0a) add an extra \n
  1510. *
  1511. * @param string $string string
  1512. *
  1513. * @return string with the chars replaced
  1514. */
  1515. public static function duplicateFirstNewline(string $string): string
  1516. {
  1517. $first_occurence = mb_strpos($string, "\r\n");
  1518. if ($first_occurence === 0) {
  1519. $string = "\n" . $string;
  1520. }
  1521. return $string;
  1522. }
  1523. /**
  1524. * Get the action word corresponding to a script name
  1525. * in order to display it as a title in navigation panel
  1526. *
  1527. * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'],
  1528. * $cfg['NavigationTreeDefaultTabTable2'],
  1529. * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase']
  1530. *
  1531. * @return string|bool Title for the $cfg value
  1532. */
  1533. public static function getTitleForTarget($target)
  1534. {
  1535. $mapping = [
  1536. 'structure' => __('Structure'),
  1537. 'sql' => __('SQL'),
  1538. 'search' => __('Search'),
  1539. 'insert' => __('Insert'),
  1540. 'browse' => __('Browse'),
  1541. 'operations' => __('Operations'),
  1542. ];
  1543. return $mapping[$target] ?? false;
  1544. }
  1545. /**
  1546. * Get the script name corresponding to a plain English config word
  1547. * in order to append in links on navigation and main panel
  1548. *
  1549. * @param string $target a valid value for
  1550. * $cfg['NavigationTreeDefaultTabTable'],
  1551. * $cfg['NavigationTreeDefaultTabTable2'],
  1552. * $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or
  1553. * $cfg['DefaultTabServer']
  1554. * @param string $location one out of 'server', 'table', 'database'
  1555. *
  1556. * @return string script name corresponding to the config word
  1557. */
  1558. public static function getScriptNameForOption($target, string $location): string
  1559. {
  1560. $url = self::getUrlForOption($target, $location);
  1561. if ($url === null) {
  1562. return './';
  1563. }
  1564. return Url::getFromRoute($url);
  1565. }
  1566. /**
  1567. * Get the URL corresponding to a plain English config word
  1568. * in order to append in links on navigation and main panel
  1569. *
  1570. * @param string $target a valid value for
  1571. * $cfg['NavigationTreeDefaultTabTable'],
  1572. * $cfg['NavigationTreeDefaultTabTable2'],
  1573. * $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or
  1574. * $cfg['DefaultTabServer']
  1575. * @param string $location one out of 'server', 'table', 'database'
  1576. *
  1577. * @return string The URL corresponding to the config word or null if nothing was found
  1578. */
  1579. public static function getUrlForOption($target, string $location): ?string
  1580. {
  1581. if ($location === 'server') {
  1582. // Values for $cfg['DefaultTabServer']
  1583. switch ($target) {
  1584. case 'welcome':
  1585. case 'index.php':
  1586. return '/';
  1587. case 'databases':
  1588. case 'server_databases.php':
  1589. return '/server/databases';
  1590. case 'status':
  1591. case 'server_status.php':
  1592. return '/server/status';
  1593. case 'variables':
  1594. case 'server_variables.php':
  1595. return '/server/variables';
  1596. case 'privileges':
  1597. case 'server_privileges.php':
  1598. return '/server/privileges';
  1599. }
  1600. } elseif ($location === 'database') {
  1601. // Values for $cfg['DefaultTabDatabase']
  1602. switch ($target) {
  1603. case 'structure':
  1604. case 'db_structure.php':
  1605. return '/database/structure';
  1606. case 'sql':
  1607. case 'db_sql.php':
  1608. return '/database/sql';
  1609. case 'search':
  1610. case 'db_search.php':
  1611. return '/database/search';
  1612. case 'operations':
  1613. case 'db_operations.php':
  1614. return '/database/operations';
  1615. }
  1616. } elseif ($location === 'table') {
  1617. // Values for $cfg['DefaultTabTable'],
  1618. // $cfg['NavigationTreeDefaultTabTable'] and
  1619. // $cfg['NavigationTreeDefaultTabTable2']
  1620. switch ($target) {
  1621. case 'structure':
  1622. case 'tbl_structure.php':
  1623. return '/table/structure';
  1624. case 'sql':
  1625. case 'tbl_sql.php':
  1626. return '/table/sql';
  1627. case 'search':
  1628. case 'tbl_select.php':
  1629. return '/table/search';
  1630. case 'insert':
  1631. case 'tbl_change.php':
  1632. return '/table/change';
  1633. case 'browse':
  1634. case 'sql.php':
  1635. return '/sql';
  1636. }
  1637. }
  1638. return null;
  1639. }
  1640. /**
  1641. * Formats user string, expanding @VARIABLES@, accepting strftime format
  1642. * string.
  1643. *
  1644. * @param string $string Text where to do expansion.
  1645. * @param array|string $escape Function to call for escaping variable values.
  1646. * Can also be an array of:
  1647. * - the escape method name
  1648. * - the class that contains the method
  1649. * - location of the class (for inclusion)
  1650. * @param array $updates Array with overrides for default parameters
  1651. * (obtained from GLOBALS).
  1652. *
  1653. * @return string
  1654. */
  1655. public static function expandUserString(
  1656. $string,
  1657. $escape = null,
  1658. array $updates = []
  1659. ) {
  1660. global $dbi;
  1661. /* Content */
  1662. $vars = [];
  1663. $vars['http_host'] = Core::getenv('HTTP_HOST');
  1664. $vars['server_name'] = $GLOBALS['cfg']['Server']['host'];
  1665. $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose'];
  1666. if (empty($GLOBALS['cfg']['Server']['verbose'])) {
  1667. $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host'];
  1668. } else {
  1669. $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose'];
  1670. }
  1671. $vars['database'] = $GLOBALS['db'];
  1672. $vars['table'] = $GLOBALS['table'];
  1673. $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION;
  1674. /* Update forced variables */
  1675. foreach ($updates as $key => $val) {
  1676. $vars[$key] = $val;
  1677. }
  1678. /* Replacement mapping */
  1679. /*
  1680. * The __VAR__ ones are for backward compatibility, because user
  1681. * might still have it in cookies.
  1682. */
  1683. $replace = [
  1684. '@HTTP_HOST@' => $vars['http_host'],
  1685. '@SERVER@' => $vars['server_name'],
  1686. '__SERVER__' => $vars['server_name'],
  1687. '@VERBOSE@' => $vars['server_verbose'],
  1688. '@VSERVER@' => $vars['server_verbose_or_name'],
  1689. '@DATABASE@' => $vars['database'],
  1690. '__DB__' => $vars['database'],
  1691. '@TABLE@' => $vars['table'],
  1692. '__TABLE__' => $vars['table'],
  1693. '@PHPMYADMIN@' => $vars['phpmyadmin_version'],
  1694. ];
  1695. /* Optional escaping */
  1696. if ($escape !== null) {
  1697. if (is_array($escape)) {
  1698. $escape_class = new $escape[1]();
  1699. $escape_method = $escape[0];
  1700. }
  1701. foreach ($replace as $key => $val) {
  1702. if (isset($escape_class, $escape_method)) {
  1703. $replace[$key] = $escape_class->$escape_method($val);
  1704. } elseif ($escape === 'backquote') {
  1705. $replace[$key] = self::backquote($val);
  1706. } elseif (is_callable($escape)) {
  1707. $replace[$key] = $escape($val);
  1708. }
  1709. }
  1710. }
  1711. /* Backward compatibility in 3.5.x */
  1712. if (mb_strpos($string, '@FIELDS@') !== false) {
  1713. $string = strtr($string, ['@FIELDS@' => '@COLUMNS@']);
  1714. }
  1715. /* Fetch columns list if required */
  1716. if (mb_strpos($string, '@COLUMNS@') !== false) {
  1717. $columns_list = $dbi->getColumns(
  1718. $GLOBALS['db'],
  1719. $GLOBALS['table']
  1720. );
  1721. // sometimes the table no longer exists at this point
  1722. if ($columns_list !== null) {
  1723. $column_names = [];
  1724. foreach ($columns_list as $column) {
  1725. if ($escape !== null) {
  1726. $column_names[] = self::$escape($column['Field']);
  1727. } else {
  1728. $column_names[] = $column['Field'];
  1729. }
  1730. }
  1731. $replace['@COLUMNS@'] = implode(',', $column_names);
  1732. } else {
  1733. $replace['@COLUMNS@'] = '*';
  1734. }
  1735. }
  1736. /* Do the replacement */
  1737. return strtr((string) strftime($string), $replace);
  1738. }
  1739. /**
  1740. * This function processes the datatypes supported by the DB,
  1741. * as specified in Types->getColumns() and either returns an array
  1742. * (useful for quickly checking if a datatype is supported)
  1743. * or an HTML snippet that creates a drop-down list.
  1744. *
  1745. * @param bool $html Whether to generate an html snippet or an array
  1746. * @param string $selected The value to mark as selected in HTML mode
  1747. *
  1748. * @return mixed An HTML snippet or an array of datatypes.
  1749. */
  1750. public static function getSupportedDatatypes($html = false, $selected = '')
  1751. {
  1752. global $dbi;
  1753. if ($html) {
  1754. $retval = Generator::getSupportedDatatypes($selected);
  1755. } else {
  1756. $retval = [];
  1757. foreach ($dbi->types->getColumns() as $value) {
  1758. if (is_array($value)) {
  1759. foreach ($value as $subvalue) {
  1760. if ($subvalue === '-') {
  1761. continue;
  1762. }
  1763. $retval[] = $subvalue;
  1764. }
  1765. } else {
  1766. if ($value !== '-') {
  1767. $retval[] = $value;
  1768. }
  1769. }
  1770. }
  1771. }
  1772. return $retval;
  1773. }
  1774. /**
  1775. * Returns a list of datatypes that are not (yet) handled by PMA.
  1776. * Used by: /table/change and libraries/Routines.php
  1777. *
  1778. * @return array list of datatypes
  1779. */
  1780. public static function unsupportedDatatypes(): array
  1781. {
  1782. return [];
  1783. }
  1784. /**
  1785. * Return GIS data types
  1786. *
  1787. * @param bool $upper_case whether to return values in upper case
  1788. *
  1789. * @return string[] GIS data types
  1790. */
  1791. public static function getGISDatatypes($upper_case = false): array
  1792. {
  1793. $gis_data_types = [
  1794. 'geometry',
  1795. 'point',
  1796. 'linestring',
  1797. 'polygon',
  1798. 'multipoint',
  1799. 'multilinestring',
  1800. 'multipolygon',
  1801. 'geometrycollection',
  1802. ];
  1803. if ($upper_case) {
  1804. $gis_data_types = array_map('mb_strtoupper', $gis_data_types);
  1805. }
  1806. return $gis_data_types;
  1807. }
  1808. /**
  1809. * Generates GIS data based on the string passed.
  1810. *
  1811. * @param string $gis_string GIS string
  1812. * @param int $mysqlVersion The mysql version as int
  1813. *
  1814. * @return string GIS data enclosed in 'ST_GeomFromText' or 'GeomFromText' function
  1815. */
  1816. public static function createGISData($gis_string, $mysqlVersion)
  1817. {
  1818. $geomFromText = $mysqlVersion >= 50600 ? 'ST_GeomFromText' : 'GeomFromText';
  1819. $gis_string = trim($gis_string);
  1820. $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING|'
  1821. . 'POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)';
  1822. if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $gis_string)) {
  1823. return $geomFromText . '(' . $gis_string . ')';
  1824. }
  1825. if (preg_match('/^' . $geom_types . '\(.*\)$/i', $gis_string)) {
  1826. return $geomFromText . "('" . $gis_string . "')";
  1827. }
  1828. return $gis_string;
  1829. }
  1830. /**
  1831. * Returns the names and details of the functions
  1832. * that can be applied on geometry data types.
  1833. *
  1834. * @param string $geom_type if provided the output is limited to the functions
  1835. * that are applicable to the provided geometry type.
  1836. * @param bool $binary if set to false functions that take two geometries
  1837. * as arguments will not be included.
  1838. * @param bool $display if set to true separators will be added to the
  1839. * output array.
  1840. *
  1841. * @return array<int|string,array<string,int|string>> names and details of the functions that can be applied on
  1842. * geometry data types.
  1843. */
  1844. public static function getGISFunctions(
  1845. $geom_type = null,
  1846. $binary = true,
  1847. $display = false
  1848. ): array {
  1849. global $dbi;
  1850. $funcs = [];
  1851. if ($display) {
  1852. $funcs[] = ['display' => ' '];
  1853. }
  1854. // Unary functions common to all geometry types
  1855. $funcs['Dimension'] = [
  1856. 'params' => 1,
  1857. 'type' => 'int',
  1858. ];
  1859. $funcs['Envelope'] = [
  1860. 'params' => 1,
  1861. 'type' => 'Polygon',
  1862. ];
  1863. $funcs['GeometryType'] = [
  1864. 'params' => 1,
  1865. 'type' => 'text',
  1866. ];
  1867. $funcs['SRID'] = [
  1868. 'params' => 1,
  1869. 'type' => 'int',
  1870. ];
  1871. $funcs['IsEmpty'] = [
  1872. 'params' => 1,
  1873. 'type' => 'int',
  1874. ];
  1875. $funcs['IsSimple'] = [
  1876. 'params' => 1,
  1877. 'type' => 'int',
  1878. ];
  1879. $geom_type = mb_strtolower(trim((string) $geom_type));
  1880. if ($display && $geom_type !== 'geometry' && $geom_type !== 'multipoint') {
  1881. $funcs[] = ['display' => '--------'];
  1882. }
  1883. // Unary functions that are specific to each geometry type
  1884. if ($geom_type === 'point') {
  1885. $funcs['X'] = [
  1886. 'params' => 1,
  1887. 'type' => 'float',
  1888. ];
  1889. $funcs['Y'] = [
  1890. 'params' => 1,
  1891. 'type' => 'float',
  1892. ];
  1893. } elseif ($geom_type === 'linestring') {
  1894. $funcs['EndPoint'] = [
  1895. 'params' => 1,
  1896. 'type' => 'point',
  1897. ];
  1898. $funcs['GLength'] = [
  1899. 'params' => 1,
  1900. 'type' => 'float',
  1901. ];
  1902. $funcs['NumPoints'] = [
  1903. 'params' => 1,
  1904. 'type' => 'int',
  1905. ];
  1906. $funcs['StartPoint'] = [
  1907. 'params' => 1,
  1908. 'type' => 'point',
  1909. ];
  1910. $funcs['IsRing'] = [
  1911. 'params' => 1,
  1912. 'type' => 'int',
  1913. ];
  1914. } elseif ($geom_type === 'multilinestring') {
  1915. $funcs['GLength'] = [
  1916. 'params' => 1,
  1917. 'type' => 'float',
  1918. ];
  1919. $funcs['IsClosed'] = [
  1920. 'params' => 1,
  1921. 'type' => 'int',
  1922. ];
  1923. } elseif ($geom_type === 'polygon') {
  1924. $funcs['Area'] = [
  1925. 'params' => 1,
  1926. 'type' => 'float',
  1927. ];
  1928. $funcs['ExteriorRing'] = [
  1929. 'params' => 1,
  1930. 'type' => 'linestring',
  1931. ];
  1932. $funcs['NumInteriorRings'] = [
  1933. 'params' => 1,
  1934. 'type' => 'int',
  1935. ];
  1936. } elseif ($geom_type === 'multipolygon') {
  1937. $funcs['Area'] = [
  1938. 'params' => 1,
  1939. 'type' => 'float',
  1940. ];
  1941. $funcs['Centroid'] = [
  1942. 'params' => 1,
  1943. 'type' => 'point',
  1944. ];
  1945. // Not yet implemented in MySQL
  1946. //$funcs['PointOnSurface'] = array('params' => 1, 'type' => 'point');
  1947. } elseif ($geom_type === 'geometrycollection') {
  1948. $funcs['NumGeometries'] = [
  1949. 'params' => 1,
  1950. 'type' => 'int',
  1951. ];
  1952. }
  1953. // If we are asked for binary functions as well
  1954. if ($binary) {
  1955. // section separator
  1956. if ($display) {
  1957. $funcs[] = ['display' => '--------'];
  1958. }
  1959. $spatialPrefix = '';
  1960. if ($dbi->getVersion() >= 50601) {
  1961. // If MySQL version is greater than or equal 5.6.1,
  1962. // use the ST_ prefix.
  1963. $spatialPrefix = 'ST_';
  1964. }
  1965. $funcs[$spatialPrefix . 'Crosses'] = [
  1966. 'params' => 2,
  1967. 'type' => 'int',
  1968. ];
  1969. $funcs[$spatialPrefix . 'Contains'] = [
  1970. 'params' => 2,
  1971. 'type' => 'int',
  1972. ];
  1973. $funcs[$spatialPrefix . 'Disjoint'] = [
  1974. 'params' => 2,
  1975. 'type' => 'int',
  1976. ];
  1977. $funcs[$spatialPrefix . 'Equals'] = [
  1978. 'params' => 2,
  1979. 'type' => 'int',
  1980. ];
  1981. $funcs[$spatialPrefix . 'Intersects'] = [
  1982. 'params' => 2,
  1983. 'type' => 'int',
  1984. ];
  1985. $funcs[$spatialPrefix . 'Overlaps'] = [
  1986. 'params' => 2,
  1987. 'type' => 'int',
  1988. ];
  1989. $funcs[$spatialPrefix . 'Touches'] = [
  1990. 'params' => 2,
  1991. 'type' => 'int',
  1992. ];
  1993. $funcs[$spatialPrefix . 'Within'] = [
  1994. 'params' => 2,
  1995. 'type' => 'int',
  1996. ];
  1997. if ($display) {
  1998. $funcs[] = ['display' => '--------'];
  1999. }
  2000. // Minimum bounding rectangle functions
  2001. $funcs['MBRContains'] = [
  2002. 'params' => 2,
  2003. 'type' => 'int',
  2004. ];
  2005. $funcs['MBRDisjoint'] = [
  2006. 'params' => 2,
  2007. 'type' => 'int',
  2008. ];
  2009. $funcs['MBREquals'] = [
  2010. 'params' => 2,
  2011. 'type' => 'int',
  2012. ];
  2013. $funcs['MBRIntersects'] = [
  2014. 'params' => 2,
  2015. 'type' => 'int',
  2016. ];
  2017. $funcs['MBROverlaps'] = [
  2018. 'params' => 2,
  2019. 'type' => 'int',
  2020. ];
  2021. $funcs['MBRTouches'] = [
  2022. 'params' => 2,
  2023. 'type' => 'int',
  2024. ];
  2025. $funcs['MBRWithin'] = [
  2026. 'params' => 2,
  2027. 'type' => 'int',
  2028. ];
  2029. }
  2030. return $funcs;
  2031. }
  2032. /**
  2033. * Checks if the current user has a specific privilege and returns true if the
  2034. * user indeed has that privilege or false if they don't. This function must
  2035. * only be used for features that are available since MySQL 5, because it
  2036. * relies on the INFORMATION_SCHEMA database to be present.
  2037. *
  2038. * Example: currentUserHasPrivilege('CREATE ROUTINE', 'mydb');
  2039. * // Checks if the currently logged in user has the global
  2040. * // 'CREATE ROUTINE' privilege or, if not, checks if the
  2041. * // user has this privilege on database 'mydb'.
  2042. *
  2043. * @param string $priv The privilege to check
  2044. * @param string|null $db null, to only check global privileges
  2045. * string, db name where to also check
  2046. * for privileges
  2047. * @param string|null $tbl null, to only check global/db privileges
  2048. * string, table name where to also check
  2049. * for privileges
  2050. */
  2051. public static function currentUserHasPrivilege(string $priv, ?string $db = null, ?string $tbl = null): bool
  2052. {
  2053. global $dbi;
  2054. // Get the username for the current user in the format
  2055. // required to use in the information schema database.
  2056. [$user, $host] = $dbi->getCurrentUserAndHost();
  2057. // MySQL is started with --skip-grant-tables
  2058. if ($user === '') {
  2059. return true;
  2060. }
  2061. $username = "''";
  2062. $username .= str_replace("'", "''", $user);
  2063. $username .= "''@''";
  2064. $username .= str_replace("'", "''", $host);
  2065. $username .= "''";
  2066. // Prepare the query
  2067. $query = 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`%s` '
  2068. . "WHERE GRANTEE='%s' AND PRIVILEGE_TYPE='%s'";
  2069. // Check global privileges first.
  2070. $user_privileges = $dbi->fetchValue(
  2071. sprintf(
  2072. $query,
  2073. 'USER_PRIVILEGES',
  2074. $username,
  2075. $priv
  2076. )
  2077. );
  2078. if ($user_privileges) {
  2079. return true;
  2080. }
  2081. // If a database name was provided and user does not have the
  2082. // required global privilege, try database-wise permissions.
  2083. if ($db === null) {
  2084. // There was no database name provided and the user
  2085. // does not have the correct global privilege.
  2086. return false;
  2087. }
  2088. $query .= " AND '%s' LIKE `TABLE_SCHEMA`";
  2089. $schema_privileges = $dbi->fetchValue(
  2090. sprintf(
  2091. $query,
  2092. 'SCHEMA_PRIVILEGES',
  2093. $username,
  2094. $priv,
  2095. $dbi->escapeString($db)
  2096. )
  2097. );
  2098. if ($schema_privileges) {
  2099. return true;
  2100. }
  2101. // If a table name was also provided and we still didn't
  2102. // find any valid privileges, try table-wise privileges.
  2103. if ($tbl !== null) {
  2104. $query .= " AND TABLE_NAME='%s'";
  2105. $table_privileges = $dbi->fetchValue(
  2106. sprintf(
  2107. $query,
  2108. 'TABLE_PRIVILEGES',
  2109. $username,
  2110. $priv,
  2111. $dbi->escapeString($db),
  2112. $dbi->escapeString($tbl)
  2113. )
  2114. );
  2115. if ($table_privileges) {
  2116. return true;
  2117. }
  2118. }
  2119. /**
  2120. * If we reached this point, the user does not
  2121. * have even valid table-wise privileges.
  2122. */
  2123. return false;
  2124. }
  2125. /**
  2126. * Returns server type for current connection
  2127. *
  2128. * Known types are: MariaDB, PerconaDB and MySQL (default)
  2129. *
  2130. * @return string
  2131. */
  2132. public static function getServerType()
  2133. {
  2134. global $dbi;
  2135. if ($dbi->isMariaDB()) {
  2136. return 'MariaDB';
  2137. }
  2138. if ($dbi->isPercona()) {
  2139. return 'Percona Server';
  2140. }
  2141. return 'MySQL';
  2142. }
  2143. /**
  2144. * Parses ENUM/SET values
  2145. *
  2146. * @param string $definition The definition of the column
  2147. * for which to parse the values
  2148. * @param bool $escapeHtml Whether to escape html entities
  2149. *
  2150. * @return array
  2151. */
  2152. public static function parseEnumSetValues($definition, $escapeHtml = true)
  2153. {
  2154. $values_string = htmlentities($definition, ENT_COMPAT, 'UTF-8');
  2155. // There is a JS port of the below parser in functions.js
  2156. // If you are fixing something here,
  2157. // you need to also update the JS port.
  2158. $values = [];
  2159. $in_string = false;
  2160. $buffer = '';
  2161. for ($i = 0, $length = mb_strlen($values_string); $i < $length; $i++) {
  2162. $curr = mb_substr($values_string, $i, 1);
  2163. $next = $i == mb_strlen($values_string) - 1
  2164. ? ''
  2165. : mb_substr($values_string, $i + 1, 1);
  2166. if (! $in_string && $curr == "'") {
  2167. $in_string = true;
  2168. } elseif (($in_string && $curr === '\\') && $next === '\\') {
  2169. $buffer .= '&#92;';
  2170. $i++;
  2171. } elseif (($in_string && $next == "'")
  2172. && ($curr == "'" || $curr === '\\')
  2173. ) {
  2174. $buffer .= '&#39;';
  2175. $i++;
  2176. } elseif ($in_string && $curr == "'") {
  2177. $in_string = false;
  2178. $values[] = $buffer;
  2179. $buffer = '';
  2180. } elseif ($in_string) {
  2181. $buffer .= $curr;
  2182. }
  2183. }
  2184. if (strlen($buffer) > 0) {
  2185. // The leftovers in the buffer are the last value (if any)
  2186. $values[] = $buffer;
  2187. }
  2188. if (! $escapeHtml) {
  2189. foreach ($values as $key => $value) {
  2190. $values[$key] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
  2191. }
  2192. }
  2193. return $values;
  2194. }
  2195. /**
  2196. * Get regular expression which occur first inside the given sql query.
  2197. *
  2198. * @param array $regex_array Comparing regular expressions.
  2199. * @param string $query SQL query to be checked.
  2200. *
  2201. * @return string Matching regular expression.
  2202. */
  2203. public static function getFirstOccurringRegularExpression(array $regex_array, $query): string
  2204. {
  2205. $minimum_first_occurence_index = null;
  2206. $regex = null;
  2207. foreach ($regex_array as $test_regex) {
  2208. if (! preg_match($test_regex, $query, $matches, PREG_OFFSET_CAPTURE)) {
  2209. continue;
  2210. }
  2211. if ($minimum_first_occurence_index !== null
  2212. && ($matches[0][1] >= $minimum_first_occurence_index)
  2213. ) {
  2214. continue;
  2215. }
  2216. $regex = $test_regex;
  2217. $minimum_first_occurence_index = $matches[0][1];
  2218. }
  2219. return $regex;
  2220. }
  2221. /**
  2222. * Return the list of tabs for the menu with corresponding names
  2223. *
  2224. * @param string $level 'server', 'db' or 'table' level
  2225. *
  2226. * @return array|null list of tabs for the menu
  2227. */
  2228. public static function getMenuTabList($level = null)
  2229. {
  2230. $tabList = [
  2231. 'server' => [
  2232. 'databases' => __('Databases'),
  2233. 'sql' => __('SQL'),
  2234. 'status' => __('Status'),
  2235. 'rights' => __('Users'),
  2236. 'export' => __('Export'),
  2237. 'import' => __('Import'),
  2238. 'settings' => __('Settings'),
  2239. 'binlog' => __('Binary log'),
  2240. 'replication' => __('Replication'),
  2241. 'vars' => __('Variables'),
  2242. 'charset' => __('Charsets'),
  2243. 'plugins' => __('Plugins'),
  2244. 'engine' => __('Engines'),
  2245. ],
  2246. 'db' => [
  2247. 'structure' => __('Structure'),
  2248. 'sql' => __('SQL'),
  2249. 'search' => __('Search'),
  2250. 'query' => __('Query'),
  2251. 'export' => __('Export'),
  2252. 'import' => __('Import'),
  2253. 'operation' => __('Operations'),
  2254. 'privileges' => __('Privileges'),
  2255. 'routines' => __('Routines'),
  2256. 'events' => __('Events'),
  2257. 'triggers' => __('Triggers'),
  2258. 'tracking' => __('Tracking'),
  2259. 'designer' => __('Designer'),
  2260. 'central_columns' => __('Central columns'),
  2261. ],
  2262. 'table' => [
  2263. 'browse' => __('Browse'),
  2264. 'structure' => __('Structure'),
  2265. 'sql' => __('SQL'),
  2266. 'search' => __('Search'),
  2267. 'insert' => __('Insert'),
  2268. 'export' => __('Export'),
  2269. 'import' => __('Import'),
  2270. 'privileges' => __('Privileges'),
  2271. 'operation' => __('Operations'),
  2272. 'tracking' => __('Tracking'),
  2273. 'triggers' => __('Triggers'),
  2274. ],
  2275. ];
  2276. if ($level == null) {
  2277. return $tabList;
  2278. }
  2279. if (array_key_exists($level, $tabList)) {
  2280. return $tabList[$level];
  2281. }
  2282. return null;
  2283. }
  2284. /**
  2285. * Add fractional seconds to time, datetime and timestamp strings.
  2286. * If the string contains fractional seconds,
  2287. * pads it with 0s up to 6 decimal places.
  2288. *
  2289. * @param string $value time, datetime or timestamp strings
  2290. *
  2291. * @return string time, datetime or timestamp strings with fractional seconds
  2292. */
  2293. public static function addMicroseconds($value)
  2294. {
  2295. if (empty($value) || $value === 'CURRENT_TIMESTAMP'
  2296. || $value === 'current_timestamp()'
  2297. ) {
  2298. return $value;
  2299. }
  2300. if (mb_strpos($value, '.') === false) {
  2301. return $value . '.000000';
  2302. }
  2303. $value .= '000000';
  2304. return mb_substr(
  2305. $value,
  2306. 0,
  2307. mb_strpos($value, '.') + 7
  2308. );
  2309. }
  2310. /**
  2311. * Reads the file, detects the compression MIME type, closes the file
  2312. * and returns the MIME type
  2313. *
  2314. * @param resource $file the file handle
  2315. *
  2316. * @return string the MIME type for compression, or 'none'
  2317. */
  2318. public static function getCompressionMimeType($file)
  2319. {
  2320. $test = fread($file, 4);
  2321. if ($test === false) {
  2322. fclose($file);
  2323. return 'none';
  2324. }
  2325. $len = strlen($test);
  2326. fclose($file);
  2327. if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
  2328. return 'application/gzip';
  2329. }
  2330. if ($len >= 3 && substr($test, 0, 3) === 'BZh') {
  2331. return 'application/bzip2';
  2332. }
  2333. if ($len >= 4 && $test == "PK\003\004") {
  2334. return 'application/zip';
  2335. }
  2336. return 'none';
  2337. }
  2338. /**
  2339. * Provide COLLATE clause, if required, to perform case sensitive comparisons
  2340. * for queries on information_schema.
  2341. *
  2342. * @return string COLLATE clause if needed or empty string.
  2343. */
  2344. public static function getCollateForIS()
  2345. {
  2346. global $dbi;
  2347. $names = $dbi->getLowerCaseNames();
  2348. if ($names === '0') {
  2349. return 'COLLATE utf8_bin';
  2350. }
  2351. if ($names === '2') {
  2352. return 'COLLATE utf8_general_ci';
  2353. }
  2354. return '';
  2355. }
  2356. /**
  2357. * Process the index data.
  2358. *
  2359. * @param array $indexes index data
  2360. *
  2361. * @return array processes index data
  2362. */
  2363. public static function processIndexData(array $indexes)
  2364. {
  2365. $lastIndex = '';
  2366. $primary = '';
  2367. $pk_array = []; // will be use to emphasis prim. keys in the table
  2368. $indexes_info = [];
  2369. $indexes_data = [];
  2370. // view
  2371. foreach ($indexes as $row) {
  2372. // Backups the list of primary keys
  2373. if ($row['Key_name'] === 'PRIMARY') {
  2374. $primary .= $row['Column_name'] . ', ';
  2375. $pk_array[$row['Column_name']] = 1;
  2376. }
  2377. // Retains keys informations
  2378. if ($row['Key_name'] != $lastIndex) {
  2379. $indexes[] = $row['Key_name'];
  2380. $lastIndex = $row['Key_name'];
  2381. }
  2382. $indexes_info[$row['Key_name']]['Sequences'][] = $row['Seq_in_index'];
  2383. $indexes_info[$row['Key_name']]['Non_unique'] = $row['Non_unique'];
  2384. if (isset($row['Cardinality'])) {
  2385. $indexes_info[$row['Key_name']]['Cardinality'] = $row['Cardinality'];
  2386. }
  2387. // I don't know what does following column mean....
  2388. // $indexes_info[$row['Key_name']]['Packed'] = $row['Packed'];
  2389. $indexes_info[$row['Key_name']]['Comment'] = $row['Comment'];
  2390. $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Column_name']
  2391. = $row['Column_name'];
  2392. if (! isset($row['Sub_part'])) {
  2393. continue;
  2394. }
  2395. $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Sub_part']
  2396. = $row['Sub_part'];
  2397. }
  2398. return [
  2399. $primary,
  2400. $pk_array,
  2401. $indexes_info,
  2402. $indexes_data,
  2403. ];
  2404. }
  2405. /**
  2406. * Returns whether the database server supports virtual columns
  2407. *
  2408. * @return bool
  2409. */
  2410. public static function isVirtualColumnsSupported()
  2411. {
  2412. global $dbi;
  2413. $serverType = self::getServerType();
  2414. $serverVersion = $dbi->getVersion();
  2415. return in_array($serverType, ['MySQL', 'Percona Server']) && $serverVersion >= 50705
  2416. || ($serverType === 'MariaDB' && $serverVersion >= 50200);
  2417. }
  2418. /**
  2419. * Gets the list of tables in the current db and information about these
  2420. * tables if possible
  2421. *
  2422. * @param string $db database name
  2423. * @param string|null $sub_part part of script name
  2424. *
  2425. * @return array
  2426. */
  2427. public static function getDbInfo($db, ?string $sub_part)
  2428. {
  2429. global $cfg, $dbi;
  2430. /**
  2431. * limits for table list
  2432. */
  2433. if (! isset($_SESSION['tmpval']['table_limit_offset'])
  2434. || $_SESSION['tmpval']['table_limit_offset_db'] != $db
  2435. ) {
  2436. $_SESSION['tmpval']['table_limit_offset'] = 0;
  2437. $_SESSION['tmpval']['table_limit_offset_db'] = $db;
  2438. }
  2439. if (isset($_REQUEST['pos'])) {
  2440. $_SESSION['tmpval']['table_limit_offset'] = (int) $_REQUEST['pos'];
  2441. }
  2442. $pos = $_SESSION['tmpval']['table_limit_offset'];
  2443. /**
  2444. * whether to display extended stats
  2445. */
  2446. $isShowStats = $cfg['ShowStats'];
  2447. /**
  2448. * whether selected db is information_schema
  2449. */
  2450. $isSystemSchema = false;
  2451. if (Utilities::isSystemSchema($db)) {
  2452. $isShowStats = false;
  2453. $isSystemSchema = true;
  2454. }
  2455. /**
  2456. * information about tables in db
  2457. */
  2458. $tables = [];
  2459. $tooltip_truename = [];
  2460. $tooltip_aliasname = [];
  2461. // Special speedup for newer MySQL Versions (in 4.0 format changed)
  2462. if ($cfg['SkipLockedTables'] === true) {
  2463. $db_info_result = $dbi->query(
  2464. 'SHOW OPEN TABLES FROM ' . self::backquote($db) . ' WHERE In_use > 0;'
  2465. );
  2466. // Blending out tables in use
  2467. if ($db_info_result && $dbi->numRows($db_info_result) > 0) {
  2468. $tables = self::getTablesWhenOpen($db, $db_info_result);
  2469. } elseif ($db_info_result) {
  2470. $dbi->freeResult($db_info_result);
  2471. }
  2472. }
  2473. if (empty($tables)) {
  2474. // Set some sorting defaults
  2475. $sort = 'Name';
  2476. $sort_order = 'ASC';
  2477. if (isset($_REQUEST['sort'])) {
  2478. $sortable_name_mappings = [
  2479. 'table' => 'Name',
  2480. 'records' => 'Rows',
  2481. 'type' => 'Engine',
  2482. 'collation' => 'Collation',
  2483. 'size' => 'Data_length',
  2484. 'overhead' => 'Data_free',
  2485. 'creation' => 'Create_time',
  2486. 'last_update' => 'Update_time',
  2487. 'last_check' => 'Check_time',
  2488. 'comment' => 'Comment',
  2489. ];
  2490. // Make sure the sort type is implemented
  2491. if (isset($sortable_name_mappings[$_REQUEST['sort']])) {
  2492. $sort = $sortable_name_mappings[$_REQUEST['sort']];
  2493. if ($_REQUEST['sort_order'] === 'DESC') {
  2494. $sort_order = 'DESC';
  2495. }
  2496. }
  2497. }
  2498. $groupWithSeparator = false;
  2499. $tbl_type = null;
  2500. $limit_offset = 0;
  2501. $limit_count = false;
  2502. $groupTable = [];
  2503. if (! empty($_REQUEST['tbl_group']) || ! empty($_REQUEST['tbl_type'])) {
  2504. if (! empty($_REQUEST['tbl_type'])) {
  2505. // only tables for selected type
  2506. $tbl_type = $_REQUEST['tbl_type'];
  2507. }
  2508. if (! empty($_REQUEST['tbl_group'])) {
  2509. // only tables for selected group
  2510. $tbl_group = $_REQUEST['tbl_group'];
  2511. // include the table with the exact name of the group if such
  2512. // exists
  2513. $groupTable = $dbi->getTablesFull(
  2514. $db,
  2515. $tbl_group,
  2516. false,
  2517. $limit_offset,
  2518. $limit_count,
  2519. $sort,
  2520. $sort_order,
  2521. $tbl_type
  2522. );
  2523. $groupWithSeparator = $tbl_group
  2524. . $GLOBALS['cfg']['NavigationTreeTableSeparator'];
  2525. }
  2526. } else {
  2527. // all tables in db
  2528. // - get the total number of tables
  2529. // (needed for proper working of the MaxTableList feature)
  2530. $tables = $dbi->getTables($db);
  2531. $total_num_tables = count($tables);
  2532. if (! (isset($sub_part) && $sub_part === '_export')) {
  2533. // fetch the details for a possible limited subset
  2534. $limit_offset = $pos;
  2535. $limit_count = true;
  2536. }
  2537. }
  2538. $tables = array_merge(
  2539. $groupTable,
  2540. $dbi->getTablesFull(
  2541. $db,
  2542. $groupWithSeparator !== false ? $groupWithSeparator : '',
  2543. $groupWithSeparator !== false,
  2544. $limit_offset,
  2545. $limit_count,
  2546. $sort,
  2547. $sort_order,
  2548. $tbl_type
  2549. )
  2550. );
  2551. }
  2552. $num_tables = count($tables);
  2553. // (needed for proper working of the MaxTableList feature)
  2554. if (! isset($total_num_tables)) {
  2555. $total_num_tables = $num_tables;
  2556. }
  2557. /**
  2558. * If coming from a Show MySQL link on the home page,
  2559. * put something in $sub_part
  2560. */
  2561. if (empty($sub_part)) {
  2562. $sub_part = '_structure';
  2563. }
  2564. return [
  2565. $tables,
  2566. $num_tables,
  2567. $total_num_tables,
  2568. $sub_part,
  2569. $isShowStats,
  2570. $isSystemSchema,
  2571. $tooltip_truename,
  2572. $tooltip_aliasname,
  2573. $pos,
  2574. ];
  2575. }
  2576. /**
  2577. * Gets the list of tables in the current db, taking into account
  2578. * that they might be "in use"
  2579. *
  2580. * @param string $db database name
  2581. * @param object $db_info_result result set
  2582. *
  2583. * @return array list of tables
  2584. */
  2585. public static function getTablesWhenOpen($db, $db_info_result): array
  2586. {
  2587. global $dbi;
  2588. $sot_cache = [];
  2589. $tables = [];
  2590. while ($tmp = $dbi->fetchAssoc($db_info_result)) {
  2591. $sot_cache[$tmp['Table']] = true;
  2592. }
  2593. $dbi->freeResult($db_info_result);
  2594. // is there at least one "in use" table?
  2595. if (count($sot_cache) > 0) {
  2596. $tblGroupSql = '';
  2597. $whereAdded = false;
  2598. if (Core::isValid($_REQUEST['tbl_group'])) {
  2599. $group = self::escapeMysqlWildcards($_REQUEST['tbl_group']);
  2600. $groupWithSeparator = self::escapeMysqlWildcards(
  2601. $_REQUEST['tbl_group']
  2602. . $GLOBALS['cfg']['NavigationTreeTableSeparator']
  2603. );
  2604. $tblGroupSql .= ' WHERE ('
  2605. . self::backquote('Tables_in_' . $db)
  2606. . " LIKE '" . $groupWithSeparator . "%'"
  2607. . ' OR '
  2608. . self::backquote('Tables_in_' . $db)
  2609. . " LIKE '" . $group . "')";
  2610. $whereAdded = true;
  2611. }
  2612. if (Core::isValid($_REQUEST['tbl_type'], ['table', 'view'])) {
  2613. $tblGroupSql .= $whereAdded ? ' AND' : ' WHERE';
  2614. if ($_REQUEST['tbl_type'] === 'view') {
  2615. $tblGroupSql .= " `Table_type` NOT IN ('BASE TABLE', 'SYSTEM VERSIONED')";
  2616. } else {
  2617. $tblGroupSql .= " `Table_type` IN ('BASE TABLE', 'SYSTEM VERSIONED')";
  2618. }
  2619. }
  2620. $db_info_result = $dbi->query(
  2621. 'SHOW FULL TABLES FROM ' . self::backquote($db) . $tblGroupSql,
  2622. DatabaseInterface::CONNECT_USER,
  2623. DatabaseInterface::QUERY_STORE
  2624. );
  2625. unset($tblGroupSql, $whereAdded);
  2626. if ($db_info_result && $dbi->numRows($db_info_result) > 0) {
  2627. $names = [];
  2628. while ($tmp = $dbi->fetchRow($db_info_result)) {
  2629. if (! isset($sot_cache[$tmp[0]])) {
  2630. $names[] = $tmp[0];
  2631. } else { // table in use
  2632. $tables[$tmp[0]] = [
  2633. 'TABLE_NAME' => $tmp[0],
  2634. 'ENGINE' => '',
  2635. 'TABLE_TYPE' => '',
  2636. 'TABLE_ROWS' => 0,
  2637. 'TABLE_COMMENT' => '',
  2638. ];
  2639. }
  2640. }
  2641. if (count($names) > 0) {
  2642. $tables = array_merge(
  2643. $tables,
  2644. $dbi->getTablesFull($db, $names)
  2645. );
  2646. }
  2647. if ($GLOBALS['cfg']['NaturalOrder']) {
  2648. uksort($tables, 'strnatcasecmp');
  2649. }
  2650. } elseif ($db_info_result) {
  2651. $dbi->freeResult($db_info_result);
  2652. }
  2653. unset($sot_cache);
  2654. }
  2655. return $tables;
  2656. }
  2657. /**
  2658. * Checks whether database extension is loaded
  2659. *
  2660. * @param string $extension mysql extension to check
  2661. */
  2662. public static function checkDbExtension(string $extension = 'mysqli'): bool
  2663. {
  2664. return function_exists($extension . '_connect');
  2665. }
  2666. /**
  2667. * Returns list of used PHP extensions.
  2668. *
  2669. * @return string[]
  2670. */
  2671. public static function listPHPExtensions(): array
  2672. {
  2673. $result = [];
  2674. if (self::checkDbExtension('mysqli')) {
  2675. $result[] = 'mysqli';
  2676. }
  2677. if (extension_loaded('curl')) {
  2678. $result[] = 'curl';
  2679. }
  2680. if (extension_loaded('mbstring')) {
  2681. $result[] = 'mbstring';
  2682. }
  2683. return $result;
  2684. }
  2685. /**
  2686. * Converts given (request) parameter to string
  2687. *
  2688. * @param mixed $value Value to convert
  2689. */
  2690. public static function requestString($value): string
  2691. {
  2692. while (is_array($value) || is_object($value)) {
  2693. if (is_object($value)) {
  2694. $value = (array) $value;
  2695. }
  2696. $value = reset($value);
  2697. }
  2698. return trim((string) $value);
  2699. }
  2700. /**
  2701. * Generates random string consisting of ASCII chars
  2702. *
  2703. * @param int $length Length of string
  2704. * @param bool $asHex (optional) Send the result as hex
  2705. */
  2706. public static function generateRandom(int $length, bool $asHex = false): string
  2707. {
  2708. $result = '';
  2709. if (class_exists(Random::class)) {
  2710. $random_func = [
  2711. Random::class,
  2712. 'string',
  2713. ];
  2714. } else {
  2715. $random_func = 'openssl_random_pseudo_bytes';
  2716. }
  2717. while (strlen($result) < $length) {
  2718. // Get random byte and strip highest bit
  2719. // to get ASCII only range
  2720. $byte = ord((string) $random_func(1)) & 0x7f;
  2721. // We want only ASCII chars
  2722. if ($byte <= 32) {
  2723. continue;
  2724. }
  2725. $result .= chr($byte);
  2726. }
  2727. return $asHex ? bin2hex($result) : $result;
  2728. }
  2729. /**
  2730. * Wrapper around PHP date function
  2731. *
  2732. * @param string $format Date format string
  2733. *
  2734. * @return string
  2735. */
  2736. public static function date($format)
  2737. {
  2738. if (defined('TESTSUITE')) {
  2739. return '0000-00-00 00:00:00';
  2740. }
  2741. return date($format);
  2742. }
  2743. /**
  2744. * Wrapper around php's set_time_limit
  2745. */
  2746. public static function setTimeLimit(): void
  2747. {
  2748. // The function can be disabled in php.ini
  2749. if (! function_exists('set_time_limit')) {
  2750. return;
  2751. }
  2752. @set_time_limit((int) $GLOBALS['cfg']['ExecTimeLimit']);
  2753. }
  2754. /**
  2755. * Access to a multidimensional array by dot notation
  2756. *
  2757. * @param array $array List of values
  2758. * @param string|array $path Path to searched value
  2759. * @param mixed $default Default value
  2760. *
  2761. * @return mixed Searched value
  2762. */
  2763. public static function getValueByKey(array $array, $path, $default = null)
  2764. {
  2765. if (is_string($path)) {
  2766. $path = explode('.', $path);
  2767. }
  2768. $p = array_shift($path);
  2769. while (isset($p)) {
  2770. if (! isset($array[$p])) {
  2771. return $default;
  2772. }
  2773. $array = $array[$p];
  2774. $p = array_shift($path);
  2775. }
  2776. return $array;
  2777. }
  2778. /**
  2779. * Creates a clickable column header for table information
  2780. *
  2781. * @param string $title Title to use for the link
  2782. * @param string $sort Corresponds to sortable data name mapped
  2783. * in Util::getDbInfo
  2784. * @param string $initialSortOrder Initial sort order
  2785. *
  2786. * @return string Link to be displayed in the table header
  2787. */
  2788. public static function sortableTableHeader($title, $sort, $initialSortOrder = 'ASC')
  2789. {
  2790. $requestedSort = 'table';
  2791. $requestedSortOrder = $futureSortOrder = $initialSortOrder;
  2792. // If the user requested a sort
  2793. if (isset($_REQUEST['sort'])) {
  2794. $requestedSort = $_REQUEST['sort'];
  2795. if (isset($_REQUEST['sort_order'])) {
  2796. $requestedSortOrder = $_REQUEST['sort_order'];
  2797. }
  2798. }
  2799. $orderImg = '';
  2800. $orderLinkParams = [];
  2801. $orderLinkParams['title'] = __('Sort');
  2802. // If this column was requested to be sorted.
  2803. if ($requestedSort == $sort) {
  2804. if ($requestedSortOrder === 'ASC') {
  2805. $futureSortOrder = 'DESC';
  2806. // current sort order is ASC
  2807. $orderImg = ' ' . Generator::getImage(
  2808. 's_asc',
  2809. __('Ascending'),
  2810. [
  2811. 'class' => 'sort_arrow',
  2812. 'title' => '',
  2813. ]
  2814. );
  2815. $orderImg .= ' ' . Generator::getImage(
  2816. 's_desc',
  2817. __('Descending'),
  2818. [
  2819. 'class' => 'sort_arrow hide',
  2820. 'title' => '',
  2821. ]
  2822. );
  2823. // but on mouse over, show the reverse order (DESC)
  2824. $orderLinkParams['onmouseover'] = "$('.sort_arrow').toggle();";
  2825. // on mouse out, show current sort order (ASC)
  2826. $orderLinkParams['onmouseout'] = "$('.sort_arrow').toggle();";
  2827. } else {
  2828. $futureSortOrder = 'ASC';
  2829. // current sort order is DESC
  2830. $orderImg = ' ' . Generator::getImage(
  2831. 's_asc',
  2832. __('Ascending'),
  2833. [
  2834. 'class' => 'sort_arrow hide',
  2835. 'title' => '',
  2836. ]
  2837. );
  2838. $orderImg .= ' ' . Generator::getImage(
  2839. 's_desc',
  2840. __('Descending'),
  2841. [
  2842. 'class' => 'sort_arrow',
  2843. 'title' => '',
  2844. ]
  2845. );
  2846. // but on mouse over, show the reverse order (ASC)
  2847. $orderLinkParams['onmouseover'] = "$('.sort_arrow').toggle();";
  2848. // on mouse out, show current sort order (DESC)
  2849. $orderLinkParams['onmouseout'] = "$('.sort_arrow').toggle();";
  2850. }
  2851. }
  2852. $urlParams = [
  2853. 'db' => $_REQUEST['db'],
  2854. 'pos' => 0, // We set the position back to 0 every time they sort.
  2855. 'sort' => $sort,
  2856. 'sort_order' => $futureSortOrder,
  2857. ];
  2858. if (Core::isValid($_REQUEST['tbl_type'], ['view', 'table'])) {
  2859. $urlParams['tbl_type'] = $_REQUEST['tbl_type'];
  2860. }
  2861. if (! empty($_REQUEST['tbl_group'])) {
  2862. $urlParams['tbl_group'] = $_REQUEST['tbl_group'];
  2863. }
  2864. $url = Url::getFromRoute('/database/structure', $urlParams);
  2865. return Generator::linkOrButton($url, $title . $orderImg, $orderLinkParams);
  2866. }
  2867. /**
  2868. * Check that input is an int or an int in a string
  2869. *
  2870. * @param mixed $input input to check
  2871. */
  2872. public static function isInteger($input): bool
  2873. {
  2874. return ctype_digit((string) $input);
  2875. }
  2876. /**
  2877. * Get the protocol from the RFC 7239 Forwarded header
  2878. *
  2879. * @param string $headerContents The Forwarded header contents
  2880. *
  2881. * @return string the protocol http/https
  2882. */
  2883. public static function getProtoFromForwardedHeader(string $headerContents): string
  2884. {
  2885. if (strpos($headerContents, '=') !== false) {// does not contain any equal sign
  2886. $hops = explode(',', $headerContents);
  2887. $parts = explode(';', $hops[0]);
  2888. foreach ($parts as $part) {
  2889. $keyValueArray = explode('=', $part, 2);
  2890. if (count($keyValueArray) !== 2) {
  2891. continue;
  2892. }
  2893. [
  2894. $keyName,
  2895. $value,
  2896. ] = $keyValueArray;
  2897. $value = trim(strtolower($value));
  2898. if (strtolower(trim($keyName)) === 'proto' && in_array($value, ['http', 'https'])) {
  2899. return $value;
  2900. }
  2901. }
  2902. }
  2903. return '';
  2904. }
  2905. /**
  2906. * Check if error reporting is available
  2907. */
  2908. public static function isErrorReportingAvailable(): bool
  2909. {
  2910. // issue #16256 - PHP 7.x does not return false for a core function
  2911. if (PHP_MAJOR_VERSION < 8) {
  2912. $disabled = ini_get('disable_functions');
  2913. if (is_string($disabled)) {
  2914. $disabled = explode(',', $disabled);
  2915. $disabled = array_map(static function (string $part) {
  2916. return trim($part);
  2917. }, $disabled);
  2918. return ! in_array('error_reporting', $disabled);
  2919. }
  2920. }
  2921. return function_exists('error_reporting');
  2922. }
  2923. }