Merge pull request #8627 from creative-commoners/pulls/4.3/abstr-action-state

FIX Provide alternatives to session for storing GridField_FormAction state
This commit is contained in:
Maxime Rainville 2018-12-03 11:58:05 +13:00 committed by GitHub
commit 731ef00f7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 17 deletions

6
_config/gridfield.yml Normal file
View File

@ -0,0 +1,6 @@
---
Name: gridfieldconfig
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Forms\GridField\FormAction\StateStore:
class: SilverStripe\Forms\GridField\FormAction\SessionStore

View File

@ -472,11 +472,29 @@ functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_grid
## Saving the GridField State ## Saving the GridField State
`GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transfered `GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transferred
between page requests by being inserted as a hidden field in the form. between page requests by being inserted as a hidden field in the form.
The `GridState_Component` sets and gets data from the `GridState`. The `GridState_Component` sets and gets data from the `GridState`.
## Saving GridField_FormAction state
By default state used for performing form actions is saved in the session and tagged with a key like `gf_abcd1234`. In
some cases session may not be an appropriate storage method. The storage method can be configured:
```yaml
Name: mysitegridfieldconfig
After: gridfieldconfig
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Forms\GridField\FormAction\StateStore:
class: SilverStripe\Forms\GridField\FormAction\AttributeStore
```
The `AttributeStore` class configures action state to be stored in the DOM and sent back on the request that performs
the action. Custom storage methods can be created and used by implementing the `StateStore` interface and configuring
`Injector` in a similar fashion.
## API Documentation ## API Documentation
* [GridField](api:SilverStripe\Forms\GridField\GridField) * [GridField](api:SilverStripe\Forms\GridField\GridField)

View File

@ -8,6 +8,7 @@
- New React-based search UI for the CMS, Asset-Admin, GridFields and ModelAdmins. - New React-based search UI for the CMS, Asset-Admin, GridFields and ModelAdmins.
- A new `GridFieldLazyLoader` component can be added to `GridField`. This will delay the fetching of data until the user access the container Tab of the GridField. - A new `GridFieldLazyLoader` component can be added to `GridField`. This will delay the fetching of data until the user access the container Tab of the GridField.
- `SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController` is now the default CMS history controller and `SilverStripe\CMS\Controllers\CMSPageHistoryController` has been deprecated. - `SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController` is now the default CMS history controller and `SilverStripe\CMS\Controllers\CMSPageHistoryController` has been deprecated.
- It's now possible to avoid storing GridField data in session. See [the documentation on GridField](../developer_guides/forms/field_types/gridfield/#saving-the-gridfield-state).
## Upgrading {#upgrading} ## Upgrading {#upgrading}

View File

@ -0,0 +1,17 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
abstract class AbstractRequestAwareStore implements StateStore
{
/**
* @return HTTPRequest
*/
public function getRequest()
{
// Replicating existing functionality from GridField_FormAction
return Controller::curr()->getRequest();
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
/**
* Stores GridField action state on an attribute on the action and then analyses request parameters to load it back
*/
class AttributeStore extends AbstractRequestAwareStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state)
{
// Just save the state in the attributes of the action
return [
'data-action-state' => json_encode($state),
];
}
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id)
{
// Check the request
return (array) json_decode((string) $this->getRequest()->requestVar('ActionState'), true);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
use SilverStripe\Control\HTTPRequest;
/**
* Stores GridField action state in the session in exactly the same way it has in the past
*/
class SessionStore extends AbstractRequestAwareStore implements StateStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state)
{
$this->getRequest()->getSession()->set($id, $state);
// This adapter does not require any additional attributes...
return [];
}
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id)
{
return (array) $this->getRequest()->getSession()->get($id);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
interface StateStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state);
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id);
}

View File

@ -9,8 +9,11 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormField; use SilverStripe\Forms\FormField;
use SilverStripe\Forms\GridField\FormAction\SessionStore;
use SilverStripe\Forms\GridField\FormAction\StateStore;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
@ -1009,9 +1012,14 @@ class GridField extends FormField
$state->setValue($fieldData['GridState']); $state->setValue($fieldData['GridState']);
} }
// Fetch the store for the "state" of actions (not the GridField)
/** @var StateStore $store */
$store = Injector::inst()->create(StateStore::class . '.' . $this->getName());
foreach ($data as $dataKey => $dataValue) { foreach ($data as $dataKey => $dataValue) {
if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) { if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
$stateChange = $request->getSession()->get($matches[1]); $stateChange = $store->load($matches[1]);
$actionName = $stateChange['actionName']; $actionName = $stateChange['actionName'];
$arguments = array(); $arguments = array();

View File

@ -289,14 +289,18 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi
}, array_keys($filters)), $filters); }, array_keys($filters)), $filters);
} }
$searchAction = GridField_FormAction::create($gridField, 'filter', false, 'filter', null);
$clearAction = GridField_FormAction::create($gridField, 'reset', false, 'reset', null);
$schema = [ $schema = [
'formSchemaUrl' => $schemaUrl, 'formSchemaUrl' => $schemaUrl,
'name' => $searchField, 'name' => $searchField,
'placeholder' => _t(__CLASS__ . '.Search', 'Search "{name}"', ['name' => $name]), 'placeholder' => _t(__CLASS__ . '.Search', 'Search "{name}"', ['name' => $name]),
'filters' => $filters ?: new \stdClass, // stdClass maps to empty json object '{}' 'filters' => $filters ?: new \stdClass, // stdClass maps to empty json object '{}'
'gridfield' => $gridField->getName(), 'gridfield' => $gridField->getName(),
'searchAction' => GridField_FormAction::create($gridField, 'filter', false, 'filter', null)->getAttribute('name'), 'searchAction' => $searchAction->getAttribute('name'),
'clearAction' => GridField_FormAction::create($gridField, 'reset', false, 'reset', null)->getAttribute('name') 'searchActionState' => $searchAction->getAttribute('data-action-state'),
'clearAction' => $clearAction->getAttribute('name'),
'clearActionState' => $clearAction->getAttribute('data-action-state'),
]; ];
return Convert::raw2json($schema); return Convert::raw2json($schema);

View File

@ -3,8 +3,10 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction; use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\GridField\FormAction\StateStore;
/** /**
* This class is the base class when you want to have an action that alters the state of the * This class is the base class when you want to have an action that alters the state of the
@ -12,6 +14,11 @@ use SilverStripe\Forms\FormAction;
*/ */
class GridField_FormAction extends FormAction class GridField_FormAction extends FormAction
{ {
/**
* A common string prefix for keys generated to store form action "state" against
*/
const STATE_KEY_PREFIX = 'gf_';
/** /**
* @var GridField * @var GridField
*/ */
@ -80,29 +87,35 @@ class GridField_FormAction extends FormAction
*/ */
public function getAttributes() public function getAttributes()
{ {
// Store state in session, and pass ID to client side. // Determine the state that goes with this action
$state = array( $state = array(
'grid' => $this->getNameFromParent(), 'grid' => $this->getNameFromParent(),
'actionName' => $this->actionName, 'actionName' => $this->actionName,
'args' => $this->args, 'args' => $this->args,
); );
// Ensure $id doesn't contain only numeric characters // Generate a key and attach it to the action name
$id = 'gf_' . substr(md5(serialize($state)), 0, 8); $key = static::STATE_KEY_PREFIX . substr(md5(serialize($state)), 0, 8);
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch will strip it
$name = 'action_gridFieldAlterAction?StateID=' . $key;
$session = Controller::curr()->getRequest()->getSession(); // Define attributes
$session->set($id, $state); $attributes = array(
$actionData['StateID'] = $id; 'name' => $name,
'data-url' => $this->gridField->Link(),
'type' => "button",
);
// Create a "store" for the "state" of this action
/** @var StateStore $store */
$store = Injector::inst()->create(StateStore::class . '.' . $this->gridField->getName());
// Store the state and update attributes as required
$attributes += $store->save($key, $state);
// Return attributes
return array_merge( return array_merge(
parent::getAttributes(), parent::getAttributes(),
array( $attributes
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch
// will strip it from the requests
'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData),
'data-url' => $this->gridField->Link(),
'type' => "button",
)
); );
} }