From 038daffd97bcc2fa5352e931772d541076a874f2 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 8 Aug 2024 12:48:29 +1200 Subject: [PATCH] TODO: Break this out into appropriate PRs for CMS 5 vs CMS 6 --- src/Forms/FormScaffolder.php | 65 ++++++++++++++----- src/Forms/Tab.php | 7 +- src/Forms/TabSet.php | 24 +++++++ src/ORM/DataObject.php | 36 ++++++---- .../InheritedPermissionsExtension.php | 17 +++++ 5 files changed, 118 insertions(+), 31 deletions(-) diff --git a/src/Forms/FormScaffolder.php b/src/Forms/FormScaffolder.php index 6ef58cf9021..8e14b97614b 100644 --- a/src/Forms/FormScaffolder.php +++ b/src/Forms/FormScaffolder.php @@ -17,38 +17,49 @@ class FormScaffolder use Injectable; /** - * @var DataObject $obj The object defining the fields to be scaffolded + * The object defining the fields to be scaffolded * through its metadata like $db, $searchable_fields, etc. */ - protected $obj; + protected DataObject $obj; /** - * @var boolean $tabbed Return fields in a tabset, with all main fields in the path "Root.Main", + * Return fields in a tabset, with all main fields in the path "Root.Main", * relation fields in "Root." (if {@link $includeRelations} is enabled). */ - public $tabbed = false; + public bool $tabbed = false; /** - * @var boolean $ajaxSafe + * Only set up the "Root.Main" tab, but skip scaffolding actual FormFields. + * If $tabbed is false, an empty FieldList will be returned. */ - public $ajaxSafe = false; + public bool $tabsOnly = false; /** - * @var array $restrictFields Numeric array of a field name whitelist. - * If left blank, all fields from {@link DataObject->db()} will be included. + * Numeric array of a field name whitelist. + * If left blank, all fields from {@link DataObject->db()} will be included unless explicitly ignored. */ - public $restrictFields; + public array $restrictFields = []; /** - * @var array $fieldClasses Optional mapping of fieldnames to subclasses of {@link FormField}. + * Numeric array of field names and has_one relations to explicitly not scaffold. + */ + public array $ignoreFields = []; + + /** + * Optional mapping of fieldnames to subclasses of {@link FormField}. * By default the scaffolder will determine the field instance by {@link DBField::scaffoldFormField()}. */ - public $fieldClasses; + public array $fieldClasses = []; /** - * @var boolean $includeRelations Include has_one, has_many and many_many relations + * Include has_many and many_many relations */ - public $includeRelations = false; + public bool|array $includeRelations = false; + + /** + * Numeric array of has_many and many_many relations to explicitly not scaffold. + */ + public array $ignoreRelations = []; /** * @param DataObject $obj @@ -76,14 +87,21 @@ public function getFieldList() $mainTab->setTitle(_t(__CLASS__ . '.TABMAIN', 'Main')); } + if ($this->tabsOnly) { + return $fields; + } + // Add logical fields directly specified in db config foreach ($this->obj->config()->get('db') as $fieldName => $fieldType) { // Skip restricted fields - if ($this->restrictFields && !in_array($fieldName, $this->restrictFields ?? [])) { + if (!empty($this->restrictFields) && !in_array($fieldName, $this->restrictFields)) { + continue; + } + if (in_array($fieldName, $this->ignoreFields)) { continue; } - if ($this->fieldClasses && isset($this->fieldClasses[$fieldName])) { + if (isset($this->fieldClasses[$fieldName])) { $fieldClass = $this->fieldClasses[$fieldName]; $fieldObject = new $fieldClass($fieldName); } else { @@ -107,13 +125,16 @@ public function getFieldList() // add has_one relation fields if ($this->obj->hasOne()) { foreach ($this->obj->hasOne() as $relationship => $component) { - if ($this->restrictFields && !in_array($relationship, $this->restrictFields ?? [])) { + if (!empty($this->restrictFields) && !in_array($relationship, $this->restrictFields)) { + continue; + } + if (in_array($relationship, $this->ignoreFields)) { continue; } $fieldName = $component === 'SilverStripe\\ORM\\DataObject' ? $relationship // Polymorphic has_one field is composite, so don't refer to ID subfield : "{$relationship}ID"; - if ($this->fieldClasses && isset($this->fieldClasses[$fieldName])) { + if (isset($this->fieldClasses[$fieldName])) { $fieldClass = $this->fieldClasses[$fieldName]; $hasOneField = new $fieldClass($fieldName); } else { @@ -138,6 +159,9 @@ public function getFieldList() && ($this->includeRelations === true || isset($this->includeRelations['has_many'])) ) { foreach ($this->obj->hasMany() as $relationship => $component) { + if (in_array($relationship, $this->ignoreRelations)) { + continue; + } $includeInOwnTab = true; $fieldLabel = $this->obj->fieldLabel($relationship); $fieldClass = (isset($this->fieldClasses[$relationship])) @@ -177,6 +201,9 @@ public function getFieldList() && ($this->includeRelations === true || isset($this->includeRelations['many_many'])) ) { foreach ($this->obj->manyMany() as $relationship => $component) { + if (in_array($relationship, $this->ignoreRelations)) { + continue; + } static::addManyManyRelationshipFields( $fields, $relationship, @@ -252,10 +279,12 @@ protected function getParamsArray() { return [ 'tabbed' => $this->tabbed, + 'tabsOnly' => $this->tabsOnly, 'includeRelations' => $this->includeRelations, + 'ignoreRelations' => $this->ignoreRelations, 'restrictFields' => $this->restrictFields, + 'ignoreFields' => $this->ignoreFields, 'fieldClasses' => $this->fieldClasses, - 'ajaxSafe' => $this->ajaxSafe ]; } } diff --git a/src/Forms/Tab.php b/src/Forms/Tab.php index 3f3fe538d28..cf1463aa404 100644 --- a/src/Forms/Tab.php +++ b/src/Forms/Tab.php @@ -75,7 +75,6 @@ public function __construct($name, $titleOrField = null, $fields = null) // Assign name and title (not assigned by parent constructor) $this->setName($name); $this->setTitle($title); - $this->setID(Convert::raw2htmlid($name)); } public function ID() @@ -99,6 +98,12 @@ public function setID($id) return $this; } + public function setName($name) + { + $this->setID(Convert::raw2htmlid($name)); + return parent::setName($name); + } + /** * Get child fields * diff --git a/src/Forms/TabSet.php b/src/Forms/TabSet.php index c397b62a25d..5117bc79eb1 100644 --- a/src/Forms/TabSet.php +++ b/src/Forms/TabSet.php @@ -249,6 +249,30 @@ public function insertAfter($insertAfter, $field, $appendIfMissing = true) return parent::insertAfter($insertAfter, $field, $appendIfMissing); } + public function changeTabOrder(array $tabNames): static + { + // Build a map of tabs indexed by their name. This will make the 2nd step much easier. + $tabMap = []; + foreach ($this->children as $tab) { + $tabMap[$tab->getName()] = $tab; + } + + // Iterate through the ordered list of names, building a new array to be put into $this->items. + // While we're doing this, empty out $tabMap so that we can keep track of leftovers. + // Unrecognised field names are okay; just ignore them + $tabs = []; + foreach ($tabNames as $tabName) { + if (isset($tabMap[$tabName])) { + $tabs[] = $tabMap[$tabName]; + unset($tabMap[$tabName]); + } + } + + // Add the leftover fields to the end of the list. + $this->setTabs(FieldList::create([...$tabs, ...$tabMap])); + return $this; + } + /** * Sets an additional default for $schemaData. * The existing keys are immutable. HideNav is added in this overriding method to ensure it is not ignored by diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 2e4e1f31b85..4f0730de638 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -289,6 +289,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ private static $table_name = null; + /** + * Settings used by the FormScaffolder that scaffolds fields for getCMSFields() + */ + private static array $scaffold_cmsfields_settings = [ + 'includeRelations' => true, + 'tabbed' => true, + ]; + /** * Non-static relationship cache, indexed by component name. * @@ -2387,8 +2395,8 @@ public function scaffoldSearchFields($_params = null) { $params = array_merge( [ - 'fieldClasses' => false, - 'restrictFields' => false + 'fieldClasses' => [], + 'restrictFields' => [] ], (array)$_params ); @@ -2474,20 +2482,24 @@ public function scaffoldFormFields($_params = null) $params = array_merge( [ 'tabbed' => false, + 'tabsOnly' => false, 'includeRelations' => false, - 'restrictFields' => false, - 'fieldClasses' => false, - 'ajaxSafe' => false + 'ignoreRelations' => [], + 'restrictFields' => [], + 'ignoreFields' => [], + 'fieldClasses' => [], ], (array)$_params ); $fs = FormScaffolder::create($this); $fs->tabbed = $params['tabbed']; + $fs->tabsOnly = $params['tabsOnly']; $fs->includeRelations = $params['includeRelations']; + $fs->ignoreRelations = $params['ignoreRelations']; $fs->restrictFields = $params['restrictFields']; + $fs->ignoreFields = $params['ignoreFields']; $fs->fieldClasses = $params['fieldClasses']; - $fs->ajaxSafe = $params['ajaxSafe']; $this->extend('updateFormScaffolder', $fs, $this); @@ -2605,12 +2617,12 @@ protected function afterUpdateCMSFields(callable $callback) */ public function getCMSFields() { - $tabbedFields = $this->scaffoldFormFields([ - // Don't allow has_many/many_many relationship editing before the record is first saved - 'includeRelations' => ($this->ID > 0), - 'tabbed' => true, - 'ajaxSafe' => true - ]); + $scaffoldOptions = static::config()->get('scaffold_cmsfields_settings'); + // Don't allow has_many/many_many relationship editing before the record is first saved + if (!$this->isInDB()) { + $scaffoldOptions['includeRelations'] = false; + } + $tabbedFields = $this->scaffoldFormFields($scaffoldOptions); $this->extend('updateCMSFields', $tabbedFields); diff --git a/src/Security/InheritedPermissionsExtension.php b/src/Security/InheritedPermissionsExtension.php index 2b2432d3d1c..f8beacf0487 100644 --- a/src/Security/InheritedPermissionsExtension.php +++ b/src/Security/InheritedPermissionsExtension.php @@ -42,4 +42,21 @@ class InheritedPermissionsExtension extends DataExtension 'ViewerMembers', 'EditorMembers', ]; + + /** + * These fields will need to be added manually, since SiteTree wants it in the special settings tab + * and nothing else in code that uses these fields is scaffolded. + */ + private static array $scaffold_cmsfields_settings = [ + 'ignoreFields' => [ + 'CanViewType', + 'CanEditType', + ], + 'ignoreRelations' => [ + 'ViewerGroups', + 'EditorGroups', + 'ViewerMembers', + 'EditorMembers', + ], + ]; }