Index: branches/5.1.x/core/kernel/utility/debugger.php =================================================================== --- branches/5.1.x/core/kernel/utility/debugger.php (revision 13988) +++ branches/5.1.x/core/kernel/utility/debugger.php (revision 13989) @@ -1,1526 +1,1526 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); if( !class_exists('Debugger') ) { class Debugger { /** * Holds reference to global KernelApplication instance * * @access public * @var kApplication */ var $Application = null; /** * Set to true if fatal error occured * * @var bool */ var $IsFatalError = false; /** * Counts warnings on the page * * @var int */ var $WarningCount = 0; /** * Allows to track compile errors, like "stack-overflow" * * @var bool */ var $_compileError = false; /** * Debugger data for building report * * @var Array */ var $Data = Array(); var $ProfilerData = Array(); var $ProfilerTotals = Array(); var $ProfilerTotalCount = Array(); var $ProfilePoints = Array(); /** * Prevent recursion when processing debug_backtrace() function results * * @var Array */ var $RecursionStack = Array(); var $scrollbarWidth = 0; /** * Long errors are saved here, because trigger_error doesn't support error messages over 1KB in size * * @var Array */ var $longErrors = Array(); var $IncludesData = Array(); var $IncludeLevel = 0; var $reportDone = false; /** * Transparent spacer image used in case of none spacer image defined via SPACER_URL contant. * Used while drawing progress bars (memory usage, time usage, etc.) * * @var string */ var $dummyImage = ''; /** * Temporary files created by debugger will be stored here * * @var string */ var $tempFolder = ''; /** * Debug rows will be separated using this string before writing to debug file * * @var string */ var $rowSeparator = '@@'; /** * Base URL for debugger includes * * @var string */ var $baseURL = ''; /** * Holds last recorded timestamp (for appendTimestamp) * * @var int */ var $LastMoment; /** * Determines, that current request is AJAX request * * @var bool */ var $_isAjax = false; function Debugger() { global $start, $dbg_options; // check if user haven't defined DEBUG_MODE contant directly if (defined('DEBUG_MODE') && DEBUG_MODE) { die('error: contant DEBUG_MODE defined directly, please use <strong>$dbg_options</strong> array instead'); } // check IP before enabling debug mode $ip_match = $this->ipMatch(isset($dbg_options['DBG_IP']) ? $dbg_options['DBG_IP'] : ''); if (!$ip_match) { define('DEBUG_MODE', 0); return ; } // debug is allowed for user, continue initialization $this->InitDebugger(); $this->profileStart('kernel4_startup', 'Startup and Initialization of kernel4', $start); $this->profileStart('script_runtime', 'Script runtime', $start); $this->LastMoment = $start; error_reporting(E_ALL); ini_set('display_errors', $this->constOn('DBG_ZEND_PRESENT') ? 0 : 1); // show errors on screen in case if not in Zend Studio debugging $this->scrollbarWidth = $this->isGecko() ? 22 : 25; // vertical scrollbar width differs in Firefox and other browsers $this->appendRequest(); } /** * Checks, that user IP address is within allowed range * * @param string $ip_list semi-column (by default) separated ip address list * @param string $separator ip address separator (default ";") * * @return bool */ function ipMatch($ip_list, $separator = ';') { $ip_match = false; $ip_addresses = $ip_list ? explode($separator, $ip_list) : Array (); foreach ($ip_addresses as $ip_address) { if ($this->netMatch($ip_address, $_SERVER['REMOTE_ADDR'])) { $ip_match = true; break; } } return $ip_match; } /** * Set's default values to constants debugger uses * */ function InitDebugger() { global $dbg_options; unset($dbg_options['DBG_IP']); // Detect fact, that this session beeing debugged by Zend Studio foreach ($_COOKIE as $cookie_name => $cookie_value) { if (substr($cookie_name, 0, 6) == 'debug_') { $this->safeDefine('DBG_ZEND_PRESENT', 1); break; } } $this->safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio // set default values for debugger constants $dbg_constMap = Array ( 'DBG_USE_HIGHLIGHT' => 1, // highlight output same as php code using "highlight_string" function 'DBG_WINDOW_WIDTH' => 700, // set width of debugger window (in pixels) for better viewing large amount of debug data 'DBG_USE_SHUTDOWN_FUNC' => DBG_ZEND_PRESENT ? 0 : 1, // use shutdown function to include debugger code into output 'DBG_HANDLE_ERRORS' => DBG_ZEND_PRESENT ? 0 : 1, // handle all allowed by php (see php manual) errors instead of default handler 'DBG_IGNORE_STRICT_ERRORS' => 1, // ignore PHP5 errors about private/public view modified missing in class declarations 'DBG_DOMVIEWER' => '/temp/domviewer.html', // path to DOMViewer on website 'DOC_ROOT' => str_replace('\\', '/', realpath($_SERVER['DOCUMENT_ROOT']) ), // windows hack 'DBG_LOCAL_BASE_PATH' => 'w:', // replace DOC_ROOT in filenames (in errors) using this path ); // only for IE, in case if no windows php script editor defined if (!defined('DBG_EDITOR')) { // $dbg_constMap['DBG_EDITOR'] = 'c:\Program Files\UltraEdit\uedit32.exe %F/%L'; $dbg_constMap['DBG_EDITOR'] = 'c:\Program Files\Zend\ZendStudio-5.2.0\bin\ZDE.exe %F'; } // debugger is initialized before kHTTPQuery, so do jQuery headers check here too if (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { $this->_isAjax = true; } elseif (array_key_exists('ajax', $_GET) && $_GET['ajax'] == 'yes') { $this->_isAjax = true; } // user defined options override debugger defaults $dbg_constMap = $this->array_merge_recursive2($dbg_constMap, $dbg_options); if ($this->_isAjax && array_key_exists('DBG_SKIP_AJAX', $dbg_constMap) && $dbg_constMap['DBG_SKIP_AJAX']) { $dbg_constMap['DBG_SKIP_REPORTING'] = 1; } // allows to validate unit configs via request variable if ( !array_key_exists('DBG_VALIDATE_CONFIGS', $dbg_constMap) ) { $dbg_constMap['DBG_VALIDATE_CONFIGS'] = array_key_exists('validate_configs', $_GET) ? (int)$_GET['validate_configs'] : 0; } // when validation configs, don't show sqls for better validation error displaying if ($dbg_constMap['DBG_VALIDATE_CONFIGS']) { $dbg_constMap['DBG_SQL_PROFILE'] = 0; } // when showing explain make shure, that debugger window is large enough if (array_key_exists('DBG_SQL_EXPLAIN', $dbg_constMap) && $dbg_constMap['DBG_SQL_EXPLAIN']) { $dbg_constMap['DBG_WINDOW_WIDTH'] = 1000; } foreach ($dbg_constMap as $dbg_constName => $dbg_constValue) { $this->safeDefine($dbg_constName, $dbg_constValue); } } function constOn($const_name) { return defined($const_name) && constant($const_name); } function safeDefine($const_name, $const_value) { if (!defined($const_name)) { define($const_name, $const_value); } } function array_merge_recursive2($paArray1, $paArray2) { if (!is_array($paArray1) or !is_array($paArray2)) { return $paArray2; } foreach ($paArray2 AS $sKey2 => $sValue2) { $paArray1[$sKey2] = isset($paArray1[$sKey2]) ? array_merge_recursive2($paArray1[$sKey2], $sValue2) : $sValue2; } return $paArray1; } function netMatch($network, $ip) { $network = trim($network); $ip = trim($ip); if ($network == $ip) { // comparing two ip addresses directly return true; } $d = strpos($network, '-'); if ($d !== false) { // ip address range specified $from = ip2long(trim(substr($network, 0, $d))); $to = ip2long(trim(substr($network, $d + 1))); $ip = ip2long($ip); return ($ip >= $from && $ip <= $to); } elseif (strpos($network, '/') !== false) { // sigle subnet specified $ip_arr = explode('/', $network); if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) { $ip_arr[0] .= '.0'; // Alternate form 194.1.4/24 } $network_long = ip2long($ip_arr[0]); $x = ip2long($ip_arr[1]); $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1])); $ip_long = ip2long($ip); return ($ip_long & $mask) == ($network_long & $mask); } return false; } function InitReport() { if (!class_exists('kApplication')) return false; $application =& kApplication::Instance(); // string used to separate debugger records while in file (used in debugger dump filename too) $this->rowSeparator = '@'.(is_object($application->Factory) ? $application->GetSID() : 0).'@'; // $this->rowSeparator = '@'.rand(0,100000).'@'; // include debugger files from this url $reg_exp = '/^'.preg_quote(FULL_PATH, '/').'/'; $kernel_path = preg_replace($reg_exp, '', KERNEL_PATH, 1); $this->baseURL = PROTOCOL.SERVER_NAME.rtrim(BASE_PATH, '/').$kernel_path.'/utility/debugger'; // save debug output in this folder $this->tempFolder = WRITEABLE . '/cache'; } function mapLongError($msg) { $key = $this->generateID(); $this->longErrors[$key] = $msg; return $key; } /** * Appends all passed variable values (wihout variable names) to debug output * */ function dumpVars() { $dump_mode = 'var_dump'; $dumpVars = func_get_args(); if ($dumpVars[count($dumpVars) - 1] === 'STRICT') { $dump_mode = 'strict_var_dump'; array_pop($dumpVars); } foreach ($dumpVars as $varValue) { $this->Data[] = Array('value' => $varValue, 'debug_type' => $dump_mode); } } function prepareHTML($dataIndex) { $Data =& $this->Data[$dataIndex]; if ($Data['debug_type'] == 'html') { return $Data['html']; } switch ($Data['debug_type']) { case 'error': $fileLink = $this->getFileLink($Data['file'], $Data['line']); $ret = '<b class="debug_error">'.$this->getErrorNameByCode($Data['no']).'</b>: '.$Data['str']; $ret .= ' in <b>'.$fileLink.'</b> on line <b>'.$Data['line'].'</b>'; return $ret; break; case 'var_dump': return $this->highlightString( $this->print_r($Data['value'], true) ); break; case 'strict_var_dump': return $this->highlightString( var_export($Data['value'], true) ); break; case 'trace': ini_set('memory_limit', '500M'); $trace =& $Data['trace']; $i = 0; $traceCount = count($trace); $ret = ''; while ($i < $traceCount) { $traceRec =& $trace[$i]; $argsID = 'trace_args_'.$dataIndex.'_'.$i; $has_args = isset($traceRec['args']); if (isset($traceRec['file'])) { $func_name = isset($traceRec['class']) ? $traceRec['class'].$traceRec['type'].$traceRec['function'] : $traceRec['function']; $args_link = $has_args ? '<a href="javascript:$Debugger.ToggleTraceArgs(\''.$argsID.'\');" title="Show/Hide Function Arguments"><b>Function</b></a>' : '<b>Function</b>'; $ret .= $args_link.': '.$this->getFileLink($traceRec['file'], $traceRec['line'], $func_name); $ret .= ' in <b>'.basename($traceRec['file']).'</b> on line <b>'.$traceRec['line'].'</b><br>'; } else { $ret .= 'no file information available'; } if ($has_args) { // if parameter value is longer then 200 symbols, then leave only first 50 $args = $this->highlightString($this->print_r($traceRec['args'], true)); $ret .= '<div id="'.$argsID.'" style="display: none;">'.$args.'</div>'; } $i++; } return $ret; break; case 'profiler': $profileKey = $Data['profile_key']; $Data =& $this->ProfilerData[$profileKey]; $runtime = ($Data['ends'] - $Data['begins']); // in seconds $totals_key = getArrayValue($Data, 'totalsKey'); if ($totals_key) { $total_before = $Data['totalsBefore']; $total = $this->ProfilerTotals[$totals_key]; $div_width = Array(); $total_width = ($this->getWindowWidth() - 10); $div_width['before'] = round(($total_before / $total) * $total_width); $div_width['current'] = round(($runtime / $total) * $total_width); $div_width['left'] = round((($total - $total_before - $runtime) / $total) * $total_width); $subtitle = array_key_exists('subtitle', $Data) ? ' (' . $Data['subtitle'] . ')' : ''; $ret = '<b>Name' . $subtitle . '</b>: '.$Data['description'].'<br />'; $additional = isset($Data['additional']) ? $Data['additional'] : Array (); if (isset($Data['file'])) { array_unshift($additional, Array('name' => 'File', 'value' => $this->getFileLink($Data['file'], $Data['line'], basename($Data['file']).':'.$Data['line']))); } array_unshift($additional, Array('name' => 'Runtime', 'value' => $runtime.'s')); $ret .= '<div>'; //FF 3.5 needs this! foreach ($additional as $mixed_param) { $ret .= '[<strong>'.$mixed_param['name'].'</strong>: '.$mixed_param['value'].'] '; } /*if (isset($Data['file'])) { $ret .= '[<b>Runtime</b>: '.$runtime.'s] [<b>File</b>: '.$this->getFileLink($Data['file'], $Data['line'], basename($Data['file']).':'.$Data['line']).']<br />'; } else { $ret .= '<b>Runtime</b>: '.$runtime.'s<br />'; }*/ $ret .= '</div>'; $ret .= '<div class="dbg_profiler" style="width: '.$div_width['before'].'px; border-right: 0px; background-color: #298DDF;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>'; $ret .= '<div class="dbg_profiler" style="width: '.$div_width['current'].'px; border-left: 0px; border-right: 0px; background-color: #EF4A4A;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>'; $ret .= '<div class="dbg_profiler" style="width: '.$div_width['left'].'px; border-left: 0px; background-color: #DFDFDF;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>'; return $ret; } else { return '<b>Name</b>: '.$Data['description'].'<br><b>Runtime</b>: '.$runtime.'s'; } break; default: return 'incorrect debug data'; break; } } function getWindowWidth() { return DBG_WINDOW_WIDTH - $this->scrollbarWidth - 8; } /** * Tells debugger to skip objects that are heavy in plan of memory usage while printing debug_backtrace results * * @param Object $object * @return bool */ function IsBigObject(&$object) { $skip_classes = Array( defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication', 'kFactory', 'kUnitConfigReader', 'NParser', ); foreach ($skip_classes as $class_name) { if (strtolower(get_class($object)) == strtolower($class_name)) { return true; } } return false; } /** * Advanced version of print_r (for debugger only). Don't print objects recursively * * @param Array $array * @param bool $return_output return output or print it out * @param int $tab_count offset in tabs * @return string */ function print_r(&$array, $return_output = false, $tab_count = -1) { static $first_line = true; // not an array at all if (!is_array($array)) { switch (gettype($array)) { case 'NULL': return 'NULL'."\n"; break; case 'object': return $this->processObject($array, $tab_count); break; default: // number or string if (strlen($array) > 200) { $array = substr($array, 0, 50).' ...'; } return $array."\n"; break; } } $output = ''; $tab_count++; $output .= "Array\n".str_repeat(' ', $tab_count)."(\n"; $tab_count++; $tabsign = $tab_count ? str_repeat(' ', $tab_count) : ''; $array_keys = array_keys($array); foreach ($array_keys as $key) { switch (gettype($array[$key])) { case 'array': $output .= $tabsign.'['.$key.'] = '.$this->print_r($array[$key], true, $tab_count); break; case 'boolean': $output .= $tabsign.'['.$key.'] = '.($array[$key] ? 'true' : 'false')."\n"; break; case 'integer': case 'double': case 'string': if (strlen($array[$key]) > 200) { $array[$key] = substr($array[$key], 0, 50).' ...'; } $output .= $tabsign.'['.$key.'] = '.$array[$key]."\n"; break; case 'NULL': $output .= $tabsign.'['.$key."] = NULL\n"; break; case 'object': $output .= $tabsign.'['.$key."] = "; $output .= "Object (".get_class($array[$key]).") = \n".str_repeat(' ', $tab_count + 1)."(\n"; $output .= $this->processObject($array[$key], $tab_count + 2); $output .= str_repeat(' ', $tab_count + 1).")\n"; break; default: $output .= $tabsign.'['.$key.'] unknown = '.gettype($array[$key])."\n"; break; } } $tab_count--; $output .= str_repeat(' ', $tab_count).")\n"; if ($first_line) { $first_line = false; $output .= "\n"; } $tab_count--; if ($return_output) { return $output; } else { echo $output; } return true; } function processObject(&$object, $tab_count) { $object_class = get_class($object); if (!in_array($object_class, $this->RecursionStack)) { if ($this->IsBigObject($object)) { return 'SKIPPED (class: '.$object_class.")\n"; } $attribute_names = get_class_vars($object_class); if (!$attribute_names) { return "NO_ATTRIBUTES\n"; } else { $output = ''; array_push($this->RecursionStack, $object_class); $tabsign = $tab_count ? str_repeat(' ', $tab_count) : ''; foreach ($attribute_names as $attribute_name => $attribute_value) { if (is_object($object->$attribute_name)) { // it is object $output .= $tabsign.'['.$attribute_name.'] = '.$this->processObject($object->$attribute_name, $tab_count + 1); } else { $output .= $tabsign.'['.$attribute_name.'] = '.$this->print_r($object->$attribute_name, true, $tab_count); } } array_pop($this->RecursionStack); return $output; } } else { // object [in recursion stack] return '*** RECURSION *** (class: '.$object_class.")\n"; } } /** * Format SQL Query using predefined formatting * and highlighting techniques * * @param string $sql * @return string */ function formatSQL($sql) { $sql = trim( preg_replace('/(\n|\t| )+/is', ' ', $sql) ); // whitespace in the beginning of the regex is to avoid splitting inside words, for exmaple "FROM int_ConfigurationValues" into "FROM intConfiguration\n\tValues" $formatted_sql = preg_replace('/\s(CREATE TABLE|DROP TABLE|SELECT|UPDATE|SET|REPLACE|INSERT|DELETE|VALUES|FROM|LEFT JOIN|INNER JOIN|LIMIT|WHERE|HAVING|GROUP BY|ORDER BY)\s/is', "\n\t$1 ", ' ' . $sql); $formatted_sql = $this->highlightString($formatted_sql); if (defined('DBG_SQL_EXPLAIN') && DBG_SQL_EXPLAIN) { if (substr($sql, 0, 6) == 'SELECT') { $formatted_sql .= '<br/>' . '<strong>Explain</strong>:<br /><br />'; $explain_result = $this->Application->Conn->Query('EXPLAIN ' . $sql, null, true); $explain_table = ''; foreach ($explain_result as $explain_row) { if (!$explain_table) { // first row -> draw header $explain_table .= '<tr class="explain_header"><td>' . implode('</td><td>', array_keys($explain_row)) . '</td></tr>'; } $explain_table .= '<tr><td>' . implode('</td><td>', $explain_row) . '</td></tr>'; } $formatted_sql .= '<table class="dbg_explain_table">' . $explain_table . '</table>'; } } return $formatted_sql; } function highlightString($string) { if (!(defined('DBG_USE_HIGHLIGHT') && DBG_USE_HIGHLIGHT) || $this->_compileError) { return nl2br($string); } $string = str_replace( Array('\\', '/') , Array('_no_match_string_', '_n_m_s_'), $string); $this->_compileError = true; // next line is possible cause of compile error $string = highlight_string('<?php '.$string.' ?>', true); $this->_compileError = false; $string = str_replace( Array('_no_match_string_', '_n_m_s_'), Array('\\', '/'), $string); if (strlen($string) >= 65536) { // preg_replace will fail, when string is longer, then 65KB return str_replace(Array ('<?php ', '?>'), '', $string); } return preg_replace('/<\?(.*)php (.*)\?>/Us', '\\2', $string); } /** * Determine by php type of browser used to show debugger * * @return bool */ function isGecko() { // we need isset because we may run scripts from shell with no user_agent at all return isset($_SERVER['HTTP_USER_AGENT']) && strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'firefox') !== false; } /** * Returns link for editing php file (from error) in external editor * * @param string $file filename with path from root folder * @param int $lineno line number in file where error is found * @param string $title text to show on file edit link * @return string */ function getFileLink($file, $lineno = 1, $title = '') { if (!$title) { $title = str_replace('/', '\\', $this->getLocalFile($file)); } if ($this->isGecko()) { return '<a href="file://'.$this->getLocalFile($file).'">'.$title.'</a>'; } else { return '<a href="javascript:$Debugger.editFile(\''.$this->getLocalFile($file).'\', '.$lineno.');" title="'.$file.'">'.$title.'</a>'; } } /** * Converts filepath on server to filepath in mapped DocumentRoot on developer pc * * @param string $remoteFile * @return string */ function getLocalFile($remoteFile) { return preg_replace('/^'.preg_quote(DOC_ROOT, '/').'/', DBG_LOCAL_BASE_PATH, $remoteFile, 1); } /** * Appends call trace till this method call * */ function appendTrace() { $trace = debug_backtrace(); array_shift($trace); $this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace'); } function appendMemoryUsage($msg, $used = null) { if (!isset($used)) { $used = round(memory_get_usage() / 1024); } $this->appendHTML('<b>Memory usage</b> '.$msg.' '.$used.'Kb'); } /** * Appends HTML code whithout transformations * * @param string $html */ function appendHTML($html) { $this->Data[] = Array('html' => $html, 'debug_type' => 'html'); } /** * Change debugger info that was already generated before. * Returns true if html was set. * * @param int $index * @param string $html * @param string $type = {'append','prepend','replace'} * @return bool */ function setHTMLByIndex($index, $html, $type = 'append') { if (!isset($this->Data[$index]) || $this->Data[$index]['debug_type'] != 'html') { return false; } switch ($type) { case 'append': $this->Data[$index]['html'] .= '<br>'.$html; break; case 'prepend': $this->Data[$index]['html'] = $this->Data[$index]['html'].'<br>'.$html; break; case 'replace': $this->Data[$index]['html'] = $html; break; } return true; } /** * Move $debugLineCount lines of input from debug output * end to beginning. * * @param int $debugLineCount */ function moveToBegin($debugLineCount) { $lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount); $this->Data = array_merge($lines,$this->Data); } function moveAfterRow($new_row, $debugLineCount) { $lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount); $rows_before = array_splice($this->Data,0,$new_row,$lines); $this->Data = array_merge($rows_before,$this->Data); } function appendRequest() { if (isset($_SERVER['SCRIPT_FILENAME'])) { $script = $_SERVER['SCRIPT_FILENAME']; } else { $script = $_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF']; } $this->appendHTML('ScriptName: <b>'.$this->getFileLink($script, 1, basename($script)).'</b> (<b>'.dirname($script).'</b>)'); if ($this->_isAjax) { $this->appendHTML('RequestURI: '.$_SERVER['REQUEST_URI'].' (QS Length:'.strlen($_SERVER['QUERY_STRING']).')'); } $tools_html = ' <table style="width: ' . $this->getWindowWidth() . 'px;"> <tr> <td>' . $this->_getDomViewerHTML() . '</td> <td>' . $this->_getToolsHTML() . '</td> </tr> </table>'; $this->appendHTML($tools_html); ob_start(); ?> <table border="0" cellspacing="0" cellpadding="0" class="dbg_flat_table" style="width: <?php echo $this->getWindowWidth(); ?>px;"> <thead style="font-weight: bold;"> <td width="20">Src</td><td>Name</td><td>Value</td> </thead> <?php foreach ($_REQUEST as $key => $value) { if(!is_array($value) && trim($value) == '') { $value = '<b class="debug_error">no value</b>'; } else { $value = htmlspecialchars($this->print_r($value, true)); } $in_cookie = isset($_COOKIE[$key]); $src = isset($_GET[$key]) && !$in_cookie ? 'GE' : (isset($_POST[$key]) && !$in_cookie ? 'PO' : ($in_cookie ? 'CO' : '?') ); echo '<tr><td>'.$src.'</td><td>'.$key.'</td><td>'.$value.'</td></tr>'; } ?> </table> <?php $this->appendHTML(ob_get_contents()); ob_end_clean(); } /** * Appends php session content to debugger output * */ function appendSession() { if (isset($_SESSION) && $_SESSION) { $this->appendHTML('PHP Session: [<b>'.ini_get('session.name').'</b>]'); $this->dumpVars($_SESSION); $this->moveToBegin(2); } } function profileStart($key, $description = null, $timeStamp = null) { if (!isset($timeStamp)) { $timeStamp = $this->getMoment(); } $this->ProfilerData[$key] = Array('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data)); if (isset($description)) { $this->ProfilerData[$key]['description'] = $description; } if (substr($key, 0, 4) == 'sql_') { // append place from what was called $trace_results = debug_backtrace(); $trace_count = count($trace_results); $i = 0; while ($i < $trace_count) { $trace_file = basename($trace_results[$i]['file']); if ($trace_file != 'db_connection.php' && $trace_file != 'adodb.inc.php') { break; } $i++; } $this->ProfilerData[$key]['file'] = $trace_results[$i]['file']; $this->ProfilerData[$key]['line'] = $trace_results[$i]['line']; if (array_key_exists('object', $trace_results[$i + 1]) && isset($trace_results[$i + 1]['object']->Prefix)) { $object =& $trace_results[$i + 1]['object']; $prefix_special = rtrim($object->Prefix . '.' . $object->Special, '.'); $this->ProfilerData[$key]['prefix_special'] = $prefix_special; } unset($trace_results); } $this->Data[] = Array('profile_key' => $key, 'debug_type' => 'profiler'); } function profileFinish($key, $description = null, $timeStamp = null) { if (!isset($timeStamp)) { $timeStamp = $this->getMoment(); } $this->ProfilerData[$key]['ends'] = $timeStamp; if (isset($description)) { $this->ProfilerData[$key]['description'] = $description; } if (substr($key, 0, 4) == 'sql_') { $func_arguments = func_get_args(); $rows_affected = $func_arguments[3]; $additional = Array (); if ($rows_affected > 0) { $additional[] = Array ('name' => 'Affected Rows', 'value' => $rows_affected); if (isset($func_arguments[4])) { if (strlen($func_arguments[4]) > 200) { $func_arguments[4] = substr($func_arguments[4], 0, 50) . ' ...'; } $additional[] = Array ('name' => 'Result', 'value' => $func_arguments[4]); } } $additional[] = Array ('name' => 'Query Number', 'value' => $func_arguments[5]); if ($func_arguments[6]) { $this->profilerAddTotal('cachable_queries', $key); $this->ProfilerData[$key]['subtitle'] = 'cachable'; } if (array_key_exists('prefix_special', $this->ProfilerData[$key])) { $additional[] = Array ('name' => 'PrefixSpecial', 'value' => $this->ProfilerData[$key]['prefix_special']); } $this->ProfilerData[$key]['additional'] =& $additional; } } function profilerAddTotal($total_key, $key = null, $value = null) { if (!isset($this->ProfilerTotals[$total_key])) { $this->ProfilerTotals[$total_key] = 0; $this->ProfilerTotalCount[$total_key] = 0; } if (!isset($value)) { $value = $this->ProfilerData[$key]['ends'] - $this->ProfilerData[$key]['begins']; } if (isset($key)) { $this->ProfilerData[$key]['totalsKey'] = $total_key; $this->ProfilerData[$key]['totalsBefore'] = $this->ProfilerTotals[$total_key]; } $this->ProfilerTotals[$total_key] += $value; $this->ProfilerTotalCount[$total_key]++; } function getMoment() { list($usec, $sec) = explode(' ', microtime()); return ((float)$usec + (float)$sec); } function appendTimestamp($message) { global $start; $time = $this->getMoment(); $from_last = $time - $this->LastMoment; $from_start = $time - $start; $this->appendHTML(sprintf("<b>%s</b> %.5f from last %.5f from start", $message, $from_last, $from_start)); $this->LastMoment = $time; } function generateID() { list($usec, $sec) = explode(' ', microtime()); $id_part_1 = substr($usec, 4, 4); $id_part_2 = mt_rand(1,9); $id_part_3 = substr($sec, 6, 4); $digit_one = substr($id_part_1, 0, 1); if ($digit_one == 0) { $digit_one = mt_rand(1,9); $id_part_1 = preg_replace('/^0/', '', $id_part_1); $id_part_1=$digit_one.$id_part_1; } return $id_part_1.$id_part_2.$id_part_3; } function getErrorNameByCode($error_code) { $error_map = Array( 'Fatal Error' => Array(E_USER_ERROR), 'Warning' => Array(E_WARNING, E_USER_WARNING), 'Notice' => Array(E_NOTICE, E_USER_NOTICE), ); if (defined('E_STRICT')) { // since PHP 5 $error_map['PHP5 Strict'] = Array(E_STRICT); } if (defined('E_RECOVERABLE_ERROR')) { // since PHP 5.2 $error_map['Fatal Error (recoverable)'] = Array(E_RECOVERABLE_ERROR); } if (defined('E_DEPRECATED')) { // since PHP 5.3 $error_map['PHP5 Depricated'] = Array(E_DEPRECATED, E_USER_DEPRECATED); } foreach ($error_map as $error_name => $error_codes) { if (in_array($error_code, $error_codes)) { return $error_name; } } return ''; } /** * Returns profile total key (check against unexisting key too) * * @param string $key * @return int */ function getProfilerTotal($key) { if (isset($this->ProfilerTotalCount[$key])) { return (int)$this->ProfilerTotalCount[$key]; } return 0; } function ProfilePoint($title, $level=1) { $trace_results = debug_backtrace(); $level = min($level,count($trace_results)-1); do { $point = $trace_results[$level]; $location = $point['file'].':'.$point['line']; $level++; $has_more = isset($trace_results[$level]); } while ($has_more && $point['function'] == $trace_results[$level]['function'] ); if (!isset($this->ProfilePoints[$title])) { $this->ProfilePoints[$title] = array(); } if (!isset($this->ProfilePoints[$title][$location])) { $this->ProfilePoints[$title][$location] = 0; } $this->ProfilePoints[$title][$location]++; } /** * Generates report * */ function printReport($returnResult = false, $clean_output_buffer = true) { if ($this->reportDone) { // don't print same report twice (in case if shutdown function used + compression + fatal error) return ''; } $this->profileFinish('script_runtime'); $this->breakOutofBuffering(!$returnResult); $debugger_start = memory_get_usage(); if (defined('SPACER_URL')) { $this->dummyImage = SPACER_URL; } $this->InitReport(); // set parameters required by AJAX // defined here, because user can define this contant while script is running, not event before debugger is started $this->safeDefine('DBG_RAISE_ON_WARNINGS', 0); $this->safeDefine('DBG_TOOLBAR_BUTTONS', 1); $this->appendSession(); // show php session if any // ensure, that 1st line of debug output always is this one: $top_line = '<table cellspacing="0" cellpadding="0" style="width: '.$this->getWindowWidth().'px; margin: 0px;"><tr><td align="left" width="50%">[<a href="javascript:window.location.reload();">Reload Frame</a>] [<a href="javascript:$Debugger.Toggle(27);">Hide Debugger</a>] [<a href="javascript:$Debugger.Clear();">Clear Debugger</a>]</td><td align="right" width="50%">[Current Time: <b>'.date('H:i:s').'</b>] [File Size: <b>#DBG_FILESIZE#</b>]</td></tr></table>'; $this->appendHTML($top_line); $this->moveToBegin(1); if (count($this->ProfilePoints)>0) { foreach($this->ProfilePoints as $point => $locations) { arsort($this->ProfilePoints[$point]); } $this->appendHTML($this->highlightString($this->print_r($this->ProfilePoints, true))); /*foreach ($this->ProfilePoints as $point => $locations) { foreach ($locations as $location => $occurences) { } }*/ } if ($this->constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql'])) { // sql query profiling was enabled -> show totals if (array_key_exists('cachable_queries', $this->ProfilerTotalCount)) { $append = ' <strong>Cachable queries</strong>: ' . $this->ProfilerTotalCount['cachable_queries']; } else { $append = ''; } $this->appendHTML('<b>SQL Total time:</b> '.$this->ProfilerTotals['sql'].' <b>Number of queries</b>: '.$this->ProfilerTotalCount['sql'] . $append); } if ($this->constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes'])) { // included file profiling was enabled -> show totals $this->appendHTML('<b>Included Files Total time:</b> '.$this->ProfilerTotals['includes'].' Number of includes: '.$this->ProfilerTotalCount['includes']); } if ($this->constOn('DBG_PROFILE_MEMORY')) { // detailed memory usage reporting by objects was enabled -> show totals $this->appendHTML('<b>Memory used by Objects:</b> '.round($this->ProfilerTotals['objects'] / 1024, 2).'Kb'); } if ($this->constOn('DBG_INCLUDED_FILES')) { $files = get_included_files(); $this->appendHTML('<b>Included files:</b>'); foreach ($files as $file) { $this->appendHTML($this->getFileLink($this->getLocalFile($file)).' ('.round(filesize($file) / 1024, 2).'Kb)'); } } if ($this->constOn('DBG_PROFILE_INCLUDES')) { $this->appendHTML('<b>Included files statistics:</b>'.( $this->constOn('DBG_SORT_INCLUDES_MEM') ? ' (sorted by memory usage)':'')); $totals = Array( 'mem' => 0, 'time' => 0); $totals_configs = Array( 'mem' => 0, 'time' => 0); if (is_array($this->IncludesData['mem'])) { if ( $this->constOn('DBG_SORT_INCLUDES_MEM') ) { array_multisort($this->IncludesData['mem'], SORT_DESC, $this->IncludesData['file'], $this->IncludesData['time'], $this->IncludesData['level']); } foreach ($this->IncludesData['file'] as $key => $file_name) { $this->appendHTML( str_repeat(' -> ', ($this->IncludesData['level'][$key] >= 0 ? $this->IncludesData['level'][$key] : 0)).$file_name.' Mem: '.sprintf("%.4f Kb", $this->IncludesData['mem'][$key]/1024).' Time: '.sprintf("%.4f", $this->IncludesData['time'][$key])); if ($this->IncludesData['level'][$key] == 0) { $totals['mem'] += $this->IncludesData['mem'][$key]; $totals['time'] += $this->IncludesData['time'][$key]; } else if ($this->IncludesData['level'][$key] == -1) { $totals_configs['mem'] += $this->IncludesData['mem'][$key]; $totals_configs['time'] += $this->IncludesData['time'][$key]; } } $this->appendHTML('<b>Sub-Total classes:</b> '.' Mem: '.sprintf("%.4f Kb", $totals['mem']/1024).' Time: '.sprintf("%.4f", $totals['time'])); $this->appendHTML('<b>Sub-Total configs:</b> '.' Mem: '.sprintf("%.4f Kb", $totals_configs['mem']/1024).' Time: '.sprintf("%.4f", $totals_configs['time'])); $this->appendHTML('<span class="error"><b>Grand Total:</b></span> '.' Mem: '.sprintf("%.4f Kb", ($totals['mem']+$totals_configs['mem'])/1024).' Time: '.sprintf("%.4f", $totals['time']+$totals_configs['time'])); } } $skip_reporting = $this->constOn('DBG_SKIP_REPORTING') || $this->constOn('DBG_ZEND_PRESENT'); if (($this->_isAjax && !$this->constOn('DBG_SKIP_AJAX')) || !$skip_reporting) { $debug_file = $this->tempFolder.'/debug_'.$this->rowSeparator.'.txt'; if (file_exists($debug_file)) unlink($debug_file); $i = 0; $fp = fopen($debug_file, 'a'); $lineCount = count($this->Data); while ($i < $lineCount) { fwrite($fp, $this->prepareHTML($i).$this->rowSeparator); $i++; } fclose($fp); } if ($skip_reporting) { // let debugger write report and then don't output anything $this->reportDone = true; return ''; } $application =& kApplication::Instance(); $dbg_path = str_replace(FULL_PATH, '', $this->tempFolder); ob_start(); // the <script .. /script> and hidden div helps browser to break out of script tag or attribute esacped // with " or ' in case fatal error (or user-error) occurs inside it in compiled template, // otherwise it has no effect ?> <div style="display: none" x='nothing'><script></script></div><html><body></body></html> <script type="text/javascript" src="<?php echo $this->baseURL; ?>/debugger.js"></script> <link rel="stylesheet" rev="stylesheet" href="<?php echo $this->baseURL; ?>/debugger.css" type="text/css" media="screen" /> <script type="text/javascript"> var $Debugger = new Debugger(<?php echo "'".$this->rowSeparator."', ".$this->getProfilerTotal('error_handling').', '.($this->IsFatalError ? 'true' : 'false').', '.$this->getProfilerTotal('sql'); ?>); $Debugger.createEnvironment(<?php echo DBG_WINDOW_WIDTH; ?>, <?php echo $this->getWindowWidth(); ?>); $Debugger.DOMViewerURL = '<?php echo constant('DBG_DOMVIEWER'); ?>'; $Debugger.EditorPath = '<?php echo defined('DBG_EDITOR') ? addslashes(DBG_EDITOR) : '' ?>'; $Debugger.DebugURL = '<?php echo $this->baseURL.'/debugger_responce.php?sid='.$this->rowSeparator.'&path='.urlencode($dbg_path); ?>'; - $Debugger.EventURL = '<?php echo is_object($application->Factory) ? $application->HREF('dummy', '', Array ('pass' => 'm')) : ''; ?>'; + $Debugger.EventURL = '<?php echo is_object($application->Factory) ? $application->HREF('dummy', '', Array ('pass' => 'm', '__NO_REWRITE__' => 1)) : ''; ?>'; <?php if ($this->IsFatalError || (DBG_RAISE_ON_WARNINGS && $this->WarningCount)) { echo '$Debugger.Toggle();'; } if (DBG_TOOLBAR_BUTTONS) { echo '$Debugger.AddToolbar("$Debugger");'; } ?> window.focus(); </script> <?php if (!isset($this->ProfilerTotals['error_handling'])) { $memory_used = $debugger_start; $this->ProfilerTotalCount['error_handling'] = 0; } else { $memory_used = $debugger_start - $this->ProfilerTotals['error_handling']; } if ($returnResult) { $ret = ob_get_contents(); if ($clean_output_buffer) { ob_end_clean(); } $ret .= $this->getShortReport($memory_used); $this->reportDone = true; return $ret; } else { if (!$this->constOn('DBG_HIDE_FULL_REPORT')) { $this->breakOutofBuffering(); } elseif ($clean_output_buffer) { ob_clean(); } echo $this->getShortReport($memory_used); $this->reportDone = true; } } /** * Format's memory usage report by debugger * * @return string * @access private */ function getShortReport($memory_used) { if ($this->constOn('DBG_TOOLBAR_BUTTONS')) { // we have sql & error count in toolbar, don't duplicate here $info = Array( 'Script Runtime' => 'PROFILE:script_runtime', 'SQL\'s Runtime' => 'PROFILE_T:sql', ); } else { // toolbar not visible, then show sql & error count too $info = Array ( 'Script Runtime' => 'PROFILE:script_runtime', 'SQL\'s Runtime' => 'PROFILE_T:sql', '-' => 'SEP:-', 'Notice / Warning' => 'PROFILE_TC:error_handling', 'SQLs Count' => 'PROFILE_TC:sql', ); } $ret = '<tr><td>Application:</td><td><b>'.$this->formatSize($memory_used).'</b> ('.$memory_used.')</td></tr>'; foreach ($info as $title => $value_key) { list ($record_type, $record_data) = explode(':', $value_key, 2); switch ($record_type) { case 'PROFILE': // profiler totals value $Data =& $this->ProfilerData[$record_data]; $profile_time = ($Data['ends'] - $Data['begins']); // in seconds $ret .= '<tr><td>'.$title.':</td><td><b>'.sprintf('%.5f', $profile_time).' s</b></td></tr>'; break; case 'PROFILE_TC': // profile totals record count $record_cell = '<td>'; if ($record_data == 'error_handling' && $this->ProfilerTotalCount[$record_data] > 0) { $record_cell = '<td class="debug_error">'; } $ret .= '<tr>'.$record_cell.$title.':</td>'.$record_cell.'<b>'.$this->ProfilerTotalCount[$record_data].'</b></td></tr>'; break; case 'PROFILE_T': // profile total $record_cell = '<td>'; $total = array_key_exists($record_data, $this->ProfilerTotals) ? $this->ProfilerTotals[$record_data] : 0; $ret .= '<tr>'.$record_cell.$title.':</td>'.$record_cell.'<b>'.sprintf('%.5f', $total).' s</b></td></tr>'; break; case 'SEP': $ret .= '<tr><td colspan="2" style="height: 1px; background-color: #000000; padding: 0px;"><img src="'.$this->dummyImage.'" height="1" alt=""/></td></tr>'; break; } } return '<br /><table class="dbg_stats_table"><tr><td style="border-color: #FFFFFF;"><table class="dbg_stats_table" align="left">'.$ret.'</table></td></tr></table>'; } /** * User-defined error handler * * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @param array $errcontext */ function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '') { $this->ProfilerData['error_handling']['begins'] = memory_get_usage(); $errorType = $this->getErrorNameByCode($errno); if (!$errorType) { trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR); return false; } if ($this->constOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT)) return; if (preg_match('/(.*)#([\d]+)$/', $errstr, $rets) ) { // replace short message with long one (due triger_error limitations on message size) $long_id = $rets[2]; $errstr = $this->longErrors[$long_id]; unset($this->longErrors[$long_id]); } if (strpos($errfile,'eval()\'d code') !== false) { $errstr = '[<b>EVAL</b>, line <b>'.$errline.'</b>]: '.$errstr; $tmpStr = $errfile; $pos = strpos($tmpStr,'('); $errfile = substr($tmpStr, 0, $pos); $pos++; $errline = substr($tmpStr,$pos,strpos($tmpStr,')',$pos)-$pos); } $this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error'); $this->ProfilerData['error_handling']['ends'] = memory_get_usage(); $this->profilerAddTotal('error_handling', 'error_handling'); if ($errorType == 'Warning') { $this->WarningCount++; } if (substr($errorType, 0, 5) == 'Fatal') { $this->IsFatalError = true; // append debugger report to data in buffer & clean buffer afterwards die( $this->breakOutofBuffering(false) . $this->printReport(true) ); } } function breakOutofBuffering($flush = true) { $buffer_content = Array(); while (ob_get_level()) { $buffer_content[] = ob_get_clean(); } $ret = implode('', array_reverse($buffer_content)); if ($flush) { echo $ret; flush(); } return $ret; } function saveToFile($msg) { $fp = fopen($_SERVER['DOCUMENT_ROOT'].'/vb_debug.txt', 'a'); fwrite($fp, $msg."\n"); fclose($fp); } /** * Formats file/memory size in nice way * * @param int $bytes * @return string * @access public */ function formatSize($bytes) { if ($bytes >= 1099511627776) { $return = round($bytes / 1024 / 1024 / 1024 / 1024, 2); $suffix = "TB"; } elseif ($bytes >= 1073741824) { $return = round($bytes / 1024 / 1024 / 1024, 2); $suffix = "GB"; } elseif ($bytes >= 1048576) { $return = round($bytes / 1024 / 1024, 2); $suffix = "MB"; } elseif ($bytes >= 1024) { $return = round($bytes / 1024, 2); $suffix = "KB"; } else { $return = $bytes; $suffix = "Byte"; } $return .= ' '.$suffix; return $return; } function printConstants($constants) { if (!is_array($constants)) { $constants = explode(',', $constants); } $contant_tpl = '<tr><td>%s</td><td><b>%s</b></td></tr>'; $ret = '<table class="dbg_flat_table" style="width: '.$this->getWindowWidth().'px;">'; foreach ($constants as $constant_name) { $ret .= sprintf($contant_tpl, $constant_name, constant($constant_name)); } $ret .= '</table>'; $this->appendHTML($ret); } function AttachToApplication() { if (!$this->constOn('DBG_HANDLE_ERRORS')) { return true; } if (class_exists('kApplication')) { restore_error_handler(); // replace application error handler with own $this->Application =& kApplication::Instance(); $this->Application->Debugger =& $this; $this->Application->errorHandlers[] = Array (&$this, 'saveError'); } else { set_error_handler(Array(&$this, 'saveError')); } } /** * Returns HTML for tools section * * @return string */ function _getToolsHTML() { $html = '<table> <tr> <td>System Tools:</td> <td> <select id="reset_cache" style="border: 1px solid #000000;"> <option value=""></option> <option value="events[adm][OnResetModRwCache]">Reset mod_rewrite Cache</option> <option value="events[adm][OnResetCMSMenuCache]">Reset SMS Menu Cache</option> <option value="events[adm][OnResetSections]">Reset Sections Cache</option> <option value="events[adm][OnResetConfigsCache]">Reset Configs Cache</option> <option value="events[adm][OnRebuildThemes]">Re-build Themes Files</option> <option value="events[lang][OnReflectMultiLingualFields]">Re-build Multilanguage Fields</option> <option value="events[adm][OnDeleteCompiledTemplates]">Delete Compiled Templates</option> </select> </td> <td> <input type="button" class="button" onclick="$Debugger.resetCache(\'reset_cache\');" value="Go"/> </td> </tr> </table>'; return $html; } /** * Returns HTML for dom viewer section * * @return string */ function _getDomViewerHTML() { $html = '<table> <tr> <td> <a href="http://www.brainjar.com/dhtml/domviewer/" target="_blank">DomViewer</a>: </td> <td> <input id="dbg_domviewer" type="text" value="window" style="border: 1px solid #000000;"/> </td> <td> <button class="button" onclick="return $Debugger.OpenDOMViewer();">Show</button> </td> </tr> </table>'; return $html; } } if (!function_exists('memory_get_usage')) { function memory_get_usage(){ return -1; } } if (!Debugger::constOn('DBG_ZEND_PRESENT')) { $debugger = new Debugger(); } if (Debugger::constOn('DBG_USE_SHUTDOWN_FUNC')) { register_shutdown_function( Array(&$debugger, 'printReport') ); } } \ No newline at end of file Index: branches/5.1.x/core/kernel/utility/unit_config_reader.php =================================================================== --- branches/5.1.x/core/kernel/utility/unit_config_reader.php (revision 13988) +++ branches/5.1.x/core/kernel/utility/unit_config_reader.php (revision 13989) @@ -1,1104 +1,1106 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kUnitConfigReader extends kBase { /** * Configs readed * * @var Array * @access private */ var $configData = Array(); var $configFiles = Array(); var $CacheExpired = false; var $prefixFiles = array(); var $ProcessAllConfigs = false; var $FinalStage = false; var $StoreCache = false; var $AfterConfigProcessed = array(); /** * Escaped directory separator for using in regular expressions * * @var string */ var $_directorySeparator = ''; /** * Regular expression for detecting module folder * * @var string */ var $_moduleFolderRegExp = ''; /** * Folders to skip during unit config search * * @var Array */ var $_skipFolders = Array ('CVS', '.svn', 'admin_templates', 'libchart'); /** * Scan kernel and user classes * for available configs * * @access protected */ function Init($prefix,$special) { parent::Init($prefix,$special); $this->_directorySeparator = preg_quote(DIRECTORY_SEPARATOR); $editor_path = explode('/', trim(EDITOR_PATH, '/')); $this->_skipFolders[] = array_pop($editor_path); // last of cmseditor folders $this->_moduleFolderRegExp = '#' . $this->_directorySeparator . '(core|modules' . $this->_directorySeparator . '.*?)' . $this->_directorySeparator . '#'; } function CacheParsedData() { $event_manager =& $this->Application->recallObject('EventManager'); $aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray'); $config_vars = Array ( // session related 'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck', 'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup', 'User_LoggedInGroup', // output related 'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel', 'Config_Site_Time', 'SystemTagCache', // tracking related 'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding', 'UseCronForRegularEvent', ); foreach ($config_vars as $var) { $this->Application->ConfigValue($var); } $cache = Array( 'Factory.Files' => $this->Application->Factory->Files, 'Factory.realClasses' => $this->Application->Factory->realClasses, 'Factory.Dependencies' => $this->Application->Factory->Dependencies, 'ConfigReader.prefixFiles' => $this->prefixFiles, 'EventManager.buildEvents' => $event_manager->buildEvents, 'EventManager.beforeRegularEvents' => $event_manager->beforeRegularEvents, 'EventManager.afterRegularEvents' => $event_manager->afterRegularEvents, 'EventManager.beforeHooks' => $event_manager->beforeHooks, 'EventManager.afterHooks' => $event_manager->afterHooks, 'TagsAggregator.data' => $aggregator->_Array, // the following caches should be reset based on admin interaction (adjusting config, enabling modules etc) 'Application.Caches.ConfigVariables' => $this->Application->Caches['ConfigVariables'], 'Application.ConfigCacheIds' => $this->Application->ConfigCacheIds, 'Application.ConfigHash' => $this->Application->ConfigHash, 'Application.ReplacementTemplates' => $this->Application->ReplacementTemplates, 'Application.RewriteListeners' => $this->Application->RewriteListeners, 'Application.ModuleInfo' => $this->Application->ModuleInfo, ); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->setCache('master:configs_parsed', serialize($cache)); $this->Application->setCache('master:config_files', serialize($this->configFiles)); } else { $this->Application->setDBCache('configs_parsed', serialize($cache)); $this->Application->setDBCache('config_files', serialize($this->configFiles)); } $cache_rebuild_by = SERVER_NAME . ' (' . getenv('REMOTE_ADDR') . ') - ' . adodb_date('d/m/Y H:i:s'); $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by); unset($this->configFiles); } function RestoreParsedData() { $conn =& $this->Application->GetADODBConnection(); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $data = $this->Application->getCache('master:configs_parsed', false); } else { $data = $this->Application->getDBCache('configs_parsed'); } if ($data) { $cache = unserialize($data); $this->Application->Factory->Files = $cache['Factory.Files']; $this->Application->Factory->realClasses = $cache['Factory.realClasses']; $this->Application->Factory->Dependencies = $cache['Factory.Dependencies']; $this->prefixFiles = $cache['ConfigReader.prefixFiles']; $event_manager =& $this->Application->recallObject('EventManager'); $event_manager->buildEvents = $cache['EventManager.buildEvents']; $event_manager->beforeRegularEvents = $cache['EventManager.beforeRegularEvents']; $event_manager->afterRegularEvents = $cache['EventManager.afterRegularEvents']; $event_manager->beforeHooks = $cache['EventManager.beforeHooks']; $event_manager->afterHooks = $cache['EventManager.afterHooks']; $aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray'); $aggregator->_Array = $cache['TagsAggregator.data']; $this->Application->ConfigHash = $cache['Application.ConfigHash']; $this->Application->Caches['ConfigVariables'] = $cache['Application.ConfigCacheIds']; $this->Application->ConfigCacheIds = $cache['Application.ConfigCacheIds']; $this->Application->ReplacementTemplates = $cache['Application.ReplacementTemplates']; $this->Application->RewriteListeners = $cache['Application.RewriteListeners']; $this->Application->ModuleInfo = $cache['Application.ModuleInfo']; return true; } else return false; } function ResetParsedData($include_sections = false) { $conn =& $this->Application->GetADODBConnection(); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->deleteCache('master:configs_parsed'); } else { $this->Application->deleteDBCache('configs_parsed'); } if ($include_sections) { if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->deleteCache('master:sections_parsed'); } else { $this->Application->deleteDBCache('sections_parsed'); } } } function scanModules($folderPath, $cache = true) { if (defined('IS_INSTALL') && IS_INSTALL && !defined('FORCE_CONFIG_CACHE')) { // disable config caching during installation $cache = false; } if ($cache) { $restored = $this->RestoreParsedData(); if ($restored) return; } $this->ProcessAllConfigs = true; $this->includeConfigFiles($folderPath, $cache); $this->ParseConfigs(); // tell AfterConfigRead to store cache if neede // can't store it here beacuse AfterConfigRead needs ability to change config data $this->StoreCache = $cache; } function findConfigFiles($folderPath, $level = 0) { /*if ($level == 0) { if ($this->Application->isDebugMode()) { $start_time = getmicrotime(); $this->Application->Debugger->appendHTML('kUnitConfigReader::findConfigFiles("' . $folderPath . '")'); $this->Application->Debugger->appendTrace(); } }*/ // if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; $folderPath = preg_replace($reg_exp, '', $folderPath, 1); // this make sense, since $folderPath may NOT contain FULL_PATH $base_folder = FULL_PATH . $folderPath . DIRECTORY_SEPARATOR; $sub_folders = glob($base_folder . '*', GLOB_ONLYDIR); if (!$sub_folders) { return ; } if ($level == 0) { // don't scan Front-End themes because of extensive directory structure $sub_folders = array_diff($sub_folders, Array ($base_folder . 'themes', $base_folder . 'tools')); } foreach ($sub_folders as $full_path) { $sub_folder = substr($full_path, strlen($base_folder)); if (in_array($sub_folder, $this->_skipFolders)) { continue; } if (preg_match('/^\./', $sub_folder)) { // don't scan ".folders" continue; } $config_name = $this->getConfigName($folderPath . DIRECTORY_SEPARATOR . $sub_folder); if (file_exists(FULL_PATH . $config_name)) { $this->configFiles[] = $config_name; } $this->findConfigFiles($full_path, $level + 1); } /*if ($level == 0) { if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('kUnitConfigReader::findConfigFiles("' . FULL_PATH . $folderPath . '"): ' . (getmicrotime() - $start_time)); } }*/ } function includeConfigFiles($folderPath, $cache = true) { $this->Application->refreshModuleInfo(); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $data = $this->Application->getCache('master:config_files', false); } else { $data = $this->Application->getDBCache('config_files'); } if ($cache && $data) { $this->configFiles = unserialize($data); if ( !defined('DBG_VALIDATE_CONFIGS') && !DBG_VALIDATE_CONFIGS ) { shuffle($this->configFiles); } } else { $this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // search from core directory $this->findConfigFiles($folderPath); // search from modules directory } foreach ($this->configFiles as $filename) { $prefix = $this->PreloadConfigFile($filename); if (!$prefix) { trigger_error('Prefix not defined in config file ' . $filename, E_USER_ERROR); } } } /** * Process all read config files - called ONLY when there is no cache! * */ function ParseConfigs() { // 1. process normal configs and their dependencies $prioritized_configs = array(); foreach ($this->configData as $prefix => $config) { if (isset($config['ConfigPriority'])) { $prioritized_configs[$prefix] = $config['ConfigPriority']; continue; } $this->parseConfig($prefix); } foreach ($this->configData as $prefix => $config) { $this->ProcessDependencies($prefix); $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); } // 2. process prioritized configs and their dependencies asort($prioritized_configs); foreach ($prioritized_configs as $prefix => $priority) { $this->parseConfig($prefix); } foreach ($prioritized_configs as $prefix => $priority) { $this->ProcessDependencies($prefix); } } function AfterConfigRead($store_cache = null) { // if (!$this->ProcessAllConfigs) return ; $this->FinalStage = true; foreach ($this->configData as $prefix => $config) { $this->runAfterConfigRead($prefix); } if (!isset($store_cache)) { // store cache not overrided -> use global setting $store_cache = $this->StoreCache; } if ($store_cache || (defined('IS_INSTALL') && IS_INSTALL)) { // cache is not stored during install, but dynamic clones should be processed in any case $this->processDynamicClones(); $this->retrieveCollections(); } if ($store_cache) { $this->_sortRewriteListeners(); $this->CacheParsedData(); if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS) { // validate configs here to have changes from OnAfterConfigRead hooks to prefixes foreach ($this->configData as $prefix => $config) { if (!isset($config['TableName'])) continue; $this->ValidateConfig($prefix); } } $after_event = new kEvent('adm:OnAfterCacheRebuild'); $this->Application->HandleEvent($after_event); } } /** * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first) * */ function _sortRewriteListeners() { $listeners = Array (); $prioritized_listeners = Array (); // process non-prioritized listeners foreach ($this->Application->RewriteListeners as $prefix => $listener_data) { if ($listener_data['priority'] === false) { $listeners[$prefix] = $listener_data; } else { $prioritized_listeners[$prefix] = $listener_data['priority']; } } // process prioritized listeners asort($prioritized_listeners, SORT_NUMERIC); foreach ($prioritized_listeners as $prefix => $priority) { $listeners[$prefix] = $this->Application->RewriteListeners[$prefix]; } $this->Application->RewriteListeners = $listeners; } /** * Re-reads all configs * */ function ReReadConfigs() { // clear restored cache (not in db) $this->Application->Factory->Files = Array (); $this->Application->Factory->realClasses = Array (); $this->Application->Factory->Dependencies = Array (); $this->Application->EventManager->beforeRegularEvents = Array (); $this->Application->EventManager->afterRegularEvents = Array (); $this->Application->EventManager->beforeHooks = Array (); $this->Application->EventManager->afterHooks = Array (); // otherwise ModulesHelper indirectly used from includeConfigFiles won't work $this->Application->RegisterDefaultClasses(); // parse all configs $this->ProcessAllConfigs = true; $this->AfterConfigProcessed = Array (); $this->includeConfigFiles(MODULES_PATH, false); $this->ParseConfigs(); $this->AfterConfigRead(false); $this->processDynamicClones(); - $this->retrieveCollections(); + + // don't call kUnitConfigReader::retrieveCollections since it + // will overwrite what we already have in kApplication class instance } /** * Process clones, that were defined via OnAfterConfigRead event * */ function processDynamicClones() { $new_clones = Array(); foreach ($this->configData as $prefix => $config) { $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); if ($clones) { $new_clones = array_merge($new_clones, $clones); } } // call OnAfterConfigRead for cloned configs $new_clones = array_unique($new_clones); foreach ($new_clones as $prefix) { $this->runAfterConfigRead($prefix); } } /** * Process all collectable unit config options here to also catch ones, defined from OnAfterConfigRead events * */ function retrieveCollections() { foreach ($this->configData as $prefix => $config) { // collect replacement templates if (array_key_exists('ReplacementTemplates', $config) && $config['ReplacementTemplates']) { $this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config['ReplacementTemplates']); } // collect rewrite listeners if (array_key_exists('RewriteListener', $config) && $config['RewriteListener']) { $rewrite_listeners = $config['RewriteListener']; if (!is_array($rewrite_listeners)) { // when one method is used to build and parse url $rewrite_listeners = Array ($rewrite_listeners, $rewrite_listeners); } foreach ($rewrite_listeners as $index => $rewrite_listener) { if (strpos($rewrite_listener, ':') === false) { $rewrite_listeners[$index] = $prefix . '_EventHandler:' . $rewrite_listener; } } $rewrite_priority = array_key_exists('RewritePriority', $config) ? $config['RewritePriority'] : false; $this->Application->RewriteListeners[$prefix] = Array ('listener' => $rewrite_listeners, 'priority' => $rewrite_priority); } } } /** * Register nessasary classes * This method should only process the data which is cached! * * @param string $prefix * @access private */ function parseConfig($prefix) { $config =& $this->configData[$prefix]; $event_manager =& $this->Application->recallObject('EventManager'); /* @var $event_manager kEventManager */ $register_classes = getArrayValue($config,'RegisterClasses'); if (!$register_classes) $register_classes = Array(); $class_params=Array('ItemClass','ListClass','EventHandlerClass','TagProcessorClass'); foreach($class_params as $param_name) { if ( !(isset($config[$param_name]) ) ) continue; $config[$param_name]['pseudo'] = $this->getPrefixByParamName($param_name,$prefix); $register_classes[] = $config[$param_name]; } foreach($register_classes as $class_info) { $require_classes = getArrayValue($class_info, 'require_classes'); if ($require_classes) { if (!is_array($require_classes)) { $require_classes = array($require_classes); } if (!isset($config['_Dependencies'][$class_info['class']])) { $config['_Dependencies'][$class_info['class']] = array(); } $config['_Dependencies'][$class_info['class']] = array_merge($config['_Dependencies'][$class_info['class']], $require_classes); } $this->Application->registerClass( $class_info['class'], $config['BasePath'] . DIRECTORY_SEPARATOR . $class_info['file'], $class_info['pseudo']/*, getArrayValue($class_info, 'require_classes')*/ ); if (getArrayValue($class_info, 'build_event')) { $event_manager->registerBuildEvent($class_info['pseudo'],$class_info['build_event']); } } $regular_events = getArrayValue($config, 'RegularEvents'); if ($regular_events) { foreach ($regular_events as $short_name => $regular_event_info) { $event_status = array_key_exists('Status', $regular_event_info) ? $regular_event_info['Status'] : STATUS_ACTIVE; $event_manager->registerRegularEvent( $short_name, $config['Prefix'].':'.$regular_event_info['EventName'], $regular_event_info['RunInterval'], $regular_event_info['Type'], $event_status); } } $hooks = getArrayValue($config, 'Hooks'); if (is_array($hooks) && count($hooks) > 0) { foreach ($hooks as $hook) { if (isset($config['ParentPrefix']) && $hook['HookToPrefix'] == $config['ParentPrefix']) { trigger_error('Depricated Hook Usage [prefix: <b>'.$config['Prefix'].'</b>; do_prefix: <b>'.$hook['DoPrefix'].'</b>] use <b>#PARENT#</b> as <b>HookToPrefix</b> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); } if ($hook['HookToPrefix'] == '') { $hook['HookToPrefix'] = $config['Prefix']; // new: set hooktoprefix to current prefix if not set } if (isset($config['ParentPrefix'])) { // new: allow to set hook to parent prefix what ever it is if ($hook['HookToPrefix'] == '#PARENT#') { $hook['HookToPrefix'] = $config['ParentPrefix']; } if ($hook['DoPrefix'] == '#PARENT#') { $hook['DoPrefix'] = $config['ParentPrefix']; } } elseif ($hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#') { continue; // we need parent prefix but it's not set ! } $do_prefix = $hook['DoPrefix'] == '' ? $config['Prefix'] : $hook['DoPrefix']; if ( !is_array($hook['HookToEvent']) ) { $hook_events = Array( $hook['HookToEvent'] ); } else { $hook_events = $hook['HookToEvent']; } foreach ($hook_events as $hook_event) { $this->Application->registerHook($hook['HookToPrefix'], $hook['HookToSpecial'], $hook_event, $hook['Mode'], $do_prefix, $hook['DoSpecial'], $hook['DoEvent'], $hook['Conditional']); } } } if ( is_array(getArrayValue($config, 'AggregateTags')) ) { foreach ($config['AggregateTags'] as $aggregate_tag) { if (isset($config['ParentPrefix'])) { if ($aggregate_tag['AggregateTo'] == $config['ParentPrefix']) { trigger_error('Depricated Aggregate Tag Usage [prefix: <b>'.$config['Prefix'].'</b>; AggregateTo: <b>'.$aggregate_tag['AggregateTo'].'</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); } if ($aggregate_tag['AggregateTo'] == '#PARENT#') { $aggregate_tag['AggregateTo'] = $config['ParentPrefix']; } } $aggregate_tag['LocalPrefix'] = $config['Prefix']; $this->Application->registerAggregateTag($aggregate_tag); } } } function ValidateConfig($prefix) { global $debugger; $config =& $this->configData[$prefix]; $tablename = $config['TableName']; $float_types = Array ('float', 'double', 'numeric'); $conn =& $this->Application->GetADODBConnection(); $table_found = $conn->Query('SHOW TABLES LIKE "'.$tablename.'"'); if (!$table_found) { // config present, but table missing, strange safeDefine('DBG_RAISE_ON_WARNINGS', 1); $debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$tablename</strong> missing, but prefix <b>".$config['Prefix']."</b> requires it!"); $debugger->WarningCount++; return ; } $res = $conn->Query('DESCRIBE '.$tablename); $config_link = $debugger->getFileLink(FULL_PATH.$this->prefixFiles[$config['Prefix']], 1, $config['Prefix']); $error_messages = Array ( 'field_not_found' => 'Field <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config', 'default_missing' => 'Default value for field <strong>%s</strong> not set in config', 'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required', 'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>', 'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>', 'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)', 'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date', 'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id', 'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config', 'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config', 'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config', 'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database', 'invalid_calculated_field' => 'Calculated field <strong>%s</strong> is missing corresponding virtual field', ); $config_errors = Array (); $tablename = preg_replace('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', '\\1', $tablename); // remove table prefix // validate unit config field declaration in relation to database table structure foreach ($res as $field) { $f_name = $field['Field']; if (getArrayValue($config, 'Fields')) { if (preg_match('/l[\d]+_[\w]/', $f_name)) { // skip multilingual fields continue; } if (!array_key_exists ($f_name, $config['Fields'])) { $config_errors[] = sprintf($error_messages['field_not_found'], $f_name); } else { $db_default = $field['Default']; if (is_numeric($db_default)) { $db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default; } $default_missing = false; $options = $config['Fields'][$f_name]; $not_null = isset($options['not_null']) && $options['not_null']; $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; if (!array_key_exists('default', $options)) { $config_errors[] = sprintf($error_messages['default_missing'], $f_name); $default_missing = true; } if ($field['Null'] != 'YES') { // field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "") if ( $f_name != $config['IDField'] && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) { $config_errors[] = sprintf($error_messages['not_null_error1'], $f_name); } if ($not_null && !isset($options['default']) ) { $config_errors[] = sprintf($error_messages['not_null_error2'], $f_name); } } elseif ($not_null) { $config_errors[] = sprintf($error_messages['not_null_error3'], $f_name); } if (($formatter == 'kDateFormatter') && $not_null) { $config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name); } // columns, holding userid should have NULL as default value if (array_key_exists('type', $options) && !$default_missing) { // both type and default value set if (preg_match('/ById$/', $f_name) && $options['default'] !== null) { $config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name); } } if (!array_key_exists('type', $options)) { $config_errors[] = sprintf($error_messages['type_missing'], $f_name); } if (!$default_missing && ($field['Type'] != 'text')) { if ( is_null($db_default) && $not_null ) { $db_default = $options['type'] == 'string' ? '' : 0; } if ($f_name == $config['IDField'] && $options['type'] != 'string' && $options['default'] !== 0) { $config_errors[] = sprintf($error_messages['invalid_default'], '<span class="debug_error">IDField</span> ', $f_name, $this->varDump($options['default']), $this->varDump($field['Default'])); } else if (((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types)) { $config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->varDump($options['default']), $this->varDump($db_default)); } } } } } // validate virtual fields if ( array_key_exists('VirtualFields', $config) ) { foreach ($config['VirtualFields'] as $f_name => $options) { if (!array_key_exists('type', $options)) { $config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name); } if (array_key_exists('not_null', $options)) { $config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name); } if (!array_key_exists('default', $options)) { $config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name); } } } // validate calculated fields if ( array_key_exists('CalculatedFields', $config) ) { foreach ($config['CalculatedFields'] as $special => $calculated_fields) { foreach ($calculated_fields as $calculated_field => $calculated_field_expr) { if ( !isset($config['VirtualFields'][$calculated_field]) ) { $config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field); } } } $config_errors = array_unique($config_errors); } if ($config_errors) { $error_prefix = '<strong class="debug_error">Config Error'.(count($config_errors) > 1 ? 's' : '').': </strong> for prefix <strong>'.$config_link.'</strong> ('.$tablename.') in unit config:<br />'; $config_errors = $error_prefix.' '.implode('<br /> ', $config_errors); safeDefine('DBG_RAISE_ON_WARNINGS', 1); $debugger->appendHTML($config_errors); $debugger->WarningCount++; } } function varDump($value) { return '<strong>'.var_export($value, true).'</strong> of '.gettype($value); } function ProcessDependencies($prefix) { $config =& $this->configData[$prefix]; $deps = getArrayValue($config, '_Dependencies'); if (!$deps) return ; foreach ($deps as $real_class => $requires) { foreach ($requires as $class) { $this->Application->registerDependency($real_class, $class); } } unset($config['_Dependencies']); } function postProcessConfig($prefix, $config_key, $dst_prefix_var) { $main_config =& $this->configData[$prefix]; $sub_configs = isset($main_config[$config_key]) && $main_config[$config_key] ? $main_config[$config_key] : false; // getArrayValue($main_config, $config_key); if (!$sub_configs) { return array(); } unset($main_config[$config_key]); $processed = array(); foreach ($sub_configs as $sub_prefix => $sub_config) { if ($config_key == 'AggregateConfigs' && !isset($this->configData[$sub_prefix])) { $this->loadConfig($sub_prefix); } $sub_config['Prefix'] = $sub_prefix; $this->configData[$sub_prefix] = array_merge_recursive2($this->configData[$$dst_prefix_var], $sub_config); // when merging empty array to non-empty results non-empty array, but empty is required foreach ($sub_config as $sub_key => $sub_value) { if (!$sub_value) { unset($this->configData[$sub_prefix][$sub_key]); } } if ($config_key == 'Clones') { $this->prefixFiles[$sub_prefix] = $this->prefixFiles[$prefix]; } $this->postProcessConfig($sub_prefix, $config_key, $dst_prefix_var); if ($config_key == 'AggregateConfigs') { $processed = array_merge($this->postProcessConfig($sub_prefix, 'Clones', 'prefix'), $processed); } elseif ($this->ProcessAllConfigs) { $this->parseConfig($sub_prefix); } array_push($processed, $sub_prefix); } if (!$prefix) { // configs, that used only for cloning & not used ifself unset($this->configData[$prefix]); } return array_unique($processed); } function PreloadConfigFile($filename) { $config_found = file_exists(FULL_PATH . $filename) && $this->configAllowed($filename); if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES) { if ( in_array($filename, get_required_files()) ) { return; } global $debugger; if ($config_found) { $file = FULL_PATH . $filename; $file_crc = crc32($file); $debugger->ProfileStart('inc_' . $file_crc, $file); include_once($file); $debugger->ProfileFinish('inc_' . $file_crc); $debugger->profilerAddTotal('includes', 'inc_' . $file_crc); } } elseif ($config_found) { include_once(FULL_PATH . $filename); } if ($config_found) { if (isset($config) && $config) { // config file is included for 1st time -> save it's content for future processing $prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : ''; preg_match($this->_moduleFolderRegExp, $filename, $rets); $config['ModuleFolder'] = str_replace(DIRECTORY_SEPARATOR, '/', $rets[1]); $config['BasePath'] = dirname(FULL_PATH . $filename); if (array_key_exists('AdminTemplatePath', $config)) { // append template base folder for admin templates path of this prefix $module_templates = $rets[1] == 'core' ? '' : substr($rets[1], 8) . '/'; $config['AdminTemplatePath'] = $module_templates . $config['AdminTemplatePath']; } if (array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename)) { trigger_error( 'Single unit config prefix "<strong>' . $prefix . '</strong>" ' . 'is used in multiple unit config files: ' . '"<strong>' . $this->prefixFiles[$prefix] . '</strong>", "<strong>' . $filename . '</strong>"', E_USER_WARNING ); } $this->configData[$prefix] = $config; $this->prefixFiles[$prefix] = $filename; return $prefix; } elseif ($prefix = array_search($filename, $this->prefixFiles)) { // attempt is made to include config file twice or more, but include_once prevents that, // but file exists on hdd, then it is already saved to all required arrays, just return it's prefix return $prefix; } } return 'dummy'; } function loadConfig($prefix) { if (!isset($this->prefixFiles[$prefix])) { if ($this->Application->isDebugMode()) $this->Application->Debugger->appendTrace(); trigger_error('Configuration file for prefix <b>'.$prefix.'</b> is unknown', E_USER_ERROR); return ; } $file = $this->prefixFiles[$prefix]; $prefix = $this->PreloadConfigFile($file); if ($this->FinalStage) { // run prefix OnAfterConfigRead so all // hooks to it can define their clonses $this->runAfterConfigRead($prefix); } $clones = $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = array_merge($this->postProcessConfig($prefix, 'Clones', 'prefix'), $clones); if ($this->FinalStage) { $clones = array_unique($clones); foreach ($clones as $a_prefix) { $this->runAfterConfigRead($a_prefix); } } } function runAfterConfigRead($prefix) { if (in_array($prefix, $this->AfterConfigProcessed)) { return ; } $this->Application->HandleEvent( new kEvent($prefix . ':OnAfterConfigRead') ); if (!(defined('IS_INSTALL') && IS_INSTALL)) { // allow to call OnAfterConfigRead multiple times during install array_push($this->AfterConfigProcessed, $prefix); } } /** * Reads unit (specified by $prefix) * option specified by $option * * @param string $prefix * @param string $name * @param mixed $default * @return string * @access public */ function getUnitOption($prefix, $name, $default = false) { if (preg_match('/(.*)\.(.*)/', $prefix, $rets)) { if (!isset($this->configData[$rets[1]])) { $this->loadConfig($rets[1]); } $ret = isset($this->configData[$rets[1]][$name][$rets[2]]) ? $this->configData[$rets[1]][$name][$rets[2]] : false; // $ret = getArrayValue($this->configData, $rets[1], $name, $rets[2]); } else { if (!isset($this->configData[$prefix])) { $this->loadConfig($prefix); } $ret = isset($this->configData[$prefix][$name]) ? $this->configData[$prefix][$name] : false; // $ret = getArrayValue($this->configData, $prefix, $name); } return $ret === false ? $default : $ret; } /** * Read all unit with $prefix options * * @param string $prefix * @return Array * @access public */ function getUnitOptions($prefix) { if (!isset($this->configData[$prefix])) { $this->loadConfig($prefix); } return $this->configData[$prefix]; } /** * Set's new unit option value * * @param string $prefix * @param string $name * @param string $value * @access public */ function setUnitOption($prefix, $name, $value) { if (preg_match('/(.*)\.(.*)/', $prefix, $rets)) { if (!isset($this->configData[$rets[1]])) { $this->loadConfig($rets[1]); } $this->configData[$rets[1]][$name][$rets[2]] = $value; } else { if (!isset($this->configData[$prefix])) { $this->loadConfig($prefix); } $this->configData[$prefix][$name] = $value; } } function getPrefixByParamName($paramName,$prefix) { $pseudo_class_map=Array( 'ItemClass'=>'%s', 'ListClass'=>'%s_List', 'EventHandlerClass'=>'%s_EventHandler', 'TagProcessorClass'=>'%s_TagProcessor' ); return sprintf($pseudo_class_map[$paramName],$prefix); } /** * Get's config file name based * on folder name supplied * * @param string $folderPath * @return string * @access private */ function getConfigName($folderPath) { return $folderPath . DIRECTORY_SEPARATOR . basename($folderPath) . '_config.php'; } /** * Checks if config file is allowed for includion (if module of config is installed) * * @param string $config_path relative path from in-portal directory */ function configAllowed($config_path) { static $module_paths = null; if (defined('IS_INSTALL') && IS_INSTALL) { // at installation start no modules in db and kernel configs could not be read return true; } if (preg_match('#^' . $this->_directorySeparator . 'core#', $config_path)) { // always allow to include configs from "core" module's folder return true; } if (!$this->Application->ModuleInfo) { return false; } if (!isset($module_paths)) { $module_paths = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $module_paths[] = str_replace('/', DIRECTORY_SEPARATOR, rtrim($module_info['Path'], '/')); } $module_paths = array_unique($module_paths); } preg_match($this->_moduleFolderRegExp, $config_path, $rets); // config file path starts with module folder path return in_array($rets[1], $module_paths); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ function prefixRegistred($prefix) { return isset($this->prefixFiles[$prefix]) ? true : false; } /** * Returns config file for given prefix * * @param string $prefix * @return string */ function getPrefixFile($prefix) { return array_key_exists($prefix, $this->prefixFiles) ? $this->prefixFiles[$prefix] : false; } function iterateConfigs($callback_function, $params) { $this->includeConfigFiles(MODULES_PATH); //make sure to re-read all configs $this->AfterConfigRead(); foreach ($this->configData as $prefix => $config_data) { $callback_function[0]->$callback_function[1]($prefix, $config_data, $params); } } } \ No newline at end of file Index: branches/5.1.x/core/units/site_domains/site_domain_eh.php =================================================================== --- branches/5.1.x/core/units/site_domains/site_domain_eh.php (revision 13988) +++ branches/5.1.x/core/units/site_domains/site_domain_eh.php (revision 13989) @@ -1,258 +1,259 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2010 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class SiteDomainEventHandler extends kDBEventHandler { /** - * Allows to override standart permission mapping + * Permission check override * + * @param kEvent $event */ - function mapPermissions() + function CheckPermission(&$event) { - parent::mapPermissions(); - - $permissions = Array ( - 'OnItemBuild' => Array ('self' => true), - ); + if ($event->Name == 'OnItemBuild') { + // check permission without using $event->getSection(), + // so first cache rebuild won't lead to "ldefault_Name" field being used + return true; + } - $this->permMapping = array_merge($this->permMapping, $permissions); + return parent::CheckPermission($event); } /** * Returns ID of site domain, that matches current url * * @param kEvent $event */ function getPassedID(&$event) { if ($event->Special == 'current') { if ($this->Application->isAdmin) { $event->setEventParam('raise_warnings', 0); } else { if (PROTOCOL == 'https://') { return $this->querySiteDomain('SSLUrl', rtrim($this->Application->BaseURL(), '/')); } return $this->querySiteDomain('DomainName', $_SERVER['HTTP_HOST']); } } return parent::getPassedID($event); } function querySiteDomain($field, $value) { $site_helper =& $this->Application->recallObject('SiteHelper'); /* @var $site_helper SiteHelper */ $site_domains = $site_helper->getSiteDomains(); $domain_by_name = $site_helper->getDomainByName($field, $value); $domain_by_ip = $site_helper->getDomainByIP(); if ($domain_by_ip) { $site_domain = $site_domains[$domain_by_ip]; $redirect_mode = $site_domain['RedirectOnIPMatch']; if (($redirect_mode == SITE_DOMAIN_REDIRECT_EXTERNAL) && ($domain_by_ip == $domain_by_name)) { // redirect to external url (when visiting protected domain) $external_url = $site_domain['ExternalUrl']; if (preg_match('/^http[s]{0,1}:\/\//', $external_url)) { $this->Application->Redirect('external:' . $external_url); } else { $this->Application->Redirect('external:' . PROTOCOL . $external_url . $_SERVER['REQUEST_URI']); } } elseif (($redirect_mode == SITE_DOMAIN_REDIRECT_CURRENT) && ($domain_by_ip != $domain_by_name)) { // redirect to a domain detected by IP (when not already on it) if ((PROTOCOL == 'https://') && !$site_domain['SSLUrlUsesRegExp'] && $site_domain['SSLUrl']) { // need to remove sub folder from ssl url $ssl_url = preg_replace('/^(https:\/\/[^\/]*)(\/.*){0,1}$/', '\\1', $site_domain['SSLUrl']); $this->Application->Redirect('external:' . $ssl_url . $_SERVER['REQUEST_URI']); } elseif ((PROTOCOL == 'http://') && !$site_domain['DomainNameUsesRegExp']) { $this->Application->Redirect('external:http://' . $site_domain['DomainName'] . $_SERVER['REQUEST_URI']); } } return $domain_by_ip; } return $domain_by_name; } /** * Load item if id is available * * @param kEvent $event */ function LoadItem(&$event) { if ($this->Application->isAdmin) { // don't load domain data from cache parent::LoadItem($event); return ; } $object =& $event->getObject(); /* @var $object kDBItem */ $id = (int)$this->getPassedID($event); if ($object->isLoaded() && ($object->GetID() == $id)) { // object is already loaded by same id return ; } $site_helper =& $this->Application->recallObject('SiteHelper'); /* @var $site_helper SiteHelper */ $site_domains = $site_helper->getSiteDomains(); $domain_data = array_key_exists($id, $site_domains) ? $site_domains[$id] : false; if ($object->LoadFromHash($domain_data)) { $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_id', $object->GetID() ); } else { $object->setID($id); } } /** * Removes In-Commerce related fields, when it's not installed * * @param kEvent $event */ function OnAfterConfigRead(&$event) { parent::OnAfterConfigRead($event); if (!$this->Application->isModuleEnabled('In-Commerce')) { $remove_fields = Array ( 'BillingCountry', 'ShippingCountry', 'PrimaryCurrencyId', 'Currencies', 'PrimaryPaymentTypeId', 'PaymentTypes' ); // remove field definitions $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); foreach ($remove_fields as $remove_field) { unset($fields[$remove_field]); } $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); // remove grid columns $grids = $this->Application->getUnitOption($event->Prefix, 'Grids'); foreach ($grids as $grid_name => $grid_info) { foreach ($remove_fields as $remove_field) { if (array_key_exists($remove_field, $grid_info['Fields'])) { unset($grids[$grid_name]['Fields'][$remove_field]); } } } $this->Application->setUnitOption($event->Prefix, 'Grids', $grids); } } /** * Delete cached information about site domains * * @param kEvent $event */ function OnSave(&$event) { parent::OnSave($event); if ($event->status == erSUCCESS) { $this->_deleteCache(); } } /** * Deletes cached information about site domains */ function _deleteCache() { if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->deleteCache('master:domains_parsed'); } else { $this->Application->deleteDBCache('domains_parsed'); } $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'; $this->Conn->Query($sql); } /** * Set's required fields based on redirect mode * * @param kEvent $event */ function OnAfterItemLoad(&$event) { parent::OnAfterItemLoad($event); $this->_setRequired($event); } /** * Set's required fields based on redirect mode * * @param kEvent $event */ function OnBeforeItemCreate(&$event) { parent::OnBeforeItemCreate($event); $this->_setRequired($event); } /** * Set's required fields based on redirect mode * * @param kEvent $event */ function OnBeforeItemUpdate(&$event) { parent::OnBeforeItemUpdate($event); $this->_setRequired($event); } /** * Set's required fields * * @param kEvent $event */ function _setRequired(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $redirect_mode = $object->GetDBField('RedirectOnIPMatch'); $object->setRequired('ExternalUrl', $redirect_mode == SITE_DOMAIN_REDIRECT_EXTERNAL); $object->setRequired('DomainIPRange', $redirect_mode > 0); } }