params->get('log-deprecated')) { JLog::addLogger(array('text_file' => 'deprecated.php'), JLog::ALL, array('deprecated')); } $this->debugLang = JFactory::getApplication()->getCfg('debug_lang'); // 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 = JFactory::getDbo(); $db->addDisconnectHandler(array($this, 'mysqlDisconnectHandler')); } /** * 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(), true); } // Only if debugging is enabled for SQL queries popovers if (JDEBUG && $this->isAuthorisedDisplayDebug()) { JHtml::_('bootstrap.tooltip'); JHtml::_('bootstrap.popover', '.hasPopover', array('placement' => 'top')); } } /** * Show the debug info * * @since 1.6 */ public function __destruct() { // 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 (strstr(strtolower($_SERVER['HTTP_USER_AGENT']), 'webkit') !== false && substr($contents, 0, 50) == ''; if (JDEBUG) { if (JError::getErrors()) { $html[] = $this->display('errors'); } $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 ($this->params->get('logs', 1) && !empty($this->logEntries)) { $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')) { $html[] = $this->display('untranslated_strings'); } } $html[] = ''; echo str_replace('', implode('', $html) . '', $contents); } /** * Method to check if the current user is allowed to see the debug information or not. * * @return boolean True is access is allowed * * @since 3.0 */ private function isAuthorisedDisplayDebug() { static $result = null; if (!is_null($result)) { return $result; } // If the user is not allowed to view the output then end here $filterGroups = (array) $this->params->get('filter_groups', null); 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 occured 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 = ''; $js = "toggleContainer('dbg_container_" . $item . "');"; $class = 'dbg-header' . $status; $html[] = '

' . $title . '

'; // @todo set with js.. ? $style = ' style="display: none;"'; $html[] = '
'; $html[] = $this->$fncName(); $html[] = '
'; return implode('', $html); } /** * Display session information. * * Called recursive. * * @param string $key A session key * @param mixed $session The session array, initially null * @param integer $id The id is used for JS toggling the div * * @return string * * @since 2.5 */ protected function displaySession($key = '', $session = null, $id = 0) { if (!$session) { $session = $_SESSION; } $html = array(); static $id; if (!is_array($session)) { $html[] = $key . ' ⇒' . $session . PHP_EOL; } else { foreach ($session as $sKey => $entries) { $display = true; if (is_array($entries) && $entries) { $display = false; } if (is_object($entries)) { $o = JArrayHelper::fromObject($entries); if ($o) { $entries = $o; $display = false; } } if (!$display) { $js = "toggleContainer('dbg_container_session" . $id . '_' . $sKey . "');"; $html[] = '

' . $sKey . '

'; // @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[] = ''; $html[] = $sKey . ' ⇒ ' . $entries . '
'; $html[] = '
'; } } } 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 += $mark->memory; $htmlMark = sprintf( JText::_('PLG_DEBUG_TIME') . ': %.1f ms / %.1f 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'; } 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'; } elseif ($mark->memory < $avgMem / 1.5) { $barClassMem = 'bar-success'; $labelClassMem = 'label-success'; } else { $barClassMem = 'bar-warning'; $labelClassMem = 'label-warning'; } $bars[] = (object) array( 'width' => round($mark->time / ($totalTime / 100), 4), 'class' => $barClass, 'tip' => $mark->tip ); $barsMem[] = (object) array( 'width' => round($mark->memory / ($totalMem / 100), 4), 'class' => $barClassMem, 'tip' => $mark->tip ); $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 = JFactory::getDbo(); $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 = $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('%.1f ms', $totalQueryTime) ) . '
'; } } 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) . ' ' . JText::_('PLG_DEBUG_BYTES') . ')'; } /** * Display logged queries. * * @return string * * @since 2.5 */ protected function displayQueries() { $db = JFactory::getDbo(); $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 (in_array($db->name, array('mysqli', 'mysql'))) { if (isset($this->sqlShowProfileEach[$id])) { $profileTable = $this->sqlShowProfileEach[$id]; $profile = $this->tableToHtml($profileTable, $hasWarningsInProfile); } } $ratio = 0.5; // how heavy should the string length count: 0 - 1 $timeScore = $queryTime / (strlen($query) * $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: $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("\t", " ", $fromString); $fromString = str_replace("\n", " ", $fromString); $fromString = trim($fromString); // Initialize 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] = $selectQueryTypeTicker[$fromString] + 1; unset($otherQueryTypeTicker[$fromString]); } else { $otherQueryTypeTicker[$fromString] = $otherQueryTypeTicker[$fromString] + 1; 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 = '
' . JText::sprintf('PLG_DEBUG_QUERY_TIME', sprintf('%.2f ms', $timing[$id]['0'])); if ($timing[$id]['1']) { $htmlTiming .= ' ' . JText::sprintf('PLG_DEBUG_QUERY_AFTER_LAST', sprintf('%.2f ms', $timing[$id]['1'])); } if (isset($callStacks[$id][0]['memory'])) { $memoryUsed = $callStacks[$id][0]['memory'][1] - $callStacks[$id][0]['memory'][0]; $memoryBeforeQuery = $callStacks[$id][0]['memory'][0]; // Determine color 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 color 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 ? $info[$id]->profile : JText::_('PLG_DEBUG_NO_PROFILE')); // Backtrace/Called $htmlCallStack = ''; if (isset($callStacks[$id])) { $htmlCallStackElements = array(); foreach ($callStacks[$id] as $functionCall) { if (isset($functionCall['file']) && isset($functionCall['line']) && (strpos($functionCall['file'], '/libraries/joomla/database/') === false)) { $htmlFile = htmlspecialchars($functionCall['file']); $htmlLine = htmlspecialchars($functionCall['line']); $htmlCallStackElements[] = '' . $this->formatLink($htmlFile, $htmlLine) . ''; } } $htmlCallStack = '
' . implode('
', $htmlCallStackElements) . '
'; if (!$this->linkFormat) { $htmlCallStack .= '
[' . JText::_('PLG_DEBUG_LINK_FORMAT') . ']
'; } } $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'); if ($htmlCallStack) { $htmlAccordions .= JHtml::_('bootstrap.addSlide', 'dbg_query_' . $id, JText::_('PLG_DEBUG_CALL_STACK'), 'dbg_query_callstack_' . $id) . $htmlCallStack . 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'; } $html = array(); $html[] = '

' . JText::sprintf('PLG_DEBUG_QUERIES_LOGGED', $db->getCount()) . sprintf(' %.1f 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); } /** * @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 ' . (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) . '
'; } /** * @param array $table * @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[] = ''; foreach (array_keys($table[0]) as $k) { $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[] = '
' . htmlspecialchars($k) . '
'; $hasWarnings = true; } else { $html[] = ''; } // Display duration in ms with the unit instead of seconds: $html[] = sprintf('%.1f ms', $td * 1000); } elseif ($k == 'Error') { // An error in the EXPLAIN query occured, 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 nbsp 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); $dbVersion5037 = (strncmp($db->name, 'mysql', 5) == 0) && 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->name, array('mysqli', 'mysql', 'postgresql'))) { $log = $db->getLog(); foreach ($log as $k => $query) { $dbVersion56 = (strncmp($db->name, 'mysql', 5) == 0) && 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[] = ''; return implode('', $html); } /** * Display loaded language files. * * @return string * * @since 2.5 */ protected function displayLanguageFilesLoaded() { $html = array(); $html[] = ''; return implode('', $html); } /** * Display untranslated language strings. * * @return string * * @since 2.5 */ protected function displayUntranslatedStrings() { $stripFirst = $this->params->get('strip-first'); $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']) ? $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 = trim(strtoupper($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 '/(' . JFactory::getDbo()->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 error * * @return string Contents of the backtrace * * @since 2.5 */ protected function renderBacktrace($error) { $backtrace = $error->getTrace(); $html = array(); if (is_array($backtrace)) { $j = 1; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; for ($i = count($backtrace) - 1; $i >= 0; $i--) { $link = ' '; if (isset($backtrace[$i]['file'])) { $link = $this->formatLink($backtrace[$i]['file'], $backtrace[$i]['line']); } $html[] = ''; $html[] = ''; if (isset($backtrace[$i]['class'])) { $html[] = ''; } else { $html[] = ''; } $html[] = ''; $html[] = ''; $j++; } $html[] = '
Call stack
#FunctionLocation
' . $j . '' . $backtrace[$i]['class'] . $backtrace[$i]['type'] . $backtrace[$i]['function'] . '()' . $backtrace[$i]['function'] . '()' . $link . '
'; } return implode('', $html); } /** * 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 = '') { $link = str_replace(JPATH_ROOT, 'JROOT', $file); $link .= ($line) ? ':' . $line : ''; if ($this->linkFormat) { $href = $this->linkFormat; $href = str_replace('%f', $file, $href); $href = str_replace('%l', $line, $href); $html = '' . $link . ''; } else { $html = $link; } return $html; } /** * Store log messages so they can be displayed later. * This function is passed log entries by JLogLoggerCallback. * * @param JLogEntry $entry A log entry. * * @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 = array(); foreach ($this->logEntries as $entry) { $out[] = '
' . $priorities[$entry->priority] . ' - ' . $entry->category . '
' . $entry->message . ''; } return implode('

', $out); } }