<?php
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 4.0.3
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2010 vBulletin Solutions Inc. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

if (!isset($GLOBALS['vbulletin']->db))
{
	exit;
}

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

@set_time_limit(0);

/**
* Class to act as a wrapper for mass (data-based) template merges. Its primary
* usage is during a template XML import.
*
* @package vBulletin
*/
class vB_Template_Merge
{
	/**
	* Registry object
	*
	* @var	vB_Registry
	*/
	protected $registry;

	/**
	* Number of templates processed in this invokation. This includes every
	* template returned from the data object, even those that it decides to skip.
	*
	* @var	int
	*/
	protected $processed_count = 0;

	/**
	* Microtime when the merge was started. Allows for time-based loop breaking.
	*
	* @var	int
	*/
	protected $start_time = 0;

	/**
	* Time limit (in seconds) to limit a merge run to. Use this to paginate
	* the merging process.
	*
	* @var	float
	*/
	public $time_limit = 0;

	/**
	* Number of records to offset from the first match. Use this to start
	* the second (or later) page of a merge set.
	*
	* @var	int
	*/
	public $start_offset = 0;

	/**
	* Controls whether text should be output during the merge process.
	*
	* @var	bool
	*/
	public $show_output = true;

	/**
	* Sets the version written to the template when a successful merge happens.
	*
	* @var	string
	*/
	public $merge_version = '';

	/**
	* Constructor.
	*
	* @param	vB_Registry	Main registry object
	*/
	public function __construct(vB_Registry $registry)
	{
		$this->registry = $registry;
		$this->merge_version = $registry->options['templateversion'];
	}

	/**
	* Actively merge the set of templates matched by the data object.
	* May be paginated by setting members correctly. Does not error;
	* return value indicates whether more data needs to be processed.
	*
	* @param	vB_Template_Merge_Data	Potential merge candidates
	*
	* @return	bool					True when all templates are processed, false when more remain
	*/
	public function merge_templates(vB_Template_Merge_Data $data)
	{
		$candidates = $data->fetch_merge_candidates();

		$this->merge_start();

		$this->processed_count = 0;

		while ($template = $this->registry->db->fetch_array($candidates))
		{
			$this->processed_count++;

			if (!$data->can_merge_template($template))
			{
				continue;
			}

			$merge = new vB_Text_Merge_Threeway(
				$template['oldmastertext'],
				$template['newmastertext'],
				$template['customtext'],
				$this->show_output
			);
			$merged_text = $merge->get_merged();

			if ($merged_text)
			{
				$this->merge_success($merged_text, $template);
			}
			else
			{
				$this->merge_conflict($template);
			}

			if ($this->break_merge_early())
			{
				return false;
			}
		}

		return true;
	}

	/**
	* Merge setup method. By default, simply starts the timer.
	*/
	protected function merge_start()
	{
		$this->start_time = microtime(true);
	}

	/**
	* Determines whether the merge process should be broken early.
	*
	* @return	bool	True to break early, false to continue
	*/
	protected function break_merge_early()
	{
		if ($this->time_limit)
		{
			return ((microtime(true) - $this->start_time) > $this->time_limit);
		}

		return false;
	}

	/**
	* Called when a merge succeeds (no conflicts).
	*
	* @param	string	Final merged text
	* @param	array	Array of template info. Record returned by data method.
	*/
	protected function merge_success($merged_text, $template_info)
	{
		global $vbphrase;
		$db = $this->registry->db;

		$db->query_write("
			INSERT INTO " . TABLE_PREFIX . "templatehistory
				(styleid, title, template, dateline, username, version)
			VALUES
				($template_info[styleid],
				'" . $db->escape_string($template_info['title']) . "',
				'" . $db->escape_string($template_info['customtext']) . "',
				$template_info[dateline],
				'" . $db->escape_string($template_info['username']) . "',
				'" . $db->escape_string($template_info['version']) . "')
		");
		$savedtemplateid = $db->insert_id();

		$db->query_write("
			REPLACE INTO " . TABLE_PREFIX . "templatemerge
				(templateid, template, version, savedtemplateid)
			VALUES
				($template_info[templateid],
				'" . $db->escape_string($template_info['oldmastertext']) . "',
				'" . $db->escape_string($template_info['oldmasterversion']) . "',
				" . intval($savedtemplateid) . "
				)
		");

		$db->query_write("
			UPDATE " . TABLE_PREFIX . "template SET
				styleid = $template_info[styleid],
				templatetype = 'template',
				title = '" . $db->escape_string($template_info['title']) . "',
				template = '" . $db->escape_string(compile_template($merged_text)) . "',
				template_un = '" . $db->escape_string($merged_text) . "',
				dateline = " . TIMENOW . ",
				username = '" . $db->escape_string(!empty($vbphrase['system']) ? $vbphrase['system'] : 'System') . "',
				version = '" . $db->escape_string($this->merge_version) . "',
				product = '" . $db->escape_string($template_info['product']) . "',
				mergestatus = 'merged'
			WHERE
				templateid = " . intval($template_info["templateid"])
		);



		if ($this->show_output)
		{
			$this->output_merge_success($merged_text, $template_info);
		}
	}

	/**
	* If output is enabled, called to output info about a successful merge.
	*
	* @param	string	Final merged text
	* @param	array	Array of template info. Record returned by data method.
	*/
	protected function output_merge_success($merged_text, $template_info)
	{
		global $vbphrase;

		echo "<div>" . construct_phrase($vbphrase['template_merged_x_y'], $template_info['title'], $template_info['styleid']) . "</div>";
		// Send some output to browser. See bug #34585
		vbflush();
	}

	/**
	* Called when a merge fails (conflicts).
	*
	* @param	array	Array of template info. Record returned by data method.
	*/
	protected function merge_conflict($template_info)
	{
		$db = $this->registry->db;

		$db->query_write("
			INSERT IGNORE INTO " . TABLE_PREFIX . "templatemerge
				(templateid, template, version)
			VALUES
				($template_info[templateid],
				'" . $db->escape_string($template_info['oldmastertext']) . "',
				'" . $db->escape_string($template_info['oldmasterversion']) . "')
		");

		$db->query_write("
			UPDATE " . TABLE_PREFIX . "template SET
				mergestatus = 'conflicted'
			WHERE templateid = $template_info[templateid]
		");

		if ($this->show_output)
		{
			$this->output_merge_conflict($template_info);
		}
	}

	/**
	* If output is enabled, called to output info about a failed merge.
	*
	* @param	array	Array of template info. Record returned by data method.
	*/
	protected function output_merge_conflict($template_info)
	{
		global $vbphrase;

		echo "<div>" . construct_phrase($vbphrase['template_conflict_x_y'], $template_info['title'], $template_info['styleid']) . "</div>";
		// Send some output to browser. See bug #34585
		vbflush();
	}

	/**
	* Returns the number of templates processed in this execution.
	*
	* @return	int
	*/
	function fetch_processed_count()
	{
		return $this->processed_count;
	}
}

/**
* Class that separates the data retrival aspect from merging for easier variation
* and reduced dependencies.
*
* @package	vBulletin
*/
class vB_Template_Merge_Data
{
	/**
	* Registry object
	*
	* @var	vB_Registry
	*/
	protected $registry;

	/**
	* Array of extra conditions to add to the data fetch
	*
	* @var	array
	*/
	protected $conditions = array();

	/**
	* Number of records to offset from the first match. Use this to start
	* the second (or later) page of a merge set.
	*
	* @var	int
	*/
	public $start_offset = 0;

	/**
	* Constructor.
	*
	* @param	vB_Registry	Main registry object
	*/
	public function __construct(vB_Registry $registry)
	{
		$this->registry = $registry;
	}

	/**
	* Adds an additional condition to the data fetch query.
	* The condition is just raw SQL put together with AND's.
	*
	* @param	string	Condition text
	*/
	public function add_condition($condition)
	{
		$this->conditions[] = $condition;
	}

	/**
	* Fetches the result set containing all (remaining) merge candidates.
	* Can be offset from the start via the start_offset member. Needs to return
	* information about the custom, origin (old), and new versions of a particular
	* template.
	*
	* @return	resource	DB result object
	*/
	public function fetch_merge_candidates()
	{
		// see top of adminfunctions_templates.php
		global $_query_special_templates;

		// there are some "templates" that aren't normally editable -- don't try to merge them
		$title_skip = ($_query_special_templates ?
			"AND tcustom.title NOT IN ('" . implode("','", $_query_special_templates) . "')" :
			''
		);

		$extra_conditions = ($this->conditions ?
			"AND (" . implode(') AND (', $this->conditions) . ")" :
			''
		);

		return $this->registry->db->query_read("
			SELECT tcustom.*,
				tcustom.template_un AS customtext,
				tnewmaster.version AS newmasterversion, tnewmaster.template_un AS newmastertext,
				toldmaster.version AS oldmasterversion, toldmaster.template_un AS oldmastertext
			FROM " . TABLE_PREFIX . "template AS tcustom
			INNER JOIN " . TABLE_PREFIX . "template AS tnewmaster ON
				(tcustom.title = tnewmaster.title AND tnewmaster.styleid = -1 AND tnewmaster.templatetype = 'template')
			INNER JOIN " . TABLE_PREFIX . "template AS toldmaster ON
				(tcustom.title = toldmaster.title AND toldmaster.styleid = -10 AND toldmaster.templatetype = 'template'
				AND toldmaster.version <> tnewmaster.version)
			WHERE tcustom.styleid > 0
				AND tcustom.templatetype = 'template'
				AND tcustom.mergestatus <> 'conflicted'
				$title_skip
				$extra_conditions
			ORDER BY tcustom.styleid, tcustom.title
			LIMIT " . intval($this->start_offset) . ", 999999
		");
	}

	/**
	* Determines whether a merge should be attempted on a template.
	*
	* @param	array	Array of template info. Record returned by data method.
	*
	* @return	bool	True if a merge should be attempted.
	*/
	public function can_merge_template($template_info)
	{
		if ($template_info['mergestatus'] == 'conflicted')
		{
			return false;
		}

		return is_newer_version($template_info['newmasterversion'], $template_info['oldmasterversion']);
	}
}


/*======================================================================*\
|| ####################################################################
|| # CVS: $RCSfile$ - $Revision: 27819 $
|| ####################################################################
\*======================================================================*/
