Passed
Pull Request — master (#21)
by
unknown
03:05
created

ActionsGridFieldItemRequest::findAction()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 20
rs 9.6111
1
<?php
2
3
namespace LeKoala\CmsActions;
4
5
use Exception;
6
use SilverStripe\Forms\Tab;
7
use SilverStripe\Forms\Form;
8
use SilverStripe\Forms\TabSet;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\Forms\FieldList;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Forms\FormAction;
13
use SilverStripe\Admin\LeftAndMain;
14
use SilverStripe\ORM\DataExtension;
15
use SilverStripe\Control\Controller;
16
use SilverStripe\Control\HTTPRequest;
17
use SilverStripe\Control\HTTPResponse;
18
use SilverStripe\ORM\ValidationResult;
19
use SilverStripe\Core\Config\Configurable;
20
use SilverStripe\Core\Extensible;
21
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...
22
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
23
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...
24
25
/**
26
 * Decorates GridDetailForm_ItemRequest to use new form actions and buttons.
27
 * This is also applied to LeftAndMain to allow actions on pages
28
 * Warning: LeftAndMain doesn't call updateItemEditForm
29
 *
30
 * This is a lightweight version of BetterButtons that use default getCMSActions functionnality
31
 * on DataObjects
32
 *
33
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons
34
 * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons/blob/master/src/Extensions/GridFieldBetterButtonsItemRequest.php
35
 * @property \SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest|\SilverStripe\Admin\LeftAndMain $owner
36
 */
37
class ActionsGridFieldItemRequest extends DataExtension
38
{
39
    use Configurable;
40
    use Extensible;
41
42
    /**
43
     * @config
44
     * @var boolean
45
     */
46
    private static $enable_save_prev_next = true;
47
48
    /**
49
     * @config
50
     * @var boolean
51
     */
52
    private static $enable_save_close = true;
53
54
    /**
55
     * @config
56
     * @var boolean
57
     */
58
    private static $enable_delete_right = true;
59
60
    /**
61
     * @config
62
     * @var boolean
63
     */
64
    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...
65
66
    /**
67
     * @var array Allowed controller actions
68
     */
69
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
70
        'doSaveAndClose',
71
        'doSaveAndNext',
72
        'doSaveAndPrev',
73
        'doCustomAction', // For CustomAction
74
        'doCustomLink', // For CustomLink
75
    ];
76
77
    /**
78
     * @return array
79
     */
80
    protected function getAvailableActions($actions)
81
    {
82
        $list = [];
83
        foreach ($actions as $action) {
84
            $list[] = $action->getName();
85
        }
86
        return $list;
87
    }
88
89
    /**
90
     * Updates the detail form to include new form actions and buttons
91
     *
92
     * This is called by GridFieldDetailForm_ItemRequest
93
     *
94
     * @param Form The ItemEditForm object
0 ignored issues
show
Bug introduced by
The type LeKoala\CmsActions\The was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
95
     */
96
    public function updateItemEditForm($form)
97
    {
98
        $itemRequest = $this->owner;
99
100
        /** @var DataObject $record  */
101
        $record = $itemRequest->record;
102
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
103
            $record = $form->getRecord();
104
        }
105
        if (!$record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
106
            return;
107
        }
108
109
        // We get the actions as defined on our record
110
        /** @var FieldList $CMSActions  */
111
        $CMSActions = $record->getCMSActions();
112
113
        // address Silverstripe bug when SiteTree buttons are broken
114
        // @link https://github.com/silverstripe/silverstripe-cms/issues/2702
115
        $CMSActions->setForm($form);
116
117
        // We can the actions from the GridFieldDetailForm_ItemRequest
118
        // It sets the Save and Delete buttons + Right Group
119
        $actions = $form->Actions();
120
121
        // The default button group that contains the Save or Create action
122
        // @link https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/how_tos/extend_cms_interface/#extending-the-cms-actions
123
        $MajorActions = $actions->fieldByName('MajorActions');
124
125
        // If it doesn't exist, push to default group
126
        if (!$MajorActions) {
127
            $MajorActions = $actions;
0 ignored issues
show
Unused Code introduced by
The assignment to $MajorActions is dead and can be removed.
Loading history...
128
        }
129
130
        // Push our actions that are otherwise ignored by SilverStripe
131
        foreach ($CMSActions as $action) {
132
            // Avoid duplicated actions (eg: when added by SilverStripe\Versioned\VersionedGridFieldItemRequest)
133
            if ($actions->fieldByName($action->getName())) {
134
                continue;
135
            }
136
            $actions->push($action);
137
        }
138
139
        // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions
140
        // and we don't want to duplicate it
141
        $this->processDropUpMenu($actions);
142
143
        // Add extension hook
144
        $this->extend('onBeforeUpdateCMSActions', $actions, $record);
145
        $record->extend('onBeforeUpdateCMSActions', $actions);
146
147
        $ActionMenus = $actions->fieldByName('ActionMenus');
148
        // Re-insert ActionMenus to make sure they always follow the buttons
149
        if ($ActionMenus) {
150
            $actions->remove($ActionMenus);
151
            $actions->push($ActionMenus);
152
        }
153
154
        // We have a 4.4 setup, before that there was no RightGroup
155
        $RightGroup = $actions->fieldByName('RightGroup');
156
157
        // Insert again to make sure our actions are properly placed after apply changes
158
        if ($RightGroup) {
159
            $actions->remove($RightGroup);
160
            $actions->push($RightGroup);
161
        }
162
163
        $opts = [
164
            'save_close' => self::config()->enable_save_close,
165
            'save_prev_next' => self::config()->enable_save_prev_next,
166
            'delete_right' => self::config()->enable_delete_right,
167
        ];
168
        if ($record->hasMethod('getCMSActionsOptions')) {
169
            $opts = array_merge($opts, $record->getCMSActionsOptions());
170
        }
171
172
        if ($opts['save_close']) {
173
            $this->addSaveAndClose($actions, $record);
174
        }
175
176
        if ($opts['save_prev_next']) {
177
            $this->addSaveNextAndPrevious($actions, $record);
178
        }
179
180
        if ($opts['delete_right']) {
181
            $this->moveCancelAndDelete($actions, $record);
182
        }
183
184
        // Add extension hook
185
        $this->extend('onAfterUpdateCMSActions', $actions, $record);
186
        $record->extend('onAfterUpdateCMSActions', $actions);
187
    }
188
189
190
    /**
191
     * Collect all Drop-Up actions into a menu.
192
     * @param FieldList $actions
193
     * @return void
194
     */
195
    protected function processDropUpMenu($actions)
196
    {
197
        // The Drop-up container may already exist
198
        $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...
199
        foreach ($actions as $action) {
200
            if ($action->hasMethod('getDropUp') && $action->getDropUp()) {
201
                if (!$dropUpContainer) {
202
                    $dropUpContainer = $this->createDropUpContainer($actions);
203
                }
204
                $action->getContainerFieldList()->removeByName($action->getName());
205
                $dropUpContainer->push($action);
206
            }
207
        }
208
    }
209
210
    /**
211
     * Prepares a Drop-Up menu
212
     * @param FieldList $actions
213
     * @return Tab
214
     */
215
    protected function createDropUpContainer($actions)
216
    {
217
        $rootTabSet = new TabSet('ActionMenus');
218
        $dropUpContainer = new Tab(
219
            'MoreOptions',
220
            _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons')
221
        );
222
        $dropUpContainer->addExtraClass('popover-actions-simulate');
223
        $rootTabSet->push($dropUpContainer);
224
        $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder');
225
226
        $actions->insertBefore('RightGroup', $rootTabSet);
227
        return $dropUpContainer;
228
    }
229
230
    /**
231
     * @param FieldList $actions
232
     * @param DataObject $record
233
     * @return void
234
     */
235
    public function moveCancelAndDelete(FieldList $actions, DataObject $record)
236
    {
237
        // We have a 4.4 setup, before that there was no RightGroup
238
        $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...
239
240
        // Move delete at the end
241
        $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...
242
        if ($deleteAction) {
0 ignored issues
show
introduced by
$deleteAction is of type null, thus it always evaluated to false.
Loading history...
243
            // Move at the end of the stack
244
            $actions->remove($deleteAction);
245
            $actions->push($deleteAction);
246
247
            if ($RightGroup) {
248
                // Stack position is enough to have it on the left
249
            } else {
250
                // Only necessary pre 4.4
251
                $deleteAction->addExtraClass('align-right');
252
            }
253
            // Set custom title
254
            if ($record->hasMethod('getDeleteButtonTitle')) {
255
                $deleteAction->setTitle($record->getDeleteButtonTitle());
256
            }
257
        }
258
        // Move cancel at the end
259
        $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...
260
        if ($cancelButton) {
0 ignored issues
show
introduced by
$cancelButton is of type null, thus it always evaluated to false.
Loading history...
261
            // Move at the end of the stack
262
            $actions->remove($cancelButton);
263
            $actions->push($cancelButton);
264
            if ($RightGroup) {
265
                // Stack position is enough to have it on the left
266
            } else {
267
                // Only necessary pre 4.4
268
                $cancelButton->addExtraClass('align-right');
269
            }
270
            // Set custom titlte
271
            if ($record->hasMethod('getCancelButtonTitle')) {
272
                $cancelButton->setTitle($record->getCancelButtonTitle());
273
            }
274
        }
275
    }
276
277
    /**
278
     * @param DataObject $record
279
     * @return int
280
     */
281
    public function getCustomPreviousRecordID(DataObject $record)
282
    {
283
        if ($record->hasMethod('PrevRecord')) {
284
            return $record->PrevRecord()->ID ?? 0;
285
        }
286
        return $this->owner->getPreviousRecordID();
287
    }
288
289
    /**
290
     * @param DataObject $record
291
     * @return int
292
     */
293
    public function getCustomNextRecordID(DataObject $record)
294
    {
295
        if ($record->hasMethod('NextRecord')) {
296
            return $record->NextRecord()->ID ?? 0;
297
        }
298
        return $this->owner->getNextRecordID();
299
    }
300
301
    /**
302
     * @param FieldList $actions
303
     * @param DataObject $record
304
     * @return void
305
     */
306
    public function addSaveNextAndPrevious(FieldList $actions, DataObject $record)
307
    {
308
        if (!$record->canEdit()) {
309
            return;
310
        }
311
        if (!$record->ID) {
312
            return;
313
        }
314
315
        $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...
316
317
        // If it doesn't exist, push to default group
318
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
319
            $MajorActions = $actions;
320
        }
321
322
        // TODO: check why with paginator, after the first page, getPreviousRecordID/getNextRecordID tend to not work properly
323
        $getPreviousRecordID = $this->getCustomPreviousRecordID($record);
324
        $getNextRecordID = $this->getCustomNextRecordID($record);
325
326
        // Coupling for HasPrevNextUtils
327
        if (Controller::has_curr()) {
328
            $request =  Controller::curr()->getRequest();
329
            $routeParams = $request->routeParams();
330
            $routeParams['PreviousRecordID'] = $getPreviousRecordID;
331
            $routeParams['NextRecordID'] = $getNextRecordID;
332
            $request->setRouteParams($routeParams);
333
        }
334
335
        if ($getPreviousRecordID) {
336
            $doSaveAndPrev = new FormAction('doSaveAndPrev', _t('ActionsGridFieldItemRequest.SAVEANDPREVIOUS', 'Save and Previous'));
337
            $doSaveAndPrev->addExtraClass($this->getBtnClassForRecord($record));
338
            $doSaveAndPrev->addExtraClass('font-icon-angle-double-left btn-mobile-collapse');
339
            $doSaveAndPrev->setUseButtonTag(true);
340
            $MajorActions->push($doSaveAndPrev);
341
        }
342
        if ($getNextRecordID) {
343
            $doSaveAndNext = new FormAction('doSaveAndNext', _t('ActionsGridFieldItemRequest.SAVEANDNEXT', 'Save and Next'));
344
            $doSaveAndNext->addExtraClass($this->getBtnClassForRecord($record));
345
            $doSaveAndNext->addExtraClass('font-icon-angle-double-right btn-mobile-collapse');
346
            $doSaveAndNext->setUseButtonTag(true);
347
            $MajorActions->push($doSaveAndNext);
348
        }
349
    }
350
351
    /**
352
     * @param FieldList $actions
353
     * @param DataObject $record
354
     * @return void
355
     */
356
    public function addSaveAndClose(FieldList $actions, DataObject $record)
357
    {
358
        if (!$record->canEdit()) {
359
            return;
360
        }
361
        if (!$record->ID && !$record->canCreate()) {
362
            return;
363
        }
364
365
        $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...
366
367
        // If it doesn't exist, push to default group
368
        if (!$MajorActions) {
0 ignored issues
show
introduced by
$MajorActions is of type null, thus it always evaluated to false.
Loading history...
369
            $MajorActions = $actions;
370
        }
371
372
        if ($record->ID) {
373
            $label = _t('ActionsGridFieldItemRequest.SAVEANDCLOSE', 'Save and Close');
374
        } else {
375
            $label = _t('ActionsGridFieldItemRequest.CREATEANDCLOSE', 'Create and Close');
376
        }
377
        $saveAndClose = new FormAction('doSaveAndClose', $label);
378
        $saveAndClose->addExtraClass($this->getBtnClassForRecord($record));
379
        $saveAndClose->setAttribute('data-text-alternate', $label);
380
        if ($record->ID) {
381
            $saveAndClose->setAttribute('data-btn-alternate-add', 'btn-primary');
382
            $saveAndClose->setAttribute('data-btn-alternate-remove', 'btn-outline-primary');
383
        }
384
        $saveAndClose->addExtraClass('font-icon-level-up btn-mobile-collapse');
385
        $saveAndClose->setUseButtonTag(true);
386
        $MajorActions->push($saveAndClose);
387
    }
388
389
    /**
390
     * New and existing records have different classes
391
     *
392
     * @param DataObject $record
393
     * @return string
394
     */
395
    protected function getBtnClassForRecord(DataObject $record)
396
    {
397
        if ($record->ID) {
398
            return 'btn-outline-primary';
399
        }
400
        return 'btn-primary';
401
    }
402
403
    protected static function findAction($action, $definedActions)
404
    {
405
        $result = null;
406
407
        foreach ($definedActions as $definedAction) {
408
            if(is_a($definedAction, \SilverStripe\Forms\CompositeField::class)) {
409
                $result = self::findAction($action, $definedAction->FieldList());
410
            }
411
412
            $definedActionName = $definedAction->getName();
413
414
            if ($definedAction->hasMethod('actionName')) {
415
                $definedActionName = $definedAction->actionName();
416
            }
417
            if ($definedActionName == $action) {
418
                $result = $definedAction;
419
            }
420
        }
421
422
        return $result;
423
    }
424
    
425
    /**
426
     * Forward a given action to a DataObject
427
     *
428
     * Action must be declared in getCMSActions to be called
429
     *
430
     * @param string $action
431
     * @param array $data
432
     * @param Form $form
433
     * @return HTTPResponse|DBHTMLText|string
0 ignored issues
show
Bug introduced by
The type LeKoala\CmsActions\DBHTMLText 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...
434
     */
435
    protected function forwardActionToRecord($action, $data = [], $form = null)
436
    {
437
        $controller = $this->getToplevelController();
438
439
        // We have an item request
440
        $record = null;
441
        if ($this->owner instanceof GridFieldDetailForm_ItemRequest) {
442
            $record = $this->owner->record;
443
        } elseif ($controller instanceof SiteConfigLeftAndMain) {
444
            $record = SiteConfig::current_site_config();
445
        } elseif ($controller instanceof LeftAndMain) {
446
            if (empty($data['ClassName']) || empty($data['ID'])) {
447
                throw new Exception("Submitted data does not contain and ID and a ClassName");
448
            }
449
            $record = DataObject::get_by_id($data['ClassName'], $data['ID']);
450
        } elseif ($controller->hasMethod("getRecord")) {
451
            $record = $controller->getRecord();
452
        }
453
454
        if (!$record) {
455
            throw new Exception("No record to handle the action $action on " . get_class($controller));
456
        }
457
        $definedActions = $record->getCMSActions();
458
        // Check if the action is indeed available
459
        $clickedAction = null;
460
        if (!empty($definedActions)) {
461
            $clickedAction = self::findAction($action, $definedActions);
462
        }
463
        if (!$clickedAction) {
464
            $class = get_class($record);
465
            $availableActions = implode(',', $this->getAvailableActions($definedActions));
466
            if (!$availableActions) {
467
                $availableActions = "(no available actions, please check getCMSActions)";
468
            }
469
            return $this->owner->httpError(403, 'Action not available on ' . $class . '. It must be one of : ' . $availableActions);
470
        }
471
        $message = null;
472
        $error = false;
473
474
        // Check record BEFORE the action
475
        // It can be deleted by the action and it will return to the list
476
        $isNewRecord = $record->ID == 0;
477
478
        try {
479
            $result = $record->$action($data, $form, $controller);
480
481
            // We have a response
482
            if ($result && $result instanceof HTTPResponse) {
483
                return $result;
484
            }
485
486
            if ($result === false) {
487
                // Result returned an error (false)
488
                $error = true;
489
                $message = _t(
490
                    'ActionsGridFieldItemRequest.FAILED',
491
                    'Action {action} failed on {name}',
492
                    ['action' => $clickedAction->getTitle(), 'name' => $record->i18n_singular_name()]
493
                );
494
            } elseif (is_string($result)) {
495
                // Result is a message
496
                $message = $result;
497
            }
498
        } catch (Exception $ex) {
499
            $error = true;
500
            $message = $ex->getMessage();
501
        }
502
503
        // Build default message
504
        if (!$message) {
505
            $message = _t(
506
                'ActionsGridFieldItemRequest.DONE',
507
                'Action {action} was done on {name}',
508
                ['action' => $clickedAction->getTitle(), 'name' => $record->i18n_singular_name()]
509
            );
510
        }
511
        $status = 'good';
512
        if ($error) {
513
            $status = 'bad';
514
        }
515
516
        // Progressive actions return array with json data
517
        if (method_exists($clickedAction, 'getProgressive') && $clickedAction->getProgressive()) {
518
            $response = $controller->getResponse();
519
            $response->addHeader('Content-Type', 'application/json');
520
            if ($result) {
521
                $response->setBody(json_encode($result));
522
            }
523
            return $response;
524
        }
525
526
        // We don't have a form, simply return the result
527
        if (!$form) {
528
            if ($error) {
529
                return $this->owner->httpError(403, $message);
530
            }
531
            return $message;
532
        }
533
        if (Director::is_ajax()) {
534
            $controller = $this->getToplevelController();
535
            $controller->getResponse()->addHeader('X-Status', rawurlencode($message));
536
            if (method_exists($clickedAction, 'getShouldRefresh') && $clickedAction->getShouldRefresh()) {
537
                $controller->getResponse()->addHeader('X-Reload', "true");
538
            }
539
            // 4xx status makes a red box
540
            if ($error) {
541
                $controller->getResponse()->setStatusCode(400);
542
            }
543
        } else {
544
            $form->sessionMessage($message, $status, ValidationResult::CAST_HTML);
545
        }
546
        // Redirect after action
547
        return $this->redirectAfterAction($isNewRecord, $record);
548
    }
549
550
    /**
551
     * Handles custom links
552
     *
553
     * Use CustomLink with default behaviour to trigger this
554
     *
555
     * See:
556
     * DefaultLink::getModelLink
557
     * GridFieldCustomLink::getLink
558
     *
559
     * @param HTTPRequest $request
560
     * @return HTTPResponse|DBHTMLText|string
561
     */
562
    public function doCustomLink(HTTPRequest $request)
563
    {
564
        $action = $request->getVar('CustomLink');
565
        return $this->forwardActionToRecord($action);
566
    }
567
568
    /**
569
     * Handles custom actions
570
     *
571
     * Use CustomAction class to trigger this
572
     *
573
     * Nested actions are submitted like this
574
     * [action_doCustomAction] => Array
575
     * (
576
     *   [doTestAction] => 1
577
     * )
578
     *
579
     * @param array The form data
580
     * @param Form The form object
581
     * @return HTTPResponse|DBHTMLText|string
582
     */
583
    public function doCustomAction($data, $form)
584
    {
585
        $action = key($data['action_doCustomAction']);
586
        return $this->forwardActionToRecord($action, $data, $form);
587
    }
588
589
    /**
590
     * Saves the form and goes back to list view
591
     *
592
     * @param array The form data
593
     * @param Form The form object
594
     */
595
    public function doSaveAndClose($data, $form)
596
    {
597
        $result = $this->owner->doSave($data, $form);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
598
        // Redirect after save
599
        $controller = $this->getToplevelController();
600
        $controller->getResponse()->addHeader("X-Pjax", "Content");
601
        return $controller->redirect($this->getBackLink());
602
    }
603
604
    /**
605
     * Saves the form and goes back to the next item
606
     *
607
     * @param array The form data
608
     * @param Form The form object
609
     */
610
    public function doSaveAndNext($data, $form)
611
    {
612
        $record = $this->owner->record;
613
        $result = $this->owner->doSave($data, $form);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
614
        // Redirect after save
615
        $controller = $this->getToplevelController();
616
        $controller->getResponse()->addHeader("X-Pjax", "Content");
617
618
        $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

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

619
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
620
        $next = $class::get()->byID($getNextRecordID);
621
622
        $link = $this->owner->getEditLink($getNextRecordID);
623
624
        // Link to a specific tab if set, see cms-actions.js
625
        if ($next && !empty($data['_activetab'])) {
626
            $link .= '#' . $data['_activetab'];
627
        }
628
        return $controller->redirect($link);
629
    }
630
631
    /**
632
     * Saves the form and goes to the previous item
633
     *
634
     * @param array The form data
635
     * @param Form The form object
636
     */
637
    public function doSaveAndPrev($data, $form)
638
    {
639
        $record = $this->owner->record;
640
        $result = $this->owner->doSave($data, $form);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
641
        // Redirect after save
642
        $controller = $this->getToplevelController();
643
        $controller->getResponse()->addHeader("X-Pjax", "Content");
644
645
        $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

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

646
        $class = get_class(/** @scrutinizer ignore-type */ $record);
Loading history...
647
        $prev = $class::get()->byID($getPreviousRecordID);
648
649
        $link = $this->owner->getEditLink($getPreviousRecordID);
650
651
        // Link to a specific tab if set, see cms-actions.js
652
        if ($prev && !empty($data['_activetab'])) {
653
            $link .= '#' . $data['_activetab'];
654
        }
655
        return $controller->redirect($link);
656
    }
657
658
    /**
659
     * Gets the top level controller.
660
     *
661
     * @return Controller
662
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
663
     * because it is a protected method and not visible to a decorator!
664
     */
665
    protected function getToplevelController()
666
    {
667
        if ($this->owner instanceof LeftAndMain) {
668
            return $this->owner;
669
        }
670
        if (!$this->owner->hasMethod("getController")) {
671
            return Controller::curr();
672
        }
673
        $c = $this->owner->getController();
674
        while ($c && $c instanceof GridFieldDetailForm_ItemRequest) {
675
            $c = $c->getController();
676
        }
677
        return $c;
678
    }
679
680
    /**
681
     * Gets the back link
682
     *
683
     * @return string
684
     * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
685
     * because it is a protected method and not visible to a decorator!
686
     */
687
    public function getBackLink()
688
    {
689
        // TODO Coupling with CMS
690
        $backlink = '';
691
        $toplevelController = $this->getToplevelController();
692
        if ($toplevelController && $toplevelController instanceof LeftAndMain) {
693
            if ($toplevelController->hasMethod('Backlink')) {
694
                $backlink = $toplevelController->Backlink();
695
            } elseif ($this->owner->getController()->hasMethod('Breadcrumbs')) {
696
                $parents = $this->owner->getController()->Breadcrumbs(false)->items;
697
                $backlink = array_pop($parents)->Link;
698
            }
699
        }
700
        if (!$backlink) {
701
            $backlink = $toplevelController->Link();
702
        }
703
        return $backlink;
704
    }
705
706
    /**
707
     * Response object for this request after a successful save
708
     *
709
     * @param bool $isNewRecord True if this record was just created
710
     * @param DataObject $record
711
     * @return HTTPResponse|DBHTMLText|string
712
     * @todo  This had to be directly copied from {@link GridFieldDetailForm_ItemRequest}
713
     * because it is a protected method and not visible to a decorator!
714
     */
715
    protected function redirectAfterAction($isNewRecord, $record = null)
716
    {
717
        $controller = $this->getToplevelController();
718
719
        if ($controller instanceof LeftAndMain) {
720
            // CMSMain => redirect to show
721
            if ($this->owner->hasMethod("LinkPageEdit")) {
722
                return $controller->redirect($this->owner->LinkPageEdit($record->ID));
723
            }
724
        }
725
726
        if ($isNewRecord) {
727
            return $controller->redirect($this->owner->Link());
728
        } elseif ($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

728
        } elseif ($this->owner->gridField && $this->owner->gridField->getList()->/** @scrutinizer ignore-call */ byID($this->owner->record->ID)) {
Loading history...
729
            // Return new view, as we can't do a "virtual redirect" via the CMS Ajax
730
            // to the same URL (it assumes that its content is already current, and doesn't reload)
731
            return $this->owner->edit($controller->getRequest());
732
        } else {
733
            // Changes to the record properties might've excluded the record from
734
            // a filtered list, so return back to the main view if it can't be found
735
            $url = $controller->getRequest()->getURL();
736
            $action = $controller->getAction();
737
            $noActionURL = $url;
738
            // Handle GridField detail form editing
739
            if (strpos($url, 'ItemEditForm') !== false) {
740
                $action = 'ItemEditForm';
741
            }
742
            if ($action) {
743
                $noActionURL = $controller->removeAction($url, $action);
744
            }
745
            $controller->getRequest()->addHeader('X-Pjax', 'Content');
746
            return $controller->redirect($noActionURL, 302);
747
        }
748
    }
749
}
750