1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Forms; |
4
|
|
|
|
5
|
|
|
use SilverStripe\Core\Injector\Injectable; |
6
|
|
|
use SilverStripe\Core\Injector\Injector; |
7
|
|
|
use SilverStripe\Forms\GridField\GridField; |
8
|
|
|
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor; |
9
|
|
|
use SilverStripe\ORM\DataObject; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* @uses DBField::scaffoldFormField() |
13
|
|
|
* @uses DataObject::fieldLabels() |
14
|
|
|
*/ |
15
|
|
|
class FormScaffolder |
16
|
|
|
{ |
17
|
|
|
use Injectable; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var DataObject $obj The object defining the fields to be scaffolded |
21
|
|
|
* through its metadata like $db, $searchable_fields, etc. |
22
|
|
|
*/ |
23
|
|
|
protected $obj; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var boolean $tabbed Return fields in a tabset, with all main fields in the path "Root.Main", |
27
|
|
|
* relation fields in "Root.<relationname>" (if {@link $includeRelations} is enabled). |
28
|
|
|
*/ |
29
|
|
|
public $tabbed = false; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var boolean $ajaxSafe |
33
|
|
|
*/ |
34
|
|
|
public $ajaxSafe = false; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var array $restrictFields Numeric array of a field name whitelist. |
38
|
|
|
* If left blank, all fields from {@link DataObject->db()} will be included. |
39
|
|
|
* |
40
|
|
|
* @todo Implement restrictions for has_many and many_many relations. |
41
|
|
|
*/ |
42
|
|
|
public $restrictFields; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var array $fieldClasses Optional mapping of fieldnames to subclasses of {@link FormField}. |
46
|
|
|
* By default the scaffolder will determine the field instance by {@link DBField::scaffoldFormField()}. |
47
|
|
|
* |
48
|
|
|
* @todo Implement fieldClasses for has_many and many_many relations |
49
|
|
|
*/ |
50
|
|
|
public $fieldClasses; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var boolean $includeRelations Include has_one, has_many and many_many relations |
54
|
|
|
*/ |
55
|
|
|
public $includeRelations = false; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param DataObject $obj |
59
|
|
|
*/ |
60
|
|
|
public function __construct($obj) |
61
|
|
|
{ |
62
|
|
|
$this->obj = $obj; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Gets the form fields as defined through the metadata |
67
|
|
|
* on {@link $obj} and the custom parameters passed to FormScaffolder. |
68
|
|
|
* Depending on those parameters, the fields can be used in ajax-context, |
69
|
|
|
* contain {@link TabSet}s etc. |
70
|
|
|
* |
71
|
|
|
* @return FieldList |
72
|
|
|
*/ |
73
|
|
|
public function getFieldList() |
74
|
|
|
{ |
75
|
|
|
$fields = new FieldList(); |
76
|
|
|
|
77
|
|
|
// tabbed or untabbed |
78
|
|
|
if ($this->tabbed) { |
79
|
|
|
$fields->push(new TabSet("Root", $mainTab = new Tab("Main"))); |
80
|
|
|
$mainTab->setTitle(_t(__CLASS__ . '.TABMAIN', 'Main')); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// Add logical fields directly specified in db config |
84
|
|
|
foreach ($this->obj->config()->get('db') as $fieldName => $fieldType) { |
85
|
|
|
// Skip restricted fields |
86
|
|
|
if ($this->restrictFields && !in_array($fieldName, $this->restrictFields)) { |
|
|
|
|
87
|
|
|
continue; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
// @todo Pass localized title |
91
|
|
|
if ($this->fieldClasses && isset($this->fieldClasses[$fieldName])) { |
|
|
|
|
92
|
|
|
$fieldClass = $this->fieldClasses[$fieldName]; |
93
|
|
|
$fieldObject = new $fieldClass($fieldName); |
94
|
|
|
} else { |
95
|
|
|
$fieldObject = $this |
96
|
|
|
->obj |
97
|
|
|
->dbObject($fieldName) |
98
|
|
|
->scaffoldFormField(null, $this->getParamsArray()); |
99
|
|
|
} |
100
|
|
|
// Allow fields to opt-out of scaffolding |
101
|
|
|
if (!$fieldObject) { |
102
|
|
|
continue; |
103
|
|
|
} |
104
|
|
|
$fieldObject->setTitle($this->obj->fieldLabel($fieldName)); |
105
|
|
|
if ($this->tabbed) { |
106
|
|
|
$fields->addFieldToTab("Root.Main", $fieldObject); |
107
|
|
|
} else { |
108
|
|
|
$fields->push($fieldObject); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
// add has_one relation fields |
113
|
|
|
if ($this->obj->hasOne()) { |
114
|
|
|
foreach ($this->obj->hasOne() as $relationship => $component) { |
115
|
|
|
if ($this->restrictFields && !in_array($relationship, $this->restrictFields)) { |
|
|
|
|
116
|
|
|
continue; |
117
|
|
|
} |
118
|
|
|
$fieldName = $component === 'SilverStripe\\ORM\\DataObject' |
119
|
|
|
? $relationship // Polymorphic has_one field is composite, so don't refer to ID subfield |
120
|
|
|
: "{$relationship}ID"; |
121
|
|
|
if ($this->fieldClasses && isset($this->fieldClasses[$fieldName])) { |
|
|
|
|
122
|
|
|
$fieldClass = $this->fieldClasses[$fieldName]; |
123
|
|
|
$hasOneField = new $fieldClass($fieldName); |
124
|
|
|
} else { |
125
|
|
|
$hasOneField = $this->obj->dbObject($fieldName)->scaffoldFormField(null, $this->getParamsArray()); |
126
|
|
|
} |
127
|
|
|
if (empty($hasOneField)) { |
128
|
|
|
continue; // Allow fields to opt out of scaffolding |
129
|
|
|
} |
130
|
|
|
$hasOneField->setTitle($this->obj->fieldLabel($relationship)); |
131
|
|
|
if ($this->tabbed) { |
132
|
|
|
$fields->addFieldToTab("Root.Main", $hasOneField); |
133
|
|
|
} else { |
134
|
|
|
$fields->push($hasOneField); |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
// only add relational fields if an ID is present |
140
|
|
|
if ($this->obj->ID) { |
141
|
|
|
// add has_many relation fields |
142
|
|
|
if ($this->obj->hasMany() |
143
|
|
|
&& ($this->includeRelations === true || isset($this->includeRelations['has_many'])) |
144
|
|
|
) { |
145
|
|
|
foreach ($this->obj->hasMany() as $relationship => $component) { |
146
|
|
|
if ($this->tabbed) { |
147
|
|
|
$fields->findOrMakeTab( |
148
|
|
|
"Root.$relationship", |
149
|
|
|
$this->obj->fieldLabel($relationship) |
150
|
|
|
); |
151
|
|
|
} |
152
|
|
|
$fieldClass = (isset($this->fieldClasses[$relationship])) |
153
|
|
|
? $this->fieldClasses[$relationship] |
154
|
|
|
: 'SilverStripe\\Forms\\GridField\\GridField'; |
155
|
|
|
/** @var GridField $grid */ |
156
|
|
|
$grid = Injector::inst()->create( |
157
|
|
|
$fieldClass, |
158
|
|
|
$relationship, |
159
|
|
|
$this->obj->fieldLabel($relationship), |
160
|
|
|
$this->obj->$relationship(), |
161
|
|
|
GridFieldConfig_RelationEditor::create() |
162
|
|
|
); |
163
|
|
|
if ($this->tabbed) { |
164
|
|
|
$fields->addFieldToTab("Root.$relationship", $grid); |
165
|
|
|
} else { |
166
|
|
|
$fields->push($grid); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
if ($this->obj->manyMany() |
172
|
|
|
&& ($this->includeRelations === true || isset($this->includeRelations['many_many'])) |
173
|
|
|
) { |
174
|
|
|
foreach ($this->obj->manyMany() as $relationship => $component) { |
175
|
|
|
static::addManyManyRelationshipFields( |
176
|
|
|
$fields, |
177
|
|
|
$relationship, |
178
|
|
|
(isset($this->fieldClasses[$relationship])) |
179
|
|
|
? $this->fieldClasses[$relationship] : null, |
180
|
|
|
$this->tabbed, |
181
|
|
|
$this->obj |
182
|
|
|
); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $fields; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Adds the default many-many relation fields for the relationship provided. |
192
|
|
|
* |
193
|
|
|
* @param FieldList $fields Reference to the @FieldList to add fields to. |
194
|
|
|
* @param string $relationship The relationship identifier. |
195
|
|
|
* @param mixed $overrideFieldClass Specify the field class to use here or leave as null to use default. |
196
|
|
|
* @param bool $tabbed Whether this relationship has it's own tab or not. |
197
|
|
|
* @param DataObject $dataObject The @DataObject that has the relation. |
198
|
|
|
*/ |
199
|
|
|
public static function addManyManyRelationshipFields( |
200
|
|
|
FieldList &$fields, |
201
|
|
|
$relationship, |
202
|
|
|
$overrideFieldClass, |
203
|
|
|
$tabbed, |
204
|
|
|
DataObject $dataObject |
205
|
|
|
) { |
206
|
|
|
if ($tabbed) { |
207
|
|
|
$fields->findOrMakeTab( |
208
|
|
|
"Root.$relationship", |
209
|
|
|
$dataObject->fieldLabel($relationship) |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
$fieldClass = $overrideFieldClass ?: GridField::class; |
214
|
|
|
|
215
|
|
|
/** @var GridField $grid */ |
216
|
|
|
$grid = Injector::inst()->create( |
217
|
|
|
$fieldClass, |
218
|
|
|
$relationship, |
219
|
|
|
$dataObject->fieldLabel($relationship), |
220
|
|
|
$dataObject->$relationship(), |
221
|
|
|
GridFieldConfig_RelationEditor::create() |
222
|
|
|
); |
223
|
|
|
|
224
|
|
|
if ($tabbed) { |
225
|
|
|
$fields->addFieldToTab("Root.$relationship", $grid); |
226
|
|
|
} else { |
227
|
|
|
$fields->push($grid); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Return an array suitable for passing on to {@link DBField->scaffoldFormField()} |
233
|
|
|
* without tying this call to a FormScaffolder interface. |
234
|
|
|
* |
235
|
|
|
* @return array |
236
|
|
|
*/ |
237
|
|
|
protected function getParamsArray() |
238
|
|
|
{ |
239
|
|
|
return array( |
240
|
|
|
'tabbed' => $this->tabbed, |
241
|
|
|
'includeRelations' => $this->includeRelations, |
242
|
|
|
'restrictFields' => $this->restrictFields, |
243
|
|
|
'fieldClasses' => $this->fieldClasses, |
244
|
|
|
'ajaxSafe' => $this->ajaxSafe |
245
|
|
|
); |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.