Permalink
Browse files

feature(routing): allow more reliable URL path rewriting

Adds an early called `route:rewrite` hook expressly for URL rewriting.
Changes there update the request object and affect the default context,
and functions like current_page_url().

Removes legacy magic quotes-related code.

Fixes #9388
  • Loading branch information...
mrclay committed Feb 15, 2016
1 parent f67a67b commit 853fc0ef65356b72ad19d62331ac6e539ca02b4f
@@ -18,7 +18,6 @@ System hooks
* headers
* params
-
**page_owner, system**
Filter the page_owner for the current page. No options are passed.
@@ -296,8 +295,11 @@ Routing
=======
**route, <identifier>**
- Allows altering the parameters used to route requests. ``identifier`` is the first URL segment,
- registered with ``elgg_register_page_handler()``.
+ Allows applying logic or returning a response before the page handler is called. See :doc:`routing`
+ for details.
+
+**route:rewrite, <identifier>**
+ Allows altering the site-relative URL path. See :doc:`routing` for details.
**ajax_response, path:<path>**
Filters ajax responses before they're sent back to the ``elgg/Ajax`` module. This hook type will
View
@@ -56,25 +56,12 @@ the request to a resource view.
The ``route`` Plugin Hook
=========================
-The ``route`` plugin hook is triggered earlier, before page handlers are called. The URL
-identifier is given as the type of the hook. This hook can be used to modify the identifier
-or segments, to take over page rendering completely, or just to add some logic before the
-request is handled elsewhere.
+The ``route`` plugin hook is triggered before page handlers are called. The URL
+identifier is given as the type of the hook. This hook can be used to add some logic before the
+request is handled elsewhere, or take over page rendering completely.
-Generally devs should use a page handler unless they need to affect a single page or a wider variety of URLs.
-
-The following code intercepts requests to the page handler for ``customblog`` and internally redirects them
-to the ``blog`` page handler.
-
-.. code:: php
-
- function myplugin_customblog_route_handler($hook, $type, $returnvalue, $params) {
- // direct Elgg to use the page handler for 'blog'
- $returnvalue['identifier'] = 'blog';
- return $returnvalue;
- }
-
- elgg_register_plugin_hook_handler('route', 'customblog', 'myplugin_customblog_route_handler');
+Generally devs should instead use a page handler unless they need to affect a single page or a wider
+variety of URLs.
The following code results in ``/blog/all`` requests being completely handled by the plugin hook handler.
For these requests the ``blog`` page handler is never called.
@@ -99,16 +86,40 @@ For these requests the ``blog`` page handler is never called.
elgg_register_plugin_hook_handler('route', 'blog', 'myplugin_blog_all_handler');
+.. note:: As of 2.1, route modification should be done in the ``route:rewrite`` hook.
+
+The ``route:rewrite`` Plugin Hook
+=================================
+
+For URL rewriting, the ``route:rewrite`` hook (with similar arguments as ``route``) is triggered very early,
+and allows modifying the request URL path (relative to the Elgg site).
+
+Here we rewrite requests for ``news/*`` to ``blog/*``:
+
+.. code:: php
+
+ function myplugin_rewrite_handler($hook, $type, $value, $params) {
+ $value['identifier'] = 'blog';
+ return $value;
+ }
+
+ elgg_register_plugin_hook_handler('route:rewrite', 'news', 'myplugin_rewrite_handler');
+
+.. warning::
+
+ The hook must be registered directly in your plugin ``start.php`` (the ``[init, system]`` event
+ is too late).
Routing overview
================
For regular pages, Elgg's program flow is something like this:
-#. A user requests ``http://example.com/blog/owner/jane``.
+#. A user requests ``http://example.com/news/owner/jane``.
#. Plugins are initialized.
-#. Elgg parses the URL to identifier ``blog`` and segments ``['owner', 'jane']``.
-#. Elgg triggers the plugin hook ``route, blog`` (see above).
+#. Elgg parses the URL to identifier ``news`` and segments ``['owner', 'jane']``.
+#. Elgg triggers the plugin hook ``route:rewrite, news`` (see above).
+#. Elgg triggers the plugin hook ``route, blog`` (was rewritten in the rewrite hook).
#. Elgg finds a registered page handler (see above) for ``blog``, and calls the function, passing in
the segments.
#. The page handler function determines it needs to render a single user's blog. It calls
@@ -297,6 +297,8 @@ public function bootCore() {
elgg_set_viewtype('default');
}
+ $this->allowPathRewrite();
+
// @todo deprecate as plugins can use 'init', 'system' event
$events->trigger('plugins_boot', 'system');
@@ -577,4 +579,20 @@ private function setupPath() {
return $_GET[self::GET_PATH_KEY];
}
+
+ /**
+ * Allow plugins to rewrite the path.
+ *
+ * @return void
+ */
+ private function allowPathRewrite() {
+ $request = $this->services->request;
+ $new = $this->services->router->allowRewrite($request);
+ if ($new === $request) {
+ return;
+ }
+
+ $this->services->setValue('request', $new);
+ _elgg_set_initial_context($new);
+ }
}
@@ -2,77 +2,19 @@
namespace Elgg\Http;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
-use Symfony\Component\HttpFoundation\ParameterBag;
-use Symfony\Component\HttpFoundation\FileBag;
-use Symfony\Component\HttpFoundation\ServerBag;
-use Symfony\Component\HttpFoundation\HeaderBag;
use Elgg\Application;
/**
- * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
+ * Elgg HTTP request.
*
- * Represents an HTTP request.
- *
- * Some methods were pulled from Symfony. They are
- * Copyright (c) 2004-2013 Fabien Potencier
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is furnished
- * to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @package Elgg.Core
- * @subpackage Http
- * @since 1.9.0
* @access private
*/
class Request extends SymfonyRequest {
/**
- * {@inheritDoc}
- */
- public function initialize(array $query = array(), array $request = array(), array $attributes = array(),
- array $cookies = array(), array $files = array(), array $server = array(), $content = null) {
- $this->request = new ParameterBag($this->stripSlashesIfMagicQuotes($request));
- $this->query = new ParameterBag($this->stripSlashesIfMagicQuotes($query));
- $this->attributes = new ParameterBag($attributes);
- $this->cookies = new ParameterBag($this->stripSlashesIfMagicQuotes($cookies));
- $this->files = new FileBag($files);
- $this->server = new ServerBag($server);
- $this->headers = new HeaderBag($this->server->getHeaders());
-
- $this->content = $content;
- $this->languages = null;
- $this->charsets = null;
- $this->encodings = null;
- $this->acceptableContentTypes = null;
- $this->pathInfo = null;
- $this->requestUri = null;
- $this->baseUrl = null;
- $this->basePath = null;
- $this->method = null;
- $this->format = null;
- }
-
- /**
- * Get URL segments from the path info
+ * Get the Elgg URL segments
*
- * @see \Elgg\Http\Request::getPathInfo()
- *
- * @return array
+ * @return string[]
*/
public function getUrlSegments() {
$path = trim($this->query->get(Application::GET_PATH_KEY), '/');
@@ -84,7 +26,20 @@ public function getUrlSegments() {
}
/**
- * Get first URL segment from the path info
+ * Get a cloned request with new Elgg URL segments
+ *
+ * @param string[] $segments URL segments
+ *
+ * @return Request
+ */
+ public function setUrlSegments(array $segments) {
+ $query = $this->query->all();
+ $query[Application::GET_PATH_KEY] = '/' . implode('/', $segments);
+ return $this->duplicate($query);
+ }
+
+ /**
+ * Get first Elgg URL segment
*
* @see \Elgg\Http\Request::getUrlSegments()
*
@@ -115,18 +70,4 @@ public function getClientIp() {
return $ip;
}
-
- /**
- * Strip slashes if magic quotes is on
- *
- * @param mixed $data Data to strip slashes from
- * @return mixed
- */
- protected function stripSlashesIfMagicQuotes($data) {
- if (get_magic_quotes_gpc()) {
- return _elgg_stripslashes_deep($data);
- } else {
- return $data;
- }
- }
}
@@ -1,6 +1,8 @@
<?php
namespace Elgg;
+use Elgg\Http\Request;
+
/**
* Delegates requests to controllers based on the registered configuration.
*
@@ -58,7 +60,7 @@ public function route(\Elgg\Http\Request $request) {
// return false to stop processing the request (because you handled it)
// return a new $result array if you want to route the request differently
- $result = array(
+ $old = array(
'identifier' => $identifier,
'handler' => $identifier, // backward compatibility
'segments' => $segments,
@@ -68,11 +70,15 @@ public function route(\Elgg\Http\Request $request) {
$this->timer->begin(['build page']);
}
- $result = $this->hooks->trigger('route', $identifier, $result, $result);
+ $result = $this->hooks->trigger('route', $identifier, $old, $old);
if ($result === false) {
return true;
}
+ if ($result !== $old) {
+ _elgg_services()->logger->warn('Use the route:rewrite hook to modify routes.');
+ }
+
if ($identifier != $result['identifier']) {
$identifier = $result['identifier'];
} else if ($identifier != $result['handler']) {
@@ -139,5 +145,44 @@ public function unregisterPageHandler($identifier) {
public function getPageHandlers() {
return $this->handlers;
}
+
+ /**
+ * Filter a request through the route:rewrite hook
+ *
+ * @param Request $request Elgg request
+ *
+ * @return Request
+ * @access private
+ */
+ public function allowRewrite(Request $request) {
+ $segments = $request->getUrlSegments();
+ if ($segments) {
+ $identifier = array_shift($segments);
+ } else {
+ $identifier = '';
+ }
+
+ $old = array(
+ 'identifier' => $identifier,
+ 'segments' => $segments,
+ );
+ $new = _elgg_services()->hooks->trigger('route:rewrite', $identifier, $old, $old);
+ if ($new === $old) {
+ return $request;
+ }
+
+ if (!isset($new['identifier'])
+ || !isset($new['segments'])
+ || !is_string($new['identifier'])
+ || !is_array($new['segments'])
+ ) {
+ throw new \RuntimeException('rewrite_path handler returned invalid route data.');
+ }
+
+ // rewrite request
+ $segments = $new['segments'];
+ array_unshift($segments, $new['identifier']);
+ return $request->setUrlSegments($segments);
+ }
}
Oops, something went wrong.

0 comments on commit 853fc0e

Please sign in to comment.