<?php
/***************************************************************************
 *                              template.php
 *                            -------------------
 *   copyright            : (C) 2001 The phpBB Group
 *   email                : support@phpbb.com
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   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 Vjacheslav Trushkin.
 * http://www.trushkin.net/phpbbmods.php
 *
 * Documentation for this mod can be found here:
 * http://www.trushkin.net/manual.php?cat=xs
 *
 */

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 = '';
	// 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();

	// Separators. Replaces '/' in filename.
	// if set to '/' then will create sub-directories.
	// if you want all cache to be in same dir change it to '.' or '_' or something like that.
	var $separator = '/';

	// 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 compiled code should use isset() php function
	var $xs_use_isset = 1;

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

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

	// 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 = '.')
	{
		global $board_config, $phpbb_root_path, $phpEx;
		// setting pointer "vars"
		$this->vars = &$this->_tpldata['.'][0];
		// getting mod version from config and comparing with real data
		$ver = isset($board_config['xs_version']) ? $board_config['xs_version'] : 0;
		// 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
			);
		// list of default values
		$default = array(
			'xs_cache_dir'				=> 'cache',
			'xs_cache_dir_absolute'		=> 0,
			'xs_auto_compile'			=> 1,
			'xs_auto_recompile'			=> 1,
			'xs_use_cache'				=> 1,
			'xs_separator'				=> '/',
			'xs_php'					=> $phpEx,
			'xs_def_template'			=> 'PNTheme', // PNphpBB2 Mod
			'xs_use_isset'				=> 1,
			'xs_check_switches'			=> 1,
			);
		// 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']))
		{
			$board_config['xs_auto_compile'] = 1;
			if(!in_array('xs_auto_compile', $up) && !in_array('xs_auto_compile', $add))
				$up[] = 'xs_auto_compile';
		}
		if($board_config['xs_separator'] == '\\')
		{
			$board_config['xs_separator'] = '/';
			if(!in_array('xs_separator', $up) && !in_array('xs_separator', $add))
				$up[] = 'xs_separator';
		}
		// install/upgrade
		if((count($add) > 0) || (count($up) > 0) || (count($del) > 0))
		{
			global $db;
			if(isset($db) && (@get_class($db) === 'sql_db'))
			{
				// adding new config values
				for($i=0; $i<count($add); $i++)
				{
					$sql = "INSERT INTO " . CONFIG_TABLE . " (config_name, config_value) VALUES ('" . $add[$i] . "', '" . 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='" . addslashes($board_config[$up[$i]]) . "' WHERE config_name='" . $up[$i] . "'";
					$db->sql_query($sql);
				}
			}
		}
		$this->php = $board_config['xs_php'];
		$this->separator = $board_config['xs_separator'];
		$this->tpldef = $board_config['xs_def_template'];
		$this->use_isset = $board_config['xs_use_isset'];
		$this->xs_check_switches = $board_config['xs_check_switches'];
		$this->set_rootdir($root);
		$this->load_replacements($root . '/xs.cfg');
	}

	/**
	 * 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 = str_replace('\\', '/', $board_config['xs_cache_dir']);
		if(!$board_config['xs_cache_dir_absolute'])
		{
			$this->cachedir = $phpbb_root_path . $this->cachedir;
		}
		// adding trailing '/'
		if(substr($this->cachedir, strlen($this->cachedir)-1, 1) != '/')
		{
			$this->cachedir .= '/';
		}
		// creating absolute path for current template and root dir
		$this->tpldir = $phpbb_root_path . 'templates/';
		// adding trailing '/'
		if(substr($this->tpldir, strlen($this->tpldir)-1, 1) != '/')
		{
			$this->tpldir .= '/';
		}
		$this->tpldir_len = strlen($this->tpldir);
		$this->root = $dir;
		$this->tpl = $this->template_name($dir);
		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;
	}

	/**
	 * 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 = '/templates/'; // can start at any position
		$tpl_null = 'templates/'; // 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)
		{
			$str = $this->template_name($str);
		}
		// check for another subdirectory
		$pos = strpos($dir, '/');
		if($pos)
		{
			$dir = substr($dir, 0, $pos);
		}
		return $dir;
	}

	/**
	 * 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)
	{
		// 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)
	{
		if(substr($file, 0, $this->tpldir_len) !== $this->tpldir)
		{
			return '';
		}
		// removing not needed part
		$file = substr($file, $this->tpldir_len, strlen($file));
		// replacing separator
		if($this->separator != '/')
		{
			$file = str_replace('/', $this->separator, $file);
		}
		// creating filename
		return $this->cachedir . str_replace('.tpl', '.'.$this->php, $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)
	{
		global $board_config;
		$can_cache = $board_config['xs_use_cache'];
		if(strpos($filename, '..') !== false)
		{
			$can_cache = false;
		}
		$this->files[$handle] = $this->make_filename($filename, $xs_include);
		$file->files_cache[$handle] = '';
		$this->files_cache2[$handle] = '';
		// checking if we have valid filename
		if(!$this->files[$handle])
		{
			if($xs_include)
			{
				return false;
			}
			else
			{
				die("Template->make_filename(): Error - cannot load 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];
			}
			else
			{
				$this->files_cache[$handle] = '';
			}
		}
		else
		{
			$this->files_cache2[$handle] = '';
			$this->files_cache[$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) && ($this->tpldef !== $this->tpl))
			{
				$this->files_cache[$handle] = '';
				$this->files_cache2[$handle] = ''; // do not use cache for unknown files
				$root = $this->root;
				$this->root = str_replace($this->tpl, $this->tpldef, $this->root);
				$this->files[$handle] = $this->make_filename($filename);
				$this->root = $root;
			}
			if(empty($this->files[$handle]) || !@file_exists($this->files[$handle]))
			{
				if($xs_include)
				{
					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']))
		{
			if(@filemtime($this->files[$handle]) > @filemtime($this->files_cache[$handle]))
			{	// 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)
	{
// Begin PNphpBB2 Module
		global $lang, $phpbb_theme, $board_config;
		$template = $phpbb_theme['template_name'];
		global $$template;
		$theme_info = &$$template;
		if($filename)
		{
			include($filename);
		}
		else
		{
			eval($code);
		}
		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;
		$this->xs_startup();
		// checking if handle exists
		if (empty($this->files[$handle]) && empty($this->files_cache[$handle]))
		{
			die("Template->loadfile(): No files found for handle $handle");
		}
		// checking if php file exists.
		// also check if we have loaded uncompiled code
		// (attachment_mod might have put uncompiled code)
		if (!empty($this->files_cache[$handle]) && empty($this->uncompiled_code[$handle]))
		{
			// php file exists - running it instead of tpl
			$this->execute($this->files_cache[$handle],'');
			return true;
		}
		else
		{
			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]))
				{
					$this->compiled_code[$handle] = $this->compile2($this->uncompiled_code[$handle], $this->files_cache2[$handle]);
				}
				else
				{
					$this->compiled_code[$handle] = $this->compile2($this->uncompiled_code[$handle]);
				}
			}
			// Run the compiled code.

			$this->execute('', $this->compiled_code[$handle]);
			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)
	{
		global $board_config;
		$this->xs_startup();
		if (!empty($this->files_cache[$handle]) && empty($this->uncompiled_code[$handle]))
		{
			// php file exists and no uncompiled code - running it instead of tpl
			ob_start();
			$this->execute($this->files_cache[$handle], '');
			$str = ob_get_contents();
			ob_end_clean();
			$this->assign_var($varname, $str);
			return true;
		}

		if (!$this->loadfile($handle))
		{
			die("Template->assign_var_from_handle(): Couldn't load template file for handle $handle");
		}

		// Compile it.
		if(!empty($this->files_cache2[$handle]) && empty($this->files_cache[$handle]))
		{
			$code = $this->compile2($this->uncompiled_code[$handle], $this->files_cache2[$handle]);
		}
		else
		{
			$code = $this->compile2($this->uncompiled_code[$handle]);
		}

		// evaluate the variable assignment.
		ob_start();
		$this->execute('', $code);
		$str = ob_get_contents();
		ob_end_clean();
		// assign the value of the generated variable to the given varname.
		$this->assign_var($varname, $str);

		return true;
	}

	/**
	 * 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)
	{
		reset ($vararray);
		while (list($key, $val) = each($vararray))
		{
			$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)
	{
		// Get an array of the blocks involved.
		$blocks = explode(".", $blockname);
		$blockcount = sizeof($blocks) - 1;
		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.

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

		// 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]);
		for ($i = 0; $i < $varcount; $i++)
		{
			$namespace = $varrefs[1][$i];
			$varname = $varrefs[3][$i];
			$new = $this->generate_block_varref($namespace, $varname, $use_isset);
			$code = str_replace($varrefs[0][$i], $new, $code);
		}
		// This will handle the remaining root-level varrefs
		if($use_isset)
		{
			$code = preg_replace('#\{([a-z0-9\-_]*?)\}#is', '<'.'?php echo isset($this->vars[\'\1\']) ? $this->vars[\'\1\'] : \'\'; ?'.'>', $code);
		}
		else
		{
			$code = preg_replace('#\{([a-z0-9\-_]*?)\}#is', '<'.'?php echo $this->vars[\'\1\']; ?'.'>', $code);
		}

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

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

		// count lines and prepare array for compiled code
		$line_count = count($code_lines);
		$compiled = array();

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

		// main loop
		for ($i = 0; $i < $line_count; $i++)
		{
			$line = $code_lines[$i];
			// resetting variables
			$keyword_type = 0; // 1 for 'BEGIN', 2 for 'END', 3 for short php tag, 4 for 'INCLUDE'
			// check if we have valid keyword in current line
			$pos1 = strpos($line, '<!-- BEGIN ');
			$pos2 = strpos($line, '<!-- END ');
			$pos3 = strpos($line, '<'.'?');
			$pos4 = strpos($line, '<!-- INCLUDE ');
			if(($pos1 !== false) || ($pos2 !== false) || ($pos3 !== false) || ($pos4 !== false))
			{
				// checking which one is first
				$max = max(intval($pos1), intval($pos2), intval($pos3), intval($pos4)) + 1;
				if(($pos1 !== false) && ($pos1 < $max))
				{
					$keyword_type = 1;
					$max = $pos = $pos1;
				}
				if(($pos2 !== false) && ($pos2 < $max))
				{
					$keyword_type = 2;
					$max = $pos = $pos2;
				}
				if(($pos3 !== false) && ($pos3 < $max))
				{
					$keyword_type = 3;
					$max = $pos = $pos3;
				}
				if(($pos4 !== false) && ($pos4 < $max))
				{
					$keyword_type = 4;
					$max = $pos = $pos4;
				}
				// remove keyword from line - it will be processed on next for()
				if($pos > 0)
				{	
					$line = substr($line, 0, $pos);
					$code_lines[$i] = substr($code_lines[$i], $pos, strlen($code_lines[$i]) - $pos);
					$i--;
					$keyword_type = 0;
				}
				// remove everything after keyword from line
				else
				{	
					// find end of keyword
					if(($keyword_type == 1) || ($keyword_type == 2) || ($keyword_type == 4))
					{
						$pos = strpos($line, '-->') + 3;
					}
					elseif($keyword_type == 3)
					{
						if(substr($line, 0, 5) === '<'.'?php')
						{	// valid php code tag
							$keyword_type = 0;
							$pos = 5;
						}
						else
						{	// short php tag or something like that
							$pos = 2;
						}
					}
					// remove everything after key - it will be processed on next for()
					$line = substr($line, 0, $pos);
					$code_lines[$i] = substr($code_lines[$i], $pos, strlen($code_lines[$i]) - $pos);
					$i--;
					// getting variable name
					if($keyword_type == 1)	// BEGIN
					{
						$var = trim(substr($line, 11, $pos-15));
						if(strpos($var, ' '))
						{
							$keyword_type = 0;
						}
						elseif($this->xs_check_switches)
						{
							// checking if variable is closed
							$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($this->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)
							{	// invalid switch
								$keyword_type = 0;
								$line = '';
							}
							else
							{	// adding to list of switches
								if(isset($sw[$found_var]))
								{
									$sw[$found_var] ++;
								}
								else
								{
									$sw[$found_var] = 1;
								}
							}
						}
					}
					elseif($keyword_type == 2)	// END
					{
						$var = trim(substr($line, 9, $pos-13));
						if(strpos($var, ' '))
						{
							$keyword_type = 0;
						}
						elseif($this->xs_check_switches)
						{	// checking if this switch was opened
							if(!isset($sw[$var]) || ($sw[$var] < 1))
							{	// there is no opening switch
								$keyword_type = 0;
								$line = '';
							}
							else
							{
								$sw[$var] --;
							}
						}
					}
					elseif($keyword_type == 4)	// INCLUDE
					{
						$var = trim(substr($line, 13, $pos-17));
						if(strpos($var, ' '))
						{
							$keyword_type = 0;
						}
					}
				}
			}

			// find key if we have it on current line (should be on line start)
			if($keyword_type == 1) // <!-- BEGIN $var -->
			{
				// We have the start of a block.
				$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];';
					$line .= "\n\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];';
					$line .= "\n\n?".">";
				}
			}
			elseif ($keyword_type == 2) // <!-- END $var -->
			{
				// We have the end of a block.
				$line = '<'."?php\n\n";
				$line .= '} // END ' . $var;
				$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--;
			}
			elseif ($keyword_type == 3) // short php tag
			{
				$line = '<'.'?php echo \'<\',\'?\'; ?'.'>';
				$php_tags ++;
			}
			elseif ($keyword_type == 4) // <!-- INCLUDE file -->
			{
				$str = str_replace(array('/', '\\', '.', ':'), array(), $var);
				$line = '<'.'?php ';
				$line .= '$this->set_filename(\'xs_include' . $this->include_count . '\', \'' . $var .'\', true); ';
				$line .= '$this->pparse(\'xs_include' . $this->include_count . '\'); ';
				$line .= ' ?'.'>';
				$this->include_count++;
			}
			/*else // usual html/php
			{
				// do nothing.
			}*/
			$compiled[] = $line;
		}

		// Bring it back into a single string of lines of code.
		$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); ?".'>';
		}

		// add warning if error detected. uncomment it if you are developing phpBB theme - might be heliful to detect errors in template
		/*if($block_nesting_level > 0)
		{
			$str = '<'.'?php ';
			for($i=0; $i<$block_nesting_level; $i++)
			{
				$str .= '} ';
			}
			$str .= '?'.'>';
			$compiled[] = $str;
			$code_header .= '<center><span class="gen"><b>Warning:</b> these blocks don\'t have match in template file: ';
			foreach($block_items as $var => $value)
			{
				if($value > 0)
				{
					$code_header .= '&lt;-- BEGIN <b>'. $var. '</b> --&gt; ';
				}
				elseif($value < 0)
				{
					$code_header .= '&lt;-- END <b>'. $var. '</b> --&gt; ';
				}
			}
			$code_header .= '</span></center><br clear="all" />';
		}*/

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

	/**
	 * Compiles code and writes to cache if needed
	 */
	function compile2($code, $cache_file = '')
	{
		global $board_config;
// Begin PNphpBB2 Module
 		global $ModName, $board_config;
				
		if (!$board_config['PNphpBB2_Theme'])
		{
  	 	 $search = array ("'<\!doctype\s+.*?>'si",
                 "'<title[^>]*?>.*?</title>'si",
                 "'<head>'i",
                 "'</head>'i",
                 "'<html>'i",
                 "'<html\s+.*?>'i",
                 "'</html>'i",
                 "'body\s*{(\s*[a-zA-Z0-9\-_]*:\s*{[a-zA-Z0-9\-_]*};*)*\s*}'i", // Strip out body segment
								 "'/\*.*?\*/'i",
								 "'font(?!-).*?\s*{(\s*[a-zA-Z0-9\-_]*\s*:\s*{[a-zA-Z0-9\-_]*};*)*\s*}'i",
 							   "'a:(link|active|visited|hover|).*?{(\s*[a-zA-Z0-9\-_]*\s*:\s*.*?;)*\s*}'i",
								 "'<body\s+.*?>'si",	
								 "'</body\s+.*?>'si",
								 "'<font.*?>'i",
                 "'</font>'i",
								 "'<input\s+type\s*=\s*\"?text\"?\s+name\s*=\s*\"?username\"?'i",
								 "'<input\s+type\s*=\s*\"?password\"?\s+name\s*=\s*\"?password\"?'i",
								 "'<input\s+type\s*=\s*\"?checkbox\"?\s+name\s*=\s*\"?autologin\"?'i",
								 "'\.copyright'i",
								 "'<span\s+class\s*=\s*\"?copyright\"?'i");

			 $replace = array ("",
									"",
                  "",
									"",
                  "",
                  "",
                  "",
									"",
									"",
									"",
									"",
									"",
  								"",
									"",
									"",
									'<input type="text" name="uname"',
									'<input type="password" name="pass"',
									'<input type="checkbox" name="rememberme"',
									"",
									'<div class="gensmall" align="center"');
       $code = preg_replace ($search, $replace, $code);
		}

		$search = array ("'<input\s+type\s*=\s*\"?text\"?\s+name\s*=\s*\"?username\"?'i",
						  "'<input\s+type\s*=\s*\"?password\"?\s+name\s*=\s*\"?password\"?'i",
							"'<input\s+type\s*=\s*\"?checkbox\"?\s+name\s*=\s*\"?autologin\"?'i",
							"'\.copyright'i",
							"'<span\s+class\s*=\s*\"?copyright\"?'i");

		$replace = array ('<input type="text" name="uname"',
							 '<input type="password" name="pass"',
							 '<input type="checkbox" name="rememberme"',
							 "",
							 '<div class="gensmall" align="center"');
    $code = preg_replace ($search, $replace, $code);
		
		if (preg_match("/L_CONFIGURATION_TITLE/i", $code))
		{
    	 $code = preg_replace ("'<tr[^>]*>[^<]*(<(?!tr)[^<]*)*(L_SERVER_NAME|L_SERVER_PORT|L_SCRIPT_PATH|L_SITE_NAME|L_SITE_DESCRIPTION|L_ACCT_ACTIVATION|L_DATE_FORMAT|L_SYSTEM_TIMEZONE|L_ENABLE_GZIP|L_COOKIE_SECURE|L_ALLOW_NAME_CHANGE|L_COPPA_SETTINGS|L_COPPA_FAX|L_COPPA_MAIL|L_DEFAULT_LANGUAGE|L_TIMEZONE).*?</tr>'si", "", $code);
		}

		if (preg_match("/L_REGISTRATION_INFO/i", $code))
		{
//		 	 $code = preg_replace ("'<table[^>]*>[^<]*(<(?!table)[^<]*)*(L_REGISTRATION_INFO).*?</table>'si", "", $code);
    	 $code = preg_replace ("'<tr[^>]*>[^<]*(<(?!tr)[^<]*)*(L_REGISTRATION_INFO|L_ITEMS_REQUIRED|L_USERNAME|L_EMAIL_ADDRESS|L_NEW_PASSWORD|L_CONFIRM_PASSWORD|L_ICQ_NUMBER|L_AIM|L_MESSENGER|L_YAHOO|L_WEBSITE|L_LOCATION|L_OCCUPATION|L_INTERESTS|L_BOARD_LANGUAGE|L_DATE_FORMAT|L_TIMEZONE).*?</tr>'si", "", $code);
		}
		
    $code = str_replace("../templates/", "./modules/$ModName/templates/", $code);
    $code = str_replace("\"templates/", "\"./modules/$ModName/templates/", $code);
    $code = str_replace("url(templates", "url(./modules/$ModName/templates", $code);
// End PNphpBB2 Module
		$code = $this->compile_code('', $code, $this->use_isset);
		if($cache_file && !empty($board_config['xs_use_cache']) && !empty($board_config['xs_auto_compile']))
		{
			$this->write_cache($cache_file, $code);
		}
		$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;
	}

	function write_cache($filename, $code)
	{
		// 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))
				{
					return false;
				}
				else
				{
					@chmod($path, 0777);
				}
			}
			if(count($dirs) > 0)
			for($i=0; $i<count($dirs)-1; $i++)
			{
				if($i>0)
				{
					$path .= '/';
				}
				$path .= $dirs[$i];
				if(!@is_dir($path))
				{
					if(!@mkdir($path))
					{
						return false;
					}
					else
					{
						@chmod($path, 0777);
					}
				}
			}
			// try to open file again after directories were created
			$file = @fopen($filename, 'w');
		}
		if(!$file)
		{
			return false;
		}
		fputs($file, $code);
		fclose($file);
		@chmod($filename, 0777);
		return true;
	}

	/**
	 *Removes unnecessary duplicate php tags. can be used to 
     */
	function clear_code($code)
	{
		$search = array("\n?"."><"."?php\n", "\n?"."><"."?php", "?"."><"."?php");
		$replace = array('', '', '');
		return str_replace($search, $replace, $code);
	}

	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'] = $php;
			// adding language variable (eg: "english" or "german")
			// can be used to make truly multi-lingual templates
			$this->vars['LANG'] = $board_config['default_lang'];
			// adding current template
			$tpl = $phpbb_root_path . 'templates/' . $this->tpl . '/';
			if(substr($tpl, 0, 2) === './')
			{
				$tpl = substr($tpl, 2, strlen($tpl));
			}
			$this->vars['TEMPLATE'] = $tpl;
			$this->vars['TEMPLATE_NAME'] = $this->tpl;
			$this->_tpldata['switch_xs_enabled.'] = array('version' => $this->xs_versiontxt);
		}
	}
}

/*

Functions that might be useful to style developers.

*/

// xs_switch(template, key)
//
// function returns true if some key present present.
// what makes it so special is that it can be used inside
// switches so, for example, you can check if user is logged
// in inside <!-- BEGIN postrow --> and so on...
//
// usage example 1:
//  if(xs_switch($this, 'switch_user_logged_in'))
//  {
//    echo 'User logged in';
//  }
// usage example 2:
//  if(xs_switch($this, 'switch_enable_pm_popup'))
//  {
//    echo 'Dude, you have new private message(s)';
//  }
//  else
//  {
//    echo 'Sorry, dude. No new messages (or popup disabled)';
//  }
function xs_switch($tpl, $name)
{
	return (isset($tpl->_tpldata[$name.'.']) && count($tpl->_tpldata[$name.'.']) > 0);
}

?>
