Passed
Push — master ( 507e08...324382 )
by Thomas
02:42
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
     * Check if a record can be edited/created/exists
237
     * @param DataObject $record
238
     * @return bool
239
     */
240
    protected function checkCan($record)
241
    {
242
        if (!$record->canEdit() || (!$record->ID && !$record->canCreate())) {
243
            return false;
244
        }
245
246
        return true;
247
    }
248
249
    /**
250
     * @param FieldList $actions
251
     * @param DataObject $record
252
     * @return void
253
     */
254
    public function moveCancelAndDelete(FieldList $actions, DataObject $record)
255
    {
256
        // We have a 4.4 setup, before that there was no RightGroup
257
        $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...
258
259
        // Move delete at the end
260
        $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...
261
        if ($deleteAction) {
0 ignored issues
show
introduced by
$deleteAction is of type null, thus it always evaluated to false.
Loading history...
262
            // Move at the end of the stack
263
            $actions->remove($deleteAction);
264
            $actions->push($deleteAction);
265
266
            if (!$RightGroup) {
267
                // Only necessary pre 4.4
268
                $deleteAction->addExtraClass('align-right');
269
            }
270
            // Set custom title
271
            if ($record->hasMethod('getDeleteButtonTitle')) {
272
                $deleteAction->setTitle($record->getDeleteButtonTitle());
273
            }
274
        }
275
        // Move cancel at the end
276
        $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...
277
        if ($cancelButton) {
0 ignored issues
show
introduced by
$cancelButton is of type null, thus it always evaluated to false.
Loading history...
278
            // Move at the end of the stack
279
            $actions->remove($cancelButton);
280
            $actions->push($cancelButton);
281
            if (!$RightGroup) {
282
                // Only necessary pre 4.4
283
                $cancelButton->addExtraClass('align-right');
284
            }
285
            // Set custom titlte
286
            if ($record->hasMethod('getCancelButtonTitle')) {
287
                $cancelButton->setTitle($record->getCancelButtonTitle());
288
            }
289
        }
290
    }
291
292
    /**
293
     * @param DataObject $record
294
     * @return int
295
     */
296
    public function getCustomPreviousRecordID(DataObject $record)
297
    {
298
        if ($record->hasMethod('PrevRecord')) {
299
            return $record->PrevRecord()->ID ?? 0;
300
        }
301
302
        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

302
        return $this->owner->/** @scrutinizer ignore-call */ getPreviousRecordID();
Loading history...
303
    }
304
305
    /**
306
     * @param DataObject $record
307
     * @return int
308
     */
309
    public function getCustomNextRecordID(DataObject $record)
310
    {
311
        if ($record->hasMethod('NextRecord')) {
312
            return $record->NextRecord()->ID ?? 0;
313
        }
314
315
        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

315
        return $this->owner->/** @scrutinizer ignore-call */ getNextRecordID();
Loading history...
316
    }
317
318
    /**
319
     * @param FieldList $actions
320
     * @param DataObject $record
321
     * @return void
322
     */
323
    public function addSaveNextAndPrevious(FieldList $actions, DataObject $record)
324
    {
325
        if (!$record->canEdit() || !$record->ID) {
326
            return;
327
        }
328
329
        $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...
330
331
        // If it doesn't exist, push to default group
332
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
333
            $MajorActions = $actions;
334
        }
335
336
        // TODO: check why with paginator, after the first page, getPreviousRecordID/getNextRecordID tend to not work properly
337
        $getPreviousRecordID = $this->getCustomPreviousRecordID($record);
338
        $getNextRecordID = $this->getCustomNextRecordID($record);
339
340
        // Coupling for HasPrevNextUtils
341
        if (Controller::has_curr()) {
342
            /** @var HTTPRequest $request */
343
            $request = Controller::curr()->getRequest();
344
            $routeParams = $request->routeParams();
345
            $routeParams['PreviousRecordID'] = $getPreviousRecordID;
346
            $routeParams['NextRecordID'] = $getNextRecordID;
347
            $request->setRouteParams($routeParams);
348
        }
349
350
        if ($getPreviousRecordID) {
351
            $doSaveAndPrev = new FormAction('doSaveAndPrev', _t('ActionsGridFieldItemRequest.SAVEANDPREVIOUS', 'Save and Previous'));
352
            $doSaveAndPrev->addExtraClass($this->getBtnClassForRecord($record));
353
            $doSaveAndPrev->addExtraClass('font-icon-angle-double-left btn-mobile-collapse');
354
            $doSaveAndPrev->setUseButtonTag(true);
355
            $MajorActions->push($doSaveAndPrev);
356
        }
357
        if ($getNextRecordID) {
358
            $doSaveAndNext = new FormAction('doSaveAndNext', _t('ActionsGridFieldItemRequest.SAVEANDNEXT', 'Save and Next'));
359
            $doSaveAndNext->addExtraClass($this->getBtnClassForRecord($record));
360
            $doSaveAndNext->addExtraClass('font-icon-angle-double-right btn-mobile-collapse');
361
            $doSaveAndNext->setUseButtonTag(true);
362
            $MajorActions->push($doSaveAndNext);
363
        }
364
    }
365
366
    /**
367
     * @param FieldList $actions
368
     * @param DataObject $record
369
     * @return void
370
     */
371
    public function addSaveAndClose(FieldList $actions, DataObject $record)
372
    {
373
        if (!$this->checkCan($record)) {
374
            return;
375
        }
376
377
        $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...
378
379
        // If it doesn't exist, push to default group
380
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
381
            $MajorActions = $actions;
382
        }
383
384
        if ($record->ID) {
385
            $label = _t('ActionsGridFieldItemRequest.SAVEANDCLOSE', 'Save and Close');
386
        } else {
387
            $label = _t('ActionsGridFieldItemRequest.CREATEANDCLOSE', 'Create and Close');
388
        }
389
        $saveAndClose = new FormAction('doSaveAndClose', $label);
390
        $saveAndClose->addExtraClass($this->getBtnClassForRecord($record));
391
        $saveAndClose->setAttribute('data-text-alternate', $label);
392
        if ($record->ID) {
393
            $saveAndClose->setAttribute('data-btn-alternate-add', 'btn-primary');
394
            $saveAndClose->setAttribute('data-btn-alternate-remove', 'btn-outline-primary');
395
        }
396
        $saveAndClose->addExtraClass('font-icon-level-up btn-mobile-collapse');
397
        $saveAndClose->setUseButtonTag(true);
398
        $MajorActions->push($saveAndClose);
399
    }
400
401
    /**
402
     * New and existing records have different classes
403
     *
404
     * @param DataObject $record
405
     * @return string
406
     */
407
    protected function getBtnClassForRecord(DataObject $record)
408
    {
409
        if ($record->ID) {
410
            return 'btn-outline-primary';
411
        }
412
413
        return 'btn-primary';
414
    }
415
416
    /**
417
     * @param $action
418
     * @param $definedActions
419
     * @return mixed|CompositeField|null
420
     */
421
    protected static function findAction($action, $definedActions)
422
    {
423
        $result = null;
424
425
        foreach ($definedActions as $definedAction) {
426
            if (is_a($definedAction, CompositeField::class)) {
427
                $result = self::findAction($action, $definedAction->FieldList());
428
            }
429
430
            $definedActionName = $definedAction->getName();
431
432
            if ($definedAction->hasMethod('actionName')) {
433
                $definedActionName = $definedAction->actionName();
434
            }
435
            if ($definedActionName === $action) {
436
                $result = $definedAction;
437
            }
438
        }
439
440
        return $result;
441
    }
442
443
    /**
444
     * Forward a given action to a DataObject
445
     *
446
     * Action must be declared in getCMSActions to be called
447
     *
448
     * @param string $action
449
     * @param array $data
450
     * @param Form $form
451
     * @return HTTPResponse|DBHTMLText|string
452
     * @throws HTTPResponse_Exception
453
     */
454
    protected function forwardActionToRecord($action, $data = [], $form = null)
455
    {
456
        $controller = $this->getToplevelController();
457
458
        // We have an item request or a controller that can provide a record
459
        $record = null;
460
        if ($this->owner->hasMethod('ItemEditForm')) {
461
            // It's a request handler. Don't check for a specific class as it may be subclassed
462
            $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...
463
        } elseif ($controller->hasMethod('save_siteconfig')) {
464
            // Check for any type of siteconfig controller
465
            $record = SiteConfig::current_site_config();
466
        } elseif (!empty($data['ClassName']) && !empty($data['ID'])) {
467
            $record = DataObject::get_by_id($data['ClassName'], $data['ID']);
468
        } elseif ($controller->hasMethod("getRecord")) {
469
            $record = $controller->getRecord();
470
        }
471
472
        if (!$record) {
473
            throw new Exception("No record to handle the action $action on " . get_class($controller));
474
        }
475
        $definedActions = $record->getCMSActions();
476
        // Check if the action is indeed available
477
        $clickedAction = null;
478
        if (!empty($definedActions)) {
479
            $clickedAction = self::findAction($action, $definedActions);
480
        }
481
        if (!$clickedAction) {
482
            $class = get_class($record);
483
            $availableActions = implode(',', $this->getAvailableActions($definedActions));
484
            if (!$availableActions) {
485
                $availableActions = "(no available actions, please check getCMSActions)";
486
            }
487
488
            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

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

631
        $this->owner->/** @scrutinizer ignore-call */ 
632
                      doSave($data, $form);
Loading history...
632
        // Redirect after save
633
        $controller = $this->getToplevelController();
634
635
        $location = $this->getBackLink();
636
        $controller->getResponse()->addHeader("X-Pjax", "Content");
637
        // Prevent Already directed to errors
638
        $controller->getResponse()->addHeader("Location", $location);
639
640
        return $controller->redirect($location);
641
    }
642
643
    /**
644
     * Saves the form and goes back to the next item
645
     *
646
     * @param array $data The form data
647
     * @param Form $form The form object
648
     */
649
    public function doSaveAndNext($data, $form)
650
    {
651
        $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...
652
        $this->owner->doSave($data, $form);
653
        // Redirect after save
654
        $controller = $this->getToplevelController();
655
        $controller->getResponse()->addHeader("X-Pjax", "Content");
656
657
        $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

657
        $getNextRecordID = $this->getCustomNextRecordID(/** @scrutinizer ignore-type */ $record);
Loading history...
658
        $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

658
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
659
        $next = $class::get()->byID($getNextRecordID);
660
661
        $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

661
        /** @scrutinizer ignore-call */ 
662
        $link = $this->owner->getEditLink($getNextRecordID);
Loading history...
662
663
        // Link to a specific tab if set, see cms-actions.js
664
        if ($next && !empty($data['_activetab'])) {
665
            $link .= sprintf('#%s', $data['_activetab']);
666
        }
667
668
        return $controller->redirect($link);
669
    }
670
671
    /**
672
     * Saves the form and goes to the previous item
673
     *
674
     * @param array $data The form data
675
     * @param Form $form The form object
676
     */
677
    public function doSaveAndPrev($data, $form)
678
    {
679
        $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...
680
        $this->owner->doSave($data, $form);
681
        // Redirect after save
682
        $controller = $this->getToplevelController();
683
        $controller->getResponse()->addHeader("X-Pjax", "Content");
684
685
        $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

685
        $getPreviousRecordID = $this->getCustomPreviousRecordID(/** @scrutinizer ignore-type */ $record);
Loading history...
686
        $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

686
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
687
        $prev = $class::get()->byID($getPreviousRecordID);
688
689
        $link = $this->owner->getEditLink($getPreviousRecordID);
690
691
        // Link to a specific tab if set, see cms-actions.js
692
        if ($prev && !empty($data['_activetab'])) {
693
            $link .= sprintf('#%s', $data['_activetab']);
694
        }
695
696
        return $controller->redirect($link);
697
    }
698
699
    /**
700
     * Gets the top level controller.
701
     *
702
     * @return Controller
703
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
704
     * because it is a protected method and not visible to a decorator!
705
     */
706
    protected function getToplevelController()
707
    {
708
        if ($this->isLeftAndMain($this->owner)) {
709
            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...
710
        }
711
        if (!$this->owner->hasMethod("getController")) {
712
            return Controller::curr();
713
        }
714
        $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

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

768
                return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ LinkPageEdit($record->ID));
Loading history...
769
            }
770
        }
771
772
        if ($isNewRecord) {
773
            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

773
            return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ Link());
Loading history...
774
        }
775
        if ($this->owner->gridField && $this->owner->gridField->getList()->byID($this->owner->record->ID)) {
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...
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

775
        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...
776
            // Return new view, as we can't do a "virtual redirect" via the CMS Ajax
777
            // to the same URL (it assumes that its content is already current, and doesn't reload)
778
            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

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