Passed
Pull Request — master (#22)
by Simon
11:20
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;
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
use SilverStripe\SiteConfig\SiteConfigLeftAndMain;
0 ignored issues
show
Bug introduced by
The type SilverStripe\SiteConfig\SiteConfigLeftAndMain 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...
27
28
/**
29
 * Decorates GridDetailForm_ItemRequest to use new form actions and buttons.
30
 *
31
 * This is also applied to LeftAndMain to allow actions on pages
32
 * Warning: LeftAndMain doesn't call updateItemEditForm
33
 *
34
 * This is a lightweight version of BetterButtons that use default getCMSActions functionnality
35
 * on DataObjects
36
 *
37
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons
38
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons/blob/master/src/Extensions/GridFieldBetterButtonsItemRequest.php
39
 * @property LeftAndMain|GridFieldDetailForm_ItemRequest|ActionsGridFieldItemRequest $owner
40
 */
41
class ActionsGridFieldItemRequest extends DataExtension
42
{
43
    use Configurable;
44
    use Extensible;
45
46
    /**
47
     * @config
48
     * @var boolean
49
     */
50
    private static $enable_save_prev_next = true;
51
52
    /**
53
     * @config
54
     * @var boolean
55
     */
56
    private static $enable_save_close = true;
57
58
    /**
59
     * @config
60
     * @var boolean
61
     */
62
    private static $enable_delete_right = true;
63
64
    /**
65
     * @config
66
     * @var boolean
67
     */
68
    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...
69
70
    /**
71
     * @var array Allowed controller actions
72
     */
73
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
74
        'doSaveAndClose',
75
        'doSaveAndNext',
76
        'doSaveAndPrev',
77
        'doCustomAction', // For CustomAction
78
        'doCustomLink', // For CustomLink
79
    ];
80
81
    /**
82
     * @return array
83
     */
84
    protected function getAvailableActions($actions)
85
    {
86
        $list = [];
87
        foreach ($actions as $action) {
88
            $list[] = $action->getName();
89
        }
90
91
        return $list;
92
    }
93
94
    /**
95
     * Updates the detail form to include new form actions and buttons
96
     *
97
     * This is called by GridFieldDetailForm_ItemRequest
98
     *
99
     * @param Form $form The ItemEditForm object
100
     */
101
    public function updateItemEditForm($form)
102
    {
103
        $itemRequest = $this->owner;
104
105
        /** @var DataObject $record */
106
        $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...
107
        if (!$this->checkCan($record)) {
108
            return;
109
        }
110
111
        // We get the actions as defined on our record
112
        /** @var FieldList $CMSActions */
113
        $CMSActions = $record->getCMSActions();
114
115
        // address Silverstripe bug when SiteTree buttons are broken
116
        // @link https://github.com/silverstripe/silverstripe-cms/issues/2702
117
        $CMSActions->setForm($form);
118
119
        // We can the actions from the GridFieldDetailForm_ItemRequest
120
        // It sets the Save and Delete buttons + Right Group
121
        $actions = $form->Actions();
122
123
        // The default button group that contains the Save or Create action
124
        // @link https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/how_tos/extend_cms_interface/#extending-the-cms-actions
125
        $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...
126
127
        // If it doesn't exist, push to default group
128
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
129
            $MajorActions = $actions;
0 ignored issues
show
Unused Code introduced by
The assignment to $MajorActions is dead and can be removed.
Loading history...
130
        }
131
132
        // Push our actions that are otherwise ignored by SilverStripe
133
        foreach ($CMSActions as $action) {
134
            // Avoid duplicated actions (eg: when added by SilverStripe\Versioned\VersionedGridFieldItemRequest)
135
            if ($actions->fieldByName($action->getName())) {
136
                continue;
137
            }
138
            $actions->push($action);
139
        }
140
141
        // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions
142
        // and we don't want to duplicate it
143
        $this->processDropUpMenu($actions);
144
145
        // Add extension hook
146
        $this->extend('onBeforeUpdateCMSActions', $actions, $record);
147
        $record->extend('onBeforeUpdateCMSActions', $actions);
148
149
        $ActionMenus = $actions->fieldByName('ActionMenus');
150
        // Re-insert ActionMenus to make sure they always follow the buttons
151
        if ($ActionMenus) {
152
            $actions->remove($ActionMenus);
153
            $actions->push($ActionMenus);
154
        }
155
156
        // We have a 4.4 setup, before that there was no RightGroup
157
        $RightGroup = $actions->fieldByName('RightGroup');
158
159
        // Insert again to make sure our actions are properly placed after apply changes
160
        if ($RightGroup) {
161
            $actions->remove($RightGroup);
162
            $actions->push($RightGroup);
163
        }
164
165
        $opts = [
166
            'save_close'     => self::config()->enable_save_close,
167
            'save_prev_next' => self::config()->enable_save_prev_next,
168
            'delete_right'   => self::config()->enable_delete_right,
169
        ];
170
        if ($record->hasMethod('getCMSActionsOptions')) {
171
            $opts = array_merge($opts, $record->getCMSActionsOptions());
172
        }
173
174
        if ($opts['save_close']) {
175
            $this->addSaveAndClose($actions, $record);
176
        }
177
178
        if ($opts['save_prev_next']) {
179
            $this->addSaveNextAndPrevious($actions, $record);
180
        }
181
182
        if ($opts['delete_right']) {
183
            $this->moveCancelAndDelete($actions, $record);
184
        }
185
186
        // Add extension hook
187
        $this->extend('onAfterUpdateCMSActions', $actions, $record);
188
        $record->extend('onAfterUpdateCMSActions', $actions);
189
    }
190
191
192
    /**
193
     * Collect all Drop-Up actions into a menu.
194
     * @param FieldList $actions
195
     * @return void
196
     */
197
    protected function processDropUpMenu($actions)
198
    {
199
        // The Drop-up container may already exist
200
        $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...
201
        foreach ($actions as $action) {
202
            if ($action->hasMethod('getDropUp') && $action->getDropUp()) {
203
                if (!$dropUpContainer) {
204
                    $dropUpContainer = $this->createDropUpContainer($actions);
205
                }
206
                $action->getContainerFieldList()->removeByName($action->getName());
207
                $dropUpContainer->push($action);
208
            }
209
        }
210
    }
211
212
    /**
213
     * Prepares a Drop-Up menu
214
     * @param FieldList $actions
215
     * @return Tab
216
     */
217
    protected function createDropUpContainer($actions)
218
    {
219
        $rootTabSet = new TabSet('ActionMenus');
220
        $dropUpContainer = new Tab(
221
            'MoreOptions',
222
            _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons')
223
        );
224
        $dropUpContainer->addExtraClass('popover-actions-simulate');
225
        $rootTabSet->push($dropUpContainer);
226
        $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder');
227
228
        $actions->insertBefore('RightGroup', $rootTabSet);
229
230
        return $dropUpContainer;
231
    }
232
233
    /**
234
     * Check if a record can be edited/created/exists
235
     * @param DataObject $record
236
     * @return bool
237
     */
238
    protected function checkCan($record)
239
    {
240
        if (!$record->canEdit() || (!$record->ID && !$record->canCreate())) {
241
            return false;
242
        }
243
244
        return true;
245
    }
246
247
    /**
248
     * @param FieldList $actions
249
     * @param DataObject $record
250
     * @return void
251
     */
252
    public function moveCancelAndDelete(FieldList $actions, DataObject $record)
253
    {
254
        // We have a 4.4 setup, before that there was no RightGroup
255
        $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...
256
257
        // Move delete at the end
258
        $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...
259
        if ($deleteAction) {
0 ignored issues
show
introduced by
$deleteAction is of type null, thus it always evaluated to false.
Loading history...
260
            // Move at the end of the stack
261
            $actions->remove($deleteAction);
262
            $actions->push($deleteAction);
263
264
            if (!$RightGroup) {
265
                // Only necessary pre 4.4
266
                $deleteAction->addExtraClass('align-right');
267
            }
268
            // Set custom title
269
            if ($record->hasMethod('getDeleteButtonTitle')) {
270
                $deleteAction->setTitle($record->getDeleteButtonTitle());
271
            }
272
        }
273
        // Move cancel at the end
274
        $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...
275
        if ($cancelButton) {
0 ignored issues
show
introduced by
$cancelButton is of type null, thus it always evaluated to false.
Loading history...
276
            // Move at the end of the stack
277
            $actions->remove($cancelButton);
278
            $actions->push($cancelButton);
279
            if (!$RightGroup) {
280
                // Only necessary pre 4.4
281
                $cancelButton->addExtraClass('align-right');
282
            }
283
            // Set custom titlte
284
            if ($record->hasMethod('getCancelButtonTitle')) {
285
                $cancelButton->setTitle($record->getCancelButtonTitle());
286
            }
287
        }
288
    }
289
290
    /**
291
     * @param DataObject $record
292
     * @return int
293
     */
294
    public function getCustomPreviousRecordID(DataObject $record)
295
    {
296
        if ($record->hasMethod('PrevRecord')) {
297
            return $record->PrevRecord()->ID ?? 0;
298
        }
299
300
        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

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

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

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

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

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

649
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
650
        $next = $class::get()->byID($getNextRecordID);
651
652
        $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

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

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

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

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

756
                return $controller->redirect($this->owner->/** @scrutinizer ignore-call */ LinkPageEdit($record->ID));
Loading history...
757
            }
758
        }
759
760
        if ($isNewRecord) {
761
            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

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

763
        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 record does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest. Did you maybe forget to declare it?
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...
764
            // Return new view, as we can't do a "virtual redirect" via the CMS Ajax
765
            // to the same URL (it assumes that its content is already current, and doesn't reload)
766
            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

766
            return $this->owner->/** @scrutinizer ignore-call */ edit($controller->getRequest());
Loading history...
767
        }
768
        // Changes to the record properties might've excluded the record from
769
        // a filtered list, so return back to the main view if it can't be found
770
        $url = $controller->getRequest()->getURL();
771
        $action = $controller->getAction();
772
        $noActionURL = $url;
773
        // Handle GridField detail form editing
774
        if (strpos($url, 'ItemEditForm') !== false) {
775
            $action = 'ItemEditForm';
776
        }
777
        if ($action) {
778
            $noActionURL = $controller->removeAction($url, $action);
779
        }
780
        $controller->getRequest()->addHeader('X-Pjax', 'Content');
781
782
        return $controller->redirect($noActionURL, 302);
783
784
    }
785
}
786