<?php

/**
 * Model for route prefixes.
 *
 * @package XenForo_RoutePrefixes
 */
class XenForo_Model_RoutePrefix extends XenForo_Model
{
	/**
	 * Retrieves all the prefixes for the specified route type.
	 *
	 * @param string $type Type of route (public or admin)
	 * @param boolean $getPrefixesForCache If true, excludes route prefixes from add-ons that are disabled
	 *
	 * @return array Format: [original prefix] => info
	 */
	public function getPrefixesByRouteType($type, $getPrefixesForCache = false)
	{
		if ($getPrefixesForCache)
		{
			$selectFields = ', addon.active';
			$joinTables = 'LEFT JOIN xf_addon AS addon ON (addon.addon_id = route_prefix.addon_id)';
			$conditions = 'AND (addon.active IS NULL OR addon.active = 1)';
		}
		else
		{
			$selectFields = '';
			$joinTables = '';
			$conditions = '';
		}

		return $this->fetchAllKeyed('
			SELECT route_prefix.*
			' . $selectFields . '
			FROM xf_route_prefix AS route_prefix
			' . $joinTables . '
			WHERE route_prefix.route_type = ?
			' . $conditions . '
			ORDER BY route_prefix.original_prefix
		', 'original_prefix', $type);
	}

	/**
	 * Gets the prefix data for the specified route type that will be used in the
	 * route cache.
	 *
	 * @param string $type
	 *
	 * @return array Format: [original prefix] => [prefix, route_class, build_link]
	 */
	public function getPrefixesForRouteCache($type)
	{
		$info = $this->getPrefixesByRouteType($type, true);

		$output = array();
		foreach ($info AS $prefixInfo)
		{
			$output[$prefixInfo['original_prefix']] = array(
				'route_class' => $prefixInfo['route_class'],
				'build_link' => $prefixInfo['build_link']
			);
		}

		return $output;
	}

	/**
	 * Gets all prefixes, grouped by the route type they belong to and
	 * keyed by their original prefix values.
	 *
	 * @return array Format: [route type][original prefix] => info
	 */
	public function getAllPrefixesGroupedByRouteType()
	{
		$prefixes = array();
		foreach ($this->_getRoutePrefixTypes() AS $routeType)
		{
			$prefixes[$routeType] = $this->getPrefixesByRouteType($routeType);
		}

		return $prefixes;
	}

	/**
	 * Gets the named prefix in the specified route type by the original prefix value.
	 *
	 * @param string $prefix Original route prefix value
	 * @param string $routeType Route type (public or admin)
	 *
	 * @return array|false
	 */
	public function getPrefixByOriginal($prefix, $routeType)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_route_prefix
			WHERE original_prefix = ?
				AND route_type = ?
		', array($prefix, $routeType));
	}

	/**
	 * Gets the default route prefix array. Useful when adding a prefix.
	 *
	 * @return array
	 */
	public function getDefaultRoutePrefix()
	{
		return array(
			'route_type' => '',
			'route_class' => '',
			'original_prefix' => '',
			'build_link' => 'data_only',
			'addon_id' => null // must fail isset()
		);
	}

	/**
	 * Gets all prefixes that belong to the specified add-on, grouped by the
	 * route type.
	 *
	 * @param string $addOnId
	 *
	 * @return array Format: [route type][original prefix] => info
	 */
	public function getPrefixesByAddOnGroupedByRouteType($addOnId)
	{
		$output = array();

		$prefixResult = $this->_getDb()->query('
			SELECT *
			FROM xf_route_prefix
			WHERE addon_id = ?
			ORDER BY original_prefix
		', $addOnId);
		while ($prefix = $prefixResult->fetch())
		{
			$output[$prefix['route_type']][$prefix['original_prefix']] = $prefix;
		}

		return $output;
	}

	/**
	 * Gets the file name for the development output.
	 *
	 * @return string
	 */
	public function getPrefixDevelopmentFileName()
	{
		$config = XenForo_Application::get('config');
		if (!$config->debug || !$config->development->directory)
		{
			return '';
		}

		return XenForo_Application::getInstance()->getRootDir()
			. '/' . $config->development->directory . '/file_output/route_prefixes.xml';
	}

	/**
	 * Determines if the prefix development file is writable. If the file
	 * does not exist, it checks whether the parent directory is writable.
	 *
	 * @param $fileName
	 *
	 * @return boolean
	 */
	public function canWritePrefixDevelopmentFile($fileName)
	{
		if (file_exists($fileName))
		{
			return is_writable($fileName);
		}
		else
		{
			return is_writable(dirname($fileName));
		}
	}

	/**
	 * Gets the DOM document that represents the prefix development file.
	 * This must be turned into XML (or HTML) by the caller.
	 *
	 * @return DOMDocument
	 */
	public function getPrefixesDevelopmentXml()
	{
		$document = new DOMDocument('1.0', 'utf-8');
		$document->formatOutput = true;
		$rootNode = $document->createElement('route_prefixes');
		$document->appendChild($rootNode);

		$this->appendPrefixesAddOnXml($rootNode, 'XenForo');

		return $document;
	}

	/**
	 * Appends the add-on route prefix XML to a given DOM element.
	 *
	 * @param DOMElement $rootNode Node to append all prefix elements to
	 * @param string $addOnId Add-on ID to be exported
	 */
	public function appendPrefixesAddOnXml(DOMElement $rootNode, $addOnId)
	{
		$prefixes = $this->getPrefixesByAddOnGroupedByRouteType($addOnId);

		$document = $rootNode->ownerDocument;

		foreach ($this->_getRoutePrefixTypes() AS $type)
		{
			if (empty($prefixes[$type]))
			{
				continue;
			}

			$typeNode = $document->createElement('route_type');
			$typeNode->setAttribute('type', $type);
			$rootNode->appendChild($typeNode);

			foreach ($prefixes[$type] AS $prefix)
			{
				$prefixNode = $document->createElement('prefix');
				$prefixNode->setAttribute('original_prefix', $prefix['original_prefix']);
				$prefixNode->setAttribute('class', $prefix['route_class']);
				$prefixNode->setAttribute('build_link', $prefix['build_link']);

				$typeNode->appendChild($prefixNode);
			}
		}
	}

	/**
	 * Deletes the route prefixes that belong to the specified add-on.
	 *
	 * @param string $addOnId
	 */
	public function deletePrefixesForAddOn($addOnId)
	{
		$db = $this->_getDb();
		$db->delete('xf_route_prefix', 'addon_id = ' . $db->quote($addOnId));
	}

	/**
	 * Imports prefixes from the development XML format. This will overwrite all prefixes.
	 *
	 * @param string $fileName
	 */
	public function importPrefixesDevelopmentXml($fileName)
	{
		$document = XenForo_Helper_DevelopmentXml::scanFile($fileName);
		$this->importPrefixesAddOnXml($document, 'XenForo');
	}

	/**
	 * Imports the add-on route prefixes XML.
	 *
	 * @param SimpleXMLElement $xml XML element pointing to the root of the prefix data
	 * @param string $addOnId Add-on to import for
	 */
	public function importPrefixesAddOnXml(SimpleXMLElement $xml, $addOnId)
	{
		$db = $this->_getDb();

		$currentPrefixes = $this->getAllPrefixesGroupedByRouteType();

		XenForo_Db::beginTransaction($db);
		$this->deletePrefixesForAddOn($addOnId);

		$routeTypes = XenForo_Helper_DevelopmentXml::fixPhpBug50670($xml->route_type);
		foreach ($routeTypes AS $typeXml)
		{
			$type = (string)$typeXml['type'];
			if (!$type)
			{
				continue;
			}

			$conflictPrefixes = $this->getPrefixesByRouteType($type);

			$types = XenForo_Helper_DevelopmentXml::fixPhpBug50670($typeXml->prefix);
			foreach ($types AS $prefix)
			{
				$originalPrefixValue = (string)$prefix['original_prefix'];

				$prefixInfo = array(
					'route_type' => $type,
					'route_class' => (string)$prefix['class'],
					'original_prefix' => $originalPrefixValue,
					'build_link' => (string)$prefix['build_link'],
					'addon_id' => $addOnId
				);

				$dw = XenForo_DataWriter::create('XenForo_DataWriter_RoutePrefix');
				if (isset($conflictPrefixes[$originalPrefixValue]))
				{
					$dw->setExistingData($conflictPrefixes[$originalPrefixValue], true);
				}
				$dw->setOption(XenForo_DataWriter_RoutePrefix::OPTION_REBUILD_CACHE, false);
				$dw->bulkSet($prefixInfo);
				$dw->save();
			}
		}

		$this->rebuildRoutePrefixCache();

		XenForo_Db::commit($db);
	}

	/**
	 * Gets all valid route prefix types.
	 *
	 * @return array
	 */
	protected function _getRoutePrefixTypes()
	{
		return array('admin', 'public');
	}

	/**
	 * Rebuilds all route prefix cache(s).
	 */
	public function rebuildRoutePrefixCache()
	{
		foreach ($this->_getRoutePrefixTypes() AS $routeType)
		{
			$this->rebuildRoutePrefixTypeCache($routeType);
		}
	}

	/**
	 * Rebuilds the route prefix cache for the specified type.
	 *
	 * @param string $type
	 *
	 * @return array Cache data
	 */
	public function rebuildRoutePrefixTypeCache($type)
	{
		$prefixes = $this->getPrefixesForRouteCache($type);
		$this->_getDataRegistryModel()->set('routes' . ucfirst($type), $prefixes);

		return $prefixes;
	}
}