ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ." ¤‡ú*õ'VŽ|¼´Úgllº¼klz[Æüï÷Aób‡Eÿ dÑ»Xx9ÃÜ£ÁT/`¼¸vI±Ýµ·Ë‚“G³þ*Ÿû´r|*}<¨îºœ @¦mÄ’M¹”.œ«Y–|6ÏU¤jç¥ÕÞqO ˜kDÆÁ¨5ÿ š;ÐЦ¦€GÙk \ –Þ=â¼=SͧµªS°ÚÍpÜãQűÀõ¬?ÃÁ1Ñ•õZà?hóœ€ L¦l{Y*K˜Ù›zc˜–ˆâ ø+¾ ­-Ök¥%ùEÜA'}ˆ><ÊIè“bpÍ/qÞâvoX€w,\úªò6Z[XdÒæ­@Ö—€$òJí#é>'°Ú ôª˜<)4ryÙ£|óAÅn5žêŸyÒäMÝ2{"}‰–¤l÷ûWX\l¾Á¸góÉOÔ /óñB¤f¸çñ[.P˜ZsÊË*ßT܈§QN¢’¡¨§V¼(Üù*eÕ“”5T¨‹Âê¥FŒã½Dü[8'Ò¥a…Ú¶k7a *•›¼'Ò·\8¨ª\@\õ¢¦íq+DÙrmÎ…_ªæ»ŠÓœ¡¯’Ré9MÅ×D™lælffc+ŒÑ,ý™ÿ ¯þǤ=Å’Á7µ÷ÚÛ/“Ü€ñýã¼àí¾ÕÑ+ƒ,uµMâÀÄbm:ÒÎPæ{˜Gz[ƒ¯«® KHà`ߨŠéí¯P8Aq.C‰ à€kòpj´kN¶qô€…Õ,ÜNŠª-­{Zö’æû44‰sŽè‰îVíRœÕm" 6?³D9¡ÇTíÅꋇ`4«¸ÝÁô ï’ýorqКÇZ«x4Žâéþuïf¹µö[P ,Q£éaX±`PÉÍZ ¸äYúg üAx ’6Lê‚xÝÓ*äQ  Ï’¨hÍ =²,6ï#rÃ<¯–£»ƒ‹,–ê•€ aÛsñ'%Æ"®ÛüìBᝠHÚ3ß°©$“XnœÖ’î2ËTeûìxîß ¦å¿çÉ ðK§þ{‘t‚Ϋ¬jéîZ[ ”š7L¥4VÚCE×]m¤Øy”ä4-dz£œ§¸x.*ãÊÊ b÷•h:©‡¦s`BTÁRû¾g⻩‹jø sF¢àJøFl‘È•Xᓁà~*j¯ +(ÚÕ6-£¯÷GŠØy‚<Ç’.F‹Hœw(+)ÜÜâÈzÄäT§FߘãÏ;DmVœ3Àu@mÚüXÝü•3B¨òÌÁÛ<·ÃÜ z,Ì@õÅ·d2]ü8s÷IôÞ¯^Ç9¢u„~ëAŸï4«M? K]­ÅàPl@s_ p:°¬ZR”´›JC[CS.h‹ƒïËœ«Æ]–÷ó‚wR×k7X‰k›‘´ù¦=¡«‰¨¨Â')—71ó’c‡Ðúµ `é.{§p¹ój\Ž{1h{o±Ý=áUÊïGÖŒõ–-BÄm+AZX¶¡ ïHðæ¥JmÙ;…䡟ˆ¦ ° äšiÉg«$üMk5¤L“’çÊvïâï ,=f“"íἊ5ô¬x6{ɏžID0e¸vçmi'︧ºð9$ò¹÷*£’9ÿ ²TÔ…×>JV¥}Œ}$p[bÔ®*[jzS*8 ”·T›Í–ñUîƒwo$áè=LT™ç—~ô·¤ÈÚ$榍q‰„+´kFm)ž‹©i–ËqÞŠ‰à¶ü( ‚•§ •°ò·‡#5ª•µÊ﯅¡X¨šÁ*F#TXJÊ ušJVÍ&=iÄs1‚3•'fý§5Ñ<=[íÞ­ PÚ;ѱÌ_~Ä££8rÞ ²w;’hDT°>ÈG¬8Á²ÚzŽ®ò®qZcqJêäÞ-ö[ܘbň±çb“ж31²n×iƒðÕ;1¶þÉ ªX‰,ßqÏ$>•î íZ¥Z 1{ç൵+ƒÕµ¥°T$§K]á»Ûï*·¤tMI’ÂZbŽÕiÒ˜}bÓ0£ª5›¨ [5Ž^ÝœWøÂÝh° ¢OWun£¤5 a2Z.G2³YL]jåtì”ä ÁÓ‘%"©<Ôúʰsº UZvä‡ÄiÆÒM .÷V·™ø#kèýiíÌ–ª)µT[)BˆõÑ xB¾B€ÖT¨.¥~ð@VĶr#¸ü*åZNDŽH;âi ],©£öØpù(šºãö¼T.uCê•4@ÿ GÕÛ)Cx›®0ø#:ÏðFÒbR\(€€Ä®fã4Þ‰Fä¯HXƒÅ,†öEÑÔÜ]Öv²?tLÃvBY£ú6Êu5ÅAQ³1‘’¬x–HŒÐ‡ ^ ¸KwJôÖŽ5×CÚ¨vÜ«/B0$×k°=ðbÇ(Ï)w±A†Á† 11Í=èQšµ626ŒÜ/`G«µ<}—-Ö7KEHÈÉðóȤmݱû±·ø«Snmá=“䫚mݱŸ¡¶~ó·“äUóJæúòB|E LêŽy´jDÔ$G¢þÐñ7óR8ýÒ…Ç› WVe#·Ÿ p·Fx~•ݤF÷0Èÿ K¯æS<6’¡WШ; ´ÿ ¥Êø\Òuî†åÝ–VNœkÒ7oòX¨Á­Ø÷FÎÑä±g÷ÿ M~Çî=p,X´ ÝÌÚÅ‹’ÃjÖ.ØöÏñ qïQ¤ÓZE†° =6·]܈ s¸>v•Ž^Ý\wq9r‰Î\¸¡kURÒ$­*‹Nq?Þª*!sŠÆ:TU_u±T+øX¡ ®¹¡,ÄâÃBTsÜ$Ø›4m椴zÜK]’’›Pƒ @€#â˜`é¹=I‡fiV•Ôî“nRm+µFPOhÍ0B£ €+¬5c v•:P'ÒyÎ ‰V~‚Ó†ÖuókDoh$å\*ö%Ю=£«…aȼ½÷Û.-½VŒŠ¼'lyî±1¬3ó#ÞE¿ÔS¤gV£m›=§\û"—WU¤ÚǼÿ ÂnÁGŒÃ ‚õN D³õNÚíŒÕ;HôyÄÈ©P¹Ä{:?R‘Ô¨âF÷ø£bÅó® JS|‚R÷ivýáâ€Æé¡è³´IئÑT!§˜•ت‚¬â@q€wnïCWÄ@JU€ê¯m6]Ï:£âx'+ÒðXvÓ¦Úm=–´7œ $ì“B£~p%ÕŸUþ« N@¼üï~w˜ñø5®—'Ôe»¤5ã//€ž~‰Tþ›Å7•#¤× Íö pÄ$ùeåì*«ÓŠEØWEÈsßg ¦ûvžSsLpºÊW–âµEWöˬH; ™!CYõZ ÃÄf æ#1W. \uWâ\,\Çf j’<qTbên›Î[vxx£ë 'ö¨1›˜ÀM¼Pÿ H)ƒêêŒA7s,|F“ 꺸k³9Ìö*ç®;Ö!Ö$Eiž•¹ÒÚ†ýóéÝû¾ÕS®ó$’NÝäŸz¤5r¦ãÄÃD÷Üø!°ø‡Ô&@m™Ì^Ãä­d q5Lnÿ N;.6½·N|#ä"1Nƒx“ã<3('&ñßt  ~ªu”1Tb㫨9ê–›–bìd$ߣ=#ÕãÒmU¯eí$EFù5ýYô櫨æì™Ç—±ssM]·á¿0ÕåJRÓªîiƒ+O58ÖñªŠÒx" \µâá¨i’¤i —Ö ” M+M¤ë9‚‰A¦°Qõ¾ßøK~¼Ã‘g…Ö´~÷Ï[3GUœÒ½#…kàÔ®Ò”‰³·dWV‰IP‰Ú8u¹”E ÖqLj¾êÕCBš{A^Âß;–¨`¯¬ìö ˼ ×tìø.tƐm*n¨y4o&Àx¥n¦×î‡aupáÛj8¿m›è¶ã!o½;ß0y^ý×^EÑ¿ÒjzŒ­)vÚÑnÄL …^ªô× ‡—‚3k Îý­hï]içå–îÏ*÷ñþ»Ô CÒjøjÍznˆ´ ¹#b'Fô‹ ‰v¥'’à'T´ƒHýÍ%M‰ ƒ&ÆÇŒï1 ‘ –Þ ‰i¬s žR-Ÿ kЬá¬7:þ 0ŒÅÒÕ/aÙ¬ÃÝ#Úøœ ©aiVc‰. ¹¦ãµ” ›Yg¦›ÆÎýº°f³7ƒhá·¸­}&D9¡ÂsÉÙÞèŠõØàC™¨ñbFC|´Ü(ŸƒÚÒ-%»'a Ì¿)ËÇn¿úÿ ÞŽX…4ÊÅH^ôΑí@ù¹Eh¶“L8Çjù ¼ÎåVªóR©Ï5uà V4lZß®=€xÖŸ–ÑÈ ÷”¨°¾__yM1tÉ?uÆþIkÄgæ@þ[¢†°XÃJ£j·:nkÅ¢u ‘}âGzö­/IµèЬ¼48q¦F°ŽR¼=ûì{´¯RýicS ÕÛ íNtÍÙï£,w4rêì®»~x(©Uñ§#Ñ&œÕ¤>ÎåÍÓ9’Ö{9eV­[Öjâ²ãu]˜å2›qÑšÕJç0€sÄ|Êëè0튔bÁ>“{×_F`Ø©ºê:µä,v¤ðfc1±"«ÔÍän1#=· Âøv~H½ÐßA¾¿Ü€Óš]Õ; I¾÷ç‚Qi†î¹9ywÔKG˜áñ zQY—§ÃÕZ07§X‚ Áh;ÁM)iÌCH-¯T‘ë|A0{Ò½LÚ–TâÖkÜ’dÀ“rmm»”جPF³ÖcbE§T€ÒxKºû’Ó®7±²(\4ŽÃ¸Uu@j™yĵ;³µ!Á¢b.W¤=mõ´êµK k ¸K^ÜÛ#p*Ü14qkZç5ïë †°5Ï%ÍÛ<Õ¤×Ô¥ê†C Õ´¼ú$ƒÖ“”]Ù¬qÞÚ[4©ý!ûÏ—Áb쳐XµA¬â~`›Çr¸8ìùÝ䫦<>ä÷«?xs´ÇÑ /á;¹øüÊÈÙà{"@Žïzâ¬[âß‚ U_<ÇŸ½4èN˜ú61®qŠu ¦þF£»äJ_ˆÙÎ~ ÞAã–݄ϗrŠD;xTž‘ô`É«…suãO`?³à™ô Lý#Íc5öoæØ‚y´´÷«ZR§<&JÇ+éâô´€i!Àˆ0æAoàðLèÖ-2ŸõW.’t^–(KÁmHµV@xÜÇy®Ñø­â^:Ú3w· 7½¹°ñ¸â¹®:',«Mœ—n­Á+Ãbš LÈ‘ÄnRÓÅœ%¦²‰¨ùQ:¤f‚ "PÕtô¸…cæl…&˜Ú˜Ôkv‹ž+vŠ,=¢v­6—Xy*¥t£«<™:“aîϲ=¦6rO]XI¿Œ÷¤zÚ­›¶ 6÷”w\d ü~v®ˆÌk«^m<ÿ ¢‰Õ\)ùºŽ;… lîÙÅEŠ®cѾ@vnMÏ,¼“ñ•ŽBxðÃzãÇç%3ˆ"}Ù•Åî> BÉú;Ò]V+P˜F_´ßé> Øše|ï‡ÄOmFæÇ ãqÞ$/xÐx­z`ï9"œÜij‚!7.\Td…9M‡•iŽ‹¾‘50ÞŽn¥ß4ÉôO ¹*í^QêËÜÇÌ8=ާs‰'ÂëÙ«á%Pú[O †ÅP¯Vsް.‰,kc¶ ¬A9n˜XÎ-ÞšN["¹QÕ‰ƒMýÁߺXJæÍaLj¾×Ãmã¾ãÚ uñÒþåQô¦¥ /ÄUx:‚ÍÜ’ Đ©ØÝ3V¨‰ÕnÐ6ó*óúK­«…c ¯U òhsý­jóÔj#,ímŒRµ«lbïUTŒÑ8†Ä0œÏr`ð¡¬É Ї ë"À² ™ 6¥ f¶ ¢ÚoܱԷ-<Àî)†a¶ž'Ú»¨TXqØæ¶÷YÄHy˜9ÈIW­YÀuMFë ºÏ’AqÌ4·/Ú †ô'i$øä­=Ä Ý|öK×40è|È6p‘0§)o¥ctî§H+CA-“ xØ|ÐXАç l8íºð3Ø:³¤¬KX¯UÿÙ url_query = $url_query; $this->_searchType = $searchType; $this->_columnNames = array(); $this->_columnNullFlags = array(); $this->_columnTypes = array(); $this->_columnCollations = array(); $this->_geomColumnFlag = false; $this->_foreigners = array(); // Loads table's information $this->_loadTableInfo(); $this->_connectionCharSet = $this->dbi->fetchValue( "SELECT @@character_set_connection" ); } /** * Gets all the columns of a table along with their types, collations * and whether null or not. * * @return void */ private function _loadTableInfo() { // Gets the list and number of columns $columns = $this->dbi->getColumns( $this->db, $this->table, null, true ); // Get details about the geometry functions $geom_types = Util::getGISDatatypes(); foreach ($columns as $row) { // set column name $this->_columnNames[] = $row['Field']; $type = $row['Type']; // check whether table contains geometric columns if (in_array($type, $geom_types)) { $this->_geomColumnFlag = true; } // reformat mysql query output if (strncasecmp($type, 'set', 3) == 0 || strncasecmp($type, 'enum', 4) == 0 ) { $type = str_replace(',', ', ', $type); } else { // strip the "BINARY" attribute, except if we find "BINARY(" because // this would be a BINARY or VARBINARY column type if (! preg_match('@BINARY[\(]@i', $type)) { $type = preg_replace('@BINARY@i', '', $type); } $type = preg_replace('@ZEROFILL@i', '', $type); $type = preg_replace('@UNSIGNED@i', '', $type); $type = mb_strtolower($type); } if (empty($type)) { $type = ' '; } $this->_columnTypes[] = $type; $this->_columnNullFlags[] = $row['Null']; $this->_columnCollations[] = ! empty($row['Collation']) && $row['Collation'] != 'NULL' ? $row['Collation'] : ''; } // end for // Retrieve foreign keys $this->_foreigners = PMA_getForeigners($this->db, $this->table); } /** * Index action * * @return void */ public function indexAction() { switch ($this->_searchType) { case 'replace': if (isset($_POST['find'])) { $this->findAction(); return; } $this->response ->getHeader() ->getScripts() ->addFile('tbl_find_replace.js'); // Show secondary level of tabs $this->response->addHTML( Template::get('secondary_tabs') ->render( array( 'url_params' => array( 'db' => $this->db, 'table' => $this->table, ), 'sub_tabs' => $this->_getSubTabs(), ) ) ); if (isset($_POST['replace'])) { $this->replaceAction(); } if (!isset($goto)) { $goto = Util::getScriptNameForOption( $GLOBALS['cfg']['DefaultTabTable'], 'table' ); } // Displays the find and replace form $this->response->addHTML( Template::get('table/search/selection_form') ->render( array( 'searchType' => $this->_searchType, 'db' => $this->db, 'table' => $this->table, 'goto' => $goto, 'self' => $this, 'geomColumnFlag' => $this->_geomColumnFlag, 'columnNames' => $this->_columnNames, 'columnTypes' => $this->_columnTypes, 'columnCollations' => $this->_columnCollations, 'dataLabel' => null, ) ) ); break; case 'normal': $this->response->getHeader() ->getScripts() ->addFiles( array( 'makegrid.js', 'sql.js', 'tbl_select.js', 'tbl_change.js', 'jquery/jquery-ui-timepicker-addon.js', 'jquery/jquery.uitablefilter.js', 'gis_data_editor.js', ) ); if (isset($_REQUEST['range_search'])) { $this->rangeSearchAction(); return; } /** * No selection criteria received -> display the selection form */ if (!isset($_POST['columnsToDisplay']) && !isset($_POST['displayAllColumns']) ) { $this->displaySelectionFormAction(); } else { $this->doSelectionAction(); } break; case 'zoom': $this->response->getHeader() ->getScripts() ->addFiles( array( 'makegrid.js', 'sql.js', 'jqplot/jquery.jqplot.js', 'jqplot/plugins/jqplot.canvasTextRenderer.js', 'jqplot/plugins/jqplot.canvasAxisLabelRenderer.js', 'jqplot/plugins/jqplot.dateAxisRenderer.js', 'jqplot/plugins/jqplot.highlighter.js', 'jqplot/plugins/jqplot.cursor.js', 'jquery/jquery-ui-timepicker-addon.js', 'tbl_zoom_plot_jqplot.js', 'tbl_change.js', ) ); /** * Handle AJAX request for data row on point select * * @var boolean Object containing parameters for the POST request */ if (isset($_REQUEST['get_data_row']) && $_REQUEST['get_data_row'] == true ) { $this->getDataRowAction(); return; } /** * Handle AJAX request for changing field information * (value,collation,operators,field values) in input form * * @var boolean Object containing parameters for the POST request */ if (isset($_REQUEST['change_tbl_info']) && $_REQUEST['change_tbl_info'] == true ) { $this->changeTableInfoAction(); return; } $this->url_query .= '&goto=tbl_select.php&back=tbl_select.php'; // Gets tables information include_once './libraries/tbl_info.inc.php'; if (!isset($goto)) { $goto = Util::getScriptNameForOption( $GLOBALS['cfg']['DefaultTabTable'], 'table' ); } //Set default datalabel if not selected if (!isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') { $dataLabel = PMA_getDisplayField($this->db, $this->table); } else { $dataLabel = $_POST['dataLabel']; } // Displays the zoom search form $this->response->addHTML( Template::get('secondary_tabs') ->render( array( 'url_params' => array( 'db' => $this->db, 'table' => $this->table, ), 'sub_tabs' => $this->_getSubTabs(), ) ) ); $this->response->addHTML( Template::get('table/search/selection_form') ->render( array( 'searchType' => $this->_searchType, 'db' => $this->db, 'table' => $this->table, 'goto' => $goto, 'self' => $this, 'geomColumnFlag' => $this->_geomColumnFlag, 'columnNames' => $this->_columnNames, 'columnTypes' => $this->_columnTypes, 'columnCollations' => $this->_columnCollations, 'dataLabel' => $dataLabel, ) ) ); /* * Handle the input criteria and generate the query result * Form for displaying query results */ if (isset($_POST['zoom_submit']) && $_POST['criteriaColumnNames'][0] != 'pma_null' && $_POST['criteriaColumnNames'][1] != 'pma_null' && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1] ) { $this->zoomSubmitAction($dataLabel, $goto); } break; } } /** * Zoom submit action * * @param string $dataLabel Data label * @param string $goto Goto * * @return void */ public function zoomSubmitAction($dataLabel, $goto) { //Query generation part $sql_query = $this->_buildSqlQuery(); $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit']; //Query execution part $result = $this->dbi->query( $sql_query . ";", null, DatabaseInterface::QUERY_STORE ); $fields_meta = $this->dbi->getFieldsMeta($result); $data = array(); while ($row = $this->dbi->fetchAssoc($result)) { //Need a row with indexes as 0,1,2 for the getUniqueCondition // hence using a temporary array $tmpRow = array(); foreach ($row as $val) { $tmpRow[] = $val; } //Get unique condition on each row (will be needed for row update) $uniqueCondition = Util::getUniqueCondition( $result, // handle count($this->_columnNames), // fields_cnt $fields_meta, // fields_meta $tmpRow, // row true, // force_unique false, // restrict_to_table null // analyzed_sql_results ); //Append it to row array as where_clause $row['where_clause'] = $uniqueCondition[0]; $row['where_clause_sign'] = Util::signSqlQuery($uniqueCondition[0]); $tmpData = array( $_POST['criteriaColumnNames'][0] => $row[$_POST['criteriaColumnNames'][0]], $_POST['criteriaColumnNames'][1] => $row[$_POST['criteriaColumnNames'][1]], 'where_clause' => $uniqueCondition[0], 'where_clause_sign' => Util::signSqlQuery($uniqueCondition[0]) ); $tmpData[$dataLabel] = ($dataLabel) ? $row[$dataLabel] : ''; $data[] = $tmpData; } unset($tmpData); //Displays form for point data and scatter plot $titles = array( 'Browse' => Util::getIcon( 'b_browse.png', __('Browse foreign values') ) ); $this->response->addHTML( Template::get('table/search/zoom_result_form') ->render( array( '_db' => $this->db, '_table' => $this->table, '_columnNames' => $this->_columnNames, '_foreigners' => $this->_foreigners, '_columnNullFlags' => $this->_columnNullFlags, '_columnTypes' => $this->_columnTypes, 'titles' => $titles, 'goto' => $goto, 'data' => $data, ) ) ); } /** * Change table info action * * @return void */ public function changeTableInfoAction() { $field = $_REQUEST['field']; if ($field == 'pma_null') { $this->response->addJSON('field_type', ''); $this->response->addJSON('field_collation', ''); $this->response->addJSON('field_operators', ''); $this->response->addJSON('field_value', ''); return; } $key = array_search($field, $this->_columnNames); $search_index = ((isset($_REQUEST['it']) && is_numeric($_REQUEST['it'])) ? intval($_REQUEST['it']) : 0); $properties = $this->getColumnProperties($search_index, $key); $this->response->addJSON( 'field_type', htmlspecialchars($properties['type']) ); $this->response->addJSON('field_collation', $properties['collation']); $this->response->addJSON('field_operators', $properties['func']); $this->response->addJSON('field_value', $properties['value']); } /** * Get data row action * * @return void */ public function getDataRowAction() { if (! Util::checkSqlQuerySignature($_REQUEST['where_clause'], $_REQUEST['where_clause_sign'])) { return; } $extra_data = array(); $row_info_query = 'SELECT * FROM `' . Util::backquote($_REQUEST['db']) . '`.`' . Util::backquote($_REQUEST['table']) . '` WHERE ' . $_REQUEST['where_clause']; $result = $this->dbi->query( $row_info_query . ";", null, DatabaseInterface::QUERY_STORE ); $fields_meta = $this->dbi->getFieldsMeta($result); while ($row = $this->dbi->fetchAssoc($result)) { // for bit fields we need to convert them to printable form $i = 0; foreach ($row as $col => $val) { if ($fields_meta[$i]->type == 'bit') { $row[$col] = Util::printableBitValue( $val, $fields_meta[$i]->length ); } $i++; } $extra_data['row_info'] = $row; } $this->response->addJSON($extra_data); } /** * Do selection action * * @return void */ public function doSelectionAction() { /** * Selection criteria have been submitted -> do the work */ $sql_query = $this->_buildSqlQuery(); /** * Add this to ensure following procedures included running correctly. */ $db = $this->db; PMA_executeQueryAndSendQueryResponse( null, // analyzed_sql_results false, // is_gotofile $this->db, // db $this->table, // table null, // find_real_end null, // sql_query_for_bookmark null, // extra_data null, // message_to_show null, // message null, // sql_data $GLOBALS['goto'], // goto $GLOBALS['pmaThemeImage'], // pmaThemeImage null, // disp_query null, // disp_message null, // query_type $sql_query, // sql_query null, // selectedTables null // complete_query ); } /** * Display selection form action * * @return void */ public function displaySelectionFormAction() { $this->url_query .= '&goto=tbl_select.php&back=tbl_select.php'; include_once 'libraries/tbl_info.inc.php'; if (! isset($goto)) { $goto = Util::getScriptNameForOption( $GLOBALS['cfg']['DefaultTabTable'], 'table' ); } // Displays the table search form $this->response->addHTML( Template::get('secondary_tabs') ->render( array( 'url_params' => array( 'db' => $this->db, 'table' => $this->table, ), 'sub_tabs' => $this->_getSubTabs(), ) ) ); $this->response->addHTML( Template::get('table/search/selection_form') ->render( array( 'searchType' => $this->_searchType, 'db' => $this->db, 'table' => $this->table, 'goto' => $goto, 'self' => $this, 'geomColumnFlag' => $this->_geomColumnFlag, 'columnNames' => $this->_columnNames, 'columnTypes' => $this->_columnTypes, 'columnCollations' => $this->_columnCollations, 'dataLabel' => null, ) ) ); } /** * Range search action * * @return void */ public function rangeSearchAction() { $min_max = $this->getColumnMinMax($_REQUEST['column']); $this->response->addJSON('column_data', $min_max); } /** * Find action * * @return void */ public function findAction() { $useRegex = array_key_exists('useRegex', $_POST) && $_POST['useRegex'] == 'on'; $preview = $this->getReplacePreview( $_POST['columnIndex'], $_POST['find'], $_POST['replaceWith'], $useRegex, $this->_connectionCharSet ); $this->response->addJSON('preview', $preview); } /** * Replace action * * @return void */ public function replaceAction() { $this->replace( $_POST['columnIndex'], $_POST['findString'], $_POST['replaceWith'], $_POST['useRegex'], $this->_connectionCharSet ); $this->response->addHTML( Util::getMessage( __('Your SQL query has been executed successfully.'), null, 'success' ) ); } /** * Returns HTML for previewing strings found and their replacements * * @param int $columnIndex index of the column * @param string $find string to find in the column * @param string $replaceWith string to replace with * @param boolean $useRegex to use Regex replace or not * @param string $charSet character set of the connection * * @return string HTML for previewing strings found and their replacements */ function getReplacePreview( $columnIndex, $find, $replaceWith, $useRegex, $charSet ) { $column = $this->_columnNames[$columnIndex]; if ($useRegex) { $result = $this->_getRegexReplaceRows( $columnIndex, $find, $replaceWith, $charSet ); } else { $sql_query = "SELECT " . Util::backquote($column) . "," . " REPLACE(" . Util::backquote($column) . ", '" . $find . "', '" . $replaceWith . "')," . " COUNT(*)" . " FROM " . Util::backquote($this->db) . "." . Util::backquote($this->table) . " WHERE " . Util::backquote($column) . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we // change the collation of the 2nd operand to a case sensitive // binary collation to make sure that the comparison // is case sensitive $sql_query .= " GROUP BY " . Util::backquote($column) . " ORDER BY " . Util::backquote($column) . " ASC"; $result = $this->dbi->fetchResult($sql_query, 0); } return Template::get('table/search/replace_preview')->render( array( 'db' => $this->db, 'table' => $this->table, 'columnIndex' => $columnIndex, 'find' => $find, 'replaceWith' => $replaceWith, 'useRegex' => $useRegex, 'result' => $result ) ); } /** * Finds and returns Regex pattern and their replacements * * @param int $columnIndex index of the column * @param string $find string to find in the column * @param string $replaceWith string to replace with * @param string $charSet character set of the connection * * @return array Array containing original values, replaced values and count */ private function _getRegexReplaceRows( $columnIndex, $find, $replaceWith, $charSet ) { $column = $this->_columnNames[$columnIndex]; $sql_query = "SELECT " . Util::backquote($column) . "," . " 1," // to add an extra column that will have replaced value . " COUNT(*)" . " FROM " . Util::backquote($this->db) . "." . Util::backquote($this->table) . " WHERE " . Util::backquote($column) . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " . $charSet . "_bin"; // here we // change the collation of the 2nd operand to a case sensitive // binary collation to make sure that the comparison is case sensitive $sql_query .= " GROUP BY " . Util::backquote($column) . " ORDER BY " . Util::backquote($column) . " ASC"; $result = $this->dbi->fetchResult($sql_query, 0); if (is_array($result)) { /* Iterate over possible delimiters to get one */ $delimiters = array('/', '@', '#', '~', '!', '$', '%', '^', '&', '_'); $found = false; for ($i = 0, $l = count($delimiters); $i < $l; $i++) { if (strpos($find, $delimiters[$i]) === false) { $found = true; break; } } if (! $found) { return false; } $find = $delimiters[$i] . $find . $delimiters[$i]; foreach ($result as $index=>$row) { $result[$index][1] = preg_replace( $find, $replaceWith, $row[0] ); } } return $result; } /** * Replaces a given string in a column with a give replacement * * @param int $columnIndex index of the column * @param string $find string to find in the column * @param string $replaceWith string to replace with * @param boolean $useRegex to use Regex replace or not * @param string $charSet character set of the connection * * @return void */ public function replace($columnIndex, $find, $replaceWith, $useRegex, $charSet ) { $column = $this->_columnNames[$columnIndex]; if ($useRegex) { $toReplace = $this->_getRegexReplaceRows( $columnIndex, $find, $replaceWith, $charSet ); $sql_query = "UPDATE " . Util::backquote($this->table) . " SET " . Util::backquote($column) . " = CASE"; if (is_array($toReplace)) { foreach ($toReplace as $row) { $sql_query .= "\n WHEN " . Util::backquote($column) . " = '" . $GLOBALS['dbi']->escapeString($row[0]) . "' THEN '" . $GLOBALS['dbi']->escapeString($row[1]) . "'"; } } $sql_query .= " END" . " WHERE " . Util::backquote($column) . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " . $charSet . "_bin"; // here we // change the collation of the 2nd operand to a case sensitive // binary collation to make sure that the comparison // is case sensitive } else { $sql_query = "UPDATE " . Util::backquote($this->table) . " SET " . Util::backquote($column) . " =" . " REPLACE(" . Util::backquote($column) . ", '" . $find . "', '" . $replaceWith . "')" . " WHERE " . Util::backquote($column) . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we // change the collation of the 2nd operand to a case sensitive // binary collation to make sure that the comparison // is case sensitive } $this->dbi->query( $sql_query, null, DatabaseInterface::QUERY_STORE ); $GLOBALS['sql_query'] = $sql_query; } /** * Finds minimum and maximum value of a given column. * * @param string $column Column name * * @return array */ public function getColumnMinMax($column) { $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, ' . 'MAX(' . Util::backquote($column) . ') AS `max` ' . 'FROM ' . Util::backquote($this->db) . '.' . Util::backquote($this->table); $result = $this->dbi->fetchSingleRow($sql_query); return $result; } /** * Returns an array with necessary configurations to create * sub-tabs in the table_select page. * * @return array Array containing configuration (icon, text, link, id, args) * of sub-tabs */ private function _getSubTabs() { $subtabs = array(); $subtabs['search']['icon'] = 'b_search.png'; $subtabs['search']['text'] = __('Table search'); $subtabs['search']['link'] = 'tbl_select.php'; $subtabs['search']['id'] = 'tbl_search_id'; $subtabs['search']['args']['pos'] = 0; $subtabs['zoom']['icon'] = 'b_select.png'; $subtabs['zoom']['link'] = 'tbl_zoom_select.php'; $subtabs['zoom']['text'] = __('Zoom search'); $subtabs['zoom']['id'] = 'zoom_search_id'; $subtabs['replace']['icon'] = 'b_find_replace.png'; $subtabs['replace']['link'] = 'tbl_find_replace.php'; $subtabs['replace']['text'] = __('Find and replace'); $subtabs['replace']['id'] = 'find_replace_id'; return $subtabs; } /** * Builds the sql search query from the post parameters * * @return string the generated SQL query */ private function _buildSqlQuery() { $sql_query = 'SELECT '; // If only distinct values are needed $is_distinct = (isset($_POST['distinct'])) ? 'true' : 'false'; if ($is_distinct == 'true') { $sql_query .= 'DISTINCT '; } // if all column names were selected to display, we do a 'SELECT *' // (more efficient and this helps prevent a problem in IE // if one of the rows is edited and we come back to the Select results) if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) { $sql_query .= '* '; } else { $sql_query .= implode( ', ', Util::backquote($_POST['columnsToDisplay']) ); } // end if $sql_query .= ' FROM ' . Util::backquote($_POST['table']); $whereClause = $this->_generateWhereClause(); $sql_query .= $whereClause; // if the search results are to be ordered if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') { $sql_query .= ' ORDER BY ' . Util::backquote($_POST['orderByColumn']) . ' ' . $_POST['order']; } // end if return $sql_query; } /** * Provides a column's type, collation, operators list, and criteria value * to display in table search form * * @param integer $search_index Row number in table search form * @param integer $column_index Column index in ColumnNames array * * @return array Array containing column's properties */ public function getColumnProperties($search_index, $column_index) { $selected_operator = (isset($_POST['criteriaColumnOperators']) ? $_POST['criteriaColumnOperators'][$search_index] : ''); $entered_value = (isset($_POST['criteriaValues']) ? $_POST['criteriaValues'] : ''); $titles = array( 'Browse' => Util::getIcon( 'b_browse.png', __('Browse foreign values') ) ); //Gets column's type and collation $type = $this->_columnTypes[$column_index]; $collation = $this->_columnCollations[$column_index]; //Gets column's comparison operators depending on column type $func = Template::get('table/search/column_comparison_operators')->render( array( 'search_index' => $search_index, 'columnTypes' => $this->_columnTypes, 'column_index' => $column_index, 'columnNullFlags' => $this->_columnNullFlags, 'selected_operator' => $selected_operator ) ); //Gets link to browse foreign data(if any) and criteria inputbox $foreignData = PMA_getForeignData( $this->_foreigners, $this->_columnNames[$column_index], false, '', '' ); $value = Template::get('table/search/input_box')->render( array( 'str' => '', 'column_type' => (string) $type, 'column_id' => 'fieldID_', 'in_zoom_search_edit' => false, '_foreigners' => $this->_foreigners, 'column_name' => $this->_columnNames[$column_index], 'foreignData' => $foreignData, 'table' => $this->table, 'column_index' => $search_index, 'foreignMaxLimit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'], 'criteriaValues' => $entered_value, 'db' => $this->db, 'titles' => $titles, 'in_fbs' => true ) ); return array( 'type' => $type, 'collation' => $collation, 'func' => $func, 'value' => $value ); } /** * Generates the where clause for the SQL search query to be executed * * @return string the generated where clause */ private function _generateWhereClause() { if (isset($_POST['customWhereClause']) && trim($_POST['customWhereClause']) != '' ) { return ' WHERE ' . $_POST['customWhereClause']; } // If there are no search criteria set or no unary criteria operators, // return if (! isset($_POST['criteriaValues']) && ! isset($_POST['criteriaColumnOperators']) && ! isset($_POST['geom_func']) ) { return ''; } // else continue to form the where clause from column criteria values $fullWhereClause = array(); foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) { $unaryFlag = $GLOBALS['PMA_Types']->isUnaryOperator($operator); $tmp_geom_func = isset($_POST['geom_func'][$column_index]) ? $_POST['geom_func'][$column_index] : null; $whereClause = $this->_getWhereClause( $_POST['criteriaValues'][$column_index], $_POST['criteriaColumnNames'][$column_index], $_POST['criteriaColumnTypes'][$column_index], $operator, $unaryFlag, $tmp_geom_func ); if ($whereClause) { $fullWhereClause[] = $whereClause; } } // end foreach if (!empty($fullWhereClause)) { return ' WHERE ' . implode(' AND ', $fullWhereClause); } return ''; } /** * Return the where clause in case column's type is ENUM. * * @param mixed $criteriaValues Search criteria input * @param string $func_type Search function/operator * * @return string part of where clause. */ private function _getEnumWhereClause($criteriaValues, $func_type) { if (! is_array($criteriaValues)) { $criteriaValues = explode(',', $criteriaValues); } $enum_selected_count = count($criteriaValues); if ($func_type == '=' && $enum_selected_count > 1) { $func_type = 'IN'; $parens_open = '('; $parens_close = ')'; } elseif ($func_type == '!=' && $enum_selected_count > 1) { $func_type = 'NOT IN'; $parens_open = '('; $parens_close = ')'; } else { $parens_open = ''; $parens_close = ''; } $enum_where = '\'' . $GLOBALS['dbi']->escapeString($criteriaValues[0]) . '\''; for ($e = 1; $e < $enum_selected_count; $e++) { $enum_where .= ', \'' . $GLOBALS['dbi']->escapeString($criteriaValues[$e]) . '\''; } return ' ' . $func_type . ' ' . $parens_open . $enum_where . $parens_close; } /** * Return the where clause for a geometrical column. * * @param mixed $criteriaValues Search criteria input * @param string $names Name of the column on which search is submitted * @param string $func_type Search function/operator * @param string $types Type of the field * @param bool $geom_func Whether geometry functions should be applied * * @return string part of where clause. */ private function _getGeomWhereClause($criteriaValues, $names, $func_type, $types, $geom_func = null ) { $geom_unary_functions = array( 'IsEmpty' => 1, 'IsSimple' => 1, 'IsRing' => 1, 'IsClosed' => 1, ); $where = ''; // Get details about the geometry functions $geom_funcs = Util::getGISFunctions($types, true, false); // If the function takes multiple parameters if ($geom_funcs[$geom_func]['params'] > 1) { // create gis data from the criteria input $gis_data = Util::createGISData($criteriaValues); $where = $geom_func . '(' . Util::backquote($names) . ', ' . $gis_data . ')'; return $where; } // New output type is the output type of the function being applied $type = $geom_funcs[$geom_func]['type']; $geom_function_applied = $geom_func . '(' . Util::backquote($names) . ')'; // If the where clause is something like 'IsEmpty(`spatial_col_name`)' if (isset($geom_unary_functions[$geom_func]) && trim($criteriaValues) == '' ) { $where = $geom_function_applied; } elseif (in_array($type, Util::getGISDatatypes()) && ! empty($criteriaValues) ) { // create gis data from the criteria input $gis_data = Util::createGISData($criteriaValues); $where = $geom_function_applied . " " . $func_type . " " . $gis_data; } elseif (mb_strlen($criteriaValues) > 0) { $where = $geom_function_applied . " " . $func_type . " '" . $criteriaValues . "'"; } return $where; } /** * Return the where clause for query generation based on the inputs provided. * * @param mixed $criteriaValues Search criteria input * @param string $names Name of the column on which search is submitted * @param string $types Type of the field * @param string $func_type Search function/operator * @param bool $unaryFlag Whether operator unary or not * @param bool $geom_func Whether geometry functions should be applied * * @return string generated where clause. */ private function _getWhereClause($criteriaValues, $names, $types, $func_type, $unaryFlag, $geom_func = null ) { // If geometry function is set if (! empty($geom_func)) { return $this->_getGeomWhereClause( $criteriaValues, $names, $func_type, $types, $geom_func ); } $backquoted_name = Util::backquote($names); $where = ''; if ($unaryFlag) { $where = $backquoted_name . ' ' . $func_type; } elseif (strncasecmp($types, 'enum', 4) == 0 && ! empty($criteriaValues)) { $where = $backquoted_name; $where .= $this->_getEnumWhereClause($criteriaValues, $func_type); } elseif ($criteriaValues != '') { // For these types we quote the value. Even if it's another type // (like INT), for a LIKE we always quote the value. MySQL converts // strings to numbers and numbers to strings as necessary // during the comparison if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types) || mb_strpos(' ' . $func_type, 'LIKE') ) { $quot = '\''; } else { $quot = ''; } // LIKE %...% if ($func_type == 'LIKE %...%') { $func_type = 'LIKE'; $criteriaValues = '%' . $criteriaValues . '%'; } if ($func_type == 'REGEXP ^...$') { $func_type = 'REGEXP'; $criteriaValues = '^' . $criteriaValues . '$'; } if ('IN (...)' != $func_type && 'NOT IN (...)' != $func_type && 'BETWEEN' != $func_type && 'NOT BETWEEN' != $func_type ) { return $backquoted_name . ' ' . $func_type . ' ' . $quot . $GLOBALS['dbi']->escapeString($criteriaValues) . $quot; } $func_type = str_replace(' (...)', '', $func_type); //Don't explode if this is already an array //(Case for (NOT) IN/BETWEEN.) if (is_array($criteriaValues)) { $values = $criteriaValues; } else { $values = explode(',', $criteriaValues); } // quote values one by one $emptyKey = false; foreach ($values as $key => &$value) { if ('' === $value) { $emptyKey = $key; $value = 'NULL'; continue; } $value = $quot . $GLOBALS['dbi']->escapeString(trim($value)) . $quot; } if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) { $where = $backquoted_name . ' ' . $func_type . ' ' . (isset($values[0]) ? $values[0] : '') . ' AND ' . (isset($values[1]) ? $values[1] : ''); } else { //[NOT] IN if (false !== $emptyKey) { unset($values[$emptyKey]); } $wheres = array(); if (!empty($values)) { $wheres[] = $backquoted_name . ' ' . $func_type . ' (' . implode(',', $values) . ')'; } if (false !== $emptyKey) { $wheres[] = $backquoted_name . ' IS NULL'; } $where = implode(' OR ', $wheres); if (1 < count($wheres)) { $where = '(' . $where . ')'; } } } // end if return $where; } }