Completed
Push — 4 ( cd71f9...196752 )
by Ingo
28s queued 20s
created

GridFieldDetailForm   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 104
dl 0
loc 401
rs 8.48
c 0
b 0
f 0
wmc 49

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A getRecordFromRequest() 0 12 2
A handleItem() 0 33 6
A getURLHandlers() 0 4 1
A getItemRequestHandler() 0 15 2
A getTemplate() 0 3 1
A setShowPagination() 0 4 1
A setValidator() 0 4 1
B getLostRecordRedirection() 0 34 10
A getName() 0 3 1
A getShowAdd() 0 7 2
A getDefaultShowPagination() 0 4 2
A setFields() 0 4 1
A setShowAdd() 0 4 1
A getShowPagination() 0 7 2
A getDefaultShowAdd() 0 4 2
A getFields() 0 3 1
A getItemRequestClass() 0 8 3
A setTemplate() 0 4 1
A setRedirectMissingRecords() 0 4 1
A setItemEditFormCallback() 0 4 1
A getValidator() 0 3 1
A setItemRequestClass() 0 4 1
A setName() 0 4 1
A getItemEditFormCallback() 0 3 1
A getRedirectMissingRecords() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like GridFieldDetailForm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GridFieldDetailForm, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Forms\GridField;
4
5
use Closure;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\HTTPResponse_Exception;
10
use SilverStripe\Control\HTTPStreamResponse;
11
use SilverStripe\Control\RequestHandler;
12
use SilverStripe\Core\ClassInfo;
13
use SilverStripe\Core\Extensible;
14
use SilverStripe\Core\Injector\Injectable;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Forms\FieldList;
17
use SilverStripe\Forms\Validator;
18
use SilverStripe\ORM\DataList;
19
use SilverStripe\ORM\DataObject;
20
use SilverStripe\ORM\Filterable;
21
22
/**
23
 * Provides view and edit forms at GridField-specific URLs.
24
 *
25
 * These can be placed into pop-ups by an appropriate front-end.
26
 *
27
 * Usually added to a {@link GridField} alongside of a
28
 * {@link GridFieldEditButton} which takes care of linking the
29
 * individual rows to their edit view.
30
 *
31
 * The URLs provided will be off the following form:
32
 *  - <FormURL>/field/<GridFieldName>/item/<RecordID>
33
 *  - <FormURL>/field/<GridFieldName>/item/<RecordID>/edit
34
 */
35
class GridFieldDetailForm implements GridField_URLHandler
36
{
37
38
    use Extensible, Injectable, GridFieldStateAware;
39
40
    /**
41
     * @var string
42
     */
43
    protected $template = null;
44
45
    /**
46
     * @var string
47
     */
48
    protected $name;
49
50
    /**
51
     * @var bool
52
     */
53
    protected $showPagination;
54
55
    /**
56
     * @var bool
57
     */
58
    protected $showAdd;
59
60
    /**
61
     * @var Validator The form validator used for both add and edit fields.
62
     */
63
    protected $validator;
64
65
    /**
66
     * @var FieldList Falls back to {@link DataObject->getCMSFields()} if not defined.
67
     */
68
    protected $fields;
69
70
    /**
71
     * @var string
72
     */
73
    protected $itemRequestClass;
74
75
    /**
76
     * If true, will redirect to missing records if they are found elsewhere
77
     * @var bool
78
     */
79
    protected $redirectMissingRecords = false;
80
81
    /**
82
     * @var callable With two parameters: $form and $component
83
     */
84
    protected $itemEditFormCallback;
85
86
    public function getURLHandlers($gridField)
87
    {
88
        return [
89
            'item/$ID' => 'handleItem'
90
        ];
91
    }
92
93
    /**
94
     * Create a popup component. The two arguments will specify how the popup form's HTML and
95
     * behaviour is created.  The given controller will be customised, putting the edit form into the
96
     * template with the given name.
97
     *
98
     * The arguments are experimental API's to support partial content to be passed back to whatever
99
     * controller who wants to display the getCMSFields
100
     *
101
     * @param string $name The name of the edit form to place into the pop-up form
102
     * @param bool $showPagination Whether the `Previous` and `Next` buttons should display or not, leave as null to use default
103
     * @param bool $showAdd Whether the `Add` button should display or not, leave as null to use default
104
     */
105
    public function __construct($name = null, $showPagination = null, $showAdd = null)
106
    {
107
        $this->setName($name ?: 'DetailForm');
108
        $this->setShowPagination($showPagination);
109
        $this->setShowAdd($showAdd);
110
    }
111
112
    /**
113
     *
114
     * @param GridField $gridField
115
     * @param HTTPRequest $request
116
     * @return HTTPResponse
117
     */
118
    public function handleItem($gridField, $request)
119
    {
120
        // Our getController could either give us a true Controller, if this is the top-level GridField.
121
        // It could also give us a RequestHandler in the form of GridFieldDetailForm_ItemRequest if this is a
122
        // nested GridField.
123
        $requestHandler = $gridField->getForm()->getController();
124
        $record = $this->getRecordFromRequest($gridField, $request);
125
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
126
            // Look for the record elsewhere in the CMS
127
            $redirectDest = $this->getLostRecordRedirection($gridField, $request);
128
            // Don't allow infinite redirections
129
            if ($redirectDest) {
130
                // Mark the remainder of the URL as parsed to trigger an immediate redirection
131
                while (!$request->allParsed()) {
132
                    $request->shift();
133
                }
134
                return (new HTTPResponse())->redirect($redirectDest);
135
            }
136
137
            return $requestHandler->httpError(404, 'That record was not found');
138
        }
139
        $handler = $this->getItemRequestHandler($gridField, $record, $requestHandler);
140
        $manager = $this->getStateManager();
141
        if ($gridStateStr = $manager->getStateFromRequest($gridField, $request)) {
142
            $gridField->getState(false)->setValue($gridStateStr);
143
        }
144
145
        // if no validator has been set on the GridField then use the Validators from the record.
146
        if (!$this->getValidator()) {
147
            $this->setValidator($record->getCMSCompositeValidator());
148
        }
149
150
        return $handler->handleRequest($request);
151
    }
152
153
    /**
154
     * @param GridField $gridField
155
     * @param HTTPRequest $request
156
     * @return DataObject|null
157
     */
158
    protected function getRecordFromRequest(GridField $gridField, HTTPRequest $request): ?DataObject
159
    {
160
        /** @var DataObject $record */
161
        if (is_numeric($request->param('ID'))) {
162
            /** @var Filterable $dataList */
163
            $dataList = $gridField->getList();
164
            $record = $dataList->byID($request->param('ID'));
0 ignored issues
show
Bug introduced by
$request->param('ID') of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\Filterable::byID(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
            $record = $dataList->byID(/** @scrutinizer ignore-type */ $request->param('ID'));
Loading history...
165
        } else {
166
            $record = Injector::inst()->create($gridField->getModelClass());
167
        }
168
169
        return $record;
170
    }
171
172
    /**
173
     * Try and find another URL at which the given record can be edited.
174
     * If redirectMissingRecords is true and the record has a CMSEditLink method, that value will be returned.
175
     * This only works when the list passed to the GridField is a {@link DataList}.
176
     *
177
     * @param $gridField The current GridField
0 ignored issues
show
Bug introduced by
The type SilverStripe\Forms\GridField\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
178
     * @param $id The ID of the DataObject to open
179
     */
180
    public function getLostRecordRedirection(GridField $gridField, HTTPRequest $request, ?int $id = null): ?string
181
    {
182
183
        if (!$this->redirectMissingRecords) {
184
            return null;
185
        }
186
187
        // If not supplied, look up the ID from the request
188
        if ($id === null && is_numeric($request->param('ID'))) {
189
            $id = (int)$request->param('ID');
190
        }
191
192
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
193
            return null;
194
        }
195
196
        $list = $gridField->getList();
197
        if (!$list instanceof DataList) {
198
            throw new \LogicException('List is not of type DataList, cannot determine redirection target');
199
        }
200
201
        $existing = DataObject::get($list->dataClass())->byID($id);
202
        if ($existing && $existing->hasMethod('CMSEditLink')) {
203
            $link = $existing->CMSEditLink();
0 ignored issues
show
Bug introduced by
The method CMSEditLink() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

203
            /** @scrutinizer ignore-call */ 
204
            $link = $existing->CMSEditLink();
Loading history...
204
        }
205
206
        if ($link && $link == $request->getURL()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $link does not seem to be defined for all execution paths leading up to this point.
Loading history...
207
            throw new \LogicException(sprintf(
208
                'Infinite redirection to "%s" detected in GridFieldDetailForm->getLostRecordRedirection()',
209
                $link
210
            ));
211
        }
212
213
        return $link;
214
    }
215
216
    /**
217
     * Build a request handler for the given record
218
     *
219
     * @param GridField $gridField
220
     * @param DataObject $record
221
     * @param RequestHandler $requestHandler
222
     * @return GridFieldDetailForm_ItemRequest
223
     */
224
    protected function getItemRequestHandler($gridField, $record, $requestHandler)
225
    {
226
        $class = $this->getItemRequestClass();
227
        $assignedClass = $this->itemRequestClass;
228
        $this->extend('updateItemRequestClass', $class, $gridField, $record, $requestHandler, $assignedClass);
229
        /** @var GridFieldDetailForm_ItemRequest $handler */
230
        $handler = Injector::inst()->createWithArgs(
231
            $class,
232
            [$gridField, $this, $record, $requestHandler, $this->name]
233
        );
234
        if ($template = $this->getTemplate()) {
235
            $handler->setTemplate($template);
236
        }
237
        $this->extend('updateItemRequestHandler', $handler);
238
        return $handler;
239
    }
240
241
    /**
242
     * @param string $template
243
     * @return $this
244
     */
245
    public function setTemplate($template)
246
    {
247
        $this->template = $template;
248
        return $this;
249
    }
250
251
    /**
252
     * @return String
253
     */
254
    public function getTemplate()
255
    {
256
        return $this->template;
257
    }
258
259
    /**
260
     * @param string $name
261
     * @return $this
262
     */
263
    public function setName($name)
264
    {
265
        $this->name = $name;
266
        return $this;
267
    }
268
269
    /**
270
     * @return String
271
     */
272
    public function getName()
273
    {
274
        return $this->name;
275
    }
276
277
    /**
278
     * Enable redirection to missing records.
279
     *
280
     * If a GridField shows a filtered list, and the DataObject is not in the list but exists in the
281
     * database, and the DataObject has a CMSEditLink method, then the system will redirect to the
282
     * URL returned by that method.
283
     */
284
    public function setRedirectMissingRecords(bool $redirectMissingRecords): self
285
    {
286
        $this->redirectMissingRecords = $redirectMissingRecords;
287
        return $this;
288
    }
289
290
    /**
291
     * Return the status of redirection to missing records.
292
     * @see setRedirectMissingRecordssetRedirectMissingRecords
293
     */
294
    public function getRedirectMissingRecords(): bool
295
    {
296
        return $this->redirectMissingRecords;
297
    }
298
299
    /**
300
     * @return bool
301
     */
302
    protected function getDefaultShowPagination()
303
    {
304
        $formActionsConfig = GridFieldDetailForm_ItemRequest::config()->get('formActions');
305
        return isset($formActionsConfig['showPagination']) ? (bool) $formActionsConfig['showPagination'] : false;
306
    }
307
308
    /**
309
     * @return bool
310
     */
311
    public function getShowPagination()
312
    {
313
        if ($this->showPagination === null) {
314
            return $this->getDefaultShowPagination();
315
        }
316
317
        return (bool) $this->showPagination;
318
    }
319
320
    /**
321
     * @param bool|null $showPagination
322
     * @return GridFieldDetailForm
323
     */
324
    public function setShowPagination($showPagination)
325
    {
326
        $this->showPagination = $showPagination;
327
        return $this;
328
    }
329
330
    /**
331
     * @return bool
332
     */
333
    protected function getDefaultShowAdd()
334
    {
335
        $formActionsConfig = GridFieldDetailForm_ItemRequest::config()->get('formActions');
336
        return isset($formActionsConfig['showAdd']) ? (bool) $formActionsConfig['showAdd'] : false;
337
    }
338
339
    /**
340
     * @return bool
341
     */
342
    public function getShowAdd()
343
    {
344
        if ($this->showAdd === null) {
345
            return $this->getDefaultShowAdd();
346
        }
347
348
        return (bool) $this->showAdd;
349
    }
350
351
    /**
352
     * @param bool|null $showAdd
353
     * @return GridFieldDetailForm
354
     */
355
    public function setShowAdd($showAdd)
356
    {
357
        $this->showAdd = $showAdd;
358
        return $this;
359
    }
360
361
    /**
362
     * @param Validator $validator
363
     * @return $this
364
     */
365
    public function setValidator(Validator $validator)
366
    {
367
        $this->validator = $validator;
368
        return $this;
369
    }
370
371
    /**
372
     * @return Validator
373
     */
374
    public function getValidator()
375
    {
376
        return $this->validator;
377
    }
378
379
    /**
380
     * @param FieldList $fields
381
     * @return $this
382
     */
383
    public function setFields(FieldList $fields)
384
    {
385
        $this->fields = $fields;
386
        return $this;
387
    }
388
389
    /**
390
     * @return FieldList
391
     */
392
    public function getFields()
393
    {
394
        return $this->fields;
395
    }
396
397
    /**
398
     * @param string $class
399
     * @return $this
400
     */
401
    public function setItemRequestClass($class)
402
    {
403
        $this->itemRequestClass = $class;
404
        return $this;
405
    }
406
407
    /**
408
     * @return string name of {@see GridFieldDetailForm_ItemRequest} subclass
409
     */
410
    public function getItemRequestClass()
411
    {
412
        if ($this->itemRequestClass) {
413
            return $this->itemRequestClass;
414
        } elseif (ClassInfo::exists(static::class . '_ItemRequest')) {
415
            return static::class . '_ItemRequest';
416
        }
417
        return GridFieldDetailForm_ItemRequest::class;
418
    }
419
420
    /**
421
     * @param Closure $cb Make changes on the edit form after constructing it.
422
     * @return $this
423
     */
424
    public function setItemEditFormCallback(Closure $cb)
425
    {
426
        $this->itemEditFormCallback = $cb;
427
        return $this;
428
    }
429
430
    /**
431
     * @return Closure
432
     */
433
    public function getItemEditFormCallback()
434
    {
435
        return $this->itemEditFormCallback;
436
    }
437
}
438