true, 'IF_NON_PASSED_OR_FALSE' => true, 'PARAM_INFO' => true, 'IF_NOT_IN_ARRAY' => true, 'IF_IN_ARRAY' => true, 'IMPLODE' => true, 'COUNT' => true, 'IF_ARRAY_EMPTY' => true, 'IF_ARRAY_NON_EMPTY' => true, 'OF' => true, 'INCLUDE' => true, 'LOOP' => true); // Work out what symbols may be compiled out (look at patterns at top of caches3.php if changing this) global $COMPILABLE_SYMBOLS; $_compilable_symbols = array( 'LANG', 'THEME', 'VERSION_NUMBER', 'VERSION', 'SITE_NAME', 'CHARSET', 'ADDON_INSTALLED', 'CONFIG_OPTION', 'VALUE_OPTION', 'COPYRIGHT', 'BASE_URL_NOHTTP', 'CUSTOM_BASE_URL_NOHTTP', 'BRAND_NAME', 'BRAND_BASE_URL', 'CNS', 'VALID_FILE_TYPES', 'COOKIE_PATH', 'COOKIE_DOMAIN', 'SESSION_COOKIE_NAME', 'INLINE_STATS', 'CURRENCY_SYMBOL', 'DOMAIN', 'STAFF_ADDRESS', 'STAFF_ADDRESS_PURE', 'SHOW_DOCS', 'SITE_SCOPE', 'IMG_WIDTH', 'IMG_HEIGHT', 'IMG_INLINE', 'SSW', 'MAILTO', ); global $SITE_INFO; if ((isset($SITE_INFO['no_keep_params'])) && ($SITE_INFO['no_keep_params'] == '1')) { $_compilable_symbols[] = 'KEEP'; $_compilable_symbols[] = 'PAGE_LINK'; $_compilable_symbols[] = 'FIND_SCRIPT'; } if (!addon_installed('ssl')) { $_compilable_symbols[] = 'IMG'; $_compilable_symbols[] = 'BASE_URL'; $_compilable_symbols[] = 'CUSTOM_BASE_URL'; } if ((function_exists('get_option')) && (get_option('detect_javascript') == '0')) { $_compilable_symbols[] = 'JS_ON'; } $COMPILABLE_SYMBOLS = array(); foreach ($_compilable_symbols as $s) { $COMPILABLE_SYMBOLS['"' . $s . '"'] = true; } } /** * Helper function or use getting line numbers. * * @param array $bits Compiler tokens * @param integer $i How far we are through the token list * @return integer The sum length of tokens passed * * @ignore */ function _length_so_far($bits, $i) { $len = 0; foreach ($bits as $_i => $x) { if ($_i == $i) { break; } $len += strlen($x); } return $len; } /** * Take some Tempcode and pre-process it for Tempcode portions encapsulated within comments (or similar). * This is done so syntax-highlighters don't break, and WYSIWYG-editors don't corrupt the Tempcode. * * @param string $data Input Tempcode * @return string Output Tempcode */ function substitute_comment_encapsulated_tempcode($data) { // HTML comment $data = cms_preg_replace_safe('#).)*)\}\s*-->#', '{${1}}', $data); // HTML attribute $data = preg_replace('#\sx-tempcode(-\w+)?="\{([^"]*)\}"#', '{${2}}', $data); // CSS/JS comment $data = cms_preg_replace_safe('#/\*\s*\{([^a-z0-9])(((?!\*/).)*)\}\s*\*/#', '{${1}${2}}', $data); return $data; } /** * Compile a template into a list of appendable outputs, for the closure-style Tempcode implementation. * * @param string $data The template file contents * @param ID_TEXT $template_name The name of the template * @param ID_TEXT $theme The name of the theme * @param ID_TEXT $lang The language it is for * @param boolean $tolerate_errors Whether to tolerate errors * @return array A pair: array Compiled result structure, array preprocessable bits (special stuff needing attention that is referenced within the template) */ function compile_template($data, $template_name, $theme, $lang, $tolerate_errors = false) { if (strpos($data, '/*{$,Parser hint: pure}*/') !== false) { return array(array('"' . php_addslashes(preg_replace('#\{\$,.*\}#U', '', str_replace('/*{$,Parser hint: pure}*/', '/*no minify*/', $data))) . '"'), array()); } $data = substitute_comment_encapsulated_tempcode($data); $data = preg_replace('#<\?php(.*)\?' . '>#sU', '{+START,PHP}${1}{+END}', $data); global $COMPILABLE_SYMBOLS, $STUCK_ABORT_SIGNAL; $sas_bak = $STUCK_ABORT_SIGNAL; require_code('lang'); require_code('urls'); $cl = fallback_lang(); $bits = array_values(preg_split('#(?" brackets, whitespace characters, and control codes, and others are used for Tempcode grammar or are valid identifier characters. // Actually +/$/! can be used at the end (+ is already taken) } } $_opener_params = ''; foreach ($opener_params as $oi => &$oparam) { if ($oparam === array()) { $oparam = array('""'); if (!isset($opener_params[$oi + 1])) { unset($opener_params[$oi]); break; } } if ($_opener_params !== '') { $_opener_params .= ','; } $_opener_params .= implode('.', $oparam); } $first_param = preg_replace('#[`%*=;\#\-~\^|\'!&./@+]+(")?$#', '$1', $_first_param); switch ($past_level_mode) { case PARSE_SYMBOL: if (!$no_preprocess) { switch ($first_param) { // These need preprocessing case '""': array_splice($preprocessable_bits, $num_preprocessable_bits); // Remove anything preprocessable marked inside the comment break; case '"REQUIRE_CSS"': case '"REQUIRE_JAVASCRIPT"': case '"JS_TEMPCODE"': case '"CSS_TEMPCODE"': case '"SET"': case '"BLOCK"': case '"LOAD_PAGE"': case '"LOAD_PANEL"': case '"INSERT_SPAMMER_BLACKHOLE"': foreach ($stack as $level_test) { // Make sure if it's a LOOP then we evaluate the parameters early, as these have extra bindings we don't know about if (($level_test[3] === PARSE_DIRECTIVE) && (isset($level_test[5][1])) && (isset($level_test[5][1][0])) && ($level_test[5][1][0] === '"LOOP"')) { // For a loop, we need to do full evaluation of symbol parameters as it may be bound to a loop variable $tpl_funcs = array(); $eval = debug_eval('return array(' . $_opener_params . ');', $tpl_funcs, array(), $cl); if (is_array($eval)) { $pp_bit = array(array(), TC_SYMBOL, str_replace('"', '', $first_param), $eval); $preprocessable_bits[] = $pp_bit; } break 2; } } $symbol_params = array(); foreach ($opener_params as $param) { $myfunc = 'tcpfunc_' . fast_uniqid(); $funcdef = build_closure_function($myfunc, $param); $symbol_params[] = new Tempcode(array(array($myfunc => $funcdef), array(array(array($myfunc, array(/* Is currently unbound */), TC_KNOWN, '', ''))))); // Parameters will be bound in later. } $pp_bit = array(array(), TC_SYMBOL, str_replace('"', '', $first_param), $symbol_params); $preprocessable_bits[] = $pp_bit; break; } } if ((($first_param === '"IMG"') || ($first_param === '"IMG_INLINE"')) && (strpos($_opener_params, ',') === false)) { // Needed to ensure correct binding $_opener_params .= ',"0","' . php_addslashes($theme) . '"'; } if ($first_param === '"?"') { // Optimise out static ternary if (implode('.', $opener_params[0]) === '"1"') { if (isset($opener_params[1])) { $current_level_data[] = implode('.', $opener_params[1]); } break; } if (implode('.', $opener_params[0]) === '"0"') { if (isset($opener_params[2])) { $current_level_data[] = implode('.', $opener_params[2]); } break; } } if ($first_param === '""') { // Optimise out comments break; } // Optimise simple PHP-compatible operators foreach (array('EQ' => '==', 'NEQ' => '!=') as $symbol_op => $php_op) { if (($first_param === '"' . $symbol_op . '"') && (count($opener_params) === 2)) { $current_level_data[] = '(((' . implode('.', $opener_params[0]) . ')' . $php_op . '(' . implode('.', $opener_params[1]) . '))?"1":"0")'; break 2; } } foreach (array('AND' => '&&', 'OR' => '||') as $symbol_op => $php_op) { if (($first_param === '"' . $symbol_op . '"') && (count($opener_params) === 2)) { $current_level_data[] = '(((' . implode('.', $opener_params[0]) . ')=="1")' . $php_op . '(' . implode('.', $opener_params[1]) . '=="1")?"1":"0")'; break 2; } } if (($first_param === '"?"') && (count($opener_params) === 3) && (count($escaped) === 0)) { $current_level_data[] = '(((' . implode('.', $opener_params[0]) . ')=="1")?(' . implode('.', $opener_params[1]) . '):(' . implode('.', $opener_params[2]) . '))'; break 2; } // Okay, a fully dynamic symbol $name = preg_replace('#(^")|("$)#', '', $first_param); if ($name === '?') { $name = 'TERNARY'; } if (function_exists('ecv_' . $name)) { $new_line = 'ecv_' . $name . '($cl,array(' . implode(',', $escaped) . '),array(' . $_opener_params . '))'; } else { $new_line = 'ecv($cl,array(' . implode(',', $escaped) . '),' . strval(TC_SYMBOL) . ',' . $first_param . ',array(' . $_opener_params . '))'; } if ((isset($COMPILABLE_SYMBOLS[$first_param])) && (preg_match('#^[^\(\)]*$#', $_opener_params) !== 0)) { // Can optimise out? $tpl_funcs = array(); $new_line = '"' . php_addslashes(debug_eval('return ' . $new_line . ';', $tpl_funcs, array(), $cl)) . '"'; } else { // We want the benefit's of keep_ variables but not with having to do lots of individual URL moniker lookup queries - so use a static URL and KEEP_ symbol combination if (($GLOBALS['OUTPUT_STREAMING']) && ($first_param === '"PAGE_LINK"') && (count($opener_params) === 1) && (preg_match('#^[^\(\)]*$#', $_opener_params) !== 0) && (function_exists('has_submit_permission')/*needed for moniker hooks to load up*/)) { $tmp = $_GET; foreach (array_keys($_GET) as $key) { if (substr($key, 0, 5) === 'keep_') { unset($_GET[$key]); } } $tpl_funcs = array(); $new_line = '"' . php_addslashes(debug_eval('return ' . $new_line . ';', $tpl_funcs, array(), $cl)) . '"'; $_GET = $tmp; $current_level_data[] = $new_line; $current_level_data[] = 'ecv_KEEP($cl,array(' . implode(',', $escaped) . '),array("' . ((strpos($new_line, '?') === false) ? '1' : '0') . '"))'; $GLOBALS['HAS_KEEP_IN_URL_CACHE'] = null; // The temporary $_GET change can cause this to go wrong break; } } $current_level_data[] = $new_line; break; case PARSE_LANGUAGE_REFERENCE: $new_line = 'ecv($cl,array(' . implode(',', $escaped) . '),' . strval(TC_LANGUAGE_REFERENCE) . ',' . $first_param . ',array(' . $_opener_params . '))'; if (($_opener_params === '') && ($escaped === array())) { // Optimise it out for simple case? $tpl_funcs = array(); $looked_up = do_lang(debug_eval('return ' . $first_param . ';', $tpl_funcs, array(), $cl), null, null, null, $lang, false); if ($looked_up !== null) { if (apply_tempcode_escaping($escaped, $_) === $looked_up) { $new_line = '"' . php_addslashes($looked_up) . '"'; } } } $current_level_data[] = $new_line; break; case PARSE_PARAMETER: $parameter = str_replace('"', '', str_replace("'", '', $first_param)); $parameter = preg_replace('#[^\w]#', '', $parameter); // security to stop PHP injection if ($escaped === array(PURE_STRING)) { $current_level_data[] = '$bound_' . php_addslashes($parameter); } else { $temp = 'otp(isset($bound_' . php_addslashes($parameter) . ')?$bound_' . php_addslashes($parameter) . ':null'; if ((!function_exists('get_value')) || (get_value('shortened_tempcode') !== '1')) { $temp .= ',"' . php_addslashes($template_name . ':' . $parameter) . '"'; } $temp .= ')'; if ($escaped === array()) { $current_level_data[] = $temp; } else { $s_escaped = ''; foreach ($escaped as $esc) { if ($s_escaped !== '') { $s_escaped .= ','; } $s_escaped .= strval($esc); } if (($s_escaped === strval(ENTITY_ESCAPED)) && (!$GLOBALS['XSS_DETECT'])) { $current_level_data[] = '(empty($bound_' . $parameter . '->pure_lang)?@htmlspecialchars(' . $temp . ',ENT_QUOTES | ENT_SUBSTITUTE,get_charset()):' . $temp . ')'; } else { if ($s_escaped === strval(ENTITY_ESCAPED)) { $current_level_data[] = '(empty($bound_' . $parameter . '->pure_lang)?apply_tempcode_escaping_inline(array(' . $s_escaped . '),' . $temp . '):' . $temp . ')'; } else { $current_level_data[] = 'apply_tempcode_escaping_inline(array(' . $s_escaped . '),' . $temp . ')'; } } } } break; } // Handle directive nesting if ($past_level_mode === PARSE_DIRECTIVE) { if ($first_param === '"START"') { // START // Open a new directive level $stack[] = array($current_level_mode, $current_level_data, $current_level_params, $past_level_mode, $past_level_data, $past_level_params, count($preprocessable_bits), $first_param, $escaped, $no_preprocess); $current_level_data = array(); $current_level_params = array(); $current_level_mode = PARSE_DIRECTIVE_INNER; if ($opener_params === array(array('"NO_PREPROCESSING"'))) { array_push($preprocessable_bits_stack, $preprocessable_bits); // So anything inside will end up being thrown away when we pop back to what we had before in $preprocessable_bits } } elseif ($first_param === '"END"') { // END // Test that the top stack does represent a started directive, and close directive level $past_level_data = $current_level_data; if ($past_level_data === array()) { $past_level_data = array('""'); } $past_level_params = $current_level_params; $past_level_mode = $current_level_mode; if ($stack === array()) { if ($tolerate_errors) { continue 2; } warn_exit(do_lang_tempcode('TEMPCODE_TOO_MANY_CLOSES', escape_html($template_name), escape_html(integer_format(1 + substr_count(substr($data, 0, _length_so_far($bits, $i)), "\n"))))); } list($current_level_mode, $current_level_data, $current_level_params, $directive_level_mode, $directive_level_data, $directive_level_params, $num_preprocessable_bits, $opening_tag, $escaped, $no_preprocess) = array_pop($stack); if (!is_array($directive_level_params)) { if ($tolerate_errors) { continue 2; } warn_exit(do_lang_tempcode('UNCLOSED_DIRECTIVE_OR_BRACE', escape_html($template_name), escape_html(integer_format(1 + substr_count(substr($data, 0, _length_so_far($bits, $i)), "\n"))))); } $directive_opener_params = array_merge($directive_level_params, array($directive_level_data)); if (($directive_level_mode !== PARSE_DIRECTIVE) || ($opening_tag !== '"START"')) { if ($tolerate_errors) { continue 2; } warn_exit(do_lang_tempcode('TEMPCODE_TOO_MANY_CLOSES', escape_html($template_name), escape_html(integer_format(1 + substr_count(substr($data, 0, _length_so_far($bits, $i)), "\n"))))); } // Handle directive if (count($directive_opener_params) === 1) { if ($tolerate_errors) { continue 2; } warn_exit(do_lang_tempcode('NO_DIRECTIVE_TYPE', escape_html($template_name), escape_html(integer_format(1 + substr_count(substr($data, 0, _length_so_far($bits, $i)), "\n"))))); } $directive_params = ''; $first_directive_param = '""'; if ($directive_opener_params[1] === array()) { $directive_opener_params[1] = array('""'); } $count_directive_opener_params = count($directive_opener_params); for ($j = 2; $j < $count_directive_opener_params; $j++) { if ($directive_opener_params[$j] === array()) { $directive_opener_params[$j] = array('""'); } if ($directive_params !== '') { $directive_params .= ','; } $directive_params .= implode('.', $directive_opener_params[$j]); if ($j === 2) { $first_directive_param = implode('.', $directive_opener_params[$j]); } } $tpl_funcs = array(); $eval = trim(debug_eval('return ' . implode('.', $directive_opener_params[1]) . ';', $tpl_funcs, array(), $cl)); if (!is_string($eval)) { $eval = ''; } $directive_name = $eval; switch ($directive_name) { case 'INCLUDE': case 'FRACTIONAL_EDITABLE': $tpl_funcs = array(); $eval = debug_eval('return array(' . $directive_params . ');', $tpl_funcs, array(), $cl); if (is_array($eval)) { $pp_bit = array(array(), TC_DIRECTIVE, str_replace('"', '', $directive_name), $eval); $preprocessable_bits[] = $pp_bit; } break; } switch ($directive_name) { case 'COMMENT': break; case 'NO_PREPROCESSING': $current_level_data[] = implode('.', $past_level_data); $preprocessable_bits = array_pop($preprocessable_bits_stack); $num_preprocessable_bits = count($preprocessable_bits); break; case 'IF': // Optimise out static NOT expressions if (preg_match('#^ecv\(\$cl,array\(\),0,"NOT",array\("1"\)\)$#', $first_directive_param) !== 0) { $first_directive_param = '"0"'; } if (preg_match('#^ecv\(\$cl,array\(\),0,"NOT",array\("0"\)\)$#', $first_directive_param) !== 0) { $first_directive_param = '"1"'; } // Optimise out static boolean expressions if (($first_directive_param === '((("1")==("1"))?"1":"0")') || ($first_directive_param === '(("0"=="0")?"1":"0")')) { $first_directive_param = '"1"'; } elseif (($first_directive_param === '((("1")==("0"))?"1":"0")') || ($first_directive_param === '(("0"=="1")?"1":"0")')) { $first_directive_param = '"0"'; } // Optimise simple expressions to PHP $matches = array(); if (preg_match('#^\((\(\([^()]+\)(==|!=)\([^()]+\))\)\?"1":"0"\)\)$#', $first_directive_param, $matches) !== 0) { $current_level_data[] = '(' . $matches[1] . '?(' . implode('.', $past_level_data) . '):\'\')'; break; } // Optimise out static IFs if ($first_directive_param === '"0"') { break; } if ($first_directive_param === '"1"') { $current_level_data[] = '(' . implode('.', $past_level_data) . ')'; break; } // Normal IF then (actually it's implemented as ternary un PHP) $current_level_data[] = '((' . $first_directive_param . '=="1")?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_EMPTY': $current_level_data[] = '((' . $first_directive_param . '==\'\')?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_NON_EMPTY': $current_level_data[] = '((' . $first_directive_param . '!=\'\')?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_PASSED': $tpl_funcs = array(); $eval = debug_eval('return ' . $first_directive_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } $current_level_data[] = '(isset($bound_' . preg_replace('#[^\w]#', '', $eval) . ')?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_NON_PASSED': $tpl_funcs = array(); $eval = debug_eval('return ' . $first_directive_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } $current_level_data[] = '(!isset($bound_' . preg_replace('#[^\w]#', '', $eval) . ')?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_PASSED_AND_TRUE': $tpl_funcs = array(); $eval = debug_eval('return ' . $first_directive_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } $current_level_data[] = '((isset($bound_' . preg_replace('#[^\w]#', '', $eval) . ') && (otp($bound_' . preg_replace('#[^\w]#', '', $eval) . ')=="1"))?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'IF_NON_PASSED_OR_FALSE': $tpl_funcs = array(); $eval = debug_eval('return ' . $first_directive_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } $current_level_data[] = '((!isset($bound_' . preg_replace('#[^\w]#', '', $eval) . ') || (otp($bound_' . preg_replace('#[^\w]#', '', $eval) . ')!="1"))?(' . implode('.', $past_level_data) . '):\'\')'; break; case 'WHILE': $current_level_data[] = 'closure_while_loop(array($parameters,$cl),' . "\n" . 'recall_named_function(\'' . uniqid('', true) . '\',\'$parameters,$cl\',"extract(\$parameters,EXTR_PREFIX_ALL,\'bound\'); return (' . php_addslashes($first_directive_param) . ')==\"1\";"),' . "\n" . 'recall_named_function(\'' . uniqid('', true) . '\',\'$parameters,$cl\',"extract(\$parameters,EXTR_PREFIX_ALL,\'bound\'); return ' . php_addslashes(implode('.', $past_level_data)) . ';"))'; break; case 'LOOP': $current_level_data[] = 'closure_loop(array(' . $directive_params . ',\'vars\'=>$parameters),array($parameters,$cl),' . "\n" . 'recall_named_function(\'' . uniqid('', true) . '\',\'$parameters,$cl\',"extract(\$parameters,EXTR_PREFIX_ALL,\'bound\'); return ' . php_addslashes(implode('.', $past_level_data)) . ';"))'; break; case 'PHP': $current_level_data[] = 'closure_eval(' . implode('.', $past_level_data) . ',$parameters)'; break; case 'INCLUDE': global $FILE_ARRAY; $tpl_funcs = array(); $eval = debug_eval('return ' . $first_directive_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } if (((!$no_preprocess) && ($template_name === $eval)) || ((!$no_preprocess) && (get_param_string('special_page_type', '') === '')) && ($count_directive_opener_params === 3) && ($past_level_data === array('""')) && (!isset($FILE_ARRAY))) { // Simple case $found = find_template_place($eval, '', $theme, '.tpl', 'templates', $template_name === $eval); if (($found !== null) && ($found[1] !== null)) { $_theme = $found[0]; $full_path = get_custom_file_base() . '/themes/' . $_theme . $found[1] . $eval . $found[2]; if (!is_file($full_path)) { $full_path = get_file_base() . '/themes/' . $_theme . $found[1] . $eval . $found[2]; } if (is_file($full_path)) { $filecontents = cms_file_get_contents_safe($full_path); } else { $filecontents = ''; } list($_current_level_data, $_preprocessable_bits) = compile_template($filecontents, $eval, $theme, $lang); $current_level_data = array_merge($current_level_data, $_current_level_data); $preprocessable_bits = array_merge($preprocessable_bits, $_preprocessable_bits); break; } } // intentionally rolls on... default: if ($directive_params !== '') { $directive_params .= ','; } $directive_params .= implode('.', $past_level_data); if (isset($GLOBALS['DIRECTIVES_NEEDING_VARS'][$directive_name])) { $current_level_data[] = 'ecv($cl,array(),' . strval(TC_DIRECTIVE) . ',' . implode('.', $directive_opener_params[1]) . ',array(' . $directive_params . ',\'vars\'=>$parameters))'; } else { $current_level_data[] = 'ecv($cl,array(),' . strval(TC_DIRECTIVE) . ',' . implode('.', $directive_opener_params[1]) . ',array(' . $directive_params . '))'; } break; } } else { $tpl_funcs = array(); $eval = debug_eval('return ' . $first_param . ';', $tpl_funcs, array(), $cl); if (!is_string($eval)) { $eval = ''; } $directive_name = $eval; if (isset($GLOBALS['DIRECTIVES_NEEDING_VARS'][$directive_name])) { $current_level_data[] = 'ecv($cl,array(' . implode(',', $escaped) . '),' . strval(TC_DIRECTIVE) . ',' . $first_param . ',array(' . $_opener_params . ',\'vars\'=>$parameters))'; } else { $current_level_data[] = 'ecv($cl,array(' . implode(',', $escaped) . '),' . strval(TC_DIRECTIVE) . ',' . $first_param . ',array(' . $_opener_params . '))'; } } } break; case ',': // NB: Escaping via "\," was handled in our regexp split switch ($current_level_mode) { case PARSE_NO_MANS_LAND: case PARSE_DIRECTIVE_INNER: $current_level_data[] = '","'; break; default: $current_level_params[] = $current_level_data; $current_level_data = array(); break; } break; default: $literal = php_addslashes(str_replace(array('\,', '\}', '\{'), array(',', '}', '{'), $next_token)); if ($GLOBALS['XSS_DETECT']) { ocp_mark_as_escaped($literal); } $current_level_data[] = '"' . $literal . '"'; break; } } if ((!array_key_exists('LAX_COMCODE', $GLOBALS)) || ($GLOBALS['LAX_COMCODE'] === false)) { if ($stack !== array()) { if (!$tolerate_errors) { warn_exit(do_lang_tempcode('UNCLOSED_DIRECTIVE_OR_BRACE', escape_html($template_name), escape_html(integer_format(1 + substr_count(substr($data, 0, _length_so_far($bits, $i)), "\n"))))); } } } if ($current_level_data === array('')) { $current_level_data = array('""'); } // Some optimisations $merged = array(); $just_done_string = false; foreach ($current_level_data as $c) { // Try and replace some unnecessary string appending which may have happened when experiencing possible (but not) control characters $c = preg_replace('#([^\\\\(])' . preg_quote('"."', '#') . '([^)])#', '$1$2', $c); // Try and merge some strings that don't need to be in separate seq_parts $c_stripped_down = str_replace(array('\\\\', '\\"'), array('', ''), $c); // Remove literal slashes and literal quotes so we can do an accurate scan to ensure it is all one string if ($c_stripped_down[0] === '"' && strpos($c_stripped_down, '"', 1) === strlen($c_stripped_down) - 1) { if ($just_done_string) { $pi = count($merged) - 1; $merged[$pi] = substr($merged[$pi], 0, strlen($merged[$pi]) - 1) . substr($c, 1, strlen($c) - 1); } else { $merged[] = $c; } $just_done_string = true; } else { $just_done_string = false; $merged[] = $c; } } $current_level_data = $merged; $STUCK_ABORT_SIGNAL = $sas_bak; return array($current_level_data, $preprocessable_bits); } /** * A template has not been structurally cached, so compile it and store in the cache. * * @param ID_TEXT $theme The theme the template is in the context of * @param PATH $path The path to the template file * @param ID_TEXT $codename The codename of the template (e.g. foo) * @param ID_TEXT $_codename The actual codename to use for the template (e.g. foo_mobile) * @param LANGUAGE_NAME $lang The language the template is in the context of * @param string $suffix File type suffix of template file (e.g. .tpl) * @param ?ID_TEXT $theme_orig The theme to cache in (null: main theme) * @return Tempcode The compiled Tempcode * * @ignore */ function _do_template($theme, $path, $codename, $_codename, $lang, $suffix, $theme_orig = null) { if (is_null($theme_orig)) { $theme_orig = $theme; } $base_dir = get_custom_file_base() . '/themes/'; if (!is_file($base_dir . $theme . $path . $codename . $suffix)) { $base_dir = get_file_base() . '/themes/'; } global $CACHE_TEMPLATES, $FILE_ARRAY, $IS_TEMPLATE_PREVIEW_OP_CACHE, $SITE_INFO; if ($IS_TEMPLATE_PREVIEW_OP_CACHE === null) { fill_template_preview_op_cache(); } //$final_css_path = null; if (isset($FILE_ARRAY)) { $template_contents = unixify_line_format(file_array_get('themes/' . $theme . $path . $codename . $suffix)); } else { $_path = $base_dir . filter_naughty($theme . $path . $codename) . $suffix; $template_contents = unixify_line_format(cms_file_get_contents_safe($_path)); } // LESS support if ((addon_installed('less')) && ($suffix == '.less')) { // Up our resources if (php_function_allowed('set_time_limit')) { @set_time_limit(300); } disable_php_memory_limit(); // Stop parallel compilation of the same file by a little hack; without this it could knock out a server /* $final_css_path = get_custom_file_base() . '/themes/' . $theme . '/templates_cached/' . $lang . '/' . $codename . '.css'; Actually this is architectually messy, just let it happen - it's not as slow as it was if ((is_file($final_css_path)) && (file_get_contents($final_css_path) == 'GENERATING')) { header('Content-type: text/plain; charset=' . get_charset()); exit('We are doing a code update. Please refresh in around 2 minutes.'); } require_code('files'); cms_file_put_contents_safe($final_css_path, 'GENERATING', FILE_WRITE_FIX_PERMISSIONS); */ if (!empty($SITE_INFO['nodejs_binary_path'])) { $less_path = get_custom_file_base() . '/node_modules/less/bin/lessc'; if (!file_exists($less_path)) { fatal_exit('Unable to find the less NPM module. Please `cd` to your Composr directory and run `npm install less` to install it.'); } $cmd = sprintf('%s %s --no-color %s', $SITE_INFO['nodejs_binary_path'], escapeshellarg($less_path), escapeshellarg($_path)); $descriptorspec = array( 0 => array('pipe', 'r'), // stdin 1 => array('pipe', 'w'), // stdout 2 => array('pipe', 'w'), // stderr ); $pipes = array(); $process = proc_open($cmd, $descriptorspec, $pipes); if ($process === false) { fatal_exit('Unable to execute the Node.js binary, please make sure it exists and proper permissions are set.'); } $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); $return_code = proc_close($process); if ($return_code !== 0) { fatal_exit('.less problem: ' . $stderr); } $template_contents = $stdout; } else { // Heavy-weight, newer (iLess) require_code('ILess/Autoloader'); ILess_Autoloader::register(); $less = new ILess_Parser(array( 'import_dirs' => array(dirname($_path)), ), new ILess_Cache_FileSystem(array( 'cache_dir' => get_custom_file_base() . '/themes/' . $theme . '/templates_cached/' . $lang, ))); try { $less->parseString($template_contents); $template_contents = $less->getCSS(); } catch (Exception $ex) { fatal_exit('.less problem: ' . $ex->getMessage()); } } } if (($GLOBALS['SEMI_DEV_MODE']) && ($suffix === '.js') && (strpos($template_contents, '.innerHTML') !== false) && (!running_script('install')) && (strpos($template_contents, 'Parser hint: .innerHTML okay') === false)) { attach_message($codename . ': Do not use the .innerHTML property in your JavaScript because it will not work in true XHTML (when the browsers real XML parser is in action). Use Composr\'s global set_inner_html/get_inner_html functions.', 'warn'); } // Strip off trailing final lines from single lines templates. Editors often put these in, and it causes annoying "visible space" issues if ((substr($template_contents, -1, 1) === "\n") && (substr_count($template_contents, "\n") === 1)) { $template_contents = substr($template_contents, 0, strlen($template_contents) - 1); } if ($IS_TEMPLATE_PREVIEW_OP_CACHE) { $test = post_param_string($codename, null); if (!is_null($test)) { $template_contents = post_param_string($test . '_new'); } } cms_profile_start_for('_do_template'); $result = template_to_tempcode($template_contents, 0, false, $codename, $theme_orig, $lang); cms_profile_end_for('_do_template', $codename . $suffix); if (($CACHE_TEMPLATES) && (!$IS_TEMPLATE_PREVIEW_OP_CACHE)) { $path2 = get_custom_file_base() . '/themes/' . $theme_orig . '/templates_cached/' . filter_naughty($lang); $_path2 = $path2 . '/' . filter_naughty($_codename) . $suffix . '.tcp'; require_code('files'); $data_to_write = '<' . '?php' . "\n" . $result->to_assembly($lang) . "\n"; cms_file_put_contents_safe($_path2, $data_to_write, FILE_WRITE_FAILURE_SOFT | FILE_WRITE_FIX_PERMISSIONS); } return $result; } /** * Convert template text into Tempcode format. * * @param string $text The template text * @param integer $symbol_pos The position we are looking at in the text * @param boolean $inside_directive Whether this text is in fact a directive, about to be put in the context of a wider template * @param ID_TEXT $codename The codename of the template (e.g. foo) * @param ?ID_TEXT $theme The theme it is for (null: current theme) * @param ?ID_TEXT $lang The language it is for (null: current language) * @param boolean $tolerate_errors Whether to tolerate errors * @return mixed The converted/compiled template as Tempcode, OR if a directive, encoded directive information */ function template_to_tempcode($text, $symbol_pos = 0, $inside_directive = false, $codename = '', $theme = null, $lang = null, $tolerate_errors = false) { if (is_null($theme)) { $theme = isset($GLOBALS['FORUM_DRIVER']) ? $GLOBALS['FORUM_DRIVER']->get_theme() : 'default'; } if (is_null($lang)) { $lang = user_lang(); } list($parts, $preprocessable_bits) = compile_template(substr($text, $symbol_pos), $codename, $theme, $lang, $tolerate_errors); if (count($parts) === 0) { return new Tempcode(); } $output_streaming = (function_exists('get_option')) && (get_option('output_streaming') === '1'); $parts_groups = array(); $parts_group = array(); foreach ($parts as $part) { if (($output_streaming) && (strpos($part, '$bound_') !== false)) { // Start a new seq_part, so output streaming can break at this parameter reference if ($parts_group !== array()) { $parts_groups[] = $parts_group; } $parts_group = array(); } $parts_group[] = $part; } if ($parts_group !== array()) { $parts_groups[] = $parts_group; } $funcdefs = array(); $seq_parts = array(); foreach ($parts_groups as $parts_group) { $myfunc = 'tcpfunc_' . fast_uniqid() . '_' . strval(count($seq_parts) + 1); $funcdef = build_closure_function($myfunc, $parts_group); $funcdefs[$myfunc] = $funcdef; $seq_parts[] = array(array($myfunc, array(/* Is currently unbound */), TC_KNOWN, '', '')); } $ret = new Tempcode(array($funcdefs, $seq_parts)); // Parameters will be bound in later. $ret->preprocessable_bits = array_merge($ret->preprocessable_bits, $preprocessable_bits); $ret->codename = $codename; return $ret; } /** * Build a closure function for a compiled template. * * @param string $myfunc The function name * @param array $parts An array of lines to be output, each one in PHP format * @return string Finished PHP code */ function build_closure_function($myfunc, $parts) { if ($parts === array()) { $parts = array('""'); } $code = ''; foreach ($parts as $part) { if ($code != '') { $code .= ",\n\t"; } $code .= $part; } if (strpos($code, '$bound') === false) { $funcdef = "\$tpl_funcs['$myfunc']=\$KEEP_TPL_FUNCS['$myfunc']=recall_named_function('" . uniqid('', true) . "','\$parameters,\$cl',\"echo " . php_addslashes($code) . ";\");"; } else { $funcdef = "\$tpl_funcs['$myfunc']=\$KEEP_TPL_FUNCS['$myfunc']=recall_named_function('" . uniqid('', true) . "','\$parameters,\$cl',\"extract(\\\$parameters,EXTR_PREFIX_ALL,'bound'); echo " . php_addslashes($code) . ";\");"; } // Eval version also works. Easier to debug. Less performant due to re-parse requirement each time it is called if ($GLOBALS['DEV_MODE']) { $unset_code = ''; if (strpos($code, 'isset($bound') !== false) {// Horrible but efficient code needed to allow IF_PASSED/IF_NON_PASSED to keep working when templates are put adjacent to each other, where some have it, and don't. This is needed as eval does not set a scope block. $reset_code = "eval(\\\$FULL_RESET_VAR_CODE);"; } elseif (strpos($code, '$bound') !== false) { $reset_code = "eval(\\\$RESET_VAR_CODE);"; } else { $reset_code = ''; } $funcdef = "\$tpl_funcs['$myfunc']=\"$reset_code echo " . php_addslashes($code) . ";\";"; } return $funcdef; }