/** * Returns the URL of a view. * * @param Site $site * @param string $view_id The identifier of the view. * @param array|null $args The arguments to format the URL, if the URL uses a pattern. * * @return string * @throws \Exception */ public static function resolve_view_url(Site $site, $view_id, $args = null) { if (isset(self::$view_url_cache[$view_id])) { return self::$view_url_cache[$view_id]; } $target = $site->resolve_view_target($view_id); if (!$target) { return '#unknown-target-for-view-' . $view_id; } $url_pattern = $target->url_pattern; if (!Pattern::is_pattern($url_pattern)) { return self::$view_url_cache[$view_id] = $target->url; } return Pattern::from($url_pattern)->format($args); }
/** * Search for a route matching the specified pathname and method. * * @param string $uri The URI to match. If the URI includes a query string it is removed * before searching for a matching route. * @param array|null $captured The parameters captured from the URI. If the URI included a * query string, its parsed params are stored under the `__query__` key. * @param string $method One of HTTP\Request::METHOD_* methods. * * @return Route|false|null */ public function find($uri, &$captured = null, $method = Request::METHOD_ANY) { $captured = []; $parsed = (array) parse_url($uri) + ['path' => null, 'query' => null]; $path = $parsed['path']; if (!$path) { return false; } # # Determine if a route matches prerequisites. # $matchable = function ($via) use($method) { if ($method != Request::METHOD_ANY) { if (is_array($via)) { if (!in_array($method, $via)) { return false; } } else { if ($via !== Request::METHOD_ANY && $via !== $method) { return false; } } } return true; }; # # Search for a matching static route. # $map_static = function ($definitions) use($path, &$matchable) { foreach ($definitions as $id => $definition) { $pattern = $definition[RouteDefinition::PATTERN]; $via = $definition[RouteDefinition::VIA]; if (!$matchable($via) || $pattern != $path) { continue; } return $id; } return null; }; # # Search for a matching dynamic route. # $map_dynamic = function ($definitions) use($path, &$matchable, &$captured) { foreach ($definitions as $id => $definition) { $pattern = $definition[RouteDefinition::PATTERN]; $via = $definition[RouteDefinition::VIA]; if (!$matchable($via) || !Pattern::from($pattern)->match($path, $captured)) { continue; } return $id; } return null; }; list($static, $dynamic) = $this->sort_routes(); $id = null; if ($static) { $id = $map_static($static); } if (!$id && $dynamic) { $id = $map_dynamic($dynamic); } if (!$id) { return null; } $query = $parsed['query']; if ($query) { parse_str($query, $parsed_query_string); $captured['__query__'] = $parsed_query_string; } return $this[$id]; }
/** * Finds a page using its path. * * @param string $path * * @return Page */ public function find_by_path($path) { global $core; $pos = strrpos($path, '.'); $extension = null; if ($pos && $pos > strrpos($path, '/')) { $extension = substr($path, $pos); $path = substr($path, 0, $pos); } $l = strlen($path); if ($l && $path[$l - 1] == '/') { $path = substr($path, 0, -1); } # # matching site # $site = $core->site; $siteid = $site->siteid; $site_path = $site->path; if ($site_path) { if (strpos($path, $site_path) !== 0) { return; } $path = substr($path, strlen($site_path)); } if (!$path) { # # The home page is requested, we load the first parentless online page of the site. # $page = $this->find_home($siteid); if (!$page) { return; } if (!$this->retrieve($page->nid)) { $this->store($page); } return $page; } $parts = explode('/', $path); array_shift($parts); $parts_n = count($parts); $vars = array(); # # We load from all the pages just what we need to find a matching path, and create a tree # with it. # $tries = $this->select('nid, parentid, slug, pattern')->filter_by_siteid($siteid)->ordered->all(\PDO::FETCH_OBJ); $tries = self::nestNodes($tries); $try = null; $pages_by_ids = array(); for ($i = 0; $i < $parts_n; $i++) { if ($try) { $tries = $try->children; } $part = $path_part = $parts[$i]; # # first we search for a matching slug # foreach ($tries as $try) { $pattern = $try->pattern; if ($pattern) { $stripped = preg_replace('#<[^>]+>#', '', $pattern); $nparts = substr_count($stripped, '/') + 1; $path_part = implode('/', array_slice($parts, $i, $nparts)); $pattern = Pattern::from($pattern); if (!$pattern->match($path_part, $path_captured)) { $try = null; continue; } # # found matching pattern ! # we skip parts ate by the pattern # $i += $nparts - 1; # # even if the pattern matched, $match is not guaranteed to be an array, # 'feed.xml' is a valid pattern. // FIXME-20110327: is it still ? # if (is_array($path_captured)) { $vars = $path_captured + $vars; } break; } else { if ($part == $try->slug) { break; } } $try = null; } # # If `try` is null at this point it's that the path could not be matched. # if (!$try) { return; } # # otherwise, we continue # $pages_by_ids[$try->nid] = array('url_part' => $path_part, 'url_variables' => $vars); } # # append the extension (if any) to the last page of the branch # $pages_by_ids[$try->nid]['url_part'] .= $extension; # # All page objects have been loaded, we need to set up some additionnal properties, link # each page to its parent and propagate the online status. # $parent = null; $pages = $this->find(array_keys($pages_by_ids)); foreach ($pages as $page) { $page->url_part = $pages_by_ids[$page->nid]['url_part']; $page->url_variables = $pages_by_ids[$page->nid]['url_variables']; if ($parent) { // $page->parent = $parent; if (!$parent->is_online) { $page->is_online = false; } } $parent = $page; } return $page; }
/** * Initializes the {@link $pattern} property and the properties provided. * * @param string $pattern * @param array $properties */ public function __construct($pattern, array $properties) { $this->pattern = Pattern::from($pattern); unset($properties['pattern']); $this->assert_properties_are_valid($properties, self::$invalid_construct_properties); foreach ($properties as $property => $value) { $this->{$property} = $value; } }
/** * Returns the URL of the page. * * @return string */ protected function get_url() { global $core; if ($this->location) { return $this->location->url; } $url_pattern = $this->url_pattern; if ($this->is_home) { return $url_pattern; } $url = null; if (Pattern::is_pattern($url_pattern)) { if ($this->url_variables) { $url = Pattern::from($url_pattern)->format($this->url_variables); // \ICanBoogie\log('URL %pattern rescued using URL variables', array('%pattern' => $pattern)); } else { $page = isset($core->request->context->page) ? $core->request->context->page : null; if ($page && $page->url_variables) { $url = Pattern::from($url_pattern)->format($page->url_variables); // \ICanBoogie\log("URL pattern %pattern was resolved using current page's variables", array('%pattern' => $pattern)); } else { $url = '#url-pattern-could-not-be-resolved'; } } } else { $url = $url_pattern; } return $url; }
/** * Resolves a request into a page. * * @param Request $request * * @return Page|Response */ protected function resolve_page(Request $request) { global $core; /* TODO-20130812: Move the following code section in the Sites module. */ $site = $request->context->site; if (!$site->siteid) { throw new NotFound('Unable to find matching website.'); } $status = $site->status; switch ($status) { case Site::STATUS_UNAUTHORIZED: throw new AuthenticationRequired(); case Site::STATUS_NOT_FOUND: throw new NotFound(\ICanBoogie\format("The requested URL does not exists: %uri", ['uri' => $request->uri])); case Site::STATUS_UNAVAILABLE: throw new ServiceUnavailable(); } /* /TODO */ $path = $request->path; $page = $core->models['pages']->find_by_path($request->path); if (!$page) { return; } if ($page->location) { return new RedirectResponse($page->location->url, 301, ['Icybee-Redirected-By' => __FILE__ . '::' . __LINE__]); } # # We make sure that a normalized URL is used. For instance, "/fr" is redirected to # "/fr/". # $url_pattern = Pattern::from($page->url_pattern); if (!$url_pattern->params && $page->url != $path) { $query_string = $request->query_string; return new RedirectResponse($page->url . ($query_string ? '?' . $query_string : ''), 301, ['Icybee-Redirected-By' => __FILE__ . '::' . __LINE__]); } if (!$page->is_online || $page->site->status != Site::STATUS_OK) { # # Offline pages are displayed if the user has ownership, otherwise an HTTP exception # with code 401 (Authentication) is thrown. We add the "✎" marker to the title of the # page to indicate that the page is offline but displayed as a preview for the user. # if (!$core->user->has_ownership('pages', $page)) { throw new AuthenticationRequired(\ICanBoogie\format('The requested URL %url requires authentication.', ['url' => $path])); } $page->title .= ' ✎'; } if (isset($page->url_variables)) { $request->path_params = array_merge($request->path_params, $page->url_variables); $request->params = array_merge($request->params, $page->url_variables); } return $page; }