mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #9072 from open-sausages/pulls/4.4/good-griddance
BUGFIX: Nested gridfields accessing incorrect URL state
This commit is contained in:
commit
54647aa0e1
@ -4,3 +4,5 @@ Name: gridfieldconfig
|
|||||||
SilverStripe\Core\Injector\Injector:
|
SilverStripe\Core\Injector\Injector:
|
||||||
SilverStripe\Forms\GridField\FormAction\StateStore:
|
SilverStripe\Forms\GridField\FormAction\StateStore:
|
||||||
class: SilverStripe\Forms\GridField\FormAction\SessionStore
|
class: SilverStripe\Forms\GridField\FormAction\SessionStore
|
||||||
|
SilverStripe\Forms\GridField\GridFieldStateManagerInterface:
|
||||||
|
class: SilverStripe\Forms\GridField\GridFieldStateManager
|
@ -8,6 +8,7 @@ use SilverStripe\Control\HTTPResponse;
|
|||||||
use SilverStripe\Control\RequestHandler;
|
use SilverStripe\Control\RequestHandler;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Core\Extensible;
|
use SilverStripe\Core\Extensible;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\Forms\Validator;
|
use SilverStripe\Forms\Validator;
|
||||||
@ -30,7 +31,7 @@ use SilverStripe\ORM\Filterable;
|
|||||||
class GridFieldDetailForm implements GridField_URLHandler
|
class GridFieldDetailForm implements GridField_URLHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
use Extensible;
|
use Extensible, Injectable, GridFieldStateAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -106,15 +107,36 @@ class GridFieldDetailForm implements GridField_URLHandler
|
|||||||
*/
|
*/
|
||||||
public function handleItem($gridField, $request)
|
public function handleItem($gridField, $request)
|
||||||
{
|
{
|
||||||
if ($gridStateStr = $request->getVar('gridState')) {
|
|
||||||
$gridField->getState(false)->setValue($gridStateStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our getController could either give us a true Controller, if this is the top-level GridField.
|
// Our getController could either give us a true Controller, if this is the top-level GridField.
|
||||||
// It could also give us a RequestHandler in the form of GridFieldDetailForm_ItemRequest if this is a
|
// It could also give us a RequestHandler in the form of GridFieldDetailForm_ItemRequest if this is a
|
||||||
// nested GridField.
|
// nested GridField.
|
||||||
$requestHandler = $gridField->getForm()->getController();
|
$requestHandler = $gridField->getForm()->getController();
|
||||||
|
$record = $this->getRecordFromRequest($gridField, $request);
|
||||||
|
if (!$record) {
|
||||||
|
return $requestHandler->httpError(404, 'That record was not found');
|
||||||
|
}
|
||||||
|
$handler = $this->getItemRequestHandler($gridField, $record, $requestHandler);
|
||||||
|
$manager = $this->getStateManager();
|
||||||
|
if ($gridStateStr = $manager->getStateFromRequest($gridField, $request)) {
|
||||||
|
$gridField->getState(false)->setValue($gridStateStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no validator has been set on the GridField and the record has a
|
||||||
|
// CMS validator, use that.
|
||||||
|
if (!$this->getValidator() && ClassInfo::hasMethod($record, 'getCMSValidator')) {
|
||||||
|
$this->setValidator($record->getCMSValidator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler->handleRequest($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return DataObject|null
|
||||||
|
*/
|
||||||
|
protected function getRecordFromRequest(GridField $gridField, HTTPRequest $request): ?DataObject
|
||||||
|
{
|
||||||
/** @var DataObject $record */
|
/** @var DataObject $record */
|
||||||
if (is_numeric($request->param('ID'))) {
|
if (is_numeric($request->param('ID'))) {
|
||||||
/** @var Filterable $dataList */
|
/** @var Filterable $dataList */
|
||||||
@ -124,15 +146,7 @@ class GridFieldDetailForm implements GridField_URLHandler
|
|||||||
$record = Injector::inst()->create($gridField->getModelClass());
|
$record = Injector::inst()->create($gridField->getModelClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
$handler = $this->getItemRequestHandler($gridField, $record, $requestHandler);
|
return $record;
|
||||||
|
|
||||||
// if no validator has been set on the GridField and the record has a
|
|
||||||
// CMS validator, use that.
|
|
||||||
if (!$this->getValidator() && ClassInfo::hasMethod($record, 'getCMSValidator')) {
|
|
||||||
$this->setValidator($record->getCMSValidator());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $handler->handleRequest($request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,12 +27,13 @@ use SilverStripe\View\SSViewer;
|
|||||||
|
|
||||||
class GridFieldDetailForm_ItemRequest extends RequestHandler
|
class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||||
{
|
{
|
||||||
|
use GridFieldStateAware;
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = [
|
||||||
'edit',
|
'edit',
|
||||||
'view',
|
'view',
|
||||||
'ItemEditForm'
|
'ItemEditForm'
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -70,10 +71,10 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
*/
|
*/
|
||||||
protected $template = null;
|
protected $template = null;
|
||||||
|
|
||||||
private static $url_handlers = array(
|
private static $url_handlers = [
|
||||||
'$Action!' => '$Action',
|
'$Action!' => '$Action',
|
||||||
'' => 'edit',
|
'' => 'edit',
|
||||||
);
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -287,7 +288,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
/** @var GridFieldDetailForm $component */
|
/** @var GridFieldDetailForm $component */
|
||||||
$component = $this->gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
|
$component = $this->gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
|
||||||
$paginator = $this->getGridField()->getConfig()->getComponentByType(GridFieldPaginator::class);
|
$paginator = $this->getGridField()->getConfig()->getComponentByType(GridFieldPaginator::class);
|
||||||
$gridState = $this->getRequest()->requestVar('gridState');
|
$gridState = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest());
|
||||||
if ($component && $paginator && $component->getShowPagination()) {
|
if ($component && $paginator && $component->getShowPagination()) {
|
||||||
$previousIsDisabled = !$this->getPreviousRecordID();
|
$previousIsDisabled = !$this->getPreviousRecordID();
|
||||||
$nextIsDisabled = !$this->getNextRecordID();
|
$nextIsDisabled = !$this->getNextRecordID();
|
||||||
@ -349,7 +350,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
protected function getFormActions()
|
protected function getFormActions()
|
||||||
{
|
{
|
||||||
$actions = FieldList::create();
|
$actions = FieldList::create();
|
||||||
|
$manager = $this->getStateManager();
|
||||||
if ($this->record->ID !== 0) { // existing record
|
if ($this->record->ID !== 0) { // existing record
|
||||||
if ($this->record->canEdit()) {
|
if ($this->record->canEdit()) {
|
||||||
$actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save'))
|
$actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save'))
|
||||||
@ -363,9 +364,9 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action--delete'));
|
->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action--delete'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$gridState = $this->getRequest()->requestVar('gridState');
|
$gridState = $manager->getStateFromRequest($this->gridField, $this->getRequest());
|
||||||
$this->gridField->getState(false)->setValue($gridState);
|
$this->gridField->getState(false)->setValue($gridState);
|
||||||
$actions->push(HiddenField::create('gridState', null, $gridState));
|
$actions->push(HiddenField::create($manager->getStateKey($this->gridField), null, $gridState));
|
||||||
|
|
||||||
$actions->push($this->getRightGroupField());
|
$actions->push($this->getRightGroupField());
|
||||||
} else { // adding new record
|
} else { // adding new record
|
||||||
@ -498,12 +499,13 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
*/
|
*/
|
||||||
public function getEditLink($id)
|
public function getEditLink($id)
|
||||||
{
|
{
|
||||||
return Controller::join_links(
|
$link = Controller::join_links(
|
||||||
$this->gridField->Link(),
|
$this->gridField->Link(),
|
||||||
'item',
|
'item',
|
||||||
$id,
|
$id
|
||||||
'?gridState=' . urlencode($this->gridField->getState(false)->Value())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return $this->getStateManager()->addStateToURL($this->gridField, $link);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -515,7 +517,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
|||||||
$gridField = $this->getGridField();
|
$gridField = $this->getGridField();
|
||||||
$list = $gridField->getManipulatedList();
|
$list = $gridField->getManipulatedList();
|
||||||
$state = $gridField->getState(false);
|
$state = $gridField->getState(false);
|
||||||
$gridStateStr = $this->getRequest()->requestVar('gridState');
|
$gridStateStr = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest());
|
||||||
if (!empty($gridStateStr)) {
|
if (!empty($gridStateStr)) {
|
||||||
$state->setValue($gridStateStr);
|
$state->setValue($gridStateStr);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace SilverStripe\Forms\GridField;
|
namespace SilverStripe\Forms\GridField;
|
||||||
|
|
||||||
use SilverStripe\Control\Controller;
|
use SilverStripe\Control\Controller;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
use SilverStripe\View\SSViewer;
|
use SilverStripe\View\SSViewer;
|
||||||
@ -19,6 +20,8 @@ use SilverStripe\View\SSViewer;
|
|||||||
*/
|
*/
|
||||||
class GridFieldEditButton implements GridField_ColumnProvider, GridField_ActionProvider, GridField_ActionMenuLink
|
class GridFieldEditButton implements GridField_ColumnProvider, GridField_ActionProvider, GridField_ActionMenuLink
|
||||||
{
|
{
|
||||||
|
use Injectable, GridFieldStateAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML classes to be added to GridField edit buttons
|
* HTML classes to be added to GridField edit buttons
|
||||||
*
|
*
|
||||||
@ -62,12 +65,13 @@ class GridFieldEditButton implements GridField_ColumnProvider, GridField_ActionP
|
|||||||
*/
|
*/
|
||||||
public function getUrl($gridField, $record, $columnName)
|
public function getUrl($gridField, $record, $columnName)
|
||||||
{
|
{
|
||||||
return Controller::join_links(
|
$link = Controller::join_links(
|
||||||
$gridField->Link('item'),
|
$gridField->Link('item'),
|
||||||
$record->ID,
|
$record->ID,
|
||||||
'edit',
|
'edit'
|
||||||
'?gridState=' . urlencode($gridField->getState(false)->Value())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return $this->getStateManager()->addStateToURL($gridField, $link);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
40
src/Forms/GridField/GridFieldStateAware.php
Normal file
40
src/Forms/GridField/GridFieldStateAware.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace SilverStripe\Forms\GridField;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait that makes a class able to consume and use a {@link GridFieldStateManagerInterface}
|
||||||
|
* implementation
|
||||||
|
*/
|
||||||
|
trait GridFieldStateAware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var GridFieldStateManagerInterface
|
||||||
|
*/
|
||||||
|
protected $stateManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridFieldStateManagerInterface $manager
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setStateManager(GridFieldStateManagerInterface $manager): self
|
||||||
|
{
|
||||||
|
$this->stateManager = $manager;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback on the direct Injector access, but allow a custom implementation
|
||||||
|
* to be applied
|
||||||
|
*
|
||||||
|
* @return GridFieldStateManagerInterface
|
||||||
|
*/
|
||||||
|
public function getStateManager(): GridFieldStateManagerInterface
|
||||||
|
{
|
||||||
|
return $this->stateManager ?: Injector::inst()->get(GridFieldStateManagerInterface::class);
|
||||||
|
}
|
||||||
|
}
|
56
src/Forms/GridField/GridFieldStateManager.php
Normal file
56
src/Forms/GridField/GridFieldStateManager.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace SilverStripe\Forms\GridField;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTP;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a unique key for the gridfield, and uses that to write to and retrieve
|
||||||
|
* its state from the request
|
||||||
|
*/
|
||||||
|
class GridFieldStateManager implements GridFieldStateManagerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getStateKey(GridField $gridField): string
|
||||||
|
{
|
||||||
|
$i = 0;
|
||||||
|
$form = $gridField->getForm();
|
||||||
|
if ($form) {
|
||||||
|
$controller = $form->getController();
|
||||||
|
while ($controller instanceof GridFieldDetailForm_ItemRequest) {
|
||||||
|
$controller = $controller->getController();
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('%s-%s-%s', 'gridState', $gridField->getName(), $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param string $url
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function addStateToURL(GridField $gridField, string $url): string
|
||||||
|
{
|
||||||
|
$key = $this->getStateKey($gridField);
|
||||||
|
$value = $gridField->getState(false)->Value();
|
||||||
|
|
||||||
|
return HTTP::setGetVar($key, $value, $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getStateFromRequest(GridField $gridField, HTTPRequest $request): ?string
|
||||||
|
{
|
||||||
|
return $request->requestVar($this->getStateKey($gridField));
|
||||||
|
}
|
||||||
|
}
|
33
src/Forms/GridField/GridFieldStateManagerInterface.php
Normal file
33
src/Forms/GridField/GridFieldStateManagerInterface.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace SilverStripe\Forms\GridField;
|
||||||
|
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a class that can create a key for a gridfield and apply its
|
||||||
|
* state to a request, and consume state from the request
|
||||||
|
*/
|
||||||
|
interface GridFieldStateManagerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getStateKey(GridField $gridField): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param string $url
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function addStateToURL(GridField $gridField, string $url): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getStateFromRequest(GridField $gridField, HTTPRequest $request): ?string;
|
||||||
|
}
|
82
tests/php/Forms/GridField/GridFieldStateManagerTest.php
Normal file
82
tests/php/Forms/GridField/GridFieldStateManagerTest.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Forms\Tests\GridField;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Controller;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Forms\FieldList;
|
||||||
|
use SilverStripe\Forms\GridField\GridField;
|
||||||
|
use SilverStripe\Forms\GridField\GridFieldDetailForm;
|
||||||
|
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
|
||||||
|
use SilverStripe\Forms\GridField\GridFieldStateManager;
|
||||||
|
use SilverStripe\Forms\Form;
|
||||||
|
use SilverStripe\Forms\Tests\GridField\GridFieldPrintButtonTest\TestObject;
|
||||||
|
use SilverStripe\View\ArrayData;
|
||||||
|
|
||||||
|
class GridFieldStateManagerTest extends SapphireTest
|
||||||
|
{
|
||||||
|
public function testStateKey()
|
||||||
|
{
|
||||||
|
$manager = new GridFieldStateManager();
|
||||||
|
$controller = new Controller();
|
||||||
|
$form1 = new Form($controller, 'form1', new FieldList(), new FieldList());
|
||||||
|
$itemRequest = new GridFieldDetailForm_ItemRequest(
|
||||||
|
new GridField('test'),
|
||||||
|
new GridFieldDetailForm(),
|
||||||
|
new TestObject(),
|
||||||
|
$controller,
|
||||||
|
'itemRequest'
|
||||||
|
);
|
||||||
|
$form2 = new Form($itemRequest, 'form1', new FieldList(), new FieldList());
|
||||||
|
|
||||||
|
$grid1 = new GridField('A');
|
||||||
|
$grid2 = new GridField('B');
|
||||||
|
$grid1->setForm($form1);
|
||||||
|
$grid2->setForm($form2);
|
||||||
|
$this->assertEquals('gridState-A-0', $manager->getStateKey($grid1));
|
||||||
|
$this->assertEquals('gridState-B-1', $manager->getStateKey($grid2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddStateToURL()
|
||||||
|
{
|
||||||
|
$manager = new GridFieldStateManager();
|
||||||
|
$grid = new GridField('TestGrid');
|
||||||
|
$grid->getState()->testValue = 'foo';
|
||||||
|
$link = '/link-to/something';
|
||||||
|
$state = $grid->getState(false)->Value();
|
||||||
|
$this->assertEquals(
|
||||||
|
'/link-to/something?gridState-TestGrid-0=' . urlencode($state),
|
||||||
|
$manager->addStateToURL($grid, $link)
|
||||||
|
);
|
||||||
|
|
||||||
|
$link = '/link-to/something-else?someParam=somevalue';
|
||||||
|
$state = $grid->getState(false)->Value();
|
||||||
|
$this->assertEquals(
|
||||||
|
'/link-to/something-else?someParam=somevalue&gridState-TestGrid-0=' . urlencode($state),
|
||||||
|
$manager->addStateToURL($grid, $link)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetStateFromRequest()
|
||||||
|
{
|
||||||
|
$manager = new GridFieldStateManager();
|
||||||
|
$controller = new Controller();
|
||||||
|
$form = new Form($controller, 'form1', new FieldList(), new FieldList());
|
||||||
|
$grid = new GridField('TestGrid');
|
||||||
|
$grid->setForm($form);
|
||||||
|
|
||||||
|
$grid->getState()->testValue = 'foo';
|
||||||
|
$state = urlencode($grid->getState(false)->Value());
|
||||||
|
$request = new HTTPRequest(
|
||||||
|
'GET',
|
||||||
|
'/link-to/something',
|
||||||
|
[
|
||||||
|
$manager->getStateKey($grid) => $state
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$result = $manager->getStateFromRequest($grid, $request);
|
||||||
|
|
||||||
|
$this->assertEquals($state, $result);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user