diff --git a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php index f4294dd94..dacca37ed 100644 --- a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php +++ b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php @@ -454,6 +454,13 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler $gridState = $this->gridField->getState(false); $actions->push(HiddenField::create($manager->getStateKey($this->gridField), null, $gridState)); + if (ClassInfo::hasMethod($manager, 'getStateRequestVar')) { + $stateRequestVar = $manager->getStateRequestVar(); + $stateValue = $this->getRequest()->requestVar($stateRequestVar); + if ($stateValue) { + $actions->push(HiddenField::create($stateRequestVar, '', $stateValue)); + } + } $actions->push($this->getRightGroupField()); } else { // adding new record diff --git a/src/Forms/GridField/GridState.php b/src/Forms/GridField/GridState.php index 1c0d9c640..6aea81f65 100644 --- a/src/Forms/GridField/GridState.php +++ b/src/Forms/GridField/GridState.php @@ -85,7 +85,7 @@ class GridState extends HiddenField public function getData() { if (!$this->data) { - $this->data = new GridState_Data(); + $this->data = new GridState_Data([], $this); } return $this->data; @@ -99,6 +99,11 @@ class GridState extends HiddenField return $this->grid->getList(); } + public function getGridField(): GridField + { + return $this->grid; + } + /** * Returns a json encoded string representation of this state. * diff --git a/src/Forms/GridField/GridState_Data.php b/src/Forms/GridField/GridState_Data.php index f74c60a23..a8d16be3d 100644 --- a/src/Forms/GridField/GridState_Data.php +++ b/src/Forms/GridField/GridState_Data.php @@ -2,6 +2,8 @@ namespace SilverStripe\Forms\GridField; +use SilverStripe\Core\ClassInfo; + /** * Simple set of data, similar to stdClass, but without the notice-level * errors. @@ -10,29 +12,33 @@ namespace SilverStripe\Forms\GridField; */ class GridState_Data { + use GridFieldStateAware; /** * @var array */ protected $data; + protected ?GridState $state; + protected $defaults = []; - public function __construct($data = []) + public function __construct($data = [], ?GridState $state = null) { $this->data = $data; + $this->state = $state; } public function __get($name) { - return $this->getData($name, new GridState_Data()); + return $this->getData($name, new GridState_Data([], $this->state)); } public function __call($name, $arguments) { // Assume first parameter is default value if (empty($arguments)) { - $default = new GridState_Data(); + $default = new GridState_Data([], $this->state); } else { $default = $arguments[0]; } @@ -72,16 +78,25 @@ class GridState_Data $this->data[$name] = $default; } else { if (is_array($this->data[$name])) { - $this->data[$name] = new GridState_Data($this->data[$name]); + $this->data[$name] = new GridState_Data($this->data[$name], $this->state); } } return $this->data[$name]; } + public function storeData() + { + $stateManager = $this->getStateManager(); + if (ClassInfo::hasMethod($stateManager, 'storeState') && $this->state) { + $stateManager->storeState($this->state->getGridField(), $this->state->Value()); + } + } + public function __set($name, $value) { $this->data[$name] = $value; + $this->storeData(); } public function __isset($name) @@ -92,6 +107,7 @@ class GridState_Data public function __unset($name) { unset($this->data[$name]); + $this->storeData(); } public function __toString() diff --git a/src/Forms/GridField/SessionGridFieldStateManager.php b/src/Forms/GridField/SessionGridFieldStateManager.php new file mode 100644 index 000000000..4ed3133ea --- /dev/null +++ b/src/Forms/GridField/SessionGridFieldStateManager.php @@ -0,0 +1,101 @@ +getStateRequestVar(); + $sessionStateID = $gridField->getForm()?->getRequestHandler()->getRequest()->requestVar($requestVar); + if (!$sessionStateID) { + $sessionStateID = Controller::curr()->getRequest()->requestVar($requestVar); + } + if ($sessionStateID) { + return $sessionStateID; + } + $stateKey = $this->getStateKey($gridField); + if (isset(self::$state_ids[$stateKey])) { + $sessionStateID = self::$state_ids[$stateKey]; + } elseif ($create) { + $sessionStateID = substr(md5(time()), 0, 8); + // we don't want session state id to be strictly numeric, since this is used as a session key, + // and session keys in php has to be usable as variable names + if (is_numeric($sessionStateID)) { + $sessionStateID .= 'a'; + } + self::$state_ids[$stateKey] = $sessionStateID; + } + return $sessionStateID; + } + + public function storeState(GridField $gridField, $value = null) + { + $sessionStateID = $this->getStateID($gridField, true); + $sessionState = Controller::curr()->getRequest()->getSession()->get($sessionStateID); + if (!$sessionState) { + $sessionState = []; + } + $stateKey = $this->getStateKey($gridField); + $sessionState[$stateKey] = $value ?? $gridField->getState(false)->Value(); + Controller::curr()->getRequest()->getSession()->set($sessionStateID, $sessionState); + } + + public function getStateRequestVar(): string + { + return 'gridSessionState'; + } + + /** + * @param GridField $gridField + * @return string + */ + public function getStateKey(GridField $gridField): string + { + $record = $gridField->getForm()?->getRecord(); + return $gridField->getName() . '-' . ($record ? $record->ID : 0); + } + + /** + * @param GridField $gridField + * @param string $url + * @return string + */ + public function addStateToURL(GridField $gridField, string $url): string + { + $sessionStateID = $this->getStateID($gridField); + if ($sessionStateID) { + return Controller::join_links($url, '?' . $this->getStateRequestVar() . '=' . $sessionStateID); + } + return $url; + } + + /** + * @param GridField $gridField + * @param HTTPRequest $request + * @return string|null + */ + public function getStateFromRequest(GridField $gridField, HTTPRequest $request): ?string + { + $gridSessionStateID = $request->requestVar($this->getStateRequestVar()); + if ($gridSessionStateID) { + $sessionState = $request->getSession()->get($gridSessionStateID); + $stateKey = $this->getStateKey($gridField); + if ($sessionState && isset($sessionState[$stateKey])) { + return $sessionState[$stateKey]; + } + } + return null; + } +} diff --git a/tests/php/Forms/GridField/SessionGridFieldStateManagerTest.php b/tests/php/Forms/GridField/SessionGridFieldStateManagerTest.php new file mode 100644 index 000000000..e06319358 --- /dev/null +++ b/tests/php/Forms/GridField/SessionGridFieldStateManagerTest.php @@ -0,0 +1,140 @@ +registerService(new SessionGridFieldStateManager(), GridFieldStateManagerInterface::class); + } + + public function testStateKey() + { + $manager = new SessionGridFieldStateManager(); + $controller = new Controller(); + $form1 = new Form($controller, 'form1', new FieldList(), new FieldList()); + $testObject = new TestObject(); + $testObject->ID = 1; + $form2 = new Form($controller, 'form2', new FieldList(), new FieldList()); + $form2->loadDataFrom($testObject); + + $grid1 = new GridField('A'); + $grid2 = new GridField('B'); + $grid1->setForm($form1); + $grid2->setForm($form2); + $this->assertEquals('A-0', $manager->getStateKey($grid1)); + $this->assertEquals('B-1', $manager->getStateKey($grid2)); + } + + public function testAddStateToURL() + { + $manager = new SessionGridFieldStateManager(); + $grid = new GridField('TestGrid'); + $grid->getState()->testValue = 'foo'; + $stateRequestVar = $manager->getStateRequestVar(); + $link = '/link-to/something'; + $this->assertTrue( + preg_match( + "|^$link\?{$stateRequestVar}=[a-zA-Z0-9]+$|", + $manager->addStateToURL($grid, $link) + ) == 1 + ); + + $link = '/link-to/something-else?someParam=somevalue'; + $this->assertTrue( + preg_match( + "|^/link-to/something-else\?someParam=somevalue&{$stateRequestVar}=[a-zA-Z0-9]+$|", + $manager->addStateToURL($grid, $link) + ) == 1 + ); + } + + public function testGetStateFromRequest() + { + $manager = new SessionGridFieldStateManager(); + + $session = new Session([]); + $request = new HTTPRequest( + 'GET', + '/link-to/something', + [ + $manager->getStateRequestVar() => 'testGetStateFromRequest' + ] + ); + $request->setSession($session); + + $controller = new Controller(); + $controller->setRequest($request); + $controller->pushCurrent(); + $form = new Form($controller, 'form1', new FieldList(), new FieldList()); + $grid = new GridField('TestGrid'); + $grid->setForm($form); + + $grid->getState()->testValue = 'foo'; + $state = $grid->getState(false)->Value() ?? '{}'; + $result = $manager->getStateFromRequest($grid, $request); + + $this->assertEquals($state, $result); + $controller->popCurrent(); + } + + public function testDefaultStateLeavesURLUnchanged() + { + $manager = new SessionGridFieldStateManager(); + $grid = new GridField('DefaultStateGrid'); + $grid->getState(false)->getData()->testValue->initDefaults(['foo' => 'bar']); + $link = '/link-to/something'; + + $this->assertEquals('{}', $grid->getState(false)->Value()); + + $this->assertEquals( + '/link-to/something', + $manager->addStateToURL($grid, $link) + ); + } + + public function testStoreState() + { + $manager = new SessionGridFieldStateManager(); + + $session = new Session([]); + $request = new HTTPRequest( + 'GET', + '/link-to/something', + [ + $manager->getStateRequestVar() => 'testStoreState' + ] + ); + $request->setSession($session); + + $controller = new Controller(); + $controller->setRequest($request); + $controller->pushCurrent(); + $form = new Form($controller, 'form1', new FieldList(), new FieldList()); + $grid = new GridField('TestGrid'); + $grid->setForm($form); + + $grid->getState()->testValue = 'foo'; + $state = $grid->getState(false)->Value() ?? '{}'; + + $manager->storeState($grid); + $this->assertEquals($state, $session->get('testStoreState')['TestGrid-0']); + + $controller->popCurrent(); + } +}