# HG changeset patch # User Dan # Date 1208037478 14400 # Node ID 698a8f04957c1a1defb360b28d50251c44018b8d # Parent 03429d7b153753d6f219aa85cfed05c319fdcab5 Huge improvements to the template_nodb class and surrounding code; moved template compiler core to its own non-classed function to allow code re-use diff -r 03429d7b1537 -r 698a8f04957c includes/functions.php --- a/includes/functions.php Sat Apr 12 10:12:25 2008 -0400 +++ b/includes/functions.php Sat Apr 12 17:57:58 2008 -0400 @@ -547,10 +547,17 @@ { global $db, $session, $paths, $template, $plugins; // Common objects $db->close(); - + if ( ob_get_status() ) ob_end_clean(); + + // If the config hasn't been fetched yet, call grinding_halt. + if ( !defined('ENANO_CONFIG_FETCHED') ) + { + grinding_halt($t, $p); + } + // also do grinding_halt() if we're in CLI mode if ( defined('ENANO_CLI') ) { grinding_halt($t, $p); @@ -563,8 +570,11 @@ exit; } + $theme = ( defined('ENANO_CONFIG_FETCHED') ) ? getConfig('theme_default') : 'oxygen'; + $style = ( defined('ENANO_CONFIG_FETCHED') ) ? '__foo__' : 'bleu'; + $tpl = new template_nodb(); - $tpl->load_theme('oxygen', 'bleu'); + $tpl->load_theme($theme, $style); $tpl->tpl_strings['SITE_NAME'] = getConfig('site_name'); $tpl->tpl_strings['SITE_DESC'] = getConfig('site_desc'); $tpl->tpl_strings['COPYRIGHT'] = getConfig('copyright_notice'); @@ -634,9 +644,11 @@ echo "$p\n"; exit; } - + $theme = ( defined('ENANO_CONFIG_FETCHED') ) ? getConfig('theme_default') : 'oxygen'; + $style = ( defined('ENANO_CONFIG_FETCHED') ) ? '__foo__' : 'bleu'; + $tpl = new template_nodb(); - $tpl->load_theme('oxygen', 'bleu'); + $tpl->load_theme($theme, $style); $tpl->tpl_strings['SITE_NAME'] = 'Critical error'; $tpl->tpl_strings['SITE_DESC'] = 'This website is experiencing a serious error and cannot load.'; $tpl->tpl_strings['COPYRIGHT'] = 'Unable to retrieve copyright information'; @@ -644,6 +656,7 @@ $tpl->header(); echo $p; $tpl->footer(); + exit; } diff -r 03429d7b1537 -r 698a8f04957c includes/template.php --- a/includes/template.php Sat Apr 12 10:12:25 2008 -0400 +++ b/includes/template.php Sat Apr 12 17:57:58 2008 -0400 @@ -12,7 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. */ -class template { +class template +{ var $tpl_strings, $tpl_bool, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list, $named_theme_list, $default_theme, $default_style, $plugin_blocks, $namespace_string, $style_list, $theme_loaded; /** @@ -1203,141 +1204,7 @@ function compile_tpl_code($text) { - global $db, $session, $paths, $template, $plugins; // Common objects - // A random seed used to salt tags - $seed = md5 ( microtime() . mt_rand() ); - - // Strip out PHP sections - preg_match_all('/<\?php(.+?)\?>/is', $text, $php_matches); - - foreach ( $php_matches[0] as $i => $match ) - { - // Substitute the PHP section with a random tag - $tag = "{PHP:$i:$seed}"; - $text = str_replace_once($match, $tag, $text); - } - - // Escape slashes and single quotes in template code - $text = str_replace('\\', '\\\\', $text); - $text = str_replace('\'', '\\\'', $text); - - // Initialize the PHP compiled code - $text = 'ob_start(); echo \''.$text.'\'; $tpl_code = ob_get_contents(); ob_end_clean(); return $tpl_code;'; - - ## - ## Main rules - ## - - // - // Conditionals - // - - $keywords = array('BEGIN', 'BEGINNOT', 'IFSET', 'IFPLUGIN'); - $code = $plugins->setHook('template_compile_logic_keyword'); - foreach ( $code as $cmd ) - { - eval($cmd); - } - - $keywords = implode('|', $keywords); - - // Matches - // 1 2 3 4 56 7 8 - $regexp = '/()(.*)(()(.*))?()/isU'; - - /* - The way this works is: match all blocks using the standard form with a different keyword in the block each time, - and replace them with appropriate PHP logic. Plugin-extensible now. :-) - - The while-loop is to bypass what is apparently a PCRE bug. It's hackish but it works. Properly written plugins should only need - to compile templates (using this method) once for each time the template file is changed. - */ - while ( preg_match($regexp, $text) ) - { - preg_match_all($regexp, $text, $matches); - for ( $i = 0; $i < count($matches[0]); $i++ ) - { - $start_tag =& $matches[1][$i]; - $type =& $matches[2][$i]; - $test =& $matches[3][$i]; - $particle_true =& $matches[4][$i]; - $else_tag =& $matches[6][$i]; - $particle_else =& $matches[7][$i]; - $end_tag =& $matches[8][$i]; - - switch($type) - { - case 'BEGIN': - $cond = "isset(\$this->tpl_bool['$test']) && \$this->tpl_bool['$test']"; - break; - case 'BEGINNOT': - $cond = "!isset(\$this->tpl_bool['$test']) || ( isset(\$this->tpl_bool['$test']) && !\$this->tpl_bool['$test'] )"; - break; - case 'IFPLUGIN': - $cond = "getConfig('plugin_$test') == '1'"; - break; - case 'IFSET': - $cond = "isset(\$this->tpl_strings['$test'])"; - break; - default: - $code = $plugins->setHook('template_compile_logic_cond'); - foreach ( $code as $cmd ) - { - eval($cmd); - } - break; - } - - if ( !isset($cond) || ( isset($cond) && !is_string($cond) ) ) - continue; - - $tag_complete = <<<?php\n" . htmlspecialchars($text."\n\n".print_r($matches,true)) . "\n\n?>"); - - // - // Data substitution/variables - // - - // System messages - $text = preg_replace('//is', '\' . $template->tplWikiFormat($paths->sysMsg(\'\\1\')) . \'', $text); - - // Template variables - $text = preg_replace('/\{([A-z0-9_-]+?)\}/is', '\' . $this->tpl_strings[\'\\1\'] . \'', $text); - - // Reinsert PHP - - foreach ( $php_matches[1] as $i => $match ) - { - // Substitute the random tag with the "real" PHP code - $tag = "{PHP:$i:$seed}"; - $text = str_replace_once($tag, "'; $match echo '", $text); - } - - // echo('
' . htmlspecialchars($text) . '
'); - - return $text; - + return template_compiler_core($text); } /** @@ -2014,6 +1881,163 @@ } // class template /** + * The core of the template compilation engine. Independent from the Enano API for failsafe operation. + * @param string text to process + * @return string Compiled PHP code + * @access private + */ + +function template_compiler_core($text) +{ + global $db, $session, $paths, $template, $plugins; // Common objects + // A random seed used to salt tags + $seed = md5 ( microtime() . mt_rand() ); + + // Strip out PHP sections + preg_match_all('/<\?php(.+?)\?>/is', $text, $php_matches); + + foreach ( $php_matches[0] as $i => $match ) + { + // Substitute the PHP section with a random tag + $tag = "{PHP:$i:$seed}"; + $text = str_replace_once($match, $tag, $text); + } + + // Escape slashes and single quotes in template code + $text = str_replace('\\', '\\\\', $text); + $text = str_replace('\'', '\\\'', $text); + + // Initialize the PHP compiled code + $text = 'ob_start(); echo \''.$text.'\'; $tpl_code = ob_get_contents(); ob_end_clean(); return $tpl_code;'; + + ## + ## Main rules + ## + + // + // Conditionals + // + + $keywords = array('BEGIN', 'BEGINNOT', 'IFSET', 'IFPLUGIN'); + + // only do this if the plugins API is loaded + if ( is_object(@$plugins) ) + { + $code = $plugins->setHook('template_compile_logic_keyword'); + foreach ( $code as $cmd ) + { + eval($cmd); + } + } + + $keywords = implode('|', $keywords); + + // Matches + // 1 2 3 4 56 7 8 + $regexp = '/()(.*)(()(.*))?()/isU'; + + /* + The way this works is: match all blocks using the standard form with a different keyword in the block each time, + and replace them with appropriate PHP logic. Plugin-extensible now. :-) + + The while-loop is to bypass what is apparently a PCRE bug. It's hackish but it works. Properly written plugins should only need + to compile templates (using this method) once for each time the template file is changed. + */ + + profiler_log("[template] compiler matchout start"); + preg_match_all($regexp, $text, $matches); + profiler_log("[template] compiler core loop start"); + for ( $i = 0; $i < count($matches[0]); $i++ ) + { + $start_tag =& $matches[1][$i]; + $type =& $matches[2][$i]; + $test =& $matches[3][$i]; + $particle_true =& $matches[4][$i]; + $else_tag =& $matches[6][$i]; + $particle_else =& $matches[7][$i]; + $end_tag =& $matches[8][$i]; + + switch($type) + { + case 'BEGIN': + $cond = "isset(\$this->tpl_bool['$test']) && \$this->tpl_bool['$test']"; + break; + case 'BEGINNOT': + $cond = "!isset(\$this->tpl_bool['$test']) || ( isset(\$this->tpl_bool['$test']) && !\$this->tpl_bool['$test'] )"; + break; + case 'IFPLUGIN': + $cond = "getConfig('plugin_$test') == '1'"; + break; + case 'IFSET': + $cond = "isset(\$this->tpl_strings['$test'])"; + break; + default: + // only do this if the plugins API is loaded + if ( is_object(@$plugins) ) + { + $code = $plugins->setHook('template_compile_logic_cond'); + foreach ( $code as $cmd ) + { + eval($cmd); + } + } + break; + } + + if ( !isset($cond) || ( isset($cond) && !is_string($cond) ) ) + continue; + + $tag_complete = <<<?php\n" . htmlspecialchars($text."\n\n".print_r($matches,true)) . "\n\n?>"); + + // + // Data substitution/variables + // + + // System messages + $text = preg_replace('//is', '\' . $template->tplWikiFormat($paths->sysMsg(\'\\1\')) . \'', $text); + + // Template variables + $text = preg_replace('/\{([A-z0-9_-]+?)\}/is', '\' . $this->tpl_strings[\'\\1\'] . \'', $text); + + // Reinsert PHP + + foreach ( $php_matches[1] as $i => $match ) + { + // Substitute the random tag with the "real" PHP code + $tag = "{PHP:$i:$seed}"; + $text = str_replace_once($tag, "'; $match echo '", $text); + } + + // echo('
' . htmlspecialchars($text) . '
'); + + profiler_log("[template] compiler subst end"); + + return $text; +} + +/** * Handles parsing of an individual template file. Instances should only be created through $template->makeParser(). To use: * - Call $template->makeParser(template file name) - file name should be something.tpl, css/whatever.css, etc. * - Make an array of strings you want the template to access. $array['STRING'] would be referenced in the template like {STRING} @@ -2023,7 +2047,8 @@ * @access private */ -class templateIndividual extends template { +class templateIndividual extends template +{ var $tpl_strings, $tpl_bool, $tpl_code; var $compiled = false; /** @@ -2084,9 +2109,9 @@ class template_nodb { - var $fading_button, $tpl_strings, $tpl_bool, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list; - function __construct() { - + var $fading_button, $tpl_strings, $tpl_bool, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list, $named_theme_list; + function __construct() + { $this->tpl_bool = Array(); $this->tpl_strings = Array(); $this->sidebar_extra = ''; @@ -2098,23 +2123,74 @@  '; - $this->theme_list = Array(Array( - 'theme_id'=>'oxygen', - 'theme_name'=>'Oxygen', - 'theme_order'=>1, - 'enabled'=>1, - )); + // get list of themes + $this->theme_list = array(); + $this->named_theme_list = array(); + $order = 0; + + if ( $dir = @opendir( ENANO_ROOT . '/themes' ) ) + { + while ( $dh = @readdir($dir) ) + { + if ( $dh == '.' || $dh == '..' || !is_dir( ENANO_ROOT . "/themes/$dh" ) ) + continue; + $theme_dir = ENANO_ROOT . "/themes/$dh"; + if ( !file_exists("$theme_dir/theme.cfg") ) + continue; + $data = array( + 'theme_id' => $dh, + 'theme_name' => ucwords($dh), + 'enabled' => 1, + 'theme_order' => ++$order, + 'default_style' => $this->get_default_style($dh) + ); + $this->named_theme_list[$dh] = $data; + $this->theme_list[] =& $this->named_theme_list[$dh]; + } + @closedir($dir); + } } function template() { $this->__construct(); } + function get_default_style($theme_id) + { + if ( !is_dir( ENANO_ROOT . "/themes/$theme_id/css" ) ) + return false; + $ds = false; + if ( $dh = @opendir( ENANO_ROOT . "/themes/$theme_id/css" ) ) + { + while ( $dir = @readdir($dh) ) + { + if ( !preg_match('/\.css$/', $dir) ) + continue; + if ( $dir == '_printable.css' ) + continue; + $ds = preg_replace('/\.css$/', '', $dir); + break; + } + closedir($dh); + } + else + { + return false; + } + return $ds; + } function get_css($s = false) { if($s) return $this->process_template('css/'.$s); else return $this->process_template('css/'.$this->style.'.css'); } - function load_theme($name, $css, $auto_init = true) { + function load_theme($name, $css, $auto_init = true) + { + if ( !isset($this->named_theme_list[$name]) ) + $name = $this->theme_list[0]['theme_id']; + + if ( !file_exists(ENANO_ROOT . "/themes/$name/css/$css.css") ) + $css = $this->named_theme_list[$name]['default_style']; + $this->theme = $name; $this->style = $css; @@ -2169,13 +2245,20 @@ } $js_dynamic .= ''; + global $site_name, $site_desc; + $site_default_name = ( !empty($site_name) ) ? $site_name : 'Critical error'; + $site_default_desc = ( !empty($site_desc) ) ? $site_desc : 'This site is experiencing a problem and cannot load.'; + + $site_name_final = ( defined('IN_ENANO_INSTALL') && is_object($lang) ) ? $lang->get('meta_site_name') : $site_default_name; + $site_desc_final = ( defined('IN_ENANO_INSTALL') && is_object($lang) ) ? $lang->get('meta_site_desc') : $site_default_desc; + // The rewritten template engine will process all required vars during the load_template stage instead of (cough) re-processing everything each time around. $tpl_strings = Array( 'PAGE_NAME'=>$this_page, 'PAGE_URLNAME'=>'Null', - 'SITE_NAME'=> ( defined('IN_ENANO_INSTALL') && is_object($lang) ) ? $lang->get('meta_site_name') : 'Critical error', + 'SITE_NAME' => $site_name_final, 'USERNAME'=>'admin', - 'SITE_DESC'=>( defined('IN_ENANO_INSTALL') && is_object($lang) ) ? $lang->get('meta_site_desc') : 'This site is experiencing a problem and cannot load.', + 'SITE_DESC' => $site_desc_final, 'TOOLBAR'=>$tb, 'SCRIPTPATH'=>scriptPath, 'CONTENTPATH'=>contentPath, @@ -2200,30 +2283,55 @@ ); $this->tpl_strings = array_merge($tpl_strings, $this->tpl_strings); - $sidebar = ( gettype($sideinfo) == 'string' ) ? $sideinfo : ''; - if($sidebar != '') + $sidebar = ( is_array(@$sideinfo) ) ? $sideinfo : ''; + if ( $sidebar != '' ) { - if(isset($tplvars['sidebar_top'])) + if ( isset($tplvars['sidebar_top']) ) { $text = $this->makeParserText($tplvars['sidebar_top']); $top = $text->run(); - } else { + } + else + { $top = ''; } + $p = $this->makeParserText($tplvars['sidebar_section']); - $p->assign_vars(Array( - 'TITLE'=>$lang->get('meta_sidebar_heading'), - 'CONTENT'=>$sidebar, + $b = $this->makeParserText($tplvars['sidebar_button']); + $sidebar_text = ''; + + foreach ( $sidebar as $title => $links ) + { + $p->assign_vars(array( + 'TITLE' => $title )); - $sidebar = $p->run(); - if(isset($tplvars['sidebar_bottom'])) + // build content + $content = ''; + foreach ( $links as $link_text => $url ) + { + $b->assign_vars(array( + 'HREF' => htmlspecialchars($url), + 'FLAGS' => '', + 'TEXT' => $link_text + )); + $content .= $b->run(); + } + $p->assign_vars(array( + 'CONTENT' => $content + )); + $sidebar_text .= $p->run(); + } + + if ( isset($tplvars['sidebar_bottom']) ) { $text = $this->makeParserText($tplvars['sidebar_bottom']); $bottom = $text->run(); - } else { + } + else + { $bottom = ''; } - $sidebar = $top . $sidebar . $bottom; + $sidebar = $top . $sidebar_text . $bottom; } $this->tpl_strings['SIDEBAR_LEFT'] = $sidebar; @@ -2296,6 +2404,9 @@ $t = str_replace('[[NumQueriesLoc]]', $q_loc, $t); $t = str_replace('[[GenTimeLoc]]', $t_loc, $t); + if ( defined('ENANO_DEBUG') ) + $t = str_replace('', '
' . profiler_make_html() . '
', $t); + echo $t; } else return ''; @@ -2325,10 +2436,14 @@ else return ''; } - function process_template($file) { - - eval($this->compile_template($file)); - return $tpl_code; + function process_template($file) + { + profiler_log("[template_nodb] STARTED eval of file $file"); + $compiled = $this->compile_template($file); + profiler_log("[template_nodb] COMPILED file $file"); + $result = eval($compiled); + profiler_log("[template_nodb] FINISHED eval of file $file"); + return $result; } function extract_vars($file) { @@ -2343,37 +2458,42 @@ } return $tplvars; } - function compile_template($text) { - global $sideinfo; + function compile_template($text) + { $text = file_get_contents(ENANO_ROOT . '/themes/'.$this->theme.'/'.$text); - $text = str_replace('', '', $text); // Remove the AJAX code - we don't need it, and it requires a database connection - $text = '$tpl_code = \''.str_replace('\'', '\\\'', $text).'\'; return $tpl_code;'; - $text = preg_replace('##is', '\'; if($this->tpl_bool[\'\\1\']) { $tpl_code .= \'', $text); - $text = preg_replace('##is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { $tpl_code .= \'', $text); - if(defined('IN_ENANO_INSTALL')) $text = str_replace('', '', $text); - else $text = str_replace('', '', $text); - $text = preg_replace('##is', '', $text); - $text = preg_replace('##is', '\'; if(!$this->tpl_bool[\'\\1\']) { $tpl_code .= \'', $text); - $text = preg_replace('##is', '\'; } else { $tpl_code .= \'', $text); - $text = preg_replace('##is', '\'; } $tpl_code .= \'', $text); - $text = preg_replace('#{([A-z0-9]*)}#is', '\'.$this->tpl_strings[\'\\1\'].\'', $text); - return $text; //('
'.htmlspecialchars($text).'
'); + return $this->compile_template_text_post(template_compiler_core($text)); + } + + function compile_template_text($text) + { + return $this->compile_template_text_post(template_compiler_core($text)); } - function compile_template_text($text) { - global $sideinfo; - $text = str_replace('', '', $text); // Remove the AJAX code - we don't need it, and it requires a database connection - $text = '$tpl_code = \''.str_replace('\'', '\\\'', $text).'\'; return $tpl_code;'; - $text = preg_replace('##is', '\'; if($this->tpl_bool[\'\\1\']) { $tpl_code .= \'', $text); - $text = preg_replace('##is', '\'; if(getConfig(\'plugin_\\1\')==\'1\') { $tpl_code .= \'', $text); - if(defined('IN_ENANO_INSTALL')) $text = str_replace('', '', $text); - else $text = str_replace('', '