/home/techb158/public_html/wp-content/plugins/kirki/libraries/framework/Routing
NameSizeModeActions
CurrentRoute.php22090644editdlrm
RouteParser.php50580644editdlrm
SiteRouter.php214520644editdlrm
Edit: /home/techb158/public_html/wp-content/plugins/kirki/libraries/framework/Routing/SiteRouter.php (21452B)
* * @since 1.0.0 */ protected $routes = []; /** * Registered dispatch hook name and priority combinations. * * @var array * * @since 1.0.0 */ protected $registered_hooks = []; /** * Whether boot hooks have been registered. * * @var bool * * @since 1.0.0 */ protected $hooked = \false; /** * Shared route parser. * * @var RouteParser * * @since 1.0.0 */ protected $parser; /** * Create a SiteRouter instance. * * @param string $namespace Unique short name for the plugin. * @param string $routing_method Routing method constant. * * @return void * * @since 1.0.0 */ public function __construct(string $namespace, string $routing_method = Route::ROUTING_REWRITE_RULES) { $safe = Sanitizer::apply_rule($namespace, Sanitizer::KEY); $this->namespace = $safe !== '' ? $safe : 'siteroute'; $this->routing_method = $routing_method === Route::ROUTING_PARSE_REQUEST ? Route::ROUTING_PARSE_REQUEST : Route::ROUTING_REWRITE_RULES; $this->parser = new RouteParser(); } /** * Boot the router with the given site routes. * * @param array $routes Site route instances. * * @return void * * @since 1.0.0 */ public function boot(array $routes) { foreach ($routes as $route) { if (!$route->is_site_route()) { continue; } $id = $this->route_id($route); $this->routes[$id] = $route; $this->ensure_hook_registered($route->get_hook_name(), $route->get_hook_priority()); } if ($this->hooked) { return; } $this->hooked = \true; if ($this->routing_method === Route::ROUTING_PARSE_REQUEST) { add_action('parse_request', [$this, 'intercept_parse_request']); return; } $this->register_rewrite_rules(); add_filter('query_vars', [$this, 'register_query_vars']); } /** * Flush rewrite rules after registering them. * * @return void * * @since 1.0.0 */ public function flush() { if ($this->routing_method !== Route::ROUTING_REWRITE_RULES) { return; } $this->register_rewrite_rules(); flush_rewrite_rules(); } /** * Build an absolute URL for a named site route. * * @param string $name Named route. * @param array $params Path param values. * * @return string * * @since 1.0.0 */ public function url(string $name, array $params = []) { $route = Route::find_named_route($name); if ($route === null || !$route->is_site_route()) { return ''; } if ($route->get_match_using() === Route::MATCH_PAGE) { $page_id = $this->resolve_page_id($route->get_endpoint()); return $page_id ? (string) get_permalink($page_id) : ''; } $segments = $route->get_segments(); $pieces = []; foreach ($segments as $segment) { if ($segment['type'] === 'literal') { $pieces[] = $segment['value']; continue; } $value = $params[$segment['name']] ?? '{' . $segment['name'] . '}'; $pieces[] = \rawurlencode((string) $value); } return esc_url(home_url('/' . \implode('/', $pieces))); } /** * Register rewrite rules for path-matched routes. * * @return void * * @since 1.0.0 */ public function register_rewrite_rules() { foreach ($this->routes as $id => $route) { if ($route->get_match_using() === Route::MATCH_PAGE) { continue; } [$pattern, $param_names] = $this->parser->build_site_pattern($route->get_segments(), $route->get_param_types()); $query = 'index.php?' . $this->namespace . '_route=' . $id; foreach ($param_names as $index => $name) { $query .= '&' . $this->query_var_name($name) . '=$matches[' . ($index + 1) . ']'; } add_rewrite_rule($pattern, $query, 'top'); } } /** * Whitelist query vars for rewrite-based routing. * * @param array $vars Existing WordPress query vars. * * @return array * * @since 1.0.0 */ public function register_query_vars(array $vars) { $vars[] = $this->namespace . '_route'; foreach ($this->routes as $route) { if ($route->get_match_using() === Route::MATCH_PAGE) { continue; } foreach ($route->get_segments() as $segment) { if ($segment['type'] === 'param') { $vars[] = $this->query_var_name($segment['name']); } } } return $vars; } /** * Match path routes during parse_request and replace query vars on hit. * * @param \WP $wp Main WordPress request object. * * @return void * * @since 1.0.0 */ public function intercept_parse_request($wp) { $path = isset($wp->request) && $wp->request !== '' ? \trim((string) $wp->request, '/') : $this->resolve_request_path(); foreach ($this->routes as $id => $route) { if ($route->get_match_using() === Route::MATCH_PAGE) { continue; } [$pattern, $param_names] = $this->parser->build_site_pattern($route->get_segments(), $route->get_param_types()); if (!\preg_match('#' . $pattern . '#', $path, $matches)) { continue; } $query_vars = [$this->namespace . '_route' => $id]; foreach ($param_names as $index => $name) { $query_vars[$this->query_var_name($name)] = $matches[$index + 1] ?? ''; } $wp->query_vars = $query_vars; add_filter('redirect_canonical', '__return_false'); return; } } /** * Dispatch matched routes registered on the template_redirect hook. * * @param int $priority Hook priority being handled. * * @return void * * @since 1.0.0 */ public function handle_template_redirect(int $priority) { $match = $this->match_current_request(HookNames::TEMPLATE_REDIRECT, $priority); if ($match === null) { return; } $this->dispatch_match($match, \true); } /** * Dispatch matched routes registered on the template_include filter. * * @param string $template Default template path from WordPress. * @param int $priority Hook priority being handled. * * @return string * * @since 1.0.0 */ public function handle_template_include(string $template, int $priority) { $match = $this->match_current_request(HookNames::TEMPLATE_INCLUDE, $priority); if ($match === null) { return $template; } $route = $match['route']; $params = $match['params']; if ($route->get_redirect() !== null) { $this->send_route_redirect($route, $params); } if ($route->get_template() !== null && $route->get_action() === null) { $file = $this->locate_route_template($route); if ($file === '') { SiteExceptionHandler::handle(new Exception('Template not found: ' . $route->get_template(), 500)); } return $file; } try { $result = $this->run_route($route, $params); if ($result instanceof View) { $path = $result->get_template(); $engine = app(TemplateEngine::class); $resolved = $engine->resolve_path($path); if ($resolved !== '') { app(ViewContext::class)->prepare($result, (string) $route->get_name(), $resolved); if ($result->uses_layout()) { return $engine->layout_wrapper_path(); } return $resolved; } } if (\is_string($result) && $result !== '' && \file_exists($result)) { return $result; } SiteExceptionHandler::handle(new Exception('Site route template_include callback must return a valid template file path.', 500)); } catch (Exception $exception) { SiteExceptionHandler::handle($exception); } return $template; } /** * Ensure a WordPress action or filter is registered for a hook and priority. * * @param string $hook_name Dispatch hook name. * @param int $priority Hook priority. * * @return void * * @since 1.0.0 */ protected function ensure_hook_registered(string $hook_name, int $priority) { $key = $hook_name . ':' . $priority; if (isset($this->registered_hooks[$key])) { return; } $this->registered_hooks[$key] = \true; if ($hook_name === HookNames::TEMPLATE_INCLUDE) { add_filter(HookNames::TEMPLATE_INCLUDE, function ($template) use($priority) { return $this->handle_template_include($template, $priority); }, $priority); return; } add_action(HookNames::TEMPLATE_REDIRECT, function () use($priority) { $this->handle_template_redirect($priority); }, $priority); } /** * Find and authorize the route due to dispatch on the given hook and priority. * * @param string $expected_hook_name Expected dispatch hook name. * @param int $expected_priority Expected dispatch priority. * * @return array{route:Route,params:array}|null * * @since 1.0.0 */ protected function match_current_request(string $expected_hook_name, int $expected_priority) { $route = $this->find_matching_path_route($expected_hook_name, $expected_priority); if ($route === null) { $route = $this->find_matching_page_route($expected_hook_name, $expected_priority); } if ($route === null) { return null; } // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase $method = \strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); if (\strtoupper($route->get_method()) !== $method) { SiteExceptionHandler::handle(new Exception('Method Not Allowed', 405)); } $params = $this->collect_route_params($route); foreach ($route->get_param_validators() as $name => $validator) { if (!\call_user_func($validator, $params[$name] ?? null)) { SiteExceptionHandler::handle(new Exception('Not Found', 404)); } } $params = \array_merge($route->get_with_data(), $params); if ($route->get_match_using() === Route::MATCH_PAGE) { $params['page_id'] = $this->resolve_page_id($route->get_endpoint()); $params['current_page_id'] = get_queried_object_id(); } CurrentRoute::set($route->get_name(), $params); return ['route' => $route, 'params' => $params]; } /** * Dispatch a matched route on template_redirect. * * @param array{route:Route,params:array} $match Matched route data. * @param bool $exit Whether to exit after sending the response. * * @return void * * @since 1.0.0 */ protected function dispatch_match(array $match, bool $exit = \true) { $route = $match['route']; $params = $match['params']; status_header(200); nocache_headers(); if ($route->get_redirect() !== null && $route->get_action() === null) { $this->flush_queued_cookies(); $this->send_route_redirect($route, $params); } if ($route->get_template() !== null && $route->get_action() === null) { $file = $this->locate_route_template($route); if ($file === '') { SiteExceptionHandler::handle(new Exception('Template not found: ' . $route->get_template(), 500)); } $this->flush_queued_cookies(); \extract($params, \EXTR_SKIP); include $file; if ($exit) { exit; } return; } try { $result = $this->run_route($route, $params); $this->send_response($result); } catch (Exception $exception) { SiteExceptionHandler::handle($exception); } if ($exit) { exit; } } /** * Run authorize, middleware, validate, and controller DI for a site route. * * @param Route $route The matched route. * @param array $params Sanitized route params. * * @return mixed * * @since 1.0.0 */ protected function run_route(Route $route, array $params) { return $route->dispatch_site($params); } /** * Emit any queued cookies before response output begins. * * @return void * * @since 1.0.0 */ protected function flush_queued_cookies() { app(CookieManager::class)->flush_queued_cookies(); } /** * Send a controller/closure return value to the browser. * * @param mixed $result The route return value. * * @return void * * @since 1.0.0 */ protected function send_response($result) { $this->flush_queued_cookies(); if ($result instanceof RedirectResponse) { $result->send(); } if ($result instanceof JsonResponse) { status_header($result->get_status()); \header('Content-Type: application/json; charset=utf-8'); foreach ($result->get_headers() as $name => $values) { $value = \is_array($values) ? \implode(', ', $values) : $values; \header($name . ': ' . $value); } echo wp_json_encode($result->get_data()); return; } if ($result instanceof View) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Assembled view HTML; dynamic data is escaped in templates via esc_*. echo $result->render(); return; } if (\is_string($result)) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Controller string returns must be pre-escaped safe HTML. echo $result; } } /** * Collect and sanitize route params from query vars. * * @param Route $route The matched route. * * @return array * * @since 1.0.0 */ protected function collect_route_params(Route $route) { $params = []; foreach ($route->get_segments() as $segment) { if ($segment['type'] !== 'param') { continue; } $name = $segment['name']; $raw = get_query_var($this->query_var_name($name)); $raw = \is_string($raw) ? wp_unslash($raw) : $raw; $type = $route->get_param_types()[$name] ?? 'any'; $sanitizer = $this->parser->resolve_sanitizer($type); $params[$name] = Sanitizer::apply_rule($raw, $sanitizer); } return $params; } /** * Find a path-matched route for the current request and hook context. * * @param string $expected_hook_name Expected dispatch hook name. * @param int $expected_priority Expected dispatch priority. * * @return Route|null * * @since 1.0.0 */ protected function find_matching_path_route(string $expected_hook_name, int $expected_priority) { $id = get_query_var($this->namespace . '_route'); if (empty($id) || !isset($this->routes[$id])) { return null; } $route = $this->routes[$id]; if ($route->get_match_using() !== Route::MATCH_PATH) { return null; } if ($route->get_hook_name() !== $expected_hook_name || $route->get_hook_priority() !== $expected_priority) { return null; } return $route; } /** * Find a page-matched route for the current request and hook context. * * @param string $expected_hook_name Expected dispatch hook name. * @param int $expected_priority Expected dispatch priority. * * @return Route|null * * @since 1.0.0 */ protected function find_matching_page_route(string $expected_hook_name, int $expected_priority) { if (is_admin() || !is_page()) { return null; } $current_page_id = get_queried_object_id(); foreach ($this->routes as $route) { if ($route->get_match_using() !== Route::MATCH_PAGE) { continue; } if ($route->get_hook_name() !== $expected_hook_name || $route->get_hook_priority() !== $expected_priority) { continue; } if ($this->resolve_page_id($route->get_endpoint()) === $current_page_id) { return $route; } } return null; } /** * Resolve a page ID or slug to a page ID. * * @param mixed $page_ref Page ID or slug. * * @return int * * @since 1.0.0 */ protected function resolve_page_id($page_ref) { if (\is_numeric($page_ref)) { return (int) $page_ref; } $page = get_page_by_path((string) $page_ref); return $page ? (int) $page->ID : 0; } /** * Locate a route-level template file. * * @param Route $route The route. * * @return string * * @since 1.0.0 */ protected function locate_route_template(Route $route) { $path = $route->get_template(); $located = locate_template($path); $file = $located !== '' ? $located : $path; return \file_exists($file) ? $file : ''; } /** * Send a route-level redirect. * * @param Route $route The route. * @param array $params Route params. * * @return void * * @since 1.0.0 */ protected function send_route_redirect(Route $route, array $params) { $redirect = $route->get_redirect(); $url = \preg_replace_callback('/\\{(\\w+)\\}/', function ($matches) use($params) { return isset($params[$matches[1]]) ? \rawurlencode((string) $params[$matches[1]]) : $matches[0]; }, $redirect['url']); wp_safe_redirect($url, $redirect['status']); exit; } /** * Resolve the current request path relative to the site home path. * * @return string * * @since 1.0.0 */ protected function resolve_request_path() { // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; $path = (string) \parse_url($request_uri, \PHP_URL_PATH); $home_path = (string) \parse_url(home_url(), \PHP_URL_PATH); if ($home_path !== '' && $home_path !== '/' && \strpos($path, $home_path) === 0) { $path = \substr($path, \strlen($home_path)); } return \trim($path, '/'); } /** * Build a stable internal route id. * * @param Route $route The route. * * @return string * * @since 1.0.0 */ protected function route_id(Route $route) { return $this->namespace . '_' . \substr(\md5(\strtoupper($route->get_method()) . '|' . $route->get_endpoint()), 0, 12); } /** * Build the namespaced query var name for a route param. * * @param string $name Param name. * * @return string * * @since 1.0.0 */ protected function query_var_name(string $name) { return $this->namespace . '_p_' . $name; } }