Router.php

<?php

namespace Lia\Addon;

/**
 *
 * Sets up routes & handles Response events, returning route lists, and printing responses
 *
 */
class Router extends \Lia\Addon implements \Lia\Http\ResponseInterface {

    /**
     * Array of routers. Typically, one for each app
     */
    protected array $routers = [];

    /**
     * The primary router, for manually adding routes.
     *
     * If you change this, you must also call `addRouter($new_main_router)`
     */
    public \Lia\Http\Router $main_router;

    /**
     * The request pssed to onGetHttpRoutes().
     */
    protected \Lia\Http\Request $request;

    /**
     * Add a route to the main router.
     *
     * @see Lia\Http\Router::addRoute()
     * @see Lia\Addon\Router::$main_router
     *
     * @error if $main_router is not set. main_router is normally set during `onEnabledByApp`
     */
    public function addRoute(string $pattern, mixed $target, array $methods=["GET"],array $args = []): void {
        $this->main_router->addRoute($pattern,$target,$methods,$args);
    }

    /**
     * Add an Http Router.
     *
     * @param $router \Lia\Http\Router an HTTP router.
     */
    public function addRouter(\Lia\Http\Router $router){
        $this->routers[] = $router;
    }

    /**
     * Setup routes for an app.
     *
     * @param $app
     * @param $public_dir string|bool `true` to use `public` dir. `false` to NOT enable. `string` to use a different app sub-directory.
     */
    public function onEnabledByApp(\Lia\AppInterface $app, mixed $public_dir){
        if ($public_dir===true)$public_dir= 'public';
        else if ($public_dir===false)return;
        else if (!is_string($public_dir)){
            $ns = $app->getNamespace();
            $class = get_class($app);
            $type = gettype($public_dir);
            throw new \Exception("App '$ns' (class '$class') enabled addon 'lia:router', but provided an invalid value. The value MUST be a boolean or string, but a '$type' was provided. The value was '$public_dir'");
        }

        // actually setup the routes

        $router = new \Lia\Http\Router();
        // @TODO configure params to pass to routes, on an app-by-app basis
        // @TODO configure varDelim, on an app-by-app basis
        $base_url = $app->hasConfig('router.base_url') ? $app->getConfig('router.base_url') : '/';
        $router->allowExecutableFile = $app->hasConfig('router.allow_executable_file') ? $app->getConfig('router.allow_executable_file') : $router->allowExecutableFile;
        $router->varDelim = $app->hasConfig('router.var_delimiter') ? $app->getConfig('router.var_delimiter') : $router->varDelim;
        $router->theme_name = $app->hasConfig('router.default_theme') ? $app->getConfig('router.default_theme') : 'raw';

        $router->route_args = [
            'lia'=>$app->getLia(),            
            'app'=>$app,
            'public_dir'=>$public_dir,
        ];
        $router->addDirectoryRoutes($app->getPath($public_dir), $base_url);

        if (!isset($this->main_router)){
            $this->main_router = $router;
        }

        $this->routers[] = $router;
    }

    public function onGetHttpRoutes(\Lia\Http\Request $request, \Lia\Http\Response $response): array {
        $this->request = $request;
        $route_list = [];
        /** @var $router \Lia\Http\Router */
        foreach ($this->routers as $router){
            foreach ($router->getRoutes($request->url,$request->method) as $route){
                $route->router = $router;
                $route_list[] = $route;
            }
        }
        return $route_list;
        
    }

    public function onAddonsLoaded(\Lia\AppInterface $app, array $addons){
        // @TODO maybe add a different callback for an addon to set itself up

        $app->getLia()->hook(\Lia\Events::GetHttpRoutes->value, [$this, 'onGetHttpRoutes']);
        $app->getLia()->hook(\Lia\Events::NoHttpRoutes->value, [$this, 'onNoHttpRoutes']);
        $app->getLia()->hook(\Lia\Events::SingleHttpRoute->value, [$this, 'onSingleHttpRoute']);
        $app->getLia()->hook(\Lia\Events::MultipleHttpRoutes->value, [$this, 'onMultipleHttpRoutes']);
        $app->getLia()->hook(\Lia\Events::ApplyTheme->value, [$this, 'onApplyTheme']);

    }

    /** do nothing */
    public function onStartRequest(\Lia\Http\Request $request, \Lia\Http\Response $response){}

    /** Send a response */
    public function onNoHttpRoutes(\Lia\Http\Request $request, \Lia\Http\Response $response){
        $response->PageNotFound();
        $response->body = "No page was found for your request to ".$request->url;

        // How can no-route be over-ridden?
        // Well, I can just have something else to subscribe to the event & modify the response.
        // So lets try that.

    }

    /** 
     * Set a 500 internal server error header & response body indicating multiple routes were found & cannot be resolved.
     *
     * @param $routes array<int index, \Lia\Http\Route $route> an array of Routes
     */
    public function onMultipleHttpRoutes(\Lia\Http\Request $request, \Lia\Http\Response $response, array $routes){
        // TODO: Add some default or config-based handling of MultipleRoutes


        $response->InternalServerError();

        $safe_url = strip_tags($request->url);

        $count = count($routes);
        $response->body = 
            "<h1>Multiple Potential Responses</h1>"
            ."\n<p>This request has $count potential responses, and the server is not configured to select a response automatically.</p>"
            ."\n<p>The Website Administrator or Developer should consult the documentation and implement a fix, unless they like errors.</p>"
            ."\n<p>The requested url was '".$safe_url."'</p>";
            ;
    
    }

    /**
     * 
     * @param $route \Lia\Http\Route 
     */
    public function onSingleHttpRoute(\Lia\Http\Request $request, \Lia\Http\Response $response, \Lia\Http\Route $route){
        // @TODO: Figure out & document what params I'm passing to callable routes & file routes.


        if (!isset($response->theme_name))$response->theme_name = $route->router->theme_name;

        $responder = new \Lia\Http\Responder();
        $responder->allowExecutableFile = $route->router->allowExecutableFile;

        $responder->respond_to_route($request, $response, $route);

        //var_dump($route);
        //exit;

    }


    /** 
     * @param $routes array<int index, \Lia\Http\Route $route> an array of Routes
     */
    public function onApplyTheme(\Lia\Http\Request $request, \Lia\Http\Response $response, array $routes){
        // @TODO consider passing more params to theme views.
        
        if (!isset($response->theme_name))$theme = 'raw';
        else $theme = $response->theme_name;
        // @TODO Create an enum for the built-in themes, moreso for documentation than internal use.
        if ($theme=='raw')return;
        if ($theme=='json')return;
        if ($theme=='html_page'){
            // apply built-in-theme
            $response->body = $this->lia->view("lia:theme", ['content'=>$response->body]).'';
            return;
        }
        $view = $this->lia->view($theme, ['content'=>$response->body]);
        if (is_null($view)){
            // throw??
            // do nothing??
        }
        $response->body = "".$view;
    }


    /**
     * 
     * @param $request The request that is finished
     */
    public function onEndRequest(\Lia\Http\Request $request, \Lia\Http\Response $response, array $routes){

    }
    
}