Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla4/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla4/Router.tar

AdministratorRouter.php000064400000002076151155474540011316
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Uri\Uri;

/**
 * Class to create and parse routes
 *
 * @since  1.5
 */
class AdministratorRouter extends Router
{
	/**
	 * Function to convert a route to an internal URI.
	 *
	 * @param   Uri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		return array();
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL
	 *
	 * @return  Uri  The absolute search engine friendly URL
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		// Create the URI object
		$uri = parent::build($url);

		// Get the path data
		$route = $uri->getPath();

		// Add basepath to the uri
		$uri->setPath(Uri::root(true) . '/' .
basename(JPATH_ADMINISTRATOR) . '/' . $route);

		return $uri;
	}
}
Exception/RouteNotFoundException.php000064400000001637151155474540013667
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Router\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an error for a missing route
 *
 * @since  3.8.0
 */
class RouteNotFoundException extends \InvalidArgumentException
{
	/**
	 * Constructor
	 *
	 * @param   string      $message   The Exception message to throw.
	 * @param   integer     $code      The Exception code.
	 * @param   \Exception  $previous  The previous exception used for the
exception chaining.
	 *
	 * @since   3.8.0
	 */
	public function __construct($message = '', $code = 404,
\Exception $previous = null)
	{
		if (empty($message))
		{
			$message = 'URL was not found';
		}

		parent::__construct($message, $code, $previous);
	}
}
Route.php000064400000012532151155474540006371 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;

/**
 * Route handling class
 *
 * @since  1.7.0
 */
class Route
{
	/**
	 * No change, use the protocol currently used.
	 *
	 * @since  3.9.7
	 */
	const TLS_IGNORE = 0;

	/**
	 * Make URI secure using http over TLS (https).
	 *
	 * @since  3.9.7
	 */
	const TLS_FORCE = 1;

	/**
	 * Make URI unsecure using plain http (http).
	 *
	 * @since  3.9.7
	 */
	const TLS_DISABLE = 2;

	/**
	 * The route object so we don't have to keep fetching it.
	 *
	 * @var    Router[]
	 * @since  3.0.1
	 */
	private static $_router = array();

	/**
	 * Translates an internal Joomla URL to a humanly readable URL. This
method builds links for the current active client.
	 *
	 * @param   string   $url       Absolute or Relative URI to Joomla
resource.
	 * @param   boolean  $xhtml     Replace & by &amp; for XML
compliance.
	 * @param   integer  $tls       Secure state for the resolved URI. Use
Route::TLS_* constants
	 *                                0: (default) No change, use the protocol
currently used in the request
	 *                                1: Make URI secure using global secure
site URI.
	 *                                2: Make URI unsecure using the global
unsecure site URI.
	 * @param   boolean  $absolute  Return an absolute URL
	 *
	 * @return  string  The translated humanly readable URL.
	 *
	 * @since   1.7.0
	 */
	public static function _($url, $xhtml = true, $tls = self::TLS_IGNORE,
$absolute = false)
	{
		try
		{
			// @deprecated  4.0 Before 3.9.7 this method silently converted $tls to
integer
			if (!is_int($tls))
			{
				Log::add(
					__METHOD__ . '() called with incompatible variable type on
parameter $tls.',
					Log::WARNING,
					'deprecated'
				);

				$tls = (int) $tls;
			}

			// @todo  Deprecate in 4.0 Before 3.9.7 this method accepted -1.
			if ($tls === -1)
			{
				$tls = self::TLS_DISABLE;
			}

			$app    = Factory::getApplication();
			$client = $app->getName();

			return static::link($client, $url, $xhtml, $tls, $absolute);
		}
		catch (\RuntimeException $e)
		{
			// @deprecated  4.0 Before 3.9.0 this method failed silently on router
error. This B/C will be removed in Joomla 4.0.
			return null;
		}
	}

	/**
	 * Translates an internal Joomla URL to a humanly readable URL.
	 * NOTE: To build link for active client instead of a specific client, you
can use <var>JRoute::_()</var>
	 *
	 * @param   string   $client    The client name for which to build the
link.
	 * @param   string   $url       Absolute or Relative URI to Joomla
resource.
	 * @param   boolean  $xhtml     Replace & by &amp; for XML
compliance.
	 * @param   integer  $tls       Secure state for the resolved URI. Use
Route::TLS_* constants
	 *                                0: (default) No change, use the protocol
currently used in the request
	 *                                1: Make URI secure using global secure
site URI.
	 *                                2: Make URI unsecure using the global
unsecure site URI.
	 * @param   boolean  $absolute  Return an absolute URL
	 *
	 * @return  string  The translated humanly readable URL.
	 *
	 * @throws  \RuntimeException
	 *
	 * @since   3.9.0
	 */
	public static function link($client, $url, $xhtml = true, $tls =
self::TLS_IGNORE, $absolute = false)
	{
		// If we cannot process this $url exit early.
		if (!is_array($url) && (strpos($url, '&') !== 0)
&& (strpos($url, 'index.php') !== 0))
		{
			return $url;
		}

		// Get the router instance, only attempt when a client name is given.
		if ($client && !isset(self::$_router[$client]))
		{
			$app = Factory::getApplication();

			self::$_router[$client] = $app->getRouter($client);
		}

		// Make sure that we have our router
		if (!isset(self::$_router[$client]))
		{
			throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD',
$client), 500);
		}

		// Build route.
		$uri    = self::$_router[$client]->build($url);
		$scheme = array('path', 'query',
'fragment');

		/*
		 * Get the secure/unsecure URLs.
		 *
		 * If the first 5 characters of the BASE are 'https', then we
are on an ssl connection over
		 * https and need to set our secure URL to the current request URL, if
not, and the scheme is
		 * 'http', then we need to do a quick string manipulation to
switch schemes.
		 */
		if ($tls === self::TLS_FORCE)
		{
			$uri->setScheme('https');
		}
		elseif ($tls === self::TLS_DISABLE)
		{
			$uri->setScheme('http');
		}

		// Set scheme if requested or
		if ($absolute || $tls > 0)
		{
			static $scheme_host_port;

			if (!is_array($scheme_host_port))
			{
				$uri2             = Uri::getInstance();
				$scheme_host_port = array($uri2->getScheme(), $uri2->getHost(),
$uri2->getPort());
			}

			if (is_null($uri->getScheme()))
			{
				$uri->setScheme($scheme_host_port[0]);
			}

			$uri->setHost($scheme_host_port[1]);
			$uri->setPort($scheme_host_port[2]);

			$scheme = array_merge($scheme, array('host', 'port',
'scheme'));
		}

		$url = $uri->toString($scheme);

		// Replace spaces.
		$url = preg_replace('/\s/u', '%20', $url);

		if ($xhtml)
		{
			$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
		}

		return $url;
	}
}
Router.php000064400000040307151155474540006554 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Router\Exception\RouteNotFoundException;

/**
 * Class to create and parse routes
 *
 * @since  1.5
 */
class Router
{
	/**
	 * Mask for the before process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_BEFORE = 'preprocess';

	/**
	 * Mask for the during process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_DURING = '';

	/**
	 * Mask for the after process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_AFTER = 'postprocess';

	/**
	 * The rewrite mode
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0
	 */
	protected $mode = null;

	/**
	 * The rewrite mode
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0
	 */
	protected $_mode = null;

	/**
	 * An array of variables
	 *
	 * @var     array
	 * @since   1.5
	 */
	protected $vars = array();

	/**
	 * An array of variables
	 *
	 * @var     array
	 * @since  1.5
	 * @deprecated  4.0 Will convert to $vars
	 */
	protected $_vars = array();

	/**
	 * An array of rules
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected $rules = array(
		'buildpreprocess' => array(),
		'build' => array(),
		'buildpostprocess' => array(),
		'parsepreprocess' => array(),
		'parse' => array(),
		'parsepostprocess' => array(),
	);

	/**
	 * An array of rules
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  4.0 Will convert to $rules
	 */
	protected $_rules = array(
		'buildpreprocess' => array(),
		'build' => array(),
		'buildpostprocess' => array(),
		'parsepreprocess' => array(),
		'parse' => array(),
		'parsepostprocess' => array(),
	);

	/**
	 * Caching of processed URIs
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $cache = array();

	/**
	 * Router instances container.
	 *
	 * @var    Router[]
	 * @since  1.7
	 */
	protected static $instances = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Array of options
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
		if (array_key_exists('mode', $options))
		{
			$this->_mode = $options['mode'];
		}
		else
		{
			$this->_mode = JROUTER_MODE_RAW;
		}
	}

	/**
	 * Returns the global Router object, only creating it if it
	 * doesn't already exist.
	 *
	 * @param   string  $client   The name of the client
	 * @param   array   $options  An associative array of options
	 *
	 * @return  Router  A Router object.
	 *
	 * @since   1.5
	 * @throws  \RuntimeException
	 */
	public static function getInstance($client, $options = array())
	{
		if (empty(self::$instances[$client]))
		{
			// Create a Router object
			$classname = 'JRouter' . ucfirst($client);

			if (!class_exists($classname))
			{
				// @deprecated 4.0 Everything in this block is deprecated but the
warning is only logged after the file_exists
				// Load the router object
				$info = ApplicationHelper::getClientInfo($client, true);

				if (is_object($info))
				{
					$path = $info->path . '/includes/router.php';

					\JLoader::register($classname, $path);

					if (class_exists($classname))
					{
						\JLog::add('Non-autoloadable Router subclasses are deprecated,
support will be removed in 4.0.', \JLog::WARNING,
'deprecated');
					}
				}
			}

			if (class_exists($classname))
			{
				self::$instances[$client] = new $classname($options);
			}
			else
			{
				throw new
\RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD',
$client), 500);
			}
		}

		return self::$instances[$client];
	}

	/**
	 * Function to convert a route to an internal URI
	 *
	 * @param   \JUri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		// Do the preprocess stage of the URL build process
		$vars = $this->processParseRules($uri, self::PROCESS_BEFORE);

		// Process the parsed variables based on custom defined rules
		// This is the main parse stage
		$vars += $this->_processParseRules($uri);

		// Parse RAW URL
		if ($this->_mode == JROUTER_MODE_RAW)
		{
			$vars += $this->_parseRawRoute($uri);
		}

		// Parse SEF URL
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			$vars += $this->_parseSefRoute($uri);
		}

		// Do the postprocess stage of the URL build process
		$vars += $this->processParseRules($uri, self::PROCESS_AFTER);

		// Check if all parts of the URL have been parsed.
		// Otherwise we have an invalid URL
		if (strlen($uri->getPath()) > 0 &&
array_key_exists('option', $vars)
			&&
ComponentHelper::getParams($vars['option'])->get('sef_advanced',
0))
		{
			throw new
RouteNotFoundException(\JText::_('JERROR_PAGE_NOT_FOUND'));
		}

		return array_merge($this->getVars(), $vars);
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL or an associative array
	 *
	 * @return  \JUri  The absolute search engine friendly URL object
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		$key = md5(serialize($url));

		if (isset($this->cache[$key]))
		{
			return clone $this->cache[$key];
		}

		// Create the URI object
		$uri = $this->createUri($url);

		// Do the preprocess stage of the URL build process
		$this->processBuildRules($uri, self::PROCESS_BEFORE);

		// Process the uri information based on custom defined rules.
		// This is the main build stage
		$this->_processBuildRules($uri);

		// Build RAW URL
		if ($this->_mode == JROUTER_MODE_RAW)
		{
			$this->_buildRawRoute($uri);
		}

		// Build SEF URL : mysite/route/index.php?var=x
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			$this->_buildSefRoute($uri);
		}

		// Do the postprocess stage of the URL build process
		$this->processBuildRules($uri, self::PROCESS_AFTER);

		$this->cache[$key] = clone $uri;

		return $uri;
	}

	/**
	 * Get the router mode
	 *
	 * @return  integer
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public function getMode()
	{
		return $this->_mode;
	}

	/**
	 * Set the router mode
	 *
	 * @param   integer  $mode  The routing mode.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public function setMode($mode)
	{
		$this->_mode = $mode;
	}

	/**
	 * Set a router variable, creating it if it doesn't exist
	 *
	 * @param   string   $key     The name of the variable
	 * @param   mixed    $value   The value of the variable
	 * @param   boolean  $create  If True, the variable will be created if it
doesn't exist yet
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function setVar($key, $value, $create = true)
	{
		if ($create || array_key_exists($key, $this->_vars))
		{
			$this->_vars[$key] = $value;
		}
	}

	/**
	 * Set the router variable array
	 *
	 * @param   array    $vars   An associative array with variables
	 * @param   boolean  $merge  If True, the array will be merged instead of
overwritten
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function setVars($vars = array(), $merge = true)
	{
		if ($merge)
		{
			$this->_vars = array_merge($this->_vars, $vars);
		}
		else
		{
			$this->_vars = $vars;
		}
	}

	/**
	 * Get a router variable
	 *
	 * @param   string  $key  The name of the variable
	 *
	 * @return  mixed  Value of the variable
	 *
	 * @since   1.5
	 */
	public function getVar($key)
	{
		$result = null;

		if (isset($this->_vars[$key]))
		{
			$result = $this->_vars[$key];
		}

		return $result;
	}

	/**
	 * Get the router variable array
	 *
	 * @return  array  An associative array of router variables
	 *
	 * @since   1.5
	 */
	public function getVars()
	{
		return $this->_vars;
	}

	/**
	 * Attach a build rule
	 *
	 * @param   callable  $callback  The function to be called
	 * @param   string    $stage     The stage of the build process that
	 *                               this should be added to. Possible values:
	 *                               'preprocess', '' for
the main build process,
	 *                               'postprocess'
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function attachBuildRule($callback, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('build' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
		}

		$this->_rules['build' . $stage][] = $callback;
	}

	/**
	 * Attach a parse rule
	 *
	 * @param   callable  $callback  The function to be called.
	 * @param   string    $stage     The stage of the parse process that
	 *                               this should be added to. Possible values:
	 *                               'preprocess', '' for
the main parse process,
	 *                               'postprocess'
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function attachParseRule($callback, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('parse' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
		}

		$this->_rules['parse' . $stage][] = $callback;
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function _parseRawRoute(&$uri)
	{
		return $this->parseRawRoute($uri);
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  array  Array of variables
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseRawRoute(&$uri)
	{
		return array();
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  string  Internal URI
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function _parseSefRoute(&$uri)
	{
		return $this->parseSefRoute($uri);
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  array  Array of variables
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseSefRoute(&$uri)
	{
		return array();
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function _buildRawRoute(&$uri)
	{
		return $this->buildRawRoute($uri);
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildRawRoute(&$uri)
	{
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  string  The SEF route
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function _buildSefRoute(&$uri)
	{
		return $this->buildSefRoute($uri);
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  string  The SEF route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildSefRoute(&$uri)
	{
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri  &$uri  The URI to parse
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use processParseRules() instead
	 */
	protected function _processParseRules(&$uri)
	{
		return $this->processParseRules($uri);
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI to parse
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess',
'postprocess'
	 *                          and '' for the main parse stage
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   3.2
	 */
	protected function processParseRules(&$uri, $stage =
self::PROCESS_DURING)
	{
		if (!array_key_exists('parse' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
		}

		$vars = array();

		foreach ($this->_rules['parse' . $stage] as $rule)
		{
			$vars += (array) call_user_func_array($rule, array(&$this,
&$uri));
		}

		return $vars;
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri  &$uri  The URI
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use processBuildRules() instead
	 */
	protected function _processBuildRules(&$uri)
	{
		$this->processBuildRules($uri);
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess',
'postprocess'
	 *                          and '' for the main build stage
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function processBuildRules(&$uri, $stage =
self::PROCESS_DURING)
	{
		if (!array_key_exists('build' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not
registered. (%s)', $stage, __METHOD__));
		}

		foreach ($this->_rules['build' . $stage] as $rule)
		{
			call_user_func_array($rule, array(&$this, &$uri));
		}
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI
	 *
	 * @return  \JUri
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use createUri() instead
	 * @codeCoverageIgnore
	 */
	protected function _createUri($url)
	{
		return $this->createUri($url);
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI or an associative array
	 *
	 * @return  \JUri
	 *
	 * @since   3.2
	 */
	protected function createUri($url)
	{
		if (!is_array($url) && substr($url, 0, 1) !== '&')
		{
			return new \JUri($url);
		}

		$uri = new \JUri('index.php');

		if (is_string($url))
		{
			$vars = array();

			if (strpos($url, '&amp;') !== false)
			{
				$url = str_replace('&amp;', '&', $url);
			}

			parse_str($url, $vars);
		}
		else
		{
			$vars = $url;
		}

		$vars = array_merge($this->getVars(), $vars);

		foreach ($vars as $key => $var)
		{
			if ($var == '')
			{
				unset($vars[$key]);
			}
		}

		$uri->setQuery($vars);

		return $uri;
	}

	/**
	 * Encode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of encoded route segments
	 *
	 * @since   1.5
	 * @deprecated  4.0  This should be performed in the component router
instead
	 * @codeCoverageIgnore
	 */
	protected function _encodeSegments($segments)
	{
		return $this->encodeSegments($segments);
	}

	/**
	 * Encode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of encoded route segments
	 *
	 * @since   3.2
	 * @deprecated  4.0  This should be performed in the component router
instead
	 */
	protected function encodeSegments($segments)
	{
		$total = count($segments);

		for ($i = 0; $i < $total; $i++)
		{
			$segments[$i] = str_replace(':', '-',
$segments[$i]);
		}

		return $segments;
	}

	/**
	 * Decode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of decoded route segments
	 *
	 * @since   1.5
	 * @deprecated  4.0  This should be performed in the component router
instead
	 * @codeCoverageIgnore
	 */
	protected function _decodeSegments($segments)
	{
		return $this->decodeSegments($segments);
	}

	/**
	 * Decode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of decoded route segments
	 *
	 * @since   3.2
	 * @deprecated  4.0  This should be performed in the component router
instead
	 */
	protected function decodeSegments($segments)
	{
		$total = count($segments);

		for ($i = 0; $i < $total; $i++)
		{
			$segments[$i] = preg_replace('/-/', ':',
$segments[$i], 1);
		}

		return $segments;
	}
}
SiteRouter.php000064400000044637151155474540007413 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterBase;
use Joomla\CMS\Component\Router\RouterInterface;
use Joomla\CMS\Component\Router\RouterLegacy;
use Joomla\String\StringHelper;

/**
 * Class to create and parse routes for the site application
 *
 * @since  1.5
 */
class SiteRouter extends Router
{
	/**
	 * Component-router objects
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $componentRouters = array();

	/**
	 * Current Application-Object
	 *
	 * @var    CMSApplication
	 * @since  3.4
	 */
	protected $app;

	/**
	 * Current \JMenu-Object
	 *
	 * @var    \JMenu
	 * @since  3.4
	 */
	protected $menu;

	/**
	 * Class constructor
	 *
	 * @param   array           $options  Array of options
	 * @param   CMSApplication  $app      CMSApplication Object
	 * @param   \JMenu          $menu     \JMenu object
	 *
	 * @since   3.4
	 */
	public function __construct($options = array(), CMSApplication $app =
null, \JMenu $menu = null)
	{
		parent::__construct($options);

		$this->app  = $app ?: CMSApplication::getInstance('site');
		$this->menu = $menu ?: $this->app->getMenu();
	}

	/**
	 * Function to convert a route to an internal URI
	 *
	 * @param   \JUri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		$vars = array();

		if ($this->app->get('force_ssl') == 2 &&
strtolower($uri->getScheme()) !== 'https')
		{
			// Forward to https
			$uri->setScheme('https');
			$this->app->redirect((string) $uri, 301);
		}

		// Get the path
		// Decode URL to convert percent-encoding to unicode so that strings
match when routing.
		$path = urldecode($uri->getPath());

		// Remove the base URI path.
		$path = substr_replace($path, '', 0,
strlen(\JUri::base(true)));

		// Check to see if a request to a specific entry point has been made.
		if (preg_match("#.*?\.php#u", $path, $matches))
		{
			// Get the current entry point path relative to the site path.
			$scriptPath         = realpath($_SERVER['SCRIPT_FILENAME'] ?:
str_replace('\\\\', '\\',
$_SERVER['PATH_TRANSLATED']));
			$relativeScriptPath = str_replace('\\', '/',
str_replace(JPATH_SITE, '', $scriptPath));

			// If a php file has been found in the request path, check to see if it
is a valid file.
			// Also verify that it represents the same file from the server variable
for entry script.
			if (file_exists(JPATH_SITE . $matches[0]) && ($matches[0] ===
$relativeScriptPath))
			{
				// Remove the entry point segments from the request path for proper
routing.
				$path = str_replace($matches[0], '', $path);
			}
		}

		// Identify format
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			if ($this->app->get('sef_suffix') &&
!(substr($path, -9) === 'index.php' || substr($path, -1) ===
'/'))
			{
				if ($suffix = pathinfo($path, PATHINFO_EXTENSION))
				{
					$vars['format'] = $suffix;
				}
			}
		}

		// Set the route
		$uri->setPath(trim($path, '/'));

		// Set the parsepreprocess components methods
		$components = ComponentHelper::getComponents();

		foreach ($components as $component)
		{
			$componentRouter = $this->getComponentRouter($component->option);

			if (method_exists($componentRouter, 'parsepreprocess'))
			{
				$this->attachParseRule(array($componentRouter,
'parsepreprocess'), static::PROCESS_BEFORE);
			}
		}

		$vars += parent::parse($uri);

		return $vars;
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL
	 *
	 * @return  string  The absolute search engine friendly URL
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		$uri = parent::build($url);

		// Get the path data
		$route = $uri->getPath();

		// Add the suffix to the uri
		if ($this->_mode == JROUTER_MODE_SEF && $route)
		{
			if ($this->app->get('sef_suffix') &&
!(substr($route, -9) === 'index.php' || substr($route, -1) ===
'/'))
			{
				if ($format = $uri->getVar('format', 'html'))
				{
					$route .= '.' . $format;
					$uri->delVar('format');
				}
			}

			if ($this->app->get('sef_rewrite'))
			{
				// Transform the route
				if ($route === 'index.php')
				{
					$route = '';
				}
				else
				{
					$route = str_replace('index.php/', '', $route);
				}
			}
		}

		// Add frontend basepath to the uri
		$uri->setPath(\JUri::root(true) . '/' . $route);

		return $uri;
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  array
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseRawRoute(&$uri)
	{
		$vars = array();

		// Handle an empty URL (special case)
		if (!$uri->getVar('Itemid') &&
!$uri->getVar('option'))
		{
			$item =
$this->menu->getDefault($this->app->getLanguage()->getTag());

			if (!is_object($item))
			{
				// No default item set
				return $vars;
			}

			// Set the information in the request
			$vars = $item->query;

			// Get the itemid
			$vars['Itemid'] = $item->id;

			// Set the active menu item
			$this->menu->setActive($vars['Itemid']);

			return $vars;
		}

		// Get the variables from the uri
		$this->setVars($uri->getQuery(true));

		// Get the itemid, if it hasn't been set force it to null
		$this->setVar('Itemid',
$this->app->input->getInt('Itemid', null));

		// Only an Itemid  OR if filter language plugin set? Get the full
information from the itemid
		if (count($this->getVars()) === 1 ||
($this->app->getLanguageFilter() &&
count($this->getVars()) === 2))
		{
			$item =
$this->menu->getItem($this->getVar('Itemid'));

			if ($item && $item->type == 'alias')
			{
				$newItem =
$this->menu->getItem($item->params->get('aliasoptions'));

				if ($newItem)
				{
					$item->query     = array_merge($item->query,
$newItem->query);
					$item->component = $newItem->component;
				}
			}

			if ($item !== null && is_array($item->query))
			{
				$vars += $item->query;
			}
		}

		// Set the active menu item
		$this->menu->setActive($this->getVar('Itemid'));

		return $vars;
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  string  Internal URI
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseSefRoute(&$uri)
	{
		$route = $uri->getPath();

		// Remove the suffix
		if ($this->app->get('sef_suffix'))
		{
			if ($suffix = pathinfo($route, PATHINFO_EXTENSION))
			{
				$route = str_replace('.' . $suffix, '', $route);
			}
		}

		// Get the variables from the uri
		$vars = $uri->getQuery(true);

		// Handle an empty URL (special case)
		if (empty($route))
		{
			// If route is empty AND option is set in the query, assume it's
non-sef url, and parse appropriately
			if (isset($vars['option']) ||
isset($vars['Itemid']))
			{
				return $this->parseRawRoute($uri);
			}

			$item =
$this->menu->getDefault($this->app->getLanguage()->getTag());

			// If user not allowed to see default menu item then avoid notices
			if (is_object($item))
			{
				// Set query variables of default menu item into the request, but keep
existing request variables
				$vars = array_merge($vars, $item->query);

				// Get the itemid
				$vars['Itemid'] = $item->id;

				// Set the active menu item
				$this->menu->setActive($vars['Itemid']);

				$this->setVars($vars);
			}

			return $vars;
		}

		// Parse the application route
		$segments = explode('/', $route);

		if (count($segments) > 1 && $segments[0] ===
'component')
		{
			$vars['option'] = 'com_' . $segments[1];
			$vars['Itemid'] = null;
			$route = implode('/', array_slice($segments, 2));
		}
		else
		{
			// Get menu items.
			$items = $this->menu->getMenu();

			$found           = false;
			$route_lowercase = StringHelper::strtolower($route);
			$lang_tag        = $this->app->getLanguage()->getTag();

			// Iterate through all items and check route matches.
			foreach ($items as $item)
			{
				if ($item->route && StringHelper::strpos($route_lowercase .
'/', $item->route . '/') === 0 &&
$item->type !== 'menulink')
				{
					// Usual method for non-multilingual site.
					if (!$this->app->getLanguageFilter())
					{
						// Exact route match. We can break iteration because exact item was
found.
						if ($item->route === $route_lowercase)
						{
							$found = $item;
							break;
						}

						// Partial route match. Item with highest level takes priority.
						if (!$found || $found->level < $item->level)
						{
							$found = $item;
						}
					}
					// Multilingual site.
					elseif ($item->language === '*' || $item->language ===
$lang_tag)
					{
						// Exact route match.
						if ($item->route === $route_lowercase)
						{
							$found = $item;

							// Break iteration only if language is matched.
							if ($item->language === $lang_tag)
							{
								break;
							}
						}

						// Partial route match. Item with highest level or same language
takes priority.
						if (!$found || $found->level < $item->level ||
$item->language === $lang_tag)
						{
							$found = $item;
						}
					}
				}
			}

			if (!$found)
			{
				$found = $this->menu->getDefault($lang_tag);
			}
			else
			{
				$route = substr($route, strlen($found->route));

				if ($route)
				{
					$route = substr($route, 1);
				}
			}

			if ($found)
			{
				if ($found->type == 'alias')
				{
					$newItem =
$this->menu->getItem($found->params->get('aliasoptions'));

					if ($newItem)
					{
						$found->query     = array_merge($found->query,
$newItem->query);
						$found->component = $newItem->component;
					}
				}

				$vars['Itemid'] = $found->id;
				$vars['option'] = $found->component;
			}
		}

		// Set the active menu item
		if (isset($vars['Itemid']))
		{
			$this->menu->setActive($vars['Itemid']);
		}

		// Set the variables
		$this->setVars($vars);

		// Parse the component route
		if (!empty($route) && isset($this->_vars['option']))
		{
			$segments = explode('/', $route);

			if (empty($segments[0]))
			{
				array_shift($segments);
			}

			// Handle component route
			$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$this->_vars['option']);

			if (count($segments))
			{
				$crouter = $this->getComponentRouter($component);
				$vars = $crouter->parse($segments);

				$this->setVars($vars);
			}

			$route = implode('/', $segments);
		}
		else
		{
			// Set active menu item
			if ($item = $this->menu->getActive())
			{
				$vars = $item->query;
			}
		}

		$uri->setPath($route);

		return $vars;
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildRawRoute(&$uri)
	{
		// Get the query data
		$query = $uri->getQuery(true);

		if (!isset($query['option']))
		{
			return;
		}

		$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
		$crouter   = $this->getComponentRouter($component);

		if ($crouter instanceof RouterBase === false)
		{
			$query = $crouter->preprocess($query);
			$uri->setQuery($query);
		}
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 * @codeCoverageIgnore
	 */
	protected function _buildSefRoute(&$uri)
	{
		$this->buildSefRoute($uri);
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildSefRoute(&$uri)
	{
		// Get the route
		$route = $uri->getPath();

		// Get the query data
		$query = $uri->getQuery(true);

		if (!isset($query['option']))
		{
			return;
		}

		// Build the component route
		$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
		$itemID    = !empty($query['Itemid']) ?
$query['Itemid'] : null;
		$crouter   = $this->getComponentRouter($component);
		$parts     = $crouter->build($query);
		$result    = implode('/', $parts);
		$tmp       = ($result !== '') ? $result : '';

		// Build the application route
		$built = false;

		if (!empty($query['Itemid']))
		{
			$item = $this->menu->getItem($query['Itemid']);

			if (is_object($item) && $query['option'] ===
$item->component)
			{
				if (!$item->home)
				{
					$tmp = !empty($tmp) ? $item->route . '/' . $tmp :
$item->route;
				}

				$built = true;
			}
		}

		if (empty($query['Itemid']) && !empty($itemID))
		{
			$query['Itemid'] = $itemID;
		}

		if (!$built)
		{
			$tmp = 'component/' . substr($query['option'], 4) .
'/' . $tmp;
		}

		if ($tmp)
		{
			$route .= '/' . $tmp;
		}

		// Unset unneeded query information
		if (isset($item) && $query['option'] ===
$item->component)
		{
			unset($query['Itemid']);
		}

		unset($query['option']);

		// Set query again in the URI
		$uri->setQuery($query);
		$uri->setPath($route);
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI to parse
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess',
'postprocess'
	 *                          and '' for the main parse stage
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   3.2
	 */
	protected function processParseRules(&$uri, $stage =
self::PROCESS_DURING)
	{
		// Process the attached parse rules
		$vars = parent::processParseRules($uri, $stage);

		if ($stage === self::PROCESS_DURING)
		{
			// Process the pagination support
			if ($this->_mode == JROUTER_MODE_SEF)
			{
				$start = $uri->getVar('start');

				if ($start !== null)
				{
					$uri->delVar('start');
					$vars['limitstart'] = $start;
				}
			}
		}

		return $vars;
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess',
'postprocess'
	 *                          and '' for the main build stage
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @deprecated  4.0  The special logic should be implemented as rule
	 */
	protected function processBuildRules(&$uri, $stage =
self::PROCESS_DURING)
	{
		if ($stage === self::PROCESS_DURING)
		{
			// Make sure any menu vars are used if no others are specified
			$query = $uri->getQuery(true);

			if ($this->_mode != 1
				&& isset($query['Itemid'])
				&& (count($query) === 2 || (count($query) === 3 &&
isset($query['lang']))))
			{
				// Get the active menu item
				$itemid = $uri->getVar('Itemid');
				$lang = $uri->getVar('lang');
				$item = $this->menu->getItem($itemid);

				if ($item)
				{
					$uri->setQuery($item->query);
				}

				$uri->setVar('Itemid', $itemid);

				if ($lang)
				{
					$uri->setVar('lang', $lang);
				}
			}
		}

		// Process the attached build rules
		parent::processBuildRules($uri, $stage);

		if ($stage === self::PROCESS_BEFORE)
		{
			// Get the query data
			$query = $uri->getQuery(true);

			if (!isset($query['option']))
			{
				return;
			}

			// Build the component route
			$component = preg_replace('/[^A-Z0-9_\.-]/i', '',
$query['option']);
			$router   = $this->getComponentRouter($component);
			$query     = $router->preprocess($query);
			$uri->setQuery($query);
		}

		if ($stage === self::PROCESS_DURING)
		{
			// Get the path data
			$route = $uri->getPath();

			if ($this->_mode == JROUTER_MODE_SEF && $route)
			{
				$limitstart = $uri->getVar('limitstart');

				if ($limitstart !== null)
				{
					$uri->setVar('start', (int) $limitstart);
					$uri->delVar('limitstart');
				}
			}

			$uri->setPath($route);
		}
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI
	 *
	 * @return  \JUri
	 *
	 * @since   3.2
	 */
	protected function createUri($url)
	{
		// Create the URI
		$uri = parent::createUri($url);

		// Get the itemid form the URI
		$itemid = $uri->getVar('Itemid');

		if ($itemid === null)
		{
			if ($option = $uri->getVar('option'))
			{
				$item =
$this->menu->getItem($this->getVar('Itemid'));

				if ($item !== null && $item->component === $option)
				{
					$uri->setVar('Itemid', $item->id);
				}
			}
			else
			{
				if ($option = $this->getVar('option'))
				{
					$uri->setVar('option', $option);
				}

				if ($itemid = $this->getVar('Itemid'))
				{
					$uri->setVar('Itemid', $itemid);
				}
			}
		}
		else
		{
			if (!$uri->getVar('option'))
			{
				if ($item = $this->menu->getItem($itemid))
				{
					$uri->setVar('option', $item->component);
				}
			}
		}

		return $uri;
	}

	/**
	 * Get component router
	 *
	 * @param   string  $component  Name of the component including com_
prefix
	 *
	 * @return  RouterInterface  Component router
	 *
	 * @since   3.3
	 */
	public function getComponentRouter($component)
	{
		if (!isset($this->componentRouters[$component]))
		{
			$compname = ucfirst(substr($component, 4));
			$class = $compname . 'Router';

			if (!class_exists($class))
			{
				// Use the component routing handler if it exists
				$path = JPATH_SITE . '/components/' . $component .
'/router.php';

				// Use the custom routing handler if it exists
				if (file_exists($path))
				{
					require_once $path;
				}
			}

			if (class_exists($class))
			{
				$reflection = new \ReflectionClass($class);

				if
(in_array('Joomla\\CMS\\Component\\Router\\RouterInterface',
$reflection->getInterfaceNames()))
				{
					$this->componentRouters[$component] = new $class($this->app,
$this->menu);
				}
			}

			if (!isset($this->componentRouters[$component]))
			{
				$this->componentRouters[$component] = new RouterLegacy($compname);
			}
		}

		return $this->componentRouters[$component];
	}

	/**
	 * Set a router for a component
	 *
	 * @param   string  $component  Component name with com_ prefix
	 * @param   object  $router     Component router
	 *
	 * @return  boolean  True if the router was accepted, false if not
	 *
	 * @since   3.3
	 */
	public function setComponentRouter($component, $router)
	{
		$reflection = new \ReflectionClass($router);

		if (in_array('Joomla\\CMS\\Component\\Router\\RouterInterface',
$reflection->getInterfaceNames()))
		{
			$this->componentRouters[$component] = $router;

			return true;
		}
		else
		{
			return false;
		}
	}
}
RouterBase.php000064400000002572151162265130007340 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Base component routing class
 *
 * @since  3.3
 */
abstract class RouterBase implements RouterInterface
{
	/**
	 * Application object to use in the router
	 *
	 * @var    \JApplicationCms
	 * @since  3.4
	 */
	public $app;

	/**
	 * Menu object to use in the router
	 *
	 * @var    \JMenu
	 * @since  3.4
	 */
	public $menu;

	/**
	 * Class constructor.
	 *
	 * @param   \JApplicationCms  $app   Application-object that the router
should use
	 * @param   \JMenu            $menu  Menu-object that the router should
use
	 *
	 * @since   3.4
	 */
	public function __construct($app = null, $menu = null)
	{
		if ($app)
		{
			$this->app = $app;
		}
		else
		{
			$this->app = \JFactory::getApplication('site');
		}

		if ($menu)
		{
			$this->menu = $menu;
		}
		else
		{
			$this->menu = $this->app->getMenu();
		}
	}

	/**
	 * Generic method to preprocess a URL
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query)
	{
		return $query;
	}
}
RouterInterface.php000064400000003115151162265130010360 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Component routing interface
 *
 * @since  3.3
 */
interface RouterInterface
{
	/**
	 * Prepare-method for URLs
	 * This method is meant to validate and complete the URL parameters.
	 * For example it can add the Itemid or set a language parameter.
	 * This method is executed on each URL, regardless of SEF mode switched
	 * on or not.
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query);

	/**
	 * Build method for URLs
	 * This method is meant to transform the query parameters into a more
human
	 * readable form. It is only executed when SEF mode is switched on.
	 *
	 * @param   array  &$query  An array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.3
	 */
	public function build(&$query);

	/**
	 * Parse method for URLs
	 * This method is meant to transform the human readable URL back into
	 * query parameters. It is only executed when SEF mode is switched on.
	 *
	 * @param   array  &$segments  The segments of the URL to parse.
	 *
	 * @return  array  The URL attributes to be used by the application.
	 *
	 * @since   3.3
	 */
	public function parse(&$segments);
}
RouterLegacy.php000064400000004261151162265130007667 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Default routing class for missing or legacy component routers
 *
 * @since  3.3
 */
class RouterLegacy implements RouterInterface
{
	/**
	 * Name of the component
	 *
	 * @var    string
	 * @since  3.3
	 */
	protected $component;

	/**
	 * Constructor
	 *
	 * @param   string  $component  Component name without the com_ prefix
this router should react upon
	 *
	 * @since   3.3
	 */
	public function __construct($component)
	{
		$this->component = $component;
	}

	/**
	 * Generic preprocess function for missing or legacy component router
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query)
	{
		return $query;
	}

	/**
	 * Generic build function for missing or legacy component router
	 *
	 * @param   array  &$query  An array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.3
	 */
	public function build(&$query)
	{
		$function = $this->component . 'BuildRoute';

		if (function_exists($function))
		{
			$segments = $function($query);
			$total    = count($segments);

			for ($i = 0; $i < $total; $i++)
			{
				$segments[$i] = str_replace(':', '-',
$segments[$i]);
			}

			return $segments;
		}

		return array();
	}

	/**
	 * Generic parse function for missing or legacy component router
	 *
	 * @param   array  &$segments  The segments of the URL to parse.
	 *
	 * @return  array  The URL attributes to be used by the application.
	 *
	 * @since   3.3
	 */
	public function parse(&$segments)
	{
		$function = $this->component . 'ParseRoute';

		if (function_exists($function))
		{
			$total = count($segments);

			for ($i = 0; $i < $total; $i++)
			{
				$segments[$i] = preg_replace('/-/', ':',
$segments[$i], 1);
			}

			return $function($segments);
		}

		return array();
	}
}
RouterView.php000064400000012623151162265130007376 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\Rules\RulesInterface;

/**
 * View-based component routing class
 *
 * @since  3.5
 */
abstract class RouterView extends RouterBase
{
	/**
	 * Name of the router of the component
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $name;

	/**
	 * Array of rules
	 *
	 * @var    RulesInterface[]
	 * @since  3.5
	 */
	protected $rules = array();

	/**
	 * Views of the component
	 *
	 * @var    RouterViewConfiguration[]
	 * @since  3.5
	 */
	protected $views = array();

	/**
	 * Register the views of a component
	 *
	 * @param   RouterViewConfiguration  $view  View configuration object
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function registerView(RouterViewConfiguration $view)
	{
		$this->views[$view->name] = $view;
	}

	/**
	 * Return an array of registered view objects
	 *
	 * @return  RouterViewConfiguration[] Array of registered view objects
	 *
	 * @since   3.5
	 */
	public function getViews()
	{
		return $this->views;
	}

	/**
	 * Get the path of views from target view to root view
	 * including content items of a nestable view
	 *
	 * @param   array  $query  Array of query elements
	 *
	 * @return  array List of views including IDs of content items
	 *
	 * @since   3.5
	 */
	public function getPath($query)
	{
		$views  = $this->getViews();
		$result = array();

		// Get the right view object
		if (isset($query['view']) &&
isset($views[$query['view']]))
		{
			$viewobj = $views[$query['view']];
		}

		// Get the path from the current item to the root view with all IDs
		if (isset($viewobj))
		{
			$path     = array_reverse($viewobj->path);
			$start    = true;
			$childkey = false;

			foreach ($path as $element)
			{
				$view = $views[$element];

				if ($start)
				{
					$key   = $view->key;
					$start = false;
				}
				else
				{
					$key = $childkey;
				}

				$childkey = $view->parent_key;

				if (($key || $view->key) && is_callable(array($this,
'get' . ucfirst($view->name) . 'Segment')))
				{
					if (isset($query[$key]))
					{
						$result[$view->name] = call_user_func_array(array($this,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$key], $query));
					}
					elseif (isset($query[$view->key]))
					{
						$result[$view->name] = call_user_func_array(array($this,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$view->key], $query));
					}
					else
					{
						$result[$view->name] = array();
					}
				}
				else
				{
					$result[$view->name] = true;
				}
			}
		}

		return $result;
	}

	/**
	 * Get all currently attached rules
	 *
	 * @return  RulesInterface[]  All currently attached rules in an array
	 *
	 * @since   3.5
	 */
	public function getRules()
	{
		return $this->rules;
	}

	/**
	 * Add a number of router rules to the object
	 *
	 * @param   RulesInterface[]  $rules  Array of
JComponentRouterRulesInterface objects
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function attachRules($rules)
	{
		foreach ($rules as $rule)
		{
			$this->attachRule($rule);
		}
	}

	/**
	 * Attach a build rule
	 *
	 * @param   RulesInterface  $rule  The function to be called.
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function attachRule(RulesInterface $rule)
	{
		$this->rules[] = $rule;
	}

	/**
	 * Remove a build rule
	 *
	 * @param   RulesInterface  $rule  The rule to be removed.
	 *
	 * @return   boolean  Was a rule removed?
	 *
	 * @since   3.5
	 */
	public function detachRule(RulesInterface $rule)
	{
		foreach ($this->rules as $id => $r)
		{
			if ($r == $rule)
			{
				unset($this->rules[$id]);

				return true;
			}
		}

		return false;
	}

	/**
	 * Generic method to preprocess a URL
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent
URL.
	 *
	 * @since   3.5
	 */
	public function preprocess($query)
	{
		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->preprocess($query);
		}

		return $query;
	}

	/**
	 * Build method for URLs
	 *
	 * @param   array  &$query  Array of query elements
	 *
	 * @return  array  Array of URL segments
	 *
	 * @since   3.5
	 */
	public function build(&$query)
	{
		$segments = array();

		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->build($query, $segments);
		}

		return $segments;
	}

	/**
	 * Parse method for URLs
	 *
	 * @param   array  &$segments  Array of URL string-segments
	 *
	 * @return  array  Associative array of query values
	 *
	 * @since   3.5
	 */
	public function parse(&$segments)
	{
		$vars = array();

		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->parse($segments, $vars);
		}

		return $vars;
	}

	/**
	 * Method to return the name of the router
	 *
	 * @return  string  Name of the router
	 *
	 * @since   3.5
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$r = null;

			if (!preg_match('/(.*)Router/i', get_class($this), $r))
			{
				throw new
\Exception('JLIB_APPLICATION_ERROR_ROUTER_GET_NAME', 500);
			}

			$this->name = strtolower($r[1]);
		}

		return $this->name;
	}
}
RouterViewConfiguration.php000064400000010170151162265130012121
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * View-configuration class for the view-based component router
 *
 * @since  3.5
 */
class RouterViewConfiguration
{
	/**
	 * Name of the view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $name;

	/**
	 * Key of the view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $key = false;

	/**
	 * Parentview of this one
	 *
	 * @var    RouterViewconfiguration
	 * @since  3.5
	 */
	public $parent = false;

	/**
	 * Key of the parent view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $parent_key = false;

	/**
	 * Is this view nestable?
	 *
	 * @var    bool
	 * @since  3.5
	 */
	public $nestable = false;

	/**
	 * Layouts that are supported by this view
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $layouts = array('default');

	/**
	 * Child-views of this view
	 *
	 * @var    RouterViewconfiguration[]
	 * @since  3.5
	 */
	public $children = array();

	/**
	 * Keys used for this parent view by the child views
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $child_keys = array();

	/**
	 * Path of views from this one to the root view
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $path = array();

	/**
	 * Constructor for the View-configuration class
	 *
	 * @param   string  $name  Name of the view
	 *
	 * @since   3.5
	 */
	public function __construct($name)
	{
		$this->name   = $name;
		$this->path[] = $name;
	}

	/**
	 * Set the name of the view
	 *
	 * @param   string  $name  Name of the view
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setName($name)
	{
		$this->name = $name;

		array_pop($this->path);
		$this->path[] = $name;

		return $this;
	}

	/**
	 * Set the key-identifier for the view
	 *
	 * @param   string  $key  Key of the view
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setKey($key)
	{
		$this->key = $key;

		return $this;
	}

	/**
	 * Set the parent view of this view
	 *
	 * @param   RouterViewconfiguration  $parent     Parent view object
	 * @param   string                   $parentKey  Key of the parent view in
this context
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setParent(RouterViewconfiguration $parent, $parentKey =
false)
	{
		if ($this->parent)
		{
			$key = array_search($this, $this->parent->children);

			if ($key !== false)
			{
				unset($this->parent->children[$key]);
			}

			if ($this->parent_key)
			{
				$child_key = array_search($this->parent_key,
$this->parent->child_keys);
				unset($this->parent->child_keys[$child_key]);
			}
		}

		$this->parent       = $parent;
		$parent->children[] = $this;

		$this->path   = $parent->path;
		$this->path[] = $this->name;

		$this->parent_key = $parentKey;

		if ($parentKey)
		{
			$parent->child_keys[] = $parentKey;
		}

		return $this;
	}

	/**
	 * Set if this view is nestable or not
	 *
	 * @param   bool  $isNestable  If set to true, the view is nestable
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setNestable($isNestable = true)
	{
		$this->nestable = (bool) $isNestable;

		return $this;
	}

	/**
	 * Add a layout to this view
	 *
	 * @param   string  $layout  Layouts that this view supports
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function addLayout($layout)
	{
		$this->layouts[] = $layout;
		$this->layouts   = array_unique($this->layouts);

		return $this;
	}

	/**
	 * Remove a layout from this view
	 *
	 * @param   string  $layout  Layouts that this view supports
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function removeLayout($layout)
	{
		$key = array_search($layout, $this->layouts);

		if ($key !== false)
		{
			unset($this->layouts[$key]);
		}

		return $this;
	}
}
Rules/MenuRules.php000064400000015202151162265130010270 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule to identify the right Itemid for a view in a component
 *
 * @since  3.4
 */
class MenuRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var   RouterView
	 * @since 3.4
	 */
	protected $router;

	/**
	 * Lookup array of the menu items
	 *
	 * @var   array
	 * @since 3.4
	 */
	protected $lookup = array();

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;

		$this->buildLookup();
	}

	/**
	 * Finds the right Itemid for this query
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query)
	{
		$active = $this->router->menu->getActive();

		/**
		 * If the active item id is not the same as the supplied item id or we
have a supplied item id and no active
		 * menu item then we just use the supplied menu item and continue
		 */
		if (isset($query['Itemid']) && ($active === null ||
$query['Itemid'] != $active->id))
		{
			return;
		}

		// Get query language
		$language = isset($query['lang']) ? $query['lang'] :
'*';

		if (!isset($this->lookup[$language]))
		{
			$this->buildLookup($language);
		}

		// Check if the active menu item matches the requested query
		if ($active !== null && isset($query['Itemid']))
		{
			// Check if active->query and supplied query are the same
			$match = true;

			foreach ($active->query as $k => $v)
			{
				if (isset($query[$k]) && $v !== $query[$k])
				{
					// Compare again without alias
					if (is_string($v) && $v == current(explode(':',
$query[$k], 2)))
					{
						continue;
					}

					$match = false;
					break;
				}
			}

			if ($match)
			{
				// Just use the supplied menu item
				return;
			}
		}

		$needles = $this->router->getPath($query);

		$layout = isset($query['layout']) &&
$query['layout'] !== 'default' ? ':' .
$query['layout'] : '';

		if ($needles)
		{
			foreach ($needles as $view => $ids)
			{
				$viewLayout = $view . $layout;

				if ($layout && isset($this->lookup[$language][$viewLayout]))
				{
					if (is_bool($ids))
					{
						$query['Itemid'] =
$this->lookup[$language][$viewLayout];

						return;
					}

					foreach ($ids as $id => $segment)
					{
						if (isset($this->lookup[$language][$viewLayout][(int) $id]))
						{
							$query['Itemid'] =
$this->lookup[$language][$viewLayout][(int) $id];

							return;
						}
					}
				}

				if (isset($this->lookup[$language][$view]))
				{
					if (is_bool($ids))
					{
						$query['Itemid'] = $this->lookup[$language][$view];

						return;
					}

					foreach ($ids as $id => $segment)
					{
						if (isset($this->lookup[$language][$view][(int) $id]))
						{
							$query['Itemid'] =
$this->lookup[$language][$view][(int) $id];

							return;
						}
					}
				}
			}
		}

		// Check if the active menuitem matches the requested language
		if ($active && $active->component === 'com_' .
$this->router->getName()
			&& ($language === '*' ||
in_array($active->language, array('*', $language)) ||
!\JLanguageMultilang::isEnabled()))
		{
			$query['Itemid'] = $active->id;
			return;
		}

		// If not found, return language specific home link
		$default = $this->router->menu->getDefault($language);

		if (!empty($default->id))
		{
			$query['Itemid'] = $default->id;
		}
	}

	/**
	 * Method to build the lookup array
	 *
	 * @param   string  $language  The language that the lookup should be
built up for
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function buildLookup($language = '*')
	{
		// Prepare the reverse lookup array.
		if (!isset($this->lookup[$language]))
		{
			$this->lookup[$language] = array();

			$component  = ComponentHelper::getComponent('com_' .
$this->router->getName());
			$views = $this->router->getViews();

			$attributes = array('component_id');
			$values     = array((int) $component->id);

			$attributes[] = 'language';
			$values[]     = array($language, '*');

			$items = $this->router->menu->getItems($attributes, $values);

			foreach ($items as $item)
			{
				if (isset($item->query['view'],
$views[$item->query['view']]))
				{
					$view = $item->query['view'];

					$layout = '';

					if (isset($item->query['layout']))
					{
						$layout = ':' . $item->query['layout'];
					}

					if ($views[$view]->key)
					{
						if (!isset($this->lookup[$language][$view . $layout]))
						{
							$this->lookup[$language][$view . $layout] = array();
						}

						if (!isset($this->lookup[$language][$view]))
						{
							$this->lookup[$language][$view] = array();
						}

						// If menuitem has no key set, we assume 0.
						if (!isset($item->query[$views[$view]->key]))
						{
							$item->query[$views[$view]->key] = 0;
						}

						/**
						 * Here it will become a bit tricky
						 * language != * can override existing entries
						 * language == * cannot override existing entries
						 */
						if (!isset($this->lookup[$language][$view .
$layout][$item->query[$views[$view]->key]]) || $item->language !==
'*')
						{
							$this->lookup[$language][$view .
$layout][$item->query[$views[$view]->key]] = $item->id;
							$this->lookup[$language][$view][$item->query[$views[$view]->key]]
= $item->id;
						}
					}
					else
					{
						/**
						 * Here it will become a bit tricky
						 * language != * can override existing entries
						 * language == * cannot override existing entries
						 */
						if (!isset($this->lookup[$language][$view . $layout]) ||
$item->language !== '*')
						{
							$this->lookup[$language][$view . $layout] = $item->id;
							$this->lookup[$language][$view] = $item->id;
						}
					}
				}
			}
		}
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function parse(&$segments, &$vars)
	{
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function build(&$query, &$segments)
	{
	}
}
Rules/NomenuRules.php000064400000005524151162265130010633 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule to process URLs without a menu item
 *
 * @since  3.4
 */
class NomenuRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var RouterView
	 * @since 3.4
	 */
	protected $router;

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function preprocess(&$query)
	{
	}

	/**
	 * Parse a menu-less URL
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars)
	{
		$active = $this->router->menu->getActive();

		if (!is_object($active))
		{
			$views = $this->router->getViews();

			if (isset($views[$segments[0]]))
			{
				$vars['view'] = array_shift($segments);

				if (isset($views[$vars['view']]->key) &&
isset($segments[0]))
				{
					$vars[$views[$vars['view']]->key] =
preg_replace('/-/', ':', array_shift($segments), 1);
				}
			}
		}
	}

	/**
	 * Build a menu-less URL
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments)
	{
		$menu_found = false;

		if (isset($query['Itemid']))
		{
			$item =
$this->router->menu->getItem($query['Itemid']);

			if (!isset($query['option']) || ($item &&
$item->query['option'] === $query['option']))
			{
				$menu_found = true;
			}
		}

		if (!$menu_found && isset($query['view']))
		{
			$views = $this->router->getViews();

			if (isset($views[$query['view']]))
			{
				$view = $views[$query['view']];
				$segments[] = $query['view'];

				if ($view->key && isset($query[$view->key]))
				{
					if (is_callable(array($this->router, 'get' .
ucfirst($view->name) . 'Segment')))
					{
						$result = call_user_func_array(array($this->router,
'get' . ucfirst($view->name) . 'Segment'),
array($query[$view->key], $query));
						$segments[] = str_replace(':', '-',
array_shift($result));
					}
					else
					{
						$segments[] = str_replace(':', '-',
$query[$view->key]);
					}

					unset($query[$views[$query['view']]->key]);
				}

				unset($query['view']);
			}
		}
	}
}
Rules/RulesInterface.php000064400000003157151162265140011273
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

/**
 * RouterRules interface for Joomla
 *
 * @since  3.4
 */
interface RulesInterface
{
	/**
	 * Prepares a query set to be handed over to the build() method.
	 * This should complete a partial query set to work as a complete
non-SEFed
	 * URL and in general make sure that all information is present and
properly
	 * formatted. For example, the Itemid should be retrieved and set here.
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query);

	/**
	 * Parses a URI to retrieve informations for the right route through
	 * the component.
	 * This method should retrieve all its input from its method arguments.
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars);

	/**
	 * Builds URI segments from a query to encode the necessary informations
	 * for a route in a human-readable URL.
	 * This method should retrieve all its input from its method arguments.
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments);
}
Rules/StandardRules.php000064400000015231151162265160011131
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule for the standard handling of component routing
 *
 * @since  3.4
 */
class StandardRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var    RouterView
	 * @since  3.4
	 */
	protected $router;

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query)
	{
	}

	/**
	 * Parse the URL
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars)
	{
		// Get the views and the currently active query vars
		$views  = $this->router->getViews();
		$active = $this->router->menu->getActive();

		if ($active)
		{
			$vars = array_merge($active->query, $vars);
		}

		// We don't have a view or its not a view of this component! We stop
here
		if (!isset($vars['view']) ||
!isset($views[$vars['view']]))
		{
			return;
		}

		// Copy the segments, so that we can iterate over all of them and at the
same time modify the original segments
		$tempSegments = $segments;

		// Iterate over the segments as long as a segment fits
		foreach ($tempSegments as $segment)
		{
			// Our current view is nestable. We need to check first if the segment
fits to that
			if ($views[$vars['view']]->nestable)
			{
				if (is_callable(array($this->router, 'get' .
ucfirst($views[$vars['view']]->name) . 'Id')))
				{
					$key = call_user_func_array(array($this->router, 'get' .
ucfirst($views[$vars['view']]->name) . 'Id'),
array($segment, $vars));

					// Did we get a proper key? If not, we need to look in the child-views
					if ($key)
					{
						$vars[$views[$vars['view']]->key] = $key;

						array_shift($segments);

						continue;
					}
				}
				else
				{
					// The router is not complete. The get<View>Id() method is
missing.
					return;
				}
			}

			// Lets find the right view that belongs to this segment
			$found = false;

			foreach ($views[$vars['view']]->children as $view)
			{
				if (!$view->key)
				{
					if ($view->name === $segment)
					{
						// The segment is a view name
						$parent       = $views[$vars['view']];
						$vars['view'] = $view->name;
						$found        = true;

						if ($view->parent_key && isset($vars[$parent->key]))
						{
							$parent_key              = $vars[$parent->key];
							$vars[$view->parent_key] = $parent_key;

							unset($vars[$parent->key]);
						}

						break;
					}
				}
				elseif (is_callable(array($this->router, 'get' .
ucfirst($view->name) . 'Id')))
				{
					// Hand the data over to the router specific method and see if there
is a content item that fits
					$key = call_user_func_array(array($this->router, 'get' .
ucfirst($view->name) . 'Id'), array($segment, $vars));

					if ($key)
					{
						// Found the right view and the right item
						$parent       = $views[$vars['view']];
						$vars['view'] = $view->name;
						$found        = true;

						if ($view->parent_key && isset($vars[$parent->key]))
						{
							$parent_key              = $vars[$parent->key];
							$vars[$view->parent_key] = $parent_key;

							unset($vars[$parent->key]);
						}

						$vars[$view->key] = $key;

						break;
					}
				}
			}

			if (!$found)
			{
				return;
			}

			array_shift($segments);
		}
	}

	/**
	 * Build a standard URL
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments)
	{
		if (!isset($query['Itemid'], $query['view']))
		{
			return;
		}

		// Get the menu item belonging to the Itemid that has been found
		$item =
$this->router->menu->getItem($query['Itemid']);

		if ($item === null
			|| $item->component !== 'com_' .
$this->router->getName()
			|| !isset($item->query['view']))
		{
			return;
		}

		// Get menu item layout
		$mLayout = isset($item->query['layout']) ?
$item->query['layout'] : null;

		// Get all views for this component
		$views = $this->router->getViews();

		// Return directly when the URL of the Itemid is identical with the URL
to build
		if ($item->query['view'] === $query['view'])
		{
			$view = $views[$query['view']];

			if (!$view->key)
			{
				unset($query['view']);

				if (isset($query['layout']) && $mLayout ===
$query['layout'])
				{
					unset($query['layout']);
				}

				return;
			}

			if (isset($query[$view->key]) &&
$item->query[$view->key] == (int) $query[$view->key])
			{
				unset($query[$view->key]);

				while ($view)
				{
					unset($query[$view->parent_key]);

					$view = $view->parent;
				}

				unset($query['view']);

				if (isset($query['layout']) && $mLayout ===
$query['layout'])
				{
					unset($query['layout']);
				}

				return;
			}
		}

		// Get the path from the view of the current URL and parse it to the menu
item
		$path  = array_reverse($this->router->getPath($query), true);
		$found = false;

		foreach ($path as $element => $ids)
		{
			$view = $views[$element];

			if ($found === false && $item->query['view'] ===
$element)
			{
				if ($view->nestable)
				{
					$found = true;
				}
				elseif ($view->children)
				{
					$found = true;

					continue;
				}
			}

			if ($found === false)
			{
				// Jump to the next view
				continue;
			}

			if ($ids)
			{
				if ($view->nestable)
				{
					$found2 = false;

					foreach (array_reverse($ids, true) as $id => $segment)
					{
						if ($found2)
						{
							$segments[] = str_replace(':', '-', $segment);
						}
						elseif ((int) $item->query[$view->key] === (int) $id)
						{
							$found2 = true;
						}
					}
				}
				elseif ($ids === true)
				{
					$segments[] = $element;
				}
				else
				{
					$segments[] = str_replace(':', '-',
current($ids));
				}
			}

			if ($view->parent_key)
			{
				// Remove parent key from query
				unset($query[$view->parent_key]);
			}
		}

		if ($found)
		{
			unset($query[$views[$query['view']]->key],
$query['view']);

			if (isset($query['layout']) && $mLayout ===
$query['layout'])
			{
				unset($query['layout']);
			}
		}
	}
}