diff --git a/_config/gridfield.yml b/_config/gridfield.yml new file mode 100644 index 000000000..a47da229f --- /dev/null +++ b/_config/gridfield.yml @@ -0,0 +1,6 @@ +--- +Name: gridfieldconfig +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Forms\GridField\FormAction\StateStore: + class: SilverStripe\Forms\GridField\FormAction\SessionStore diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md index 3750c0761..9461ae801 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md @@ -472,11 +472,29 @@ functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_grid ## 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. 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 * [GridField](api:SilverStripe\Forms\GridField\GridField) diff --git a/docs/en/04_Changelogs/4.3.0.md b/docs/en/04_Changelogs/4.3.0.md index ab31d003e..ffa49c558 100644 --- a/docs/en/04_Changelogs/4.3.0.md +++ b/docs/en/04_Changelogs/4.3.0.md @@ -8,6 +8,7 @@ - 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. - `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} diff --git a/src/Forms/GridField/FormAction/AbstractRequestAwareStore.php b/src/Forms/GridField/FormAction/AbstractRequestAwareStore.php new file mode 100644 index 000000000..84f683292 --- /dev/null +++ b/src/Forms/GridField/FormAction/AbstractRequestAwareStore.php @@ -0,0 +1,17 @@ +getRequest(); + } +} diff --git a/src/Forms/GridField/FormAction/AttributeStore.php b/src/Forms/GridField/FormAction/AttributeStore.php new file mode 100644 index 000000000..1f84fb768 --- /dev/null +++ b/src/Forms/GridField/FormAction/AttributeStore.php @@ -0,0 +1,36 @@ + 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); + } +} diff --git a/src/Forms/GridField/FormAction/SessionStore.php b/src/Forms/GridField/FormAction/SessionStore.php new file mode 100644 index 000000000..0798d0c6a --- /dev/null +++ b/src/Forms/GridField/FormAction/SessionStore.php @@ -0,0 +1,37 @@ +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); + } +} diff --git a/src/Forms/GridField/FormAction/StateStore.php b/src/Forms/GridField/FormAction/StateStore.php new file mode 100644 index 000000000..3a0b89c70 --- /dev/null +++ b/src/Forms/GridField/FormAction/StateStore.php @@ -0,0 +1,23 @@ +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) { if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) { - $stateChange = $request->getSession()->get($matches[1]); + $stateChange = $store->load($matches[1]); + $actionName = $stateChange['actionName']; $arguments = array(); diff --git a/src/Forms/GridField/GridFieldFilterHeader.php b/src/Forms/GridField/GridFieldFilterHeader.php index 2d2440877..d51ea5023 100755 --- a/src/Forms/GridField/GridFieldFilterHeader.php +++ b/src/Forms/GridField/GridFieldFilterHeader.php @@ -289,14 +289,18 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi }, array_keys($filters)), $filters); } + $searchAction = GridField_FormAction::create($gridField, 'filter', false, 'filter', null); + $clearAction = GridField_FormAction::create($gridField, 'reset', false, 'reset', null); $schema = [ 'formSchemaUrl' => $schemaUrl, 'name' => $searchField, 'placeholder' => _t(__CLASS__ . '.Search', 'Search "{name}"', ['name' => $name]), 'filters' => $filters ?: new \stdClass, // stdClass maps to empty json object '{}' 'gridfield' => $gridField->getName(), - 'searchAction' => GridField_FormAction::create($gridField, 'filter', false, 'filter', null)->getAttribute('name'), - 'clearAction' => GridField_FormAction::create($gridField, 'reset', false, 'reset', null)->getAttribute('name') + 'searchAction' => $searchAction->getAttribute('name'), + 'searchActionState' => $searchAction->getAttribute('data-action-state'), + 'clearAction' => $clearAction->getAttribute('name'), + 'clearActionState' => $clearAction->getAttribute('data-action-state'), ]; return Convert::raw2json($schema); diff --git a/src/Forms/GridField/GridField_FormAction.php b/src/Forms/GridField/GridField_FormAction.php index 10dc772bc..7e791410d 100644 --- a/src/Forms/GridField/GridField_FormAction.php +++ b/src/Forms/GridField/GridField_FormAction.php @@ -3,8 +3,10 @@ namespace SilverStripe\Forms\GridField; use SilverStripe\Control\Controller; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\Form; 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 @@ -12,6 +14,11 @@ use SilverStripe\Forms\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 */ @@ -80,29 +87,35 @@ class GridField_FormAction extends FormAction */ public function getAttributes() { - // Store state in session, and pass ID to client side. + // Determine the state that goes with this action $state = array( 'grid' => $this->getNameFromParent(), 'actionName' => $this->actionName, 'args' => $this->args, ); - // Ensure $id doesn't contain only numeric characters - $id = 'gf_' . substr(md5(serialize($state)), 0, 8); + // Generate a key and attach it to the action name + $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(); - $session->set($id, $state); - $actionData['StateID'] = $id; + // Define attributes + $attributes = array( + '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( parent::getAttributes(), - array( - // 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", - ) + $attributes ); }