Passed
Push — master ( feb734...8c4c1e )
by Thomas
02:30
created

getBtnClassForRecord()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace LeKoala\CmsActions;
4
5
use Exception;
6
use SilverStripe\Admin\LeftAndMain;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Admin\LeftAndMain 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...
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 (!$this->checkCan($record)) {
107
            return;
108
        }
109
110
        // We get the actions as defined on our record
111
        /** @var FieldList $CMSActions */
112
        $CMSActions = $record->getCMSActions();
113
114
        // address Silverstripe bug when SiteTree buttons are broken
115
        // @link https://github.com/silverstripe/silverstripe-cms/issues/2702
116
        $CMSActions->setForm($form);
117
118
        // We can the actions from the GridFieldDetailForm_ItemRequest
119
        // It sets the Save and Delete buttons + Right Group
120
        $actions = $form->Actions();
121
122
        // The default button group that contains the Save or Create action
123
        // @link https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/how_tos/extend_cms_interface/#extending-the-cms-actions
124
        $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...
125
126
        // If it doesn't exist, push to default group
127
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
128
            $MajorActions = $actions;
0 ignored issues
show
Unused Code introduced by
The assignment to $MajorActions is dead and can be removed.
Loading history...
129
        }
130
131
        // Push our actions that are otherwise ignored by SilverStripe
132
        foreach ($CMSActions as $action) {
133
            // Avoid duplicated actions (eg: when added by SilverStripe\Versioned\VersionedGridFieldItemRequest)
134
            if ($actions->fieldByName($action->getName())) {
135
                continue;
136
            }
137
            $actions->push($action);
138
        }
139
140
        // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions
141
        // and we don't want to duplicate it
142
        $this->processDropUpMenu($actions);
143
144
        // Add extension hook
145
        $this->extend('onBeforeUpdateCMSActions', $actions, $record);
146
        $record->extend('onBeforeUpdateCMSActions', $actions);
147
148
        $ActionMenus = $actions->fieldByName('ActionMenus');
149
        // Re-insert ActionMenus to make sure they always follow the buttons
150
        if ($ActionMenus) {
151
            $actions->remove($ActionMenus);
152
            $actions->push($ActionMenus);
153
        }
154
155
        // We have a 4.4 setup, before that there was no RightGroup
156
        $RightGroup = $actions->fieldByName('RightGroup');
157
158
        // Insert again to make sure our actions are properly placed after apply changes
159
        if ($RightGroup) {
160
            $actions->remove($RightGroup);
161
            $actions->push($RightGroup);
162
        }
163
164
        $opts = [
165
            'save_close'     => self::config()->enable_save_close,
166
            'save_prev_next' => self::config()->enable_save_prev_next,
167
            'delete_right'   => self::config()->enable_delete_right,
168
        ];
169
        if ($record->hasMethod('getCMSActionsOptions')) {
170
            $opts = array_merge($opts, $record->getCMSActionsOptions());
171
        }
172
173
        if ($opts['save_close']) {
174
            $this->addSaveAndClose($actions, $record);
175
        }
176
177
        if ($opts['save_prev_next']) {
178
            $this->addSaveNextAndPrevious($actions, $record);
179
        }
180
181
        if ($opts['delete_right']) {
182
            $this->moveCancelAndDelete($actions, $record);
183
        }
184
185
        // Add extension hook
186
        $this->extend('onAfterUpdateCMSActions', $actions, $record);
187
        $record->extend('onAfterUpdateCMSActions', $actions);
188
    }
189
190
191
    /**
192
     * Collect all Drop-Up actions into a menu.
193
     * @param FieldList $actions
194
     * @return void
195
     */
196
    protected function processDropUpMenu($actions)
197
    {
198
        // The Drop-up container may already exist
199
        $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...
200
        foreach ($actions as $action) {
201
            if ($action->hasMethod('getDropUp') && $action->getDropUp()) {
202
                if (!$dropUpContainer) {
203
                    $dropUpContainer = $this->createDropUpContainer($actions);
204
                }
205
                $action->getContainerFieldList()->removeByName($action->getName());
206
                $dropUpContainer->push($action);
207
            }
208
        }
209
    }
210
211
    /**
212
     * Prepares a Drop-Up menu
213
     * @param FieldList $actions
214
     * @return Tab
215
     */
216
    protected function createDropUpContainer($actions)
217
    {
218
        $rootTabSet = new TabSet('ActionMenus');
219
        $dropUpContainer = new Tab(
220
            'MoreOptions',
221
            _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons')
222
        );
223
        $dropUpContainer->addExtraClass('popover-actions-simulate');
224
        $rootTabSet->push($dropUpContainer);
225
        $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder');
226
227
        $actions->insertBefore('RightGroup', $rootTabSet);
228
229
        return $dropUpContainer;
230
    }
231
232
    /**
233
     * Check if a record can be edited/created/exists
234
     * @param DataObject $record
235
     * @return bool
236
     */
237
    protected function checkCan($record)
238
    {
239
        if (!$record->canEdit() || (!$record->ID && !$record->canCreate())) {
240
            return false;
241
        }
242
243
        return true;
244
    }
245
246
    /**
247
     * @param FieldList $actions
248
     * @param DataObject $record
249
     * @return void
250
     */
251
    public function moveCancelAndDelete(FieldList $actions, DataObject $record)
252
    {
253
        // We have a 4.4 setup, before that there was no RightGroup
254
        $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...
255
256
        // Move delete at the end
257
        $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...
258
        if ($deleteAction) {
0 ignored issues
show
introduced by
$deleteAction is of type null, thus it always evaluated to false.
Loading history...
259
            // Move at the end of the stack
260
            $actions->remove($deleteAction);
261
            $actions->push($deleteAction);
262
263
            if (!$RightGroup) {
264
                // Only necessary pre 4.4
265
                $deleteAction->addExtraClass('align-right');
266
            }
267
            // Set custom title
268
            if ($record->hasMethod('getDeleteButtonTitle')) {
269
                $deleteAction->setTitle($record->getDeleteButtonTitle());
270
            }
271
        }
272
        // Move cancel at the end
273
        $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...
274
        if ($cancelButton) {
0 ignored issues
show
introduced by
$cancelButton is of type null, thus it always evaluated to false.
Loading history...
275
            // Move at the end of the stack
276
            $actions->remove($cancelButton);
277
            $actions->push($cancelButton);
278
            if (!$RightGroup) {
279
                // Only necessary pre 4.4
280
                $cancelButton->addExtraClass('align-right');
281
            }
282
            // Set custom titlte
283
            if ($record->hasMethod('getCancelButtonTitle')) {
284
                $cancelButton->setTitle($record->getCancelButtonTitle());
285
            }
286
        }
287
    }
288
289
    /**
290
     * @param DataObject $record
291
     * @return int
292
     */
293
    public function getCustomPreviousRecordID(DataObject $record)
294
    {
295
        if ($record->hasMethod('PrevRecord')) {
296
            return $record->PrevRecord()->ID ?? 0;
297
        }
298
299
        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

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

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

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

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

655
        /** @scrutinizer ignore-call */ 
656
        $link = $this->owner->getEditLink($getNextRecordID);
Loading history...
656
657
        // Link to a specific tab if set, see cms-actions.js
658
        if ($next && !empty($data['_activetab'])) {
659
            $link .= sprintf('#%s', $data['_activetab']);
660
        }
661
662
        return $controller->redirect($link);
663
    }
664
665
    /**
666
     * Saves the form and goes to the previous item
667
     *
668
     * @param array $data The form data
669
     * @param Form $form The form object
670
     */
671
    public function doSaveAndPrev($data, $form)
672
    {
673
        $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...
674
        $this->owner->doSave($data, $form);
675
        // Redirect after save
676
        $controller = $this->getToplevelController();
677
        $controller->getResponse()->addHeader("X-Pjax", "Content");
678
679
        $getPreviousRecordID = $this->getCustomPreviousRecordID($record);
680
        $class = get_class($record);
681
        $prev = $class::get()->byID($getPreviousRecordID);
682
683
        $link = $this->owner->getEditLink($getPreviousRecordID);
684
685
        // Link to a specific tab if set, see cms-actions.js
686
        if ($prev && !empty($data['_activetab'])) {
687
            $link .= sprintf('#%s', $data['_activetab']);
688
        }
689
690
        return $controller->redirect($link);
691
    }
692
693
    /**
694
     * Gets the top level controller.
695
     *
696
     * @return Controller
697
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
698
     * because it is a protected method and not visible to a decorator!
699
     */
700
    protected function getToplevelController()
701
    {
702
        if ($this->isLeftAndMain($this->owner)) {
703
            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...
704
        }
705
        if (!$this->owner->hasMethod("getController")) {
706
            return Controller::curr();
707
        }
708
        $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

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

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

769
            return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ Link());
Loading history...
770
        }
771
        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

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

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