<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license		http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright	ocProducts Ltd
 * @package		core
 */

/**
 * Standard code module initialisation function.
 */
function init__tempcode()
{
	if (defined('ENTITY_ESCAPED')) return;
	
	define('ENTITY_ESCAPED',1); // HTML entities
	define('SQ_ESCAPED',2); // Single quotes
	define('DQ_ESCAPED',3); // Double quotes
	define('NL_ESCAPED',4); // New lines disappear
	define('CC_ESCAPED',5); // Comcode
	define('UL_ESCAPED',6); // URL
	define('JSHTML_ESCAPED',7); // Javascript </ -> <\/
	define('NL2_ESCAPED',8); // New lines go to \n
	define('ID_ESCAPED',9); // Strings to to usable IDs
	define('NAUGHTY_ESCAPED',10); // Used as a Javascript variable name, for example... to prevent code injection
	define('NULL_ESCAPED',11); // This is useful to mark something that takes strings but does not need escaping (usually because it is escaped further down the line)
	define('FORCIBLY_ENTITY_ESCAPED',12); // To force a language string to be escaped
	define('CSS_ESCAPED',13); // To stop CSS injection
	define('UL2_ESCAPED',14); // rawurlencode

	define('TC_SYMBOL',0);
	define('TC_KNOWN',1); // Either tempcode or string
	define('TC_LANGUAGE_REFERENCE',2);
	define('TC_PARAMETER',3); // A late parameter for a compiled template
	define('TC_DIRECTIVE',4);
	
	global $XHTML_SPIT_OUT,$NO_EVAL_CACHE,$MEMORY_OVER_SPEED,$CACHED_FOUND,$REQUEST_BLOCK_NEST_LEVEL,$LOADED_TPL_CACHE;
	$XHTML_SPIT_OUT=NULL;
	$NO_EVAL_CACHE=false;
	$MEMORY_OVER_SPEED=false;
	$CACHED_FOUND=array();
	$REQUEST_BLOCK_NEST_LEVEL=0;
	$LOADED_TPL_CACHE=array();

	global $RECORD_TEMPLATES_USED,$RECORDED_TEMPLATES_USED,$RECORD_TEMPLATES_TREE,$POSSIBLY_IN_SAFE_MODE,$SCREEN_TEMPLATE_CALLED,$TITLE_CALLED;
	$SCREEN_TEMPLATE_CALLED=false;
	$RECORD_TEMPLATES_USED=false;
	$RECORDED_TEMPLATES_USED=array();
	$RECORD_TEMPLATES_TREE=false;
	$SCREEN_TEMPLATE_CALLED=NULL;
	$TITLE_CALLED=false;
	$POSSIBLY_IN_SAFE_MODE=(get_param_integer('keep_safe_mode',0)==1);

	global $SIMPLE_ESCAPED,$XSS_DETECT;
	$SIMPLE_ESCAPED=array(ENTITY_ESCAPED);
	if ($XSS_DETECT) $SIMPLE_ESCAPED=array(12345); // Don't allow $SIMPLE_ESCAPED to work, as we need to work through full manual escaping

	require_code('symbols');
	
	global $FULL_RESET_VAR_CODE,$RESET_VAR_CODE;
	$FULL_RESET_VAR_CODE='foreach(array_keys(get_defined_vars()) as $x) { if ($x[0]==\'b\' && substr($x,0,6)==\'bound_\') unset($$x); } extract($parameters,EXTR_PREFIX_ALL,\'bound\');';
	$RESET_VAR_CODE='extract($parameters,EXTR_PREFIX_ALL,\'bound\');';

	global $TEMPLATE_PREVIEW_OP;
	$TEMPLATE_PREVIEW_OP=array_key_exists('template_preview_op',$_POST) && ($_POST['template_preview_op']=='1') && ((get_page_name()!='admin_themes') || (get_param('type','')=='view'));
	
	global $OB_GET_CLEAN;
	$OB_GET_CLEAN=function_exists('ob_get_clean');
}

/**
 * Simple function to evaluate some Tempcode. Very rarely to be used, only if you can't call a method (e.g. you are copying direct into an array, such as in block cacheing).
 *
 * @param  tempcode		Tempcode object
 * @return string			Evaluated string
 */
function static_evaluate_tempcode($ob)
{
	return $ob->evaluate();
}

/**
 * Escape a string to fit within PHP double quotes TWICE. Needed sometimes when generating code. This function exists for performance reasons.
 *
 * @param  string			String in
 * @return string			Resultant string
 */
function php_addslashes_twice($in)
{
	return php_addslashes(php_addslashes($in));
	
	// This code does not work, provides awfully confusing Tempcode errors...
	
	/*global $PHP_REP_FROM,$PHP_REP_TO_TWICE;
	return str_replace($PHP_REP_FROM,$PHP_REP_TO_TWICE,$in);
	//return str_replace("\n",'\n',str_replace('$','\$',str_replace('\\\'','\'',addslashes($in))));*/
}

/**
 * The PHP uniqid function is slow on some PHP versions. This version is much faster.
 *
 * @return string				Unique Identifier
 */
function fast_uniqid()
{
	return uniqid('',true);
}

/**
 * Get a string (natural for Tempcode's stream-based processing-model) representation of a bound Tempcode variable
 *
 * @param  mixed				Variable (or NULL if not set)
 * @param  ID_TEXT			The name of the parameter
 * @param  ID_TEXT			The name of the template
 * @return string				Value
 */
function output_tempcode_parameter($var,$parameter,$template_name)
{
	switch (gettype($var))
	{
		case 'NULL':
			return attach_message(do_lang_tempcode('MISSING_TEMPLATE_PARAMETER',$parameter,$template_name),'warn');
		case 'string':
			return $var;
		case 'object':
			return $var->evaluate();
		case 'boolean':
			return $var?'1':'0';
	}
	// Assuming array
	$cnt=count($var);
	return ($cnt==0)?'':strval($cnt);
}

/**
 * Build a conventional tempcode object
 *
 * @param  integer			The type of symbol this is (TC_SYMBOL, TC_LANGUAGE_REFERENCE)
 * @set    0 2
 * @param  ID_TEXT			The name of the symbol
 * @param  ?array				Parameters to the symbol (NULL: none). In same format as expected by ecv.
 * @param  ?array				Escaping for the symbol (NULL: none)
 * @return tempcode			Tempcode object.
 */
function build_closure_tempcode($type,$name,$parameters,$escaping=NULL)
{
	if ($escaping===NULL)
	{
		$_escaping='array()';
	} else
	{
		$_escaping='array('.@implode(',',$escaping).')';
	}
	
	$_type=strval($type);
	if (preg_match('#^[\w\-]*$#',$name)==0)
		$_name=php_addslashes_twice($name);
	else $_name=$name;

	$myfunc='do_runtime_'.uniqid('',true)/*fast_uniqid()*/;
	$funcdef=/*if (!isset(\$TPL_FUNCS['$myfunc']))\n\t*/"\$TPL_FUNCS['$myfunc']=\"echo ecv(\\\$cl,".($_escaping).",".($_type).",\\\"".($_name)."\\\",\\\$parameters);\";\n";

	$ret=new ocp_tempcode(array($funcdef,array(array($myfunc,($parameters===NULL)?array():$parameters,$type,$name,''))));
	if ($type==TC_LANGUAGE_REFERENCE) $ret->pure_lang=true;
	return $ret;
}

/**
 * This will create a new tempcode object that is containing a single specifed symbol
 *
 * @param  ID_TEXT		The ID of the symbol to use
 * @param  ?array			Symbol parameters (NULL: none)
 * @param  ?array			Escaping (NULL: none)
 * @return tempcode		A symbol tempcode object
 */
function symbol_tempcode($symbol,$parameters=NULL,$escape=NULL)
{
	if ($parameters===NULL) $parameters=array();

	return build_closure_tempcode(TC_SYMBOL,$symbol,$parameters,$escape);
}

/**
 * This will create a new tempcode object that is containing a single specifed directive
 *
 * @param  ID_TEXT		The ID of the directive to use
 * @param  tempcode		The contents
 * @param  ?array			Directive parameters (NULL: none)
 * @return tempcode		A directive tempcode object
 */
function directive_tempcode($directive,$content,$parameters=NULL)
{
	if ($parameters===NULL) $parameters=array();
	$parameters[]=$content;

	return build_closure_tempcode(TC_DIRECTIVE,$directive,$parameters);
}

/**
 * Perform a simple loop, that can be inlined in an expression.
 *
 * @param  array			The template bound parameters
 * @param  array			The loop control function
 * @param  array			The loop execution function
 * @return string			Result
 */
function closure_while_loop($args,$control_function,$main_function)
{
	$out='';
	while (call_user_func_array($control_function,$args)) $out.=call_user_func_array($main_function,$args);
	return $out;
}

/**
 * Evaluate some PHP code to put the result into an expression (code is allowed to have side effects).
 *
 * @param  string			The code
 * @param  array			Template parameters
 * @return string			Result
 */
function closure_eval($code,$parameters)
{
	if (get_value('allow_php_in_templates')!=='1')
	{
		return do_lang('NO_PHP_IN_TEMPLATES');
	}
	
	$ret=eval($code);
	if (!is_string($ret)) $ret=@strval($ret);
	return $ret;
}

/**
 * Perform a simple loop, that can be inlined in an expression.
 *
 * @param  array			The template bound parameters
 * @param  array			The loop directive parameters
 * @param  array			The loop execution function
 * @return string			Result
 */
function closure_loop($param,$args,$main_function)
{
	$value='';
	
	if (isset($param[0]))
	{
		$key=$param[0];
		if ((is_numeric($key)) || (strpos($key,',')!==false))
		{
			$array=explode(',',$key);
		} else
		{
			$array=isset($param['vars'][$key])?$param['vars'][$key]:array();
		}
		if (isset($param[1+1])) /* NB: +1 is due to there being a non-numeric index here too */
		{
			$columns=intval($param[1]);
			$row_starter=isset($param[2+1])?$param[2]:'<tr>';
			$row_terminator=isset($param[3+1])?$param[3]:'</tr>';
			if ($array!=array()) $value.=$row_starter;

			// Sorting
			if (isset($param[4+1]))
			{
				$sort_key=$param[4];

				$rev=((isset($param[5+1])) && ($param[5]=='DESC'));
				if ($sort_key!='')
				{
					global $M_SORT_KEY;
					$M_SORT_KEY=$sort_key;
					uasort($array,'multi_sort');
				}
				if ($rev) $array=array_reverse($array);
			}
		}
		$col=0;
		$first=true;
		$max_index=count($array)-1;
		foreach ($array as $go_key=>$go)
		{
			if (!is_array($go)) $go=array('_loop_var'=>$go); else $go['_loop_var']='(array)'; // In case it's not a list of maps, but just a list

			if ((isset($param[2])) && ($col%$columns==0) && ($col!=0))
			{
				$value.=$row_starter;
			}

			$ps=$go+array('_loop_key'=>is_integer($go_key)?strval($go_key):$go_key,'_i'=>strval($col),'_first'=>$first,'_last'=>$col==$max_index);
			$args[0]=$ps+$args[0];
			$args[0]['vars']=$args[0];
			$value.=call_user_func_array($main_function,$args);

			++$col;
			if ((isset($param[3])) && ($col%$columns==0))
			{
				$value.=$row_terminator;
			}
			$first=false;
		}
		if ((isset($param[2])) && ($col%$columns!=0))
		{
			$value.=$row_terminator;
		}
	}

	return $value;
}

/**
 * Convert a string to tempcode.
 *
 * @param  string			String
 * @return tempcode		Tempcode
 */
function make_string_tempcode($string)
{
	$myfunc='string_attach_'.uniqid('',true)/*fast_uniqid()*/;
	$code_to_preexecute="\$TPL_FUNCS['$myfunc']=\"echo \\\"".php_addslashes_twice($string)."\\\";\";\n";
	$seq_parts=array(array($myfunc,array(),TC_KNOWN,'',''));
	return new ocp_tempcode(array($code_to_preexecute,$seq_parts));
}

/**
 * Apply whatever escaping is requested to the given value.
 *
 * @param  array			A list of escaping to do
 * @param  string			The string to apply the escapings to
 * @return string			Output string (you do not need to collect this, as $value is pass-by-reference -- but this is useful for chaining)
 */
function apply_tempcode_escaping($escaped,&$value)
{
	global $HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2;
	foreach ((isset($escaped[1])?array_reverse($escaped):$escaped) as $escape)
	{
		//if ($escape==NL_ESCAPED) echo str_replace(chr(10),'',$value)."\n\n\n\n\n";

		if ($escape==ENTITY_ESCAPED) $value=str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$value);
		elseif ($escape==FORCIBLY_ENTITY_ESCAPED) $value=str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$value);
		elseif ($escape==SQ_ESCAPED) $value=str_replace('&#039;','\&#039;',str_replace('\'','\\\'',str_replace('\\','\\\\',$value)));
		elseif ($escape==DQ_ESCAPED) $value=str_replace('&quot;','\&quot;',str_replace('"','\\"',str_replace('\\','\\\\',$value)));
		elseif ($escape==NL_ESCAPED) $value=str_replace(chr(13),'',str_replace(chr(10),'',$value));
		elseif ($escape==NL2_ESCAPED) $value=str_replace(chr(13),'',str_replace(chr(10),'\n',$value));
		elseif ($escape==CC_ESCAPED) $value=str_replace('[','\\[',str_replace('\\','\\\\',$value));
		elseif ($escape==UL_ESCAPED) $value=ocp_url_encode($value);
		elseif ($escape==UL2_ESCAPED) $value=rawurlencode($value);
		elseif ($escape==JSHTML_ESCAPED) $value=str_replace(']]>',']]\'+\'>',str_replace('</','<\/',$value));
		elseif ($escape==ID_ESCAPED) $value=preg_replace('#^[\_\.\-]#','x',preg_replace('#[^A-Za-z\_\d\.\-]#','_',$value));
		elseif ($escape==CSS_ESCAPED) $value=preg_replace('#[^\w\#\.\-\%]#','_',$value);
		elseif ($escape==NAUGHTY_ESCAPED) $value=filter_naughty_harsh($value,true);
	}
	if (($GLOBALS['XSS_DETECT']) && ($escaped!=array())) ocp_mark_as_escaped($value);

	return $value;
}

/**
 * Apply whatever escaping is requested to the given value.
 *
 * @param  array			A list of escaping to do
 * @param  string			The string to apply the escapings to
 * @return string			Output string
 */
function apply_tempcode_escaping_inline($escaped,$value)
{
	global $HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2;
	foreach ((isset($escaped[1])?array_reverse($escaped):$escaped) as $escape)
	{
		//if ($escape==NL_ESCAPED) echo str_replace(chr(10),'',$value)."\n\n\n\n\n";

		if ($escape==ENTITY_ESCAPED) $value=str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$value);
		elseif ($escape==FORCIBLY_ENTITY_ESCAPED) $value=str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$value);
		elseif ($escape==SQ_ESCAPED) $value=str_replace('&#039;','\&#039;',str_replace('\'','\\\'',str_replace('\\','\\\\',$value)));
		elseif ($escape==DQ_ESCAPED) $value=str_replace('&quot;','\&quot;',str_replace('"','\\"',str_replace('\\','\\\\',$value)));
		elseif ($escape==NL_ESCAPED) $value=str_replace(chr(13),'',str_replace(chr(10),'',$value));
		elseif ($escape==NL2_ESCAPED) $value=str_replace(chr(13),'',str_replace(chr(10),'\n',$value));
		elseif ($escape==CC_ESCAPED) $value=str_replace('[','\\[',str_replace('\\','\\\\',$value));
		elseif ($escape==UL_ESCAPED) $value=ocp_url_encode($value);
		elseif ($escape==UL2_ESCAPED) $value=rawurlencode($value);
		elseif ($escape==JSHTML_ESCAPED) $value=str_replace(']]>',']]\'+\'>',str_replace('</','<\/',$value));
		elseif ($escape==ID_ESCAPED) $value=preg_replace('#^[\_\.\-]#','x',preg_replace('#[^A-Za-z\_\d\.\-]#','_',$value));
		elseif ($escape==CSS_ESCAPED) $value=preg_replace('#[^\w\#\.\-\%]#','_',$value);
		elseif ($escape==NAUGHTY_ESCAPED) $value=filter_naughty_harsh($value,true);
	}
	if (($GLOBALS['XSS_DETECT']) && ($escaped!=array())) ocp_mark_as_escaped($value);

	return $value;
}

/**
 * This will create a new tempcode object that is containing a single specifed language code
 *
 * @param  ID_TEXT		The ID of the language string to use
 * @param  ?mixed			The first token [string or tempcode] (replaces {1}) (NULL: none)
 * @param  ?mixed			The second token [string or tempcode] (replaces {2}) (NULL: none)
 * @param  ?mixed			The third token (replaces {3}). May be an array of [of string], to allow any number of additional args (NULL: none)
 * @return tempcode		A language tempcode object
 */
function do_lang_tempcode($lang_string,$token1=NULL,$token2=NULL,$token3=NULL)
{
	$parameters=array();
	if (isset($token1)) $parameters[]=$token1;
	if (isset($token2)) $parameters[]=$token2;
	if (isset($token3))
	{
		if (!is_array($token3))
		{
			$parameters[]=$token3;
		} else
		{
			$parameters=array_merge($parameters,$token3);
		}
	}

	return build_closure_tempcode(TC_LANGUAGE_REFERENCE,$lang_string,$parameters);
}

/**
 * Get a tempcoded version of a normal XHTML template. It is perhaps the most common ocPortal function to load up templates using do_template, and then attach them together either as parameters to each other, or via the tempcode attach method.
 *
 * @param  ID_TEXT			The codename of the template being loaded
 * @param  ?array				A map of parameters for the template (key to value) (NULL: no parameters)
 * @param  ?LANGUAGE_NAME 	The language to load the template in (templates can embed language references) (NULL: users own language)
 * @param  boolean			Whether to not produce a stack dump if the template is missing
 * @param  ?ID_TEXT			Alternate template to use if the primary one does not exist (NULL: none)
 * @param  string				File type suffix of template file (e.g. .tpl)
 * @param  string				Subdirectory type to look in
 * @set    templates css
 * @param  ?ID_TEXT			Theme to use (NULL: current theme)
 * @return tempcode			The tempcode for this template
 */
function do_template($codename,$parameters=NULL,$lang=NULL,$light_error=false,$fallback=NULL,$suffix='.tpl',$type='templates',$theme=NULL)
{
	if ((!isset($lang)) || ($lang==''))
	{
		global $USER_LANG_CACHED;
		$lang=isset($USER_LANG_CACHED)?$USER_LANG_CACHED:(function_exists('user_lang')?user_lang():'EN');
	}

	if ($GLOBALS['SEMI_DEBUG_MODE'])
	{
		if (($codename==strtolower($codename)) && ($type!='css') && ($codename!='tempcode_test') && ($codename!='handle_conflict_resolution'))
		{
			fatal_exit('Template names should be in upper case, and the files should be stored in upper case.');
		}

		if ((substr($codename,-7)=='_SCREEN') || (substr($codename,-8)=='_OVERLAY') || ($codename=='POOR_XHTML_WRAPPER') || ($codename=='OCF_WRAPPER'))
		{
			$GLOBALS['SCREEN_TEMPLATE_CALLED']=$codename;
		}
	}

	global $TEMPLATE_PREVIEW_OP,$RECORD_TEMPLATES_USED,$RECORDED_TEMPLATES_USED,$FILE_ARRAY,$MEM_CACHE,$KEEP_MARKERS,$SHOW_EDIT_LINKS,$XHTML_SPIT_OUT,$CACHE_TEMPLATES,$FORUM_DRIVER,$POSSIBLY_IN_SAFE_MODE,$CACHED_THEME,$CACHED_FOUND,$LOADED_TPL_CACHE;
	$special_treatment=((($KEEP_MARKERS) || ($SHOW_EDIT_LINKS)) && (is_null($XHTML_SPIT_OUT)));

	if ($RECORD_TEMPLATES_USED)
	{
		$RECORDED_TEMPLATES_USED[]=$codename;
	}

	// Variables we'll need
	if (!isset($theme))
		$theme=isset($CACHED_THEME)?$CACHED_THEME:(((isset($FORUM_DRIVER)) && (is_object($FORUM_DRIVER)) && (method_exists($FORUM_DRIVER,'get_theme')))?filter_naughty($FORUM_DRIVER->get_theme()):'default');
	$prefix_default=get_file_base().'/themes/';
	$prefix=($theme=='default')?$prefix_default:(get_custom_file_base().'/themes/');

	// Is it structurally cached on disk yet?
	if (!isset($CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]))
	{
		$loaded_this_once=false;
	} else
	{
		$loaded_this_once=true;
	}
	$_data=false;
	if (($CACHE_TEMPLATES) && (!$TEMPLATE_PREVIEW_OP) && ((!$POSSIBLY_IN_SAFE_MODE)/* || ($GLOBALS['SEMI_DEBUG_MODE'])*/ || (!in_safe_mode())))
	{
		$tcp_path=$prefix.$theme.'/templates_cached/'.$lang.'/'.$codename.$suffix.'.tcp';
		if ($loaded_this_once)
		{
			if (isset($LOADED_TPL_CACHE[$codename][$theme]))
			{
				$_data=$LOADED_TPL_CACHE[$codename][$theme];
			} else
			{
				$_data=new ocp_tempcode();
				$test=$_data->from_assembly_executed($tcp_path,array($codename,$codename,$lang,$theme,$suffix,$type,$fallback));
				if (!$test) $_data=false; // failed
			}
		} else
		{
			global $SITE_INFO;
			$support_smart_decaching=(!isset($SITE_INFO['disable_smart_decaching'])) || ($SITE_INFO['disable_smart_decaching']=='0');
			if ($support_smart_decaching)
			{
				if (!isset($CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]))
				{
					$found=find_template_place($codename,$lang,$theme,$suffix,$type);
					$CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]=$found;
				} else
				{
					$found=$CACHED_FOUND[$codename][$lang][$theme][$suffix][$type];
				}

				if (isset($GLOBALS['CURRENT_SHARE_USER']))
				{
					$file_path=get_custom_file_base().'/themes/'.$found[0].$found[1].$codename.$suffix;
					if (!is_file($file_path))
						$file_path=get_file_base().'/themes/'.$found[0].$found[1].$codename.$suffix;
				} else
				{
					$file_path=((($theme=='default') && ($suffix!='.css'))?get_file_base():get_custom_file_base()).'/themes/'.$found[0].$found[1].$codename.$suffix;
				}
				$tcp_time=@filemtime($tcp_path);
			}
			if ((!$support_smart_decaching) || (($tcp_time!==false) && (is_file($file_path)))/*if in install can be found yet no file at path due to running from data.ocp*/ && ($found!==NULL))
			{
				if ((!$support_smart_decaching) || (filemtime($file_path)<$tcp_time))
				{
					$_data=new ocp_tempcode();
					$test=$_data->from_assembly_executed($tcp_path,array($codename,$codename,$lang,$theme,$suffix,$type,$fallback));
					if (!$test) $_data=false; // failed
				}
			}
		}
	}
	if ($_data===false) // No, it's not
	{
		if (!isset($CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]))
		{
			$found=find_template_place($codename,$lang,$theme,$suffix,$type);
			$CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]=$found;
		} else
		{
			$found=$CACHED_FOUND[$codename][$lang][$theme][$suffix][$type];
		}

		unset($CACHED_FOUND[$codename][$lang][$theme][$suffix][$type]);
		if (is_null($found))
		{
			if (is_null($fallback))
			{
				if ($light_error) return paragraph(do_lang_tempcode('MISSING_TEMPLATE_FILE',escape_html($codename)),'34rwefwfdee');
				fatal_exit(do_lang_tempcode('MISSING_TEMPLATE_FILE',escape_html($codename)));
			} else
			{
				$result=do_template($fallback,$parameters,$lang);
				return $result;
			}
		} else
		{
			require_code('tempcode_compiler');
			$_data=_do_template($found[0],$found[1],$codename,$codename,$lang,$suffix,$theme);
		}
	}

	if ($loaded_this_once) // On 3rd load (and onwards) it will be fully cached
		$LOADED_TPL_CACHE[$codename][$theme]=$_data;

	if (!isset($parameters)) // Streamlined if no parameters involved
	{
		$out=new ocp_tempcode();
		$out->codename=$codename;
		$out->code_to_preexecute=$_data->code_to_preexecute;
		$out->preprocessable_bits=$_data->preprocessable_bits;
		$out->seq_parts=$_data->seq_parts;

		foreach ($out->seq_parts as $i=>$bit)
		{
			if ($bit[1]!=array())
				$out->seq_parts[$i][1]=array();
		}
		
		return $out;
	}

	$ret=$_data->bind($parameters,$codename);
	if ($special_treatment)
	{
		$ret->codename='(mixed)'; // Stop optimisation that assumes the codename represents the sole content of it
	}

	if ($special_treatment)
	{
		if ($KEEP_MARKERS)
		{
			$__data=new ocp_tempcode();
			$__data->attach('<!-- START-TEMPLATE='.$codename.' -->');
			$__data->attach($ret);
			$__data->attach('<!-- END-TEMPLATE='.$codename.' -->');
			$ret=$__data;
		}
		if (($SHOW_EDIT_LINKS) && ($codename!='PARAM_INFO'))
		{
			$edit_url=build_url(array('page'=>'admin_themes','type'=>'_edit_templates','theme'=>$theme,'f0file'=>$codename),'adminzone');

			$parameters2=array();
			foreach ($parameters as $k=>$v)
			{
				if (is_array($v))
				{
					$parameters2[$k]='(array)';
				} elseif (!is_object($v))
				{
					$parameters2[$k]=$v;
				} else
				{
					$parameters2[$k]=$v->evaluate();
					if (strlen($parameters2[$k])>100) $parameters2[$k]=substr($parameters2[$k],0,100).'...';
				}
			}
			$param_info=do_template('PARAM_INFO',array('MAP'=>$parameters2));

			$SHOW_EDIT_LINKS=false;
			$ret=do_template('TEMPLATE_EDIT_LINK',array('_GUID'=>'511ae911d31a5b237a4371ff22fc78fd','PARAM_INFO'=>$param_info,'CONTENTS'=>$ret,'CODENAME'=>$codename,'EDIT_URL'=>$edit_url));
			$SHOW_EDIT_LINKS=true;
		}
	}

	return $ret;
}

/**
 * Certain symbols need preprocessing, before the output stream is made.
 *
 * @param  array			Symbol details
 * @param  array			Where we store children stuff
 */
function handle_symbol_preprocessing($bit,&$children)
{
	switch ($bit[2])
	{
		case 'PAGE_LINK':
			$param=$bit[3];

			if (isset($param[0]))
			{
				if (is_object($param[0])) $param[0]=$param[0]->evaluate();

				list(,$url_parts,)=page_link_decode($param[0]);

				if ((!isset($url_parts['id'])) && (!array_key_exists('id',$url_parts))) return;
				if ((!isset($url_parts['type'])) && (!array_key_exists('type',$url_parts))) $url_parts['type']='misc';
				if ($url_parts['type']===NULL) $url_parts['type']='misc'; // NULL means "do not take from environment"; so we default it to 'misc' (even though it might actually be left out when SEO URLs are off, we know it cannot be for SEO URLs)
				if (!array_key_exists('page',$url_parts)) return;
				if ($url_parts['id']===NULL) $url_parts['id']=/*get_param('id',*/strval(db_get_first_id())/*)*/;

				// Does this URL arrangement support monikers?
				global $LOADED_MONIKERS;
				if (!isset($LOADED_MONIKERS[$url_parts['page']][$url_parts['type']][$url_parts['id']]))
				{
					global $CONTENT_OBS,$LOADED_MONIKERS;
					load_moniker_hooks();
					$found=false;
					$looking_for='_SEARCH:'.$url_parts['page'].':'.$url_parts['type'].':_WILD';

					$ob_info=isset($CONTENT_OBS[$looking_for])?$CONTENT_OBS[$looking_for]:NULL;
					if ($ob_info!==NULL)
						$LOADED_MONIKERS[$url_parts['page']][$url_parts['type']][$url_parts['id']]=true; // Indicator to preload this
				}
			}
			return;

		case 'SET':
			$param=$bit[3];

			if (isset($param[1]))
			{
				global $TEMPCODE_SETGET;
				$param_copy=array();
				foreach ($param as $i=>$x)
				{
					if ($i!=0) $param_copy[]=is_object($x)?$x->evaluate():$x;
				}
				$TEMPCODE_SETGET[is_object($param[0])?$param[0]->evaluate():$param[0]]=implode(',',$param_copy);
				if (($GLOBALS['RECORD_TEMPLATES_TREE']) && (is_object($param[1])))
				{
					$children[]=array(':set: '.(is_object($param[0])?$param[0]->evaluate():$param[0]),isset($param[1]->children)?$param[1]->children:array(),isset($param[1]->fresh)?$param[1]->fresh:false);
				}
			}
			return;

		case 'BLOCK':
			$param=$bit[3];

			global $REQUEST_BLOCK_NEST_LEVEL;
			$REQUEST_BLOCK_NEST_LEVEL++;
			if ($REQUEST_BLOCK_NEST_LEVEL>40) // 100 caused xdebug error, but ocPortal will have some overhead in both error handler and other code to get to here. We want xdebug error to not show, but of course to provide the same benefits as that error.
			{
				$REQUEST_BLOCK_NEST_LEVEL=0;
				warn_exit(do_lang_tempcode('STOPPED_RECURSIVE_RESOURCE_INCLUDE'));
			}

			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();

			if ((count($param)==1) && (strpos($param[0],',')!==false))
			{
				$param=preg_split('#((?<![^\\\\])|(?<!\\\\\\\\)|(?<!^)),#',$param[0]);
			}

			//if (strpos(serialize($param),'side_stored_menu')!==false) { @debug_print_backtrace();exit(); } // Useful for debugging

			global $LOADED_BLOCKS;
			if (isset($LOADED_BLOCKS[serialize($param)]))
			{
				$REQUEST_BLOCK_NEST_LEVEL--;
				return;
			}

			$block_parms=array();
			foreach ($param as $_param)
			{
				$block_parts=explode('=',$_param,2);
				if (!isset($block_parts[1]))
				{
					$LOADED_BLOCKS[serialize($param)]=do_lang_tempcode('INTERNAL_ERROR');
					return;
				}
				list($key,$val)=$block_parts;
				$block_parms[$key]=$val;
			}

			if ((isset($_GET['keep_show_loading'])) && (function_exists('memory_get_usage')) && ($_GET['keep_show_loading']=='1'))
			{
				$before=memory_get_usage();
			}
			$b_value=do_block($block_parms['block'],$block_parms);
			if ((isset($_GET['keep_show_loading'])) && (function_exists('memory_get_usage')) && ($_GET['keep_show_loading']=='1'))
			{
				@ob_end_flush();
				@ob_end_flush();
				@ob_end_flush();
				print('<!-- block: '.htmlentities($block_parms['block']).' ('.clean_file_size(memory_get_usage()-$before).' bytes used, now at '.integer_format(memory_get_usage()).') -->'."\n");
				flush();
			}

			if ($GLOBALS['RECORD_TEMPLATES_TREE'])
			{
				$children[]=array(':block: '.$block_parms['block'],array(array($b_value->codename,isset($b_value->children)?$b_value->children:array(),isset($b_value->fresh)?$b_value->fresh:false)),true);
			}
			$b_value->handle_symbol_preprocessing();

			$LOADED_BLOCKS[serialize($param)]=$b_value;

			$REQUEST_BLOCK_NEST_LEVEL--;

			return;

		case 'JAVASCRIPT_INCLUDE':
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();
			require_javascript($param[0]);
			return;

		case 'FACILITATE_AJAX_BLOCK_CALL':
			require_javascript('javascript_ajax');
			return;

		case 'CSS_INHERIT':

		case 'CSS_INCLUDE':
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();
			require_css($param[0]);
			return;

		case 'TRIM':
			$param=$bit[3];
			if ((isset($param[0])) && (is_object($param[0])))
			{
				if ($GLOBALS['RECORD_TEMPLATES_TREE'])
				{
					$param[0]->handle_symbol_preprocessing();
					$children[]=array(':trim',isset($param[0]->children)?$param[0]->children:array(),isset($param[0]->fresh)?$param[0]->fresh:false,true);
				}
			}
			break;

		case 'LOAD_PANEL':
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();

			global $LOADED_PANELS;
			if (isset($LOADED_PANELS[serialize($param)])) return;

			if (array_key_exists(0,$param))
			{
				if (substr(get_page_name(),0,6)!='panel_')
				{
					if (strpos($param[0],':')!==false)
						$param=array_reverse(explode(':',$param[0],2));
					if (substr($param[0],0,6)=='panel_') $param[0]=substr($param[0],6);
					
					global $ZONE;
					$wide_high=is_wide_high();
					$wide=is_wide();
					if ((($wide==0) || (($wide_high==0) && (($param[0]=='bottom') || ($param[0]=='top')))) && ((get_option('site_closed')=='0') || ($GLOBALS['IS_ACTUALLY_ADMIN']) || (has_specific_permission(get_member(),'access_closed_site'))))
					{
						if ((function_exists('memory_get_usage')) && (isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading']=='1'))
						{
							$before=memory_get_usage();
						}
						$tp_value=request_page('panel_'.$param[0],false,array_key_exists(1,$param)?$param[1]:NULL,NULL);
						if ((function_exists('memory_get_usage')) && (isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading']=='1'))
						{
							@ob_end_flush();
							@ob_end_flush();
							@ob_end_flush();
							print('<!-- panel: '.htmlentities('panel_'.$param[0]).' ('.clean_file_size(memory_get_usage()-$before).' bytes used, now at '.number_format(memory_get_usage()).') -->'."\n");
							flush();
						}

						$tp_value->handle_symbol_preprocessing();
						if ($GLOBALS['RECORD_TEMPLATES_TREE'])
						{
							$children[]=array(':panel: '.$param[0],array(array($tp_value->codename,isset($tp_value->children)?$tp_value->children:array(),isset($tp_value->fresh)?$tp_value->fresh:false)),true);
						}

						$value=$tp_value->evaluate();
					} else $value='';
				} else $value='';
			} else $value='';

			$LOADED_PANELS[serialize($param)]=$value;

			return;

		case 'JS_TEMPCODE':
			if ($GLOBALS['RECORD_TEMPLATES_TREE'])
			{
				$param=$bit[3];
				foreach ($param as $i=>$p)
					if (is_object($p)) $param[$i]=$p->evaluate();

				$temp=javascript_tempcode(array_key_exists(0,$param)?$param[0]:NULL);

				$children[]=array(':container',isset($temp->children)?$temp->children:array(),isset($temp->fresh)?$temp->fresh:false);
			}
			return;

		case 'CSS_TEMPCODE':
			if ($GLOBALS['RECORD_TEMPLATES_TREE'])
			{
				$param=$bit[3];

				$temp=css_tempcode();

				$children[]=array(':container',isset($temp->children)?$temp->children:array(),isset($temp->fresh)?$temp->fresh:false);
			}
			return;

		case 'LOAD_PAGE':
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();

			global $LOADED_PAGES;
			if (array_key_exists(serialize($param),$LOADED_PAGES)) return;

			if (array_key_exists(0,$param))
			{
				if (strpos($param[0],':')!==false)
					$param=array_reverse(explode(':',$param[0],2));
				
				$being_included=(!array_key_exists(2,$param)) || ($param[2]=='1');

				if ((function_exists('memory_get_usage')) && (isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading']=='1'))
				{
					$before=memory_get_usage();
				}
				$tp_value=request_page($param[0],false,array_key_exists(1,$param)?$param[1]:NULL,NULL,$being_included);
				if ((function_exists('memory_get_usage')) && (isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading']=='1'))
				{
					@ob_end_flush();
					@ob_end_flush();
					@ob_end_flush();
					print('<!-- page: '.htmlentities($param[0]).' ('.clean_file_size(memory_get_usage()-$before).' bytes used, now at '.number_format(memory_get_usage()).') -->'."\n");
					flush();
				}
				if ($GLOBALS['RECORD_TEMPLATES_TREE'])
				{
					$children[]=array(':page: '.$param[0],isset($tp_value->children)?$tp_value->children:array(),isset($tp_value->fresh)?$tp_value->fresh:false);
				}
			} else $tp_value=new ocp_tempcode();

			$LOADED_PAGES[serialize($param)]=$tp_value;

			return;
			
		case 'FRACTIONAL_EDITABLE':
			require_javascript('javascript_fractional_edit');
			return;
	}
}

class ocp_tempcode
{
	var $code_to_preexecute;
	var $seq_parts; // List of closure pairs: function name, and parameters, type, name, and also last attach
	var $preprocessable_bits; // List of tuples: escape (ignored), type (e.g. TC_SYMBOL), name, parameters
	var $last_attach;
	var $pure_lang;

	var $codename=':container'; // The name of the template it came from
	
	var $cached_output;

	/**
	 * Constructor of tempcode
	 *
	 * @param  ?array			Pair: Code to preexecute, Initialisation seq-parts (NULL: start as empty)
	 */
	function ocp_tempcode($details=NULL)
	{
		$this->preprocessable_bits=array();
		$this->cached_output=NULL;
		
		if (!isset($details))
		{
			$this->seq_parts=array();
			$this->code_to_preexecute='';
		} else
		{
			$this->code_to_preexecute=$details[0];
			$this->seq_parts=$details[1];
			
			foreach ($this->seq_parts as $seq_part)
			{
				if ($seq_part[2]==TC_SYMBOL)
				{
					switch ($seq_part[3])
					{
						case 'CSS_INCLUDE':
						case 'JAVASCRIPT_INCLUDE':
						case 'FACILITATE_AJAX_BLOCK_CALL':
						case 'JS_TEMPCODE':
						case 'CSS_TEMPCODE':
						case 'SET':
						case 'BLOCK':
						case 'PAGE_LINK':
						case 'LOAD_PAGE':
						case 'LOAD_PANEL':
						case 'TRIM':
							$this->preprocessable_bits[]=array(array(),TC_SYMBOL,$seq_part[3],$seq_part[1]);
							break;
					}
				}
				elseif ($seq_part[2]==TC_DIRECTIVE)
				{
					switch ($seq_part[3])
					{
						case 'FRACTIONAL_EDITABLE':
							$this->preprocessable_bits[]=array(array(),TC_DIRECTIVE,$seq_part[3],$seq_part[1]);
							break;
					}
				}
			}
		}

		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$this->fresh=true;
			$this->children=array();
		}
	}
	
	/**
	 * PHP magic function to handle serialisation.
	 *
	 * @return array				What is to be serialised
	 */
	function __sleep()
	{
		return array('code_to_preexecute','seq_parts','preprocessable_bits','last_attach','pure_lang','codename');
	}
	
	/**
	 * Decache the object.
	 */
	function decache()
	{
		foreach ($this->seq_parts as $seq_part)
		{
			foreach ($seq_part[1] as $val)
			{
				if (is_object($val)) $val->decache();
			}
		}
		$this->cached_output=NULL;
	}

	/**
	 * Scan this Tempcode for anything that needs to be symbol-preprocessed
	 */
	function handle_symbol_preprocessing()
	{
		if (isset($this->preprocessed)) return;
		
		foreach ($this->preprocessable_bits as $bit)
		{
			handle_symbol_preprocessing($bit,$this->children);
		}
		
		$this->preprocessed=true;
	}

	/**
	 * Find whether a variable within this Tempcode is parameterless.
	 *
	 * @param  integer			Offset to the variable
	 * @return boolean			Whether it is parameterless
	 */
	function parameterless($at)
	{
		return ((!array_key_exists($at,$this->seq_parts)) || ($this->seq_parts[$at][1]==array()));
	}

	/**
	 * Parse a single symbol from an input stream and append it.
	 *
	 * @param  string				Code string (input stream)
	 * @param  integer			Start position of input string
	 * @param  integer			End position of input string
	 */
	function parse_from(&$code,&$pos,&$len)
	{
		$this->cached_output=NULL;
		require_code('tempcode_compiler');
		$temp=template_to_tempcode(substr($code,$pos,$len-$pos),0,false,'');
		$this->code_to_preexecute=$temp->code_to_preexecute;
		$this->seq_parts=$temp->seq_parts;
		$this->preprocessable_bits=$temp->preprocessable_bits;
	}

	/**
	 * Attach the specified tempcode to the right of the current tempcode object.
	 *
	 * @param  mixed				The tempcode/string to attach
	 * @param  boolean			If we've already merged the children from what we're attaching into the child tree (at bind stage)
	 */
	function attach($attach,$avoid_child_merge=false)
	{
		if ($attach==='') return;

		unset($this->is_really_empty);

		$this->cached_output=NULL;

		if (is_object($attach)) // Consider it another piece of tempcode
		{
			foreach ($attach->seq_parts as $seq)
			{
				$seq[4]=$this->last_attach;
				$this->seq_parts[]=$seq;
			}

			if (((strpos($this->code_to_preexecute,'/'.$attach->codename.'.')===false) && (strpos($this->code_to_preexecute,'$TPL_FUNCS[\'tcpfunc_'.$attach->codename.'\']')===false)) || ($GLOBALS['KEEP_MARKERS'])) // Optimisation for memory.
			{
				$this->code_to_preexecute.=$attach->code_to_preexecute;
			}
			$this->preprocessable_bits=array_merge($this->preprocessable_bits,$attach->preprocessable_bits);

			if ((!$avoid_child_merge) && ($GLOBALS['RECORD_TEMPLATES_TREE']))
			{
				$this->children[]=array($attach->codename,isset($attach->children)?$attach->children:array(),isset($attach->fresh)?$attach->fresh:false);
			}

			$this->last_attach=$attach->codename;

		} else // Consider it a string
		{
			$cnt=count($this->seq_parts);
			if (($cnt!=0) && ($this->seq_parts[$cnt-1][2]==TC_KNOWN) && ($this->seq_parts[$cnt-1][1]==array()))
			{
				$myfunc=$this->seq_parts[count($this->seq_parts)-1][0];
				$code=$this->code_to_preexecute;
				$pos=strpos($code,"\$TPL_FUNCS['$myfunc']=\"echo ");
				if ($pos!==false)
				{
					$pos2=strpos($code,"\";\n",$pos);
					$code=substr($code,0,$pos2)."echo \\\"".php_addslashes_twice($attach)."\\\";".substr($code,$pos2);
					$this->code_to_preexecute=$code;
					return;
				}
			}

			$myfunc='string_attach_'.uniqid('',true)/*fast_uniqid()*/;
			$funcdef=/*if (!isset(\$TPL_FUNCS['$myfunc']))\n\t*/"\$TPL_FUNCS['$myfunc']=\"echo \\\"".php_addslashes_twice($attach)."\\\";\";\n";
			$this->code_to_preexecute.=$funcdef;
			$this->seq_parts[]=array($myfunc,array(),TC_KNOWN,'','');
			$this->last_attach='';
		}
		
		$this->codename='(mixed)';
	}

	/**
	 * Find whether the current tempcode object is definitely blank, by doing a very quick check.
	 *
	 * @return boolean		Whether the tempcode object is empty
	 */
	function is_definitely_empty()
	{
		return !isset($this->seq_parts[0]);
	}

	/**
	 * Find whether the current tempcode object is blank or not.
	 *
	 * @return boolean		Whether the tempcode object is empty
	 */
	function is_empty()
	{
		//return $this->code_to_preexecute=='';
		return $this->is_really_empty();
	}

	/**
	 * Tests to see if something would evaluate to blank or not
	 *
	 * @return boolean		Whether it is really empty
	 */
	function is_really_empty()
	{
		if ($this->cached_output!==NULL) return strlen($this->cached_output)==0;
		if (isset($this->is_really_empty)) return $this->is_really_empty;

		if (!isset($this->seq_parts[0]))
		{
			$this->is_really_empty=true;
			return true;
		}

		//$tempcode_profile_log_start=microtime();
		//tempcode_profile_log($this->seq_parts);

		ob_start();

		global $NO_EVAL_CACHE,$XSS_DETECT,$USER_LANG_CACHED,$OB_GET_CLEAN;

		if ($XSS_DETECT)
		{
			$before=@ini_get('ocproducts.xss_detect');
			@ini_set('ocproducts.xss_detect','0');
		}

		$no_eval_cache_before=$NO_EVAL_CACHE;

		if (isset($USER_LANG_CACHED))
		{
			$current_lang=$USER_LANG_CACHED;
		} else
		{
			if (!function_exists('user_lang')) require_code('lang');
			$current_lang=user_lang();
		}
		$cl=$current_lang;

		$first_of_long=isset($this->seq_parts[3]);
		global $KEEP_TPL_FUNCS,$MEMORY_OVER_SPEED,$FULL_RESET_VAR_CODE,$RESET_VAR_CODE;
		$TPL_FUNCS=$KEEP_TPL_FUNCS;

		foreach ($this->seq_parts as $bit)
		{
			//$before=memory_get_usage();

			$bit_0=$bit[0];
			if (!isset($TPL_FUNCS[$bit_0]))
			{
				//$MY_LOG_FILE=fopen(get_custom_file_base().'/../test.log','wt'); fwrite($MY_LOG_FILE,$this->code_to_preexecute); fclose($MY_LOG_FILE);

				if (eval($this->code_to_preexecute)===false) fatal_exit(@strval($php_errormsg));
				if (!isset($TPL_FUNCS[$bit_0])) $TPL_FUNCS[$bit_0]=' '; // Fudge to stop error. Actually caused by a race condition and output will be incomplete
			}
			if (($TPL_FUNCS[$bit_0][0]!='e'/*for echo*/) && (function_exists($TPL_FUNCS[$bit_0])))
			{
				call_user_func($TPL_FUNCS[$bit_0],$bit[1],$current_lang,$bit[4]);
			} else
			{
				$parameters=$bit[1];
				$last_attach=$bit[4];
				if (eval($TPL_FUNCS[$bit_0])===false) fatal_exit(@strval($php_errormsg));
			}

			//@ob_end_flush();@ob_end_flush();@ob_end_flush();print('<!-- tempcode-eval-is_really_empty: '.htmlentities($this->codename).' ('.clean_file_size(memory_get_usage()-$before).' used, now at '.number_format(memory_get_usage()).') -->'."\n");flush();

			if ((($first_of_long) || ($MEMORY_OVER_SPEED)) && (ob_get_length()>0)) // We only quick exit on the first iteration, as we know we likely didn't spend much time getting to it- anything more and we finish so that we can cache for later use by evaluate/evaluate_echo
			{
				//$tempcode_profile_log_end=microtime();
				//tempcode_profile_log_diff($tempcode_profile_log_start,$tempcode_profile_log_end,$this->seq_parts);

				@ob_end_clean();
				if (!$no_eval_cache_before)
					$NO_EVAL_CACHE=$no_eval_cache_before;
				if ($XSS_DETECT)
					@ini_set('ocproducts.xss_detect',$before);
				$this->is_really_empty=false;
				return false;
			}

			$first_of_long=false;
		}

		//$tempcode_profile_log_end=microtime();
		//tempcode_profile_log_diff($tempcode_profile_log_start,$tempcode_profile_log_end,$this->seq_parts);

		if (($MEMORY_OVER_SPEED) || ($NO_EVAL_CACHE))
		{
			if ($OB_GET_CLEAN)
			{
				$tmp=ob_get_clean();
			} else
			{
				$tmp=ob_get_contents();
				@ob_end_clean();
			}
			if ($XSS_DETECT)
				@ini_set('ocproducts.xss_detect',$before);
			if (!$no_eval_cache_before)
				$NO_EVAL_CACHE=$no_eval_cache_before;
			$ret=($tmp=='');
			$this->is_really_empty=$ret;
			return $ret;
		}

		if ($OB_GET_CLEAN)
		{
			$this->cached_output=ob_get_clean(); // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result
		} else
		{
			$this->cached_output=ob_get_contents(); // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result
			@ob_end_clean();
		}
		if (!$no_eval_cache_before)
			$NO_EVAL_CACHE=$no_eval_cache_before;
		if ($XSS_DETECT)
			@ini_set('ocproducts.xss_detect',$before);
		$ret=($this->cached_output=='');
		$this->is_really_empty=$ret;
		return $ret;
	}

	/**
	 * Assemble the current tempcode object into a single serialised (compiled) tempcode storage representation (parameters and certain symbols and not evaluated). The output of the function is language-tied.
	 *
	 * @return string			The assembly result
	 */
	function to_assembly()
	{
		return 'return unserialize("'.php_addslashes(serialize(array($this->seq_parts,$this->preprocessable_bits,$this->codename,$this->last_attach,$this->pure_lang,$this->code_to_preexecute))).'");'.chr(10);
	}

	/**
	 * The opposite of to_assembly - it decodes a tempcode storage representation and turns it into a proper tempcode object. This version handles the result of evaled code.
	 *
	 * @param  PATH			The file to load
	 * @param  array			List of parameters for a forced reload if required
	 * @return boolean		Success status (it can fail, if the compiled cache file is corrupt)
	 */
	function from_assembly_executed($file,$forced_reload_details)
	{
		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$this->fresh=false;
			$this->children=array();
		}

		$result=@include($file); // We don't eval on this because we want it to potentially be op-code cached by e.g. Zend Accelerator
		if (!is_array($result)) return false; // May never get here, as PHP fatal errors can't be supressed or skipped over

		$this->cached_output=NULL;
		list($this->seq_parts,$this->preprocessable_bits,$this->codename,$this->last_attach,$this->pure_lang,$this->code_to_preexecute)=$result;

		if ($forced_reload_details[6]===NULL) $forced_reload_details[6]='';
		if ((isset($this->code_to_preexecute[800])) && ($GLOBALS['CACHE_TEMPLATES']))
		{
			// We don't actually use $code_to_preexecute, because it uses too much RAM and DB space throwing full templates into the cacheing. Instead we rewrite to custom load it whenever it's needed. This isn't inefficient due to normal opcode cacheing and optimizer opcode cacheing, and because we cache Tempcode object's evaluations at runtime so it can only happen once per screen view.
			$this->code_to_preexecute='if (($result=@include(\''.php_addslashes($file).'\'))===false) { $tmp=do_template(\''.php_addslashes($forced_reload_details[0]).'\',NULL,\''.php_addslashes($forced_reload_details[2]).'\',false,\''.(($forced_reload_details[6]=='')?'':php_addslashes($forced_reload_details[6])).'\',\''.($forced_reload_details[4]).'\',\''.($forced_reload_details[5]).'\'); clearstatcache(); if (mt_rand(0,100)==1 || !is_file(\''.php_addslashes($file).'\')) { $GLOBALS[\'CACHE_TEMPLATES\']=false; } eval($tmp->code_to_preexecute); unset($tmp); }
			else { eval($result[5]); unset($result); }';
		}

		global $SITE_INFO;
		if (((!isset($SITE_INFO['disable_decaching_shift_encode'])) || ($SITE_INFO['disable_decaching_shift_encode']!='1')) && (@strpos(file_get_contents($file),'SHIFT_ENCODE')!==false))
		{
			$this->code_to_preexecute.='/*SHIFT_ENCODE*/';
		}

		if ($GLOBALS['XSS_DETECT'])
		{
			$this->_mark_all_as_escaped();
		}
		
		return true;
	}
	
	/**
	 * Recursively mark all parameters in this Tempcode as escaped. This is needed when loading from cache, as escape tainting data would have been lost.
	 */
	function _mark_all_as_escaped()
	{
		foreach ($this->seq_parts as $seq_part)
		{
			foreach ($seq_part[1] as $key=>$val)
			{
				if (is_string($val))
				{
					ocp_mark_as_escaped($val);
				} elseif (is_object($val))
				{
					$val->_mark_all_as_escaped();
				}
				
				$seq_part[1][$key]=$val;
			}
		}
	}
	
	/**
	 * The opposite of to_assembly - it decodes a tempcode storage representation and turns it into a proper tempcode object.
	 *
	 * @param  string			The assembled tempcode
	 * @param  boolean		Return error code on failure, rather than exiting
	 * @return boolean		Success status (it can fail, if the compiled cache file is corrupt)
	 */
	function from_assembly(&$raw_data,$allow_failure=false)
	{
		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$this->fresh=false;
			$this->children=array();
		}

		$result=eval($raw_data);
		if ($result===false)
		{
			if ($allow_failure) return false;
			//inspect_plain($raw_data);
			fatal_exit(@strval($php_errormsg));
		}

		$this->cached_output=NULL;
		list($this->seq_parts,$this->preprocessable_bits,$this->codename,$this->last_attach,$this->pure_lang,$this->code_to_preexecute)=$result;

		if ($GLOBALS['XSS_DETECT'])
		{
			$this->_mark_all_as_escaped();
		}

		if (strpos($raw_data,'SHIFT_ENCODE')!==false)
		{
			$this->code_to_preexecute.='/*SHIFT_ENCODE*/';
			$this->evaluate();
		}
		
		return true;
	}

	/**
	 * Replace the named parameter with a specific value. Hardly used, but still important. Note that this will bind to all kinds of things that might not normally take named parameters, like symbols; this should not cause problems though.
	 *
	 * @param  string			Named parameter
	 * @param  tempcode		Specific value
	 */
	function singular_bind($parameter,$value)
	{
		$this->cached_output=NULL;

		if ($this->seq_parts==array()) return;

		foreach ($this->seq_parts as $i=>$bit)
		{
			if ((($bit[0][0]!='s') || (substr($bit[0],0,14)!='string_attach_')) && ($bit[2]!=TC_LANGUAGE_REFERENCE))
				$bit[1][$parameter]=$value;
			$this->seq_parts[$i]=$bit;
		}

		$this->preprocessable_bits=array_merge($value->preprocessable_bits,$value->preprocessable_bits);
	}

	/**
	 * Bind the parameter bits, or recursively bind children (doesn't change self, returns a bound tempcode object)
	 *
	 * @param  array			Map of parameters to bind parameter bits to
	 * @param  ID_TEXT		The codename of the template this tempcode is from
	 * @return tempcode		The new bound tempcode object
	 */
	function bind(&$parameters,$codename)
	{
		if (!isset($parameters['_GUID']))
		{
			$parameters['_GUID']='';

			static $dbt=NULL;
			if ($dbt===NULL)
				$dbt=function_exists('debug_backtrace');
			if ($dbt)
			{
				$trace=debug_backtrace();
				$parameters['_GUID']=isset($trace[3])?($trace[3]['function'].'/'.$trace[2]['function']):(isset($trace[2])?$trace[2]['function']:$trace[1]['function']);
			}
		}

		$out=new ocp_tempcode();
		$out->codename=$codename;
		$out->code_to_preexecute=$this->code_to_preexecute;
		$out->preprocessable_bits=array();
		foreach ($this->preprocessable_bits as $preprocessable_bit)
		{
			foreach ($preprocessable_bit[3] as $i=>$param)
			{
				if ((($preprocessable_bit[2]!='SET') || (($i==1))) && (is_object($param)))
					$preprocessable_bit[3][$i]=$param->bind($parameters,'');
			}
			$out->preprocessable_bits[]=$preprocessable_bit;
		}

		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$out->children=isset($this->children)?$this->children:array();
			foreach ($parameters as $key=>$parameter)
			{
				if (is_object($parameter))
				{
					if (count($parameter->preprocessable_bits)!=0)
						$parameter->handle_symbol_preprocessing(); // Needed to force children to be populated. Otherwise it is possible but not definite that evaluation will result in children being pushed down (SHIFT_ENCODING in a template can cause them to be misappropriated, as it causes a runtime cache string in the Tempcode tree).
					$out->children[]=array($parameter->codename,isset($parameter->children)?$parameter->children:array(),isset($parameter->fresh)?$parameter->fresh:false);
				}
				elseif ((is_string($parameter)) && ($key=='_GUID'))
				{
					$out->children[]=array(':guid',array(array(':'.$parameter,array(),true)),true);
				}
			}
		}
		foreach ($parameters as $key=>$parameter)
		{
			$p_type=gettype($parameter);
			if ($p_type=='string')
			{
				// Performance, this is most likely
			}
			elseif ($p_type=='object')
			{
				if (isset($parameter->preprocessable_bits[0]))
				{
					$out->preprocessable_bits=array_merge($out->preprocessable_bits,$parameter->preprocessable_bits);
				} elseif (!isset($parameter->seq_parts[0])) $parameters[$key]=''; // Little optimisation to save memory
			}
			elseif ($p_type=='boolean')
			{
				$parameters[$key]=$parameter?'1':'0';
			}
			elseif (($p_type!='array') && ($p_type!='NULL'))
			{
				warn_exit(do_lang_tempcode('NO_INTEGERS_TEMPLATE',escape_html($key)));
			}
		}

		foreach ($this->seq_parts as $bit)
		{
			if ((($bit[0][0]!='s') || (substr($bit[0],0,14)!='string_attach_')) && ($bit[2]!=TC_LANGUAGE_REFERENCE))
				$bit[1]=&$parameters; // & is to preserve memory
			$out->seq_parts[]=$bit;
		}

		// Force pre-execution, so shift encoding handled. This is prior to pre-processing (it needs to be for validity in interaction with what is in itself preprocessed), so you could consider it pre-pre-processing
		if (strpos($out->code_to_preexecute,'SHIFT_ENCODE')!==false) $out->evaluate();

		return $out;
	}

	/**
	 * Parse the current tempcode object, then echo it to the browser.
	 *
	 * @param  ?LANGUAGE_NAME	The language to evaluate with (NULL: current users language)
	 * @param  mixed				Whether to escape the tempcode object (children may be recursively escaped regardless if those children/parents are marked to be)
	 * @return string				Blank string. Allows chaining within echo statements
	 */
	function evaluate_echo($current_lang=NULL,$_escape=false)
	{
		if (ocp_srv('REQUEST_METHOD')=='HEAD') return '';

		if ($this->cached_output!==NULL)
		{
			echo $this->cached_output;
			$this->cached_output=NULL; // Won't be needed again
			return '';
		}
		if (!isset($this->seq_parts[0]))
		{
			$this->cached_output='';
			return '';
		}

		$cl=$current_lang;

		//ob_start();

		//$tempcode_profile_log_start=microtime();
		//tempcode_profile_log($this->seq_parts);

		global $KEEP_TPL_FUNCS,$FULL_RESET_VAR_CODE,$RESET_VAR_CODE;
		$TPL_FUNCS=$KEEP_TPL_FUNCS;
		$seq_parts=&$this->seq_parts;
		foreach ($seq_parts as $i=>$bit)
		{
			//$before=memory_get_usage();

			$bit_0=$bit[0];
			if (!isset($TPL_FUNCS[$bit_0]))
			{
				//eval_log($this->code_to_preexecute);
				if (eval($this->code_to_preexecute)===false) fatal_exit(@strval($php_errormsg));
				if (!isset($TPL_FUNCS[$bit_0])) $TPL_FUNCS[$bit_0]=' '; // Fudge to stop error. Actually caused by a race condition and output will be incomplete
			}
			if (($TPL_FUNCS[$bit_0][0]!='e'/*for echo*/) && (function_exists($TPL_FUNCS[$bit_0])))
			{
				call_user_func($TPL_FUNCS[$bit_0],$bit[1],$current_lang,$bit[4]);
			} else
			{
				$parameters=$bit[1];
				$last_attach=$bit[4];
				//eval_log($TPL_FUNCS[$bit_0]);
				if (eval($TPL_FUNCS[$bit_0])===false) fatal_exit(@strval($php_errormsg));
			}

			//@ob_end_flush();@ob_end_flush();@ob_end_flush();print('<!-- tempcode-eval-evaluate_echo: '.htmlentities($this->codename).' ('.clean_file_size(memory_get_usage()-$before).' used, now at '.number_format(memory_get_usage()).') -->'."\n");flush();

//			if (isset($GLOBALS['FINISHING_OUTPUT'])) $seq_parts[$i]=NULL;
		}
	/*	if (isset($GLOBALS['FINISHING_OUTPUT']))		Optimisation to free memory up during wind down. Does not work well enough to risk the possible bugs doing this
		{
			$this->preprocessable_bits=NULL;
			$this->seq_parts=NULL;
			$this->code_to_preexecute=NULL;
		}*/

		//ob_end_flush();
		
		//$tempcode_profile_log_end=microtime();
		//tempcode_profile_log_diff($tempcode_profile_log_start,$tempcode_profile_log_end,$this->seq_parts);

		return '';
	}

	/**
	 * Parses the current tempcode object, then return the parsed string
	 *
	 * @param  ?LANGUAGE_NAME	The language to evaluate with (NULL: current user's language)
	 * @param  mixed				Whether to escape the tempcode object (children may be recursively escaped regardless if those children/parents are marked to be)
	 * @param  ?integer			Evaluate at least this much (NULL: evaluate all)
	 * @return string				The evaluated thing. Voila, it's all over!
	 */
	function evaluate($current_lang=NULL,$_escape=false,$up_to=NULL)
	{
		/*static $do_memory_tracking=NULL;	Breaks installer, and poor performance
		if ($do_memory_tracking===NULL) $do_memory_tracking=(get_value('memory_tracking')==='1');
		if ($do_memory_tracking)
		{
			if ((memory_get_usage()>50*1024*1024) && ((!isset($GLOBALS['FORUM_DRIVER'])) || (!$GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()))))
			{
				fatal_exit('Memory problem - over 50MB used');
			}
		}*/

		if (isset($this->cached_output)) return $this->cached_output;
		if (!isset($this->seq_parts[0]))
		{
			$this->cached_output='';
			return '';
		}

		global $NO_EVAL_CACHE,$MEMORY_OVER_SPEED,$USER_LANG_CACHED,$XSS_DETECT,$OB_GET_CLEAN;

		ob_start();

		if ($XSS_DETECT)
		{
			$before=@ini_get('ocproducts.xss_detect');
			@ini_set('ocproducts.xss_detect','0');
		}

		if ($current_lang===NULL)
		{
			if (isset($USER_LANG_CACHED))
			{
				$current_lang=$USER_LANG_CACHED;
			} else
			{
				if (!function_exists('user_lang')) require_code('lang');
				$current_lang=user_lang();
			}
		}
		$cl=$current_lang;

		//$tempcode_profile_log_start=microtime();
		//tempcode_profile_log($this->seq_parts);

		global $KEEP_TPL_FUNCS,$FULL_RESET_VAR_CODE,$RESET_VAR_CODE;
		$TPL_FUNCS=$KEEP_TPL_FUNCS; // NB: $TPL_FUNCS isn't really global, done like this for legacy reasons so our cache doesn't break. Eval'd code is in same variable scope
		$doing_up_to=isset($up_to);
		$seq_parts=&$this->seq_parts;
		$no_eval_cache_before=$NO_EVAL_CACHE;
		foreach ($seq_parts as $i=>$bit)
		{
			if (($doing_up_to) && (ob_get_length()>$up_to))
			{
				if ($OB_GET_CLEAN)
				{
					$ret=ob_get_clean();
				} else
				{
					$ret=ob_get_contents();
					@ob_end_clean();
				}

				if ($XSS_DETECT)
					@ini_set('ocproducts.xss_detect',$before);
				return $ret;
			}

			//$before=memory_get_usage();

			$bit_0=$bit[0];
			if (!isset($TPL_FUNCS[$bit_0]))
			{
				//eval_log($this->code_to_preexecute);
				if (eval($this->code_to_preexecute)===false) fatal_exit(@strval($php_errormsg));
				if (!isset($TPL_FUNCS[$bit_0])) $TPL_FUNCS[$bit_0]=' '; // Fudge to stop error. Actually caused by a race condition and output will be incomplete
			}
			if (($TPL_FUNCS[$bit_0][0]!='e'/*for echo*/) && (function_exists($TPL_FUNCS[$bit_0])))
			{
				call_user_func($TPL_FUNCS[$bit_0],$bit[1],$current_lang,$bit[4]);
			} else
			{
				$parameters=$bit[1];
				$last_attach=$bit[4];

				/*$code=$TPL_FUNCS[$bit_0];		Debug code to find good stack traces but unfortunately it never worked
				$file=get_custom_file_base().'/uploads/website_specific/'.substr(md5($code),0,10).'.php';
				$myfile=fopen($file,'wb');
				fwrite($myfile,'<?php'.chr(10));
				fwrite($myfile,$code);
				fclose($myfile);
				fix_permissions($file);
				sync_file($file);
				if (@include($file)===false)*/

				//eval_log($TPL_FUNCS[$bit_0]);
				if (eval($TPL_FUNCS[$bit_0])===false)
				{
					//@ob_end_clean();@var_dump($this);@exit($php_errormsg);
					fatal_exit(@strval($php_errormsg));
				}
			}

			//@ob_end_flush();@ob_end_flush();@ob_end_flush();print('<!-- tempcode-eval-evaluate: '.htmlentities($this->codename).' ('.clean_file_size(memory_get_usage()-$before).' used, now at '.number_format(memory_get_usage()).') -->'."\n");flush();
		}

		if ($XSS_DETECT)
			@ini_set('ocproducts.xss_detect',$before);

		//$tempcode_profile_log_end=microtime();
		//tempcode_profile_log_diff($tempcode_profile_log_start,$tempcode_profile_log_end,$this->seq_parts);

		if ($NO_EVAL_CACHE || $MEMORY_OVER_SPEED)
		{
			if (!$no_eval_cache_before)
				$NO_EVAL_CACHE=$no_eval_cache_before;
			if ($OB_GET_CLEAN)
			{
				$ret=ob_get_clean();
			} else
			{
				$ret=ob_get_contents();
				@ob_end_clean();
			}
			return $ret;
		}

		if ($OB_GET_CLEAN)
		{
			$this->cached_output=ob_get_clean(); // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result
		} else
		{
			$this->cached_output=ob_get_contents(); // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result
			@ob_end_clean();
		}

		return $this->cached_output;
	}

}

/*f unction tempcode_profile_log($bit)
{
	global $MY_LOG_FILE,$SUMMARY;
	if (!isset($MY_LOG_FILE))
	{
		$MY_LOG_FILE=fopen(get_custom_file_base().'/../test.log','wt');
		$SUMMARY=array();
		fwrite($MY_LOG_FILE,ocp_srv('SCRIPT_NAME').'?'.ocp_srv('QUERY_STRING')."...\n\n");
		
		if (function_exists('set_time_limit')) @set_time_limit(2);
		
		register_shutdown_function('finish_logging');
	}

	$backtrace=debug_backtrace();
	
	$level=count($backtrace)-1;
	$function=$backtrace[1]['function'];
	$sz=serialize($bit);
	$signature=md5($sz);
	$complexity=strlen($sz);
	
	if (!isset($SUMMARY[$signature])) $SUMMARY[$signature]=0;
	$SUMMARY[$signature]++;
	
	$bit=array(); // Comment out this line to show full data in the dump
	fwrite($MY_LOG_FILE,$function.': '.$signature.' [recursive level='.integer_format($level).', parts='.integer_format(count($bit)).', complexity='.integer_format($complexity).' bytes]      '.str_replace("\n",'\n',serialize($bit))."\n");
}

f unction tempcode_profile_log_diff($from,$to,$bit)
{
	global $MY_LOG_FILE,$SUMMARY;
	
	if (isset($MY_LOG_FILE))
	{
		$sz=serialize($bit);
		$signature=md5($sz);

		fwrite($MY_LOG_FILE,'Time taken for '.$signature.': '.float_format(microtime_diff($from,$to)).' seconds'."\n");
	}
}

f unction finish_logging()
{
	global $MY_LOG_FILE,$SUMMARY;
	
	if (isset($MY_LOG_FILE))
	{
		fwrite($MY_LOG_FILE,"\n\nSUMMARY...\n\n");
		foreach ($SUMMARY as $sig=>$count)
		{
			fwrite($MY_LOG_FILE,$sig.' [x '.integer_format($count).']'."\n");
		}
	
		fclose($MY_LOG_FILE);
		$MY_LOG_FILE=NULL;
	}
}

f unction eval_log($in)
{
	global $SLOGFILE;
	if (!isset($SLOGFILE)) $SLOGFILE=fopen(get_custom_file_base().'/data_custom/tempcode.log','wt');
	fwrite($SLOGFILE,$in."\n\n\n\n");
}*/
