<?php

/***************************************************************************
 *                               template.php
 *                            ------------------
 *   copyright            : (C) 2001 The phpBB Group
 *   email                : support@phpbb.com
 *
 *   phpBB version        : 2.0.x
 *   eXtreme Styles mod   : 2.4.0
 *   Support              : http://www.stsoftware.biz/forum
 *
 *   file revision        : 80
 *   project revision     : 83
 *   last modified        : 12 Mar 2007  10:28:52
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

/**
 *
 * Template class. By Nathan Codding of the phpBB group.
 * The interface was originally inspired by PHPLib templates,
 * and the template file formats are quite similar.
 *
 * eXtreme Styles mod by CyberAlien.
 *
 * IF, ELSEIF, ENDIF tags are backported from phpBB 2.2
 *
 * Documentation for this mod can be found here:
 * http://www.stsoftware.biz/forum
 *
 * Support for eXtreme Styles mod is provided at http://www.stsoftware.biz/forum
 *
 * Thanks to DMaJ007 for idea on how to include some extra tags.
 *
 */

define('XS_SEPARATOR', '.');
define('XS_DIR_CACHE', 'cache');
define('XS_USE_ISSET', '1');

// cache filenames prefix
define('XS_TPL_PREFIX', 'tpl_');
define('XS_TPL_PREFIX2', 'tpl2_');

// templates directory
define('XS_TPL_START', 'templates/');
define('XS_TPL_ANY', '/templates/');

// internal xs mod definitions. do not edit.
define('XS_TAG_NONE', 0);
define('XS_TAG_PHP', 1);
define('XS_TAG_BEGIN', 2);
define('XS_TAG_END', 3);
define('XS_TAG_INCLUDE', 4);
define('XS_TAG_IF', 5);
define('XS_TAG_ELSE', 6);
define('XS_TAG_ELSEIF', 7);
define('XS_TAG_ENDIF', 8);
define('XS_TAG_DEFINE', 9);
define('XS_TAG_UNDEFINE', 10);
define('XS_TAG_BEGINELSE', 11);


class Template {
	var $classname = "Template";

	// variable that holds all the data we'll be substituting into
	// the compiled templates.
	// ...
	// This will end up being a multi-dimensional array like this:
	// $this->_tpldata[block.][iteration#][child.][iteration#][child2.][iteration#][variablename] == value
	// if it's a root-level variable, it'll be like this:
	// $this->vars[varname] == value  or  $this->_tpldata['.'][0][varname] == value
	// array "vars" is added for easier access to data
	var $_tpldata = array('.' => array(0 => array()));
	var $vars;

	// Hash of filenames for each template handle.
	var $files = array();
	var $files_cache = array(); // array of cache files that exists
	var $files_cache2 = array(); // array of cache files (exists or not exists)

	// Root template directory.
	var $root = '';

	// Cache directory (compatible with default cache mod)
	var $cachedir = '';

	// Search/replace for unknown files
	var $cache_search = array();
	var $cache_replace = array();

	// Template root directory (generated by set_rootdir)
	var $tpldir = '';
	var $tpldir_len = 0;

	// Default template directory.
	// If file for default template isn't found file from this template is used.
	var $tpldef = 'subSilver';

	// this will hash handle names to the compiled code for that handle.
	var $compiled_code = array();

	// This will hold the uncompiled code for that handle.
	var $uncompiled_code = array();

	// Cache settings
	var $use_cache = 1;
	var $cache_writable = 1;

	// Auto-compile setting
	var $auto_compile = 1;

	// Current template name
	var $tpl = '';

	// List of replacements. tpl files in this list will be replaced with other tpl files
	// according to configuration in xs.cfg
	var $replace = array();

	// counter for include
	var $include_count = 0;

	// php extension. will be replaced by $phpEx in Template() function unless you disable it there
	var $php = 'php';

	// True if check switches
	var $xs_check_switches = 1;

	// eXtreme Styles variables
	var $xs_started = 0;
	var $xs_version = 8; // number version. internal. do not change.
	var $xs_versiontxt = '2.4.1'; // text version

	// These handles will be parsed if pparse() is executed.
	// Can be used to automatically include header/footer if there is any content.
	var $preparse = '';
	var $postparse = '';

	// subtemplates mod detection
	var $subtemplates = false;

	// style configuration
	var $style_config = array();

	// list of switches that are known typos in some mods.
	// when error checking is enabled these errors will be auto-fixed.
	// format:
	//  array(start_tag, end_tag)
	var $bugs = array(
		// ezportal typo:
		array('fetchpost_row', 'fetch_post_row'),
		// mycalendar 2.2.7 typos:
		array('date_cell', 'date_cells'),
		array('date_row', 'date_rows'),
		// history mod typo:
		array('site_today', 'site_week'),
		);

	/**
	 * Constructor. Installs XS mod on first run or updates it and sets the root dir.
	 */
	function Template($root = '.')
	{
		// setting pointer "vars"
		$this->vars = &$this->_tpldata['.'][0];
		// load configuration
		$this->load_config($root, true);
	}

	/**
	 * Load mod configuration
	 */
	function load_config($root, $edit_db)
	{
		global $board_config, $phpbb_root_path, $phpEx;
		// getting mod version from config and comparing with real data
		$ver = isset($board_config['xs_version']) ? $board_config['xs_version'] : 0;
		// check configuration
		// set config values if there aren't any
		$add = array();
		$del = array();
		$up = array();
		// list of outdated variables
		$outdated = array(
			'xs_versoin',	// was a typo in one of previous versions
			'xs_separator',	// no longer used
			'xs_cache_dir_absolute',	// no longer used
			'xs_cache_dir',	// no longer used
			'xs_use_isset',	// no longer used
			);
		// list of default values
		$default = array(
			'xs_auto_compile'			=> 1,
			'xs_auto_recompile'			=> 1,
			'xs_use_cache'				=> 1,
			'xs_php'					=> $phpEx,
			'xs_def_template'			=> 'subSilver',
			'xs_check_switches'			=> 1,
			'xs_warn_includes'			=> 1,
			'xs_add_comments'			=> 0,
			'xs_ftp_host'				=> '',
			'xs_ftp_login'				=> '',
			'xs_ftp_path'				=> '',
			'xs_downloads_count'		=> '0',
			'xs_downloads_default'		=> '0',
			'xs_shownav'				=> '1',
			'xs_template_time'			=> '0',
			);
		// checking if all variables exist
		foreach($default as $var => $value)
		{
			if(!isset($board_config[$var]))
			{
				$board_config[$var] = $value;
				$add[] = $var;
			}
		}
		// checking if there are any outdated variables that should be deleted
		for($i=0; $i<count($outdated); $i++)
		{
			if(isset($board_config[$outdated[$i]]))
			{
				$del[] = $outdated[$i];
			}
		}
		if(!isset($board_config['xs_version']))
		{
			$board_config['xs_version'] = $this->xs_version;
			$add[] = 'xs_version';
		}
		elseif($board_config['xs_version'] != $this->xs_version)
		{
			$board_config['xs_version'] = $this->xs_version;
			$up[] = 'xs_version';
		}
		// check config
		if(!empty($board_config['xs_auto_recompile']))
		{
			if(!$board_config['xs_auto_compile'])
			{
				$board_config['xs_auto_compile'] = 1;
				if(!in_array('xs_auto_compile', $up) && !in_array('xs_auto_compile', $add))
				{
					$up[] = 'xs_auto_compile';
				}
			}
		}
		// install/upgrade
		if($edit_db && ((count($add) > 0) || (count($up) > 0) || (count($del) > 0)))
		{
			$board_config['xs_template_time'] = time();
			if(!in_array('xs_template_time', $up))
			{
				$up[] = 'xs_template_time';
			}
			global $db;
			if(!empty($db))
			{
				// adding new config values
				for($i=0; $i<count($add); $i++)
				{
					$sql = "INSERT INTO " . CONFIG_TABLE . " (config_name, config_value) VALUES ('" . $add[$i] . "', '" . str_replace('\\\'', '\'\'', addslashes($board_config[$add[$i]])) . "')";
					$db->sql_query($sql);
				}
				// removing old configuration variables that aren't used
				for($i=0; $i<count($del); $i++)
				{
					$sql = "DELETE FROM " . CONFIG_TABLE . " WHERE config_name='" . $del[$i] . "'";
					$db->sql_query($sql);
				}
				// updating variables that should be overwritten
				for($i=0; $i<count($up); $i++)
				{
					$sql = "UPDATE " . CONFIG_TABLE . " SET config_value='" . str_replace('\\\'', '\'\'', addslashes($board_config[$up[$i]])) . "' WHERE config_name='" . $up[$i] . "'";
					$db->sql_query($sql);
				}
				// recache config table for cat_hierarchy 2.1.0
				global $config;
				if(isset($config->data) && $config->data === $board_config && isset($config->data['mod_cat_hierarchy']))
				{
					$config->read(true);
				}
			}
		}
		$this->php = $board_config['xs_php'];
		$this->tpldef = $board_config['xs_def_template'];
		$this->use_cache = $board_config['xs_use_cache'];
		$this->auto_compile = $board_config['xs_auto_compile'];
		$this->xs_check_switches = $board_config['xs_check_switches'];
		$this->cache_search = array('.', '\\', '/', '_tpl');
		$this->cache_replace = array('_', XS_SEPARATOR, XS_SEPARATOR, '.'.$this->php);
		$old_root = $this->root;
		$this->set_rootdir($root);
		if(!empty($this->tpl))
		{
			$this->load_replacements($this->tpldir . $this->tpl . '/xs.cfg');
		}
		if($old_root !== $this->root)
		{
			$this->clear_files();
		}
	}

	/**
	 * Sets the template root directory for this Template object.
	 */
	function set_rootdir($dir)
	{
		global $board_config, $phpbb_root_path;
		if (!@is_dir($dir))
		{
			return false;
		}
		$dir = str_replace('\\', '/', $dir);
		// creating absolute path for cache
		$this->cachedir = $phpbb_root_path . XS_DIR_CACHE . '/';
		// creating absolute path for current template and root dir
		$this->tpldir = $phpbb_root_path . 'templates/';
		$this->tpldir_len = strlen($this->tpldir);
		$this->root = $dir;
		$this->tpl = $this->template_name($dir);
		// check configuration
		$this->get_config();
		// check subtemplates mod
		$sub_templates_cfg = $this->root . '/sub_templates.cfg';
		if(defined('LANG_EXTEND_DONE') && @file_exists($sub_templates_cfg))
		{
			$this->subtemplates = true;
		}
		return true;
	}

	/**
	 * Destroys this template object. Should be called when you're done with it, in order
	 * to clear out the template data so you can load/parse a new template set.
	 */
	function destroy()
	{
		$this->_tpldata = array('.' => array(0 => array()));
		$this->vars = &$this->_tpldata['.'][0];
		$this->xs_started = 0;
	}

	/**
	 * Clears list of compiled files.
	 */
	function clear_files()
	{
		$this->files = array();
		$this->files_cache = array();
		$this->files_cache2 = array();
		$this->compiled_code = array();
		$this->uncompiled_code = array();
	}

	/**
	 * Loads replacements from .cfg file
	 */
	function load_replacements($file)
	{
		if(@file_exists($file))
		{
			$replace = array();
			@include($file);
			$this->replace = array_merge($this->replace, $replace);
		}
	}

	/**
	 * Extracts template name from path
	 */
	function template_name($dir)
	{
		$tpl = XS_TPL_ANY; // can start at any position
		$tpl_null = XS_TPL_START; // can start only at zero position
		// searching for 'templates/' and removing everything before it
		$pos = strpos($dir, $tpl);
		if($pos === false)
		{
			if(substr($dir, 0, strlen($tpl_null)) !== $tpl_null)
			{
				return '';
			}
			$str = substr($dir, strlen($tpl_null), strlen($dir));
		}
		else
		{
			$str = substr($dir, $pos + strlen($tpl), strlen($dir));
		}
		// searching for one more 'templates/'
		// that can happen if full path is like /home/some_dude/templates/phpbb/templates/subSilver/
		$dir = $this->template_name($str);
		if(!$dir)
		{
			$dir = $str;
		}
		if(strpos($str, $tpl) !== false)
		{
			$dir = $this->template_name($str);
		}
		// check for another subdirectory
		$pos = strpos($dir, '/');
		if($pos)
		{
			$dir = substr($dir, 0, $pos);
		}
		return $dir;
	}

	function subtemplates_make_filename($filename)
	{
		global $HTTP_GET_VARS, $HTTP_POST_VARS, $db, $board_config, $images, $theme;
		global $sub_template_key_image, $sub_templates;
		global $tree;

		// initiate the sub-template image pack that will be use
		$sub_template_key_image = POST_CAT_URL . '0';

		// Check if sub_templates are defined for this theme
		if ( $board_config['version'] > '.0.5' )
		{
			$sub_templates_cfg = @phpbb_realpath($this->root . '/sub_templates.cfg');
		}
		else
		{
			$sub_templates_cfg = $this->root . '/sub_templates.cfg';
		}
		@include($sub_templates_cfg);
		if ( isset($sub_templates) )
		{
			// search an id
			$cat_id = 0;
			$forum_id = 0;
			$topic_id = 0;
			$post_id = 0;

			if ( !defined('IN_PRIVMSG') && ( isset($HTTP_GET_VARS[POST_POST_URL]) || isset($HTTP_POST_VARS[POST_POST_URL]) ) )
			{
				$post_id = isset($HTTP_GET_VARS[POST_POST_URL]) ? intval($HTTP_GET_VARS[POST_POST_URL]) : intval($HTTP_POST_VARS[POST_POST_URL]);
			}

			if ( isset($HTTP_GET_VARS[POST_TOPIC_URL]) || isset($HTTP_POST_VARS[POST_TOPIC_URL]) )
			{
				$topic_id = intval($HTTP_GET_VARS[POST_TOPIC_URL]) ? intval($HTTP_GET_VARS[POST_TOPIC_URL]) : intval($HTTP_POST_VARS[POST_TOPIC_URL]);
			}

			if ( isset($HTTP_GET_VARS[POST_FORUM_URL]) || isset($HTTP_POST_VARS[POST_FORUM_URL]) )
			{
				$forum_id = isset($HTTP_GET_VARS[POST_FORUM_URL]) ? intval($HTTP_GET_VARS[POST_FORUM_URL]) : intval($HTTP_POST_VARS[POST_FORUM_URL]);
			}

			if ( isset($HTTP_GET_VARS[POST_CAT_URL]) || isset($HTTP_POST_VARS[POST_CAT_URL]) )
			{
				$cat_id = isset($HTTP_GET_VARS[POST_CAT_URL]) ? intval($HTTP_GET_VARS[POST_CAT_URL]) : intval($HTTP_POST_VARS[POST_CAT_URL]);
			}

			if ( isset($HTTP_GET_VARS['selected_id']) || isset($HTTP_POST_VARS['selected_id']) )
			{
				$selected_id = isset($HTTP_GET_VARS['selected_id']) ? $HTTP_GET_VARS['selected_id'] : $HTTP_POST_VARS['selected_id'];
				$type = substr($selected_id, 0, 1);
				$id = intval(substr($selected_id, 1));
				if (!empty($id)) switch ($type)
				{
					case POST_CAT_URL:
						$cat_id = $id;
						break;
					case POST_FORUM_URL:
						$forum_id = $id;
						break;
					case POST_TOPIC_URL:
						$topic_id = $id;
						break;
					case POST_POST_URL:
						if ( !defined('IN_PRIVMSG') )
						{
							$post_id = $id;
							break;
						}
					default:
						break;
				}
			}

			// find the forum
			if ( ($forum_id <= 0) && ($cat_id <= 0) )
			{
				if ($post_id > 0)
				{
					$sql = "SELECT * FROM " . POSTS_TABLE . " WHERE post_id=$post_id";			
					if ( !($result = $db->sql_query($sql)) )
					{
						message_die(GENERAL_ERROR, 'Wasn\'t able to access posts', '', __LINE__, __FILE__, $sql);
					}
					if ( $row = $db->sql_fetchrow($result) )
					{
						$forum_id = $row['forum_id'];
					}
				}

				if ($topic_id > 0)
				{
					$sql = "SELECT * FROM " . TOPICS_TABLE . " WHERE topic_id=$topic_id";			
					if ( !($result = $db->sql_query($sql)) )
					{
						message_die(GENERAL_ERROR, 'Wasn\'t able to access topics', '', __LINE__, __FILE__, $sql);
					}
					if ( $row = $db->sql_fetchrow($result) )
					{
						$forum_id = $row['forum_id'];
					}
				}
			}

			// is the categories hierarchy v 2 installed ?
			$cat_hierarchy = function_exists('get_auth_keys');

			// get the ids (forums and cats)
			$fids = array();
			if (!$cat_hierarchy)
			{
				if ($forum_id > 0)
				{
					// add the forum_id
					$fids[] = POST_FORUM_URL . $forum_id;

					// get the cat_id
					$sql = "SELECT * FROM " . FORUMS_TABLE . " WHERE forum_id=$forum_id";
					if ( !($result = $db->sql_query($sql)) )
					{
						message_die(GENERAL_ERROR, 'Wasn\'t able to access forums', '', __LINE__, __FILE__, $sql);
					}
					if ( $row = $db->sql_fetchrow($result) )
					{
						$cat_id = $row['cat_id'];
					}
				}

				// add the cat_id
				if ($cat_id > 0)
				{
					$fids[] = POST_CAT_URL . $cat_id;
				}

				// add the root level
				$fids[] = 'Root';
			}
			else
			{
				// categories hierarchy v 2 compliancy
				$cur = 'Root';
				if ($forum_id > 0)
				{
					$cur = POST_FORUM_URL . $forum_id;
				}
				else if ($cat_id > 0)
				{
					$cur = POST_CAT_URL . $cat_id;
				}

				// add start
				$fids[] = $cur;
				while ( ($cur != 'Root') && !empty($cur) )
				{
					// get parent level
					$cur = (isset($tree['main'][ $tree['keys'][$cur] ])) ? $tree['main'][ $tree['keys'][$cur] ] : 'Root';

					// add the parent level
					if ( !empty($cur) )
					{
						$fids[] = $cur;
					}
				}
			}

			// search if this file is part of a sub-template
			$sub_tpl_file = '';
			$sub_css_file = '';
			$sub_img_file = '';
			$sub_img_path = '';
			$template_path = 'templates/';
			$template_name = substr( $this->root, strpos($this->root, $template_path) + strlen($template_path) );
			$real_root = $this->root;
			if ( $board_config['version'] > '.0.5' )
			{
				$real_root = @phpbb_realpath($this->root);
			}
			if (substr($filename, 0, 1) != '/')
			{
				$found = false;
				$num_fids = count($fids);
				for ($i = 0; !$found && ($i < $num_fids); $i++)
				{
					$key = $fids[$i];

					// convert root into c0 category
					if ( ($key == 'Root') || empty($key) )
					{
						$key = POST_CAT_URL . '0';
					}

					if ( isset($sub_templates[$key]) )
					{
						// get the sub-template path
						$current_template_path = $template_path . $template_name . '/' . $sub_templates[$key]['dir'];
						$root_template_path = $real_root . '/' . $sub_templates[$key]['dir'];

						// set the filename
						if ( empty($sub_tpl_file) && file_exists($root_template_path . '/' . $filename) )
						{
							$found = true;
							$sub_tpl_file = $sub_templates[$key]['dir'] . '/' . $filename;
							break;
						}
					}
				}

				// set the css file name
				$found = false;
				$num_fids = count($fids);
				for ($i = 0; !$found && ($i < $num_fids); $i++)
				{
					$key = $fids[$i];

					// convert root into c0 category
					if ( ($key == 'Root') || empty($key) )
					{
						$key = POST_CAT_URL . '0';
					}

					if ( isset($sub_templates[$key]) )
					{
						// get the sub-template path
						$current_template_path = $template_path . $template_name . '/' . $sub_templates[$key]['dir'];
						$root_template_path = $real_root . '/' . $sub_templates[$key]['dir'];
						if ( empty($sub_css_file) && isset($sub_templates[$key]['head_stylesheet']) && file_exists($root_template_path . '/' . $sub_templates[$key]['head_stylesheet']) )
						{
							$found = true;
							$sub_css_file = $sub_templates[$key]['dir'] . '/' . $sub_templates[$key]['head_stylesheet'];
							break;
						}
					}
				}

				// set the img file name
				$found = false;
				$num_fids = count($fids);
				for ($i = 0; !$found && ($i < $num_fids); $i++)
				{
					$key = $fids[$i];

					// convert root into c0 category
					if ( ($key == 'Root') || empty($key) )
					{
						$key = POST_CAT_URL . '0';
					}

					if ( isset($sub_templates[$key]) )
					{
						// get the sub-template path
						$current_template_path = $template_path . $template_name . '/' . $sub_templates[$key]['dir'];
						$root_template_path = $real_root . '/' . $sub_templates[$key]['dir'];
						if ( isset($sub_templates[$key]['imagefile']) && file_exists($root_template_path . '/' . $sub_templates[$key]['imagefile']) )
						{
							$sub_img_path = $sub_templates[$key]['dir'];
							$sub_img_file = $sub_templates[$key]['imagefile'];

							// send back the lowest level of the images file
							$found = true;
							$sub_template_key_image = $key;
							break;
						}
					}
				}
			}

			// set the tpl file
			if ( !empty($sub_tpl_file) )
			{
				$filename = $sub_tpl_file;
			}

			// set the css file
			if ( !empty($sub_css_file) )
			{
				$theme['head_stylesheet'] = $sub_css_file;
			}

			// get the root level images
			$key = POST_CAT_URL . '0';
			if ( isset($sub_templates[$key]) )
			{
				// get the sub-template path
				$current_template_path = $template_path . $template_name . '/' . $sub_templates[$key]['dir'];
				$root_template_path = $real_root . '/' . $sub_templates[$key]['dir'];
				if ( isset($sub_templates[$key]['imagefile']) && file_exists($root_template_path . '/' . $sub_templates[$key]['imagefile']) )
				{
					$sav_images = $images;
					$images = array();
					@include($root_template_path . '/' . $sub_templates[$key]['imagefile']);
					$img_lang = ( file_exists($root_template_path . '/images/lang_' . $board_config['default_lang']) ) ? $board_config['default_lang'] : 'english';
					foreach($images as $key => $value)
					{
						if ( !is_array($value) )
						{
							$images[$key] = str_replace('{LANG}', 'lang_' . $img_lang, $value);
						}
						$sav_images[$key] = $images[$key];
					}
					$images = $sav_images;
					$sav_images = array();
				}
			}

			// get the current images
			if ( !empty($sub_template_key_image) && ($sub_template_key_image != POST_CAT_URL . '0') )
			{
				$key = $sub_template_key_image;

				// get the sub-template path
				$current_template_path = $template_path . $template_name . '/' . $sub_templates[$key]['dir'];
				$root_template_path = $real_root . '/' . $sub_templates[$key]['dir'];
				if ( isset($sub_templates[$key]['imagefile']) && file_exists($root_template_path . '/' . $sub_templates[$key]['imagefile']) )
				{
					$sav_images = $images;
					$images = array();
					@include($root_template_path . '/' . $sub_templates[$key]['imagefile']);
					$img_lang = ( file_exists($root_template_path . '/images/lang_' . $board_config['default_lang']) ) ? $board_config['default_lang'] : 'english';
					foreach($images as $key => $value)
					{
						if ( !is_array($value) )
						{
							$images[$key] = str_replace('{LANG}', 'lang_' . $img_lang, $value);
						}
						$sav_images[$key] = $images[$key];
					}
					$images = $sav_images;
					$sav_images = array();
				}
			}
		}
		return $filename;
	}

	/**
	 * Generates a full path+filename for the given filename, which can either
	 * be an absolute name, or a name relative to the rootdir for this Template
	 * object.
	 */
	function make_filename($filename, $xs_include = false)
	{
		if($this->subtemplates)
		{
			$filename = $this->subtemplates_make_filename($filename);
		}
		// Check replacements list
		if(!$xs_include && isset($this->replace[$filename]))
		{
			$filename = $this->replace[$filename];
		}
		// Check if it's an absolute or relative path.
		if ((substr($filename, 0, 1) !== '/') && (substr($filename, 1, 1) !== ':'))
		{
       		return $this->root . '/' . $filename;
		}
		else
		{
			return str_replace('\\', '/', $filename);
		}
	}

	/**
	 * Converts template filename to cache filename.
	 * Returns empty string if non-cachable (for tpl files outside of root dir).
	 * $file should be absolute filename
	 */
	function make_filename_cache($file)
	{
		$str = str_replace($this->cache_search, $this->cache_replace, $file);
		if(substr($file, 0, $this->tpldir_len) !== $this->tpldir || empty($this->tpl))
		{
			return $this->cachedir . XS_TPL_PREFIX2 . $str;
		}
		// removing not needed part
		$file = substr($file, $this->tpldir_len, strlen($file));
		// creating filename
		return $this->cachedir . XS_TPL_PREFIX . str_replace($this->cache_search, $this->cache_replace, $file);
	}

	/**
	 * Sets the template filenames for handles. $filename_array
	 * should be a hash of handle => filename pairs.
	 */
	function set_filenames($filename_array)
	{
		if (!is_array($filename_array))
		{
			return false;
		}

		foreach($filename_array as $handle => $filename)
		{
			$this->set_filename($handle, $filename);
		}

		return true;
	}


	/**
	 * Assigns template filename for handle.
	 */
	function set_filename($handle, $filename, $xs_include = false, $quiet = false)
	{
		global $board_config;
		$can_cache = $this->use_cache;
		if(strpos($filename, '..') !== false)
		{
			$can_cache = false;
		}
		$this->files[$handle] = $this->make_filename($filename, $xs_include);
		$this->files_cache[$handle] = '';
		$this->files_cache2[$handle] = '';
		// check if we are in admin control panel and override extreme styles mod controls if needed
		if(defined('XS_ADMIN_OVERRIDE') && XS_ADMIN_OVERRIDE === true && @function_exists('xs_admin_override'))
		{
			xs_admin_override();
		}
		// checking if we have valid filename
		if(!$this->files[$handle])
		{
			if($xs_include || $quiet)
			{
				return false;
			}
			else
			{
				die("Template->make_filename(): Error - invalid template $filename");
			}
		}
		// creating cache filename
		if($can_cache)
		{
			$this->files_cache2[$handle] = $this->make_filename_cache($this->files[$handle]);
			if(@file_exists($this->files_cache2[$handle]))
			{
				$this->files_cache[$handle] = $this->files_cache2[$handle];
			}
		}
		// checking if tpl and/or php file exists
		if(empty($this->files_cache[$handle]) && !@file_exists($this->files[$handle]))
		{
			// trying to load alternative filename (usually subSilver)
			if(!empty($this->tpldef) && !empty($this->tpl) && ($this->tpldef !== $this->tpl))
			{
				$this->files[$handle] = '';
				// save old configuration
				$root = $this->root;
				$tpl_name = $this->tpl;
				// set temporary configuration
				$this->root = $this->tpldir . $this->tpldef;
				$this->tpl = $this->tpldef;
				// recursively run set_filename
				$res = $this->set_filename($handle, $filename, $xs_include, $quiet);
				// restore old configuration
				$this->root = $root;
				$this->tpl = $tpl_name;
				return $res;
			}
			if($quiet)
			{
				return false;
			}
			if($xs_include)
			{
				if($board_config['xs_warn_includes'])
				{
					die('Template->make_filename(): Error - included template file not found: ' . $filename);
				}
				return false;
			}
			else
			{
				die('Template->make_filename(): Error - template file not found: ' . $filename);
			}
		}
		// checking if we should recompile cache
		if(!empty($this->files_cache[$handle]) && !empty($board_config['xs_auto_recompile']))
		{
			$cache_time = @filemtime($this->files_cache[$handle]);
			if(@filemtime($this->files[$handle]) > $cache_time || $board_config['xs_template_time'] > $cache_time)
			{	
				// file was changed. don't use cache file (will be recompled if configuration allowes it)
				$this->files_cache[$handle] = '';
			}
		}
		return true;
	}

	/**
	 * includes file or executes code
	 */
	function execute($filename, $code, $handle)
	{
		global $lang, $theme, $board_config;
		$template = $theme['template_name'];
		global $$template;
		$theme_info = &$$template;
		if($board_config['xs_add_comments'] && $handle)
		{
			echo '<!-- template ', $this->files[$handle], ' start -->';
		}
		if($filename)
		{
			include($filename);
		}
		else
		{
			eval($code);
		}
		if($board_config['xs_add_comments'] && $handle)
		{
			echo '<!-- template ', $this->files[$handle], ' end -->';
		}
		return true;
	}

	/**
	 * Load the file for the handle, compile the file,
	 * and run the compiled code. This will print out
	 * the results of executing the template.
	 */
	function pparse($handle)
	{
		global $board_config;
		// parsing header if there is one
		if($this->preparse || $this->postparse)
		{
			$preparse = $this->preparse;
			$postparse = $this->postparse;
			$this->preparse = '';
			$this->postparse = '';
			if($preparse)
			{
				$this->pparse($preparse);
			}
			if($postparse)
			{
				$str = $handle;
				$handle = $postparse;
				$this->pparse($str);
			}
		}
		// checking if handle exists
		if (empty($this->files[$handle]) && empty($this->files_cache[$handle]))
		{
			die("Template->loadfile(): No files found for handle $handle");
		}
		$this->xs_startup();
		$force_recompile = empty($this->uncompiled_code[$handle]) ? false : true;
		// checking if php file exists.
		if (!empty($this->files_cache[$handle]) && !$force_recompile)
		{
			// php file exists - running it instead of tpl
			$this->execute($this->files_cache[$handle], '', $handle);
			return true;
		}
		if (!$this->loadfile($handle))
		{
			die("Template->pparse(): Couldn't load template file for handle $handle");
		}
		// actually compile the template now.
		if (empty($this->compiled_code[$handle]))
		{
			// Actually compile the code now.
			if(!empty($this->files_cache2[$handle]) && empty($this->files_cache[$handle]) && !$force_recompile)
			{
				$this->compiled_code[$handle] = $this->compile2($this->uncompiled_code[$handle], $handle, $this->files_cache2[$handle]);
			}
			else
			{
				$this->compiled_code[$handle] = $this->compile2($this->uncompiled_code[$handle], '', '');
			}
		}
		// Run the compiled code.
		if (empty($this->files_cache[$handle]) || $force_recompile)
		{
			$this->execute('', $this->compiled_code[$handle], $handle);
		}
		else
		{
			$this->execute($this->files_cache[$handle], '', $handle);
		}
		return true;
	}

	/**
	 * Precompile file
	 */
	function precompile($template, $filename)
	{
		global $precompile_num, $board_config;
		if(empty($precompile_num))
		{
			$precompile_num = 0;
		}
		$precompile_num ++;
		$handle = 'precompile_' . $precompile_num;
		// save old configuration
		$root = $this->root;
		$tpl_name = $this->tpl;
		$old_config = $this->use_cache;
		$old_autosave = $this->auto_compile;
		// set temporary configuration
		$this->root = $this->tpldir . $template;
		$this->tpl = $template;
		$this->use_cache = 1;
		$this->auto_compile = 1;
		// set filename
		$res = $this->set_filename($handle, $filename, true, true);
		if(!$res || !$this->files_cache2[$handle])
		{
			$this->root = $root;
			$this->tpl = $tpl_name;
			$this->use_cache = $old_config;
			$this->auto_compile = $old_autosave;
			return false;
		}
		$this->files_cache[$handle] = '';
		// load template
		$res = $this->loadfile($handle);
		if(!$res || empty($this->uncompiled_code[$handle]))
		{
			$this->root = $root;
			$this->tpl = $tpl_name;
			$this->use_cache = $old_config;
			$this->auto_compile = $old_autosave;
			return false;
		}
		// compile the code
		$this->compile2($this->uncompiled_code[$handle], $handle, $this->files_cache2[$handle]);
		// restore confirugation
		$this->root = $root;
		$this->tpl = $tpl_name;
		$this->use_cache = $old_config;
		$this->auto_compile = $old_autosave;
		return true;
	}

	/**
	 * Inserts the uncompiled code for $handle as the
	 * value of $varname in the root-level. This can be used
	 * to effectively include a template in the middle of another
	 * template.
	 * Note that all desired assignments to the variables in $handle should be done
	 * BEFORE calling this function.
	 */
	function assign_var_from_handle($varname, $handle)
	{
		ob_start();
		$res = $this->pparse($handle);
		$this->vars[$varname] = ob_get_contents();
		ob_end_clean();
		return $res;
	}

	/**
	 * Block-level variable assignment. Adds a new block iteration with the given
	 * variable assignments. Note that this should only be called once per block
	 * iteration.
	 */
	function assign_block_vars($blockname, $vararray)
	{
		if (strstr($blockname, '.'))
		{
			// Nested block.
			$blocks = explode('.', $blockname);
			$blockcount = sizeof($blocks) - 1;

			$str = &$this->_tpldata; 
			for($i = 0; $i < $blockcount; $i++) 
			{ 
				$str = &$str[$blocks[$i].'.']; 
				$str = &$str[sizeof($str)-1]; 
			} 
			// Now we add the block that we're actually assigning to. 
			// We're adding a new iteration to this block with the given 
			//	variable assignments. 
			$str[$blocks[$blockcount].'.'][] = $vararray;
		}
		else
		{
			// Top-level block.
			// Add a new iteration to this block with the variable assignments
			// we were given.
			$this->_tpldata[$blockname.'.'][] = $vararray;
		}

		return true;
	}

	/**
	 * Root-level variable assignment. Adds to current assignments, overriding
	 * any existing variable assignment with the same name.
	 */
	function assign_vars($vararray)
	{
		foreach($vararray as $key => $val)
		{
			$this->vars[$key] = $val;
		}
		return true;
	}

	/**
	 * Root-level variable assignment. Adds to current assignments, overriding
	 * any existing variable assignment with the same name.
	 */
	function assign_var($varname, $varval)
	{
		$this->vars[$varname] = $varval;

		return true;
	}

	/**
	 * If not already done, load the file for the given handle and populate
	 * the uncompiled_code[] hash with its code. Do not compile.
	 */
	function loadfile($handle)
	{
		global $board_config;
		// If cached file exists do nothing - it will be included via include()
		if(!empty($this->files_cache[$handle]))
		{
			return true;
		}

		// If the file for this handle is already loaded and compiled, do nothing.
		if (!empty($this->uncompiled_code[$handle]))
		{
			return true;
		}

		// If we don't have a file assigned to this handle, die.
		if (empty($this->files[$handle]))
		{
			die("Template->loadfile(): No file specified for handle $handle");
		}

		$filename = $this->files[$handle];

		$str = implode('', @file($filename));
		if (empty($str))
		{
			die("Template->loadfile(): File $filename for handle $handle is empty");
		}

		$this->uncompiled_code[$handle] = $str;

		return true;
	}



	/**
	 * Generates a reference to the given variable inside the given (possibly nested)
	 * block namespace. This is a string of the form:
	 * ' . $this->_tpldata['parent.'][$_parent_i]['$child1.'][$_child1_i]['$child2.'][$_child2_i]...['varname'] . '
	 * It's ready to be inserted into an "echo" line in one of the templates.
	 * NOTE: expects a trailing "." on the namespace.
	 */
	function generate_block_varref($namespace, $varname, $use_isset = true)
	{
		// Strip the trailing period.
		$namespace = substr($namespace, 0, strlen($namespace) - 1);

		// Get a reference to the data block for this namespace.
		$varref = $this->generate_block_data_ref($namespace, true);
		// Prepend the necessary code to stick this in an echo line.

		// Append the variable reference.
		$varref .= '[\'' . $varname . '\']';

		if($use_isset)
		{
			$varref = '<'.'?php echo isset(' . $varref . ') ? ' . $varref . ' : \'\'; ?'.'>';
		}
		else
		{
			$varref = '<'.'?php echo ' . $varref . '; ?'.'>';
		}

		return $varref;

	}


	/**
	 * Generates a reference to the array of data values for the given
	 * (possibly nested) block namespace. This is a string of the form:
	 * $this->_tpldata['parent.'][$_parent_i]['$child1.'][$_child1_i]['$child2.'][$_child2_i]...['$childN.']
	 *
	 * If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above.
	 * NOTE: does not expect a trailing "." on the blockname.
	 */
	function generate_block_data_ref($blockname, $include_last_iterator, $defop = false)
	{
		// Get an array of the blocks involved.
		$blocks = explode('.', $blockname);
		$blockcount = sizeof($blocks) - 1;
		if($defop)
		{
			$varref = '$this->_tpldata[\'DEFINE\']';
			// Build up the string with everything but the last child.
			for ($i = 0; $i < $blockcount; $i++)
			{
				$varref .= "['" . $blocks[$i] . ".'][\$" . $blocks[$i] . '_i]';
			}
			// Add the block reference for the last child.
			$varref .= "['" . $blocks[$blockcount] . ".']";
			// Add the iterator for the last child if requried.
			if ($include_last_iterator)
			{
				$varref .= '[$' . $blocks[$blockcount] . '_i]';
			}
			return $varref;
		}
		if($include_last_iterator)
		{
			return '$'. $blocks[$blockcount]. '_item';
		}
		else
		{
			return '$'. $blocks[$blockcount-1]. '_item[\''. $blocks[$blockcount]. '.\']';
		}
	}

	function compile_code($filename, $code, $use_isset = false)
	{
		//	$filename - file to load code from. used if $code is empty
		//	$code - tpl code
		//	$use_isset - if false then compiled code looks more beautiful and easier
		//      to understand and it adds error_reporting() to supress php warnings.
		//      if true then isset() is used to check variables instead of supressing
		//	    php warnings. note: for extreme styles mod 2.x it works only for
		//		block variables and for usual variables its always true.

		// load code from file
		if(!$code && !empty($filename))
		{
			$code = @implode('', @file($filename));
		}

		// Replace phpBB 2.2 <!-- (END)PHP --> tags
		$search = array('<!-- PHP -->', '<!-- ENDPHP -->');
		$replace = array('<'.'?php ', ' ?'.'>');
		$code = str_replace($search, $replace, $code);

		// Break it up into lines and put " -->" back.
		$code_lines = explode(' -->', $code);
		$count = count($code_lines);
		for ($i = 0; $i < ($count - 1); $i++)
		{
			$code_lines[$i] .= ' -->';
		}

		$block_nesting_level = 0;
		$block_names = array();
		$block_names[0] = ".";
		$block_items = array();
		$count_if = 0;

		// prepare array for compiled code
		$compiled = array();
		$count_bugs = count($this->bugs);

		// array of switches
		$sw = array();

		// replace all short php tags
		$new_code = array();
		$line_count = count($code_lines);
		for($i=0; $i<$line_count; $i++)
		{
			$line = $code_lines[$i];
			$pos = strpos($line, '<?');
			if($pos === false)
			{
				$new_code[] = $line;
				continue;
			}
			if(substr($line, $pos, 5) === '<?php')
			{
				// valid php tag. skip it
				$new_code[] = substr($line, 0, $pos + 5);
				$code_lines[$i] = substr($line, $pos + 5);
				$i --;
				continue;
			}
			// invalid php tag
			$new_code[] = substr($line, 0, $pos) . '<?php echo \'<?\'; ?>';
			$code_lines[$i] = substr($line, $pos + 2);
			$i --;
		}
		$code_lines = $new_code;

		// main loop
		$line_count = count($code_lines);
		for($i=0; $i<$line_count; $i++)
		{
			$line = $code_lines[$i];
			// reset keyword type
			$keyword_type = XS_TAG_NONE;
			// check if we have valid keyword in current line
			$pos1 = strpos($line, '<!-- ');
			if($pos1 === false)
			{
				// no keywords in this line
				$compiled[] = $this->_compile_text($line, $use_isset);
				continue;
			}
			// find end of html comment
			$pos2 = strpos($line, ' -->', $pos1);
			if($pos2 !== false)
			{
				// find end of keyword in comment
				$pos3 = strpos($line, ' ', $pos1 + 5);
				if($pos3 !== false && $pos3 <= $pos2)
				{
					$keyword = substr($line, $pos1 + 5, $pos3 - $pos1 - 5);
					// check keyword against list of supported keywords. case-sensitive
					if($keyword === 'BEGIN')
					{
						$keyword_type = XS_TAG_BEGIN;
					}
					elseif($keyword === 'END')
					{
						$keyword_type = XS_TAG_END;
					}
					elseif($keyword === 'INCLUDE')
					{
						$keyword_type = XS_TAG_INCLUDE;
					}
					elseif($keyword === 'IF')
					{
						$keyword_type = XS_TAG_IF;
					}
					elseif($keyword === 'ELSE')
					{
						$keyword_type = XS_TAG_ELSE;
					}
					elseif($keyword === 'ELSEIF')
					{
						$keyword_type = XS_TAG_ELSEIF;
					}
					elseif($keyword === 'ENDIF')
					{
						$keyword_type = XS_TAG_ENDIF;
					}
					elseif($keyword === 'DEFINE')
					{
						$keyword_type = XS_TAG_DEFINE;
					}
					elseif($keyword === 'UNDEFINE')
					{
						$keyword_type = XS_TAG_UNDEFINE;
					}
					elseif($keyword === 'BEGINELSE')
					{
						$keyword_type = XS_TAG_BEGINELSE;
					}
				}
			}
			if(!$keyword_type)
			{
				// not valid keyword. process the rest of line
				$compiled[] = $this->_compile_text(substr($line, 0, $pos1 + 4), $use_isset);
				$code_lines[$i] = substr($line, $pos1 + 4);
				$i --;
				continue;
			}
			// remove code before keyword
			if($pos1 > 0)
			{
				$compiled[] = $this->_compile_text(substr($line, 0, $pos1), $use_isset);
			}
			// remove keyword
			$keyword_str = substr($line, $pos1, $pos2 - $pos1 + 4);
			$params_str = $pos2 == $pos3 ? '' : substr($line, $pos3 + 1, $pos2 - $pos3 - 1);
			$code_lines[$i] = substr($line, $pos2 + 4);
			$i--;
			// Check keywords

			/*
			* <!-- BEGIN -->
			*/
			if($keyword_type == XS_TAG_BEGIN)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				// get variable name
				if($num_params == 1)
				{
					$var = $params[0];
				}
				elseif($num_params == 2)
				{
					if($params[0] === '')
					{
						$var = $params[1];
					}
					elseif($params[1] === '')
					{
						$var = $params[0];
					}
					else
					{
						// invalid tag
						$compiled[] = $keyword_str;
						continue;
					}
				}
				else
				{
					// invalid tag
					$compiled[] = $keyword_str;
					continue;
				}
				// check variable for matching end
				if($this->xs_check_switches)
				{
					$found = 0;
					$str = '<!-- END ' . $var . ' -->';
					for ($j = $i+1; ($j < $line_count) && !$found; $j++)
					{
						$pos = strpos($code_lines[$j], $str);
						if($pos !== false)
						{
							$found = 1;
							$found_var = $var;
						}
					}
					if(!$found && ($this->xs_check_switches == 1))
					{
						// checking list of known buggy switches
						$item = -1;
						for($j=0; $j<$count_bugs; $j++)
						{
							if($this->bugs[$j][0] === $var)
							{
								$item = $j;
							}
						}
						if($item >= 0)
						{
							$str1 = '<!-- END ' . $this->bugs[$item][1] . ' -->';
							for ($j = $i+1; ($j < $line_count) && !$found; $j++)
							{
								$pos = strpos($code_lines[$j], $str1);
								if($pos !== false)
								{
									$found_var = $this->bugs[$item][1];
									$found = 1;
									$code_lines[$j] = str_replace($str, $str1, $code_lines[$j]);
								}
							}
						}
					}
					if(!$found)
					{
						$compiled[] = $keyword_str;
						continue;
					}
					// adding to list of switches
					if(isset($sw[$found_var]))
					{
						$sw[$found_var] ++;
					}
					else
					{
						$sw[$found_var] = 1;
					}
				}
				// adding code
				$block_nesting_level++;
				$block_names[$block_nesting_level] = $var;
				if(isset($block_items[$var]))
				{
					$block_items[$var] ++;
				}
				else
				{
					$block_items[$var] = 1;
				}
				if ($block_nesting_level < 2)
				{
					// Block is not nested.
					$line = '<'."?php\n\n";
					if($use_isset)
					{
						$line .= '$'. $var. '_count = ( isset($this->_tpldata[\''. $var. '.\']) ) ?  sizeof($this->_tpldata[\''. $var. '.\']) : 0;';
					}
					else
					{
						$line .= '$'. $var. '_count = sizeof($this->_tpldata[\''. $var. '.\']);';
					}
					$line .= "\n" . 'for ($'. $var. '_i = 0; $'. $var. '_i < $'. $var. '_count; $'. $var. '_i++)';
					$line .= "\n". '{'. "\n";
					$line .= ' $'. $var. '_item = &$this->_tpldata[\''. $var. '.\'][$'. $var. '_i];'."\n";
					$line .= " \${$var}_item['S_ROW_COUNT'] = \${$var}_i;\n";
					$line .= " \${$var}_item['S_NUM_ROWS'] = \${$var}_count;\n";
					$line .= "\n?".">";
				}
				else
				{
					// This block is nested.
					// Generate a namespace string for this block.
					$namespace = implode('.', $block_names);
					// strip leading period from root level..
					$namespace = substr($namespace, 2);
					// Get a reference to the data array for this block that depends on the
					// current indices of all parent blocks.
					$varref = $this->generate_block_data_ref($namespace, false);
					// Create the for loop code to iterate over this block.
					$line = '<'."?php\n\n";
					if($use_isset)
					{
						$line .= '$'. $var. '_count = ( isset('. $varref. ') ) ? sizeof('. $varref. ') : 0;';
					}
					else
					{
						$line .= '$'. $var. '_count = sizeof('. $varref. ');';
					}
					$line .= "\n". 'for ($'. $var. '_i = 0; $'. $var. '_i < $'. $var. '_count; $'. $var. '_i++)';
					$line .= "\n". '{'. "\n";
					$line .= ' $'. $var. '_item = &'. $varref. '[$'. $var. '_i];'."\n";
					$line .= " \${$var}_item['S_ROW_COUNT'] = \${$var}_i;\n";
					$line .= " \${$var}_item['S_NUM_ROWS'] = \${$var}_count;\n";
					$line .= "\n?".">";
				}
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- END -->
			*/
			if($keyword_type == XS_TAG_END)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				if($num_params == 1)
				{
					$var = $params[0];
				}
				elseif($num_params == 2 && $params[0] === '')
				{
					$var = $params[1];
				}
				elseif($num_params == 2 && $params[1] === '')
				{
					$var = $params[0];
				}
				else
				{
					$compiled[] = $keyword_str;
					continue;
				}
				if($this->xs_check_switches)
				{	
					// checking if this switch was opened
					if(!isset($sw[$var]) || ($sw[$var] < 1))
					{	
						// there is no opening switch
						$compiled[] = $keyword_str;
						continue;
					}
					$sw[$var] --;
				}
				// We have the end of a block.
				$line = '<'."?php\n\n";
				$line .= '} // END ' . $var . "\n\n";
				$line .= 'if(isset($' . $var . '_item)) { unset($' . $var . '_item); } ';
				$line .= "\n\n?".">";
				if(isset($block_items[$var]))
				{
					$block_items[$var] --;
				}
				else
				{
					$block_items[$var] = -1;
				}
				unset($block_names[$block_nesting_level]);
				$block_nesting_level--;
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- BEGINELSE -->
			*/
			if($keyword_type == XS_TAG_BEGINELSE)
			{
				if($block_nesting_level)
				{
					$var = $block_names[$block_nesting_level];
					$compiled[] = '<' . '?php } if(!$' . $var . '_count) { ?' . '>';
				}
				else
				{
					$compiled[] = $keyword_str;
					continue;
				}
			}
			/*
			* <!-- INCLUDE -->
			*/
			if($keyword_type == XS_TAG_INCLUDE)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				if($num_params != 1)
				{
					$compiled[] = $keyword_str;
					continue;
				}
				$line = '<'.'?php ';
				$filehash = md5($params_str . $this->include_count . time());
				$line .= ' $this->set_filename(\'xs_include_' . $filehash . '\', \'' . $params_str .'\', true); ';
				$line .= ' $this->pparse(\'xs_include_' . $filehash . '\'); ';
				$line .= ' ?'.'>';
				$this->include_count ++;
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- IF -->
			*/
			if($keyword_type == XS_TAG_IF || $keyword_type == XS_TAG_ELSEIF)
			{
				if(!$count_if)
				{
					$keyword_type = XS_TAG_IF;
				}
				$str = $this->compile_tag_if($params_str, $keyword_type == XS_TAG_IF ? false : true);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
					if($keyword_type == XS_TAG_IF)
					{
						$count_if ++;
					}
				}
				else
				{
					$compiled[] = $keyword_str;
				}
				continue;
			}
			/*
			* <!-- ELSE -->
			*/
			if($keyword_type == XS_TAG_ELSE && $count_if > 0)
			{
				$compiled[] = '<?php } else { ?>';
				continue;
			}
			/*
			* <!-- ENDIF -->
			*/
			if($keyword_type == XS_TAG_ENDIF && $count_if > 0)
			{
				$compiled[] = '<?php } ?>';
				$count_if --;
				continue;
			}
			/*
			* <!-- DEFINE -->
			*/
			if($keyword_type == XS_TAG_DEFINE)
			{
				$str = $this->compile_tag_define($params_str);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
				}
				else
				{
					$compiled[] = $keyword_str;
				}
			}
			/*
			* <!-- UNDEFINE -->
			*/
			if($keyword_type == XS_TAG_UNDEFINE)
			{
				$str = $this->compile_tag_undefine($params_str);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
				}
				else
				{
					$compiled[] = $keyword_str;
				}
			}
		}
		
		// bring it back into a single string.
		$code_header = '';
		$code_footer = '';
		if(!$use_isset)
		{
			$code_header =	"<". "?php\n\$old_level = @error_reporting(E_ERROR | E_WARNING | E_PARSE); \n?".">";
			$code_footer = '<'."?php @error_reporting(\$old_level); ?".'>';
		}

		return $code_header . implode('', $compiled) . $code_footer;
	}

	/*
	* Compile code between tags
	*/
	function _compile_text($code, $use_isset)
	{
		if(strlen($code) < 3)
		{
			return $code;
		}
		// change template varrefs into PHP varrefs
		// This one will handle varrefs WITH namespaces
		$varrefs = array();
		preg_match_all('#\{(([a-z0-9\-_]+?\.)+?)([a-z0-9\-_]+?)\}#is', $code, $varrefs);
		$varcount = sizeof($varrefs[1]);
		$search = array();
		$replace = array();
		for ($i = 0; $i < $varcount; $i++)
		{
			$namespace = $varrefs[1][$i];
			$varname = $varrefs[3][$i];
			$new = $this->generate_block_varref($namespace, $varname, $use_isset);
			$search[] = $varrefs[0][$i];
			$replace[] = $new;
		}
		if(count($search) > 0)
		{
			$code = str_replace($search, $replace, $code);
		}
		// This will handle the remaining root-level varrefs
		$code = preg_replace('#\{([a-z0-9\-_]*?)\}#is', '<'.'?php echo isset($this->vars[\'\1\']) ? $this->vars[\'\1\'] : $this->lang(\'\1\'); ?'.'>', $code);
		$code = preg_replace('#\{\$([a-z0-9\-_]*?)\}#is', '<'.'?php echo isset($this->_tpldata[\'DEFINE\'][\'.\'][\'\\1\']) ? $this->_tpldata[\'DEFINE\'][\'.\'][\'\\1\'] : \'\'; ?'.'>', $code);
		return $code;
	}

	//
	// Compile IF tags - much of this is from Smarty with
	// some adaptions for our block level methods
	//
	function compile_tag_if($tag_args, $elseif)
	{
        /* Tokenize args for 'if' tag. */
        preg_match_all('/(?:
                         "[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         |
                         \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     |
                         [(),]                                  |
                         [^\s(),]+)/x', $tag_args, $match);

        $tokens = $match[0];
        $is_arg_stack = array();

        for ($i = 0; $i < count($tokens); $i++)
		{
			$token = &$tokens[$i];

			switch ($token)
			{
                case '!':
                case '%':
                case '!==':
                case '==':
                case '===':
                case '>':
                case '<':
                case '!=':
                case '<>':
                case '<<':
                case '>>':
                case '<=':
                case '>=':
                case '&&':
                case '||':
				case '|':
				case '^':
				case '&':
				case '~':
				case ')':
				case ',':
				case '+':
				case '-':
				case '*':
				case '/':
				case '@':
					break;	

				case 'eq':
					$token = '==';
					break;

				case 'ne':
				case 'neq':
					$token = '!=';
					break;

				case 'lt':
					$token = '<';
					break;

				case 'le':
				case 'lte':
					$token = '<=';
					break;

				case 'gt':
					$token = '>';
					break;

				case 'ge':
				case 'gte':
					$token = '>=';
					break;

				case 'and':
					$token = '&&';
					break;

				case 'or':
					$token = '||';
					break;

				case 'not':
					$token = '!';
					break;

				case 'mod':
					$token = '%';
					break;

				case '(':
					array_push($is_arg_stack, $i);
					break;

				case 'is':
					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1;
					$is_arg	= implode('	', array_slice($tokens,	$is_arg_start, $i -	$is_arg_start));

					$new_tokens	= $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));

					array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);

					$i = $is_arg_start;

				default:
					if (preg_match('#^(([a-z0-9\-_]+?\.)+?)?(\$)?([A-Z]+[A-Z0-9\-_]+)$#s', $token, $varrefs))
					{
						$token = (!empty($varrefs[1])) ? $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[3]) . '[\'' . $varrefs[4] . '\']' : (($varrefs[3]) ? '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[4] . '\']' : '$this->vars[\'' . $varrefs[4] . '\']');
					}
					break;
            }
        }

		$code = (($elseif) ? '} elseif (' : 'if (') . (implode(' ', $tokens) . ') { ');
	
		return $code;
	}

	// This is from Smarty
	function _parse_is_expr($is_arg, $tokens)
	{
		$expr_end =	0;
		$negate_expr = false;

		if (($first_token = array_shift($tokens)) == 'not')
		{
			$negate_expr = true;
			$expr_type = array_shift($tokens);
		}
		else
		{
			$expr_type = $first_token;
		}

		switch ($expr_type)
		{
			case 'even':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"!(($is_arg	/ $expr_arg) % $expr_arg)";
				}
				else
				{
					$expr =	"!($is_arg % 2)";
				}
				break;

			case 'odd':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"(($is_arg / $expr_arg)	% $expr_arg)";
				}
				else
				{
					$expr =	"($is_arg %	2)";
				}
				break;

			case 'div':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"!($is_arg % $expr_arg)";
				}
				break;

			default:
				break;
		}

		if ($negate_expr)
		{
			$expr =	"!($expr)";
		}

		array_splice($tokens, 0, $expr_end,	$expr);

		return $tokens;
	}


	function compile_tag_define($tag_args)
	{
		preg_match('#^(([a-z0-9\-_]+?\.)+?)?\$([A-Z][A-Z0-9_\-]*?) = (\'?)(.*?)(\'?)$#', $tag_args, $match);

		if (empty($match[3]) || empty($match[5]))
		{
			return '';
		}

		// Are we a string?
		if ($match[4] && $match[6])
		{
			$match[5] = "'" . addslashes(str_replace(array('\\\'', '\\\\'), array('\'', '\\'), $match[5])) . "'";
		}
		else
		{
			preg_match('#(true|false|\.)#i', $match[5], $type);

			switch (strtolower($type[1]))
			{
				case 'true':
				case 'false':
					$match[5] = strtoupper($match[5]);
					break;
				case '.';
					$match[5] = doubleval($match[5]);
					break;
				default:
					$match[5] = intval($match[5]);
					break;
			}
		}

		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[3] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[3] . '\']') . ' = ' . $match[5] . ';';
	}

	function compile_tag_undefine($tag_args)
	{
		preg_match('#^(([a-z0-9\-_]+?\.)+?)?\$([A-Z][A-Z0-9_\-]*?)$#', $tag_args, $match);
		if (empty($match[3]))
		{
			return '';
		}
		return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[3] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[3] . '\']') . ');';
	}

	/**
	 * Compiles code and writes to cache if needed
	 */
	function compile2($code, $handle, $cache_file)
	{
		$code = $this->compile_code('', $code, XS_USE_ISSET);
		if($cache_file && !empty($this->use_cache) && !empty($this->auto_compile))
		{
			$res = $this->write_cache($cache_file, $code);
			if($handle && $res)
			{
				$this->files_cache[$handle] = $cache_file;
			}
		}
		$code = '?'.'>'.$code.'<'."?php\n";
		return $code;
	}

	/**
	 * Compiles the given string of code, and returns
	 * the result in a string.
	 * If "do_not_echo" is true, the returned code will not be directly
	 * executable, but can be used as part of a variable assignment
	 * for use in assign_code_from_handle().
	 * This function isn't used and kept only for compatibility with original template.php
	 */
	function compile($code, $do_not_echo = false, $retvar = '')
	{
		$code = ' ?'.'>' . $this->compile_code('', $code, true) . '<'."?php \n";
		if($do_not_echo)
		{
			$code = "ob_start();\n". $code. "\n\${$retvar} = ob_get_contents();\nob_end_clean();\n";
		}
		return $code;
	}

	/**
	 * Write cache to disk
	 */
	function write_cache($filename, $code)
	{
		// check if cache is writable
		if(!$this->cache_writable)
		{
			return false;
		}
		// check if filename is valid
		if(substr($filename, 0, strlen($this->cachedir)) !== $this->cachedir)
		{
			return false;
		}
		// try to open file
		$file = @fopen($filename, 'w');
		if(!$file)
		{
			// try to create directories
			$dir = substr($filename, strlen($this->cachedir), strlen($filename));
			$dirs = explode('/', $dir);
			$path = $this->cachedir; 
			@umask(0);
			if(!@is_dir($path))
			{
				if(!@mkdir($path))
				{
					$this->cache_writable = 0;
					return false;
				}
				else
				{
					@chmod($path, 0777);
				}
			}
			$count = count($dirs);
			if($count > 0)
			for($i=0; $i<$count-1; $i++)
			{
				if($i>0)
				{
					$path .= '/';
				}
				$path .= $dirs[$i];
				if(!@is_dir($path))
				{
					if(!@mkdir($path))
					{
						$this->cache_writable = 0;
						return false;
					}
					else
					{
						@chmod($path, 0777);
					}
				}
			}
			// try to open file again after directories were created
			$file = @fopen($filename, 'w');
		}
		if(!$file)
		{
			$this->cache_writable = 0;
			return false;
		}
		fputs($file, "<?php\n\n// eXtreme Styles mod cache. Generated on " . date('r') . " (time=" . time() . ")\n\n?>");
		fputs($file, $code);
		fclose($file);
		@chmod($filename, 0777);
		return true;
	}

	function xs_startup()
	{
		global $phpEx, $board_config, $phpbb_root_path;
		if(empty($this->xs_started))
		{	// adding predefined variables
			$this->xs_started = 1;
			// file extension with session ID (eg: "php?sid=123&" or "php?")
			// can be used to make custom URLs without modding phpbb
			// contains "&" or "?" at the end so you can easily append paramenters
			$php = append_sid($phpEx);
			if(strpos($php, '?'))
			{
				$php .= '&';
			}
			else
			{
				$php .= '?';
			}
			$this->vars['PHP'] = isset($this->vars['PHP']) ? $this->vars['PHP'] : $php;
			// adding language variable (eg: "english" or "german")
			// can be used to make truly multi-lingual templates
			$this->vars['LANG'] = isset($this->vars['LANG']) ? $this->vars['LANG'] : $board_config['default_lang'];
			// adding current template
			$tpl = $this->root . '/'; // $phpbb_root_path . 'templates/' . $this->tpl . '/';
			if(substr($tpl, 0, 2) === './')
			{
				$tpl = substr($tpl, 2, strlen($tpl));
			}
			$this->vars['TEMPLATE'] = isset($this->vars['TEMPLATE']) ? $this->vars['TEMPLATE'] : $tpl;
			$this->vars['TEMPLATE_NAME'] = isset($this->vars['TEMPLATE_NAME']) ? $this->vars['TEMPLATE_NAME'] : $this->tpl;
			$this->_tpldata['switch_xs_enabled.'] = array(array('version' => $this->xs_versiontxt));
		}
	}

	/**
	 * Checks for empty variable and shows language variable if possible.
	 */
	function lang($var)
	{
		global $lang;
		if(substr($var, 0, 2) === 'L_')
		{
			$var = substr($var, 2);
			// check variable as it is
			if(isset($lang[$var]))
			{
				return $lang[$var];
			}
			// check variable in lower case
			if(isset($lang[strtolower($var)]))
			{
				return $lang[strtolower($var)];
			}
			// check variable with first letter in upper case
			$str = ucfirst(strtolower($var));
			if(isset($lang[$str]))
			{
				return $lang[$str];
			}
			return ''; //str_replace('_', ' ', $var);
		}
		return '';
	}

	//
	//
	// Functions added for USERGROUP MOD (optimized)
	//
	//
	function append_var_from_handle_to_block($blockname, $varname, $handle)
	{
		$this->assign_var_from_handle('_tmp', $handle);
		// assign the value of the generated variable to the given varname.
		$this->append_block_vars($blockname, array($varname => $this->vars['_tmp']));
		return true;
	}

	function append_block_vars($blockname, $vararray)
	{
		if(strstr($blockname, '.'))
		{
			// Nested block.
			$blocks = explode('.', $blockname);
			$blockcount = sizeof($blocks) - 1;
			$str = &$this->_tpldata;
			for($i = 0; $i < $blockcount; $i++)
			{
				$str = &$str[$blocks[$i].'.'];
				$str = &$str[sizeof($str)-1];
			}
			// Now we add the block that we're actually assigning to.
			// We're adding a new iteration to this block with the given
			//   variable assignments.
			$str = &$str[$blocks[$blockcount].'.'];
			$count = sizeof($str) - 1;
			if($count >= 0)
			{
				// adding only if there is at least one item
				$str[$count] = array_merge($str[$count], $vararray);
			}
		}
		else
		{
			// Top-level block.
			// Add a new iteration to this block with the variable assignments
			// we were given.
			$str = &$this->_tpldata[$blockname.'.'];
			$count = sizeof($str) - 1;
			if($count >= 0)
			{
				// adding only if there is at least one item
				$str[$count] = array_merge($str[$count], $vararray);
			}
		}
		return true;
	}

	/*
	* Flush a root level block, so it becomes empty.
	*/
	function flush_block_vars($blockname)
	{
		// Top-level block.
		// flush a existing block we were given.
		$current_iteration = sizeof($this->_tpldata[$blockname . '.']) - 1;
		unset($this->_tpldata[$blockname . '.']);
		return true;
	}

	/*
	* Add style configuration
	*/
	function _add_config($tpl, $add_vars = true)
	{
		global $phpbb_root_path;
		if(@file_exists($phpbb_root_path . 'templates/' . $tpl . '/xs_config.cfg'))
		{
			$style_config = array();
			include($phpbb_root_path . 'templates/' . $tpl . '/xs_config.cfg');
			if(count($style_config))
			{
				global $board_config, $db;
				for($i=0; $i<count($style_config); $i++)
				{
					$this->style_config[$style_config[$i]['var']] = $style_config[$i]['default'];
					if($add_vars)
					{
						$this->vars['TPL_CFG_' . strtoupper($style_config[$i]['var'])] = $style_config[$i]['default'];
					}
				}
				$str = $this->_serialize($this->style_config);
				$config_name = 'xs_style_' . $tpl;
				$board_config[$config_name] = $str;
				$sql = "INSERT INTO " . CONFIG_TABLE . " (config_name, config_value) VALUES ('" . str_replace('\\\'', '\'\'', addslashes($config_name)) . "', '" . str_replace('\\\'', '\'\'', addslashes($str)) . "')";
				$db->sql_query($sql);
				// recache config table for cat_hierarchy 2.1.0
				global $config;
				if(isset($config->data) && $config->data === $board_config && isset($config->data['mod_cat_hierarchy']))
				{
					$config->read(true);
				}
				return true;
			}
		}
		return false;
	}

	function add_config($tpl)
	{
		$config_name = 'xs_style_' . $tpl;
		global $board_config;
		$result = false;
		if(empty($board_config[$config_name]))
		{
			$old = $this->style_config;
			$result = $this->_add_config($tpl, false);
			$this->style_config = $old;
		}
		return $result;
	}

	/*
	* Refresh config data
	*/
	function _refresh_config($tpl, $add_vars = false)
	{
		global $phpbb_root_path;
		if(@file_exists($phpbb_root_path . 'templates/' . $tpl . '/xs_config.cfg'))
		{
			$style_config = array();
			include($phpbb_root_path . 'templates/' . $tpl . '/xs_config.cfg');
			if(count($style_config))
			{
				global $board_config, $db;
				for($i=0; $i<count($style_config); $i++)
				{
					if(!isset($this->style_config[$style_config[$i]['var']]))
					{
						$this->style_config[$style_config[$i]['var']] = $style_config[$i]['default'];
						if($add_vars)
						{
							$this->vars['TPL_CFG_' . strtoupper($style_config[$i]['var'])] = $style_config[$i]['default'];
						}
					}
				}
				$str = $this->_serialize($this->style_config);
				$config_name = 'xs_style_' . $tpl;
				if(isset($board_config[$config_name]))
				{
					$sql = "UPDATE " . CONFIG_TABLE . " SET config_value='" . str_replace('\\\'', '\'\'', addslashes($str)) . "' WHERE config_name='" . str_replace('\\\'', '\'\'', addslashes($config_name)) . "'";
				}
				else
				{
					$sql = "INSERT INTO " . CONFIG_TABLE . " (config_name, config_value) VALUES ('" . str_replace('\\\'', '\'\'', addslashes($config_name)) . "', '" . str_replace('\\\'', '\'\'', addslashes($str)) . "')";
				}
				$db->sql_query($sql);
				$board_config[$config_name] = $str;
				// recache config table for cat_hierarchy 2.1.0
				global $config;
				if(isset($config->data) && $config->data === $board_config && isset($config->data['mod_cat_hierarchy']))
				{
					$config->read(true);
				}
				return true;
			}
		}
		return false;
	}

	function refresh_config($tpl = '')
	{
		if($tpl === '')
		{
			$tpl = $this->tpl;
		}
		if($tpl == $this->tpl)
		{
			$result = $this->_refresh_config($tpl, true);
		}
		else
		{
			$old = $this->style_config;
			$result = $this->_refresh_config($tpl, false);
			$this->style_config = $old;
		}
		return $result;
	}

	/*
	* Get style configuration
	*/
	function _get_config($tpl, $add_config)
	{
		$this->style_config = array();
		if(empty($tpl))
		{
			$tpl = $this->tpl;
		}
		$config_name = 'xs_style_' . $tpl;
		global $board_config;
		if(empty($board_config[$config_name]))
		{
			if($add_config)
			{
				$this->_add_config($tpl, $tpl === $this->tpl ? true : false);
			}
			return $this->style_config;
		}
		$this->style_config = $this->_unserialize($board_config[$config_name]);
		if($tpl === $this->tpl)
		{
			foreach($this->style_config as $var => $value)
			{
				$this->vars['TPL_CFG_' . strtoupper($var)] = $value;
			}
		}
		return $this->style_config;
	}

	function get_config($tpl = '', $add_config = true)
	{
		if(empty($tpl))
		{
			if(empty($this->tpl))
			{
				return array();
			}
			$this->_get_config($this->tpl, $add_config);
			return $this->style_config;
		}
		else
		{
			$old_config = $this->style_config;
			$result = $this->_get_config($tpl, $add_config);
			$this->style_config = $old_config;
			return $result;
		}
	}

	/*
	* Split/merge config data.
	* Using this function instead of (un)serialize because it generates smaller string so it can be stored in phpbb_config
	*/
	function _serialize($array)
	{
		if(!is_array($array))
		{
			return '';
		}
		$str = '';
		foreach($array as $var => $value)
		{
			if($str)
			{
				$str .= '|';
			}
			$str .= $var . '=' . str_replace('|', '', $value);
		}
		return $str;
	}
	function _unserialize($str)
	{
		$array = array();
		$list = explode('|', $str);
		for($i=0; $i<count($list); $i++)
		{
			$row = explode('=', $list[$i], 2);
			if(count($row) == 2)
			{
				$array[$row[0]] = $row[1];
			}
		}
		return $array;
	}

}

function xs_switch($tpl, $name)
{
	return (isset($tpl->_tpldata[$name.'.']) && count($tpl->_tpldata[$name.'.']) > 0);
}

?>