<?php namespace App\Classes; use Klein\Klein; use App\Classes\Db; use App\Model\SectionModel; use App\Model\PageModel; class Router { protected static $router; protected static $routes = []; public static function _init(): void { self::$router = new Klein(); } //-------------------------------------// /** * Load all routes into the Klein object * * @return void */ public static function fire(): void { $path = parse_url($_SERVER['REQUEST_URI'])['path']; $check = str_replace('.', '', $path); // If it's a dynamic file or the file doesn't exist, go through the router. if ($path == $check || !file_exists(getcwd() . $path)) { Db::load(); self::setDefaultLayout(); self::loadConfigRoutes(); self::loadDbRoutes(); // Process basic routes foreach (self::$routes as $route) { // ["/example/my-page", "ExampleController", "action", "" : ["view", "title", "description"]], self::addRoute(['GET', 'POST'], $route); } self::createNavigation(); self::setHttpError(); self::$router->dispatch(); } } /** * Add CRUD routes * Example usage: Router::resource('/example', 'CrudController'); * * @param string $route The URL location * @param string $controller Controller to handle this route * * @return void */ public static function resource(string $route, string $controller): void { /* * HTTP Verb Part (URL) Action (Method) * * GET /route indexAction * GET /route/create createAction * POST /route storeAction * GET /route/{id} showAction * GET /route/{id}/edit editAction * PUT/PATCH /route/{id} updateAction * DELETE /route/{id} destroyAction */ self::addRoute(['GET'], [$route, $controller, 'index']); self::addRoute(['GET'], [$route . '/create', $controller, 'create']); self::addRoute(['POST'], [$route, $controller, 'store']); self::addRoute(['GET'], [$route . '/[i:id]', $controller, 'show']); self::addRoute(['GET'], [$route . '/[i:id]/edit', $controller, 'edit']); self::addRoute(['PUT', 'PATCH'], [$route . '/[i:id]', $controller, 'update']); self::addRoute(['DELETE'], [$route . '/[i:id]', $controller, 'destroy']); } //-------------------------------------// protected static function setDefaultLayout(): void { self::$router->respond(function ($request, $response, $service) { $service->layout('../app/views/layouts/default.php'); }); } protected static function loadConfigRoutes(): void { if (file_exists('../route.php')) { self::$routes = require_once '../route.php'; } } /** * Add all pages in the Db to self::$routes * * @return void */ protected static function loadDbRoutes(): void { // Load all sections from Db $sections = SectionModel::selectAll('*', 'WHERE `active` = 1 ORDER BY `order` ASC'); // Return if no sections if (!_exists($sections)) { return; } // Load all pages from Db $pages = PageModel::selectAll('DISTINCT page.*', ' LEFT JOIN page_has_content ON page_has_content.page_id = page.id LEFT JOIN content ON content.id = page_has_content.content_id WHERE page.active = 1 AND content.active = 1 ORDER BY page.order ASC; '); // Return if no pages if (!_exists($pages)) { return; } // Select id column $section = array_column($sections, 'section', 'id'); // Loop through all pages foreach ($pages as $pageKey => $page) { // Skip if section isn't created / active if (!_exists($section, $page['section_id'])) { continue; } // url = /section/page $url = '/' . $section[$page['section_id']] . '/' . $page['page']; // Add route self::$routes[] = [$url, 'PageController', 'route', [$page['id']]]; } // Cache sections and pages Db::setSections($sections); Db::setPages($pages); } protected static function addRoute(array $method = [], array $data = []): void { if (!_exists($method) || !_exists($data) || count($data) < 2) { return; } $route = $data[0] ?? null; $controller = $data[1] ?? null; $action = $data[2] ?? null; $parameters = $data[3] ?? []; if (!$route || !$controller || !is_array($parameters)) { return; } // Create Klein route self::$router->respond($method, $route, function($request, $response, $service) use($controller, $action, $parameters) { // Create new Controller object $controller = '\App\Controllers\\' . $controller; $controller = new $controller(self::$router); // Complete action variable if (!$action) { $action = 'indexAction'; } else { $action .= 'Action'; } // If method does not exist in object if (!method_exists($controller, $action)) { return $controller->throw404(); } // If no valid permissions if ($controller->getAdminSection() && !$controller->getLoggedIn()) { return $controller->throw404(); } // Get URL parameters $namedParameters = array_filter($request->paramsNamed()->all(), 'is_string', ARRAY_FILTER_USE_KEY); $namedParameters = array_values($namedParameters); // Append URL parameters to the hard-coded parameters array_push($parameters, ...$namedParameters); // Call Controller action return $controller->{$action}(...$parameters); }); } public static function createNavigation(): void { // Pull from cache $sections = Db::getSections(); $pages = Db::getPages(); // [ // [ // 'section url', // 'title', // ['page url', 'title'], // ['page url', 'titleOfPage2'], // ], // [], // [], // ] $navigation = []; // Generate sections foreach ($sections as $section) { // Skip hidden sections if ($section['hide_navigation'] == '1') { continue; } // Add URL, title to ID $navigation[$section['id']] = [ $section['section'], $section['title'] ]; } // Generate pages foreach ($pages as $page) { // Skip hidden sections if (!_exists($navigation, $page['section_id'])) { continue; } // Skip hidden pages if ($page['hide_navigation'] == '1') { continue; } // Add [URL, title] to ID $navigation[$page['section_id']][] = [ $page['page'], $page['title'] ]; } self::$router->service()->navigation = $navigation; } //-------------------------------------// protected static function setHttpError(): void { self::$router->onHttpError(function($code) { $service = self::$router->service(); switch ($code) { case 404: // @Todo: find out why this was needed and remove if possible $service->escape = function (?string $string) { return htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8'); }; self::$router->response()->sendHeaders(true, true); $service->pageTitle = 'Error 404 (Not Found)'; $service->render('../app/views/errors/404.php'); break; } }); } //-------------------------------------// public static function getRoutes(): array { return self::$routes; } } Router::_init();