ÿØÿà 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ÿÙ params->get('log-deprecated', 0)) { JLog::addLogger(array('text_file' => 'deprecated.php'), JLog::ALL, array('deprecated')); } // Log everything (except deprecated APIs, these are logged separately with the option above). if ($this->params->get('log-everything', 0)) { JLog::addLogger(array('text_file' => 'everything.php'), JLog::ALL, array('deprecated', 'databasequery'), true); } // Get the application if not done by JPlugin. This may happen during upgrades from Joomla 2.5. if (!$this->app) { $this->app = JFactory::getApplication(); } // Get the db if not done by JPlugin. This may happen during upgrades from Joomla 2.5. if (!$this->db) { $this->db = JFactory::getDbo(); } $this->debugLang = $this->app->get('debug_lang'); // Skip the plugin if debug is off if ($this->debugLang == '0' && $this->app->get('debug') == '0') { return; } // Only if debugging or language debug is enabled. if (JDEBUG || $this->debugLang) { JFactory::getConfig()->set('gzip', 0); ob_start(); ob_implicit_flush(false); } $this->linkFormat = ini_get('xdebug.file_link_format'); if ($this->params->get('logs', 1)) { $priority = 0; foreach ($this->params->get('log_priorities', array()) as $p) { $const = 'JLog::' . strtoupper($p); if (!defined($const)) { continue; } $priority |= constant($const); } // Split into an array at any character other than alphabet, numbers, _, ., or - $categories = array_filter(preg_split('/[^A-Z0-9_\.-]/i', $this->params->get('log_categories', ''))); $mode = $this->params->get('log_category_mode', 0); JLog::addLogger(array('logger' => 'callback', 'callback' => array($this, 'logger')), $priority, $categories, $mode); } // Prepare disconnect handler for SQL profiling. $db = $this->db; $db->addDisconnectHandler(array($this, 'mysqlDisconnectHandler')); // Log deprecated class aliases foreach (JLoader::getDeprecatedAliases() as $deprecation) { JLog::add( sprintf( '%1$s has been aliased to %2$s and the former class name is deprecated. The alias will be removed in %3$s.', $deprecation['old'], $deprecation['new'], $deprecation['version'] ), JLog::WARNING, 'deprecated' ); } } /** * Add the CSS for debug. * We can't do this in the constructor because stuff breaks. * * @return void * * @since 2.5 */ public function onAfterDispatch() { // Only if debugging or language debug is enabled. if ((JDEBUG || $this->debugLang) && $this->isAuthorisedDisplayDebug()) { JHtml::_('stylesheet', 'cms/debug.css', array('version' => 'auto', 'relative' => true)); } // Disable asset media version if needed. if (JDEBUG && (int) $this->params->get('refresh_assets', 1) === 0) { $this->app->getDocument()->setMediaVersion(null); } // Only if debugging is enabled for SQL query popovers. if (JDEBUG && $this->isAuthorisedDisplayDebug()) { JHtml::_('bootstrap.tooltip'); JHtml::_('bootstrap.popover', '.hasPopover', array('placement' => 'top')); } } /** * Show the debug info. * * @return void * * @since 1.6 */ public function onAfterRespond() { // Do not render if debugging or language debug is not enabled. if (!JDEBUG && !$this->debugLang) { return; } // User has to be authorised to see the debug information. if (!$this->isAuthorisedDisplayDebug()) { return; } // Only render for HTML output. if (JFactory::getDocument()->getType() !== 'html') { return; } // Capture output. $contents = ob_get_contents(); if ($contents) { ob_end_clean(); } // No debug for Safari and Chrome redirection. if (strpos($contents, ''; if (JDEBUG) { if (JError::getErrors()) { $html[] = $this->display('errors'); } if ($this->params->get('session', 1)) { $html[] = $this->display('session'); } if ($this->params->get('profile', 1)) { $html[] = $this->display('profile_information'); } if ($this->params->get('memory', 1)) { $html[] = $this->display('memory_usage'); } if ($this->params->get('queries', 1)) { $html[] = $this->display('queries'); } if (!empty($this->logEntries) && $this->params->get('logs', 1)) { $html[] = $this->display('logs'); } } if ($this->debugLang) { if ($this->params->get('language_errorfiles', 1)) { $languageErrors = JFactory::getLanguage()->getErrorFiles(); $html[] = $this->display('language_files_in_error', $languageErrors); } if ($this->params->get('language_files', 1)) { $html[] = $this->display('language_files_loaded'); } if ($this->params->get('language_strings', 1)) { $html[] = $this->display('untranslated_strings'); } } foreach (self::$displayCallbacks as $name => $callable) { $html[] = $this->displayCallback($name, $callable); } $html[] = ''; echo str_replace('', implode('', $html) . '', $contents); } /** * Add a display callback to be rendered with the debug console. * * @param string $name The name of the callable, this is used to generate the section title. * @param callable $callable The callback function to be added. * * @return boolean * * @since 3.7.0 * @throws InvalidArgumentException */ public static function addDisplayCallback($name, $callable) { // TODO - When PHP 5.4 is the minimum the parameter should be typehinted "callable" and this check removed if (!is_callable($callable)) { throw new InvalidArgumentException('A valid callback function must be given.'); } self::$displayCallbacks[$name] = $callable; return true; } /** * Remove a registered display callback * * @param string $name The name of the callable. * * @return boolean * * @since 3.7.0 */ public static function removeDisplayCallback($name) { unset(self::$displayCallbacks[$name]); return true; } /** * Method to check if the current user is allowed to see the debug information or not. * * @return boolean True if access is allowed. * * @since 3.0 */ private function isAuthorisedDisplayDebug() { static $result = null; if ($result !== null) { return $result; } // If the user is not allowed to view the output then end here. $filterGroups = (array) $this->params->get('filter_groups', array()); if (!empty($filterGroups)) { $userGroups = JFactory::getUser()->get('groups'); if (!array_intersect($filterGroups, $userGroups)) { $result = false; return false; } } $result = true; return true; } /** * General display method. * * @param string $item The item to display. * @param array $errors Errors occurred during execution. * * @return string * * @since 2.5 */ protected function display($item, array $errors = array()) { $title = JText::_('PLG_DEBUG_' . strtoupper($item)); $status = ''; if (count($errors)) { $status = ' dbg-error'; } $fncName = 'display' . ucfirst(str_replace('_', '', $item)); if (!method_exists($this, $fncName)) { return __METHOD__ . ' -- Unknown method: ' . $fncName . '
'; } $html = array(); $js = "toggleContainer('dbg_container_" . $item . "');"; $class = 'dbg-header' . $status; $html[] = ''; // @todo set with js.. ? $style = ' style="display: none;"'; $html[] = '
'; $html[] = $this->$fncName(); $html[] = '
'; return implode('', $html); } /** * Display method for callback functions. * * @param string $name The name of the callable. * @param callable $callable The callable function. * * @return string * * @since 3.7.0 */ protected function displayCallback($name, $callable) { $title = JText::_('PLG_DEBUG_' . strtoupper($name)); $html = array(); $js = "toggleContainer('dbg_container_" . $name . "');"; $class = 'dbg-header'; $html[] = ''; // @todo set with js.. ? $style = ' style="display: none;"'; $html[] = '
'; $html[] = call_user_func($callable); $html[] = '
'; return implode('', $html); } /** * Display session information. * * Called recursively. * * @param string $key A session key. * @param mixed $session The session array, initially null. * @param integer $id Used to identify the DIV for the JavaScript toggling code. * * @return string * * @since 2.5 */ protected function displaySession($key = '', $session = null, $id = 0) { if (!$session) { $session = JFactory::getSession()->getData(); } $html = array(); static $id; if (!is_array($session)) { $html[] = $key . '
' . $this->prettyPrintJSON($session) . '
' . PHP_EOL; } else { foreach ($session as $sKey => $entries) { $display = true; if (is_array($entries) && $entries) { $display = false; } if (is_object($entries)) { $o = ArrayHelper::fromObject($entries); if ($o) { $entries = $o; $display = false; } } if (!$display) { $js = "toggleContainer('dbg_container_session" . $id . '_' . $sKey . "');"; $html[] = ''; // @todo set with js.. ? $style = ' style="display: none;"'; $html[] = '
'; $id++; // Recurse... $this->displaySession($sKey, $entries, $id); $html[] = '
'; continue; } if (is_array($entries)) { $entries = implode($entries); } if (is_string($entries)) { $html[] = $sKey . '
' . $this->prettyPrintJSON($entries) . '
' . PHP_EOL; } } } return implode('', $html); } /** * Display errors. * * @return string * * @since 2.5 */ protected function displayErrors() { $html = array(); $html[] = '
    '; while ($error = JError::getError(true)) { $col = (E_WARNING == $error->get('level')) ? 'red' : 'orange'; $html[] = '
  1. '; $html[] = '' . $error->getMessage() . '
    '; $info = $error->get('info'); if ($info) { $html[] = '
    ' . print_r($info, true) . '

    '; } $html[] = $this->renderBacktrace($error); $html[] = '
  2. '; } $html[] = '
'; return implode('', $html); } /** * Display profile information. * * @return string * * @since 2.5 */ protected function displayProfileInformation() { $html = array(); $htmlMarks = array(); $totalTime = 0; $totalMem = 0; $marks = array(); foreach (JProfiler::getInstance('Application')->getMarks() as $mark) { $totalTime += $mark->time; $totalMem += (float) $mark->memory; $htmlMark = sprintf( JText::_('PLG_DEBUG_TIME') . ': %.2f ms / %.2f ms' . ' ' . JText::_('PLG_DEBUG_MEMORY') . ': %0.3f MB / %0.2f MB' . ' %s: %s', $mark->time, $mark->totalTime, $mark->memory, $mark->totalMemory, $mark->prefix, $mark->label ); $marks[] = (object) array( 'time' => $mark->time, 'memory' => $mark->memory, 'html' => $htmlMark, 'tip' => $mark->label, ); } $avgTime = $totalTime / count($marks); $avgMem = $totalMem / count($marks); foreach ($marks as $mark) { if ($mark->time > $avgTime * 1.5) { $barClass = 'bar-danger'; $labelClass = 'label-important label-danger'; } elseif ($mark->time < $avgTime / 1.5) { $barClass = 'bar-success'; $labelClass = 'label-success'; } else { $barClass = 'bar-warning'; $labelClass = 'label-warning'; } if ($mark->memory > $avgMem * 1.5) { $barClassMem = 'bar-danger'; $labelClassMem = 'label-important label-danger'; } elseif ($mark->memory < $avgMem / 1.5) { $barClassMem = 'bar-success'; $labelClassMem = 'label-success'; } else { $barClassMem = 'bar-warning'; $labelClassMem = 'label-warning'; } $barClass .= " progress-$barClass"; $barClassMem .= " progress-$barClassMem"; $bars[] = (object) array( 'width' => round($mark->time / ($totalTime / 100), 4), 'class' => $barClass, 'tip' => $mark->tip . ' ' . round($mark->time, 2) . ' ms', ); $barsMem[] = (object) array( 'width' => round((float) $mark->memory / ($totalMem / 100), 4), 'class' => $barClassMem, 'tip' => $mark->tip . ' ' . round($mark->memory, 3) . ' MB', ); $htmlMarks[] = '
' . str_replace('label-time', $labelClass, str_replace('label-memory', $labelClassMem, $mark->html)) . '
'; } $html[] = '

' . JText::_('PLG_DEBUG_TIME') . '

'; $html[] = $this->renderBars($bars, 'profile'); $html[] = '

' . JText::_('PLG_DEBUG_MEMORY') . '

'; $html[] = $this->renderBars($barsMem, 'profile'); $html[] = '
' . implode('', $htmlMarks) . '
'; $db = $this->db; // fix for support custom shutdown function via register_shutdown_function(). $db->disconnect(); $log = $db->getLog(); if ($log) { $timings = $db->getTimings(); if ($timings) { $totalQueryTime = 0.0; $lastStart = null; foreach ($timings as $k => $v) { if (!($k % 2)) { $lastStart = $v; } else { $totalQueryTime += $v - $lastStart; } } $totalQueryTime *= 1000; if ($totalQueryTime > ($totalTime * 0.25)) { $labelClass = 'label-important'; } elseif ($totalQueryTime < ($totalTime * 0.15)) { $labelClass = 'label-success'; } else { $labelClass = 'label-warning'; } $html[] = '
' . JText::sprintf( 'PLG_DEBUG_QUERIES_TIME', sprintf('%.2f ms', $totalQueryTime) ) . '
'; if ($this->params->get('log-executed-sql', 0)) { $this->writeToFile(); } } } return implode('', $html); } /** * Display memory usage. * * @return string * * @since 2.5 */ protected function displayMemoryUsage() { $bytes = memory_get_usage(); return '' . JHtml::_('number.bytes', $bytes) . '' . ' (' . number_format($bytes, 0, JText::_('DECIMALS_SEPARATOR'), JText::_('THOUSANDS_SEPARATOR')) . ' ' . JText::_('PLG_DEBUG_BYTES') . ')'; } /** * Display logged queries. * * @return string * * @since 2.5 */ protected function displayQueries() { $db = $this->db; $log = $db->getLog(); if (!$log) { return null; } $timings = $db->getTimings(); $callStacks = $db->getCallStacks(); $db->setDebug(false); $selectQueryTypeTicker = array(); $otherQueryTypeTicker = array(); $timing = array(); $maxtime = 0; if (isset($timings[0])) { $startTime = $timings[0]; $endTime = $timings[count($timings) - 1]; $totalBargraphTime = $endTime - $startTime; if ($totalBargraphTime > 0) { foreach ($log as $id => $query) { if (isset($timings[$id * 2 + 1])) { // Compute the query time: $timing[$k] = array( queryTime, timeBetweenQueries ). $timing[$id] = array( ($timings[$id * 2 + 1] - $timings[$id * 2]) * 1000, $id > 0 ? ($timings[$id * 2] - $timings[$id * 2 - 1]) * 1000 : 0, ); $maxtime = max($maxtime, $timing[$id]['0']); } } } } else { $startTime = null; $totalBargraphTime = 1; } $bars = array(); $info = array(); $totalQueryTime = 0; $duplicates = array(); foreach ($log as $id => $query) { $did = md5($query); if (!isset($duplicates[$did])) { $duplicates[$did] = array(); } $duplicates[$did][] = $id; if ($timings && isset($timings[$id * 2 + 1])) { // Compute the query time. $queryTime = ($timings[$id * 2 + 1] - $timings[$id * 2]) * 1000; $totalQueryTime += $queryTime; // Run an EXPLAIN EXTENDED query on the SQL query if possible. $hasWarnings = false; $hasWarningsInProfile = false; if (isset($this->explains[$id])) { $explain = $this->tableToHtml($this->explains[$id], $hasWarnings); } else { $explain = JText::sprintf('PLG_DEBUG_QUERY_EXPLAIN_NOT_POSSIBLE', htmlspecialchars($query)); } // Run a SHOW PROFILE query. $profile = ''; if (isset($this->sqlShowProfileEach[$id]) && $db->getServerType() === 'mysql') { $profileTable = $this->sqlShowProfileEach[$id]; $profile = $this->tableToHtml($profileTable, $hasWarningsInProfile); } // How heavy should the string length count: 0 - 1. $ratio = 0.5; $timeScore = $queryTime / ((strlen($query) + 1) * $ratio) * 200; // Determine color of bargraph depending on query speed and presence of warnings in EXPLAIN. if ($timeScore > 10) { $barClass = 'bar-danger'; $labelClass = 'label-important'; } elseif ($hasWarnings || $timeScore > 5) { $barClass = 'bar-warning'; $labelClass = 'label-warning'; } else { $barClass = 'bar-success'; $labelClass = 'label-success'; } // Computes bargraph as follows: Position begin and end of the bar relatively to whole execution time. // TODO: $prevBar is not used anywhere. Remove? $prevBar = $id && isset($bars[$id - 1]) ? $bars[$id - 1] : 0; $barPre = round($timing[$id][1] / ($totalBargraphTime * 10), 4); $barWidth = round($timing[$id][0] / ($totalBargraphTime * 10), 4); $minWidth = 0.3; if ($barWidth < $minWidth) { $barPre -= ($minWidth - $barWidth); if ($barPre < 0) { $minWidth += $barPre; $barPre = 0; } $barWidth = $minWidth; } $bars[$id] = (object) array( 'class' => $barClass, 'width' => $barWidth, 'pre' => $barPre, 'tip' => sprintf('%.2f ms', $queryTime), ); $info[$id] = (object) array( 'class' => $labelClass, 'explain' => $explain, 'profile' => $profile, 'hasWarnings' => $hasWarnings, ); } } // Remove single queries from $duplicates. $total_duplicates = 0; foreach ($duplicates as $did => $dups) { if (count($dups) < 2) { unset($duplicates[$did]); } else { $total_duplicates += count($dups); } } // Fix first bar width. $minWidth = 0.3; if ($bars[0]->width < $minWidth && isset($bars[1])) { $bars[1]->pre -= ($minWidth - $bars[0]->width); if ($bars[1]->pre < 0) { $minWidth += $bars[1]->pre; $bars[1]->pre = 0; } $bars[0]->width = $minWidth; } $memoryUsageNow = memory_get_usage(); $list = array(); foreach ($log as $id => $query) { // Start query type ticker additions. $fromStart = stripos($query, 'from'); $whereStart = stripos($query, 'where', $fromStart); if ($whereStart === false) { $whereStart = stripos($query, 'order by', $fromStart); } if ($whereStart === false) { $whereStart = strlen($query) - 1; } $fromString = substr($query, 0, $whereStart); $fromString = str_replace(array("\t", "\n"), ' ', $fromString); $fromString = trim($fromString); // Initialise the select/other query type counts the first time. if (!isset($selectQueryTypeTicker[$fromString])) { $selectQueryTypeTicker[$fromString] = 0; } if (!isset($otherQueryTypeTicker[$fromString])) { $otherQueryTypeTicker[$fromString] = 0; } // Increment the count. if (stripos($query, 'select') === 0) { $selectQueryTypeTicker[$fromString]++; unset($otherQueryTypeTicker[$fromString]); } else { $otherQueryTypeTicker[$fromString]++; unset($selectQueryTypeTicker[$fromString]); } $text = $this->highlightQuery($query); if ($timings && isset($timings[$id * 2 + 1])) { // Compute the query time. $queryTime = ($timings[$id * 2 + 1] - $timings[$id * 2]) * 1000; // Timing // Formats the output for the query time with EXPLAIN query results as tooltip: $htmlTiming = '
'; $htmlTiming .= JText::sprintf( 'PLG_DEBUG_QUERY_TIME', sprintf( '%.2f ms', $info[$id]->class, $timing[$id]['0'] ) ); if ($timing[$id]['1']) { $htmlTiming .= ' ' . JText::sprintf( 'PLG_DEBUG_QUERY_AFTER_LAST', sprintf('%.2f ms', $timing[$id]['1']) ); } $htmlTiming .= ''; if (isset($callStacks[$id][0]['memory'])) { $memoryUsed = $callStacks[$id][0]['memory'][1] - $callStacks[$id][0]['memory'][0]; $memoryBeforeQuery = $callStacks[$id][0]['memory'][0]; // Determine colour of query memory usage. if ($memoryUsed > 0.1 * $memoryUsageNow) { $labelClass = 'label-important'; } elseif ($memoryUsed > 0.05 * $memoryUsageNow) { $labelClass = 'label-warning'; } else { $labelClass = 'label-success'; } $htmlTiming .= ' ' . '' . JText::sprintf( 'PLG_DEBUG_MEMORY_USED_FOR_QUERY', sprintf('%.3f MB', $memoryUsed / 1048576), sprintf('%.3f MB', $memoryBeforeQuery / 1048576) ) . ''; if ($callStacks[$id][0]['memory'][2] !== null) { // Determine colour of number or results. $resultsReturned = $callStacks[$id][0]['memory'][2]; if ($resultsReturned > 3000) { $labelClass = 'label-important'; } elseif ($resultsReturned > 1000) { $labelClass = 'label-warning'; } elseif ($resultsReturned == 0) { $labelClass = ''; } else { $labelClass = 'label-success'; } $htmlResultsReturned = '' . (int) $resultsReturned . ''; $htmlTiming .= ' ' . JText::sprintf('PLG_DEBUG_ROWS_RETURNED_BY_QUERY', $htmlResultsReturned) . ''; } } $htmlTiming .= '
'; // Bar. $htmlBar = $this->renderBars($bars, 'query', $id); // Profile query. $title = JText::_('PLG_DEBUG_PROFILE'); if (!$info[$id]->profile) { $title = '' . $title . ''; } $htmlProfile = $info[$id]->profile ?: JText::_('PLG_DEBUG_NO_PROFILE'); $htmlAccordions = JHtml::_( 'bootstrap.startAccordion', 'dbg_query_' . $id, array( 'active' => $info[$id]->hasWarnings ? ('dbg_query_explain_' . $id) : '', ) ); $htmlAccordions .= JHtml::_('bootstrap.addSlide', 'dbg_query_' . $id, JText::_('PLG_DEBUG_EXPLAIN'), 'dbg_query_explain_' . $id) . $info[$id]->explain . JHtml::_('bootstrap.endSlide'); $htmlAccordions .= JHtml::_('bootstrap.addSlide', 'dbg_query_' . $id, $title, 'dbg_query_profile_' . $id) . $htmlProfile . JHtml::_('bootstrap.endSlide'); // Call stack and back trace. if (isset($callStacks[$id])) { $htmlAccordions .= JHtml::_('bootstrap.addSlide', 'dbg_query_' . $id, JText::_('PLG_DEBUG_CALL_STACK'), 'dbg_query_callstack_' . $id) . $this->renderCallStack($callStacks[$id]) . JHtml::_('bootstrap.endSlide'); } $htmlAccordions .= JHtml::_('bootstrap.endAccordion'); $did = md5($query); if (isset($duplicates[$did])) { $dups = array(); foreach ($duplicates[$did] as $dup) { if ($dup != $id) { $dups[] = '#' . ($dup + 1) . ''; } } $htmlQuery = '
' . JText::_('PLG_DEBUG_QUERY_DUPLICATES') . ': ' . implode('  ', $dups) . '
' . '
' . $text . '
'; } else { $htmlQuery = '
' . $text . '
'; } $list[] = '' . $htmlTiming . $htmlBar . $htmlQuery . $htmlAccordions; } else { $list[] = '
' . $text . '
'; } } $totalTime = 0; foreach (JProfiler::getInstance('Application')->getMarks() as $mark) { $totalTime += $mark->time; } if ($totalQueryTime > ($totalTime * 0.25)) { $labelClass = 'label-important'; } elseif ($totalQueryTime < ($totalTime * 0.15)) { $labelClass = 'label-success'; } else { $labelClass = 'label-warning'; } if ($this->totalQueries === 0) { $this->totalQueries = $db->getCount(); } $html = array(); $html[] = '

' . JText::sprintf('PLG_DEBUG_QUERIES_LOGGED', $this->totalQueries) . sprintf(' %.2f ms', $totalQueryTime) . '


'; if ($total_duplicates) { $html[] = '
' . '

' . JText::sprintf('PLG_DEBUG_QUERY_DUPLICATES_TOTAL_NUMBER', $total_duplicates) . '

'; foreach ($duplicates as $dups) { $links = array(); foreach ($dups as $dup) { $links[] = '#' . ($dup + 1) . ''; } $html[] = '
' . JText::sprintf('PLG_DEBUG_QUERY_DUPLICATES_NUMBER', count($links)) . ': ' . implode('  ', $links) . '
'; } $html[] = '
'; } $html[] = '
  1. ' . implode('
  2. ', $list) . '
'; if (!$this->params->get('query_types', 1)) { return implode('', $html); } // Get the totals for the query types. $totalSelectQueryTypes = count($selectQueryTypeTicker); $totalOtherQueryTypes = count($otherQueryTypeTicker); $totalQueryTypes = $totalSelectQueryTypes + $totalOtherQueryTypes; $html[] = '

' . JText::sprintf('PLG_DEBUG_QUERY_TYPES_LOGGED', $totalQueryTypes) . '

'; if ($totalSelectQueryTypes) { $html[] = '
' . JText::_('PLG_DEBUG_SELECT_QUERIES') . '
'; arsort($selectQueryTypeTicker); $list = array(); foreach ($selectQueryTypeTicker as $query => $occurrences) { $list[] = '
'
					. JText::sprintf('PLG_DEBUG_QUERY_TYPE_AND_OCCURRENCES', $this->highlightQuery($query), $occurrences)
					. '
'; } $html[] = '
  1. ' . implode('
  2. ', $list) . '
'; } if ($totalOtherQueryTypes) { $html[] = '
' . JText::_('PLG_DEBUG_OTHER_QUERIES') . '
'; arsort($otherQueryTypeTicker); $list = array(); foreach ($otherQueryTypeTicker as $query => $occurrences) { $list[] = '
'
					. JText::sprintf('PLG_DEBUG_QUERY_TYPE_AND_OCCURRENCES', $this->highlightQuery($query), $occurrences)
					. '
'; } $html[] = '
  1. ' . implode('
  2. ', $list) . '
'; } return implode('', $html); } /** * Render the bars. * * @param array &$bars Array of bar data * @param string $class Optional class for items * @param integer $id Id if the bar to highlight * * @return string * * @since 3.1.2 */ protected function renderBars(&$bars, $class = '', $id = null) { $html = array(); foreach ($bars as $i => $bar) { if (isset($bar->pre) && $bar->pre) { $html[] = '
'; } $barClass = trim('bar dbg-bar progress-bar ' . (isset($bar->class) ? $bar->class : '')); if ($id !== null && $i == $id) { $barClass .= ' dbg-bar-active'; } $tip = ''; if (isset($bar->tip) && $bar->tip) { $barClass .= ' hasTooltip'; $tip = JHtml::_('tooltipText', $bar->tip, '', 0); } $html[] = ''; } return '
' . implode('', $html) . '
'; } /** * Render an HTML table based on a multi-dimensional array. * * @param array $table An array of tabular data. * @param boolean &$hasWarnings Changes value to true if warnings are displayed, otherwise untouched * * @return string * * @since 3.1.2 */ protected function tableToHtml($table, &$hasWarnings) { if (!$table) { return null; } $html = array(); $html[] = ''; $html[] = ''; $html[] = ''; foreach (array_keys($table[0]) as $k) { $html[] = ''; } $html[] = ''; $html[] = ''; $html[] = ''; $durations = array(); foreach ($table as $tr) { if (isset($tr['Duration'])) { $durations[] = $tr['Duration']; } } rsort($durations, SORT_NUMERIC); foreach ($table as $tr) { $html[] = ''; foreach ($tr as $k => $td) { if ($td === null) { // Display null's as 'NULL'. $td = 'NULL'; } // Treat special columns. if ($k === 'Duration') { if ($td >= 0.001 && ($td == $durations[0] || (isset($durations[1]) && $td == $durations[1]))) { // Duration column with duration value of more than 1 ms and within 2 top duration in SQL engine: Highlight warning. $html[] = ''; } $html[] = ''; } $html[] = ''; $html[] = '
' . htmlspecialchars($k) . '
'; $hasWarnings = true; } else { $html[] = ''; } // Display duration in milliseconds with the unit instead of seconds. $html[] = sprintf('%.2f ms', $td * 1000); } elseif ($k === 'Error') { // An error in the EXPLAIN query occurred, display it instead of the result (means original query had syntax error most probably). $html[] = '' . htmlspecialchars($td); $hasWarnings = true; } elseif ($k === 'key') { if ($td === 'NULL') { // Displays query parts which don't use a key with warning: $html[] = '' . '' . JText::_('PLG_DEBUG_WARNING_NO_INDEX') . '' . ''; $hasWarnings = true; } else { $html[] = '' . htmlspecialchars($td) . ''; } } elseif ($k === 'Extra') { $htmlTd = htmlspecialchars($td); // Replace spaces with   (non-breaking spaces) for less tall tables displayed. $htmlTd = preg_replace('/([^;]) /', '\1 ', $htmlTd); // Displays warnings for "Using filesort": $htmlTdWithWarnings = str_replace( 'Using filesort', '' . JText::_('PLG_DEBUG_WARNING_USING_FILESORT') . '', $htmlTd ); if ($htmlTdWithWarnings !== $htmlTd) { $hasWarnings = true; } $html[] = '' . $htmlTdWithWarnings; } else { $html[] = '' . htmlspecialchars($td); } $html[] = '
'; return implode('', $html); } /** * Disconnect handler for database to collect profiling and explain information. * * @param JDatabaseDriver &$db Database object. * * @return void * * @since 3.1.2 */ public function mysqlDisconnectHandler(&$db) { $db->setDebug(false); $this->totalQueries = $db->getCount(); $dbVersion5037 = $db->getServerType() === 'mysql' && version_compare($db->getVersion(), '5.0.37', '>='); if ($dbVersion5037) { try { // Check if profiling is enabled. $db->setQuery("SHOW VARIABLES LIKE 'have_profiling'"); $hasProfiling = $db->loadResult(); if ($hasProfiling) { // Run a SHOW PROFILE query. $db->setQuery('SHOW PROFILES'); $this->sqlShowProfiles = $db->loadAssocList(); if ($this->sqlShowProfiles) { foreach ($this->sqlShowProfiles as $qn) { // Run SHOW PROFILE FOR QUERY for each query where a profile is available (max 100). $db->setQuery('SHOW PROFILE FOR QUERY ' . (int) $qn['Query_ID']); $this->sqlShowProfileEach[(int) ($qn['Query_ID'] - 1)] = $db->loadAssocList(); } } } else { $this->sqlShowProfileEach[0] = array(array('Error' => 'MySql have_profiling = off')); } } catch (Exception $e) { $this->sqlShowProfileEach[0] = array(array('Error' => $e->getMessage())); } } if (in_array($db->getServerType(), array('mysql', 'postgresql'), true)) { $log = $db->getLog(); foreach ($log as $k => $query) { $dbVersion56 = $db->getServerType() === 'mysql' && version_compare($db->getVersion(), '5.6', '>='); if ((stripos($query, 'select') === 0) || ($dbVersion56 && ((stripos($query, 'delete') === 0) || (stripos($query, 'update') === 0)))) { try { $db->setQuery('EXPLAIN ' . ($dbVersion56 ? 'EXTENDED ' : '') . $query); $this->explains[$k] = $db->loadAssocList(); } catch (Exception $e) { $this->explains[$k] = array(array('Error' => $e->getMessage())); } } } } } /** * Displays errors in language files. * * @return string * * @since 2.5 */ protected function displayLanguageFilesInError() { $errorfiles = JFactory::getLanguage()->getErrorFiles(); if (!count($errorfiles)) { return '

' . JText::_('JNONE') . '

'; } $html = array(); $html[] = '
    '; foreach ($errorfiles as $file => $error) { $html[] = '
  • ' . $this->formatLink($file) . str_replace($file, '', $error) . '
  • '; } $html[] = '
'; return implode('', $html); } /** * Display loaded language files. * * @return string * * @since 2.5 */ protected function displayLanguageFilesLoaded() { $html = array(); $html[] = '
    '; foreach (JFactory::getLanguage()->getPaths() as /* $extension => */ $files) { foreach ($files as $file => $status) { $html[] = '
  • '; $html[] = $status ? JText::_('PLG_DEBUG_LANG_LOADED') : JText::_('PLG_DEBUG_LANG_NOT_LOADED'); $html[] = ' : '; $html[] = $this->formatLink($file); $html[] = '
  • '; } } $html[] = '
'; return implode('', $html); } /** * Display untranslated language strings. * * @return string * * @since 2.5 */ protected function displayUntranslatedStrings() { $stripFirst = $this->params->get('strip-first', 1); $stripPref = $this->params->get('strip-prefix'); $stripSuff = $this->params->get('strip-suffix'); $orphans = JFactory::getLanguage()->getOrphans(); if (!count($orphans)) { return '

' . JText::_('JNONE') . '

'; } ksort($orphans, SORT_STRING); $guesses = array(); foreach ($orphans as $key => $occurance) { if (is_array($occurance) && isset($occurance[0])) { $info = $occurance[0]; $file = $info['file'] ?: ''; if (!isset($guesses[$file])) { $guesses[$file] = array(); } // Prepare the key. if (($pos = strpos($info['string'], '=')) > 0) { $parts = explode('=', $info['string']); $key = $parts[0]; $guess = $parts[1]; } else { $guess = str_replace('_', ' ', $info['string']); if ($stripFirst) { $parts = explode(' ', $guess); if (count($parts) > 1) { array_shift($parts); $guess = implode(' ', $parts); } } $guess = trim($guess); if ($stripPref) { $guess = trim(preg_replace(chr(1) . '^' . $stripPref . chr(1) . 'i', '', $guess)); } if ($stripSuff) { $guess = trim(preg_replace(chr(1) . $stripSuff . '$' . chr(1) . 'i', '', $guess)); } } $key = strtoupper(trim($key)); $key = preg_replace('#\s+#', '_', $key); $key = preg_replace('#\W#', '', $key); // Prepare the text. $guesses[$file][] = $key . '="' . $guess . '"'; } } $html = array(); foreach ($guesses as $file => $keys) { $html[] = "\n\n# " . ($file ? $this->formatLink($file) : JText::_('PLG_DEBUG_UNKNOWN_FILE')) . "\n\n"; $html[] = implode("\n", $keys); } return '
' . implode('', $html) . '
'; } /** * Simple highlight for SQL queries. * * @param string $query The query to highlight. * * @return string * * @since 2.5 */ protected function highlightQuery($query) { $newlineKeywords = '#\b(FROM|LEFT|INNER|OUTER|WHERE|SET|VALUES|ORDER|GROUP|HAVING|LIMIT|ON|AND|CASE)\b#i'; $query = htmlspecialchars($query, ENT_QUOTES); $query = preg_replace($newlineKeywords, '
  \\0', $query); $regex = array( // Tables are identified by the prefix. '/(=)/' => '$1', // All uppercase words have a special meaning. '/(?)([A-Z_]{2,})(?!\w)/x' => '$1', // Tables are identified by the prefix. '/(' . $this->db->getPrefix() . '[a-z_0-9]+)/' => '$1', ); $query = preg_replace(array_keys($regex), array_values($regex), $query); $query = str_replace('*', '*', $query); return $query; } /** * Render the backtrace. * * Stolen from JError to prevent it's removal. * * @param Exception $error The Exception object to be rendered. * * @return string Rendered backtrace. * * @since 2.5 */ protected function renderBacktrace($error) { return JLayoutHelper::render('joomla.error.backtrace', array('backtrace' => $error->getTrace())); } /** * Replaces the Joomla! root with "JROOT" to improve readability. * Formats a link with a special value xdebug.file_link_format * from the php.ini file. * * @param string $file The full path to the file. * @param string $line The line number. * * @return string * * @since 2.5 */ protected function formatLink($file, $line = '') { return JHtml::_('debug.xdebuglink', $file, $line); } /** * Store log messages so they can be displayed later. * This function is passed log entries by JLogLoggerCallback. * * @param JLogEntry $entry A log entry. * * @return void * * @since 3.1 */ public function logger(JLogEntry $entry) { $this->logEntries[] = $entry; } /** * Display log messages. * * @return string * * @since 3.1 */ protected function displayLogs() { $priorities = array( JLog::EMERGENCY => 'EMERGENCY', JLog::ALERT => 'ALERT', JLog::CRITICAL => 'CRITICAL', JLog::ERROR => 'ERROR', JLog::WARNING => 'WARNING', JLog::NOTICE => 'NOTICE', JLog::INFO => 'INFO', JLog::DEBUG => 'DEBUG', ); $out = ''; $logEntriesTotal = count($this->logEntries); // SQL log entries $showExecutedSQL = $this->params->get('log-executed-sql', 0); if (!$showExecutedSQL) { $logEntriesDatabasequery = count( array_filter( $this->logEntries, function ($logEntry) { return $logEntry->category === 'databasequery'; } ) ); $logEntriesTotal -= $logEntriesDatabasequery; } // Deprecated log entries $logEntriesDeprecated = count( array_filter( $this->logEntries, function ($logEntry) { return $logEntry->category === 'deprecated'; } ) ); $showDeprecated = $this->params->get('log-deprecated', 0); if (!$showDeprecated) { $logEntriesTotal -= $logEntriesDeprecated; } $showEverything = $this->params->get('log-everything', 0); $out .= '

' . JText::sprintf('PLG_DEBUG_LOGS_LOGGED', $logEntriesTotal) . '


'; if ($showDeprecated && $logEntriesDeprecated > 0) { $out .= '

' . JText::sprintf('PLG_DEBUG_LOGS_DEPRECATED_FOUND_TITLE', $logEntriesDeprecated) . '

' . JText::_('PLG_DEBUG_LOGS_DEPRECATED_FOUND_TEXT') . '

'; } $out .= '
    '; $count = 1; foreach ($this->logEntries as $entry) { // Don't show database queries if not selected. if (!$showExecutedSQL && $entry->category === 'databasequery') { continue; } // Don't show deprecated logs if not selected. if (!$showDeprecated && $entry->category === 'deprecated') { continue; } // Don't show everything logs if not selected. if (!$showEverything && !in_array($entry->category, array('deprecated', 'databasequery'), true)) { continue; } $out .= '
  1. '; $out .= '
    ' . $priorities[$entry->priority] . ' ' . $entry->category . '

    ' . $entry->message . '
    '; if ($entry->callStack) { $out .= JHtml::_('bootstrap.startAccordion', 'dbg_logs_' . $count, array('active' => '')); $out .= JHtml::_('bootstrap.addSlide', 'dbg_logs_' . $count, JText::_('PLG_DEBUG_CALL_STACK'), 'dbg_logs_backtrace_' . $count); $out .= $this->renderCallStack($entry->callStack); $out .= JHtml::_('bootstrap.endSlide'); $out .= JHtml::_('bootstrap.endAccordion'); } $out .= '
  2. '; $count++; } $out .= '
'; return $out; } /** * Renders call stack and back trace in HTML. * * @param array $callStack The call stack and back trace array. * * @return string The call stack and back trace in HMTL format. * * @since 3.5 */ protected function renderCallStack(array $callStack = array()) { $htmlCallStack = ''; if ($callStack !== null) { $htmlCallStack .= '
'; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $count = count($callStack); foreach ($callStack as $call) { // Dont' back trace log classes. if (isset($call['class']) && strpos($call['class'], 'JLog') !== false) { $count--; continue; } $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $htmlCallStack .= ''; $count--; } $htmlCallStack .= ''; $htmlCallStack .= '
#' . JText::_('PLG_DEBUG_CALL_STACK_CALLER') . '' . JText::_('PLG_DEBUG_CALL_STACK_FILE_AND_LINE') . '
' . $count . ''; if (isset($call['class'])) { // If entry has Class/Method print it. $htmlCallStack .= htmlspecialchars($call['class'] . $call['type'] . $call['function']) . '()'; } else { if (isset($call['args'])) { // If entry has args is a require/include. $htmlCallStack .= htmlspecialchars($call['function']) . ' ' . $this->formatLink($call['args'][0]); } else { // It's a function. $htmlCallStack .= htmlspecialchars($call['function']) . '()'; } } $htmlCallStack .= ''; // If entry doesn't have line and number the next is a call_user_func. if (!isset($call['file']) && !isset($call['line'])) { $htmlCallStack .= JText::_('PLG_DEBUG_CALL_STACK_SAME_FILE'); } // If entry has file and line print it. else { $htmlCallStack .= $this->formatLink(htmlspecialchars($call['file']), htmlspecialchars($call['line'])); } $htmlCallStack .= '
'; $htmlCallStack .= '
'; if (!$this->linkFormat) { $htmlCallStack .= '
['; $htmlCallStack .= JText::_('PLG_DEBUG_LINK_FORMAT') . ']
'; } } return $htmlCallStack; } /** * Pretty print JSON with colors. * * @param string $json The json raw string. * * @return string The json string pretty printed. * * @since 3.5 */ protected function prettyPrintJSON($json = '') { // In PHP 5.4.0 or later we have pretty print option. if (version_compare(PHP_VERSION, '5.4', '>=')) { $json = json_encode($json, JSON_PRETTY_PRINT); } // Add some colors $json = preg_replace('#"([^"]+)":#', '"$1":', $json); $json = preg_replace('#"(|[^"]+)"(\n|\r\n|,)#', '"$1"$2', $json); $json = str_replace('null,', 'null,', $json); return $json; } /** * Write query to the log file * * @return void * * @since 3.5 */ protected function writeToFile() { $app = JFactory::getApplication(); $domain = $app->isClient('site') ? 'site' : 'admin'; $input = $app->input; $file = $app->get('log_path') . '/' . $domain . '_' . $input->get('option') . $input->get('view') . $input->get('layout') . '.sql'; // Get the queries from log. $current = ''; $db = $this->db; $log = $db->getLog(); $timings = $db->getTimings(); foreach ($log as $id => $query) { if (isset($timings[$id * 2 + 1])) { $temp = str_replace('`', '', $log[$id]); $temp = str_replace(array("\t", "\n", "\r\n"), ' ', $temp); $current .= $temp . ";\n"; } } if (JFile::exists($file)) { JFile::delete($file); } // Write new file. JFile::write($file, $current); } }