Util.php 167 KB

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