Merge pull request #9655 from sminnee/pulls/9647-find-lost-records

NEW: Add GridFieldDetailForm::setRedirectMissingRecords()
This commit is contained in:
Ingo Schommer 2021-05-21 13:53:18 +12:00 committed by GitHub
commit 196752566f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 137 additions and 5 deletions

View File

@ -3,8 +3,11 @@
namespace SilverStripe\Forms\GridField;
use Closure;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\HTTPStreamResponse;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Extensible;
@ -12,6 +15,7 @@ use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Validator;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Filterable;
@ -68,6 +72,12 @@ class GridFieldDetailForm implements GridField_URLHandler
*/
protected $itemRequestClass;
/**
* If true, will redirect to missing records if they are found elsewhere
* @var bool
*/
protected $redirectMissingRecords = false;
/**
* @var callable With two parameters: $form and $component
*/
@ -113,6 +123,17 @@ class GridFieldDetailForm implements GridField_URLHandler
$requestHandler = $gridField->getForm()->getController();
$record = $this->getRecordFromRequest($gridField, $request);
if (!$record) {
// Look for the record elsewhere in the CMS
$redirectDest = $this->getLostRecordRedirection($gridField, $request);
// Don't allow infinite redirections
if ($redirectDest) {
// Mark the remainder of the URL as parsed to trigger an immediate redirection
while (!$request->allParsed()) {
$request->shift();
}
return (new HTTPResponse())->redirect($redirectDest);
}
return $requestHandler->httpError(404, 'That record was not found');
}
$handler = $this->getItemRequestHandler($gridField, $record, $requestHandler);
@ -148,6 +169,50 @@ class GridFieldDetailForm implements GridField_URLHandler
return $record;
}
/**
* Try and find another URL at which the given record can be edited.
* If redirectMissingRecords is true and the record has a CMSEditLink method, that value will be returned.
* This only works when the list passed to the GridField is a {@link DataList}.
*
* @param $gridField The current GridField
* @param $id The ID of the DataObject to open
*/
public function getLostRecordRedirection(GridField $gridField, HTTPRequest $request, ?int $id = null): ?string
{
if (!$this->redirectMissingRecords) {
return null;
}
// If not supplied, look up the ID from the request
if ($id === null && is_numeric($request->param('ID'))) {
$id = (int)$request->param('ID');
}
if (!$id) {
return null;
}
$list = $gridField->getList();
if (!$list instanceof DataList) {
throw new \LogicException('List is not of type DataList, cannot determine redirection target');
}
$existing = DataObject::get($list->dataClass())->byID($id);
if ($existing && $existing->hasMethod('CMSEditLink')) {
$link = $existing->CMSEditLink();
}
if ($link && $link == $request->getURL()) {
throw new \LogicException(sprintf(
'Infinite redirection to "%s" detected in GridFieldDetailForm->getLostRecordRedirection()',
$link
));
}
return $link;
}
/**
* Build a request handler for the given record
*
@ -209,6 +274,28 @@ class GridFieldDetailForm implements GridField_URLHandler
return $this->name;
}
/**
* Enable redirection to missing records.
*
* If a GridField shows a filtered list, and the DataObject is not in the list but exists in the
* database, and the DataObject has a CMSEditLink method, then the system will redirect to the
* URL returned by that method.
*/
public function setRedirectMissingRecords(bool $redirectMissingRecords): self
{
$this->redirectMissingRecords = $redirectMissingRecords;
return $this;
}
/**
* Return the status of redirection to missing records.
* @see setRedirectMissingRecordssetRedirectMissingRecords
*/
public function getRedirectMissingRecords(): bool
{
return $this->redirectMissingRecords;
}
/**
* @return bool
*/

View File

@ -623,6 +623,11 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
// to the same URL (it assumes that its content is already current, and doesn't reload)
return $this->edit($controller->getRequest());
} else {
// We might be able to redirect to open the record in a different view
if ($redirectDest = $this->component->getLostRecordRedirection($this->gridField, $controller->getRequest(), $this->record->ID)) {
return $controller->redirect($redirectDest, 302);
}
// Changes to the record properties might've excluded the record from
// a filtered list, so return back to the main view if it can't be found
$url = $controller->getRequest()->getURL();

View File

@ -207,7 +207,7 @@ class GridFieldDetailFormTest extends FunctionalTest
);
$this->assertFalse($response->isError());
$person = Person::get()->sort('FirstName')->First();
$person = $this->objFromFixture(Person::class, 'jane');
$favouriteGroup = $person->FavouriteGroups()->first();
$this->assertInstanceOf(PeopleGroup::class, $favouriteGroup);
@ -248,7 +248,7 @@ class GridFieldDetailFormTest extends FunctionalTest
]
);
$this->assertFalse($response->isError());
$person = Person::get()->sort('FirstName')->First();
$person = $this->objFromFixture(Person::class, 'jane');
$category = $person->Categories()->filter(['Name' => 'Updated Category'])->First();
$this->assertEquals(
[
@ -271,7 +271,7 @@ class GridFieldDetailFormTest extends FunctionalTest
);
$this->assertFalse($response->isError());
$person = Person::get()->sort('FirstName')->First();
$person = $this->objFromFixture(Person::class, 'jane');
$category = $person->Categories()->filter(['Name' => 'Updated Category'])->First();
$this->assertEquals(
[
@ -405,4 +405,34 @@ class GridFieldDetailFormTest extends FunctionalTest
$this->assertEquals($group->Name, (string) $title[0]);
$this->assertEquals($group->ID, (string) $id[0]['value']);
}
public function testRedirectMissingRecords()
{
$origAutoFollow = $this->autoFollowRedirection;
$this->autoFollowRedirection = false;
// GridField is filtered people in "My Group", which does't include "jack"
$included = $this->objFromFixture(Person::class, 'joe');
$excluded = $this->objFromFixture(Person::class, 'jack');
$response = $this->get(sprintf(
'GridFieldDetailFormTest_Controller/Form/field/testfield/item/%d/edit',
$included->ID
));
$this->assertFalse(
$response->isRedirect(),
'Existing records are not redirected'
);
$response = $this->get(sprintf(
'GridFieldDetailFormTest_Controller/Form/field/testfield/item/%d/edit',
$excluded->ID
));
$this->assertTrue(
$response->isRedirect(),
'Non-existing records are redirected'
);
$this->autoFollowRedirection = $origAutoFollow;
}
}

View File

@ -5,6 +5,9 @@ SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Person:
jane:
FirstName: Jane
Surname: Doe
jack:
FirstName: Jack
Surname: Doe
SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PeopleGroup:
group:

View File

@ -31,7 +31,7 @@ class CategoryController extends Controller implements TestOnly
public function Form()
{
// GridField lists categories for a specific person
$person = Person::get()->sort('FirstName')->First();
$person = Person::get()->filter('FirstName', 'Jane')->First();
$detailFields = singleton(Category::class)->getCMSFields();
$detailFields->addFieldsToTab(
'Root.Main',

View File

@ -69,4 +69,9 @@ class Person extends DataObject implements TestOnly
]
);
}
public function CMSEditLink()
{
return sprintf('my-admin/%d', $this->ID);
}
}

View File

@ -48,7 +48,9 @@ class TestController extends Controller implements TestOnly
$field->getConfig()->addComponent(new GridFieldViewButton());
$field->getConfig()->addComponent(new GridFieldEditButton());
/** @skipUpgrade */
$field->getConfig()->addComponent($gridFieldForm = new GridFieldDetailForm($this, 'Form'));
$gridFieldForm = new GridFieldDetailForm($this, 'Form');
$gridFieldForm->setRedirectMissingRecords(true);
$field->getConfig()->addComponent($gridFieldForm);
$field->getConfig()->addComponent(new GridFieldEditButton());
/** @skipUpgrade */
return new Form($this, 'Form', new FieldList($field), new FieldList());