diff --git a/PluboRoutes/Endpoint/DeleteEndpoint.php b/PluboRoutes/Endpoint/DeleteEndpoint.php index 211efa1..f907f86 100644 --- a/PluboRoutes/Endpoint/DeleteEndpoint.php +++ b/PluboRoutes/Endpoint/DeleteEndpoint.php @@ -1,4 +1,5 @@ path = $path; $this->config = $config; $this->permission_callback = $permission_callback ?? '__return_true'; - $this->args = []; } /** diff --git a/PluboRoutes/Endpoint/EndpointInterface.php b/PluboRoutes/Endpoint/EndpointInterface.php index e1be79f..e98d839 100644 --- a/PluboRoutes/Endpoint/EndpointInterface.php +++ b/PluboRoutes/Endpoint/EndpointInterface.php @@ -1,4 +1,5 @@ self::DIGIT, 'number' => self::NUMBER, 'word' => self::WORD, 'text' => self::TEXT, + 'alphanumeric' => self::ALPHANUMERIC, + 'hex' => self::HEXADECIMAL, + 'uuid' => self::UUID, + 'file' => self::FILE_PATH, 'date' => self::DATE, - 'slug' => self::SLUG, - 'digit' => self::DIGIT, 'year' => self::YEAR, 'month' => self::MONTH, 'day' => self::DAY, + 'ip' => self::IP, 'jwt' => self::JWT, - 'email' => self::EMAIL, - 'ip' => self::IP + 'slug' => self::SLUG, ]; /** @@ -36,7 +43,7 @@ abstract class RegexHelper * * @param string $path */ - public static function getRegexMatches(string $regex_path) + public function getRegexMatches(string $regex_path) { preg_match_all('#\{(.+?)\}#', $regex_path, $matches); return $matches; @@ -48,7 +55,7 @@ public static function getRegexMatches(string $regex_path) * @param string $path * @return string */ - public static function cleanPath(string $path) + public function cleanPath(string $path) { return ltrim(trim($path), '/'); } @@ -59,5 +66,5 @@ public static function cleanPath(string $path) * @param mixed $type * @return string */ - abstract public static function getRegex($type): string; + abstract public function getRegex($type): string; } diff --git a/PluboRoutes/Helpers/RegexHelperEndpoints.php b/PluboRoutes/Helpers/RegexHelperEndpoints.php index c1a8f89..b4a7414 100644 --- a/PluboRoutes/Helpers/RegexHelperEndpoints.php +++ b/PluboRoutes/Helpers/RegexHelperEndpoints.php @@ -1,9 +1,10 @@ $regex_code)"; diff --git a/PluboRoutes/Helpers/RegexHelperRoutes.php b/PluboRoutes/Helpers/RegexHelperRoutes.php index 21e7ead..ebaf295 100644 --- a/PluboRoutes/Helpers/RegexHelperRoutes.php +++ b/PluboRoutes/Helpers/RegexHelperRoutes.php @@ -1,9 +1,10 @@ matched_route = $route; + $this->matched_args = $args; + $this->current_user = wp_get_current_user(); + } + + /** + * Check permissions for the matched route. + */ + public function checkPermissions() + { + $permission_callback = $this->matched_route->getPermissionCallback(); + if (!$permission_callback || !is_callable($permission_callback)) { + return; + } + $has_access = $permission_callback($this->matched_args); + if (!$has_access) { + $this->forbidAccess(); + } + + if ($this->checkLoggedIn()) { + $this->checkRoles(); + $this->checkCapabilities(); + } + } + + /** + * Check if the user is logged in and has access based on route settings. + * + * @return bool Whether the user is logged in. + */ + private function checkLoggedIn() + { + $is_logged_in = $this->current_user->exists(); + + if ( + !$this->matched_route->guestHasAccess() && !$is_logged_in + || !$this->matched_route->memberHasAccess() && $is_logged_in + ) { + $this->forbidAccess(); + } + + return $is_logged_in; + } + + /** + * Check if the user has the required roles for the matched route. + */ + private function checkRoles() + { + $allowed_roles = $this->matched_route->getRoles(); + if ($this->matched_route->hasRolesCallback()) { + $allowed_roles = $allowed_roles($this->matched_args); + } + if ($allowed_roles !== false && !array_intersect((array)$this->current_user->roles, (array)$allowed_roles)) { + $this->forbidAccess(); + } + } + + /** + * Check if the user has the required capabilities for the matched route. + */ + private function checkCapabilities() + { + $allowed_caps = $this->getAllowedCapabilities(); + if ($allowed_caps === false) { + return; + } + + $is_allowed = false; + foreach ((array)$allowed_caps as $allowed_cap) { + if ($this->current_user->has_cap($allowed_cap)) { + $is_allowed = true; + break; + } + } + if (!$is_allowed) { + $this->forbidAccess(); + } + } + + /** + * Get the allowed capabilities for the matched route. + * + * @return mixed + */ + private function getAllowedCapabilities() + { + $allowed_caps = $this->matched_route->getCapabilities(); + if ($this->matched_route->hasCapabilitiesCallback()) { + $allowed_caps = $allowed_caps($this->matched_args); + } + return $allowed_caps; + } + + /** + * Forbid access based on route settings. + */ + private function forbidAccess() + { + if ($this->matched_route->hasRedirect()) { + wp_redirect(esc_url_raw($this->matched_route->getRedirect()), $this->matched_route->getNotAllowedStatus()); + exit; + } + status_header($this->matched_route->getNotAllowedStatus()); + exit(); + } +} diff --git a/PluboRoutes/Route/ActionRoute.php b/PluboRoutes/Route/ActionRoute.php index 73c15ec..72ac044 100644 --- a/PluboRoutes/Route/ActionRoute.php +++ b/PluboRoutes/Route/ActionRoute.php @@ -1,4 +1,5 @@ action); } + + /** + * Get the status. + * + * @return int + */ + public function getStatus() + { + $status = $this->config['status'] ?? 200; + return (int)$status; + } } diff --git a/PluboRoutes/Route/PageRoute.php b/PluboRoutes/Route/PageRoute.php index d48fa52..5970df7 100644 --- a/PluboRoutes/Route/PageRoute.php +++ b/PluboRoutes/Route/PageRoute.php @@ -1,4 +1,5 @@ page_id; } + + /** + * Get the status. + * + * @return int + */ + public function getStatus() + { + $status = $this->config['status'] ?? 200; + return (int)$status; + } } diff --git a/PluboRoutes/Route/RedirectRoute.php b/PluboRoutes/Route/RedirectRoute.php index 42d442e..18a59eb 100644 --- a/PluboRoutes/Route/RedirectRoute.php +++ b/PluboRoutes/Route/RedirectRoute.php @@ -1,4 +1,5 @@ template; - } - - /** - * Check if guests have access. - * - * @return boolean - */ - public function guestHasAccess() - { - $guest_has_access = $this->config['guest'] ?? true; - return filter_var($guest_has_access, FILTER_VALIDATE_BOOLEAN); - } + $template_name = $this->template; + $custom_directory = $this->config['template_path'] ?? ''; - /** - * Check if a logged in user has access. - * - * @return boolean - */ - public function memberHasAccess() - { - $member_has_access = $this->config['logged_in'] ?? true; - return filter_var($member_has_access, FILTER_VALIDATE_BOOLEAN); - } - - /** - * Check if the route has a redirect if access not allowed. - * - * @return boolean - */ - public function hasRedirect() - { - $redirect = $this->config['redirect'] ?? true; - return filter_var(($redirect != false), FILTER_VALIDATE_BOOLEAN); - } + // Check if a custom directory is provided + if ($custom_directory) { + $customTemplate = trailingslashit($custom_directory) . $template_name; + if (is_readable($customTemplate)) { + return $customTemplate; + } + } - /** - * Get the http status if access not allowed. - * - * @return int - */ - public function getStatus() - { - $status = $this->hasRedirect() ? 302 : 403; - return $this->config['status'] ?? $status; - } + // Check if the template exists in the theme + $themeTemplate = locate_template(apply_filters('plubo/template', $template_name)); - /** - * Get the redirect URL. - * - * @return int - */ - public function getRedirect() - { - $redirect = $this->config['redirect'] ?? home_url(); - return $redirect; - } - - /** - * Check if the roles option is a callable. - * - * @return boolean - */ - public function hasRolesCallback() - { - $roles = $this->config['allowed_roles'] ?? []; - return is_callable($roles); - } - - /** - * Get the allowed roles. - * - * @return array|string - */ - public function getRoles() - { - $roles = $this->config['allowed_roles'] ?? false; - return $roles; - } - - /** - * Check if the capabilities option is a callable. - * - * @return boolean - */ - public function hasCapabilitiesCallback() - { - $capabilities = $this->config['allowed_caps'] ?? []; - return is_callable($capabilities); - } - - /** - * Get the allowed capabilities. - * - * @return array|string - */ - public function getCapabilities() - { - $capabilities = $this->config['allowed_caps'] ?? false; - return $capabilities; - } - - /** - * Get the permission callback. - * - * @return boolean - */ - public function getPermissionCallback() - { - $permission_callback = $this->config['permission_callback'] ?? false; - return $permission_callback; + return $themeTemplate ?: $template_name; } /** @@ -213,4 +118,15 @@ public function isRender() $render = $this->config['render'] ?? false; return filter_var(($render != false), FILTER_VALIDATE_BOOLEAN); } + + /** + * Get the status. + * + * @return int + */ + public function getStatus() + { + $status = $this->config['status'] ?? 200; + return (int)$status; + } } diff --git a/PluboRoutes/Route/RouteInterface.php b/PluboRoutes/Route/RouteInterface.php index 46bbd9d..48a37cd 100644 --- a/PluboRoutes/Route/RouteInterface.php +++ b/PluboRoutes/Route/RouteInterface.php @@ -1,4 +1,5 @@ config['guest'] ?? true; + return filter_var($guest_has_access, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Check if a logged in user has access. + * + * @return boolean + */ + public function memberHasAccess() + { + $member_has_access = $this->config['logged_in'] ?? true; + return filter_var($member_has_access, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Check if the route has a redirect if access not allowed. + * + * @return boolean + */ + public function hasRedirect() + { + $redirect = $this->config['redirect'] ?? false; + return filter_var(($redirect != false), FILTER_VALIDATE_BOOLEAN); + } + + /** + * Get the http status if access not allowed. + * + * @return int + */ + public function getNotAllowedStatus() + { + $status = $this->hasRedirect() ? 302 : 403; + return $this->config['forbidden_status'] ?? $status; + } + + /** + * Get the redirect URL. + * + * @return int + */ + public function getRedirect() + { + $redirect = $this->config['redirect'] ?? home_url(); + return $redirect; + } + + /** + * Check if the roles option is a callable. + * + * @return boolean + */ + public function hasRolesCallback() + { + $roles = $this->config['allowed_roles'] ?? []; + return is_callable($roles); + } + + /** + * Get the allowed roles. + * + * @return array|string + */ + public function getRoles() + { + $roles = $this->config['allowed_roles'] ?? false; + return $roles; + } + + /** + * Check if the capabilities option is a callable. + * + * @return boolean + */ + public function hasCapabilitiesCallback() + { + $capabilities = $this->config['allowed_caps'] ?? []; + return is_callable($capabilities); + } + + /** + * Get the allowed capabilities. + * + * @return array|string + */ + public function getCapabilities() + { + $capabilities = $this->config['allowed_caps'] ?? false; + return $capabilities; + } + + /** + * Get the permission callback. + * + * @return callable + */ + public function getPermissionCallback() + { + $permission_callback = $this->config['permission_callback'] ?? '__return_true'; + return $permission_callback; + } + /** * Serialize the route. * diff --git a/PluboRoutes/Router.php b/PluboRoutes/Router.php index f306862..cd84cad 100644 --- a/PluboRoutes/Router.php +++ b/PluboRoutes/Router.php @@ -1,4 +1,5 @@ routes = []; $this->endpoints = []; $this->route_variable = apply_filters('plubo/route_variable', 'route_name'); + $this->regex_routes = new RegexHelperRoutes(); + $this->regex_endpoints = new RegexHelperEndpoints(); } /** @@ -71,7 +88,8 @@ public function addEndpoint(EndpointInterface $endpoint) */ public function compileRoutes() { - add_rewrite_tag('%'.$this->route_variable.'%', '(.+)'); + add_rewrite_tag('%' . $this->route_variable . '%', '(.+)'); + foreach ($this->routes as $route) { if ($route instanceof PageRoute) { $this->addPageRule($route); @@ -89,9 +107,9 @@ public function compileEndpoints() foreach ($this->endpoints as $endpoint) { $path = $this->getEndpointPath($endpoint->getPath()); register_rest_route($endpoint->getNamespace(), $path, [ - 'methods' => $endpoint->getMethod(), - 'callback' => $endpoint->getConfig(), - 'permission_callback' => $endpoint->getPermissionCallback() + 'methods' => $endpoint->getMethod(), + 'callback' => $endpoint->getConfig(), + 'permission_callback' => $endpoint->getPermissionCallback() ]); } } @@ -102,19 +120,21 @@ public function compileEndpoints() * * @param array $query_variables * - * @return RouteInterface|WP_Error + * @return RouteInterface|\WP_Error */ public function match(array $query_variables) { if (empty($query_variables[$this->route_variable])) { return new \WP_Error('missing_route_variable'); } + $route_name = $query_variables[$this->route_variable]; foreach ($this->routes as $route) { if ($route->getName() === $route_name) { return $route; } } + return new \WP_Error('route_not_found'); } @@ -126,18 +146,20 @@ public function match(array $query_variables) */ private function addRule(RouteInterface $route, $position = 'top') { - $regex_path = RegexHelperRoutes::cleanPath($route->getPath()); - $matches = RegexHelperRoutes::getRegexMatches($regex_path); + $regex_path = $this->regex_routes->cleanPath($route->getPath()); + $matches = $this->regex_routes->getRegexMatches($regex_path); $index_string = 'index.php?' . $this->route_variable . '=' . $route->getName(); + if (!$matches) { return; } + foreach ($matches[1] as $key => $pattern) { $pattern = explode(':', $pattern); if (count($pattern) > 1) { $name = $pattern[0]; - $num_arg = $key+1; - $regex_code = RegexHelperRoutes::getRegex($pattern[1]); + $num_arg = $key + 1; + $regex_code = $this->regex_routes->getRegex($pattern[1]); $regex_path = str_replace($matches[0][$key], $regex_code, $regex_path); add_rewrite_tag("%$name%", $regex_code); $index_string .= "&$name=\$matches[$num_arg]"; @@ -160,6 +182,7 @@ private function addPageRule(PageRoute $route, $position = 'top') { $index_string = 'index.php?pagename=' . $route->getPageUri(); $page_path = $route->getPath(); + add_rewrite_rule("^$page_path$", $index_string, $position); add_filter('page_link', function ($link, $post_id) use ($route) { if ($post_id === $route->getPageId()) { @@ -173,16 +196,20 @@ private function addPageRule(PageRoute $route, $position = 'top') * Get translated Regex path for an endpoint route. * * @param string $path + * + * @return string */ private function getEndpointPath(string $path) { - $regex_path = RegexHelperEndpoints::cleanPath($path); - $matches = RegexHelperEndpoints::getRegexMatches($regex_path); + $regex_path = $this->regex_endpoints->cleanPath($path); + $matches = $this->regex_endpoints->getRegexMatches($regex_path); + if ($matches) { foreach ($matches[1] as $key => $pattern) { $regex_path = $this->getEndpointPatternPath($regex_path, $key, $pattern, $matches); } } + return $regex_path; } @@ -193,15 +220,18 @@ private function getEndpointPath(string $path) * @param int $key * @param string $pattern * @param array $matches - * @return string $regex_path + * + * @return string */ private function getEndpointPatternPath(string $path, int $key, string $pattern, array $matches) { $pattern = explode(':', $pattern); + if (count($pattern) > 1) { - $regex_code = RegexHelperEndpoints::getRegex($pattern); + $regex_code = $this->regex_endpoints->getRegex($pattern); $path = str_replace($matches[0][$key], $regex_code, $path); } + return $path; } @@ -210,16 +240,19 @@ private function getEndpointPatternPath(string $path, int $key, string $pattern, * * @param Route $route * @param string $index_string - * @return string $index_string + * + * @return string */ private function addExtraVars(Route $route, string $index_string) { $extra_vars = $route->getExtraVars(); + foreach ($extra_vars as $var_name => $var_value) { $index_string .= "&$var_name=$var_value"; $route->addArg($var_name); add_rewrite_tag("%$var_name%", '([a-z0-9-]+)'); } + return $index_string; } } diff --git a/PluboRoutes/RoutesProcessor.php b/PluboRoutes/RoutesProcessor.php index fa5958f..ece7520 100644 --- a/PluboRoutes/RoutesProcessor.php +++ b/PluboRoutes/RoutesProcessor.php @@ -1,4 +1,5 @@ router = $router; + $this->initHooks(); + } + + /** + * Initialize hooks with WordPress. + */ + private function initHooks() + { add_action('init', [$this, 'addRoutes']); add_action('parse_request', [$this, 'matchRouteRequest']); add_action('send_headers', [$this, 'basicAuth']); add_action('rest_api_init', [$this, 'addEndpoints']); add_action('template_redirect', [$this, 'doRouteActions']); - add_action('template_include', [$this, 'includeRouteTemplate']); + add_filter('template_include', [$this, 'includeRouteTemplate']); add_filter('body_class', [$this, 'addBodyClasses']); add_filter('document_title_parts', [$this, 'modifyTitle']); } @@ -65,9 +74,9 @@ public function __construct(Router $router) * Clone not allowed. * */ - private function __clone() - { - } + private function __clone() + { + } /** * Initialize processor with WordPress. @@ -75,10 +84,14 @@ private function __clone() */ public static function init() { - if (is_null(self::$instance)) { - self::$instance = new self(new Router()); - } - return self::$instance; + if (self::$instance === null) { + self::$instance = new self(new Router()); + } + + // Custom action for router initialization + do_action('plubo/router_init'); + + return self::$instance; } /** @@ -87,9 +100,12 @@ public static function init() public function addRoutes() { $routes = apply_filters('plubo/routes', []); + $routes = is_array($routes) ? $routes : []; + foreach ($routes as $route) { $this->router->addRoute($route); } + $this->router->compileRoutes(); $this->maybeFlushRewriteRules($routes, 'plubo-routes-hash'); } @@ -100,9 +116,12 @@ public function addRoutes() public function addEndpoints() { $endpoints = apply_filters('plubo/endpoints', []); + $endpoints = is_array($endpoints) ? $endpoints : []; + foreach ($endpoints as $endpoint) { $this->router->addEndpoint($endpoint); } + $this->router->compileEndpoints(); $this->maybeFlushRewriteRules($endpoints, 'plubo-endpoints-hash'); } @@ -112,6 +131,10 @@ public function addEndpoints() */ public function maybeFlushRewriteRules(array $values, string $option_name) { + if (!is_array($values)) { + return; + } + $hash = md5(serialize($values)); if ($hash != get_option($option_name)) { flush_rewrite_rules(); @@ -122,63 +145,88 @@ public function maybeFlushRewriteRules(array $values, string $option_name) /** * Step 2: Attempts to match the current request to an added route. * - * @param WP $env + * @param \WP $env */ public function matchRouteRequest(\WP $env) { $found_route = $this->router->match($env->query_vars); + if ($found_route instanceof RouteInterface) { - $found_args = []; - $args_names = $found_route->getArgs(); - $extra_args = $found_route->getExtraVars(); - foreach ($args_names as $arg_name) { - $query_value = $env->query_vars[$arg_name] ?? ($extra_args[$arg_name] ?? false); - $found_args[$arg_name] = $query_value; - } - $this->matched_route = $found_route; - $this->matched_args = $found_args; + $this->processMatchedRoute($found_route, $env); } - if ($found_route instanceof \WP_Error && - in_array('route_not_found', $found_route->get_error_codes())) { - wp_die($found_route, 'Route Not Found', ['response' => 404]); + + if ($found_route instanceof \WP_Error && in_array('route_not_found', $found_route->get_error_codes())) { + $this->handleRouteNotFound(); } } + /** + * Process matched route and set matched route and args. + * + * @param RouteInterface $found_route + * @param \WP $env + */ + private function processMatchedRoute(RouteInterface $found_route, \WP $env) + { + $found_args = []; + $args_names = $found_route->getArgs(); + $extra_args = $found_route->getExtraVars(); + + foreach ($args_names as $arg_name) { + $query_value = $env->query_vars[$arg_name] ?? ($extra_args[$arg_name] ?? false); + $found_args[$arg_name] = $query_value; + } + + $this->matched_route = $found_route; + $this->matched_args = $found_args; + + // Action hook after matching route request + do_action('plubo/after_matching_route_request', $this->matched_route, $this->matched_args, $extra_args, $env); + } + + /** + * Handle route not found error. + */ + private function handleRouteNotFound() + { + wp_die(esc_html('Route Not Found'), esc_html('Route Not Found'), ['response' => 404]); + } + /** * Step 3: Check if route has basic Auth enabled. */ public function basicAuth() { - if ($this->matched_route instanceof Route) { - if (!$this->matched_route->hasBasicAuth()) { - return; - } + if ($this->matched_route instanceof Route && $this->matched_route->hasBasicAuth()) { $this->checkBasicAuth(); } } + /** + * Check basic authentication for the matched route. + */ private function checkBasicAuth() { header('Cache-Control: no-cache, must-revalidate, max-age=0'); $basic_auth = $this->matched_route->getBasicAuth(); - $auth_user = $_SERVER['PHP_AUTH_USER'] ?? ''; - $auth_pass = $_SERVER['PHP_AUTH_PW'] ?? ''; - if (empty($auth_user) || empty($auth_pass)) { - $this->unauthorized(); - } - if (!array_key_exists($auth_user, $basic_auth)) { - $this->unauthorized(); - } - if ($auth_pass != $basic_auth[$auth_user]) { + $auth_user = isset($_SERVER['PHP_AUTH_USER']) ? wp_unslash(sanitize_text_field($_SERVER['PHP_AUTH_USER'])) : ''; + $auth_pass = isset($_SERVER['PHP_AUTH_PW']) ? wp_unslash(sanitize_text_field($_SERVER['PHP_AUTH_PW'])) : ''; + + if ( + empty($auth_user) || empty($auth_pass) + || !array_key_exists($auth_user, $basic_auth) + || $auth_pass != $basic_auth[$auth_user] + ) { $this->unauthorized(); } } + /** + * Handle unauthorized access. + */ private function unauthorized() { - header('HTTP/1.1 401 Authorization Required'); - header('WWW-Authenticate: Basic realm="Access denied"'); - exit; + wp_die(esc_html('Unauthorized Access'), esc_html('Unauthorized Access'), ['response' => 401]); } /** @@ -186,6 +234,21 @@ private function unauthorized() */ public function doRouteActions() { + if ($this->matched_route instanceof Route || $this->matched_route instanceof ActionRoute || $this->matched_route instanceof RedirectRoute) { + // Action hook before executing route actions + do_action('plubo/before_executing_route_actions', $this->matched_route, $this->matched_args); + $this->executeRouteActions(); + } + } + + /** + * Execute actions based on the type of matched route. + */ + private function executeRouteActions() + { + $permission_checker = new PermissionChecker($this->matched_route, $this->matched_args); + $permission_checker->checkPermissions(); + if ($this->matched_route instanceof Route) { $this->executeRouteHook(); } elseif ($this->matched_route instanceof ActionRoute) { @@ -195,105 +258,43 @@ public function doRouteActions() } } + /** + * Execute route hook action. + */ private function executeRouteHook() { - $this->checkPermissionCallback(); - $user = wp_get_current_user(); - if ($this->checkLoggedIn($user)) { - $this->checkRoles($user); - $this->checkCapabilities($user); - } - status_header(200); - do_action($this->matched_route->getAction(), $this->matched_args); - } - - private function checkPermissionCallback() - { - $permission_callback = $this->matched_route->getPermissionCallback(); - if (!$permission_callback || !is_callable($permission_callback)) { - return; - } - $has_access = call_user_func($permission_callback, $this->matched_args); - if (!$has_access) { - $this->forbidAccess(); - } - } - - private function checkLoggedIn($user) - { - $is_logged_in = $user->exists(); - if (!$this->matched_route->guestHasAccess() && !$is_logged_in - || !$this->matched_route->memberHasAccess() && $is_logged_in) { - $this->forbidAccess(); - } - return $is_logged_in; - } - - private function checkRoles($user) - { - $allowed_roles = $this->matched_route->getRoles(); - if ($this->matched_route->hasRolesCallback()) { - $allowed_roles = call_user_func($allowed_roles, $this->matched_args); - } - if ($allowed_roles !== false && !array_intersect((array)$user->roles, (array)$allowed_roles)) { - $this->forbidAccess(); - } - } - - private function checkCapabilities($user) - { - $allowed_caps = $this->getAllowedCapabilities(); - if($allowed_caps === false) { - return; - } - $is_allowed = false; - foreach ((array)$allowed_caps as $allowed_cap) { - if ($user->has_cap($allowed_cap)) { - $is_allowed = true; - break; - } - } - if (!$is_allowed) { - $this->forbidAccess(); - } - } - - private function getAllowedCapabilities() - { - $allowed_caps = $this->matched_route->getCapabilities(); - if ($this->matched_route->hasCapabilitiesCallback()) { - $allowed_caps = call_user_func($allowed_caps, $this->matched_args); - } - return $allowed_caps; - } - - private function forbidAccess() - { - if ($this->matched_route->hasRedirect()) { - wp_redirect($this->matched_route->getRedirect(), $this->matched_route->getStatus()); - exit; - } status_header($this->matched_route->getStatus()); - exit; + do_action($this->matched_route->getAction(), $this->matched_args); } + /** + * Execute route function action. + */ private function executeRouteFunction() { + status_header($this->matched_route->getStatus()); $action = $this->matched_route->getAction(); if ($this->matched_route->hasCallback()) { - $action = call_user_func($action, $this->matched_args); + $action = $action($this->matched_args); } } + /** + * Execute redirect action. + */ private function executeRedirect() { + if (!$this->matched_route instanceof RedirectRoute) { + exit; + } + $redirect_to = $this->matched_route->getAction(); if ($this->matched_route->hasCallback()) { - $redirect_to = call_user_func($redirect_to, $this->matched_args); + $redirect_to = $redirect_to($this->matched_args); } nocache_headers(); if ($this->matched_route->isExternal()) { - wp_redirect($redirect_to, $this->matched_route->getStatus()); + wp_redirect(esc_url_raw($redirect_to), $this->matched_route->getStatus()); exit; } wp_safe_redirect(home_url($redirect_to), $this->matched_route->getStatus()); @@ -312,48 +313,64 @@ public function includeRouteTemplate($template) if (!$this->matched_route instanceof Route) { return $template; } + $matched_template = $this->matched_route->getTemplate(); if ($this->matched_route->isRender()) { $template = $this->createTempFile(sys_get_temp_dir(), $this->matched_route->getName(), '.blade.php'); if ($this->matched_route->hasTemplateCallback()) { - $content = call_user_func($matched_template, $this->matched_args); - file_put_contents($template, $content); - } else { - file_put_contents($template, $matched_template); + $matched_template = $matched_template($this->matched_args); } + file_put_contents($template, $matched_template); return $template; } if ($this->matched_route->hasTemplateCallback()) { $template_func = $this->matched_route->getTemplate(); - $template = call_user_func($template_func, $this->matched_args); - } else { - $template = locate_template(apply_filters('plubo/template', $this->matched_route->getTemplate())); + return $template_func($this->matched_args); } - return $template; + + return $matched_template; } - private function createTempFile($dir, $prefix, $postfix) + /** + * Create a temporary file. + * + * @param string $dir + * @param string $prefix + * @param string $postfix + * + * @return string|false + */ + private function createTempFile(string $dir, string $prefix, string $postfix) { // Trim trailing slashes from $dir. $dir = rtrim($dir, DIRECTORY_SEPARATOR); + // If we don't have permission to create a directory, fail, otherwise we will be stuck in an endless loop. if (!is_dir($dir) || !is_writable($dir)) { return false; } + // Make sure characters in prefix and postfix are safe. if ((strpbrk($prefix, '\\/:*?"<>|') !== false) || (strpbrk($postfix, '\\/:*?"<>|') !== false)) { return false; } - $path = $dir.DIRECTORY_SEPARATOR.$prefix.$postfix; - $fp = @fopen($path, 'x+'); - if ($fp) { - fclose($fp); + + $path = $dir . DIRECTORY_SEPARATOR . $prefix . $postfix; + $tmp_file = @fopen($path, 'x+'); + + if ($tmp_file) { + fclose($tmp_file); } + return $path; } /** * Filter: If a route was found, add name as body tag. + * + * @param array $classes + * + * @return array */ public function addBodyClasses($classes) { @@ -369,18 +386,21 @@ public function addBodyClasses($classes) } /** - * Filter: If a route was found, add name as body tag. + * Filter: If a route was found, modify the title. + * + * @param array $title_parts + * + * @return array */ public function modifyTitle($title_parts) { if ($this->matched_route instanceof Route) { $route_title = $this->matched_route->getTitle(); - if (is_callable($route_title)) { - $title_parts['title'] = call_user_func($route_title, $this->matched_args); - return $title_parts; - } - $title_parts['title'] = $route_title ?? get_bloginfo( 'name' ); + $route_title = is_callable($route_title) ? $route_title($this->matched_args) : $route_title; + $title_parts['title'] = $route_title ?? get_bloginfo('name'); + $title_parts = apply_filters('plubo/title_parts', $title_parts, $route_title, $this->matched_args); } + return $title_parts; } } diff --git a/plubo-routes.php b/plubo-routes.php index e281061..5e6a8bb 100644 --- a/plubo-routes.php +++ b/plubo-routes.php @@ -1,8 +1,6 @@ true, - // 'logged_in' => false, - 'redirect' => 'https://sirvelia.com', - 'permission_callback' => [null, 'check_permission'], - // 'extra_vars' => [ - // 'lss_page' => 'test-page' - // ], - 'basic_auth' => [ - 'user' => 'testing' - ], - ) - ); - $routes[] = new PluboRoutes\Route\RedirectRoute( - 'city/{city:word}', - function ($matches) { - return 'https://www.google.com/search?q=' . $matches['city']; //SAGE 10 example - }, - array( - 'status' => 302, //Default 301 - 'external' => true, //Default false - ) - ); - $routes[] = new PluboRoutes\Route\ActionRoute( - 'sendEmail', - function () { - $to = get_option('admin_email'); - $subject = 'Hello world'; - $message = 'Wow!'; - $headers = ['Content-Type: text/html; charset=UTF-8']; - wp_mail($to, $subject, $message, $headers); - } - ); - - $routes[] = new PluboRoutes\Route\PageRoute('testing/page', 2); - $routes[] = new PluboRoutes\Route\PageRoute('patata', 5); - - return $routes; -}); - -add_filter('plubo/endpoints', function ($endpoints) { - $endpoints[] = new PluboRoutes\Endpoint\GetEndpoint( - 'plubo/v1', - 'client/{client_id:number}', - function ($request) { - $params = $request->get_params(); - return array('client', $params['client_id']); - } - ); - return $endpoints; -}); - -function check_permission($matches) -{ - return true; -}