<?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
 */

/*
This is the run-time version of the ocPortal Tempcode implementation.
It works on Hip Hop PHP, whilst the normal (but more efficient for Zend PHP) eval-based-version will not.
*/

/**
 * Standard code module initialisation function.
 */
function init__tempcode__runtime()
{
	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 $PREPROCESSED_BLOCKS;
	$PREPROCESSED_BLOCKS=array('BLOCK'=>1,'LOAD_PANEL'=>1,'LOAD_PAGE'=>1);

	global $TEMPLATE_PREVIEW_OP;
	$TEMPLATE_PREVIEW_OP=array_key_exists('template_preview_op',$_POST) && ($_POST['template_preview_op']==1);

	global $TEMPLATE_CACHE,$XHTML_SPIT_OUT,$MEMORY_OVER_SPEED,$REQUEST_BLOCK_NEST_LEVEL;
	$TEMPLATE_CACHE=array();
	$XHTML_SPIT_OUT=NULL;
	$MEMORY_OVER_SPEED=false;
	$REQUEST_BLOCK_NEST_LEVEL=0;

	global $RECORD_TEMPLATES_USED,$RECORDED_TEMPLATES_USED,$RECORD_TEMPLATES_TREE,$SCREEN_TEMPLATE_CALLED,$TITLE_CALLED;
	$RECORD_TEMPLATES_USED=false;
	$RECORDED_TEMPLATES_USED=array();
	$RECORD_TEMPLATES_TREE=false;
	$SCREEN_TEMPLATE_CALLED=NULL;
	$TITLE_CALLED=false;

	define('SYMBOL_PARSE_NAME',0);
	define('SYMBOL_PARSE_PARAM',1);

	global $SIMPLE_ESCAPED;
	$SIMPLE_ESCAPED=array(ENTITY_ESCAPED);

	global $DIRECTIVES_NEEDING_VARS;
	$DIRECTIVES_NEEDING_VARS=array('IF_PASSED'=>1,'IF_NON_PASSED'=>1,'IF_PASSED_AND_TRUE'=>1,'IF_NON_PASSED_OR_FALSE'=>1,'IN_ARRAY'=>1,'IMPLODE'=>1,'COUNT'=>1,'IF_ARRAY_EMPTY'=>1,'IF_ARRAY_NON_EMPTY'=>1,'OF'=>1,'INCLUDE'=>1,'LOOP'=>1);

	global $PHP_REP_FROM,$PHP_REP_TO,$PHP_REP_TO_TWICE;
	$PHP_REP_FROM=array('\\',"\n",'$','"');
	$PHP_REP_TO=array('\\\\','\n','\$','\\"');
	$PHP_REP_TO_TWICE=array('\\\\\\\\','\\n','\\\\$','\\\\\"');

	global $PREG_MATCH_OFFSET;
	$PREG_MATCH_OFFSET=(version_compare(phpversion(),'4.3.3')!=-1);

	require_code('symbols');
	require_code('tempcode_compiler');
}

/**
 * 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))));*/
}

/**
 * Convert a string to tempcode.
 *
 * @param  string			String
 * @return tempcode		Tempcode
 */
function make_string_tempcode($string)
{
	$tempcode=new ocp_tempcode();
	$tempcode->codename=':string';
	$tempcode->attach($string);
	return $tempcode;
}

/**
 * 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 ($escaped as $escape)
	{
		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=fix_id($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 ($escaped as $escape)
	{
		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=fix_id($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 symbol 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($symbol,$token1=NULL,$token2=NULL,$token3=NULL)
{
	$parameters=array();
	if (!is_null($token1)) $parameters[]=$token1;
	if (!is_null($token2)) $parameters[]=$token2;
	if (!is_null($token3))
	{
		if (!is_array($token3))
		{
			$parameters[]=$token3;
		} else
		{
			$parameters=array_merge($parameters,$token3);
		}
	}
	$_ret=new ocp_tempcode();
	$_ret->bits=array(array(array(),TC_LANGUAGE_REFERENCE,$symbol,$parameters)); // An list of bits, which are stored as tuples, which contain a list of escaping
	$_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 (is_null($parameters)) $parameters=array();
	$_ret=new ocp_tempcode();
	$_ret->bits=array(array(is_null($escape)?array():$escape,TC_SYMBOL,$symbol,$parameters)); // An list of bits, which are stored as tuples, which contain a list of escaping
	return $_ret;
}

/**
 * This will create a new tempcode object that is containing a single specifed directive
 *
 * @param  ID_TEXT		The ID of the symbol 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 (is_null($parameters)) $parameters=array();
	$parameters[]=$content;
	$_ret=new ocp_tempcode();
	$_ret->bits=array(array(array(),TC_DIRECTIVE,$directive,$parameters)); // An list of bits, which are stored as tuples, which contain a list of escaping
	return $_ret;
}

/**
 * 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();
}

/**
 * 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
 * @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 ((is_null($lang)) || ($lang==''))
	{
		global $USER_LANG_CACHED;
		$lang=isset($USER_LANG_CACHED)?$USER_LANG_CACHED:(function_exists('user_lang')?user_lang():'EN');
	}

	if ($GLOBALS['SEMI_DEV_MODE'])
	{
		if (($codename!='tempcode_test') && (strtoupper($codename)!=strtoupper($codename)))
		{
			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'))
		{
			$GLOBALS['SCREEN_TEMPLATE_CALLED']=$codename;
		}
	}

	if (is_null($parameters)) $parameters=array();

	global $RECORD_TEMPLATES_USED,$FILE_ARRAY,$MEM_CACHE,$CACHE_TEMPLATES,$KEEP_MARKERS,$SHOW_EDIT_LINKS,$XHTML_SPIT_OUT,$TEMPLATE_CACHE,$MOBILE,$FORUM_DRIVER;
	$special_treatment=((($KEEP_MARKERS) || ($SHOW_EDIT_LINKS)) && (is_null($XHTML_SPIT_OUT)));

	// Is it already loaded?
	if ($RECORD_TEMPLATES_USED)
	{
		global $RECORDED_TEMPLATES_USED;
		$RECORDED_TEMPLATES_USED[]=$codename;
	}

	// Variables we'll need
	if (!isset($theme))
		$theme=((isset($FORUM_DRIVER)) && (is_object($FORUM_DRIVER)) && (method_exists($FORUM_DRIVER,'get_theme')))?filter_naughty($FORUM_DRIVER->get_theme()):'default';
	$_codename=($MOBILE)?$codename.'_mobile':$codename;

	if (isset($TEMPLATE_CACHE[$theme][$codename][$lang]))
	{
		$_data=$TEMPLATE_CACHE[$theme][$codename][$lang]->bind($parameters,$codename);

		// Copy and pasted to remove need for an function call
		if ($special_treatment)
		{
			if ($KEEP_MARKERS)
			{
				$__data=make_string_tempcode('<!-- START-TEMPLATE='.$codename.' -->');
				$__data->attach($_data);
				$__data->attach('<!-- END-TEMPLATE='.$codename.' -->');
				$_data=$__data;
			}
			if ($SHOW_EDIT_LINKS)
			{
				$edit_url=build_url(array('page'=>'admin_themes','theme'=>$FORUM_DRIVER->get_theme(),'template'=>$codename),'adminzone');
				$_data->attach('<br /><a href="'.escape_html($edit_url->evaluate()).'">'.do_lang('EDIT').' '.$codename.'</a>');
			}
		}

		return $_data;
	}

	// Is it structurally cached on disk yet?
	$data=mixed();
	if ($CACHE_TEMPLATES)
	{
		if (!is_null($MEM_CACHE))
		{
			$data=persistent_cache_get(array('TEMPLATE',$theme,$lang,$_codename));
			if (!is_null($data))
			{
				$_data=new ocp_tempcode();
				$_data->from_assembly($data);
				if ($_data->bits==array()) $data=false; // Corrupt somehow
			} else
			{
				$data=false;
			}
		}
		elseif (is_null($data))
		{
			$_data=new ocp_tempcode();
			$tcp_path=get_custom_file_base().'/themes/'.$theme.'/templates_cached/'.$lang.'/'.$_codename.$suffix.'.tcd';
			if (is_file($tcp_path))
			{
				$tmp=fopen($tcp_path,'rb');
				flock($tmp,LOCK_SH);
				$data=file_get_contents($tcp_path);
				flock($tmp,LOCK_UN);
				fclose($tmp);
			} else $data=false;
			if ($data!==false)
			{
				$_data->from_assembly($data);
				if ($_data->bits==array()) $data=false; // Corrupt somehow
			}
		}
	} else $data=false;
	if ($data===false) // No, it's not
	{
		if (!isset($FILE_ARRAY))
		{
			$_data=NULL;

			$prefix_default=get_file_base().'/themes/';
			$prefix=($theme=='default')?$prefix_default:(get_custom_file_base().'/themes/');

			if (file_exists($prefix.$theme.'/'.$type.'_custom/'.$_codename.$suffix))
				$_data=_do_template($theme,'/'.$type.'_custom/',$_codename,$_codename,$lang,$suffix,$theme);
			elseif (file_exists($prefix.$theme.'/'.$type.'/'.$_codename.$suffix))
				$_data=_do_template($theme,'/'.$type.'/',$_codename,$_codename,$lang,$suffix,$theme);
			elseif ((!is_null($GLOBALS['CURRENT_SHARE_USER'])) && ($theme!='default') && (file_exists($prefix_default.$theme.'/'.$type.'_custom/'.$_codename.$suffix)))
				$_data=_do_template($theme,'/'.$type.'_custom/',$_codename,$_codename,$lang,$suffix,$theme);
			elseif ((!is_null($GLOBALS['CURRENT_SHARE_USER'])) && ($theme!='default') && (file_exists($prefix_default.$theme.'/'.$type.'/'.$_codename.$suffix)))
				$_data=_do_template($theme,'/'.$type.'/',$_codename,$_codename,$lang,$suffix,$theme);
			elseif (file_exists($prefix_default.'default'.'/'.$type.'_custom/'.$_codename.$suffix))
				$_data=_do_template('default','/'.$type.'_custom/',$_codename,$_codename,$lang,$suffix,$theme);
			elseif (file_exists($prefix_default.'default'.'/'.$type.'/'.$_codename.$suffix))
				$_data=_do_template('default','/'.$type.'/',$_codename,$_codename,$lang,$suffix,$theme);

			elseif ($codename!=$_codename)
			{
				if (file_exists($prefix.$theme.'/'.$type.'_custom/'.$codename.$suffix))
					$_data=_do_template($theme,'/'.$type.'_custom/',$codename,$_codename,$lang,$suffix,$theme);
				elseif (file_exists($prefix.$theme.'/'.$type.'/'.$codename.$suffix))
					$_data=_do_template($theme,'/'.$type.'/',$codename,$_codename,$lang,$suffix,$theme);
				elseif (file_exists($prefix_default.'default'.'/'.$type.'_custom/'.$codename.$suffix))
					$_data=_do_template('default','/'.$type.'_custom/',$codename,$_codename,$lang,$suffix,$theme);
				elseif (file_exists($prefix_default.'default'.'/'.$type.'/'.$codename.$suffix))
					$_data=_do_template('default','/'.$type.'/',$codename,$_codename,$lang,$suffix,$theme);
			}
			if (is_null($_data))
			{
				if (is_null($fallback))
				{
					if ($light_error) return paragraph(do_lang_tempcode('MISSING_TEMPLATE_FILE',escape_html($codename)));
					fatal_exit(do_lang_tempcode('MISSING_TEMPLATE_FILE',escape_html($codename)));
				}
				else
				{
					$result=do_template($fallback,$parameters,$lang);
					$TEMPLATE_CACHE[$theme][$codename][$lang]=$TEMPLATE_CACHE[$fallback][$lang];
					return $result;
				}
			}
		} else
		{
			$_data=_do_template('default','/'.$type.'/',$codename,$codename,$lang,$suffix,$theme);
		}
	}

	$TEMPLATE_CACHE[$theme][$codename][$lang]=$_data;

	$ret=$_data->bind($parameters,$codename);

	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'))
		{
			$param_info=do_template('PARAM_INFO',array('_GUID'=>'fd3a19c3c4f2e54ded5c784c04f2b622','MAP'=>$parameters));

			$edit_url=build_url(array('page'=>'admin_themes','theme'=>$FORUM_DRIVER->get_theme(),'template'=>$codename),'adminzone');
			$SHOW_EDIT_LINKS=false;
			$ret=do_template('TEMPLATE_EDIT_LINK',array('_GUID'=>'f0a439e3384a57e4c9a0e0bc366d7ce9','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':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$param=$bit[3];

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

				list(,$url_parts,)=page_link_decode(str_replace(chr(10),'',$param[0]));

				if (!array_key_exists('id',$url_parts)) return;
				if (!array_key_exists('type',$url_parts)) $url_parts['type']='misc';
				if (is_null($url_parts['type'])) $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 (!is_string($url_parts['id']))
				{
					if (is_null($url_parts['id'])) $url_parts['id']=/*get_param('id',*/strval(db_get_first_id())/*)*/;
				}

				// Does this URL arrangement support monikers?
				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 (!is_null($ob_info))
				{
					if (!isset($LOADED_MONIKERS[$url_parts['page']][$url_parts['type']][$url_parts['id']]))
						$LOADED_MONIKERS[$url_parts['page']][$url_parts['type']][$url_parts['id']]=true; // Indicator to preload this
				}
			}
			return;

		case 'SET':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$param=$bit[3];

			if (array_key_exists(1,$param))
			{
				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);
			}
			return;

		case 'BLOCK':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$param=$bit[3];

			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 $REQUEST_BLOCK_NEST_LEVEL;

			global $LOADED_BLOCKS;
			if (array_key_exists(serialize($param),$LOADED_BLOCKS))
			{
				$REQUEST_BLOCK_NEST_LEVEL--;
				return;
			}

			$REQUEST_BLOCK_NEST_LEVEL++;
			if ($REQUEST_BLOCK_NEST_LEVEL>20)
			{
				$REQUEST_BLOCK_NEST_LEVEL=0;
				$LOADED_BLOCKS[serialize($param)]=do_lang_tempcode('INTERNAL_ERROR');
				attach_message(do_lang_tempcode('STOPPED_RECURSIVE_RESOURCE_INCLUDE',is_string($param[0])?$param[0]:'block'),'warn');
				return;
			}

			$block_parms=array();
			foreach ($param as $_param)
			{
				$block_parts=explode('=',$_param,2);
				if (count($block_parts)!=2)
				{
					$LOADED_BLOCKS[serialize($param)]=new ocp_tempcode();
					continue 2;
				}
				list($key,$val)=$block_parts;
				$block_parms[$key]=$val;
			}
			$b_value=do_block($block_parms['block'],$block_parms);

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

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

			$REQUEST_BLOCK_NEST_LEVEL--;

			return;

		case 'REQUIRE_JAVASCRIPT':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$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 'REQUIRE_CSS':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();
			require_css($param[0]);
			return;

		case 'LOAD_PANEL':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$param=$bit[3];
			foreach ($param as $i=>$p)
				if (is_object($p)) $param[$i]=$p->evaluate();

			global $LOADED_PANELS;
			if (array_key_exists(serialize($param),$LOADED_PANELS)) 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'))))
					{
						$tp_value=request_page('panel_'.$param[0],false,array_key_exists(1,$param)?$param[1]:NULL,NULL);

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

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

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

			return;

		case 'JS_TEMPCODE':
			if ($GLOBALS['RECORD_TEMPLATES_TREE'])
			{
				if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
				$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',$temp->children,$temp->fresh);
			}
			return;

		case 'CSS_TEMPCODE':
			if ($GLOBALS['RECORD_TEMPLATES_TREE'])
			{
				if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;

				$temp=css_tempcode();

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

		case 'LOAD_PAGE':
			if ((!array_key_exists(3,$bit)) || (is_null($bit[3]))) return;
			$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');
				$tp_value=request_page($param[0],false,array_key_exists(1,$param)?$param[1]:NULL,NULL,$being_included);
				if ($GLOBALS['RECORD_TEMPLATES_TREE'])
				{
					$children[]=array(':page: '.$param[0],$tp_value->children,$tp_value->fresh);
				}
			} else $tp_value=new ocp_tempcode();

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

			return;

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

/**
 * Tempcode (non-compiled implementation).
 * @package		core
 */
class ocp_tempcode
{
	// An array of bits where each bit is array($escape,$type,$value[,$params])
	//	NB: 'escape' doesn't apply for tempcode-typed-parameters or language-references
	var $bits;

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

	var $pure_lang=false;

	/**
	 * Constructor of tempcode
	 *
	 * @param  ?array			Pair: Code to preexecute, Initialisation seq-parts (NULL: start as empty)
	 */
	function ocp_tempcode($details=NULL)
	{
		if (!isset($details))
		{
			$this->bits=array();
		} else
		{
			$this->bits=$details[1];

			foreach ($this->bits as $seq_part)
			{
				if ($seq_part[1]==TC_SYMBOL)
				{
					switch ($seq_part[2])
					{
						case 'REQUIRE_CSS':
						case 'REQUIRE_JAVASCRIPT':
						case 'FACILITATE_AJAX_BLOCK_CALL':
						case 'JS_TEMPCODE':
						case 'CSS_TEMPCODE':
						case 'SET':
						case 'BLOCK':
						case 'PAGE_LINK':
						case 'LOAD_PAGE':
						case 'LOAD_PANEL':
							$this->bits[]=array(array(),TC_SYMBOL,$seq_part[2]);
							break;
					}
				}
				elseif ($seq_part[1]==TC_DIRECTIVE)
				{
					switch ($seq_part[2])
					{
						case 'FRACTIONAL_EDITABLE':
							$this->bits[]=array(array(),TC_DIRECTIVE,$seq_part[2]);
							break;
					}
				}
			}
		}

		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$this->fresh=true;
			$this->children=array();
		}
	}

	/**
	 * Parse a single symbol from an input stream and append it.
	 *
	 * @param  string				Code string (input stream)
	 * @param  integer			Read position
	 * @param  integer			Length of input string
	 */
	function parse_from(&$code,&$pos,&$len)
	{
		$temp=template_to_tempcode(substr($code,$pos,$len-$pos),0,false,'');
		$this->bits=$temp->bits;
	}

	/**
	 * Decache the object.
	 */
	function decache()
	{
	}

	/**
	 * Scan this Tempcode for anything that needs to be symbol-preprocessed
	 */
	function handle_symbol_preprocessing()
	{
		foreach ($this->bits as $bit)
		{
			if (($bit[1]==TC_DIRECTIVE) || ($bit[1]==TC_SYMBOL))
			{
				if (isset($bit[3]))
				{
					foreach ($bit[3] as $v)
					{
						if (is_object($v))
						{
							$v->handle_symbol_preprocessing();
						}
					}
				}
			}
			handle_symbol_preprocessing($bit,$this->children);
		}
	}

	/**
	 * 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->bits)) || ($this->bits[$at][3]==array()));
	}

	/**
	 * 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)
	 * @param  ?array       Extra escaping (NULL: none)
	 */
	function attach($attach,$avoid_children_merge=false,$escape=NULL)
	{
		if ($attach==='') return;

		$last=count($this->bits)-1;

		global $SIMPLE_ESCAPED;

		if (is_object($attach)) // Consider it another piece of tempcode
		{
			//$done_one=false;
			if (is_null($escape)) $escape=array();

			foreach ($attach->bits as $bit)
			{
				if (($bit[1]==TC_DIRECTIVE) && (($bit[2]=='IF_NON_ADJACENT') || ($bit[2]=='IF_ADJACENT')))
				{
					if (!isset($this->last_attach)) $this->last_attach='';
					if ((($bit[2]=='IF_NON_ADJACENT') && ($this->last_attach!=$bit[3][0]->evaluate()))
						|| (($bit[2]=='IF_ADJACENT') && ($this->last_attach==$bit[3][0]->evaluate())))
					{
						$this->attach($bit[3][1],$avoid_children_merge,$escape);
						$last=count($this->bits)-1;
					}
				} else
				{
					if ($escape!=array()) $bit[0]=array_merge($escape,$bit[0]);

					// Can we add into another string at our edge
					if (/*($done_one) || */($last==-1) || ($bit[1]!=TC_KNOWN) || ($this->bits[$last][1]!=TC_KNOWN) || (/*Commented out due to bug on Quercus($this->bits[$last][0]!=$bit[0]) && */((((array_merge($bit[0],$this->bits[$last][0]))!=$SIMPLE_ESCAPED) && ((array_merge($bit[0],$this->bits[$last][0]))!=array())) || (preg_match('#[&<>"\']#',$bit[2])!=0)))) // No
					{
						$this->bits[]=$bit;
						$last++;
						//$done_one=true;
					} else // Yes
					{
						$this->bits[$last][2].=$bit[2];
					}
				}
			}

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

		} else // Consider it a string
		{
			// Can we add into another string at our edge
			if (is_null($escape)) $escape=array();
			if (($last==-1) || ($this->bits[$last][1]!=TC_KNOWN) || (/*Commented out due to bug on Quercus($this->bits[$last][0]!=$escape) && */(((array_merge($escape,$this->bits[$last][0]))!=$SIMPLE_ESCAPED) && ((array_merge($escape,$this->bits[$last][0]))!=array()) || (preg_match('#[&<>??"\']#',$attach)!=0)))) // No
			{
				$this->bits[]=array($escape,TC_KNOWN,$attach,NULL);
			} else // Yes
			{
				$this->bits[$last][2].=$attach;
			}
		}
	}

	/**
	 * Replace the named parameter with a specific value. Hardly used, but still important.
	 *
	 * @param  string			Named parameter
	 * @param  object			Specific value
	 */
	function singular_bind($parameter,$value)
	{
		foreach ($this->bits as $i=>$bit)
		{
			if ((isset($bit[3])) && (count($bit[3])!=0)) // Has parameters, so we need to decode them
			{
				foreach ($bit[3] as $j=>$param)
				{
					if (is_object($param)) $param->singular_bind($parameter,$value);
					$bit[3][$j]=$param;
				}
			}

			if (($bit[1]!=TC_KNOWN) && ($bit[2]==$parameter))
			{
				array_splice($this->bits,$i,1,$value->bits);
				$cnt=count($value->bits);
				$i+=$cnt;
				continue;
			}

			$this->bits[$i]=$bit;
		}
	}

	/**
	 * 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.
	 *
	 * @param  ?LANGUAGE_NAME	The language to evaluate with (NULL: current users language)
	 * @return string				The assembly result
	 */
	function to_assembly($lang=NULL)
	{
		if (is_null($lang)) $lang=user_lang();

		// Decode all language-codes we are allowed to. Escape anything we are told to
		$count=count($this->bits);
		for ($i=0;$i<$count;++$i)
		{
			$bit=&$this->bits[$i];

			// Decode language codes
			$cant_decode=$bit[1]!=TC_LANGUAGE_REFERENCE;
			if (!$cant_decode)
			{
				if ((array_key_exists(3,$bit)) && (!is_null($bit[3])))
				{
					foreach ($bit[3] as $decode_bit) // We can't decode anything that has complex parameters
					{
						if (is_object($decode_bit)) $cant_decode=true;
					}
				}
			}

			if (!$cant_decode)
			{
				$bit[1]=TC_KNOWN;
				$bit[2]=ecv($lang,array(),TC_LANGUAGE_REFERENCE,$bit[2],is_null($bit[3])?array():$bit[3]);
				$bit[3]=NULL;
			}

			// Escaping
			if ((count($bit[0])!=0) && ($bit[1]==TC_KNOWN))
			{
				if (count($bit[0])!=0) apply_tempcode_escaping($bit[0],$bit[2]);
				$bit[0]=array();
			}
		}

		return serialize($this->bits);
	}

	/**
	 * 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();
		}

		$this->bits=@unserialize($raw_data) OR $this->bits=array();

//		global $PREPROCESSED_BLOCKS;

//		foreach ($this->bits as $bit)
//			if (($bit[1]==TC_SYMBOL) && (isset($PREPROCESSED_BLOCKS[$bit[2]])))
//				handle_symbol_preprocessing($bit);

		$this->codename='';

		return true;
	}

	/**
	 * 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
	 * @param  boolean			Whether we are looking under a loop
	 * @return tempcode			The new bound tempcode object
	 */
	function bind(&$parameters,$codename,$under_loop=false)
	{
//		global $PREPROCESSED_BLOCKS;

		foreach ($parameters as $key=>$val)
		{
			if (is_bool($val)) $parameters[$key]=$val?'1':'0';
		}

		$out=new ocp_tempcode();
		$out->codename=$codename;
		if ($GLOBALS['RECORD_TEMPLATES_TREE'])
		{
			$out->children=isset($this->children)?$this->children:array();
			foreach ($parameters as $key=>$parameter)
			{
				if (is_object($parameter))
				{
					$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);
				}
			}
		}

		$last_param=false;

		foreach ($this->bits as $bit) // Remembering that each tempcode object is divided into bits, and each bit is on the same level, taking the bind parameters. And the parameters themselves take parameters, which exist on the same parameter level (e.g. {!FOO,{BAR}}{FOO} -- {BAR} is bound on the same level as {FOO}
		{
			$bit_1=$bit[1];
//			if (($bit_1==TC_SYMBOL) && (isset($PREPROCESSED_BLOCKS[$bit[2]])))
//				handle_symbol_preprocessing($bit);

			// Parameter binding of the variable as needed
			if ($bit[3])
			{
				if ($bit_1!=TC_DIRECTIVE)
				{
					foreach ($bit[3] as $i=>$param)
					{
						if (is_object($param))
						{
							$bit[3][$i]=$param->bind($parameters,$codename,$under_loop);
						}
					}
				} else
				{
					$send_parameters=&$parameters;
					$bit_2=$bit[2];
					if (($bit_2=='IF_NON_PASSED') || ($bit_2=='IF_PASSED') || ($bit_2=='IF_PASSED_AND_TRUE') || ($bit_2=='IF_NON_PASSED_OR_FALSE'))
					{
						$spec=$bit[3][0]->bits[0][2];
						if (!isset($send_parameters[$spec])) $send_parameters[$spec]=NULL;
					}
					if ($bit_2=='LOOP')
					{
						$bit[3]['vars']=$send_parameters;
					} else
					{
						foreach ($bit[3] as $i=>$param)
						{
							if (is_object($param)) $bit[3][$i]=$param->bind($send_parameters,$codename,$under_loop || ($bit[2]=='LOOP') || ($bit[2]=='IF_PASSED') || ($bit[2]=='IF_PASSED_AND_TRUE')); // We need to be able to keep the parameters for bubbling down later

							if (($i==0) && ($bit_2=='IF_NON_EMPTY')) // To save memory
							{
								if (!$bit[3][$i]->is_empty())
								{
									$bit[3][$i]=make_string_tempcode('1');
								}
							}
						}
						if (isset($GLOBALS['DIRECTIVES_NEEDING_VARS'][$bit[2]]))
						{
							$bit[3]['vars']=$send_parameters;
						}
					}
				}
			}

			// You ain't see me, right!

			// These may need to be bound
			if (($bit_1==TC_PARAMETER) || ($bit_1==TC_DIRECTIVE))
			{
				$bit_2=$bit[2];

				if ($bit_1==TC_PARAMETER)
				{
					$last_param=true;

					if ((!array_key_exists($bit_2,$parameters)) || (is_null($parameters[$bit_2])))
					{
						if ($bit_2=='_GUID')
						{
							$parameters['_GUID']='';

							if (function_exists('debug_backtrace'))
							{
								$trace=debug_backtrace();
								$parameters['_GUID']=array_key_exists(3,$trace)?($trace[3]['function'].'/'.$trace[2]['function']):(array_key_exists(2,$trace)?$trace[2]['function']:$trace[1]['function']);
							}
						} else
						{
							//$out->bits[]=array($bit[0],$bit_1,$bit_2,$bit[3]);
							require_code('site');
							if (!$under_loop) attach_message(do_lang_tempcode('MISSING_TEMPLATE_PARAMETER',$bit_2,$codename),'warn');
							continue;
						}
					}

					// Do the actual parameter lookup: turns the late parameter into an actual known quantity
					$param_value=$parameters[$bit_2];
					$looked_up=(is_array($param_value))?((count($param_value)==0)?'':strval(count($param_value))):$param_value;

					$out->attach($looked_up,false,$bit[0]);
					continue;
				}

				$last_param=false;

				// Handle the pass checking directives
				if (($bit_2=='IF_PASSED') || ($bit_2=='IF_NON_PASSED') || ($bit_2=='IF_PASSED_AND_TRUE') || ($bit_2=='IF_NON_PASSED_OR_FALSE'))
				{
					$out->bits[]=array($bit[0],$bit_1,$bit_2,$bit[3]);
					continue;
				}
			}

			if (($last_param) && ($bit_1==TC_KNOWN)) // Try and attach it, because we may save some space
			{
				$last_param=false;
				$out->attach($bit[2],false,$bit[0]);
				continue;
			}

			// If we have got this far, $bit does not need any special binding itself (although it's parameters [if it has them] may have been)
			$out->bits[]=$bit;

			$last_param=false;
		}

		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($lang=NULL,$_escape=false)
	{
		if (ocp_srv('REQUEST_METHOD')=='HEAD') return '';

		global $HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$RECORD_TEMPLATES_TREE,$SIMPLE_ESCAPED;
		$empty_array=array();

		$before=@ini_get('ocproducts.xss_detect');
		@ini_set('ocproducts.xss_detect','0');

		foreach ($this->bits as $bit)
		{
			$bit_0=$bit[0];
			if ($_escape!==false) array_unshift($bit_0,$_escape);

			if ($bit[1]==TC_KNOWN) // Just pick up the string
			{
				if ($bit_0==$empty_array) echo $bit[2];
				elseif ($bit_0==$SIMPLE_ESCAPED)
				{
					echo str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$bit[2]);
				} else
				{
					apply_tempcode_escaping($bit_0,$bit[2]);
					echo $bit[2];
				}
			} else
			{
				$bit_3=$bit[3];
				if (($bit_3) && ($bit[1]!=TC_DIRECTIVE))
				{
					foreach ($bit_3 as $i=>$decode_bit)
					{
						if (is_object($decode_bit))
						{
							if ($RECORD_TEMPLATES_TREE)
							{
								if (!isset($this->children)) $this->children=array();
								if (!isset($decode_bit->children)) $decode_bit->children=array();
								if (!isset($decode_bit->fresh)) $decode_bit->fresh=false;
								$this->children[]=array($decode_bit->codename,$decode_bit->children,$decode_bit->fresh);
							}
							$bit_3[$i]=$decode_bit->evaluate($lang,false);
						}
					}
				}
				echo ecv($lang,$bit_0,$bit[1],$bit[2],is_null($bit_3)?array():$bit_3);
			}
		}

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

		return '';
	}

	/**
	 * Tests to see if something would evaluate to empty or not
	 *
	 * @return boolean		Whether it is really empty
	 */
	function is_definitely_empty()
	{
		return count($this->bits)==0;
	}

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

	/**
	 * Tests to see if something would evaluate to empty or not
	 *
	 * @return boolean		Whether it is really empty
	 */
	function is_really_empty()
	{
		foreach ($this->bits as $bit)
		{
			if (($bit[1]==TC_KNOWN) || (($bit[1]==TC_SYMBOL) && ($bit[2]=='PAGE_LINK'/*So that we don't do moniker lookup*/))) // Just pick up the string
			{
				if ($bit[2]!='') return false;
			} else
			{
				$bit_3=$bit[3];
				if (($bit_3) && ($bit[1]!=TC_DIRECTIVE))
				{
					foreach ($bit_3 as $i=>$decode_bit)
					{
						if (is_object($decode_bit))
						{
							$bit_3[$i]=$decode_bit->evaluate();
						}
					}
				}
				$out=ecv(function_exists('get_site_default_lang')?get_site_default_lang():'EN',array(),$bit[1],$bit[2],is_null($bit_3)?array():$bit_3);
				if ($out!='') return false;
			}
		}

		return true;
	}

	/**
	 * 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)
	 * @return string				The evaluated thing. Voila, it's all over!
	 */
	function evaluate($lang=NULL,$_escape=false)
	{
		global $HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$RECORD_TEMPLATES_TREE,$SIMPLE_ESCAPED,$EVALUATE_LANG,$EVALUATE_ESCAPE;

		$empty_array=array();
		$out='';

		foreach ($this->bits as $bit)
		{
			$bit_0=$bit[0];
			if ($_escape!==false) array_unshift($bit_0,$_escape);

			if ($bit[1]==TC_KNOWN) // Just pick up the string
			{
				if ($bit_0==$empty_array) $out.=$bit[2];
				elseif ($bit_0==$SIMPLE_ESCAPED)
				{
					$out.=str_replace($HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2,$bit[2]);
				} else
				{
					apply_tempcode_escaping($bit_0,$bit[2]);
					$out.=$bit[2];
				}
			} else
			{
				$bit_3=$bit[3];
				if (($bit_3) && ($bit[1]!=TC_DIRECTIVE))
				{
					foreach ($bit_3 as $i=>$decode_bit)
					{
						if (is_object($decode_bit))
						{
							if ($RECORD_TEMPLATES_TREE)
							{
								if (!isset($this->children)) $this->children=array();
								if (!isset($decode_bit->children)) $decode_bit->children=array();
								if (!isset($decode_bit->fresh)) $decode_bit->fresh=false;
								$this->children[]=array($decode_bit->codename,$decode_bit->children,$decode_bit->fresh);
							}
							$bit_3[$i]=$decode_bit->evaluate($lang,false);
						}
					}
				}

				$out.=ecv($lang,$bit_0,$bit[1],$bit[2],is_null($bit_3)?array():$bit_3);
			}
		}

		return $out;
	}
}
