Passed
Push — 4 ( 54ebcc...a33866 )
by Luke
08:19
created

FormScaffolder::addManyManyRelationshipFields()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 4
nop 5
dl 0
loc 29
rs 9.7666
c 0
b 0
f 0
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->restrictFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
87
                continue;
88
            }
89
90
            // @todo Pass localized title
91
            if ($this->fieldClasses && isset($this->fieldClasses[$fieldName])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fieldClasses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->restrictFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fieldClasses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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