Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
1328 lines (1162 sloc) 42.5 KB
<?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,'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 (array_reverse($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 (array_reverse($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_DEBUG_MODE'])
{
if (($codename!='tempcode_test') && ($codename!='handle_conflict_resolution') && (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') || ($codename=='POOR_XHTML_WRAPPER') || ($codename=='OCF_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=persistant_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';
$data=@file_get_contents($tcp_path,FILE_BINARY);
if ($data==='') $data=false; // '' needed for PHP4 - weird
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('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'=>'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':
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];
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 (array_key_exists(serialize($param),$LOADED_BLOCKS))
{
$REQUEST_BLOCK_NEST_LEVEL--;
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 'JAVASCRIPT_INCLUDE':
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 'CSS_INCLUDE':
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;
}
}
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 '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':
$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) Makes it incredibly slow if we go over symbols, so we compromise and leave it off ; HEADER.tpl contains SET'd panels to force them to evaluate early, in case anything important is in them like CSS included */)
{
if (isset($bit[3]))
{
// Handle the shift encode directive
if ($bit[2]=='SHIFT_ENCODE')
{
global $SHIFT_VARIABLES;
$key=$bit[3][0]->evaluate();
if (array_key_exists($key,$SHIFT_VARIABLES))
{
if (array_key_exists(2,$bit[3])) $set=$bit[3][1]->evaluate(); else $set='0';
switch ($set)
{
case '0': // Replace
$SHIFT_VARIABLES[$key]=$bit[3][count($bit[3])-1];
break;
case '1': // Ignore
break;
case '2': // Append
$SHIFT_VARIABLES[$key]->attach($bit[3][count($bit[3])-1]);
break;
}
} else
{
$SHIFT_VARIABLES[$key]=$bit[3][count($bit[3])-1];
}
continue;
}
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'))
{
$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')); // 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'))
{
$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;
}
}
Something went wrong with that request. Please try again.