NEW Make most GridField components work with arbitrary data

This commit is contained in:
Guy Sartorelli 2023-11-14 15:39:20 +13:00
parent 5838772b19
commit 3d64eac129
No known key found for this signature in database
GPG Key ID: F313E3B9504D496A
19 changed files with 234 additions and 116 deletions

View File

@ -12,7 +12,6 @@ use SilverStripe\Control\RequestHandler;
use SilverStripe\Control\Session; use SilverStripe\Control\Session;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\ValidationResult; use SilverStripe\ORM\ValidationResult;
@ -136,7 +135,7 @@ class Form extends ViewableData implements HasRequestHandler
/** /**
* Populated by {@link loadDataFrom()}. * Populated by {@link loadDataFrom()}.
* *
* @var DataObject|null * @var ViewableData|null
*/ */
protected $record; protected $record;
@ -1223,10 +1222,10 @@ class Form extends ViewableData implements HasRequestHandler
} }
/** /**
* Returns the DataObject that has given this form its data * Returns the record that has given this form its data
* through {@link loadDataFrom()}. * through {@link loadDataFrom()}.
* *
* @return DataObject * @return ViewableData
*/ */
public function getRecord() public function getRecord()
{ {
@ -1285,7 +1284,7 @@ class Form extends ViewableData implements HasRequestHandler
const MERGE_AS_SUBMITTED_VALUE = 0b1000; const MERGE_AS_SUBMITTED_VALUE = 0b1000;
/** /**
* Load data from the given DataObject or array. * Load data from the given record or array.
* *
* It will call $object->MyField to get the value of MyField. * It will call $object->MyField to get the value of MyField.
* If you passed an array, it will call $object[MyField]. * If you passed an array, it will call $object[MyField].
@ -1306,7 +1305,7 @@ class Form extends ViewableData implements HasRequestHandler
* @uses FormField::setSubmittedValue() * @uses FormField::setSubmittedValue()
* @uses FormField::setValue() * @uses FormField::setValue()
* *
* @param array|DataObject $data * @param array|ViewableData $data
* @param int $mergeStrategy * @param int $mergeStrategy
* For every field, {@link $data} is interrogated whether it contains a relevant property/key, and * For every field, {@link $data} is interrogated whether it contains a relevant property/key, and
* what that property/key's value is. * what that property/key's value is.
@ -1351,7 +1350,7 @@ class Form extends ViewableData implements HasRequestHandler
// If an object is passed, save it for historical reference through {@link getRecord()} // If an object is passed, save it for historical reference through {@link getRecord()}
// Also use this to determine if we are loading a submitted form, or loading // Also use this to determine if we are loading a submitted form, or loading
// from a dataobject // from a record
$submitted = true; $submitted = true;
if (is_object($data)) { if (is_object($data)) {
$this->record = $data; $this->record = $data;
@ -1480,7 +1479,7 @@ class Form extends ViewableData implements HasRequestHandler
* Save the contents of this form into the given data object. * Save the contents of this form into the given data object.
* It will make use of setCastedField() to do this. * It will make use of setCastedField() to do this.
* *
* @param DataObjectInterface $dataObject The object to save data into * @param ViewableData&DataObjectInterface $dataObject The object to save data into
* @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a * @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a
* form that has some fields that save to one object, and some that save to another. * form that has some fields that save to one object, and some that save to another.
*/ */
@ -1523,7 +1522,7 @@ class Form extends ViewableData implements HasRequestHandler
* {@link FieldList->dataFields()}, which filters out * {@link FieldList->dataFields()}, which filters out
* any form-specific data like form-actions. * any form-specific data like form-actions.
* Calls {@link FormField->dataValue()} on each field, * Calls {@link FormField->dataValue()} on each field,
* which returns a value suitable for insertion into a DataObject * which returns a value suitable for insertion into a record
* property. * property.
* *
* @return array * @return array

View File

@ -8,13 +8,13 @@ use SilverStripe\Control\Controller;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\ValidationResult; use SilverStripe\ORM\ValidationResult;
use SilverStripe\View\AttributesHTML; use SilverStripe\View\AttributesHTML;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\ViewableData;
/** /**
* Represents a field in a form. * Represents a field in a form.
@ -454,11 +454,11 @@ class FormField extends RequestHandler
} }
/** /**
* Method to save this form field into the given {@link DataObject}. * Method to save this form field into the given record.
* *
* By default, makes use of $this->dataValue() * By default, makes use of $this->dataValue()
* *
* @param DataObject|DataObjectInterface $record DataObject to save data into * @param ViewableData|DataObjectInterface $record Record to save data into
*/ */
public function saveInto(DataObjectInterface $record) public function saveInto(DataObjectInterface $record)
{ {
@ -697,7 +697,7 @@ class FormField extends RequestHandler
* or a submitted form value they should override setSubmittedValue() instead. * or a submitted form value they should override setSubmittedValue() instead.
* *
* @param mixed $value Either the parent object, or array of source data being loaded * @param mixed $value Either the parent object, or array of source data being loaded
* @param array|DataObject $data {@see Form::loadDataFrom} * @param array|ViewableData $data {@see Form::loadDataFrom}
* @return $this * @return $this
*/ */
public function setValue($value, $data = null) public function setValue($value, $data = null)
@ -712,7 +712,7 @@ class FormField extends RequestHandler
* data formats. * data formats.
* *
* @param mixed $value * @param mixed $value
* @param array|DataObject $data * @param array|ViewableData $data
* @return $this * @return $this
*/ */
public function setSubmittedValue($value, $data = null) public function setSubmittedValue($value, $data = null)

View File

@ -20,11 +20,14 @@ use SilverStripe\Forms\GridField\FormAction\SessionStore;
use SilverStripe\Forms\GridField\FormAction\StateStore; 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\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\Filterable;
use SilverStripe\ORM\Limitable;
use SilverStripe\ORM\Sortable;
use SilverStripe\ORM\SS_List; use SilverStripe\ORM\SS_List;
use SilverStripe\View\HTML; use SilverStripe\View\HTML;
use SilverStripe\View\ViewableData;
/** /**
* Displays a {@link SS_List} in a grid format. * Displays a {@link SS_List} in a grid format.
@ -83,12 +86,12 @@ class GridField extends FormField
/** /**
* Data source. * Data source.
* *
* @var SS_List * @var SS_List&Filterable&Sortable&Limitable
*/ */
protected $list = null; protected $list = null;
/** /**
* Class name of the DataObject that the GridField will display. * Class name of the records that the GridField will display.
* *
* Defaults to the value of $this->list->dataClass. * Defaults to the value of $this->list->dataClass.
* *
@ -205,7 +208,7 @@ class GridField extends FormField
} }
/** /**
* Returns a data class that is a DataObject type that this GridField should look like. * Returns the class name of the record type that this GridField should contain.
* *
* @return string * @return string
* *
@ -374,7 +377,7 @@ class GridField extends FormField
/** /**
* Set the data source. * Set the data source.
* *
* @param SS_List $list * @param SS_List&Filterable&Sortable&Limitable $list
* *
* @return $this * @return $this
*/ */
@ -388,7 +391,7 @@ class GridField extends FormField
/** /**
* Get the data source. * Get the data source.
* *
* @return SS_List * @return SS_List&Filterable&Sortable&Limitable
*/ */
public function getList() public function getList()
{ {
@ -398,7 +401,7 @@ class GridField extends FormField
/** /**
* Get the data source after applying every {@link GridField_DataManipulator} to it. * Get the data source after applying every {@link GridField_DataManipulator} to it.
* *
* @return SS_List * @return SS_List&Filterable&Sortable&Limitable
*/ */
public function getManipulatedList() public function getManipulatedList()
{ {
@ -461,7 +464,7 @@ class GridField extends FormField
if (($request instanceof NullHTTPRequest) && Controller::has_curr()) { if (($request instanceof NullHTTPRequest) && Controller::has_curr()) {
$request = Controller::curr()->getRequest(); $request = Controller::curr()->getRequest();
} }
$stateStr = $this->getStateManager()->getStateFromRequest($this, $request); $stateStr = $this->getStateManager()->getStateFromRequest($this, $request);
if ($stateStr) { if ($stateStr) {
$oldState = $this->getState(false); $oldState = $this->getState(false);
@ -744,7 +747,7 @@ class GridField extends FormField
/** /**
* @param int $total * @param int $total
* @param int $index * @param int $index
* @param DataObject $record * @param ViewableData $record
* @param array $attributes * @param array $attributes
* @param string $content * @param string $content
* *
@ -762,7 +765,7 @@ class GridField extends FormField
/** /**
* @param int $total * @param int $total
* @param int $index * @param int $index
* @param DataObject $record * @param ViewableData $record
* @param array $attributes * @param array $attributes
* @param string $content * @param string $content
* *
@ -780,7 +783,7 @@ class GridField extends FormField
/** /**
* @param int $total * @param int $total
* @param int $index * @param int $index
* @param DataObject $record * @param ViewableData $record
* *
* @return array * @return array
*/ */
@ -798,7 +801,7 @@ class GridField extends FormField
/** /**
* @param int $total * @param int $total
* @param int $index * @param int $index
* @param DataObject $record * @param ViewableData $record
* *
* @return array * @return array
*/ */
@ -869,7 +872,7 @@ class GridField extends FormField
/** /**
* Get the value from a column. * Get the value from a column.
* *
* @param DataObject $record * @param ViewableData $record
* @param string $column * @param string $column
* *
* @return string * @return string
@ -922,7 +925,7 @@ class GridField extends FormField
* Use of this method ensures that any special rules around the data for this gridfield are * Use of this method ensures that any special rules around the data for this gridfield are
* followed. * followed.
* *
* @param DataObject $record * @param ViewableData $record
* @param string $fieldName * @param string $fieldName
* *
* @return mixed * @return mixed
@ -949,7 +952,7 @@ class GridField extends FormField
/** /**
* Get extra columns attributes used as HTML attributes. * Get extra columns attributes used as HTML attributes.
* *
* @param DataObject $record * @param ViewableData $record
* @param string $column * @param string $column
* *
* @return array * @return array

View File

@ -2,7 +2,9 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use LogicException;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Core\ClassInfo;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\RelationList; use SilverStripe\ORM\RelationList;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
@ -12,8 +14,7 @@ use SilverStripe\View\SSViewer;
* This component provides a button for opening the add new form provided by * This component provides a button for opening the add new form provided by
* {@link GridFieldDetailForm}. * {@link GridFieldDetailForm}.
* *
* Only returns a button if {@link DataObject->canCreate()} for this record * Only returns a button if canCreate() for this record returns true.
* returns true.
*/ */
class GridFieldAddNewButton extends AbstractGridFieldComponent implements GridField_HTMLProvider class GridFieldAddNewButton extends AbstractGridFieldComponent implements GridField_HTMLProvider
{ {
@ -36,7 +37,16 @@ class GridFieldAddNewButton extends AbstractGridFieldComponent implements GridFi
public function getHTMLFragments($gridField) public function getHTMLFragments($gridField)
{ {
$singleton = singleton($gridField->getModelClass()); $modelClass = $gridField->getModelClass();
$singleton = singleton($modelClass);
if (!$singleton->hasMethod('canCreate')) {
throw new LogicException(
__CLASS__ . ' cannot be used with models that do not implement canCreate().'
. " Remove this component from your GridField or implement canCreate() on $modelClass"
);
}
$context = []; $context = [];
if ($gridField->getList() instanceof RelationList) { if ($gridField->getList() instanceof RelationList) {
$record = $gridField->getForm()->getRecord(); $record = $gridField->getForm()->getRecord();
@ -51,7 +61,7 @@ class GridFieldAddNewButton extends AbstractGridFieldComponent implements GridFi
if (!$this->buttonName) { if (!$this->buttonName) {
// provide a default button name, can be changed by calling {@link setButtonName()} on this component // provide a default button name, can be changed by calling {@link setButtonName()} on this component
$objectName = $singleton->i18n_singular_name(); $objectName = $singleton->hasMethod('i18n_singular_name') ? $singleton->i18n_singular_name() : ClassInfo::shortName($singleton);
$this->buttonName = _t('SilverStripe\\Forms\\GridField\\GridField.Add', 'Add {name}', ['name' => $objectName]); $this->buttonName = _t('SilverStripe\\Forms\\GridField\\GridField.Add', 'Add {name}', ['name' => $objectName]);
} }

View File

@ -4,7 +4,8 @@ namespace SilverStripe\Forms\GridField;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\ORM\DataObject; use LogicException;
use SilverStripe\View\ViewableData;
/** /**
* @see GridField * @see GridField
@ -87,7 +88,15 @@ class GridFieldDataColumns extends AbstractGridFieldComponent implements GridFie
public function getDisplayFields($gridField) public function getDisplayFields($gridField)
{ {
if (!$this->displayFields) { if (!$this->displayFields) {
return singleton($gridField->getModelClass())->summaryFields(); $modelClass = $gridField->getModelClass();
$singleton = singleton($modelClass);
if (!$singleton->hasMethod('summaryFields')) {
throw new LogicException(
'Cannot dynamically determine columns. Pass the column names to setDisplayFields()'
. " or implement a summaryFields() method on $modelClass"
);
}
return $singleton->summaryFields();
} }
return $this->displayFields; return $this->displayFields;
} }
@ -146,7 +155,7 @@ class GridFieldDataColumns extends AbstractGridFieldComponent implements GridFie
* HTML for the column, content of the <td> element. * HTML for the column, content of the <td> element.
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record Record displayed in this row * @param ViewableData $record Record displayed in this row
* @param string $columnName * @param string $columnName
* @return string HTML for the column. Return NULL to skip. * @return string HTML for the column. Return NULL to skip.
*/ */
@ -180,7 +189,7 @@ class GridFieldDataColumns extends AbstractGridFieldComponent implements GridFie
* Attributes for the element containing the content returned by {@link getColumnContent()}. * Attributes for the element containing the content returned by {@link getColumnContent()}.
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record displayed in this row * @param ViewableData $record displayed in this row
* @param string $columnName * @param string $columnName
* @return array * @return array
*/ */
@ -216,7 +225,7 @@ class GridFieldDataColumns extends AbstractGridFieldComponent implements GridFie
/** /**
* Translate a Object.RelationName.ColumnName $columnName into the value that ColumnName returns * Translate a Object.RelationName.ColumnName $columnName into the value that ColumnName returns
* *
* @param DataObject $record * @param ViewableData $record
* @param string $columnName * @param string $columnName
* @return string|null - returns null if it could not found a value * @return string|null - returns null if it could not found a value
*/ */
@ -269,7 +278,7 @@ class GridFieldDataColumns extends AbstractGridFieldComponent implements GridFie
/** /**
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $item * @param ViewableData $item
* @param string $fieldName * @param string $fieldName
* @param string $value * @param string $value
* @return string * @return string

View File

@ -2,9 +2,12 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use LogicException;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\ValidationException; use SilverStripe\ORM\ValidationException;
use SilverStripe\View\ViewableData;
/** /**
* This class is a {@link GridField} component that adds a delete action for * This class is a {@link GridField} component that adds a delete action for
@ -72,13 +75,12 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
/** /**
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param DataObjectInterface&ViewableData $record
* @param string $columnName * @param string $columnName
* @return string|null the attribles for the action * @return string|null the attribles for the action
*/ */
public function getExtraData($gridField, $record, $columnName) public function getExtraData($gridField, $record, $columnName)
{ {
$field = $this->getRemoveAction($gridField, $record, $columnName); $field = $this->getRemoveAction($gridField, $record, $columnName);
if ($field) { if ($field) {
@ -105,7 +107,7 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
* Return any special attributes that will be used for FormField::create_tag() * Return any special attributes that will be used for FormField::create_tag()
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param DataObjectInterface&ViewableData $record
* @param string $columnName * @param string $columnName
* @return array * @return array
*/ */
@ -153,7 +155,7 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
/** /**
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param DataObjectInterface&ViewableData $record
* @param string $columnName * @param string $columnName
* @return string|null the HTML for the column * @return string|null the HTML for the column
*/ */
@ -179,29 +181,39 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
*/ */
public function handleAction(GridField $gridField, $actionName, $arguments, $data) public function handleAction(GridField $gridField, $actionName, $arguments, $data)
{ {
$list = $gridField->getList();
if ($actionName == 'deleterecord' || $actionName == 'unlinkrelation') { if ($actionName == 'deleterecord' || $actionName == 'unlinkrelation') {
/** @var DataObject $item */ /** @var DataObjectInterface&ViewableData $item */
$item = $gridField->getList()->byID($arguments['RecordID']); $item = $list->byID($arguments['RecordID']);
if (!$item) { if (!$item) {
return; return;
} }
if ($actionName == 'deleterecord') { if ($actionName == 'deleterecord') {
$this->checkForRequiredMethod($item, 'canDelete');
if (!$item->canDelete()) { if (!$item->canDelete()) {
throw new ValidationException( throw new ValidationException(
_t(__CLASS__ . '.DeletePermissionsFailure', "No delete permissions") _t(__CLASS__ . '.DeletePermissionsFailure', "No delete permissions")
); );
} }
if (!($list instanceof DataList)) {
// We need to make sure to exclude the item since the list items have already been determined.
// This must happen before deletion while the item still has its ID set.
$gridField->setList($list->exclude(['ID' => $item->ID]));
}
$item->delete(); $item->delete();
} else { } else {
$this->checkForRequiredMethod($item, 'canEdit');
if (!$item->canEdit()) { if (!$item->canEdit()) {
throw new ValidationException( throw new ValidationException(
_t(__CLASS__ . '.EditPermissionsFailure', "No permission to unlink record") _t(__CLASS__ . '.EditPermissionsFailure', "No permission to unlink record")
); );
} }
$gridField->getList()->remove($item); $list->remove($item);
} }
} }
} }
@ -209,16 +221,19 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
/** /**
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param DataObjectInterface&ViewableData $record
* @param string $columnName * @param string $columnName
* @return GridField_FormAction|null * @return GridField_FormAction|null
*/ */
private function getRemoveAction($gridField, $record, $columnName) private function getRemoveAction($gridField, $record, $columnName)
{ {
if ($this->getRemoveRelation()) { if ($this->getRemoveRelation()) {
$this->checkForRequiredMethod($record, 'canEdit');
if (!$record->canEdit()) { if (!$record->canEdit()) {
return null; return null;
} }
$title = _t(__CLASS__ . '.UnlinkRelation', "Unlink"); $title = _t(__CLASS__ . '.UnlinkRelation', "Unlink");
$field = GridField_FormAction::create( $field = GridField_FormAction::create(
@ -233,9 +248,12 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
->setDescription($title) ->setDescription($title)
->setAttribute('aria-label', $title); ->setAttribute('aria-label', $title);
} else { } else {
$this->checkForRequiredMethod($record, 'canDelete');
if (!$record->canDelete()) { if (!$record->canDelete()) {
return null; return null;
} }
$title = _t(__CLASS__ . '.Delete', "Delete"); $title = _t(__CLASS__ . '.Delete', "Delete");
$field = GridField_FormAction::create( $field = GridField_FormAction::create(
@ -274,4 +292,20 @@ class GridFieldDeleteAction extends AbstractGridFieldComponent implements GridFi
$this->removeRelation = (bool) $removeRelation; $this->removeRelation = (bool) $removeRelation;
return $this; return $this;
} }
/**
* Checks if a required method exists - and if not, throws an exception.
*
* @throws LogicException if the required method doesn't exist
*/
private function checkForRequiredMethod($record, string $method): void
{
if (!$record->hasMethod($method)) {
$modelClass = get_class($record);
throw new LogicException(
__CLASS__ . " cannot be used with models that don't implement {$method}()."
. " Remove this component from your GridField or implement {$method}() on $modelClass"
);
}
}
} }

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use Closure; use Closure;
use LogicException;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
@ -13,10 +14,11 @@ use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Extensible; use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FieldsValidator;
use SilverStripe\Forms\Validator; use SilverStripe\Forms\Validator;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Filterable; use SilverStripe\View\ViewableData;
/** /**
* Provides view and edit forms at GridField-specific URLs. * Provides view and edit forms at GridField-specific URLs.
@ -62,7 +64,7 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
protected $validator; protected $validator;
/** /**
* @var FieldList Falls back to {@link DataObject->getCMSFields()} if not defined. * @var FieldList Falls back to {@link $record->getCMSFields()} if not defined.
*/ */
protected $fields; protected $fields;
@ -143,28 +145,31 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
// if no validator has been set on the GridField then use the Validators from the record. // if no validator has been set on the GridField then use the Validators from the record.
if (!$this->getValidator()) { if (!$this->getValidator()) {
$this->setValidator($record->getCMSCompositeValidator()); if ($record->hasMethod('getCMSCompositeValidator')) {
$validator = $record->getCMSCompositeValidator();
} else {
$validator = FieldsValidator::create();
}
$this->setValidator($validator);
} }
return $handler->handleRequest($request); return $handler->handleRequest($request);
} }
/** protected function getRecordFromRequest(GridField $gridField, HTTPRequest $request): ?ViewableData
* @param GridField $gridField
* @param HTTPRequest $request
* @return DataObject|null
*/
protected function getRecordFromRequest(GridField $gridField, HTTPRequest $request): ?DataObject
{ {
/** @var DataObject $record */ /** @var ViewableData $record */
if (is_numeric($request->param('ID'))) { if (is_numeric($request->param('ID'))) {
/** @var Filterable $dataList */
$dataList = $gridField->getList(); $dataList = $gridField->getList();
$record = $dataList->byID($request->param('ID')); $record = $dataList->byID($request->param('ID'));
} else { } else {
$record = Injector::inst()->create($gridField->getModelClass()); $record = Injector::inst()->create($gridField->getModelClass());
} }
if ($record && !$record->hasField('ID')) {
throw new LogicException(get_class($record) . ' must have an ID field.');
}
return $record; return $record;
} }
@ -174,7 +179,7 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
* This only works when the list passed to the GridField is a {@link DataList}. * This only works when the list passed to the GridField is a {@link DataList}.
* *
* @param $gridField The current GridField * @param $gridField The current GridField
* @param $id The ID of the DataObject to open * @param $id The ID of the record to open
*/ */
public function getLostRecordRedirection(GridField $gridField, HTTPRequest $request, ?int $id = null): ?string public function getLostRecordRedirection(GridField $gridField, HTTPRequest $request, ?int $id = null): ?string
{ {
@ -216,7 +221,7 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
* Build a request handler for the given record * Build a request handler for the given record
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* @param RequestHandler $requestHandler * @param RequestHandler $requestHandler
* @return GridFieldDetailForm_ItemRequest * @return GridFieldDetailForm_ItemRequest
*/ */
@ -248,7 +253,7 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
} }
/** /**
* @return String * @return string
*/ */
public function getTemplate() public function getTemplate()
{ {
@ -266,7 +271,7 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
} }
/** /**
* @return String * @return string
*/ */
public function getName() public function getName()
{ {
@ -276,8 +281,8 @@ class GridFieldDetailForm extends AbstractGridFieldComponent implements GridFiel
/** /**
* Enable redirection to missing records. * Enable redirection to missing records.
* *
* If a GridField shows a filtered list, and the DataObject is not in the list but exists in the * If a GridField shows a filtered list, and the record is not in the list but exists in the
* database, and the DataObject has a CMSEditLink method, then the system will redirect to the * database, and the record has a CMSEditLink method, then the system will redirect to the
* URL returned by that method. * URL returned by that method.
*/ */
public function setRedirectMissingRecords(bool $redirectMissingRecords): self public function setRedirectMissingRecords(bool $redirectMissingRecords): self

View File

@ -9,6 +9,7 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\CompositeField;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
@ -17,6 +18,7 @@ use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\LiteralField;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\HasManyList; use SilverStripe\ORM\HasManyList;
use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ManyManyList;
@ -28,6 +30,7 @@ use SilverStripe\ORM\ValidationResult;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\HTML; use SilverStripe\View\HTML;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\ViewableData;
class GridFieldDetailForm_ItemRequest extends RequestHandler class GridFieldDetailForm_ItemRequest extends RequestHandler
{ {
@ -64,7 +67,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
protected $component; protected $component;
/** /**
* @var DataObject * @var ViewableData
*/ */
protected $record; protected $record;
@ -96,7 +99,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
* *
* @param GridField $gridField * @param GridField $gridField
* @param GridFieldDetailForm $component * @param GridFieldDetailForm $component
* @param DataObject $record * @param ViewableData&DataObjectInterface $record
* @param RequestHandler $requestHandler * @param RequestHandler $requestHandler
* @param string $popupFormName * @param string $popupFormName
*/ */
@ -125,11 +128,12 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
*/ */
public function view($request) public function view($request)
{ {
if (!$this->record->canView()) { // Assume item can be viewed if canView() isn't implemented
if ($this->record->hasMethod('canView') && !$this->record->canView()) {
$this->httpError(403, _t( $this->httpError(403, _t(
__CLASS__ . '.ViewPermissionsFailure', __CLASS__ . '.ViewPermissionsFailure',
'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"', 'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"',
['ObjectTitle' => $this->record->singular_name()] ['ObjectTitle' => $this->getModelName()]
)); ));
} }
@ -207,17 +211,25 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
} }
} }
if (!$this->record->canView()) { // Assume item can be viewed if canView() isn't implemented
if ($this->record->hasMethod('canView') && !$this->record->canView()) {
$controller = $this->getToplevelController(); $controller = $this->getToplevelController();
return $controller->httpError(403, _t( return $controller->httpError(403, _t(
__CLASS__ . '.ViewPermissionsFailure', __CLASS__ . '.ViewPermissionsFailure',
'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"', 'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"',
['ObjectTitle' => $this->record->singular_name()] ['ObjectTitle' => $this->getModelName()]
)); ));
} }
$fields = $this->component->getFields(); $fields = $this->component->getFields();
if (!$fields) { if (!$fields) {
if (!$this->record->hasMethod('getCMSFields')) {
$modelClass = get_class($this->record);
throw new LogicException(
'Cannot dynamically determine form fields. Pass the fields to GridFieldDetailForm::setFields()'
. " or implement a getCMSFields() method on {$modelClass}"
);
}
$fields = $this->record->getCMSFields(); $fields = $this->record->getCMSFields();
} }
@ -241,15 +253,15 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT); $form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
if ($this->record->ID && !$this->record->canEdit()) { if ($this->record->ID && (!$this->record->hasMethod('canEdit') || !$this->record->canEdit())) {
// Restrict editing of existing records // Restrict editing of existing records
$form->makeReadonly(); $form->makeReadonly();
// Hack to re-enable delete button if user can delete // Hack to re-enable delete button if user can delete
if ($this->record->canDelete()) { if ($this->record->hasMethod('canDelete') && $this->record->canDelete()) {
$form->Actions()->fieldByName('action_doDelete')->setReadonly(false); $form->Actions()->fieldByName('action_doDelete')->setReadonly(false);
} }
} elseif (!$this->record->ID } elseif (!$this->record->ID
&& !$this->record->canCreate(null, $this->getCreateContext()) && (!$this->record->hasMethod('canCreate') || !$this->record->canCreate(null, $this->getCreateContext()))
) { ) {
// Restrict creation of new records // Restrict creation of new records
$form->makeReadonly(); $form->makeReadonly();
@ -359,7 +371,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$rightGroup->push($previousAndNextGroup); $rightGroup->push($previousAndNextGroup);
if ($component && $component->getShowAdd() && $this->record->canCreate()) { if ($component && $component->getShowAdd() && $this->record->hasMethod('canCreate') && $this->record->canCreate()) {
$rightGroup->push( $rightGroup->push(
LiteralField::create( LiteralField::create(
'new-record', 'new-record',
@ -378,7 +390,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
} }
/** /**
* Build the set of form field actions for this DataObject * Build the set of form field actions for the record being handled
* *
* @return FieldList * @return FieldList
*/ */
@ -391,8 +403,12 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$majorActions->setFieldHolderTemplate(get_class($majorActions) . '_holder_buttongroup'); $majorActions->setFieldHolderTemplate(get_class($majorActions) . '_holder_buttongroup');
$actions->push($majorActions); $actions->push($majorActions);
if ($this->record->ID !== 0) { // existing record if ($this->record->ID !== null && $this->record->ID !== 0) { // existing record
if ($this->record->canEdit()) { if ($this->record->hasMethod('canEdit') && $this->record->canEdit()) {
if (!($this->record instanceof DataObjectInterface)) {
throw new LogicException(get_class($this->record) . ' must implement ' . DataObjectInterface::class);
}
$noChangesClasses = 'btn-outline-primary font-icon-tick'; $noChangesClasses = 'btn-outline-primary font-icon-tick';
$majorActions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save')) $majorActions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save'))
->addExtraClass($noChangesClasses) ->addExtraClass($noChangesClasses)
@ -402,7 +418,10 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
->setAttribute('data-text-alternate', _t('SilverStripe\\CMS\\Controllers\\CMSMain.SAVEDRAFT', 'Save'))); ->setAttribute('data-text-alternate', _t('SilverStripe\\CMS\\Controllers\\CMSMain.SAVEDRAFT', 'Save')));
} }
if ($this->record->canDelete()) { if ($this->record->hasMethod('canDelete') && $this->record->canDelete()) {
if (!($this->record instanceof DataObjectInterface)) {
throw new LogicException(get_class($this->record) . ' must implement ' . DataObjectInterface::class);
}
$actions->insertAfter('MajorActions', FormAction::create('doDelete', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Delete', 'Delete')) $actions->insertAfter('MajorActions', FormAction::create('doDelete', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Delete', 'Delete'))
->setUseButtonTag(true) ->setUseButtonTag(true)
->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'));
@ -482,9 +501,9 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
* {@see Form::saveInto()} * {@see Form::saveInto()}
* *
* Handles detection of falsey values explicitly saved into the * Handles detection of falsey values explicitly saved into the
* DataObject by formfields * record by formfields
* *
* @param DataObject $record * @param ViewableData $record
* @param SS_List $list * @param SS_List $list
* @return array List of data to write to the relation * @return array List of data to write to the relation
*/ */
@ -510,11 +529,11 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$isNewRecord = $this->record->ID == 0; $isNewRecord = $this->record->ID == 0;
// Check permission // Check permission
if (!$this->record->canEdit()) { if (!$this->record->hasMethod('canEdit') || !$this->record->canEdit()) {
$this->httpError(403, _t( $this->httpError(403, _t(
__CLASS__ . '.EditPermissionsFailure', __CLASS__ . '.EditPermissionsFailure',
'It seems you don\'t have the necessary permissions to edit "{ObjectTitle}"', 'It seems you don\'t have the necessary permissions to edit "{ObjectTitle}"',
['ObjectTitle' => $this->record->singular_name()] ['ObjectTitle' => $this->getModelName()]
)); ));
return null; return null;
} }
@ -529,7 +548,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Saved', 'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Saved',
'Saved {name} {link}', 'Saved {name} {link}',
[ [
'name' => $this->record->i18n_singular_name(), 'name' => $this->getModelName(),
'link' => $link 'link' => $link
] ]
); );
@ -736,12 +755,12 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
} }
/** /**
* Loads the given form data into the underlying dataobject and relation * Loads the given form data into the underlying record and relation
* *
* @param array $data * @param array $data
* @param Form $form * @param Form $form
* @throws ValidationException On error * @throws ValidationException On error
* @return DataObject Saved record * @return ViewableData&DataObjectInterface Saved record
*/ */
protected function saveFormIntoRecord($data, $form) protected function saveFormIntoRecord($data, $form)
{ {
@ -758,7 +777,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$this->record = $this->record->newClassInstance($newClassName); $this->record = $this->record->newClassInstance($newClassName);
} }
// Save form and any extra saved data into this dataobject. // Save form and any extra saved data into this record.
// Set writeComponents = true to write has-one relations / join records // Set writeComponents = true to write has-one relations / join records
$form->saveInto($this->record); $form->saveInto($this->record);
// https://github.com/silverstripe/silverstripe-assets/issues/365 // https://github.com/silverstripe/silverstripe-assets/issues/365
@ -780,7 +799,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
public function doDelete($data, $form) public function doDelete($data, $form)
{ {
$title = $this->record->Title; $title = $this->record->Title;
if (!$this->record->canDelete()) { if (!$this->record->hasMethod('canDelete') || !$this->record->canDelete()) {
throw new ValidationException( throw new ValidationException(
_t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.DeletePermissionsFailure', "No delete permissions") _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.DeletePermissionsFailure', "No delete permissions")
); );
@ -791,7 +810,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Deleted', 'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Deleted',
'Deleted {type} "{name}"', 'Deleted {type} "{name}"',
[ [
'type' => $this->record->i18n_singular_name(), 'type' => $this->getModelName(),
'name' => htmlspecialchars($title ?? '', ENT_QUOTES) 'name' => htmlspecialchars($title ?? '', ENT_QUOTES)
] ]
); );
@ -862,7 +881,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
} }
/** /**
* @return DataObject * @return ViewableData
*/ */
public function getRecord() public function getRecord()
{ {
@ -898,7 +917,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
])); ]));
} else { } else {
$items->push(ArrayData::create([ $items->push(ArrayData::create([
'Title' => _t('SilverStripe\\Forms\\GridField\\GridField.NewRecord', 'New {type}', ['type' => $this->record->i18n_singular_name()]), 'Title' => _t('SilverStripe\\Forms\\GridField\\GridField.NewRecord', 'New {type}', ['type' => $this->getModelName()]),
'Link' => false 'Link' => false
])); ]));
} }
@ -912,4 +931,12 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
$this->extend('updateBreadcrumbs', $items); $this->extend('updateBreadcrumbs', $items);
return $items; return $items;
} }
private function getModelName(): string
{
if ($this->record->hasMethod('i18n_singular_name')) {
return $this->record->i18n_singular_name();
}
return ClassInfo::shortName($this->record);
}
} }

View File

@ -3,9 +3,9 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\ORM\DataObject;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\ViewableData;
/** /**
* Provides the entry point to editing a single record presented by the * Provides the entry point to editing a single record presented by the
@ -91,7 +91,7 @@ class GridFieldEditButton extends AbstractGridFieldComponent implements GridFiel
* Return any special attributes that will be used for FormField::create_tag() * Return any special attributes that will be used for FormField::create_tag()
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* @param string $columnName * @param string $columnName
* @return array * @return array
*/ */
@ -139,7 +139,7 @@ class GridFieldEditButton extends AbstractGridFieldComponent implements GridFiel
/** /**
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* @param string $columnName * @param string $columnName
* @return string The HTML for the column * @return string The HTML for the column
*/ */

View File

@ -3,12 +3,13 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use League\Csv\Writer; use League\Csv\Writer;
use LogicException;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ViewableData;
/** /**
* Adds an "Export list" button to the bottom of a {@link GridField}. * Adds an "Export list" button to the bottom of a {@link GridField}.
@ -155,7 +156,15 @@ class GridFieldExportButton extends AbstractGridFieldComponent implements GridFi
return $dataCols->getDisplayFields($gridField); return $dataCols->getDisplayFields($gridField);
} }
return DataObject::singleton($gridField->getModelClass())->summaryFields(); $modelClass = $gridField->getModelClass();
$singleton = singleton($modelClass);
if (!$singleton->hasMethod('summaryFields')) {
throw new LogicException(
'Cannot dynamically determine columns. Add a GridFieldDataColumns component to your GridField'
. " or implement a summaryFields() method on $modelClass"
);
}
return $singleton->summaryFields();
} }
/** /**
@ -225,8 +234,9 @@ class GridFieldExportButton extends AbstractGridFieldComponent implements GridFi
// Remove limit as the list may be paginated, we want the full list for the export // Remove limit as the list may be paginated, we want the full list for the export
$items = $items->limit(null); $items = $items->limit(null);
/** @var DataObject $item */ /** @var ViewableData $item */
foreach ($items as $item) { foreach ($items as $item) {
// Assume item can be viewed if canView() isn't implemented
if (!$item->hasMethod('canView') || $item->canView()) { if (!$item->hasMethod('canView') || $item->canView()) {
$columnData = []; $columnData = [];

View File

@ -256,7 +256,15 @@ class GridFieldFilterHeader extends AbstractGridFieldComponent implements GridFi
public function getSearchContext(GridField $gridField) public function getSearchContext(GridField $gridField)
{ {
if (!$this->searchContext) { if (!$this->searchContext) {
$this->searchContext = singleton($gridField->getModelClass())->getDefaultSearchContext(); $modelClass = $gridField->getModelClass();
$singleton = singleton($modelClass);
if (!$singleton->hasMethod('getDefaultSearchContext')) {
throw new LogicException(
'Cannot dynamically instantiate SearchContext. Pass the SearchContext to setSearchContext()'
. " or implement a getDefaultSearchContext() method on $modelClass"
);
}
$this->searchContext = $singleton->getDefaultSearchContext();
} }
return $this->searchContext; return $this->searchContext;

View File

@ -2,16 +2,17 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use LogicException;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Extensible; use SilverStripe\Core\Extensible;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements; use SilverStripe\View\Requirements;
use SilverStripe\View\ViewableData;
/** /**
* Adds an "Print" button to the bottom or top of a GridField. * Adds an "Print" button to the bottom or top of a GridField.
@ -161,7 +162,15 @@ class GridFieldPrintButton extends AbstractGridFieldComponent implements GridFie
return $dataCols->getDisplayFields($gridField); return $dataCols->getDisplayFields($gridField);
} }
return DataObject::singleton($gridField->getModelClass())->summaryFields(); $modelClass = $gridField->getModelClass();
$singleton = singleton($modelClass);
if (!$singleton->hasMethod('summaryFields')) {
throw new LogicException(
'Cannot dynamically determine columns. Add a GridFieldDataColumns component to your GridField'
. " or implement a summaryFields() method on $modelClass"
);
}
return $singleton->summaryFields();
} }
/** /**
@ -226,8 +235,9 @@ class GridFieldPrintButton extends AbstractGridFieldComponent implements GridFie
/** @var GridFieldDataColumns $gridFieldColumnsComponent */ /** @var GridFieldDataColumns $gridFieldColumnsComponent */
$gridFieldColumnsComponent = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class); $gridFieldColumnsComponent = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
/** @var DataObject $item */ /** @var ViewableData $item */
foreach ($items->limit(null) as $item) { foreach ($items->limit(null) as $item) {
// Assume item can be viewed if canView() isn't implemented
if (!$item->hasMethod('canView') || $item->canView()) { if (!$item->hasMethod('canView') || $item->canView()) {
$itemRow = new ArrayList(); $itemRow = new ArrayList();

View File

@ -11,6 +11,7 @@ use SilverStripe\ORM\DataObject;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use LogicException; use LogicException;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
@ -154,7 +155,7 @@ class GridFieldSortableHeader extends AbstractGridFieldComponent implements Grid
if ($tmpItem instanceof SS_List) { if ($tmpItem instanceof SS_List) {
// It's impossible to sort on a HasManyList/ManyManyList // It's impossible to sort on a HasManyList/ManyManyList
break; break;
} elseif ($tmpItem && method_exists($tmpItem, 'hasMethod') && $tmpItem->hasMethod($methodName)) { } elseif ($tmpItem && ClassInfo::hasMethod($tmpItem, $methodName)) {
// The part is a relation name, so get the object/list from it // The part is a relation name, so get the object/list from it
$tmpItem = $tmpItem->$methodName(); $tmpItem = $tmpItem->$methodName();
} elseif ($tmpItem instanceof DataObject } elseif ($tmpItem instanceof DataObject

View File

@ -62,7 +62,8 @@ class GridFieldViewButton extends AbstractGridFieldComponent implements GridFiel
public function getColumnContent($field, $record, $col) public function getColumnContent($field, $record, $col)
{ {
if (!$record->canView()) { // Assume item can be viewed if canView() isn't implemented
if ($record->hasMethod('canView') && !$record->canView()) {
return null; return null;
} }
$data = new ArrayData([ $data = new ArrayData([

View File

@ -2,7 +2,7 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\ORM\DataObject; use SilverStripe\View\ViewableData;
/** /**
* GridField action menu item interface, this provides data so the action * GridField action menu item interface, this provides data so the action
@ -21,7 +21,7 @@ interface GridField_ActionMenuItem extends GridFieldComponent
* @see {@link GridField_ActionMenu->getColumnContent()} * @see {@link GridField_ActionMenu->getColumnContent()}
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* *
* @return string $title * @return string $title
*/ */
@ -33,7 +33,7 @@ interface GridField_ActionMenuItem extends GridFieldComponent
* @see {@link GridField_ActionMenu->getColumnContent()} * @see {@link GridField_ActionMenu->getColumnContent()}
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* *
* @return array $data * @return array $data
*/ */
@ -46,7 +46,7 @@ interface GridField_ActionMenuItem extends GridFieldComponent
* @see {@link GridField_ActionMenu->getColumnContent()} * @see {@link GridField_ActionMenu->getColumnContent()}
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* *
* @return string|null $group * @return string|null $group
*/ */

View File

@ -2,7 +2,7 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\ORM\DataObject; use SilverStripe\View\ViewableData;
/** /**
* Allows GridField_ActionMenuItem to act as a link * Allows GridField_ActionMenuItem to act as a link
@ -15,7 +15,7 @@ interface GridField_ActionMenuLink extends GridField_ActionMenuItem
* @see {@link GridField_ActionMenu->getColumnContent()} * @see {@link GridField_ActionMenu->getColumnContent()}
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record * @param ViewableData $record
* *
* @return string $url * @return string $url
*/ */

View File

@ -34,7 +34,7 @@ interface GridField_ColumnProvider extends GridFieldComponent
* HTML for the column, content of the <td> element. * HTML for the column, content of the <td> element.
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record - Record displayed in this row * @param ViewableData $record - Record displayed in this row
* @param string $columnName * @param string $columnName
* @return string - HTML for the column. Return NULL to skip. * @return string - HTML for the column. Return NULL to skip.
*/ */
@ -44,7 +44,7 @@ interface GridField_ColumnProvider extends GridFieldComponent
* Attributes for the element containing the content returned by {@link getColumnContent()}. * Attributes for the element containing the content returned by {@link getColumnContent()}.
* *
* @param GridField $gridField * @param GridField $gridField
* @param DataObject $record displayed in this row * @param ViewableData $record displayed in this row
* @param string $columnName * @param string $columnName
* @return array * @return array
*/ */

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\View\ViewableData;
/** /**
* A component which is used to handle when a {@link GridField} is saved into * A component which is used to handle when a {@link GridField} is saved into
@ -15,7 +16,7 @@ interface GridField_SaveHandler extends GridFieldComponent
* Called when a grid field is saved - i.e. the form is submitted. * Called when a grid field is saved - i.e. the form is submitted.
* *
* @param GridField $grid * @param GridField $grid
* @param DataObjectInterface $record * @param DataObjectInterface&ViewableData $record
*/ */
public function handleSave(GridField $grid, DataObjectInterface $record); public function handleSave(GridField $grid, DataObjectInterface $record);
} }

View File

@ -105,7 +105,7 @@ class SearchContext
*/ */
public function getSearchFields() public function getSearchFields()
{ {
if ($this->fields->exists()) { if ($this->fields?->exists()) {
return $this->fields; return $this->fields;
} }
@ -443,7 +443,7 @@ class SearchContext
*/ */
public function addField($field) public function addField($field)
{ {
$this->fields->push($field); $this->fields?->push($field);
} }
/** /**
@ -453,7 +453,7 @@ class SearchContext
*/ */
public function removeFieldByName($fieldName) public function removeFieldByName($fieldName)
{ {
$this->fields->removeByName($fieldName); $this->fields?->removeByName($fieldName);
} }
/** /**
@ -500,7 +500,7 @@ class SearchContext
continue; continue;
} }
$field = $this->fields->fieldByName($filter->getFullName()); $field = $this->fields?->fieldByName($filter->getFullName());
if (!$field) { if (!$field) {
continue; continue;
} }