Passed
Push — pr/775 ( 88e336 )
by
unknown
08:38
created

ModuleController::getModuleRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
namespace A17\Twill\Http\Controllers\Admin;
4
5
use A17\Twill\Helpers\FlashLevel;
6
use A17\Twill\Services\Blocks\BlockCollection;
7
use Illuminate\Contracts\Foundation\Application;
8
use Illuminate\Http\Request;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Collection;
11
use Illuminate\Support\Facades\App;
12
use Illuminate\Support\Facades\Auth;
13
use Illuminate\Support\Facades\Config;
14
use Illuminate\Support\Facades\Redirect;
15
use Illuminate\Support\Facades\Response;
16
use Illuminate\Support\Facades\Route;
17
use Illuminate\Support\Facades\Session;
18
use Illuminate\Support\Facades\URL;
19
use Illuminate\Support\Facades\View;
20
use Illuminate\Support\Str;
21
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
22
23
abstract class ModuleController extends Controller
24
{
25
    /**
26
     * @var Application
27
     */
28
    protected $app;
29
30
    /**
31
     * @var Request
32
     */
33
    protected $request;
34
35
    /**
36
     * @var string
37
     */
38
    protected $namespace;
39
40
    /**
41
     * @var string
42
     */
43
    protected $routePrefix;
44
45
    /**
46
     * @var string
47
     */
48
    protected $moduleName;
49
50
    /**
51
     * @var string
52
     */
53
    protected $modelName;
54
55
    /**
56
     * @var string
57
     */
58
    protected $modelTitle;
59
60
    /**
61
     * @var \A17\Twill\Repositories\ModuleRepository
62
     */
63
    protected $repository;
64
65
    /**
66
     * Options of the index view.
67
     *
68
     * @var array
69
     */
70
    protected $defaultIndexOptions = [
71
        'create' => true,
72
        'edit' => true,
73
        'publish' => true,
74
        'bulkPublish' => true,
75
        'feature' => false,
76
        'bulkFeature' => false,
77
        'restore' => true,
78
        'bulkRestore' => true,
79
        'forceDelete' => true,
80
        'bulkForceDelete' => true,
81
        'delete' => true,
82
        'duplicate' => false,
83
        'bulkDelete' => true,
84
        'reorder' => false,
85
        'permalink' => true,
86
        'bulkEdit' => true,
87
        'editInModal' => false,
88
        'skipCreateModal' => false,
89
    ];
90
91
    /**
92
     * Relations to eager load for the index view
93
     *
94
     * @var array
95
     */
96
    protected $indexWith = [];
97
98
    /**
99
     * Relations to eager load for the form view.
100
     *
101
     * @var array
102
     */
103
    protected $formWith = [];
104
105
    /**
106
     * Relation count to eager load for the form view.
107
     *
108
     * @var array
109
     */
110
    protected $formWithCount = [];
111
112
    /**
113
     * Additional filters for the index view.
114
     *
115
     * To automatically have your filter added to the index view use the following convention:
116
     * suffix the key containing the list of items to show in the filter by 'List' and
117
     * name it the same as the filter you defined in this array.
118
     *
119
     * Example: 'fCategory' => 'category_id' here and 'fCategoryList' in indexData()
120
     * By default, this will run a where query on the category_id column with the value
121
     * of fCategory if found in current request parameters. You can intercept this behavior
122
     * from your repository in the filter() function.
123
     *
124
     * @var array
125
     */
126
    protected $filters = [];
127
128
    /**
129
     * Additional links to display in the listing filter
130
     *
131
     * @var array
132
     */
133
    protected $filterLinks = [];
134
135
    /**
136
     * Filters that are selected by default in the index view.
137
     *
138
     * Example: 'filter_key' => 'default_filter_value'
139
     *
140
     * @var array
141
     */
142
    protected $filtersDefaultOptions = [];
143
144
    /**
145
     * Default orders for the index view.
146
     *
147
     * @var array
148
     */
149
    protected $defaultOrders = [
150
        'created_at' => 'desc',
151
    ];
152
153
    /**
154
     * @var int
155
     */
156
    protected $perPage = 20;
157
158
    /**
159
     * Name of the index column to use as name column.
160
     *
161
     * @var string
162
     */
163
    protected $titleColumnKey = 'title';
164
165
    /**
166
     * Name of the index column to use as identifier column.
167
     *
168
     * @var string
169
     */
170
    protected $identifierColumnKey = 'id';
171
172
    /**
173
     * Attribute to use as title in forms.
174
     *
175
     * @var string
176
     */
177
    protected $titleFormKey;
178
179
    /**
180
     * Feature field name if the controller is using the feature route (defaults to "featured").
181
     *
182
     * @var string
183
     */
184
    protected $featureField = 'featured';
185
186
    /**
187
     * Indicates if this module is edited through a parent module.
188
     *
189
     * @var bool
190
     */
191
    protected $submodule = false;
192
193
    /**
194
     * @var int|null
195
     */
196
    protected $submoduleParentId = null;
197
198
    /**
199
     * Can be used in child classes to disable the content editor (full screen block editor).
200
     *
201
     * @var bool
202
     */
203
    protected $disableEditor = false;
204
205
    /**
206
     * @var array
207
     */
208
    protected $indexOptions;
209
210
    /**
211
     * @var array
212
     */
213
    protected $indexColumns;
214
215
    /**
216
     * @var array
217
     */
218
    protected $browserColumns;
219
220
    /**
221
     * @var string
222
     */
223
    protected $permalinkBase;
224
225
    /**
226
     * @var array
227
     */
228
    protected $defaultFilters;
229
230
    /**
231
     * @var string
232
     */
233
    protected $viewPrefix;
234
235
    /**
236
     * @var string
237
     */
238
    protected $previewView;
239
240
    /**
241
     * List of permissions keyed by a request field. Can be used to prevent unauthorized field updates.
242
     *
243
     * @var array
244
     */
245
    protected $fieldsPermissions = [];
246
247
    public function __construct(Application $app, Request $request)
248
    {
249
        parent::__construct();
250
        $this->app = $app;
251
        $this->request = $request;
252
253
        $this->setMiddlewarePermission();
254
255
        $this->modelName = $this->getModelName();
256
        $this->routePrefix = $this->getRoutePrefix();
257
        $this->namespace = $this->getNamespace();
258
        $this->repository = $this->getRepository();
259
        $this->viewPrefix = $this->getViewPrefix();
260
        $this->modelTitle = $this->getModelTitle();
261
262
        /*
263
         * Default filters for the index view
264
         * By default, the search field will run a like query on the title field
265
         */
266
        if (!isset($this->defaultFilters)) {
267
            $this->defaultFilters = [
268
                'search' => ($this->moduleHas('translations') ? '' : '%') . $this->titleColumnKey,
269
            ];
270
        }
271
272
        /*
273
         * Apply any filters that are selected by default
274
         */
275
        $this->applyFiltersDefaultOptions();
276
277
        /*
278
         * Available columns of the index view
279
         */
280
        if (!isset($this->indexColumns)) {
281
            $this->indexColumns = [
282
                $this->titleColumnKey => [
283
                    'title' => ucfirst($this->titleColumnKey),
284
                    'field' => $this->titleColumnKey,
285
                    'sort' => true,
286
                ],
287
            ];
288
        }
289
290
        /*
291
         * Available columns of the browser view
292
         */
293
        if (!isset($this->browserColumns)) {
294
            $this->browserColumns = [
295
                $this->titleColumnKey => [
296
                    'title' => ucfirst($this->titleColumnKey),
297
                    'field' => $this->titleColumnKey,
298
                ],
299
            ];
300
        }
301
    }
302
303
    /**
304
     * @return void
305
     */
306
    protected function setMiddlewarePermission()
307
    {
308
        $this->middleware('can:list', ['only' => ['index', 'show']]);
309
        $this->middleware('can:edit', ['only' => ['store', 'edit', 'update']]);
310
        $this->middleware('can:duplicate', ['only' => ['duplicate']]);
311
        $this->middleware('can:publish', ['only' => ['publish', 'feature', 'bulkPublish', 'bulkFeature']]);
312
        $this->middleware('can:reorder', ['only' => ['reorder']]);
313
        $this->middleware('can:delete', ['only' => ['destroy', 'bulkDelete', 'restore', 'bulkRestore', 'forceDelete', 'bulkForceDelete', 'restoreRevision']]);
314
    }
315
316
    /**
317
     * @param Request $request
318
     * @return string|int
319
     */
320
    protected function getParentModuleIdFromRequest(Request $request)
321
    {
322
        $moduleParts = explode('.', $this->moduleName);
323
324
        if (count($moduleParts) > 1) {
325
            $parentModule = Str::singular($moduleParts[count($moduleParts) - 2]);
326
327
            return $request->route()->parameters()[$parentModule];
328
        }
329
    }
330
331
    /**
332
     * @param Request $request
333
     * @return array|\Illuminate\View\View
334
     */
335
    public function index(Request $request)
336
    {
337
        $parentModuleId = $this->getParentModuleIdFromRequest($request);
338
339
        $this->submodule = isset($parentModuleId);
340
        $this->submoduleParentId = $parentModuleId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parentModuleId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
341
342
        $indexData = $this->getIndexData($this->submodule ? [
343
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
344
        ] : []);
345
346
        if ($this->request->ajax()) {
347
            return $indexData + ['replaceUrl' => true];
348
        }
349
350
        if ($this->request->has('openCreate') && $this->request->get('openCreate')) {
351
            $indexData += ['openCreate' => true];
352
        }
353
354
        $view = Collection::make([
355
            "$this->viewPrefix.index",
356
            "twill::$this->moduleName.index",
357
            "twill::layouts.listing",
358
        ])->first(function ($view) {
359
            return View::exists($view);
360
        });
361
362
        return View::make($view, $indexData);
363
    }
364
365
    /**
366
     * @return \Illuminate\Http\JsonResponse
367
     */
368
    public function browser()
369
    {
370
        return Response::json($this->getBrowserData());
371
    }
372
373
    /**
374
     * @param Request $request
375
     * @return \Illuminate\Http\JsonResponse
376
     */
377
    public function store(Request $request)
378
    {
379
        $parentModuleId = $this->getParentModuleIdFromRequest($request);
380
381
        $input = $this->validateFormRequest()->all();
382
        $optionalParent = $parentModuleId ? [$this->getParentModuleForeignKey() => $parentModuleId] : [];
383
384
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
385
            return $this->respondWithRedirect(moduleRoute(
386
                $this->moduleName,
387
                $this->routePrefix,
388
                'create'
389
            ));
390
        }
391
392
        $item = $this->repository->create($input + $optionalParent);
393
394
        activity()->performedOn($item)->log('created');
395
396
        $this->fireEvent($input);
397
398
        Session::put($this->moduleName . '_retain', true);
399
400
        if ($this->getIndexOption('editInModal')) {
401
            return $this->respondWithSuccess(twillTrans('twill::lang.publisher.save-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.publisher.save-success') can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, 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

401
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
402
        }
403
404
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-close')) {
405
            return $this->respondWithRedirect($this->getBackLink());
406
        }
407
408
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-new')) {
409
            return $this->respondWithRedirect(moduleRoute(
410
                $this->moduleName,
411
                $this->routePrefix,
412
                'create'
413
            ));
414
        }
415
416
        return $this->respondWithRedirect(moduleRoute(
417
            $this->moduleName,
418
            $this->routePrefix,
419
            'edit',
420
            [Str::singular(last(explode('.', $this->moduleName))) => $item[$this->identifierColumnKey]]
421
        ));
422
    }
423
424
    /**
425
     * @param Request $request
426
     * @param int|$id
427
     * @return \Illuminate\Http\RedirectResponse
428
     */
429
    public function show(Request $request, $id)
430
    {
431
        if ($this->getIndexOption('editInModal')) {
432
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
433
        }
434
435
        return $this->redirectToForm($this->getParentModuleIdFromRequest($request) ?? $id);
0 ignored issues
show
Bug introduced by
It seems like $this->getParentModuleId...equest($request) ?? $id can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::redirectToForm() does only seem to accept integer, 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

435
        return $this->redirectToForm(/** @scrutinizer ignore-type */ $this->getParentModuleIdFromRequest($request) ?? $id);
Loading history...
436
    }
437
438
    /**
439
     * @param Request $request
440
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
441
     */
442
    public function edit(Request $request)
443
    {
444
        $params = $request->route()->parameters();
445
        $this->submodule = count($params) > 1;
446
        $this->submoduleParentId = $this->submodule
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->submodule ? $this...equest) : head($params) can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
447
            ? $this->getParentModuleIdFromRequest($request)
448
            : head($params);
449
        $id = last($params);
450
451
        if ($this->getIndexOption('editInModal')) {
452
            return $this->request->ajax()
453
            ? Response::json($this->modalFormData($id))
454
            : Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
455
        }
456
457
        $this->setBackLink();
458
459
        $view = Collection::make([
460
            "$this->viewPrefix.form",
461
            "twill::$this->moduleName.form",
462
            "twill::layouts.form",
463
        ])->first(function ($view) {
464
            return View::exists($view);
465
        });
466
467
        return View::make($view, $this->form($id));
468
    }
469
470
    /**
471
     * @param Request $request
472
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
473
     */
474
    public function create(Request $request)
475
    {
476
        if (!$this->getIndexOption('skipCreateModal')) {
477
            return Redirect::to(moduleRoute(
478
                $this->moduleName,
479
                $this->routePrefix,
480
                'index',
481
                ['openCreate' => true]
482
            ));
483
        }
484
485
        $parentModuleId = $this->getParentModuleIdFromRequest($request);
486
487
        $this->submodule = isset($parentModuleId);
488
        $this->submoduleParentId = $parentModuleId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parentModuleId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
489
490
        $view = Collection::make([
491
            "$this->viewPrefix.form",
492
            "twill::$this->moduleName.form",
493
            "twill::layouts.form",
494
        ])->first(function ($view) {
495
            return View::exists($view);
496
        });
497
498
        return View::make($view, $this->form(null));
499
    }
500
501
    /**
502
     * @param Request $request
503
     * @return \Illuminate\Http\JsonResponse
504
     */
505
    public function update(Request $request)
506
    {
507
        $id = last($request->route()->parameters());
508
        $submoduleParentId = $this->getParentModuleIdFromRequest($request);
509
        $this->submodule = isset($submoduleParentId);
510
        $this->submoduleParentId = $submoduleParentId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $submoduleParentId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
511
512
        $item = $this->repository->getById($id);
513
        $input = $this->request->all();
514
515
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
516
            return $this->respondWithRedirect(moduleRoute(
517
                $this->moduleName,
518
                $this->routePrefix,
519
                'edit',
520
                [Str::singular($this->moduleName) => $id]
521
            ));
522
        } else {
523
            $formRequest = $this->validateFormRequest();
524
525
            $this->repository->update($id, $formRequest->all());
526
527
            activity()->performedOn($item)->log('updated');
528
529
            $this->fireEvent();
530
531
            if (isset($input['cmsSaveType'])) {
532
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
533
                    return $this->respondWithRedirect($this->getBackLink());
534
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
535
                    if ($this->getIndexOption('skipCreateModal')) {
536
                        return $this->respondWithRedirect(moduleRoute(
537
                            $this->moduleName,
538
                            $this->routePrefix,
539
                            'create'
540
                        ));
541
                    }
542
                    return $this->respondWithRedirect(moduleRoute(
543
                        $this->moduleName,
544
                        $this->routePrefix,
545
                        'index',
546
                        ['openCreate' => true]
547
                    ));
548
                } elseif ($input['cmsSaveType'] === 'restore') {
549
                    Session::flash('status', twillTrans('twill::lang.publisher.restore-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang....isher.restore-success') can also be of type array and array; however, parameter $message of Illuminate\Support\Facades\Session::flash() does only seem to accept string, 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

549
                    Session::flash('status', /** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.restore-success'));
Loading history...
550
551
                    return $this->respondWithRedirect(moduleRoute(
552
                        $this->moduleName,
553
                        $this->routePrefix,
554
                        'edit',
555
                        [Str::singular($this->moduleName) => $id]
556
                    ));
557
                }
558
            }
559
560
            if ($this->moduleHas('revisions')) {
561
                return Response::json([
562
                    'message' => twillTrans('twill::lang.publisher.save-success'),
563
                    'variant' => FlashLevel::SUCCESS,
564
                    'revisions' => $item->revisionsArray(),
565
                ]);
566
            }
567
568
            return $this->respondWithSuccess(twillTrans('twill::lang.publisher.save-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.publisher.save-success') can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, 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

568
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
569
        }
570
    }
571
572
    /**
573
     * @param int $id
574
     * @return \Illuminate\View\View
575
     */
576
    public function preview($id)
577
    {
578
        if ($this->request->has('revisionId')) {
579
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
0 ignored issues
show
Bug introduced by
The method previewForRevision() does not exist on A17\Twill\Repositories\ModuleRepository. 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

579
            /** @scrutinizer ignore-call */ 
580
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
Loading history...
580
        } else {
581
            $formRequest = $this->validateFormRequest();
582
            $item = $this->repository->preview($id, $formRequest->all());
0 ignored issues
show
Bug introduced by
The method preview() does not exist on A17\Twill\Repositories\ModuleRepository. 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

582
            /** @scrutinizer ignore-call */ 
583
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
583
        }
584
585
        if ($this->request->has('activeLanguage')) {
586
            App::setLocale($this->request->get('activeLanguage'));
587
        }
588
589
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
590
591
        return View::exists($previewView) ? View::make($previewView, array_replace([
592
            'item' => $item,
593
        ], $this->previewData($item))) : View::make('twill::errors.preview', [
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $item of A17\Twill\Http\Controlle...ntroller::previewData() does only seem to accept Illuminate\Http\Request, 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

593
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
594
            'moduleName' => Str::singular($this->moduleName),
595
        ]);
596
    }
597
598
    /**
599
     * @param int $id
600
     * @return \Illuminate\View\View
601
     */
602
    public function restoreRevision($id)
603
    {
604
        if ($this->request->has('revisionId')) {
605
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
606
            $item[$this->identifierColumnKey] = $id;
607
            $item->cmsRestoring = true;
0 ignored issues
show
Bug introduced by
The property cmsRestoring does not seem to exist on Illuminate\Database\Eloquent\Builder.
Loading history...
608
        } else {
609
            throw new NotFoundHttpException();
610
        }
611
612
        $this->setBackLink();
613
614
        $view = Collection::make([
615
            "$this->viewPrefix.form",
616
            "twill::$this->moduleName.form",
617
            "twill::layouts.form",
618
        ])->first(function ($view) {
619
            return View::exists($view);
620
        });
621
622
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
623
        $date = $revision->created_at->toDayDateTimeString();
0 ignored issues
show
Bug introduced by
The method toDayDateTimeString() does not exist on DateTime. It seems like you code against a sub-type of DateTime such as Carbon\Carbon. ( Ignorable by Annotation )

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

623
        /** @scrutinizer ignore-call */ 
624
        $date = $revision->created_at->toDayDateTimeString();
Loading history...
624
625
        Session::flash('restoreMessage', twillTrans('twill::lang.publisher.restore-message', ['user' => $revision->byUser, 'date' => $date]));
0 ignored issues
show
Bug introduced by
The property byUser does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by
It seems like twillTrans('twill::lang....User, 'date' => $date)) can also be of type array and array; however, parameter $message of Illuminate\Support\Facades\Session::flash() does only seem to accept string, 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

625
        Session::flash('restoreMessage', /** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.restore-message', ['user' => $revision->byUser, 'date' => $date]));
Loading history...
626
627
        return View::make($view, $this->form($id, $item));
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $item of A17\Twill\Http\Controlle...oduleController::form() does only seem to accept A17\Twill\Models\Model|null, 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

627
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
628
    }
629
630
    /**
631
     * @return \Illuminate\Http\JsonResponse
632
     */
633
    public function publish()
634
    {
635
        try {
636
            if ($this->repository->updateBasic($this->request->get('id'), [
637
                'published' => !$this->request->get('active'),
638
            ])) {
639
                activity()->performedOn(
640
                    $this->repository->getById($this->request->get('id'))
641
                )->log(
642
                    ($this->request->get('active') ? 'un' : '') . 'published'
643
                );
644
645
                $this->fireEvent();
646
647
                return $this->respondWithSuccess(
648
                    $this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'published!'
649
                );
650
            }
651
        } catch (\Exception $e) {
652
            \Log::error($e);
653
        }
654
655
        return $this->respondWithError(
656
            $this->modelTitle . ' was not published. Something wrong happened!'
657
        );
658
    }
659
660
    /**
661
     * @return \Illuminate\Http\JsonResponse
662
     */
663
    public function bulkPublish()
664
    {
665
        try {
666
            if ($this->repository->updateBasic(explode(',', $this->request->get('ids')), [
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

666
            if ($this->repository->updateBasic(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')), [
Loading history...
667
                'published' => $this->request->get('publish'),
668
            ])) {
669
                $this->fireEvent();
670
671
                return $this->respondWithSuccess(
672
                    $this->modelTitle . ' items ' . ($this->request->get('publish') ? '' : 'un') . 'published!'
673
                );
674
            }
675
        } catch (\Exception $e) {
676
            \Log::error($e);
677
        }
678
679
        return $this->respondWithError(
680
            $this->modelTitle . ' items were not published. Something wrong happened!'
681
        );
682
    }
683
684
    /**
685
     * @param Request $request
686
     * @return \Illuminate\Http\JsonResponse
687
     */
688
    public function duplicate(Request $request)
689
    {
690
        $id = last($request->route()->parameters());
691
692
        $item = $this->repository->getById($id);
693
        if ($newItem = $this->repository->duplicate($id, $this->titleColumnKey)) {
694
            $this->fireEvent();
695
            activity()->performedOn($item)->log('duplicated');
696
697
            return Response::json([
698
                'message' => $this->modelTitle . ' duplicated with Success!',
699
                'variant' => FlashLevel::SUCCESS,
700
                'redirect' => moduleRoute(
701
                    $this->moduleName,
702
                    $this->routePrefix,
703
                    'edit',
704
                    array_filter([Str::singular($this->moduleName) => $newItem->id])
705
                ),
706
            ]);
707
        }
708
709
        return $this->respondWithError($this->modelTitle . ' was not duplicated. Something wrong happened!');
710
    }
711
712
    /**
713
     * @param Request $request
714
     * @return \Illuminate\Http\JsonResponse
715
     */
716
    public function destroy(Request $request)
717
    {
718
        $id = last($request->route()->parameters());
719
720
        $item = $this->repository->getById($id);
721
        if ($this->repository->delete($id)) {
722
            $this->fireEvent();
723
            activity()->performedOn($item)->log('deleted');
724
            return $this->respondWithSuccess($this->modelTitle . ' moved to trash!');
725
        }
726
727
        return $this->respondWithError($this->modelTitle . ' was not moved to trash. Something wrong happened!');
728
    }
729
730
    /**
731
     * @return \Illuminate\Http\JsonResponse
732
     */
733
    public function bulkDelete()
734
    {
735
        if ($this->repository->bulkDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

735
        if ($this->repository->bulkDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
736
            $this->fireEvent();
737
            return $this->respondWithSuccess($this->modelTitle . ' items moved to trash!');
738
        }
739
740
        return $this->respondWithError($this->modelTitle . ' items were not moved to trash. Something wrong happened!');
741
    }
742
743
    /**
744
     * @return \Illuminate\Http\JsonResponse
745
     */
746
    public function forceDelete()
747
    {
748
        if ($this->repository->forceDelete($this->request->get('id'))) {
749
            $this->fireEvent();
750
            return $this->respondWithSuccess($this->modelTitle . ' destroyed!');
751
        }
752
753
        return $this->respondWithError($this->modelTitle . ' was not destroyed. Something wrong happened!');
754
    }
755
756
    /**
757
     * @return \Illuminate\Http\JsonResponse
758
     */
759
    public function bulkForceDelete()
760
    {
761
        if ($this->repository->bulkForceDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

761
        if ($this->repository->bulkForceDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
762
            $this->fireEvent();
763
            return $this->respondWithSuccess($this->modelTitle . ' items destroyed!');
764
        }
765
766
        return $this->respondWithError($this->modelTitle . ' items were not destroyed. Something wrong happened!');
767
    }
768
769
    /**
770
     * @return \Illuminate\Http\JsonResponse
771
     */
772
    public function restore()
773
    {
774
        if ($this->repository->restore($this->request->get('id'))) {
775
            $this->fireEvent();
776
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
777
            return $this->respondWithSuccess($this->modelTitle . ' restored!');
778
        }
779
780
        return $this->respondWithError($this->modelTitle . ' was not restored. Something wrong happened!');
781
    }
782
783
    /**
784
     * @return \Illuminate\Http\JsonResponse
785
     */
786
    public function bulkRestore()
787
    {
788
        if ($this->repository->bulkRestore(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

788
        if ($this->repository->bulkRestore(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
789
            $this->fireEvent();
790
            return $this->respondWithSuccess($this->modelTitle . ' items restored!');
791
        }
792
793
        return $this->respondWithError($this->modelTitle . ' items were not restored. Something wrong happened!');
794
    }
795
796
    /**
797
     * @return \Illuminate\Http\JsonResponse
798
     */
799
    public function feature()
800
    {
801
        if (($id = $this->request->get('id'))) {
802
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
803
            $featured = !$this->request->get('active');
804
805
            if ($this->repository->isUniqueFeature()) {
806
                if ($featured) {
807
                    $this->repository->updateBasic(null, [$featuredField => false]);
808
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
809
                }
810
            } else {
811
                $this->repository->updateBasic($id, [$featuredField => $featured]);
812
            }
813
814
            activity()->performedOn(
815
                $this->repository->getById($id)
816
            )->log(
817
                ($this->request->get('active') ? 'un' : '') . 'featured'
818
            );
819
820
            $this->fireEvent();
821
            return $this->respondWithSuccess($this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'featured!');
822
        }
823
824
        return $this->respondWithError($this->modelTitle . ' was not featured. Something wrong happened!');
825
    }
826
827
    /**
828
     * @return \Illuminate\Http\JsonResponse
829
     */
830
    public function bulkFeature()
831
    {
832
        if (($ids = explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

832
        if (($ids = explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
833
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
834
            $featured = $this->request->get('feature') ?? true;
835
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
836
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
837
            $this->fireEvent();
838
            return $this->respondWithSuccess($this->modelTitle . ' items ' . ($this->request->get('feature') ? '' : 'un') . 'featured!');
839
        }
840
841
        return $this->respondWithError($this->modelTitle . ' items were not featured. Something wrong happened!');
842
    }
843
844
    /**
845
     * @return \Illuminate\Http\JsonResponse
846
     */
847
    public function reorder()
848
    {
849
        if (($values = $this->request->get('ids')) && !empty($values)) {
850
            $this->repository->setNewOrder($values);
851
            $this->fireEvent();
852
            return $this->respondWithSuccess($this->modelTitle . ' order changed!');
853
        }
854
855
        return $this->respondWithError($this->modelTitle . ' order was not changed. Something wrong happened!');
856
    }
857
858
    /**
859
     * @return \Illuminate\Http\JsonResponse
860
     */
861
    public function tags()
862
    {
863
        $query = $this->request->input('q');
864
        $tags = $this->repository->getTags($query);
0 ignored issues
show
Bug introduced by
The method getTags() does not exist on A17\Twill\Repositories\ModuleRepository. 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

864
        /** @scrutinizer ignore-call */ 
865
        $tags = $this->repository->getTags($query);
Loading history...
865
866
        return Response::json(['items' => $tags->map(function ($tag) {
867
            return $tag->name;
868
        })], 200);
869
    }
870
871
    /**
872
     * @param array $prependScope
873
     * @return array
874
     */
875
    protected function getIndexData($prependScope = [])
876
    {
877
        $scopes = $this->filterScope($prependScope);
878
        $items = $this->getIndexItems($scopes);
879
880
        $data = [
881
            'tableData' => $this->getIndexTableData($items),
882
            'tableColumns' => $this->getIndexTableColumns($items),
883
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
884
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('filter') can also be of type null; however, parameter $json of json_decode() does only seem to accept string, 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

884
            'filters' => json_decode(/** @scrutinizer ignore-type */ $this->request->get('filter'), true) ?? [],
Loading history...
885
            'hiddenFilters' => array_keys(Arr::except($this->filters, array_keys($this->defaultFilters))),
886
            'filterLinks' => $this->filterLinks ?? [],
887
            'maxPage' => method_exists($items, 'lastPage') ? $items->lastPage() : 1,
888
            'defaultMaxPage' => method_exists($items, 'total') ? ceil($items->total() / $this->perPage) : 1,
889
            'offset' => method_exists($items, 'perPage') ? $items->perPage() : count($items),
890
            'defaultOffset' => $this->perPage,
891
        ] + $this->getIndexUrls($this->moduleName, $this->routePrefix);
892
893
        $baseUrl = $this->getPermalinkBaseUrl();
894
895
        $options = [
896
            'moduleName' => $this->moduleName,
897
            'skipCreateModal' => $this->getIndexOption('skipCreateModal'),
898
            'reorder' => $this->getIndexOption('reorder'),
899
            'create' => $this->getIndexOption('create'),
900
            'duplicate' => $this->getIndexOption('duplicate'),
901
            'translate' => $this->moduleHas('translations'),
902
            'translateTitle' => $this->titleIsTranslatable(),
903
            'permalink' => $this->getIndexOption('permalink'),
904
            'bulkEdit' => $this->getIndexOption('bulkEdit'),
905
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
906
            'baseUrl' => $baseUrl,
907
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
908
        ];
909
910
        return array_replace_recursive($data + $options, $this->indexData($this->request));
911
    }
912
913
    /**
914
     * @param Request $request
915
     * @return array
916
     */
917
    protected function indexData($request)
918
    {
919
        return [];
920
    }
921
922
    /**
923
     * @param array $scopes
924
     * @param bool $forcePagination
925
     * @return \Illuminate\Database\Eloquent\Collection
926
     */
927
    protected function getIndexItems($scopes = [], $forcePagination = false)
928
    {
929
        return $this->transformIndexItems($this->repository->get(
930
            $this->indexWith,
931
            $scopes,
932
            $this->orderScope(),
933
            $this->request->get('offset') ?? $this->perPage ?? 50,
934
            $forcePagination
935
        ));
936
    }
937
938
    /**
939
     * @param \Illuminate\Database\Eloquent\Collection $items
940
     * @return \Illuminate\Database\Eloquent\Collection
941
     */
942
    protected function transformIndexItems($items)
943
    {
944
        return $items;
945
    }
946
947
    /**
948
     * @param \Illuminate\Database\Eloquent\Collection $items
949
     * @return array
950
     */
951
    protected function getIndexTableData($items)
952
    {
953
        $translated = $this->moduleHas('translations');
954
        return $items->map(function ($item) use ($translated) {
955
            $columnsData = Collection::make($this->indexColumns)->mapWithKeys(function ($column) use ($item) {
956
                return $this->getItemColumnData($item, $column);
957
            })->toArray();
958
959
            $name = $columnsData[$this->titleColumnKey];
960
961
            if (empty($name)) {
962
                if ($this->moduleHas('translations')) {
963
                    $fallBackTranslation = $item->translations()->where('active', true)->first();
964
965
                    if (isset($fallBackTranslation->{$this->titleColumnKey})) {
966
                        $name = $fallBackTranslation->{$this->titleColumnKey};
967
                    }
968
                }
969
970
                $name = $name ?? ('Missing ' . $this->titleColumnKey);
971
            }
972
973
            unset($columnsData[$this->titleColumnKey]);
974
975
            $itemIsTrashed = method_exists($item, 'trashed') && $item->trashed();
976
            $itemCanDelete = $this->getIndexOption('delete') && ($item->canDelete ?? true);
977
            $canEdit = $this->getIndexOption('edit');
978
            $canDuplicate = $this->getIndexOption('duplicate');
979
980
            return array_replace([
981
                'id' => $item[$this->identifierColumnKey],
982
                'name' => $name,
983
                'publish_start_date' => $item->publish_start_date,
984
                'publish_end_date' => $item->publish_end_date,
985
                'edit' => $canEdit ? $this->getModuleRoute($item[$this->identifierColumnKey], 'edit') : null,
986
                'duplicate' => $canDuplicate ? $this->getModuleRoute($item[$this->identifierColumnKey], 'duplicate') : null,
987
                'delete' => $itemCanDelete ? $this->getModuleRoute($item[$this->identifierColumnKey], 'destroy') : null,
988
            ] + ($this->getIndexOption('editInModal') ? [
989
                'editInModal' => $this->getModuleRoute($item[$this->identifierColumnKey], 'edit'),
990
                'updateUrl' => $this->getModuleRoute($item[$this->identifierColumnKey], 'update'),
991
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
992
                'published' => $item->published,
993
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
994
                'featured' => $item->{$this->featureField},
995
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
996
                'deleted' => true,
997
            ] : []) + (($this->getIndexOption('forceDelete') && $itemIsTrashed) ? [
998
                'destroyable' => true,
999
            ] : []) + ($translated ? [
1000
                'languages' => $item->getActiveLanguages(),
1001
            ] : []) + $columnsData, $this->indexItemData($item));
1002
        })->toArray();
1003
    }
1004
1005
    /**
1006
     * @param \A17\Twill\Models\Model $item
1007
     * @return array
1008
     */
1009
    protected function indexItemData($item)
1010
    {
1011
        return [];
1012
    }
1013
1014
    /**
1015
     * @param \A17\Twill\Models\Model $item
1016
     * @param array $column
1017
     * @return array
1018
     */
1019
    protected function getItemColumnData($item, $column)
1020
    {
1021
        if (isset($column['thumb']) && $column['thumb']) {
1022
            if (isset($column['present']) && $column['present']) {
1023
                return [
1024
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
1025
                ];
1026
            } else {
1027
                $variant = isset($column['variant']);
1028
                $role = $variant ? $column['variant']['role'] : head(array_keys($item->mediasParams));
0 ignored issues
show
Bug introduced by
The property mediasParams does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
1029
                $crop = $variant ? $column['variant']['crop'] : head(array_keys(head($item->mediasParams)));
1030
                $params = $variant && isset($column['variant']['params'])
1031
                ? $column['variant']['params']
1032
                : ['w' => 80, 'h' => 80, 'fit' => 'crop'];
1033
1034
                return [
1035
                    'thumbnail' => $item->cmsImage($role, $crop, $params),
1036
                ];
1037
            }
1038
        }
1039
1040
        if (isset($column['nested']) && $column['nested']) {
1041
            $field = $column['nested'];
1042
            $nestedCount = $item->{$column['nested']}->count();
1043
            $module = Str::singular(last(explode('.', $this->moduleName)));
1044
            $value = '<a href="';
1045
            $value .= moduleRoute("$this->moduleName.$field", $this->routePrefix, 'index', [$module => $item[$this->identifierColumnKey]]);
1046
            $value .= '">' . $nestedCount . " " . (strtolower($nestedCount > 1
1047
                ? Str::plural($column['title'])
1048
                : Str::singular($column['title']))) . '</a>';
1049
        } else {
1050
            $field = $column['field'];
1051
            $value = $item->$field;
1052
        }
1053
1054
        if (isset($column['relationship'])) {
1055
            $field = $column['relationship'] . ucfirst($column['field']);
1056
            $value = Arr::get($item, "{$column['relationship']}.{$column['field']}");
1057
        } elseif (isset($column['present']) && $column['present']) {
1058
            $value = $item->presentAdmin()->{$column['field']};
1059
        }
1060
1061
        return [
1062
            "$field" => $value,
1063
        ];
1064
    }
1065
1066
    /**
1067
     * @param \Illuminate\Database\Eloquent\Collection $items
1068
     * @return array
1069
     */
1070
    protected function getIndexTableColumns($items)
1071
    {
1072
        $tableColumns = [];
1073
        $visibleColumns = $this->request->get('columns') ?? false;
1074
1075
        if (isset(Arr::first($this->indexColumns)['thumb'])
1076
            && Arr::first($this->indexColumns)['thumb']
1077
        ) {
1078
            array_push($tableColumns, [
1079
                'name' => 'thumbnail',
1080
                'label' => twillTrans('twill::lang.listing.columns.thumbnail'),
1081
                'visible' => $visibleColumns ? in_array('thumbnail', $visibleColumns) : true,
1082
                'optional' => true,
1083
                'sortable' => false,
1084
            ]);
1085
            array_shift($this->indexColumns);
1086
        }
1087
1088
        if ($this->getIndexOption('feature')) {
1089
            array_push($tableColumns, [
1090
                'name' => 'featured',
1091
                'label' => twillTrans('twill::lang.listing.columns.featured'),
1092
                'visible' => true,
1093
                'optional' => false,
1094
                'sortable' => false,
1095
            ]);
1096
        }
1097
1098
        if ($this->getIndexOption('publish')) {
1099
            array_push($tableColumns, [
1100
                'name' => 'published',
1101
                'label' => twillTrans('twill::lang.listing.columns.published'),
1102
                'visible' => true,
1103
                'optional' => false,
1104
                'sortable' => false,
1105
            ]);
1106
        }
1107
1108
        array_push($tableColumns, [
1109
            'name' => 'name',
1110
            'label' => $this->indexColumns[$this->titleColumnKey]['title'] ?? twillTrans('twill::lang.listing.columns.name'),
1111
            'visible' => true,
1112
            'optional' => false,
1113
            'sortable' => $this->getIndexOption('reorder') ? false : ($this->indexColumns[$this->titleColumnKey]['sort'] ?? false),
1114
        ]);
1115
1116
        unset($this->indexColumns[$this->titleColumnKey]);
1117
1118
        foreach ($this->indexColumns as $column) {
1119
            $columnName = isset($column['relationship'])
1120
            ? $column['relationship'] . ucfirst($column['field'])
1121
            : (isset($column['nested']) ? $column['nested'] : $column['field']);
1122
1123
            array_push($tableColumns, [
1124
                'name' => $columnName,
1125
                'label' => $column['title'],
1126
                'visible' => $visibleColumns ? in_array($columnName, $visibleColumns) : ($column['visible'] ?? true),
1127
                'optional' => $column['optional'] ?? true,
1128
                'sortable' => $this->getIndexOption('reorder') ? false : ($column['sort'] ?? false),
1129
                'html' => $column['html'] ?? false,
1130
            ]);
1131
        }
1132
1133
        if ($this->moduleHas('translations')) {
1134
            array_push($tableColumns, [
1135
                'name' => 'languages',
1136
                'label' => twillTrans('twill::lang.listing.languages'),
1137
                'visible' => $visibleColumns ? in_array('languages', $visibleColumns) : true,
1138
                'optional' => true,
1139
                'sortable' => false,
1140
            ]);
1141
        }
1142
1143
        return $tableColumns;
1144
    }
1145
1146
    /**
1147
     * @param \Illuminate\Database\Eloquent\Collection $items
1148
     * @param array $scopes
1149
     * @return array
1150
     */
1151
    protected function getIndexTableMainFilters($items, $scopes = [])
1152
    {
1153
        $statusFilters = [];
1154
1155
        $scope = ($this->submodule ? [
1156
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
1157
        ] : []) + $scopes;
1158
1159
        array_push($statusFilters, [
1160
            'name' => twillTrans('twill::lang.listing.filter.all-items'),
1161
            'slug' => 'all',
1162
            'number' => $this->repository->getCountByStatusSlug('all', $scope),
1163
        ]);
1164
1165
        if ($this->moduleHas('revisions') && $this->getIndexOption('create')) {
1166
            array_push($statusFilters, [
1167
                'name' => twillTrans('twill::lang.listing.filter.mine'),
1168
                'slug' => 'mine',
1169
                'number' => $this->repository->getCountByStatusSlug('mine', $scope),
1170
            ]);
1171
        }
1172
1173
        if ($this->getIndexOption('publish')) {
1174
            array_push($statusFilters, [
1175
                'name' => twillTrans('twill::lang.listing.filter.published'),
1176
                'slug' => 'published',
1177
                'number' => $this->repository->getCountByStatusSlug('published', $scope),
1178
            ], [
1179
                'name' => twillTrans('twill::lang.listing.filter.draft'),
1180
                'slug' => 'draft',
1181
                'number' => $this->repository->getCountByStatusSlug('draft', $scope),
1182
            ]);
1183
        }
1184
1185
        if ($this->getIndexOption('restore')) {
1186
            array_push($statusFilters, [
1187
                'name' => twillTrans('twill::lang.listing.filter.trash'),
1188
                'slug' => 'trash',
1189
                'number' => $this->repository->getCountByStatusSlug('trash', $scope),
1190
            ]);
1191
        }
1192
1193
        return $statusFilters;
1194
    }
1195
1196
    /**
1197
     * @param string $moduleName
1198
     * @param string $routePrefix
1199
     * @return array
1200
     */
1201
    protected function getIndexUrls($moduleName, $routePrefix)
1202
    {
1203
        return Collection::make([
1204
            'create',
1205
            'store',
1206
            'publish',
1207
            'bulkPublish',
1208
            'restore',
1209
            'bulkRestore',
1210
            'forceDelete',
1211
            'bulkForceDelete',
1212
            'reorder',
1213
            'feature',
1214
            'bulkFeature',
1215
            'bulkDelete',
1216
        ])->mapWithKeys(function ($endpoint) {
1217
            return [
1218
                $endpoint . 'Url' => $this->getIndexOption($endpoint) ? moduleRoute(
1219
                    $this->moduleName,
1220
                    $this->routePrefix,
1221
                    $endpoint,
1222
                    $this->submodule ? [$this->submoduleParentId] : []
1223
                ) : null,
1224
            ];
1225
        })->toArray();
1226
    }
1227
1228
    /**
1229
     * @param string $option
1230
     * @return bool
1231
     */
1232
    protected function getIndexOption($option)
1233
    {
1234
        return once(function () use ($option) {
1235
            $customOptionNamesMapping = [
1236
                'store' => 'create',
1237
            ];
1238
1239
            $option = array_key_exists($option, $customOptionNamesMapping) ? $customOptionNamesMapping[$option] : $option;
1240
1241
            $authorizableOptions = [
1242
                'create' => 'edit',
1243
                'edit' => 'edit',
1244
                'publish' => 'publish',
1245
                'feature' => 'feature',
1246
                'reorder' => 'reorder',
1247
                'delete' => 'delete',
1248
                'duplicate' => 'duplicate',
1249
                'restore' => 'delete',
1250
                'forceDelete' => 'delete',
1251
                'bulkForceDelete' => 'delete',
1252
                'bulkPublish' => 'publish',
1253
                'bulkRestore' => 'delete',
1254
                'bulkFeature' => 'feature',
1255
                'bulkDelete' => 'delete',
1256
                'bulkEdit' => 'edit',
1257
                'editInModal' => 'edit',
1258
                'skipCreateModal' => 'edit',
1259
            ];
1260
1261
            $authorized = array_key_exists($option, $authorizableOptions) ? Auth::guard('twill_users')->user()->can($authorizableOptions[$option]) : true;
1262
            return ($this->indexOptions[$option] ?? $this->defaultIndexOptions[$option] ?? false) && $authorized;
1263
        });
1264
    }
1265
1266
    /**
1267
     * @param array $prependScope
1268
     * @return array
1269
     */
1270
    protected function getBrowserData($prependScope = [])
1271
    {
1272
        if ($this->request->has('except')) {
1273
            $prependScope['exceptIds'] = $this->request->get('except');
1274
        }
1275
1276
        $scopes = $this->filterScope($prependScope);
1277
        $items = $this->getBrowserItems($scopes);
1278
        $data = $this->getBrowserTableData($items);
1279
1280
        return array_replace_recursive(['data' => $data], $this->indexData($this->request));
1281
    }
1282
1283
    /**
1284
     * @param \Illuminate\Database\Eloquent\Collection $items
1285
     * @return array
1286
     */
1287
    protected function getBrowserTableData($items)
1288
    {
1289
        $withImage = $this->moduleHas('medias');
1290
1291
        return $items->map(function ($item) use ($withImage) {
1292
            $columnsData = Collection::make($this->browserColumns)->mapWithKeys(function ($column) use ($item) {
1293
                return $this->getItemColumnData($item, $column);
1294
            })->toArray();
1295
1296
            $name = $columnsData[$this->titleColumnKey];
1297
            unset($columnsData[$this->titleColumnKey]);
1298
1299
            return [
1300
                'id' => $item[$this->identifierColumnKey],
1301
                'name' => $name,
1302
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', $item[$this->identifierColumnKey]),
1303
                'endpointType' => $this->repository->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on A17\Twill\Repositories\ModuleRepository. 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

1303
                'endpointType' => $this->repository->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
1304
            ] + $columnsData + ($withImage && !array_key_exists('thumbnail', $columnsData) ? [
1305
                'thumbnail' => $item->defaultCmsImage(['w' => 100, 'h' => 100]),
1306
            ] : []);
1307
        })->toArray();
1308
    }
1309
1310
    /**
1311
     * @param array $scopes
1312
     * @return \Illuminate\Database\Eloquent\Collection
1313
     */
1314
    protected function getBrowserItems($scopes = [])
1315
    {
1316
        return $this->getIndexItems($scopes, true);
1317
    }
1318
1319
    /**
1320
     * @param array $prepend
1321
     * @return array
1322
     */
1323
    protected function filterScope($prepend = [])
1324
    {
1325
        $scope = [];
1326
1327
        $requestFilters = $this->getRequestFilters();
1328
1329
        $this->filters = array_merge($this->filters, $this->defaultFilters);
1330
1331
        if (array_key_exists('status', $requestFilters)) {
1332
            switch ($requestFilters['status']) {
1333
                case 'published':
1334
                    $scope['published'] = true;
1335
                    break;
1336
                case 'draft':
1337
                    $scope['draft'] = true;
1338
                    break;
1339
                case 'trash':
1340
                    $scope['onlyTrashed'] = true;
1341
                    break;
1342
                case 'mine':
1343
                    $scope['mine'] = true;
1344
                    break;
1345
            }
1346
1347
            unset($requestFilters['status']);
1348
        }
1349
1350
        foreach ($this->filters as $key => $field) {
1351
            if (array_key_exists($key, $requestFilters)) {
1352
                $value = $requestFilters[$key];
1353
                if ($value == 0 || !empty($value)) {
1354
                    // add some syntaxic sugar to scope the same filter on multiple columns
1355
                    $fieldSplitted = explode('|', $field);
1356
                    if (count($fieldSplitted) > 1) {
1357
                        $requestValue = $requestFilters[$key];
1358
                        Collection::make($fieldSplitted)->each(function ($scopeKey) use (&$scope, $requestValue) {
1359
                            $scope[$scopeKey] = $requestValue;
1360
                        });
1361
                    } else {
1362
                        $scope[$field] = $requestFilters[$key];
1363
                    }
1364
                }
1365
            }
1366
        }
1367
1368
        return $prepend + $scope;
1369
    }
1370
1371
    /**
1372
     * @return array
1373
     */
1374
    protected function getRequestFilters()
1375
    {
1376
        if ($this->request->has('search')) {
1377
            return ['search' => $this->request->get('search')];
1378
        }
1379
1380
        return json_decode($this->request->get('filter'), true) ?? [];
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('filter') can also be of type null; however, parameter $json of json_decode() does only seem to accept string, 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

1380
        return json_decode(/** @scrutinizer ignore-type */ $this->request->get('filter'), true) ?? [];
Loading history...
1381
    }
1382
1383
    /**
1384
     * @return void
1385
     */
1386
    protected function applyFiltersDefaultOptions()
1387
    {
1388
        if (!count($this->filtersDefaultOptions) || $this->request->has('search')) {
1389
            return;
1390
        }
1391
1392
        $filters = $this->getRequestFilters();
1393
1394
        foreach ($this->filtersDefaultOptions as $filterName => $defaultOption) {
1395
            if (!isset($filters[$filterName])) {
1396
                $filters[$filterName] = $defaultOption;
1397
            }
1398
        }
1399
1400
        $this->request->merge(['filter' => json_encode($filters)]);
1401
    }
1402
1403
    /**
1404
     * @return array
1405
     */
1406
    protected function orderScope()
1407
    {
1408
        $orders = [];
1409
        if ($this->request->has('sortKey') && $this->request->has('sortDir')) {
1410
            if (($key = $this->request->get('sortKey')) == 'name') {
1411
                $sortKey = $this->titleColumnKey;
1412
            } elseif (!empty($key)) {
1413
                $sortKey = $key;
1414
            }
1415
1416
            if (isset($sortKey)) {
1417
                $orders[$this->indexColumns[$sortKey]['sortKey'] ?? $sortKey] = $this->request->get('sortDir');
1418
            }
1419
        }
1420
1421
        // don't apply default orders if reorder is enabled
1422
        $reorder = $this->getIndexOption('reorder');
1423
        $defaultOrders = ($reorder ? [] : ($this->defaultOrders ?? []));
1424
1425
        return $orders + $defaultOrders;
1426
    }
1427
1428
    /**
1429
     * @param int $id
1430
     * @param \A17\Twill\Models\Model|null $item
1431
     * @return array
1432
     */
1433
    protected function form($id, $item = null)
1434
    {
1435
        if (!$item && $id) {
1436
            $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1437
        } elseif (!$item && !$id) {
1438
            $item = $this->repository->newInstance();
0 ignored issues
show
Bug introduced by
The method newInstance() does not exist on A17\Twill\Repositories\ModuleRepository. 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

1438
            /** @scrutinizer ignore-call */ 
1439
            $item = $this->repository->newInstance();
Loading history...
1439
        }
1440
1441
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1442
        $previewRouteName = $fullRoutePrefix . 'preview';
1443
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1444
1445
        $baseUrl = $item->urlWithoutSlug ?? $this->getPermalinkBaseUrl();
0 ignored issues
show
Bug introduced by
The property urlWithoutSlug does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
1446
1447
        $data = [
1448
            'item' => $item,
1449
            'moduleName' => $this->moduleName,
1450
            'routePrefix' => $this->routePrefix,
1451
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1452
            'publish' => $item->canPublish ?? true,
0 ignored issues
show
Bug introduced by
The property canPublish does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
1453
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1454
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1455
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1456
            'translate' => $this->moduleHas('translations'),
1457
            'translateTitle' => $this->titleIsTranslatable(),
1458
            'permalink' => $this->getIndexOption('permalink'),
1459
            'createWithoutModal' => !$item[$this->identifierColumnKey] && $this->getIndexOption('skipCreateModal'),
1460
            'form_fields' => $this->repository->getFormFields($item),
1461
            'baseUrl' => $baseUrl,
1462
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1463
            'saveUrl' => $item[$this->identifierColumnKey] ? $this->getModuleRoute($item[$this->identifierColumnKey], 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
1464
            'editor' => Config::get('twill.enabled.block-editor') && $this->moduleHas('blocks') && !$this->disableEditor,
1465
            'blockPreviewUrl' => Route::has('admin.blocks.preview') ? URL::route('admin.blocks.preview') : '#',
1466
            'availableRepeaters' => $this->getRepeaterList()->toJson(),
1467
            'revisions' => $this->moduleHas('revisions') ? $item->revisionsArray() : null,
1468
        ] + (Route::has($previewRouteName) && $item[$this->identifierColumnKey] ? [
1469
            'previewUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'preview', [$item[$this->identifierColumnKey]]),
1470
        ] : [])
1471
             + (Route::has($restoreRouteName) && $item[$this->identifierColumnKey] ? [
1472
            'restoreUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'restoreRevision', [$item[$this->identifierColumnKey]]),
1473
        ] : []);
1474
1475
        return array_replace_recursive($data, $this->formData($this->request));
1476
    }
1477
1478
    /**
1479
     * @param int $id
1480
     * @return array
1481
     */
1482
    protected function modalFormData($id)
1483
    {
1484
        $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1485
        $fields = $this->repository->getFormFields($item);
1486
        $data = [];
1487
1488
        if ($this->moduleHas('translations') && isset($fields['translations'])) {
1489
            foreach ($fields['translations'] as $fieldName => $fieldValue) {
1490
                $data['fields'][] = [
1491
                    'name' => $fieldName,
1492
                    'value' => $fieldValue,
1493
                ];
1494
            }
1495
1496
            $data['languages'] = $item->getActiveLanguages();
1497
1498
            unset($fields['translations']);
1499
        }
1500
1501
        foreach ($fields as $fieldName => $fieldValue) {
1502
            $data['fields'][] = [
1503
                'name' => $fieldName,
1504
                'value' => $fieldValue,
1505
            ];
1506
        }
1507
1508
        return array_replace_recursive($data, $this->formData($this->request));
1509
    }
1510
1511
    /**
1512
     * @param Request $request
1513
     * @return array
1514
     */
1515
    protected function formData($request)
1516
    {
1517
        return [];
1518
    }
1519
1520
    /**
1521
     * @param Request $item
1522
     * @return array
1523
     */
1524
    protected function previewData($item)
1525
    {
1526
        return [];
1527
    }
1528
1529
    /**
1530
     * @return \A17\Twill\Http\Requests\Admin\Request
1531
     */
1532
    protected function validateFormRequest()
1533
    {
1534
        $unauthorizedFields = Collection::make($this->fieldsPermissions)->filter(function ($permission, $field) {
1535
            return Auth::guard('twill_users')->user()->cannot($permission);
1536
        })->keys();
1537
1538
        $unauthorizedFields->each(function ($field) {
1539
            $this->request->offsetUnset($field);
1540
        });
1541
1542
        return App::make("$this->namespace\Http\Requests\Admin\\" . $this->modelName . "Request");
1543
    }
1544
1545
    /**
1546
     * @return string
1547
     */
1548
    protected function getNamespace()
1549
    {
1550
        return $this->namespace ?? Config::get('twill.namespace');
1551
    }
1552
1553
    /**
1554
     * @return string
1555
     */
1556
    protected function getRoutePrefix()
1557
    {
1558
        if ($this->request->route() != null) {
1559
            $routePrefix = ltrim(str_replace(Config::get('twill.admin_app_path'), '', $this->request->route()->getPrefix()), "/");
1560
            return str_replace("/", ".", $routePrefix);
1561
        }
1562
1563
        return '';
1564
    }
1565
1566
    /**
1567
     * @return string
1568
     */
1569
    protected function getModelName()
1570
    {
1571
        return $this->modelName ?? ucfirst(Str::singular($this->moduleName));
1572
    }
1573
1574
    /**
1575
     * @return \A17\Twill\Repositories\ModuleRepository
1576
     */
1577
    protected function getRepository()
1578
    {
1579
        return App::make("$this->namespace\Repositories\\" . $this->modelName . "Repository");
1580
    }
1581
1582
    /**
1583
     * @return string
1584
     */
1585
    protected function getViewPrefix()
1586
    {
1587
        return "admin.$this->moduleName";
1588
    }
1589
1590
    /**
1591
     * @return string
1592
     */
1593
    protected function getModelTitle()
1594
    {
1595
        return camelCaseToWords($this->modelName);
1596
    }
1597
1598
    /**
1599
     * @return string
1600
     */
1601
    protected function getParentModuleForeignKey()
1602
    {
1603
        $moduleParts = explode('.', $this->moduleName);
1604
1605
        return Str::singular($moduleParts[count($moduleParts) - 2]) . '_id';
1606
    }
1607
1608
    /**
1609
     * @return string
1610
     */
1611
    protected function getPermalinkBaseUrl()
1612
    {
1613
        $appUrl = Config::get('app.url');
1614
1615
        if (blank(parse_url($appUrl)['scheme'] ?? null)) {
1616
            $appUrl = $this->request->getScheme() . '://' . $appUrl;
1617
        }
1618
1619
        return $appUrl . '/'
1620
            . ($this->moduleHas('translations') ? '{language}/' : '')
1621
            . ($this->moduleHas('revisions') ? '{preview}/' : '')
1622
            . ($this->permalinkBase ?? $this->moduleName)
1623
            . (isset($this->permalinkBase) && empty($this->permalinkBase) ? '' : '/');
1624
    }
1625
1626
    /**
1627
     * @param string $baseUrl
1628
     * @return string
1629
     */
1630
    protected function getPermalinkPrefix($baseUrl)
1631
    {
1632
        return rtrim(str_replace(['http://', 'https://', '{preview}/', '{language}/'], '', $baseUrl), "/") . '/';
1633
    }
1634
1635
    /**
1636
     * @param int $id
1637
     * @param string $action
1638
     * @return string
1639
     */
1640
    protected function getModuleRoute($id, $action)
1641
    {
1642
        return moduleRoute($this->moduleName, $this->routePrefix, $action, [$id]);
1643
    }
1644
1645
    /**
1646
     * @param string $behavior
1647
     * @return bool
1648
     */
1649
    protected function moduleHas($behavior)
1650
    {
1651
        return $this->repository->hasBehavior($behavior);
1652
    }
1653
1654
    /**
1655
     * @return bool
1656
     */
1657
    protected function titleIsTranslatable()
1658
    {
1659
        return $this->repository->isTranslatable(
1660
            $this->titleColumnKey
1661
        );
1662
    }
1663
1664
    /**
1665
     * @param string|null $back_link
1666
     * @param array $params
1667
     * @return void
1668
     */
1669
    protected function setBackLink($back_link = null, $params = [])
1670
    {
1671
        if (!isset($back_link)) {
1672
            if (($back_link = Session::get($this->getBackLinkSessionKey())) == null) {
1673
                $back_link = $this->request->headers->get('referer') ?? moduleRoute(
1674
                    $this->moduleName,
1675
                    $this->routePrefix,
1676
                    'index',
1677
                    $params
1678
                );
1679
            }
1680
        }
1681
1682
        if (!Session::get($this->moduleName . '_retain')) {
1683
            Session::put($this->getBackLinkSessionKey(), $back_link);
1684
        } else {
1685
            Session::put($this->moduleName . '_retain', false);
1686
        }
1687
    }
1688
1689
    /**
1690
     * @param string|null $fallback
1691
     * @param array $params
1692
     * @return string
1693
     */
1694
    protected function getBackLink($fallback = null, $params = [])
1695
    {
1696
        $back_link = Session::get($this->getBackLinkSessionKey(), $fallback);
1697
        return $back_link ?? moduleRoute($this->moduleName, $this->routePrefix, 'index', $params);
1698
    }
1699
1700
    /**
1701
     * @return string
1702
     */
1703
    protected function getBackLinkSessionKey()
1704
    {
1705
        return $this->moduleName . ($this->submodule ? $this->submoduleParentId ?? '' : '') . '_back_link';
1706
    }
1707
1708
    /**
1709
     * @param int $id
1710
     * @param array $params
1711
     * @return \Illuminate\Http\RedirectResponse
1712
     */
1713
    protected function redirectToForm($id, $params = [])
1714
    {
1715
        Session::put($this->moduleName . '_retain', true);
1716
1717
        return Redirect::to(moduleRoute(
1718
            $this->moduleName,
1719
            $this->routePrefix,
1720
            'edit',
1721
            array_filter($params) + [Str::singular($this->moduleName) => $id]
1722
        ));
1723
    }
1724
1725
    /**
1726
     * @param string $message
1727
     * @return \Illuminate\Http\JsonResponse
1728
     */
1729
    protected function respondWithSuccess($message)
1730
    {
1731
        return $this->respondWithJson($message, FlashLevel::SUCCESS);
1732
    }
1733
1734
    /**
1735
     * @param string $redirectUrl
1736
     * @return \Illuminate\Http\JsonResponse
1737
     */
1738
    protected function respondWithRedirect($redirectUrl)
1739
    {
1740
        return Response::json([
1741
            'redirect' => $redirectUrl,
1742
        ]);
1743
    }
1744
1745
    /**
1746
     * @param string $message
1747
     * @return \Illuminate\Http\JsonResponse
1748
     */
1749
    protected function respondWithError($message)
1750
    {
1751
        return $this->respondWithJson($message, FlashLevel::ERROR);
1752
    }
1753
1754
    /**
1755
     * @param string $message
1756
     * @param mixed $variant
1757
     * @return \Illuminate\Http\JsonResponse
1758
     */
1759
    protected function respondWithJson($message, $variant)
1760
    {
1761
        return Response::json([
1762
            'message' => $message,
1763
            'variant' => $variant,
1764
        ]);
1765
    }
1766
1767
    /**
1768
     * @param array $input
1769
     * @return void
1770
     */
1771
    protected function fireEvent($input = [])
1772
    {
1773
        fireCmsEvent('cms-module.saved', $input);
1774
    }
1775
1776
    /**
1777
     * @return Collection
1778
     */
1779
    public function getRepeaterList()
1780
    {
1781
        return app(BlockCollection::class)->getRepeaterList()->mapWithKeys(function ($repeater) {
1782
            return [$repeater['name'] => $repeater];
1783
        });
1784
    }
1785
}
1786