<?php

/**
 * OpenMage
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available at https://opensource.org/license/osl-3-0-php
 *
 * @category   Mage
 * @package    Mage_Core
 * @copyright  Copyright (c) 2006-2020 Magento, Inc. (https://www.magento.com)
 * @copyright  Copyright (c) 2019-2024 The OpenMage Contributors (https://www.openmage.org)
 * @license    https://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * Url rewrite request model
 *
 * @category   Mage
 * @package    Mage_Core
 */
class Mage_Core_Model_Url_Rewrite_Request
{
    /**
     * Instance of request
     *
     * @var Mage_Core_Controller_Request_Http
     */
    protected $_request;

    /**
     * Instance of core config model
     *
     * @var Mage_Core_Model_Config
     */
    protected $_config;

    /**
     * Collection of front controller's routers
     *
     * @var array
     */
    protected $_routers = [];

    /**
     * Instance of url rewrite model
     *
     * @var Mage_Core_Model_Url_Rewrite
     */
    protected $_rewrite;

    /**
     * Application
     *
     * @var Mage_Core_Model_App
     */
    protected $_app;

    /**
     * Mage Factory model
     *
     * @var Mage_Core_Model_Factory
     */
    protected $_factory;

    /**
     * Constructor
     * Arguments:
     *   request  - Zend_Controller_Request_Http
     *   config   - Mage_Core_Model_Config
     *   factory  - Mage_Core_Model_Factory
     *   routers  - array
     */
    public function __construct(array $args)
    {
        $this->_factory = !empty($args['factory']) ? $args['factory'] : Mage::getModel('core/factory');
        $this->_app     = !empty($args['app']) ? $args['app'] : Mage::app();
        $this->_config  = !empty($args['config']) ? $args['config'] : Mage::getConfig();
        $this->_request = !empty($args['request'])
            ? $args['request'] : Mage::app()->getFrontController()->getRequest();
        $this->_rewrite = !empty($args['rewrite'])
            ? $args['rewrite'] : $this->_factory->getModel('core/url_rewrite');

        if (!empty($args['routers'])) {
            $this->_routers = $args['routers'];
        }
    }

    /**
     * Implement logic of custom rewrites
     *
     * @return bool
     */
    public function rewrite()
    {
        if (!Mage::isInstalled()) {
            return false;
        }

        if (!$this->_request->isStraight()) {
            Varien_Profiler::start('mage::dispatch::db_url_rewrite');
            $this->_rewriteDb();
            Varien_Profiler::stop('mage::dispatch::db_url_rewrite');
        }

        Varien_Profiler::start('mage::dispatch::config_url_rewrite');
        $this->_rewriteConfig();
        Varien_Profiler::stop('mage::dispatch::config_url_rewrite');

        return true;
    }

    /**
     * Implement logic of custom rewrites
     *
     * @return bool
     */
    protected function _rewriteDb()
    {
        if ($this->_rewrite->getStoreId() === null || $this->_rewrite->getStoreId() === false) {
            $this->_rewrite->setStoreId($this->_app->getStore()->getId());
        }

        $requestCases = $this->_getRequestCases();
        $this->_rewrite->loadByRequestPath($requestCases);

        $fromStore = $this->_request->getQuery('___from_store');
        if (!$this->_rewrite->getId() && $fromStore) {
            $stores = $this->_app->getStores(false, true);
            if (!empty($stores[$fromStore])) {
                $store = $stores[$fromStore];
                $fromStoreId = $store->getId();
            } else {
                return false;
            }

            $this->_rewrite->setStoreId($fromStoreId)->loadByRequestPath($requestCases);
            if (!$this->_rewrite->getId()) {
                return false;
            }

            // Load rewrite by id_path
            $currentStore = $this->_app->getStore();
            $this->_rewrite->setStoreId($currentStore->getId())->loadByIdPath($this->_rewrite->getIdPath());

            $this->_setStoreCodeCookie($currentStore->getCode());

            $targetUrl = $currentStore->getBaseUrl() . $this->_rewrite->getRequestPath();
            $this->_sendRedirectHeaders($targetUrl, true);
        }

        if (!$this->_rewrite->getId()) {
            return false;
        }

        $this->_request->setAlias(
            Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
            $this->_rewrite->getRequestPath(),
        );
        $this->_processRedirectOptions();

        return true;
    }

    /**
     * Set store code to a cookie
     *
     * @param string $storeCode
     */
    protected function _setStoreCodeCookie($storeCode)
    {
        $store = $this->_app->getStore($storeCode);
        if ($store->getWebsite()->getDefaultStore()->getId() == $store->getId()) {
            $this->_app->getCookie()->delete(Mage_Core_Model_Store::COOKIE_NAME);
        } else {
            $this->_app->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $storeCode, true);
        }
    }

    /**
     * Process redirect (R) and permanent redirect (RP)
     *
     * @return $this
     */
    protected function _processRedirectOptions()
    {
        $isPermanentRedirectOption = $this->_rewrite->hasOption('RP');

        $external = substr($this->_rewrite->getTargetPath(), 0, 6);
        if ($external === 'http:/' || $external === 'https:') {
            $destinationStoreCode = $this->_app->getStore($this->_rewrite->getStoreId())->getCode();
            $this->_setStoreCodeCookie($destinationStoreCode);
            $this->_sendRedirectHeaders($this->_rewrite->getTargetPath(), $isPermanentRedirectOption);
        }

        $targetUrl = $this->_request->getBaseUrl() . '/' . $this->_rewrite->getTargetPath();

        $storeCode = $this->_app->getStore()->getCode();
        if (Mage::getStoreConfig(Mage_Core_Model_Store::XML_PATH_STORE_IN_URL) && !empty($storeCode)) {
            $targetUrl = $this->_request->getBaseUrl() . '/' . $storeCode . '/' . $this->_rewrite->getTargetPath();
        }

        if ($this->_rewrite->hasOption('R') || $isPermanentRedirectOption) {
            $this->_sendRedirectHeaders($targetUrl, $isPermanentRedirectOption);
        }

        $queryString = $this->_getQueryString();
        if ($queryString) {
            $targetUrl .= '?' . $queryString;
        }

        $this->_request->setRequestUri($targetUrl);
        $this->_request->setPathInfo($this->_rewrite->getTargetPath());

        return $this;
    }

    /**
     * Apply configuration rewrites to current url
     *
     * @return bool
     */
    protected function _rewriteConfig()
    {
        $config = $this->_config->getNode('global/rewrite');
        if (!$config) {
            return false;
        }
        foreach ($config->children() as $rewrite) {
            $from = (string) $rewrite->from;
            $to = (string) $rewrite->to;
            if (empty($from) || empty($to)) {
                continue;
            }
            $from = $this->_processRewriteUrl($from);
            $to   = $this->_processRewriteUrl($to);

            $pathInfo = preg_replace($from, $to, $this->_request->getPathInfo());
            if (isset($rewrite->complete)) {
                $this->_request->setPathInfo($pathInfo);
            } else {
                $this->_request->rewritePathInfo($pathInfo);
            }
        }
        return true;
    }

    /**
     * Prepare request cases.
     *
     * We have two cases of incoming paths - with and without slashes at the end ("/somepath/" and "/somepath").
     * Each of them matches two url rewrite request paths
     * - with and without slashes at the end ("/somepath/" and "/somepath").
     * Choose any matched rewrite, but in priority order that depends on same presence of slash and query params.
     *
     * @return array
     */
    protected function _getRequestCases()
    {
        $pathInfo = $this->_request->getPathInfo();
        $requestPath = trim($pathInfo, '/');
        $origSlash = (substr($pathInfo, -1) == '/') ? '/' : '';
        // If there were final slash - add nothing to less priority paths. And vice versa.
        $altSlash = $origSlash ? '' : '/';

        $requestCases = [];
        // Query params in request, matching "path + query" has more priority
        $queryString = $this->_getQueryString();
        if ($queryString) {
            $requestCases[] = $requestPath . $origSlash . '?' . $queryString;
            $requestCases[] = $requestPath . $altSlash . '?' . $queryString;
        }
        $requestCases[] = $requestPath . $origSlash;
        $requestCases[] = $requestPath . $altSlash;
        return $requestCases;
    }

    /**
     * Add location header and disable browser page caching
     *
     * @param string $url
     * @param bool $isPermanent
     * @SuppressWarnings("PHPMD.ExitExpression")
     */
    protected function _sendRedirectHeaders($url, $isPermanent = false)
    {
        if ($isPermanent) {
            header('HTTP/1.1 301 Moved Permanently');
        }

        header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Pragma: no-cache');
        header('Location: ' . $url);
        exit;
    }

    /**
     * Prepare and return QUERY_STRING
     *
     * @return bool|string
     * @SuppressWarnings("PHPMD.Superglobals")
     */
    protected function _getQueryString()
    {
        if (!empty($_SERVER['QUERY_STRING'])) {
            $queryParams = [];
            parse_str($_SERVER['QUERY_STRING'], $queryParams);
            $hasChanges = false;
            foreach (array_keys($queryParams) as $key) {
                if (substr($key, 0, 3) === '___') {
                    unset($queryParams[$key]);
                    $hasChanges = true;
                }
            }
            if ($hasChanges) {
                return http_build_query($queryParams);
            } else {
                return $_SERVER['QUERY_STRING'];
            }
        }
        return false;
    }

    /**
     * Replace route name placeholders in url to front name
     *
     * @param string $url
     * @return string
     */
    protected function _processRewriteUrl($url)
    {
        $startPos = strpos($url, '{');
        if ($startPos !== false) {
            $endPos = strpos($url, '}');
            $routeName = substr($url, $startPos + 1, $endPos - $startPos - 1);
            $router = $this->_getRouterByRoute($routeName);
            if ($router) {
                $frontName = $router->getFrontNameByRoute($routeName);
                $url = str_replace('{' . $routeName . '}', $frontName, $url);
            }
        }
        return $url;
    }

    /**
     * Retrieve router by name
     *
     * @param string $name
     * @return Mage_Core_Controller_Varien_Router_Abstract|false
     */
    protected function _getRouter($name)
    {
        return $this->_routers[$name] ?? false;
    }

    /**
     * Retrieve router by name
     *
     * @param string $routeName
     * @return Mage_Core_Controller_Varien_Router_Abstract
     */
    protected function _getRouterByRoute($routeName)
    {
        // empty route supplied - return base url
        if (empty($routeName)) {
            $router = $this->_getRouter('standard');
        } elseif ($this->_getRouter('admin')->getFrontNameByRoute($routeName)) {
            // try standard router url assembly
            $router = $this->_getRouter('admin');
        } elseif ($this->_getRouter('standard')->getFrontNameByRoute($routeName)) {
            // try standard router url assembly
            $router = $this->_getRouter('standard');
        } else {
            // try custom router url assembly
            $router = $this->_getRouter($routeName);
            if (!$router) {
                // get default router url
                $router = $this->_getRouter('default');
            }
        }
        return $router;
    }
}
