<?php
/*------------------------------------------------------------------------------
  $Id$

  AbanteCart, Ideal OpenSource Ecommerce Solution
  http://www.AbanteCart.com

  Copyright © 2011-2023 Belavier Commerce LLC

  This source file is subject to Open Software License (OSL 3.0)
  License details is bundled with this package in the file LICENSE.txt.
  It is also available at this URL:
  <http://www.opensource.org/licenses/OSL-3.0>

 UPGRADE NOTE:
   Do not edit or add to this file if you wish to upgrade AbanteCart to newer
   versions in the future. If you wish to customize AbanteCart for your
   needs please refer to http://www.AbanteCart.com for more information.
------------------------------------------------------------------------------*/

/**
 * Class ARouter
 *
 * @property AResponse $response
 * @property ARequest $request
 * @property AConfig $config
 */
final class ARouter
{
    /**
     * @var Registry
     */
    protected $registry;
    /**
     * @var string
     */
    protected $rt;
    /**
     * @var string
     */
    protected $request_type;
    /**
     * @var string
     */
    protected $controller;
    /**
     * @var string
     */
    protected $method;

    /**
     * @param Registry $registry
     */
    public function __construct($registry)
    {
        $this->registry = $registry;
    }

    public function __destruct()
    {
        $this->rt = '';
    }

    public function __get($key)
    {
        return $this->registry->get($key);
    }

    public function __set($key, $value)
    {
        $this->registry->set($key, $value);
    }

    /**
     * @param $rt
     *
     * @throws AException
     */
    public function processRoute($rt)
    {
        $this->rt = $rt;
        if (empty($this->rt)) {
            throw new AException(AC_ERR_LOAD, 'Error: Route is undefined!');
        }

        $this->route();
    }

    /**
     * @return string
     */
    public function getRequestType()
    {
        return $this->request_type;
    }

    /**
     * @return string
     */
    public function getController()
    {
        return $this->controller;
    }

    /**
     * @param string $rt
     *
     * @return string
     */
    public function resetController($rt = '')
    {
        if ($rt) {
            $this->controller = $rt;
        }
        return $this->controller;
    }
    /**
     * @param string $rt
     *
     * @return string
     */
    public function resetRt($rt = '')
    {
        if ($rt) {
            $this->rt = $rt;
        }
        return $this->rt;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    protected function route()
    {
        $path_nodes = explode('/', $this->rt);
        //Identify what resource do we load explicitly. Page, Response or API type
        //Check the path. If started with p/, r/ or a/ -> This is explicit call of page, response or API
        if ($path_nodes[0] == 'p') {
            $this->request_type = 'page';
            $this->rt = preg_replace('/^p\//', '', $this->rt);
        } else {
            if ($path_nodes[0] == 'r') {
                $this->request_type = 'response';
                $this->rt = preg_replace('/^r\//', '', $this->rt);
            } else {
                if ($path_nodes[0] == 'a') {
                    $this->request_type = 'api';
                    $this->rt = preg_replace('/^a\//', '', $this->rt);
                } else {
                    if ($path_nodes[0] == 'task') {
                        $this->request_type = 'task';
                        $this->rt = preg_replace('/^task\//', '', $this->rt);
                    } else {
                        //find implicit path of controller
                        //Pages section has priority
                        if ($this->detectController("pages")) {
                            $this->request_type = 'page';
                            // defense from click-jacking attack (prevention of bad iframe covering)
                            // adds header only for page controllers or when embed mode is disabled
                            if ($this->config && $this->request && isset($this->request->get['embed_mode']) && $this->request->get['embed_mode']) {
                                $this->config->set('embed_mode', true);
                            } elseif ($this->config
                                && $this->request
                                && strpos($this->request->get['rt'], 'embed') === false
                                && !$this->config->get('embed_mode')
                            ) {
                                $this->response->addHeader('X-Frame-Options: SAMEORIGIN');
                            }
                        } else {
                            if ($this->detectController("responses")) {
                                $this->request_type = 'response';
                            } else {
                                if ($this->detectController("api")) {
                                    $this->request_type = 'api';
                                }
                            }
                        }
                    }
                }
            }
        }

        if ($this->request_type == 'page') {
            $page_controller = new APage($this->registry);

            if (!defined('IS_ADMIN') || !IS_ADMIN) {
                //Load required controller for storefront
                $page_controller->addPreDispatch('common/maintenance');
                $page_controller->addPreDispatch('common/seo_url');
            } else {
                //Load required controller for admin
                $page_controller->addPreDispatch('common/home/login');
                $page_controller->addPreDispatch('common/ant');
                $page_controller->addPreDispatch('common/home/permission');
            }
            //Validate controller only. If does not exist process not found
            if ($this->detectController("pages")) {
                // Build the page
                $page_controller->build($this->rt);
            } else {
                $page_controller->build('error/not_found');
            }
        } else {
            if ($this->request_type == 'response') {
                $resp_controller = new ATypeResponse($this->registry);
                if (!defined('IS_ADMIN') || !IS_ADMIN) {
                    //Load required controller for storefront
                    $resp_controller->addPreDispatch('common/maintenance/response');
                } else {
                    //Load required controller for admin
                    $resp_controller->addPreDispatch('responses/common/access/login');
                    $resp_controller->addPreDispatch('responses/common/access/permission');
                }
                //Validate controller only. If does not exist process not found
                if ($this->detectController("responses")) {
                    // Build the response
                    $resp_controller->build($this->rt);
                } else {
                    $resp_controller->build('error/ajaxerror/not_found');
                }
            } else {
                if ($this->request_type == 'api') {
                    $api_controller = new AAPI($this->registry);
                    //CORS preflight request
                    $api_controller->addPreDispatch('api/common/preflight');
                    //Validate Admin access, login and permissions
                    $api_controller->addPreDispatch('api/common/access');
                    if (defined('IS_ADMIN') && IS_ADMIN) {
                        $api_controller->addPreDispatch('api/common/access/login');
                        $api_controller->addPreDispatch('api/common/access/permission');
                    }
                    //Validate controller only. If it does not exist - process not found
                    if ($this->detectController("api")) {
                        // Build the response
                        $api_controller->build($this->rt);
                    } else {
                        $api_controller->build('error/ajaxerror/not_found');
                    }
                } else {
                    if ($this->request_type == 'task') {
                        $task_controller = new ATypeTask($this->registry);
                        // do not allow calling task controllers from SF-side
                        $resp_controller = new ATypeResponse($this->registry);
                        if (!defined('IS_ADMIN') || !IS_ADMIN) {
                            $resp_controller->build('error/ajaxerror/not_found');
                        } else {
                            //Load the required controller for admin and check authorization
                            $resp_controller->addPreDispatch('responses/common/access/login');
                            $resp_controller->addPreDispatch('responses/common/access/permission');
                        }
                        //Validate controller only. If it does not exist - process not found
                        if ($this->detectController("task")) {
                            // Build the response
                            $task_controller->build($this->rt);
                        } else {
                            $resp_controller = new ATypeResponse($this->registry);
                            $resp_controller->build('error/ajaxerror/not_found');
                        }
                    } else {
                        //Security: this is not the main controller. Do not allow running it.
                        $this->request_type = 'page';
                        $this->controller = 'error/not_found';
                        $this->method = '';
                        $page_controller = new APage($this->registry);
                        $page_controller->build($this->controller);
                    }
                }
            }
        }
    }

    /**
     * @param $type
     *
     * @return bool
     */
    public function detectController($type)
    {
        //looking for controller in admin/storefront section
        $dir_app = DIR_APP_SECTION . 'controller' . DS . $type . DS;
        $path_nodes = explode('/', $this->rt);
        $path_build = '';

        // looking for a controller in an extensions section
        $result = $this->registry->get('extensions')->isExtensionController($type . '/' . $this->rt);
        if ($result) {
            $extension_id = $result['extension'];
            // set new path if controller was found in admin/storefront section && in extensions section
            $current_section = IS_ADMIN ? DIR_EXT_ADMIN : DIR_EXT_STORE;
            $dir_app = DIR_EXT . $extension_id . $current_section . 'controller' . DS . $type . DS;
        }
        //process path and try to locate the controller
        foreach ($path_nodes as $path_node) {
            $path_node = trim($path_node);
            $path_build .= $path_node;
            if (is_dir($dir_app . str_replace('/', DS, $path_build))) {
                $path_build .= '/';
                array_shift($path_nodes);
                continue;
            }

            if (is_file($dir_app . $path_build . '.php')) {
                //Controller found. Save information and return TRUE
                //Set controller and method for future use
                $this->controller = $type . '/' . $path_build;
                //The last part is the method of function to call
                array_shift($path_nodes);
                $lastKey = array_key_last($path_nodes);
                $method_to_call = $lastKey ? $path_nodes[$lastKey] : '';
                if ($method_to_call) {
                    $this->method = $method_to_call;
                } else {
                    //Set default method
                    $this->method = 'main';
                }
                return true;
            }
        }
        return false;
    }
}