浏览代码

Merge pull request #245 from dereuromark/master-redirect

Add RefererRedirect component.
Mark Sch 6 年之前
父节点
当前提交
9b42d6d717

+ 54 - 0
docs/Component/RefererRedirect.md

@@ -0,0 +1,54 @@
+# RefererRedirect component
+
+## Why this component
+(Referer) Redirecting is a often wrongly/badly implemented pattern.
+Especially the session is here usually the worst possible usability approach.
+As soon as you have two tabs open they will basically kill each other, creating very weird experiences for the user.
+
+This component uses a referer key in query string to redirect back to given referer.
+The neat thing here is that it doesn't require changes to existing actions. This can just be
+added on top, for one or all controllers.
+
+## Alternatives
+You can also pass along all query strings always, but then you need to make sure all URLs in controllers and templates are adjusted here.
+
+## Setting it up for a controller
+Let's set it up. Inside your controller: 
+```php
+    /**
+     * @return void
+     */
+    public function initialize()
+    {
+        parent::initialize();
+        $this->loadComponent('RefererRedirect', [
+            'actions' => ['edit'],
+        ]);
+    }
+```
+We whitelisted the edit action to be auto-referer redirectable.
+
+## Adjust your links to this action
+
+From your paginated and filtered index page you can now point to the edit page like this:
+
+```php
+<?php echo $this->Html->link(
+    $this->Format->icon('edit'), 
+    ['controller' => 'Versions', 'action' => 'edit', $version->id, '?' => ['ref' => $this->getRequest()->getRequestTarget()]], 
+    ['escape' => false]
+); ?>
+```
+
+After successful save it will then redirect to exactly the current URL including all filter query strings, page, sort order etc.
+
+
+## When not to use
+Make sure you are not using such approaches when linking to an action that removes an entity from the view of that entity.
+So do not use it for deleting actions while you are on the view action of this entity, for example.
+
+## Security
+It is advised to whitelist the actions for valid referer redirect.
+
+Also make sure the referer is always a local one (not pointing to external URLs).  
+The component here itself already checks for this.

+ 68 - 0
src/Controller/Component/RefererRedirectComponent.php

@@ -0,0 +1,68 @@
+<?php
+namespace Tools\Controller\Component;
+
+use Cake\Controller\Component;
+use Cake\Event\Event;
+use Cake\Http\Response;
+
+/**
+ * Uses a referer key in query string to redirect to given referer.
+ * Useful for passing to edit forms if you want a different target as redirect than the default.
+ * The neat thing here is that it doesn't require changes to existing actions. This can just be
+ * added on top, for one or all controllers.
+ */
+class RefererRedirectComponent extends Component {
+
+	const QUERY_REFERER = 'ref';
+
+	/**
+	 * @var array
+	 */
+	protected $_defaultConfig = [
+		'actions' => [],
+	];
+
+	/**
+	 * @param \Cake\Event\Event $event
+	 * @param string|array $url A string or array containing the redirect location
+	 * @param \Cake\Http\Response $response The response object.
+	 *
+	 * @return \Cake\Http\Response|null
+	 */
+	public function beforeRedirect(Event $event, $url, Response $response) {
+		$actions = $this->getConfig('actions');
+		$currentAction = $this->getController()->getRequest()->getParam('action');
+
+		if ($currentAction && $actions && !in_array($currentAction, $actions, true)) {
+			return null;
+		}
+
+		$referer = $this->referer();
+		if (!$referer) {
+			return null;
+		}
+
+		return $response->withLocation($referer);
+	}
+
+	/**
+	 * Only accept relative URLs.
+	 *
+	 * @see \Cake\Http\ServerRequest::referer()
+	 *
+	 * @return string|null
+	 */
+	protected function referer() {
+		$referer = $this->getController()->getRequest()->getQuery(static::QUERY_REFERER);
+		if (!$referer) {
+			return null;
+		}
+
+		if (strpos($referer, '/') !== 0) {
+			return null;
+		}
+
+		return $referer;
+	}
+
+}

+ 64 - 0
tests/TestCase/Controller/Component/RefererRedirectComponentTest.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Tools\Test\TestCase\Controller\Component;
+
+use App\Controller\RefererRedirectComponentTestController;
+use Cake\Controller\ComponentRegistry;
+use Cake\Core\Configure;
+use Cake\Event\Event;
+use Cake\Http\Response;
+use Cake\Http\ServerRequest;
+use Tools\Controller\Component\RefererRedirectComponent;
+use Tools\TestSuite\TestCase;
+
+class RefererRedirectComponentTest extends TestCase {
+
+	/**
+	 * @var \Cake\Event\Event
+	 */
+	public $event;
+
+	/**
+	 * @var \App\Controller\RefererRedirectComponentTestController
+	 */
+	public $Controller;
+
+	/**
+	 * @return void
+	 */
+	public function setUp() {
+		parent::setUp();
+
+		$serverRequest = new ServerRequest();
+		$serverRequest = $serverRequest->withQueryParams(['ref' => '/somewhere-else']);
+
+		$this->event = new Event('Controller.beforeFilter');
+		$this->Controller = new RefererRedirectComponentTestController($serverRequest);
+
+		Configure::write('App.fullBaseUrl', 'http://localhost');
+	}
+
+	/**
+	 * @return void
+	 */
+	public function tearDown() {
+		parent::tearDown();
+
+		unset($this->Controller);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testBeforeRedirect() {
+		$response = new Response();
+
+		$componentRegistry = new ComponentRegistry($this->Controller);
+		$refererRedirectComponent = new RefererRedirectComponent($componentRegistry);
+
+		$modifiedResponse = $refererRedirectComponent->beforeRedirect($this->event, ['action' => 'foo'], $response);
+
+		$this->assertSame(['/somewhere-else'], $modifiedResponse->getHeader('Location'));
+	}
+
+}

+ 3 - 0
tests/TestCase/Model/Behavior/TypographicBehaviorTest.php

@@ -29,6 +29,9 @@ class TypographicBehaviorTest extends TestCase {
 		$this->Model->addBehavior('Tools.Typographic', ['fields' => ['body'], 'before' => 'marshal']);
 	}
 
+	/**
+	 * @return void
+	 */
 	public function testObject() {
 		$this->assertInstanceOf(TypographicBehavior::class, $this->Model->behaviors()->Typographic);
 	}

+ 18 - 0
tests/test_app/Controller/RefererRedirectComponentTestController.php

@@ -0,0 +1,18 @@
+<?php
+namespace App\Controller;
+
+use Tools\Controller\Controller;
+
+/**
+ * Use Controller instead of AppController to avoid conflicts
+ *
+ * @property \Tools\Controller\Component\CommonComponent $Common
+ */
+class RefererRedirectComponentTestController extends Controller {
+
+	/**
+	 * @var array
+	 */
+	public $components = ['Tools.RefererRedirect'];
+
+}