<?php

/**
 * vB_Api_Template
 *
 * @package vBApi
 * @access public
 */
class vB_Api_Template extends vB_Api
{
	protected static $special_templates = array(
	/* None currently exist.
	The dummy stops some crashes */
		'dummy entry',
	);

	protected static $common_templates = array(
		'header',
		'footer',
	);

	private static $templatecache = array();
	private static $bbcode_style = array('code' => -1, 'html' => -1, 'php' => -1, 'quote' => -1);

	protected function __construct()
	{
		parent::__construct();
	}

	/**
	 * Fetch one template based on its name and style ID.
	 *
	 * @param string $template_name Template name.
	 * @param integer $styleid Style ID. If empty, this method will fetch template from default style.
	 * @return mixed
	 */
	public function fetch($template_name, $styleid = -1)
	{
		if (!empty(self::$templatecache[$template_name]))
		{
			return self::$templatecache[$template_name];
		}
		$templates = $this->fetchBulk(array($template_name), $styleid);

		if ($templates[$template_name])
		{
			return $templates[$template_name];
		}

		return false;
	}

	/**
	 * Fetches a bulk of templates from the database
	 *
	 * @param array $template_names List of template names to be fetched.
	 * @param integer $styleid Style ID. If empty, this method will fetch template from default style.
	 *
	 * @return array Array of information about the imported style
	 */
	public function fetchBulk($template_names, $styleid = -1, $type = 'compiled')
	{
		if ($styleid == -1)
		{
			$vboptions = vB::getDatastore()->get_value('options');
			$styleid = $vboptions['styleid'];
		}
		$style = false;

		$response = array();
		foreach ($template_names AS $template)
		{
			// see if we have it in cache already
			if ($type == 'compiled' AND !empty(self::$templatecache[$template]))
			{
				$response[$template] = self::$templatecache[$template];
				continue;
			}
			// load the cache only(once) when we need it
			if (empty($style))
			{
				$style = vB_Library::instance('Style')->fetchStyleRecord($styleid);
				$templateassoc = unserialize($style['templatelist']);
			}
			//handle bad template names -- they should be blank by default.
			if (isset($templateassoc["$template"]))
			{
				$templateids[] = intval($templateassoc["$template"]);
			}
			else
			{
				// @todo: throw an exception if the template doesn't exist and we are in debug mode?
				$response[$template] = '';
			}
		}

		if (!empty($templateids))
		{
			$result = vB::getDbAssertor()->assertQuery('template', array('templateid' => $templateids));

			//vB::getDbAssertor()->assertQuery('template_fetchbyids', array('templateids' => $templateids));

			if ($result->valid())
			{
				$template = $result->current();

				while ($result->valid())
				{
					if ($type == 'compiled')
					{
						$response[$template['title']] = $template['template'];
					}
					else
					{
						$response[$template['title']] = $template['template_un'];
					}
					self::$templatecache[$template['title']] = $template['template'];
					$template = $result->next();
				}
			}
		}

		return $response;
	}


	/**
	 * @deprecated
	 */
	public function bulk_fetch($template_names, $styleid = -1, $type = 'compiled')
	{
		return $this->fetchBulk($template_names, $styleid, $type);
	}

	/**
	 * Get template ID by its template name and style id
	 *
	 * @param $template_name
	 * @param $styleid
	 */
	public function getTemplateID($template_name, $styleid = -1)
	{
		return vB::getDbAssertor()->getField('template', array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			'title' => $template_name,
			'styleid' => $styleid
		));
	}


	/**
	 * Fetch template by its ID
	 *
	 * @param integer $templateid Template ID.
	 *
	 * @return array Return template array if $templateid is valid.
	 */
	public function fetchByID($templateid)
	{
		$templateid = intval($templateid);
		$result = vB::getDbAssertor()->assertQuery('template_fetchbyid', array('templateid' => $templateid));

		if ($result->valid())
		{
			return $result->current();
		}
		else
		{
			throw new vB_Exception_Api('invalidid', array('templateid'));
		}
	}

	/**
	 * Fetch one uncompiled template based on its name and style ID.
	 *
	 * @param string $template_name Template name.
	 * @param integer $styleid Style ID.
	 */
	public function fetchUncompiled($template_name, $styleid = -1)
	{
		$this->checkCanAdminStyles();

		$templates = $this->bulk_fetch(array($template_name), $styleid, 'uncompiled');

		if ($templates[$template_name])
		{
			return $templates[$template_name];
		}

		return false;
	}

	/**
	 * Fetches a number of templates from the database and puts them into the templatecache
	 *
	 * @param	array	List of template names to be fetched
	 * @param	string	Serialized array of template name => template id pairs
	 * @param	bool	Whether to skip adding the bbcode style refs
	 * @param	bool	Whether to force setting the template
	 */
	public function cacheTemplates($templates, $templateidlist, $skip_bbcode_style = false, $force_set = false)
	{
		$vboptions = vB::getDatastore()->get_value('options');
		$templateassoc = unserialize($templateidlist);

		if ($vboptions['legacypostbit'] AND in_array('postbit', $templates))
		{
			$templateassoc['postbit'] = $templateassoc['postbit_legacy'];
		}

		foreach ($templates AS $template)
		{
			$templateids[] = intval($templateassoc["$template"]);
		}

		if (!empty($templateids))
		{
			// run query
			$temps = vB::getDbAssertor()->assertQuery("vBForum:fetchtemplates", array(
					vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED,
					'templateids' => $templateids,
			));

			// cache templates
			foreach ($temps as $temp)
			{
				if (empty(self::$templatecache["$temp[title]"]) OR $force_set)
				{
					self::$templatecache["$temp[title]"] = $temp['template'];
				}
			}
		}

		if (!$skip_bbcode_style)
		{
			self::$bbcode_style = array(
					'code'  => &$templateassoc['bbcode_code_styleid'],
					'html'  => &$templateassoc['bbcode_html_styleid'],
					'php'   => &$templateassoc['bbcode_php_styleid'],
					'quote' => &$templateassoc['bbcode_quote_styleid']
			);
		}
	}


	/**
	 * Insert a new template
	 *
	 * @param integer $dostyleid Style ID which the new template belongs to.
	 * @param string $title Template name.
	 * @param string $content Template content.
	 * @param string $product The product ID which the template belongs to.
	 * @param boolean $savehistory Whether to save the change in template history.
	 * @param string $histcomment Comment of the change to be saved to template history.
	 *
	 * @return integer New inserted template ID.
	 */
	public function insert
	(
		$dostyleid,
		$title,
		$content,
		$product = 'vbulletin',
		$savehistory = false,
		$histcomment = '',
		$forcesaveonerror = false
	)
	{
		$this->checkCanAdminStyles();

		$dostyleid = intval($dostyleid);
		$title = trim($title);
		$content = trim($content);
		$product = trim($product);
		$histcomment = trim($histcomment);
		$userinfo = vB::getCurrentSession()->fetch_userinfo();
		$timenow = vB::getRequest()->getTimeNow();

		if (!$title)
		{
			throw new vB_Exception_Api('please_complete_required_fields');
		}

		require_once(DIR . '/includes/adminfunctions.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		// Compile template
		$template = $this->compile($content, $forcesaveonerror);
		// TODO: Product API
		$full_product_info = fetch_product_list(true);

		$result = vB::getDbAssertor()->assertQuery('template_get_existing', array('title' => $title));

		foreach ($result as $curtemplate)
		{
			$exists["$curtemplate[styleid]"] = $curtemplate;
		}

		// work out what we should be doing with the product field
		if ($exists['-1'] AND $dostyleid != -1)
		{
			// there is already a template with this name in the master set - don't allow a different product id
			$product = $exists['-1']['product'];
		}
		else if ($dostyleid != -1)
		{
			// we are not adding a new template to the master set - only allow the default product id
			$product = 'vbulletin';
		}
		else
		{
			// allow this - we are adding a totally new template to the master set
		}

		// check if template already exists
		if (!$exists[$dostyleid])
		{
			$templateid = vB::getDbAssertor()->assertQuery('template_insert', array(
				'dostyleid' 	=> $dostyleid,
				'title' 		=> $title,
				'template' 		=> $template,
				'template_un' 	=> $content,
				'dateline'		=> $timenow,
				'username'		=> $userinfo['username'],
				'version'		=> $full_product_info[$product]['version'],
				'product'		=> $product,
			));

			// now update the file system if we setup to do so and we are in the master style
			if (defined('DEV_AUTOEXPORT') AND DEV_AUTOEXPORT AND $dostyleid == -1)
			{
				require_once(DIR . '/includes/functions_filesystemxml.php');
				autoexport_write_template(
					$title,
					$content,
					$product,
					$full_product_info[$product]['version'],
					$userinfo['username'],
					$timenow
				);
			}

			// now to update the template id list for this style and all its dependents...
			// TODO: new buildStyle() method for style API
			build_style($dostyleid, $title, array(
				'docss' => 0,
				'dostylevars' => 0,
				'doreplacements' => 0,
				'doposteditor' => 0
			), '', '', false, false);

		}
		else
		{
			throw new vB_Exception_Api('template_x_exists_error', array($title));
		}

		if ($savehistory)
		{
			$result = vB::getDbAssertor()->assertQuery('template_savehistory', array(
				'dostyleid' 	=> $dostyleid,
				'title' 		=> $title,
				'template_un' 	=> $content,
				'dateline'		=> $timenow,
				'username'		=> $userinfo['username'],
				'version'		=> $full_product_info[$product]['version'],
				'comment'		=> $histcomment,
			));
		}

		vB_Library::instance('Style')->setCssDate();

		return $templateid;
	}

	/**
	 * Update a template
	 *
	 * @param integer $templateid Template ID to be updated
	 * @param string $title Template name.
	 * @param string $content Template content.
	 * @param string $product The product ID which the template belongs to.
	 * @param string $oldcontent The content of the template at the time it was loaded.  This is used to prevent
	 *	cases where the template was changed while editing. Pass false to force an update.
	 * @param boolean $savehistory Whether to save the change in template history.
	 * @param string $histcomment Comment of the change to be saved to template history.
	 * @param boolean $forcesaveonerror save the template even though there are errors.
	 */
	public function update
	(
		$templateid,
		$title,
		$content,
		$product,
		$oldcontent,
		$savehistory,
		$histcomment,
		$forcesaveonerror = false
	)
	{
		$this->checkCanAdminStyles();

		$templateid = intval($templateid);
		$title = trim($title);
		$content = trim($content);
		$product = trim($product);
		$histcomment = trim($histcomment);
		$userinfo = vB::getCurrentSession()->fetch_userinfo();
		$timenow = vB::getRequest()->getTimeNow();

		require_once(DIR . '/includes/adminfunctions.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		// Compile template
		$template = $this->compile($content, $forcesaveonerror);

		// TODO: Product API
		$full_product_info = fetch_product_list(true);

		if (!$forcesaveonerror)
		{
			$errors = check_template_conflict_error($template);
			if (!empty($errors))
			{
				throw new vB_Exception_Api('template_conflict_errors', array($errors));
			}
		}

		$old_template = $this->fetchByID($templateid);

		// Test whether the template exists if new template title is not the same as old one's
		if (strtolower($title) != strtolower($old_template['title']))
		{
			$result = vB::getDbAssertor()->assertQuery('template_fetchbystyleandtitle', array(
				'styleid' => $old_template['styleid'],
				'title' => $title,
			));

			if ($result->valid())
			{
				throw new vB_Exception_Api('invalidid', array('templateid'));
			}
		}

		if ($oldcontent === false)
		{
			$hash = md5($old_template['template_un']);
		}
		else
		{
			$hash = md5($oldcontent);
		}

		// Do update
		$result = vB::getDbAssertor()->assertQuery('template_update', array(
			'title' 		=> $title,
			'template' 		=> $template,
			'template_un' 	=> $content,
			'dateline'		=> $timenow,
			'username'		=> $userinfo['username'],
			'version'		=> $full_product_info[$product]['version'],
			'product'		=> $product,
			'hash'			=> $hash,
			'templateid'	=> $templateid,
		));

		if ($result == 0)
		{
			// we have an edit conflict
			throw new vB_Exception_Api('edit_conflict');
		}
		else
		{
			// template saved successfully (no edit conflict)

			// now update the file system if we setup to do so and we are in the master style
			if (defined('DEV_AUTOEXPORT') AND DEV_AUTOEXPORT AND $old_template['styleid'] == -1)
			{
				require_once(DIR . '/includes/functions_filesystemxml.php');
				autoexport_write_template(
					$title,
					$content,
					$product,
					$full_product_info[$product]['version'],
					$userinfo['username'],
					$timenow
				);
			}

			// Remove templatemerge record
			vB::getDbAssertor()->assertQuery('templatemerge',
				array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_DELETE,	'templateid' => $templateid));

			// update any customized templates to reflect a change of product id
			if ($old_template['styleid'] == -1 AND $product != $old_template['product'])
			{
				$result = vB::getDbAssertor()->assertQuery('template_updatecustom_product', array(
					'product'	=> $product,
					'title' 	=> $title,
				));
			}

			if ($savehistory)
			{
				$result = vB::getDbAssertor()->assertQuery('template_savehistory', array(
					'dostyleid' 	=> $old_template['styleid'],
					'title' 		=> $title,
					'template_un' 	=> $content,
					'dateline'		=> $timenow,
					'username'		=> $userinfo['username'],
					'version'		=> $full_product_info[$product]['version'],
					'comment'		=> $histcomment,
				));
			}

			//we need to rebuild the style if a css template is changed, we may need to
			//republish.
			if (preg_match('#\.css$#i', $title))
			{
				build_style($old_template['styleid'], $title, array(
					'docss' => 0,
					'dostylevars' => 0,
					'doreplacements' => 0,
					'doposteditor' => 0
				), '', '', false, false);
			}

			vB_Library::instance('Style')->setCssDate();

			return true;
		}
	}

	/**
	 * Delete a template
	 *
	 * @param integer $templateid Template ID to be deleted.
	 */
	public function delete($templateid)
	{
		$this->checkCanAdminStyles();

		$templateid = intval($templateid);

		$template = $this->fetchByID($templateid);

		if ($template)
		{
			$this->deleteTemplateInternal($templateid);
		}

		if (defined('DEV_AUTOEXPORT') AND DEV_AUTOEXPORT AND $template['styleid'] == -1)
		{
			require_once(DIR . '/includes/functions_filesystemxml.php');
			autoexport_delete_template($template['title']);
		}

		vB_Library::instance('Style')->setCssDate();

		return true;
	}

	/**
	 * Fetch original (not customized) template content
	 *
	 * @param string $title Template name.
	 *
	 * @return array Original template information
	 */
	public function fetchOriginal($title)
	{
		$this->checkCanAdminStyles();

		$title = trim($title);

		$result = vB::getDbAssertor()->assertQuery('template_fetchoriginal', array('title' => $title));

		if ($result->valid())
		{
			return $result->current();
		}
		else
		{
			throw new vB_Exception_Api('invalidid', array('templateid'));
		}
	}

	/**
	 * Find custom templates that need updating
	 *
	 * @return array Templates that need updating.
	 */
	public function findUpdates()
	{
		$this->checkCanAdminStyles();

		require_once(DIR . '/includes/adminfunctions.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		$customcache = fetch_changed_templates();
		// TODO: Product API
		$full_product_info = fetch_product_list(true);

		$stylecache = vB_Library::instance('Style')->fetchStyles();

		$return = array();
		foreach ($stylecache AS $styleid => $style)
		{
			if (is_array($customcache["$styleid"]))
			{
				$return[] = $customcache["$styleid"];
			}
		}

		return $return;
	}

	/**
	 * Dismiss automatical merge
	 *
	 * @param array Template IDs which merge needs to be dismissed.
	 * @return array Number of affected templates.
	 */
	public function dismissMerge($templateids)
	{
		$this->checkCanAdminStyles();

		if (empty($templateids))
		{
			throw new vB_Exception_Api('please_complete_required_fields');
		}

		foreach ($templateids as &$templateid)
		{
			$templateid = intval($templateid);
		}

		$result = vB::getDbAssertor()->assertQuery('template_update_mergestatus', array('templateids' => $templateids));

		vB::getDbAssertor()->assertQuery('templatemerge',
			array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_DELETE,	'templateid' => $templateids));

		return $result;
	}

	/**
	 * Search and fetch a list of templates
	 *
	 * @param integer $dostyleid Style ID to be searched in. -1 means search in all styles.
	 * @param mixed $expandset
	 * @param string $searchstring Search for text.
	 * @param boolean $titlesonly Wether to search template titles (names) only.
	 *
	 * @return mixed false if no templates are found. Otherwise an array will be returned with styleids as its keys.
	 */
	public function search($dostyleid, $expandset = null, $searchstring = '', $titlesonly = true)
	{
		$this->checkCanAdminStyles();

		if ($searchstring)
		{
			$group = 'all';
		}

		if (!empty($expandset))
		{
			$result = vB::getDbAssertor()->assertQuery('template_getmasters');
			foreach ($result as $master)
			{
				$masterset["$master[title]"] = $master['templateid'];
			}
		}
		else
		{
			$masterset = array();
		}

		$stylecache = vB_Library::instance('Style')->fetchStyles();

		$return = array();
		foreach($stylecache AS $styleid => $style)
		{
			if ($styleid == -1)
			{
				$THISstyleid = 0;
				$vbphrase = vB_Api::instanceInternal('phrase')->fetch(array('master_style'));
				$style['title'] = $vbphrase['master_style'];
				$style['templatelist'] = serialize($masterset);
			}
			else
			{
				$THISstyleid = $styleid;
			}

			if ($expandset == 'all' OR $expandset == $styleid)
			{
				$showstyle = 1;
			}
			else
			{
				$showstyle = 0;
			}

			$result = vB::getDbAssertor()->assertQuery('searchTemplates', array(
				vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_METHOD,
				'searchstring' => $searchstring,
				'titlesonly' => $titlesonly,
				'templateids' => implode(',' , unserialize($style['templatelist'])),
			));

			if (!$result->valid())
			{
				return false;
			}

			foreach ($result as $template)
			{
				if ($template['templatetype'] == 'replacement')
				{
					$replacements["$template[templateid]"] = $template;
				}
				else
				{
					// don't show any special templates
					if (in_array($template['title'], vB_Api::instanceInternal('template')->fetchSpecialTemplates()))
					{
						continue;
					}
					else
					{
						$m = substr(strtolower($template['title']), 0, iif($n = strpos($template['title'], '_'), $n, strlen($template['title'])));
						if ($template['styleid'] != -1 AND !isset($masterset["$template[title]"]) AND !isset($only["$m"]))
						{
							$customtemplates["$template[templateid]"] = $template;
						}
						else
						{
							$maintemplates["$template[templateid]"] = $template;
						}
					}
				}

				$return[$styleid]['customtemplates'] = $customtemplates;
				$return[$styleid]['maintemplates'] = $maintemplates;
			}
		}

		return $return;
	}

	/**
	 * Search and Replace templates.
	 *
	 * @param integer $dostyleid Style ID to be searched in. -1 means search in all styles.
	 * @param string $searchstring Search for text.
	 * @param string $replacestring Replace with text.
	 * @param boolean $case_insensitive Case-Insensitive or not.
	 * @param boolean $regex Whether to use regular expressions.
	 * @param boolean $test Test only.
	 * @param integer $startat_style Replacement startat style ID.
	 * @param integer $startat_template Replacement startat template ID.
	 *
	 * @return mixed False if no templates found. Otherwise an array will be returned.
	 */
	public function searchAndReplace(
		$dostyleid,
		$searchstring,
		$replacestring,
		$case_insensitive,
		$regex,
		$test,
		$startat_style,
		$startat_template
	)
	{
		$this->checkCanAdminStyles();
		require_once(DIR . '/includes/adminfunctions.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		// TODO: Product API
		$full_product_info = fetch_product_list(true);

		$vb5_config = &vB::getConfig();

		$perpage = 50;
		$searchstring = str_replace(chr(0), '', $searchstring);

		if (empty($searchstring))
		{
			throw new vB_Exception_Api('please_complete_required_fields');
		}

		$limit_style = $startat_style;
		$conditions = array();
		if ($dostyleid == -1)
		{
			if ($vb5_config['Misc']['debug'])
			{
				$conditions[] = array('field' => 'styleid', 'value' => -2, 'operator' => vB_dB_Query::OPERATOR_NE);
				if ($startat_style == 0)
				{
					$editmaster = true;
				}
				else
				{
					$limit_style--; // since 0 means the master style, we have to renormalize
				}
			}
			else
			{
				$conditions[] = array('field' => 'styleid', 'value' => 0, 'operator' => vB_dB_Query::OPERATOR_GT);
			}
		}
		else
		{
			$conditions['styleid'] = $dostyleid;
		}

		if ($editmaster != true)
		{
			$result = vB::getDbAssertor()->assertQuery('getStyleByConds', array(
				vB_Db_Query::TYPE_KEY => vB_Db_Query::QUERY_METHOD,
				'conds' => $conditions,
				'limit_style' => $limit_style
			));

			if (!$result->valid())
			{
				// couldn't grab a style, so we're done
				return false;
			}
			$styleinfo = $result->current();
			$templatelist = unserialize($styleinfo['templatelist']);
		}
		else
		{
			$styleinfo = array(
				'styleid' => -1,
				'title' => 'MASTER STYLE'
			);
			$templatelist = array();

			$result = vB::getDbAssertor()->assertQuery('template_getmasters2');
			foreach ($result as $tid)
			{
				$templatelist["$tid[title]"] = $tid['templateid'];
			}
			$styleinfo['templatelist'] = serialize($templatelist); // for sanity
		}

		$loopend = $startat_template + $perpage;
		$process_templates = array(0);
		$i = 0;

		foreach ($templatelist AS $title => $tid)
		{
			if ($i >= $startat_template AND $i < $loopend)
			{
				$process_templates[] = $tid;
			}
			if ($i >= $loopend)
			{
				break;
			}
			$i++;
		}
		if ($i != $loopend)
		{
			// didn't get the $perpage templates, so we're done with this style
			$styledone = true;
		}
		else
		{
			$styledone = false;
		}

		$templates = vB::getDbAssertor()->assertQuery('template', array(
				vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
				'templateid' => $process_templates
			)
		);
		$stats['page'] = $startat_template / $perpage + 1;
		$stats['first'] = $startat_template + 1;
		$count = 0;
		$processed_templates = array();
		foreach ($templates as $temp)
		{
			$count ++;
			$insensitive_mod = ($case_insensitive ? 'i' : '');

			if ($test)
			{
				if ($regex)
				{
					$encodedsearchstr = str_replace('(?&lt;', '(?<', htmlspecialchars_uni($searchstring));
				}
				else
				{
					$encodedsearchstr = preg_quote(htmlspecialchars_uni($searchstring), '#');
				}

				$newtemplate = preg_replace("#$encodedsearchstr#sU$insensitive_mod",
					'<span class="col-i" style="text-decoration:underline;">' .
					htmlspecialchars_uni($replacestring) . '</span>', htmlspecialchars_uni($temp['template_un']));

				if ($newtemplate != htmlspecialchars_uni($temp['template_un']))
				{
					$temp['newtemplate'] = $newtemplate;
					$processed_templates[] = $temp;
				}
				else
				{
					continue;
				}
			}
			else
			{
				if ($regex)
				{
					$newtemplate = preg_replace("#" . $searchstring . "#sU$insensitive_mod", $replacestring, $temp['template_un']);
				}
				else
				{
					$usedstr = preg_quote($searchstring, '#');
					$newtemplate = preg_replace("#$usedstr#sU$insensitive_mod", $replacestring, $temp['template_un']);
				}

				if ($newtemplate != $temp['template_un'])
				{
					if ($temp['styleid'] == $styleinfo['styleid'])
					{
						vB::getDbAssertor()->assertQuery('template_update2', array(
							'template' 		=> $this->compile($newtemplate, true),
							'template_un' 	=> $newtemplate,
							'dateline'		=> TIMENOW,
							'username'		=> $vbulletin->userinfo['username'], // TODO: User API
							'version'		=> $full_product_info["$temp[product]"]['version'],
							'templateid'	=> $temp['templateid']
						));

						// now update the file system if we setup to do so and we are in the master style
						if (defined('DEV_AUTOEXPORT') AND DEV_AUTOEXPORT AND $temp['styleid'] == -1)
						{
							require_once(DIR . '/includes/functions_filesystemxml.php');
							autoexport_write_template(
								$temp['title'],
								$newtemplate,
								$temp['product'],
								$full_product_info["$temp[product]"]['version'],
								$vbulletin->userinfo['username'],
								TIMENOW
							);
						}

						vB::getDbAssertor()->assertQuery('templatemerge',
							array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_DELETE,	'templateid' => $temp['templateid']));
					}
					else
					{
						/*insert query*/
						$result = vB::getDbAssertor()->assertQuery('template_insert', array(
							'dostyleid' 	=> $styleinfo['styleid'],
							'title' 		=> $temp['title'],
							'template' 		=> $this->compile($newtemplate, true),
							'template_un' 	=> $newtemplate,
							'dateline'		=> TIMENOW,
							'username'		=> $vbulletin->userinfo['username'], // TODO: User API
							'version'		=> $full_product_info["$temp[product]"]['version'],
							'product'		=> $temp['product'],
						));

						$requirerebuild = true;
					}
					$temp['newtemplate'] = $newtemplate;
					$processed_templates[] = $temp;
					vB_Library::instance('Style')->setCssDate();
				}
				else
				{
					continue;
				}
			}
		} // End foreach
		$stats['last'] = $startat_template + $count;

		if ($styledone == true)
		{
			// Go to the next style. If we're only doing replacements in one style,
			// this will trigger the finished message.
			$startat_style++;
			$loopend = 0;
		}

		return array(
			'processed_templates'	=> $processed_templates,
			'startat_style' 		=> $startat_style,
			'startat_template'		=> $loopend,
			'requirerebuild'		=> $requirerebuild,
			'styleinfo'				=> $styleinfo,
			'stats'					=> $stats,
		);
	}

	/**
	 * Revert all templates in a style
	 *
	 * @param integer $dostyleid Style ID where the custom templates in it will be reverted
	 *
	 * @return boolean False if nothing to do.
	 */
	public function revertAllInStyle($dostyleid)
	{
		$this->checkCanAdminStyles();

		if ($dostyleid == -1)
		{
			throw new vB_Exception_Api('invalid_style_specified');
		}

		$style = vB_Library::instance('Style')->fetchStyleByID($dostyleid);

		if (!$style)
		{
			throw new vB_Exception_Api('invalid_style_specified');
		}

		if (!$style['parentlist'])
		{
			$style['parentlist'] = '-1';
		}
		else if (is_string($style['parentlist']))
		{
			$style['parentlist'] = explode(',', $style['parentlist']);
		}

		$result = vB::getDbAssertor()->assertQuery('template_getrevertingtemplates', array(
			'styleparentlist' => $style['parentlist'],
			'styleid'	=> $style['styleid'],
		));

		if ($result->valid())
		{
			$deletetemplates = array();
			foreach ($result as $template)
			{
				$deletetemplates["$template[title]"] = $template['templateid'];
			}

			if (!empty($deletetemplates))
			{
				$this->deleteTemplateInternal($deletetemplates);

				build_style($style['styleid'], $style['title'], array(
					'docss' => 0,
					'dostylevars' => 0,
					'doreplacements' => 0,
					'doposteditor' => 0
				), '', '', false, false);

				return true;
			}
		}

		vB_Library::instance('Style')->setCssDate();

		return false;
	}

	/**
	 * Massive merge templates
	 *
	 * @param string $product Product string ID.
	 * @param integer $startat Start offset of the merge.
	 *
	 * @return integer New startat value. -1 if no more to do.
	 */
	public function massMerge($product = 'vbulletin', $startat = 0)
	{
		$this->checkCanAdminStyles();

		require_once(DIR . '/includes/adminfunctions.php');

		// TODO: Product API
		$full_product_info = fetch_product_list(true);
		$vbulletin = &vB::get_registry();
		require_once(DIR . '/includes/class_template_merge.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		$merge = new vB_Template_Merge($vbulletin);
		$merge->time_limit = 5;

		$merge_data = new vB_Template_Merge_Data($vbulletin);
		$merge_data->start_offset = $startat;

		if ($product == 'vbulletin' OR !$product)
		{
			$merge_data->add_condition("tnewmaster.product IN ('', 'vbulletin')");
		}
		else
		{
			$merge_data->add_condition("tnewmaster.product = '" . mysql_escape_string($product) . "'");

			$merge->merge_version = $full_product_info[$product]['version'];
		}

		$completed = $merge->merge_templates($merge_data, $output);

		if ($completed)
		{
			// completed
			build_all_styles();

			vB_Library::instance('Style')->setCssDate();

			return -1;
		}
		else
		{
			return $merge_data->start_offset + $merge->fetch_processed_count();
		}
	}

	/**
	 * Return editing history of a template, including old versions and diffs between versions
	 *
	 * @param string $title Template name.
	 * @param integer $dostyleid Style ID of the template.
	 *
	 * @return array Array of template history revisions.
	 */
	public function history($title, $dostyleid)
	{
		$this->checkCanAdminStyles();

		$revisions = array();
		$have_cur_def = false;
		$cur_temp_time = 0;

		$current_temps = vB::getDbAssertor()->assertQuery('template_fetchbystyleandtitle2', array(
			'title' => $title,
			'styleid' => $dostyleid
		));
		foreach ($current_temps as $template)
		{
			$template['type'] = 'current';

			// the point of the second part of this key is to prevent dateline
			// collisions, as rare as that may be
			$revisions["$template[dateline]|b$template[templateid]"] = $template;

			if ($template['styleid'] == -1)
			{
				$have_cur_def = true;
			}
			else
			{
				$cur_temp_time = $template['dateline'];
			}
		}

		$historical_temps = vB::getDbAssertor()->getRows('templatehistory',
			array('title' => $title, 'styleid' => array(-1, $dostyleid)));
		$history_count = count($historical_temps);
		foreach ($historical_temps as $template)
		{
			$template['type'] = 'historical';

			// the point of the second part of this key is to prevent dateline
			// collisions, as rare as that may be
			$revisions["$template[dateline]|a$template[templatehistoryid]"] = $template;
		}

		// I used a/b above, so current versions sort above historical versions
		usort($revisions, "history_compare");

		return $revisions;
	}

	/**
	 * Return editing history of a template by its ID, including old versions and diffs between versions
	 *
	 * @param integer $templateid Template ID.
	 *
	 * @return array Array of template history revisions.
	 */
	public function historyByTemplateID($templateid)
	{
		$this->checkCanAdminStyles();

		$template = $this->fetchByID($templateid);
		return $this->history($template['title'], $template['styleid']);
	}

	/**
	 * Fetch current or historical uncompiled version of a template
	 *
	 * @param integer The ID (in the appropriate table) of the record you want to fetch.
	 * @param string Type of template you want to fetch; should be "current" or "historical"
	 */
	public function fetchVersion($historyid, $type)
	{
		$this->checkCanAdminStyles();

		require_once(DIR . '/includes/adminfunctions_template.php');

		$template = fetch_template_current_historical($historyid, $type);

		return $template;
	}

	/**
	 * Delete template history versions
	 *
	 * @param array $historyids History IDs to be deleted
	 */
	public function deleteHistoryVersion($historyids)
	{
		$this->checkCanAdminStyles();

		if (!is_array($historyids))
		{
			throw new vB_Exception_Api('please_complete_required_fields');
		}

		vB::getDbAssertor()->assertQuery('templatehistory', array(
			vB_Db_Query::TYPE_KEY => vB_Db_Query::QUERY_DELETE,
			'templatehistoryid' => $historyids
		));
	}

	/**
	 * Compile a template.
	 *
	 * @param string $template_un The uncompiled content of a template.
	 */
	protected function compile($template, $forcesaveonerror)
	{
		// @todo
		// Incorrect hack warning!!!
		// The legacy code in class_template_parser.php needs this to be set
		// but it apparrently does not actually need to be an instance of the
		// legacy db class for purposes of compiling a template.
		if (empty($GLOBALS['vbulletin']->db))
		{
			$GLOBALS['vbulletin']->db = false;
		}

		require_once(DIR . '/includes/class_template_parser.php');
		$parser = new vB_TemplateParser($template);

		try
		{
			$parser->validate($errors);
		}
		catch (vB_Exception_TemplateFatalError $e)
		{
			throw new vB_Exception_Api($e->getMessage());
		}

		$template = $parser->compile();

		// This is a comment from vB4 moved here.  Need to figure out what replace_template_variables
		// is supposed to do.
		// TODO: Reimplement these - if done, $session[], $bbuserinfo[], $vboptions
		// will parse in the template without using {vb:raw, which isn't what we
		// necessarily want to happen
		/*
		if (!function_exists('replace_template_variables'))
		{
			require_once(DIR . '/includes/functions_misc.php');
		}
		$template = replace_template_variables($template, false);
		*/

		if (function_exists('verify_demo_template'))
		{
			verify_demo_template($template);
		}

		// Legacy Hook 'template_compile' Removed //

		if (!$forcesaveonerror AND !empty($errors))
		{
			throw new vB_Exception_Api('template_compile_error', array($errors));
		}

		//extra set of error checking.  This can be skipped in many situations.
		if (!$forcesaveonerror)
		{
			$errors = check_template_errors($template);
			if (!empty($errors))
			{
				throw new vB_Exception_Api('template_eval_error', array($errors));
			}
		}

		return $template;
	}


	protected function checkCanAdminStyles()
	{
		$usercontext = &vB::getUserContext();
		if (!$usercontext->hasAdminPermission('canadminstyles'))
		{
			$loginuser = &vB::getCurrentSession()->fetch_userinfo();
			throw new vB_Exception_Api('nopermission_loggedin',
				array(
					$loginuser['username'],
					vB_Template_Runtime::fetchStyleVar('right'),
					vB::getCurrentSession()->get('sessionurl'),
					$loginuser['securitytoken'],
					class_exists('vB5_Config') ? vB5_Config::instance()->baseurl : vB::getDatastore()->getOption['bburl']
					//fetch_seo_url('forumhome', array())
				)
			);
		}
	}

	public function fetchSpecialTemplates()
	{
		return self::$special_templates;
	}

	public function fetchCommonTemplates()
	{
		return self::$common_templates;
	}

	private function buildHooklist()
	{
		$hooks = array();
		$templateHooks = vB::getDatastore()->get_value('hooks');
		
		if ($templateHooks)
		{
			foreach ($templateHooks as $hook)
			{
				$hooks[$hook['hookname']][][$hook['template']] = $hook['arguments'] ? unserialize($hook['arguments']) : array();
			}
		}

		return $hooks;
	}

	public function fetchTemplateHooks($hookName)
	{
		static $hooklist, $hooks_set;
		
		$vboptions = vB::getDatastore()->get_value('options');

		if (!$vboptions['enablehooks'] OR defined('DISABLE_HOOKS'))
		{
			return false;
		}

		if (!$hooks_set)
		{
			$hooks_set = true;
			$hooklist = $this->buildHooklist();
		}

		if (isset($hooklist[$hookName]))
		{
			return $hooklist[$hookName];
		}
		else
		{
			return false;
		}
	}

	//make sure that we handle all of the associated operations when we delete a template
	protected function deleteTemplateInternal($templateids)
	{
		vB_Library::instance('Style')->setCssDate();

		vB::getDbAssertor()->assertQuery('template',
			array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_DELETE,	'templateid' => $templateids));

		vB::getDbAssertor()->assertQuery('templatemerge',
			array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_DELETE,	'templateid' => $templateids));
	}
}
