Passed
Pull Request — master (#29)
by Sergey
02:20
created

ActionsGridFieldItemRequest::doCustomAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
namespace LeKoala\CmsActions;
4
5
use Exception;
6
use SilverStripe\Admin\LeftAndMain;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Control\Director;
9
use SilverStripe\Control\HTTPRequest;
10
use SilverStripe\Control\HTTPResponse;
11
use SilverStripe\Control\HTTPResponse_Exception;
12
use SilverStripe\Core\Config\Configurable;
13
use SilverStripe\Core\Extensible;
14
use SilverStripe\Forms\CompositeField;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\Form;
17
use SilverStripe\Forms\FormAction;
18
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
19
use SilverStripe\Forms\Tab;
20
use SilverStripe\Forms\TabSet;
21
use SilverStripe\ORM\DataExtension;
22
use SilverStripe\ORM\DataObject;
23
use SilverStripe\ORM\FieldType\DBHTMLText;
24
use SilverStripe\ORM\ValidationResult;
25
use SilverStripe\SiteConfig\SiteConfig;
0 ignored issues
show
Bug introduced by
The type SilverStripe\SiteConfig\SiteConfig 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...
26
27
/**
28
 * Decorates GridDetailForm_ItemRequest to use new form actions and buttons.
29
 *
30
 * This is also applied to LeftAndMain to allow actions on pages
31
 * Warning: LeftAndMain doesn't call updateItemEditForm
32
 *
33
 * This is a lightweight version of BetterButtons that use default getCMSActions functionnality
34
 * on DataObjects
35
 *
36
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons
37
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons/blob/master/src/Extensions/GridFieldBetterButtonsItemRequest.php
38
 * @property LeftAndMain|GridFieldDetailForm_ItemRequest|ActionsGridFieldItemRequest $owner
39
 */
40
class ActionsGridFieldItemRequest extends DataExtension
41
{
42
    use Configurable;
43
    use Extensible;
44
45
    /**
46
     * @config
47
     * @var boolean
48
     */
49
    private static $enable_save_prev_next = true;
50
51
    /**
52
     * @config
53
     * @var boolean
54
     */
55
    private static $enable_save_close = true;
56
57
    /**
58
     * @config
59
     * @var boolean
60
     */
61
    private static $enable_delete_right = true;
62
63
    /**
64
     * @config
65
     * @var boolean
66
     */
67
    private static $enable_utils_prev_next = false;
0 ignored issues
show
introduced by
The private property $enable_utils_prev_next is not used, and could be removed.
Loading history...
68
69
    /**
70
     * @var array Allowed controller actions
71
     */
72
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
73
        'doSaveAndClose',
74
        'doSaveAndNext',
75
        'doSaveAndPrev',
76
        'doCustomAction', // For CustomAction
77
        'doCustomLink', // For CustomLink
78
    ];
79
80
    /**
81
     * @return array
82
     */
83
    protected function getAvailableActions($actions)
84
    {
85
        $list = [];
86
        foreach ($actions as $action) {
87
            $list[] = $action->getName();
88
        }
89
90
        return $list;
91
    }
92
93
    /**
94
     * Updates the detail form to include new form actions and buttons
95
     *
96
     * This is called by GridFieldDetailForm_ItemRequest
97
     *
98
     * @param Form $form The ItemEditForm object
99
     */
100
    public function updateItemEditForm($form)
101
    {
102
        $itemRequest = $this->owner;
103
104
        /** @var DataObject $record */
105
        $record = $itemRequest->record;
0 ignored issues
show
Bug Best Practice introduced by
The property record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
106
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
107
            $record = $form->getRecord();
108
        }
109
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
110
            return;
111
        }
112
113
        // We get the actions as defined on our record
114
        /** @var FieldList $CMSActions */
115
        $CMSActions = $record->getCMSActions();
116
117
        // address Silverstripe bug when SiteTree buttons are broken
118
        // @link https://github.com/silverstripe/silverstripe-cms/issues/2702
119
        $CMSActions->setForm($form);
120
121
        // We can the actions from the GridFieldDetailForm_ItemRequest
122
        // It sets the Save and Delete buttons + Right Group
123
        $actions = $form->Actions();
124
125
        // The default button group that contains the Save or Create action
126
        // @link https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/how_tos/extend_cms_interface/#extending-the-cms-actions
127
        $MajorActions = $actions->fieldByName('MajorActions');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $MajorActions is correct as $actions->fieldByName('MajorActions') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
128
129
        // If it doesn't exist, push to default group
130
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
131
            $MajorActions = $actions;
0 ignored issues
show
Unused Code introduced by
The assignment to $MajorActions is dead and can be removed.
Loading history...
132
        }
133
134
        // Push our actions that are otherwise ignored by SilverStripe
135
        foreach ($CMSActions as $action) {
136
            // Avoid duplicated actions (eg: when added by SilverStripe\Versioned\VersionedGridFieldItemRequest)
137
            if ($actions->fieldByName($action->getName())) {
138
                continue;
139
            }
140
            $actions->push($action);
141
        }
142
143
        // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions
144
        // and we don't want to duplicate it
145
        $this->processDropUpMenu($actions);
146
147
        // Add extension hook
148
        $this->extend('onBeforeUpdateCMSActions', $actions, $record);
149
        $record->extend('onBeforeUpdateCMSActions', $actions);
150
151
        $ActionMenus = $actions->fieldByName('ActionMenus');
152
        // Re-insert ActionMenus to make sure they always follow the buttons
153
        if ($ActionMenus) {
154
            $actions->remove($ActionMenus);
155
            $actions->push($ActionMenus);
156
        }
157
158
        // We have a 4.4 setup, before that there was no RightGroup
159
        $RightGroup = $actions->fieldByName('RightGroup');
160
161
        // Insert again to make sure our actions are properly placed after apply changes
162
        if ($RightGroup) {
163
            $actions->remove($RightGroup);
164
            $actions->push($RightGroup);
165
        }
166
167
        $opts = [
168
            'save_close'     => self::config()->enable_save_close,
169
            'save_prev_next' => self::config()->enable_save_prev_next,
170
            'delete_right'   => self::config()->enable_delete_right,
171
        ];
172
        if ($record->hasMethod('getCMSActionsOptions')) {
173
            $opts = array_merge($opts, $record->getCMSActionsOptions());
174
        }
175
176
        if ($opts['save_close']) {
177
            $this->addSaveAndClose($actions, $record);
178
        }
179
180
        if ($opts['save_prev_next']) {
181
            $this->addSaveNextAndPrevious($actions, $record);
182
        }
183
184
        if ($opts['delete_right']) {
185
            $this->moveCancelAndDelete($actions, $record);
186
        }
187
188
        // Add extension hook
189
        $this->extend('onAfterUpdateCMSActions', $actions, $record);
190
        $record->extend('onAfterUpdateCMSActions', $actions);
191
    }
192
193
194
    /**
195
     * Collect all Drop-Up actions into a menu.
196
     * @param FieldList $actions
197
     * @return void
198
     */
199
    protected function processDropUpMenu($actions)
200
    {
201
        // The Drop-up container may already exist
202
        $dropUpContainer = $actions->fieldByName('ActionMenus.MoreOptions');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $dropUpContainer is correct as $actions->fieldByName('ActionMenus.MoreOptions') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
203
        foreach ($actions as $action) {
204
            if ($action->hasMethod('getDropUp') && $action->getDropUp()) {
205
                if (!$dropUpContainer) {
206
                    $dropUpContainer = $this->createDropUpContainer($actions);
207
                }
208
                $action->getContainerFieldList()->removeByName($action->getName());
209
                $dropUpContainer->push($action);
210
            }
211
        }
212
    }
213
214
    /**
215
     * Prepares a Drop-Up menu
216
     * @param FieldList $actions
217
     * @return Tab
218
     */
219
    protected function createDropUpContainer($actions)
220
    {
221
        $rootTabSet = new TabSet('ActionMenus');
222
        $dropUpContainer = new Tab(
223
            'MoreOptions',
224
            _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons')
225
        );
226
        $dropUpContainer->addExtraClass('popover-actions-simulate');
227
        $rootTabSet->push($dropUpContainer);
228
        $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder');
229
230
        $actions->insertBefore('RightGroup', $rootTabSet);
231
232
        return $dropUpContainer;
233
    }
234
235
    /**
236
     * @param FieldList $actions
237
     * @param DataObject $record
238
     * @return void
239
     */
240
    public function moveCancelAndDelete(FieldList $actions, DataObject $record)
241
    {
242
        // We have a 4.4 setup, before that there was no RightGroup
243
        $RightGroup = $actions->fieldByName('RightGroup');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $RightGroup is correct as $actions->fieldByName('RightGroup') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
244
245
        // Move delete at the end
246
        $deleteAction = $actions->fieldByName('action_doDelete');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $deleteAction is correct as $actions->fieldByName('action_doDelete') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
247
        if ($deleteAction) {
0 ignored issues
show
introduced by
$deleteAction is of type null, thus it always evaluated to false.
Loading history...
248
            // Move at the end of the stack
249
            $actions->remove($deleteAction);
250
            $actions->push($deleteAction);
251
252
            if (!$RightGroup) {
253
                // Only necessary pre 4.4
254
                $deleteAction->addExtraClass('align-right');
255
            }
256
            // Set custom title
257
            if ($record->hasMethod('getDeleteButtonTitle')) {
258
                $deleteAction->setTitle($record->getDeleteButtonTitle());
259
            }
260
        }
261
        // Move cancel at the end
262
        $cancelButton = $actions->fieldByName('cancelbutton');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $cancelButton is correct as $actions->fieldByName('cancelbutton') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
263
        if ($cancelButton) {
0 ignored issues
show
introduced by
$cancelButton is of type null, thus it always evaluated to false.
Loading history...
264
            // Move at the end of the stack
265
            $actions->remove($cancelButton);
266
            $actions->push($cancelButton);
267
            if (!$RightGroup) {
268
                // Only necessary pre 4.4
269
                $cancelButton->addExtraClass('align-right');
270
            }
271
            // Set custom titlte
272
            if ($record->hasMethod('getCancelButtonTitle')) {
273
                $cancelButton->setTitle($record->getCancelButtonTitle());
274
            }
275
        }
276
    }
277
278
    /**
279
     * @param DataObject $record
280
     * @return int
281
     */
282
    public function getCustomPreviousRecordID(DataObject $record)
283
    {
284
        if ($record->hasMethod('PrevRecord')) {
285
            return $record->PrevRecord()->ID ?? 0;
286
        }
287
288
        return $this->owner->getPreviousRecordID();
0 ignored issues
show
Bug introduced by
The method getPreviousRecordID() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

288
        return $this->owner->/** @scrutinizer ignore-call */ getPreviousRecordID();
Loading history...
289
    }
290
291
    /**
292
     * @param DataObject $record
293
     * @return int
294
     */
295
    public function getCustomNextRecordID(DataObject $record)
296
    {
297
        if ($record->hasMethod('NextRecord')) {
298
            return $record->NextRecord()->ID ?? 0;
299
        }
300
301
        return $this->owner->getNextRecordID();
0 ignored issues
show
Bug introduced by
The method getNextRecordID() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

301
        return $this->owner->/** @scrutinizer ignore-call */ getNextRecordID();
Loading history...
302
    }
303
304
    /**
305
     * @param FieldList $actions
306
     * @param DataObject $record
307
     * @return void
308
     */
309
    public function addSaveNextAndPrevious(FieldList $actions, DataObject $record)
310
    {
311
        if (!$record->canEdit()) {
312
            return;
313
        }
314
        if (!$record->ID) {
315
            return;
316
        }
317
318
        $MajorActions = $actions->fieldByName('MajorActions');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $MajorActions is correct as $actions->fieldByName('MajorActions') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
319
320
        // If it doesn't exist, push to default group
321
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
322
            $MajorActions = $actions;
323
        }
324
325
        // TODO: check why with paginator, after the first page, getPreviousRecordID/getNextRecordID tend to not work properly
326
        $getPreviousRecordID = $this->getCustomPreviousRecordID($record);
327
        $getNextRecordID = $this->getCustomNextRecordID($record);
328
329
        // Coupling for HasPrevNextUtils
330
        if (Controller::has_curr()) {
331
            /** @var HTTPRequest $request */
332
            $request = Controller::curr()->getRequest();
333
            $routeParams = $request->routeParams();
334
            $routeParams['PreviousRecordID'] = $getPreviousRecordID;
335
            $routeParams['NextRecordID'] = $getNextRecordID;
336
            $request->setRouteParams($routeParams);
337
        }
338
339
        if ($getPreviousRecordID) {
340
            $doSaveAndPrev = new FormAction('doSaveAndPrev', _t('ActionsGridFieldItemRequest.SAVEANDPREVIOUS', 'Save and Previous'));
341
            $doSaveAndPrev->addExtraClass($this->getBtnClassForRecord($record));
342
            $doSaveAndPrev->addExtraClass('font-icon-angle-double-left btn-mobile-collapse');
343
            $doSaveAndPrev->setUseButtonTag(true);
344
            $MajorActions->push($doSaveAndPrev);
345
        }
346
        if ($getNextRecordID) {
347
            $doSaveAndNext = new FormAction('doSaveAndNext', _t('ActionsGridFieldItemRequest.SAVEANDNEXT', 'Save and Next'));
348
            $doSaveAndNext->addExtraClass($this->getBtnClassForRecord($record));
349
            $doSaveAndNext->addExtraClass('font-icon-angle-double-right btn-mobile-collapse');
350
            $doSaveAndNext->setUseButtonTag(true);
351
            $MajorActions->push($doSaveAndNext);
352
        }
353
    }
354
355
    /**
356
     * @param FieldList $actions
357
     * @param DataObject $record
358
     * @return void
359
     */
360
    public function addSaveAndClose(FieldList $actions, DataObject $record)
361
    {
362
        if (!$record->canEdit()) {
363
            return;
364
        }
365
        if (!$record->ID && !$record->canCreate()) {
366
            return;
367
        }
368
369
        $MajorActions = $actions->fieldByName('MajorActions');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $MajorActions is correct as $actions->fieldByName('MajorActions') targeting SilverStripe\Forms\FieldList::fieldByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
370
371
        // If it doesn't exist, push to default group
372
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
373
            $MajorActions = $actions;
374
        }
375
376
        if ($record->ID) {
377
            $label = _t('ActionsGridFieldItemRequest.SAVEANDCLOSE', 'Save and Close');
378
        } else {
379
            $label = _t('ActionsGridFieldItemRequest.CREATEANDCLOSE', 'Create and Close');
380
        }
381
        $saveAndClose = new FormAction('doSaveAndClose', $label);
382
        $saveAndClose->addExtraClass($this->getBtnClassForRecord($record));
383
        $saveAndClose->setAttribute('data-text-alternate', $label);
384
        if ($record->ID) {
385
            $saveAndClose->setAttribute('data-btn-alternate-add', 'btn-primary');
386
            $saveAndClose->setAttribute('data-btn-alternate-remove', 'btn-outline-primary');
387
        }
388
        $saveAndClose->addExtraClass('font-icon-level-up btn-mobile-collapse');
389
        $saveAndClose->setUseButtonTag(true);
390
        $MajorActions->push($saveAndClose);
391
    }
392
393
    /**
394
     * New and existing records have different classes
395
     *
396
     * @param DataObject $record
397
     * @return string
398
     */
399
    protected function getBtnClassForRecord(DataObject $record)
400
    {
401
        if ($record->ID) {
402
            return 'btn-outline-primary';
403
        }
404
405
        return 'btn-primary';
406
    }
407
408
    /**
409
     * @param $action
410
     * @param $definedActions
411
     * @return mixed|CompositeField|null
412
     */
413
    protected static function findAction($action, $definedActions)
414
    {
415
        $result = null;
416
417
        foreach ($definedActions as $definedAction) {
418
            if (is_a($definedAction, CompositeField::class)) {
419
                $result = self::findAction($action, $definedAction->FieldList());
420
            }
421
422
            $definedActionName = $definedAction->getName();
423
424
            if ($definedAction->hasMethod('actionName')) {
425
                $definedActionName = $definedAction->actionName();
426
            }
427
            if ($definedActionName === $action) {
428
                $result = $definedAction;
429
            }
430
        }
431
432
        return $result;
433
    }
434
435
    /**
436
     * Forward a given action to a DataObject
437
     *
438
     * Action must be declared in getCMSActions to be called
439
     *
440
     * @param string $action
441
     * @param array $data
442
     * @param Form $form
443
     * @return HTTPResponse|DBHTMLText|string
444
     * @throws HTTPResponse_Exception
445
     */
446
    protected function forwardActionToRecord($action, $data = [], $form = null)
447
    {
448
        $controller = $this->getToplevelController();
449
450
        // We have an item request or a controller that can provide a record
451
        $record = null;
452
        if ($this->owner->hasMethod('ItemEditForm')) {
453
            // It's a request handler. Don't check for a specific class as it may be subclassed
454
            $record = $this->owner->record;
0 ignored issues
show
Bug Best Practice introduced by
The property record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
455
        } elseif ($controller->hasMethod('save_siteconfig')) {
456
            // Check for any type of siteconfig controller
457
            $record = SiteConfig::current_site_config();
458
        } elseif (!empty($data['ClassName']) && !empty($data['ID'])) {
459
            $record = DataObject::get_by_id($data['ClassName'], $data['ID']);
460
        } elseif ($controller->hasMethod("getRecord")) {
461
            $record = $controller->getRecord();
462
        }
463
464
        if (!$record) {
465
            throw new Exception("No record to handle the action $action on " . get_class($controller));
466
        }
467
        $definedActions = $record->getCMSActions();
468
        // Check if the action is indeed available
469
        $clickedAction = null;
470
        if (!empty($definedActions)) {
471
            $clickedAction = self::findAction($action, $definedActions);
472
        }
473
        if (!$clickedAction) {
474
            $class = get_class($record);
475
            $availableActions = implode(',', $this->getAvailableActions($definedActions));
476
            if (!$availableActions) {
477
                $availableActions = "(no available actions, please check getCMSActions)";
478
            }
479
480
            return $this->owner->httpError(403, sprintf(
0 ignored issues
show
Bug introduced by
The method httpError() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

480
            return $this->owner->/** @scrutinizer ignore-call */ httpError(403, sprintf(
Loading history...
481
                'Action not available on %s. It must be one of : %s',
482
                $class,
483
                $availableActions
484
            ));
485
        }
486
        $message = null;
487
        $error = false;
488
489
        // Check record BEFORE the action
490
        // It can be deleted by the action, and it will return to the list
491
        $isNewRecord = $record->ID === 0;
492
493
        try {
494
            $result = $record->$action($data, $form, $controller);
495
496
            // We have a response
497
            if ($result instanceof HTTPResponse) {
498
                return $result;
499
            }
500
501
            if ($result === false) {
502
                // Result returned an error (false)
503
                $error = true;
504
                $message = _t(
505
                    'ActionsGridFieldItemRequest.FAILED',
506
                    'Action {action} failed on {name}',
507
                    ['action' => $clickedAction->getTitle(), 'name' => $record->i18n_singular_name()]
508
                );
509
            } elseif (is_string($result)) {
510
                // Result is a message
511
                $message = $result;
512
            }
513
        } catch (Exception $ex) {
514
            $error = true;
515
            $message = $ex->getMessage();
516
        }
517
518
        // Build default message
519
        if (!$message) {
520
            $message = _t(
521
                'ActionsGridFieldItemRequest.DONE',
522
                'Action {action} was done on {name}',
523
                ['action' => $clickedAction->getTitle(), 'name' => $record->i18n_singular_name()]
524
            );
525
        }
526
        $status = 'good';
527
        if ($error) {
528
            $status = 'bad';
529
        }
530
531
        // Progressive actions return array with json data
532
        if (method_exists($clickedAction, 'getProgressive') && $clickedAction->getProgressive()) {
533
            $response = $controller->getResponse();
534
            $response->addHeader('Content-Type', 'application/json');
535
            if ($result) {
536
                $response->setBody(json_encode($result));
537
            }
538
539
            return $response;
540
        }
541
542
        // We don't have a form, simply return the result
543
        if (!$form) {
544
            if ($error) {
545
                return $this->owner->httpError(403, $message);
546
            }
547
548
            return $message;
549
        }
550
        if (Director::is_ajax()) {
551
            $controller = $this->getToplevelController();
552
            $controller->getResponse()->addHeader('X-Status', rawurlencode($message));
553
            if (method_exists($clickedAction, 'getShouldRefresh') && $clickedAction->getShouldRefresh()) {
554
                $controller->getResponse()->addHeader('X-Reload', "true");
555
            }
556
            // 4xx status makes a red box
557
            if ($error) {
558
                $controller->getResponse()->setStatusCode(400);
559
            }
560
        } else {
561
            // If the controller support sessionMessage, use it instead of form
562
            if ($controller->hasMethod('sessionMessage')) {
563
                $controller->sessionMessage($message, $status, ValidationResult::CAST_HTML);
564
            } else {
565
                $form->sessionMessage($message, $status, ValidationResult::CAST_HTML);
566
            }
567
        }
568
569
        // Redirect after action
570
        return $this->redirectAfterAction($isNewRecord, $record);
571
    }
572
573
    /**
574
     * Handles custom links
575
     *
576
     * Use CustomLink with default behaviour to trigger this
577
     *
578
     * See:
579
     * DefaultLink::getModelLink
580
     * GridFieldCustomLink::getLink
581
     *
582
     * @param HTTPRequest $request
583
     * @return HTTPResponse|DBHTMLText|string
584
     * @throws Exception
585
     */
586
    public function doCustomLink(HTTPRequest $request)
587
    {
588
        $action = $request->getVar('CustomLink');
589
        return $this->forwardActionToRecord($action);
590
    }
591
592
    /**
593
     * Handles custom actions
594
     *
595
     * Use CustomAction class to trigger this
596
     *
597
     * Nested actions are submitted like this
598
     * [action_doCustomAction] => Array
599
     * (
600
     *   [doTestAction] => 1
601
     * )
602
     *
603
     * @param array $data The form data
604
     * @param Form $form The form object
605
     * @return HTTPResponse|DBHTMLText|string
606
     * @throws Exception
607
     */
608
    public function doCustomAction($data, $form)
609
    {
610
        $action = key($data['action_doCustomAction']);
611
612
        return $this->forwardActionToRecord($action, $data, $form);
613
    }
614
615
    /**
616
     * Saves the form and goes back to list view
617
     *
618
     * @param array $data The form data
619
     * @param Form $form The form object
620
     */
621
    public function doSaveAndClose($data, $form)
622
    {
623
        $this->owner->doSave($data, $form);
0 ignored issues
show
Bug introduced by
The method doSave() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

623
        $this->owner->/** @scrutinizer ignore-call */ 
624
                      doSave($data, $form);
Loading history...
624
        // Redirect after save
625
        $controller = $this->getToplevelController();
626
627
        $location = $this->getBackLink();
628
        $controller->getResponse()->addHeader("X-Pjax", "Content");
629
        // Prevent Already directed to errors
630
        $controller->getResponse()->addHeader("Location", $location);
631
632
        return $controller->redirect($location);
633
    }
634
635
    /**
636
     * Saves the form and goes back to the next item
637
     *
638
     * @param array $data The form data
639
     * @param Form $form The form object
640
     */
641
    public function doSaveAndNext($data, $form)
642
    {
643
        $record = $this->owner->record;
0 ignored issues
show
Bug Best Practice introduced by
The property record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
644
        $this->owner->doSave($data, $form);
645
        // Redirect after save
646
        $controller = $this->getToplevelController();
647
        $controller->getResponse()->addHeader("X-Pjax", "Content");
648
649
        $getNextRecordID = $this->getCustomNextRecordID($record);
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type null; however, parameter $record of LeKoala\CmsActions\Actio...getCustomNextRecordID() does only seem to accept SilverStripe\ORM\DataObject, maybe add an additional type check? ( Ignorable by Annotation )

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

649
        $getNextRecordID = $this->getCustomNextRecordID(/** @scrutinizer ignore-type */ $record);
Loading history...
650
        $class = get_class($record);
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

650
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
651
        $next = $class::get()->byID($getNextRecordID);
652
653
        $link = $this->owner->getEditLink($getNextRecordID);
0 ignored issues
show
Bug introduced by
The method getEditLink() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

653
        /** @scrutinizer ignore-call */ 
654
        $link = $this->owner->getEditLink($getNextRecordID);
Loading history...
654
655
        // Link to a specific tab if set, see cms-actions.js
656
        if ($next && !empty($data['_activetab'])) {
657
            $link .= sprintf('#%s', $data['_activetab']);
658
        }
659
660
        return $controller->redirect($link);
661
    }
662
663
    /**
664
     * Saves the form and goes to the previous item
665
     *
666
     * @param array $data The form data
667
     * @param Form $form The form object
668
     */
669
    public function doSaveAndPrev($data, $form)
670
    {
671
        $record = $this->owner->record;
0 ignored issues
show
Bug Best Practice introduced by
The property record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
672
        $this->owner->doSave($data, $form);
673
        // Redirect after save
674
        $controller = $this->getToplevelController();
675
        $controller->getResponse()->addHeader("X-Pjax", "Content");
676
677
        $getPreviousRecordID = $this->getCustomPreviousRecordID($record);
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type null; however, parameter $record of LeKoala\CmsActions\Actio...ustomPreviousRecordID() does only seem to accept SilverStripe\ORM\DataObject, maybe add an additional type check? ( Ignorable by Annotation )

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

677
        $getPreviousRecordID = $this->getCustomPreviousRecordID(/** @scrutinizer ignore-type */ $record);
Loading history...
678
        $class = get_class($record);
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

678
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
679
        $prev = $class::get()->byID($getPreviousRecordID);
680
681
        $link = $this->owner->getEditLink($getPreviousRecordID);
682
683
        // Link to a specific tab if set, see cms-actions.js
684
        if ($prev && !empty($data['_activetab'])) {
685
            $link .= sprintf('#%s', $data['_activetab']);
686
        }
687
688
        return $controller->redirect($link);
689
    }
690
691
    /**
692
     * Gets the top level controller.
693
     *
694
     * @return Controller
695
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
696
     * because it is a protected method and not visible to a decorator!
697
     */
698
    protected function getToplevelController()
699
    {
700
        if ($this->isLeftAndMain($this->owner)) {
701
            return $this->owner;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->owner also could return the type LeKoala\CmsActions\Actio...dDetailForm_ItemRequest which is incompatible with the documented return type SilverStripe\Control\Controller.
Loading history...
702
        }
703
        if (!$this->owner->hasMethod("getController")) {
704
            return Controller::curr();
705
        }
706
        $controller = $this->owner->getController();
0 ignored issues
show
Bug introduced by
The method getController() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

706
        /** @scrutinizer ignore-call */ 
707
        $controller = $this->owner->getController();
Loading history...
707
        while ($controller instanceof GridFieldDetailForm_ItemRequest) {
708
            $controller = $controller->getController();
709
        }
710
711
        return $controller;
712
    }
713
714
    protected function isLeftAndMain($controller)
715
    {
716
        return is_subclass_of($controller, LeftAndMain::class);
717
    }
718
719
    /**
720
     * Gets the back link
721
     *
722
     * @return string
723
     * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
724
     * because it is a protected method and not visible to a decorator!
725
     */
726
    public function getBackLink()
727
    {
728
        $backlink = '';
729
        $toplevelController = $this->getToplevelController();
730
        // Check for LeftAndMain and alike controllers with a Backlink or Breadcrumbs methods
731
        if ($toplevelController->hasMethod('Backlink')) {
732
            $backlink = $toplevelController->Backlink();
733
        } elseif ($this->owner->getController()->hasMethod('Breadcrumbs')) {
734
            $parents = $this->owner->getController()->Breadcrumbs(false)->items;
735
            $backlink = array_pop($parents)->Link;
736
        }
737
        if (!$backlink) {
738
            $backlink = $toplevelController->Link();
739
        }
740
741
        return $backlink;
742
    }
743
744
    /**
745
     * Response object for this request after a successful save
746
     *
747
     * @param bool $isNewRecord True if this record was just created
748
     * @param DataObject $record
749
     * @return HTTPResponse|DBHTMLText|string
750
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
751
     * because it is a protected method and not visible to a decorator!
752
     */
753
    protected function redirectAfterAction($isNewRecord, $record = null)
754
    {
755
        $controller = $this->getToplevelController();
756
757
        if ($this->isLeftAndMain($controller)) {
758
            // CMSMain => redirect to show
759
            if ($this->owner->hasMethod("LinkPageEdit")) {
760
                return $controller->redirect($this->owner->LinkPageEdit($record->ID));
0 ignored issues
show
Bug introduced by
The method LinkPageEdit() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

760
                return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ LinkPageEdit($record->ID));
Loading history...
761
            }
762
        }
763
764
        if ($isNewRecord) {
765
            return $controller->redirect($this->owner->Link());
0 ignored issues
show
Bug introduced by
The method Link() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

765
            return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ Link());
Loading history...
766
        }
767
        if ($this->owner->gridField && $this->owner->gridField->getList()->byID($this->owner->record->ID)) {
0 ignored issues
show
Bug introduced by
The method byID() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Limitable. Are you sure you never get one of those? ( Ignorable by Annotation )

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

767
        if ($this->owner->gridField && $this->owner->gridField->getList()->/** @scrutinizer ignore-call */ byID($this->owner->record->ID)) {
Loading history...
Bug Best Practice introduced by
The property gridField does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
Bug Best Practice introduced by
The property record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
Loading history...
768
            // Return new view, as we can't do a "virtual redirect" via the CMS Ajax
769
            // to the same URL (it assumes that its content is already current, and doesn't reload)
770
            return $this->owner->edit($controller->getRequest());
0 ignored issues
show
Bug introduced by
The method edit() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. 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

770
            return $this->owner->/** @scrutinizer ignore-call */ edit($controller->getRequest());
Loading history...
771
        }
772
        // Changes to the record properties might've excluded the record from
773
        // a filtered list, so return back to the main view if it can't be found
774
        $noActionURL = $url = $controller->getRequest()->getURL();
775
776
        // The controller may not have these
777
        if ($controller->hasMethod('getAction')) {
778
            $action = $controller->getAction();
779
            // Handle GridField detail form editing
780
            if (strpos($url, 'ItemEditForm') !== false) {
781
                $action = 'ItemEditForm';
782
            }
783
            if ($action) {
784
                $noActionURL = $controller->removeAction($url, $action);
785
            }
786
        } else {
787
            // Simple fallback (last index of)
788
            $pos = strrpos($url ?? '', 'ItemEditForm');
789
            $noActionURL = substr($url ?? '', 0, $pos);
790
        }
791
792
        $controller->getRequest()->addHeader('X-Pjax', 'Content');
793
794
        return $controller->redirect($noActionURL, 302);
795
    }
796
}
797