. * */ namespace Vvveb\System; class Routes { const stringRegex = '{(\w+)}'; const varRegex = '[{#]([a-zA-Z]\w+)({([\d,]+)})?[#}]'; const stringLimitRegex = '{([a-zA-Z]\w+){([\d,]+)}}'; const numericRegex = '#([a-zA-Z]\w+)#'; const numericLimitRegex = '#([a-zA-Z]\w+){([\d,]+)}#'; const wildcardRegex = '\*'; private static $routes = []; private static $urls = []; private static $modules = null; private static $init = false; private static function processRoute($url, $data) { $module = $data['module']; //self :: $modules[$module] = $url; $parameters = []; if (preg_match_all('/' . self :: varRegex . '/', $url, $matches)) { if ($matches[1]) { $parameters = $matches[1]; } } self :: $modules[$module][] = ['url' => $url, 'parameters' => $parameters, 'count' => count($parameters)]; //add urls with most parameters first for proper matching uasort(self :: $modules[$module], function ($a, $b) { return $b['count'] <=> $a['count']; }); $route = $url; //escape / for regex $url = str_replace('/', '\/', $url); //numeric limit $url = preg_replace('/' . self :: numericLimitRegex . '/', '(?<$1>\d{$2})', $url); //numeric $url = preg_replace('/' . self :: numericRegex . '/', '(?<$1>\d+)', $url); //string limit $url = preg_replace('/' . self :: stringLimitRegex . '/', '(?<$1>[^$\/]{$2})', $url); //string $url = preg_replace('/' . self :: stringRegex . '/', '(?<$1>[^$\/]+)', $url); //wildcard $url = preg_replace('/' . self :: wildcardRegex . '/', '.*?', $url); self :: $urls[$route] = [$url, $module]; return $url; } public static function addRoute($url, $data) { self :: $routes[$url] = $data; self :: processRoute($url, $data); } public static function removeRoute($url) { if (is_array($url)) { foreach ($url as $route) { $module = self :: $urls[$route] ?? false; if ($module) { unset(self :: $modules[$module]); } unset(self :: $urls[$route]); } } else { $module = self :: $urls[$url] ?? false; if ($module) { unset(self :: $modules[$module]); } unset(self :: $urls[$url]); } } public static function init($app = 'app') { $cacheDriver = Cache :: getInstance(); $cacheKey = $app; if ($result = $cacheDriver->get('routes', $cacheKey)) { static::$routes = $result['routes']; static::$urls = $result['urls']; static::$modules = $result['modules']; } else { $routesConfig = DIR_ROOT . "/config/$app-routes.php"; if (file_exists($routesConfig)) { self :: $routes += include $routesConfig; } list(self :: $routes) = Event::trigger(__CLASS__, __FUNCTION__ , self :: $routes); foreach (self :: $routes as $url => $data) { self :: processRoute($url, $data); } $result = []; $result['routes'] = static::$routes; $result['modules'] = static::$modules; $result['urls'] = static::$urls; $cacheDriver->set('routes', $cacheKey, $result); } self :: $init = true; return true; } public static function match($url) { if (! self :: $init) { self :: init(); } //remove get parameters $url = preg_replace('/\?.*$/', '', str_replace('//', '/', $url)); foreach (self :: $urls as $route => $data) { $params = static::$routes[$route] ?? ''; list($pattern, $module) = $data; if ($url == $pattern || preg_match('/^' . $pattern . '$/', $url, $matches)) { $parameters = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY); $parameters['route'] = $route; $parameters['module'] = $module; $parameters['pattern'] = $pattern; $parameters += $params; return $parameters; } } return false; } public static function get($route) { return self :: $routes[$route] ?? []; } public static function getRoutes() { return self :: $routes ?? []; } public static function varReplace($url, $parameters) { return preg_replace_callback('/' . self :: stringRegex . '|' . self :: numericRegex . '/', function ($matches) use ($parameters) { $var = $matches[1]; if (isset($parameters[$var])) { return $parameters[$var]; } return ''; }, $url); } public static function getRouteData($module) { if (! self :: $init) { self :: init(); } return self :: $modules[$module] ?? []; } public static function getUrlData($url = false) { if (! $url) { $url = \Vvveb\getCurrentUrl(); } $parameters = self :: match($url); $route = stripslashes($parameters['route'] ?? ''); if ($parameters) { //$parameters['pattern'] = self :: $modules[$parameters['route']][0]['url']; $parameters = $parameters + self :: $routes[$route]; if (isset($parameters['edit'])) { $parameters['edit'] = self :: varReplace($parameters['edit'], $parameters); } } return $parameters; } public static function url($route, $parameters = false) { if (! self :: $init) { self :: init(); } if (isset(self :: $modules[$route])) { $pattern = self :: $modules[$route][0]['url'] ?? ''; if ($parameters && isset($parameters['route'])) { unset($parameters['route']); } $parameters_count = is_array($parameters) ? count($parameters) : 0; $param_keys = is_array($parameters) ? array_keys($parameters) : []; //if ($param_keys) { foreach (self :: $modules[$route] as $value) { //select route that has all parameters if ($value['parameters'] && $parameters_count) { if (count(array_intersect($value['parameters'], $param_keys)) === count($value['parameters'])) { $pattern = $value['url']; break; } } else { $no_parameters = $value['url']; } } //} if (! $parameters) { $pattern = $no_parameters; } $missing = false; $url = preg_replace_callback('/' . self :: varRegex . '/', function ($matches) use ($parameters, &$missing) { $var = $matches[1]; if (isset($parameters[$var])) { return $parameters[$var]; } else { $missing = true; } return ''; }, $pattern); if ($missing) { return '/?route=' . $route . '&' . (is_array($parameters) ? http_build_query($parameters) : ''); } else { return $url; } } } }