Passed
Push — fix/stdclass-support ( e3b384 )
by Quentin
07:26
created

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

407
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
408
        }
409
410
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-close')) {
411
            return $this->respondWithRedirect($this->getBackLink());
412
        }
413
414
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-new')) {
415
            return $this->respondWithRedirect(moduleRoute(
416
                $this->moduleName,
417
                $this->routePrefix,
418
                'create'
419
            ));
420
        }
421
422
        return $this->respondWithRedirect(moduleRoute(
423
            $this->moduleName,
424
            $this->routePrefix,
425
            'edit',
426
            [Str::singular(last(explode('.', $this->moduleName))) => $this->getItemIdentifier($item)]
427
        ));
428
    }
429
430
    /**
431
     * @param Request $request
432
     * @param int|$id
433
     * @param int|null $submoduleId
434
     * @return \Illuminate\Http\RedirectResponse
435
     */
436
    public function show($id, $submoduleId = null)
437
    {
438
        if ($this->getIndexOption('editInModal')) {
439
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
440
        }
441
442
        return $this->redirectToForm($this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
0 ignored issues
show
Bug introduced by
It seems like $this->getParentModuleId... ?? $submoduleId ?? $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

442
        return $this->redirectToForm(/** @scrutinizer ignore-type */ $this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
Loading history...
443
    }
444
445
    /**
446
     * @param int $id
447
     * @param int|null $submoduleId
448
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
449
     */
450
    public function edit($id, $submoduleId = null)
451
    {
452
        $params = $this->request->route()->parameters();
453
454
        $this->submodule = count($params) > 1;
455
        $this->submoduleParentId = $this->submodule
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->submodule ? $this... ?? $id : 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...
456
        ? $this->getParentModuleIdFromRequest($this->request) ?? $id
457
        : head($params);
458
459
        $id = last($params);
460
461
        if ($this->getIndexOption('editInModal')) {
462
            return $this->request->ajax()
463
            ? Response::json($this->modalFormData($id))
464
            : Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
465
        }
466
467
        $this->setBackLink();
468
469
        $view = Collection::make([
470
            "$this->viewPrefix.form",
471
            "twill::$this->moduleName.form",
472
            "twill::layouts.form",
473
        ])->first(function ($view) {
474
            return View::exists($view);
475
        });
476
477
        return View::make($view, $this->form($id));
478
    }
479
480
    /**
481
     * @param int $parentModuleId
482
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
483
     */
484
    public function create($parentModuleId = null)
485
    {
486
        if (!$this->getIndexOption('skipCreateModal')) {
487
            return Redirect::to(moduleRoute(
488
                $this->moduleName,
489
                $this->routePrefix,
490
                'index',
491
                ['openCreate' => true]
492
            ));
493
        }
494
495
        $parentModuleId = $this->getParentModuleIdFromRequest($this->request) ?? $parentModuleId;
496
497
        $this->submodule = isset($parentModuleId);
498
        $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...
499
500
        $view = Collection::make([
501
            "$this->viewPrefix.form",
502
            "twill::$this->moduleName.form",
503
            "twill::layouts.form",
504
        ])->first(function ($view) {
505
            return View::exists($view);
506
        });
507
508
        return View::make($view, $this->form(null));
509
    }
510
511
    /**
512
     * @param int $id
513
     * @param int|null $submoduleId
514
     * @return \Illuminate\Http\JsonResponse
515
     */
516
    public function update($id, $submoduleId = null)
517
    {
518
        $params = $this->request->route()->parameters();
519
520
        $submoduleParentId = $this->getParentModuleIdFromRequest($this->request) ?? $id;
521
        $this->submodule = isset($submoduleParentId);
522
        $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...
523
524
        $id = last($params);
525
526
        $item = $this->repository->getById($id);
527
        $input = $this->request->all();
528
529
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
530
            return $this->respondWithRedirect(moduleRoute(
531
                $this->moduleName,
532
                $this->routePrefix,
533
                'edit',
534
                [Str::singular($this->moduleName) => $id]
535
            ));
536
        } else {
537
            $formRequest = $this->validateFormRequest();
538
539
            $this->repository->update($id, $formRequest->all());
540
541
            activity()->performedOn($item)->log('updated');
542
543
            $this->fireEvent();
544
545
            if (isset($input['cmsSaveType'])) {
546
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
547
                    return $this->respondWithRedirect($this->getBackLink());
548
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
549
                    if ($this->getIndexOption('skipCreateModal')) {
550
                        return $this->respondWithRedirect(moduleRoute(
551
                            $this->moduleName,
552
                            $this->routePrefix,
553
                            'create'
554
                        ));
555
                    }
556
                    return $this->respondWithRedirect(moduleRoute(
557
                        $this->moduleName,
558
                        $this->routePrefix,
559
                        'index',
560
                        ['openCreate' => true]
561
                    ));
562
                } elseif ($input['cmsSaveType'] === 'restore') {
563
                    Session::flash('status', twillTrans('twill::lang.publisher.restore-success'));
564
565
                    return $this->respondWithRedirect(moduleRoute(
566
                        $this->moduleName,
567
                        $this->routePrefix,
568
                        'edit',
569
                        [Str::singular($this->moduleName) => $id]
570
                    ));
571
                }
572
            }
573
574
            if ($this->moduleHas('revisions')) {
575
                return Response::json([
576
                    'message' => twillTrans('twill::lang.publisher.save-success'),
577
                    'variant' => FlashLevel::SUCCESS,
578
                    'revisions' => $item->revisionsArray(),
579
                ]);
580
            }
581
582
            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

582
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
583
        }
584
    }
585
586
    /**
587
     * @param int $id
588
     * @return \Illuminate\View\View
589
     */
590
    public function preview($id)
591
    {
592
        if ($this->request->has('revisionId')) {
593
            $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

593
            /** @scrutinizer ignore-call */ 
594
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
Loading history...
594
        } else {
595
            $formRequest = $this->validateFormRequest();
596
            $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

596
            /** @scrutinizer ignore-call */ 
597
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
597
        }
598
599
        if ($this->request->has('activeLanguage')) {
600
            App::setLocale($this->request->get('activeLanguage'));
601
        }
602
603
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
604
605
        return View::exists($previewView) ? View::make($previewView, array_replace([
606
            'item' => $item,
607
        ], $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

607
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
608
            'moduleName' => Str::singular($this->moduleName),
609
        ]);
610
    }
611
612
    /**
613
     * @param int $id
614
     * @return \Illuminate\View\View
615
     */
616
    public function restoreRevision($id)
617
    {
618
        if ($this->request->has('revisionId')) {
619
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
620
            $item[$this->identifierColumnKey] = $id;
621
            $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...
622
        } else {
623
            throw new NotFoundHttpException();
624
        }
625
626
        $this->setBackLink();
627
628
        $view = Collection::make([
629
            "$this->viewPrefix.form",
630
            "twill::$this->moduleName.form",
631
            "twill::layouts.form",
632
        ])->first(function ($view) {
633
            return View::exists($view);
634
        });
635
636
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
637
        $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 Nette\Utils\DateTime or 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

637
        /** @scrutinizer ignore-call */ 
638
        $date = $revision->created_at->toDayDateTimeString();
Loading history...
638
639
        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...
640
641
        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

641
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
642
    }
643
644
    /**
645
     * @return \Illuminate\Http\JsonResponse
646
     */
647
    public function publish()
648
    {
649
        try {
650
            if ($this->repository->updateBasic($this->request->get('id'), [
651
                'published' => !$this->request->get('active'),
652
            ])) {
653
                activity()->performedOn(
654
                    $this->repository->getById($this->request->get('id'))
655
                )->log(
656
                    ($this->request->get('active') ? 'un' : '') . 'published'
657
                );
658
659
                $this->fireEvent();
660
661
                if ($this->request->get('active')) {
662
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.publish.unpublished', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

662
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.publish.unpublished', ['modelTitle' => $this->modelTitle]));
Loading history...
663
                } else {
664
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.publish.published', ['modelTitle' => $this->modelTitle]));
665
                }
666
            }
667
        } catch (\Exception $e) {
668
            \Log::error($e);
669
        }
670
671
        return $this->respondWithError(twillTrans('twill::lang.listing.publish.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

671
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.publish.error', ['modelTitle' => $this->modelTitle]));
Loading history...
672
    }
673
674
    /**
675
     * @return \Illuminate\Http\JsonResponse
676
     */
677
    public function bulkPublish()
678
    {
679
        try {
680
            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

680
            if ($this->repository->updateBasic(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')), [
Loading history...
681
                'published' => $this->request->get('publish'),
682
            ])) {
683
                $this->fireEvent();
684
                if ($this->request->get('publish')) {
685
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-publish.published', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

685
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-publish.published', ['modelTitle' => $this->modelTitle]));
Loading history...
686
                } else {
687
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-publish.unpublished', ['modelTitle' => $this->modelTitle]));
688
                }
689
            }
690
        } catch (\Exception $e) {
691
            \Log::error($e);
692
        }
693
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-publish.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

693
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-publish.error', ['modelTitle' => $this->modelTitle]));
Loading history...
694
    }
695
696
    /**
697
     * @param int $id
698
     * @param int|null $submoduleId
699
     * @return \Illuminate\Http\JsonResponse
700
     */
701
    public function duplicate($id, $submoduleId = null)
702
    {
703
        $params = $this->request->route()->parameters();
704
705
        $id = last($params);
706
707
        $item = $this->repository->getById($id);
708
        if ($newItem = $this->repository->duplicate($id, $this->titleColumnKey)) {
709
            $this->fireEvent();
710
            activity()->performedOn($item)->log('duplicated');
711
712
            return Response::json([
713
                'message' => twillTrans('twill::lang.listing.duplicate.success', ['modelTitle' => $this->modelTitle]),
714
                'variant' => FlashLevel::SUCCESS,
715
                'redirect' => moduleRoute(
716
                    $this->moduleName,
717
                    $this->routePrefix,
718
                    'edit',
719
                    array_filter([Str::singular($this->moduleName) => $newItem->id])
720
                ),
721
            ]);
722
        }
723
724
        return $this->respondWithError(twillTrans('twill::lang.listing.duplicate.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

724
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.duplicate.error', ['modelTitle' => $this->modelTitle]));
Loading history...
725
    }
726
727
    /**
728
     * @param int $id
729
     * @param int|null $submoduleId
730
     * @return \Illuminate\Http\JsonResponse
731
     */
732
    public function destroy($id, $submoduleId = null)
733
    {
734
        $params = $this->request->route()->parameters();
735
736
        $id = last($params);
737
738
        $item = $this->repository->getById($id);
739
        if ($this->repository->delete($id)) {
740
            $this->fireEvent();
741
            activity()->performedOn($item)->log('deleted');
742
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

742
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
743
        }
744
745
        return $this->respondWithError(twillTrans('twill::lang.listing.delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

745
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
746
    }
747
748
    /**
749
     * @return \Illuminate\Http\JsonResponse
750
     */
751
    public function bulkDelete()
752
    {
753
        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

753
        if ($this->repository->bulkDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
754
            $this->fireEvent();
755
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

755
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
756
        }
757
758
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

758
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
759
    }
760
761
    /**
762
     * @return \Illuminate\Http\JsonResponse
763
     */
764
    public function forceDelete()
765
    {
766
        if ($this->repository->forceDelete($this->request->get('id'))) {
767
            $this->fireEvent();
768
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.force-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

768
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.force-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
769
        }
770
771
        return $this->respondWithError(twillTrans('twill::lang.listing.force-delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

771
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.force-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
772
    }
773
774
    /**
775
     * @return \Illuminate\Http\JsonResponse
776
     */
777
    public function bulkForceDelete()
778
    {
779
        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

779
        if ($this->repository->bulkForceDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
780
            $this->fireEvent();
781
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-force-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

781
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-force-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
782
        }
783
784
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-force-delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

784
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-force-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
785
    }
786
787
    /**
788
     * @return \Illuminate\Http\JsonResponse
789
     */
790
    public function restore()
791
    {
792
        if ($this->repository->restore($this->request->get('id'))) {
793
            $this->fireEvent();
794
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
795
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.restore.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

795
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
796
        }
797
798
        return $this->respondWithError(twillTrans('twill::lang.listing.restore.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

798
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.restore.error', ['modelTitle' => $this->modelTitle]));
Loading history...
799
    }
800
801
    /**
802
     * @return \Illuminate\Http\JsonResponse
803
     */
804
    public function bulkRestore()
805
    {
806
        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

806
        if ($this->repository->bulkRestore(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
807
            $this->fireEvent();
808
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-restore.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

808
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
809
        }
810
811
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-restore.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

811
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-restore.error', ['modelTitle' => $this->modelTitle]));
Loading history...
812
    }
813
814
    /**
815
     * @return \Illuminate\Http\JsonResponse
816
     */
817
    public function feature()
818
    {
819
        if (($id = $this->request->get('id'))) {
820
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
821
            $featured = !$this->request->get('active');
822
823
            if ($this->repository->isUniqueFeature()) {
824
                if ($featured) {
825
                    $this->repository->updateBasic(null, [$featuredField => false]);
826
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
827
                }
828
            } else {
829
                $this->repository->updateBasic($id, [$featuredField => $featured]);
830
            }
831
832
            activity()->performedOn(
833
                $this->repository->getById($id)
834
            )->log(
835
                ($this->request->get('active') ? 'un' : '') . 'featured'
836
            );
837
838
            $this->fireEvent();
839
840
            if ($this->request->get('active')) {
841
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.featured.unfeatured', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

841
                return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.featured.unfeatured', ['modelTitle' => $this->modelTitle]));
Loading history...
842
            } else {
843
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.featured.featured', ['modelTitle' => $this->modelTitle]));
844
            }
845
        }
846
847
        return $this->respondWithError(twillTrans('twill::lang.listing.featured.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

847
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.featured.error', ['modelTitle' => $this->modelTitle]));
Loading history...
848
    }
849
850
    /**
851
     * @return \Illuminate\Http\JsonResponse
852
     */
853
    public function bulkFeature()
854
    {
855
        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

855
        if (($ids = explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
856
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
857
            $featured = $this->request->get('feature') ?? true;
858
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
859
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
860
            $this->fireEvent();
861
862
            if ($this->request->get('feature')) {
863
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-featured.featured', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

863
                return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-featured.featured', ['modelTitle' => $this->modelTitle]));
Loading history...
864
            } else {
865
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-featured.unfeatured', ['modelTitle' => $this->modelTitle]));
866
            }
867
        }
868
869
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-featured.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

869
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-featured.error', ['modelTitle' => $this->modelTitle]));
Loading history...
870
    }
871
872
    /**
873
     * @return \Illuminate\Http\JsonResponse
874
     */
875
    public function reorder()
876
    {
877
        if (($values = $this->request->get('ids')) && !empty($values)) {
878
            $this->repository->setNewOrder($values);
879
            $this->fireEvent();
880
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.reorder.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) 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

880
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.reorder.success', ['modelTitle' => $this->modelTitle]));
Loading history...
881
        }
882
883
        return $this->respondWithError(twillTrans('twill::lang.listing.reorder.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() 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

883
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.reorder.error', ['modelTitle' => $this->modelTitle]));
Loading history...
884
    }
885
886
    /**
887
     * @return \Illuminate\Http\JsonResponse
888
     */
889
    public function tags()
890
    {
891
        $query = $this->request->input('q');
892
        $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

892
        /** @scrutinizer ignore-call */ 
893
        $tags = $this->repository->getTags($query);
Loading history...
893
894
        return Response::json(['items' => $tags->map(function ($tag) {
895
            return $tag->name;
896
        })], 200);
897
    }
898
899
    /**
900
     * @return array
901
     */
902
    public function additionalTableActions()
903
    {
904
        return [];
905
    }
906
907
    /**
908
     * @param array $prependScope
909
     * @return array
910
     */
911
    protected function getIndexData($prependScope = [])
912
    {
913
        $scopes = $this->filterScope($prependScope);
914
        $items = $this->getIndexItems($scopes);
915
916
        $data = [
917
            'tableData' => $this->getIndexTableData($items),
918
            'tableColumns' => $this->getIndexTableColumns($items),
919
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
920
            '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

920
            'filters' => json_decode(/** @scrutinizer ignore-type */ $this->request->get('filter'), true) ?? [],
Loading history...
921
            'hiddenFilters' => array_keys(Arr::except($this->filters, array_keys($this->defaultFilters))),
922
            'filterLinks' => $this->filterLinks ?? [],
923
            'maxPage' => method_exists($items, 'lastPage') ? $items->lastPage() : 1,
924
            'defaultMaxPage' => method_exists($items, 'total') ? ceil($items->total() / $this->perPage) : 1,
925
            'offset' => method_exists($items, 'perPage') ? $items->perPage() : count($items),
926
            'defaultOffset' => $this->perPage,
927
        ] + $this->getIndexUrls($this->moduleName, $this->routePrefix);
928
929
        $baseUrl = $this->getPermalinkBaseUrl();
930
931
        $options = [
932
            'moduleName' => $this->moduleName,
933
            'skipCreateModal' => $this->getIndexOption('skipCreateModal'),
934
            'reorder' => $this->getIndexOption('reorder'),
935
            'create' => $this->getIndexOption('create'),
936
            'duplicate' => $this->getIndexOption('duplicate'),
937
            'translate' => $this->moduleHas('translations'),
938
            'translateTitle' => $this->titleIsTranslatable(),
939
            'permalink' => $this->getIndexOption('permalink'),
940
            'bulkEdit' => $this->getIndexOption('bulkEdit'),
941
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
942
            'baseUrl' => $baseUrl,
943
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
944
            'additionalTableActions' => $this->additionalTableActions(),
945
        ];
946
947
        return array_replace_recursive($data + $options, $this->indexData($this->request));
948
    }
949
950
    /**
951
     * @param Request $request
952
     * @return array
953
     */
954
    protected function indexData($request)
955
    {
956
        return [];
957
    }
958
959
    /**
960
     * @param array $scopes
961
     * @param bool $forcePagination
962
     * @return \Illuminate\Database\Eloquent\Collection
963
     */
964
    protected function getIndexItems($scopes = [], $forcePagination = false)
965
    {
966
        return $this->transformIndexItems($this->repository->get(
967
            $this->indexWith,
968
            $scopes,
969
            $this->orderScope(),
970
            $this->request->get('offset') ?? $this->perPage ?? 50,
971
            $forcePagination
972
        ));
973
    }
974
975
    /**
976
     * @param \Illuminate\Database\Eloquent\Collection $items
977
     * @return \Illuminate\Database\Eloquent\Collection
978
     */
979
    protected function transformIndexItems($items)
980
    {
981
        return $items;
982
    }
983
984
    /**
985
     * @param \Illuminate\Database\Eloquent\Collection $items
986
     * @return array
987
     */
988
    protected function getIndexTableData($items)
989
    {
990
        $translated = $this->moduleHas('translations');
991
        return $items->map(function ($item) use ($translated) {
992
            $columnsData = Collection::make($this->indexColumns)->mapWithKeys(function ($column) use ($item) {
993
                return $this->getItemColumnData($item, $column);
994
            })->toArray();
995
996
            $name = $columnsData[$this->titleColumnKey];
997
998
            if (empty($name)) {
999
                if ($this->moduleHas('translations')) {
1000
                    $fallBackTranslation = $item->translations()->where('active', true)->first();
1001
1002
                    if (isset($fallBackTranslation->{$this->titleColumnKey})) {
1003
                        $name = $fallBackTranslation->{$this->titleColumnKey};
1004
                    }
1005
                }
1006
1007
                $name = $name ?? ('Missing ' . $this->titleColumnKey);
1008
            }
1009
1010
            unset($columnsData[$this->titleColumnKey]);
1011
1012
            $itemIsTrashed = method_exists($item, 'trashed') && $item->trashed();
1013
            $itemCanDelete = $this->getIndexOption('delete') && ($item->canDelete ?? true);
1014
            $canEdit = $this->getIndexOption('edit');
1015
            $canDuplicate = $this->getIndexOption('duplicate');
1016
1017
            $itemId = $this->getItemIdentifier($item);
1018
1019
            return array_replace([
1020
                'id' => $itemId,
1021
                'name' => $name,
1022
                'publish_start_date' => $item->publish_start_date,
1023
                'publish_end_date' => $item->publish_end_date,
1024
                'edit' => $canEdit ? $this->getModuleRoute($itemId, 'edit') : null,
0 ignored issues
show
Bug introduced by
It seems like $itemId can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::getModuleRoute() 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

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

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

1365
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', /** @scrutinizer ignore-type */ $this->getItemIdentifier($item)),
Loading history...
1366
                '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

1366
                'endpointType' => $this->repository->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
1367
            ] + $columnsData + ($withImage && !array_key_exists('thumbnail', $columnsData) ? [
1368
                'thumbnail' => $item->defaultCmsImage(['w' => 100, 'h' => 100]),
1369
            ] : []);
1370
        })->toArray();
1371
    }
1372
1373
    /**
1374
     * @param array $scopes
1375
     * @return \Illuminate\Database\Eloquent\Collection
1376
     */
1377
    protected function getBrowserItems($scopes = [])
1378
    {
1379
        return $this->getIndexItems($scopes, true);
1380
    }
1381
1382
    /**
1383
     * @param array $prepend
1384
     * @return array
1385
     */
1386
    protected function filterScope($prepend = [])
1387
    {
1388
        $scope = [];
1389
1390
        $requestFilters = $this->getRequestFilters();
1391
1392
        $this->filters = array_merge($this->filters, $this->defaultFilters);
1393
1394
        if (array_key_exists('status', $requestFilters)) {
1395
            switch ($requestFilters['status']) {
1396
                case 'published':
1397
                    $scope['published'] = true;
1398
                    break;
1399
                case 'draft':
1400
                    $scope['draft'] = true;
1401
                    break;
1402
                case 'trash':
1403
                    $scope['onlyTrashed'] = true;
1404
                    break;
1405
                case 'mine':
1406
                    $scope['mine'] = true;
1407
                    break;
1408
            }
1409
1410
            unset($requestFilters['status']);
1411
        }
1412
1413
        foreach ($this->filters as $key => $field) {
1414
            if (array_key_exists($key, $requestFilters)) {
1415
                $value = $requestFilters[$key];
1416
                if ($value == 0 || !empty($value)) {
1417
                    // add some syntaxic sugar to scope the same filter on multiple columns
1418
                    $fieldSplitted = explode('|', $field);
1419
                    if (count($fieldSplitted) > 1) {
1420
                        $requestValue = $requestFilters[$key];
1421
                        Collection::make($fieldSplitted)->each(function ($scopeKey) use (&$scope, $requestValue) {
1422
                            $scope[$scopeKey] = $requestValue;
1423
                        });
1424
                    } else {
1425
                        $scope[$field] = $requestFilters[$key];
1426
                    }
1427
                }
1428
            }
1429
        }
1430
1431
        return $prepend + $scope;
1432
    }
1433
1434
    /**
1435
     * @return array
1436
     */
1437
    protected function getRequestFilters()
1438
    {
1439
        if ($this->request->has('search')) {
1440
            return ['search' => $this->request->get('search')];
1441
        }
1442
1443
        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

1443
        return json_decode(/** @scrutinizer ignore-type */ $this->request->get('filter'), true) ?? [];
Loading history...
1444
    }
1445
1446
    /**
1447
     * @return void
1448
     */
1449
    protected function applyFiltersDefaultOptions()
1450
    {
1451
        if (!count($this->filtersDefaultOptions) || $this->request->has('search')) {
1452
            return;
1453
        }
1454
1455
        $filters = $this->getRequestFilters();
1456
1457
        foreach ($this->filtersDefaultOptions as $filterName => $defaultOption) {
1458
            if (!isset($filters[$filterName])) {
1459
                $filters[$filterName] = $defaultOption;
1460
            }
1461
        }
1462
1463
        $this->request->merge(['filter' => json_encode($filters)]);
1464
    }
1465
1466
    /**
1467
     * @return array
1468
     */
1469
    protected function orderScope()
1470
    {
1471
        $orders = [];
1472
        if ($this->request->has('sortKey') && $this->request->has('sortDir')) {
1473
            if (($key = $this->request->get('sortKey')) == 'name') {
1474
                $sortKey = $this->titleColumnKey;
1475
            } elseif (!empty($key)) {
1476
                $sortKey = $key;
1477
            }
1478
1479
            if (isset($sortKey)) {
1480
                $orders[$this->indexColumns[$sortKey]['sortKey'] ?? $sortKey] = $this->request->get('sortDir');
1481
            }
1482
        }
1483
1484
        // don't apply default orders if reorder is enabled
1485
        $reorder = $this->getIndexOption('reorder');
1486
        $defaultOrders = ($reorder ? [] : ($this->defaultOrders ?? []));
1487
1488
        return $orders + $defaultOrders;
1489
    }
1490
1491
    /**
1492
     * @param int $id
1493
     * @param \A17\Twill\Models\Model|null $item
1494
     * @return array
1495
     */
1496
    protected function form($id, $item = null)
1497
    {
1498
        if (!$item && $id) {
1499
            $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1500
        } elseif (!$item && !$id) {
1501
            $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

1501
            /** @scrutinizer ignore-call */ 
1502
            $item = $this->repository->newInstance();
Loading history...
1502
        }
1503
1504
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1505
        $previewRouteName = $fullRoutePrefix . 'preview';
1506
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1507
1508
        $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...
1509
        $localizedPermalinkBase = $this->getLocalizedPermalinkBase();
1510
1511
        $itemId = $this->getItemIdentifier($item);
1512
1513
        $data = [
1514
            'item' => $item,
1515
            'moduleName' => $this->moduleName,
1516
            'routePrefix' => $this->routePrefix,
1517
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1518
            '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...
1519
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1520
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1521
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1522
            'translate' => $this->moduleHas('translations'),
1523
            'translateTitle' => $this->titleIsTranslatable(),
1524
            'permalink' => $this->getIndexOption('permalink'),
1525
            'createWithoutModal' => !$itemId && $this->getIndexOption('skipCreateModal'),
1526
            'form_fields' => $this->repository->getFormFields($item),
1527
            'baseUrl' => $baseUrl,
1528
            'localizedPermalinkBase'=>$localizedPermalinkBase,
1529
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1530
            'saveUrl' => $itemId ? $this->getModuleRoute($itemId, 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
0 ignored issues
show
Bug introduced by
It seems like $itemId can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::getModuleRoute() 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

1530
            'saveUrl' => $itemId ? $this->getModuleRoute(/** @scrutinizer ignore-type */ $itemId, 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
Loading history...
1531
            'editor' => Config::get('twill.enabled.block-editor') && $this->moduleHas('blocks') && !$this->disableEditor,
1532
            'blockPreviewUrl' => Route::has('admin.blocks.preview') ? URL::route('admin.blocks.preview') : '#',
1533
            'availableRepeaters' => $this->getRepeaterList()->toJson(),
1534
            'revisions' => $this->moduleHas('revisions') ? $item->revisionsArray() : null,
1535
        ] + (Route::has($previewRouteName) && $itemId ? [
1536
            'previewUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'preview', [$itemId]),
1537
        ] : [])
1538
             + (Route::has($restoreRouteName) && $itemId ? [
1539
            'restoreUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'restoreRevision', [$itemId]),
1540
        ] : []);
1541
1542
        return array_replace_recursive($data, $this->formData($this->request));
1543
    }
1544
1545
    /**
1546
     * @param int $id
1547
     * @return array
1548
     */
1549
    protected function modalFormData($id)
1550
    {
1551
        $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1552
        $fields = $this->repository->getFormFields($item);
1553
        $data = [];
1554
1555
        if ($this->moduleHas('translations') && isset($fields['translations'])) {
1556
            foreach ($fields['translations'] as $fieldName => $fieldValue) {
1557
                $data['fields'][] = [
1558
                    'name' => $fieldName,
1559
                    'value' => $fieldValue,
1560
                ];
1561
            }
1562
1563
            $data['languages'] = $item->getActiveLanguages();
1564
1565
            unset($fields['translations']);
1566
        }
1567
1568
        foreach ($fields as $fieldName => $fieldValue) {
1569
            $data['fields'][] = [
1570
                'name' => $fieldName,
1571
                'value' => $fieldValue,
1572
            ];
1573
        }
1574
1575
        return array_replace_recursive($data, $this->formData($this->request));
1576
    }
1577
1578
    /**
1579
     * @param Request $request
1580
     * @return array
1581
     */
1582
    protected function formData($request)
1583
    {
1584
        return [];
1585
    }
1586
1587
    /**
1588
     * @param Request $item
1589
     * @return array
1590
     */
1591
    protected function previewData($item)
1592
    {
1593
        return [];
1594
    }
1595
1596
    /**
1597
     * @return \A17\Twill\Http\Requests\Admin\Request
1598
     */
1599
    protected function validateFormRequest()
1600
    {
1601
        $unauthorizedFields = Collection::make($this->fieldsPermissions)->filter(function ($permission, $field) {
1602
            return Auth::guard('twill_users')->user()->cannot($permission);
1603
        })->keys();
1604
1605
        $unauthorizedFields->each(function ($field) {
1606
            $this->request->offsetUnset($field);
1607
        });
1608
1609
        return App::make($this->getFormRequestClass());
1610
    }
1611
1612
    public function getFormRequestClass()
1613
    {
1614
        $request = "$this->namespace\Http\Requests\Admin\\" . $this->modelName . "Request";
1615
1616
        if (@class_exists($request)) {
1617
            return $request;
1618
        }
1619
1620
        return $this->getCapsuleFormRequestClass($this->modelName);
1621
    }
1622
1623
    /**
1624
     * @return string
1625
     */
1626
    protected function getNamespace()
1627
    {
1628
        return $this->namespace ?? Config::get('twill.namespace');
1629
    }
1630
1631
    /**
1632
     * @return string
1633
     */
1634
    protected function getRoutePrefix()
1635
    {
1636
        if ($this->request->route() != null) {
1637
            $routePrefix = ltrim(str_replace(Config::get('twill.admin_app_path'), '', $this->request->route()->getPrefix()), "/");
1638
            return str_replace("/", ".", $routePrefix);
1639
        }
1640
1641
        return '';
1642
    }
1643
1644
    /**
1645
     * @return string
1646
     */
1647
    protected function getModulePermalinkBase()
1648
    {
1649
        $base = '';
1650
        $moduleParts = explode('.', $this->moduleName);
1651
1652
        foreach ($moduleParts as $index => $name) {
1653
            if (array_key_last($moduleParts) !== $index) {
1654
                $singularName = Str::singular($name);
1655
                $modelClass = config('twill.namespace') . '\\Models\\' . Str::studly($singularName);
1656
1657
                if(!class_exists($modelClass)) {
1658
                    $modelClass = $this->getCapsuleByModule($name)['model'];
1659
                }
1660
1661
                $model = (new $modelClass)->findOrFail(request()->route()->parameter($singularName));
1662
                $hasSlug = Arr::has(class_uses($modelClass), HasSlug::class);
1663
1664
                $base .= $name . '/' . ($hasSlug ? $model->slug : $model->id) . '/';
1665
            } else {
1666
                $base .= $name;
1667
            }
1668
        }
1669
1670
        return $base;
1671
    }
1672
1673
    /**
1674
     * @return string
1675
     */
1676
    protected function getModelName()
1677
    {
1678
        return $this->modelName ?? ucfirst(Str::singular($this->moduleName));
1679
    }
1680
1681
    /**
1682
     * @return \A17\Twill\Repositories\ModuleRepository
1683
     */
1684
    protected function getRepository()
1685
    {
1686
        return App::make($this->getRepositoryClass($this->modelName));
1687
    }
1688
1689
    public function getRepositoryClass($model)
1690
    {
1691
        if (@class_exists($class = "$this->namespace\Repositories\\" . $model . "Repository")) {
1692
            return $class;
1693
        }
1694
1695
        return $this->getCapsuleRepositoryClass($model);
1696
    }
1697
1698
    /**
1699
     * @return string
1700
     */
1701
    protected function getViewPrefix()
1702
    {
1703
        $prefix = "admin.$this->moduleName";
1704
1705
        if (view()->exists("$prefix.form")) {
1706
            return $prefix;
1707
        }
1708
1709
        return $this->getCapsuleViewPrefix($this->moduleName);
1710
    }
1711
1712
    /**
1713
     * @return string
1714
     */
1715
    protected function getModelTitle()
1716
    {
1717
        return camelCaseToWords($this->modelName);
1718
    }
1719
1720
    /**
1721
     * @return string
1722
     */
1723
    protected function getParentModuleForeignKey()
1724
    {
1725
        $moduleParts = explode('.', $this->moduleName);
1726
1727
        return Str::singular($moduleParts[count($moduleParts) - 2]) . '_id';
1728
    }
1729
1730
    /**
1731
     * @return string
1732
     */
1733
    protected function getPermalinkBaseUrl()
1734
    {
1735
        $appUrl = Config::get('app.url');
1736
1737
        if (blank(parse_url($appUrl)['scheme'] ?? null)) {
1738
            $appUrl = $this->request->getScheme() . '://' . $appUrl;
1739
        }
1740
1741
        return $appUrl . '/'
1742
            . ($this->moduleHas('translations') ? '{language}/' : '')
1743
            . ($this->moduleHas('revisions') ? '{preview}/' : '')
1744
            . (empty($this->getLocalizedPermalinkBase()) ? ($this->permalinkBase ?? $this->getModulePermalinkBase()) : '')
1745
            . (((isset($this->permalinkBase) && empty($this->permalinkBase)) || !empty($this->getLocalizedPermalinkBase())) ? '' : '/');
1746
    }
1747
1748
    /**
1749
     * @return array
1750
     */
1751
    protected function getLocalizedPermalinkBase()
1752
    {
1753
        return [];
1754
    }
1755
1756
    /**
1757
     * @param string $baseUrl
1758
     * @return string
1759
     */
1760
    protected function getPermalinkPrefix($baseUrl)
1761
    {
1762
        return rtrim(str_replace(['http://', 'https://', '{preview}/', '{language}/'], '', $baseUrl), "/") . '/';
1763
    }
1764
1765
    /**
1766
     * @param int $id
1767
     * @param string $action
1768
     * @return string
1769
     */
1770
    protected function getModuleRoute($id, $action)
1771
    {
1772
        return moduleRoute($this->moduleName, $this->routePrefix, $action, [$id]);
1773
    }
1774
1775
    /**
1776
     * @param string $behavior
1777
     * @return bool
1778
     */
1779
    protected function moduleHas($behavior)
1780
    {
1781
        return $this->repository->hasBehavior($behavior);
1782
    }
1783
1784
    /**
1785
     * @return bool
1786
     */
1787
    protected function titleIsTranslatable()
1788
    {
1789
        return $this->repository->isTranslatable(
1790
            $this->titleColumnKey
1791
        );
1792
    }
1793
1794
    /**
1795
     * @param string|null $back_link
1796
     * @param array $params
1797
     * @return void
1798
     */
1799
    protected function setBackLink($back_link = null, $params = [])
1800
    {
1801
        if (!isset($back_link)) {
1802
            if (($back_link = Session::get($this->getBackLinkSessionKey())) == null) {
1803
                $back_link = $this->request->headers->get('referer') ?? moduleRoute(
1804
                    $this->moduleName,
1805
                    $this->routePrefix,
1806
                    'index',
1807
                    $params
1808
                );
1809
            }
1810
        }
1811
1812
        if (!Session::get($this->moduleName . '_retain')) {
1813
            Session::put($this->getBackLinkSessionKey(), $back_link);
1814
        } else {
1815
            Session::put($this->moduleName . '_retain', false);
1816
        }
1817
    }
1818
1819
    /**
1820
     * @param string|null $fallback
1821
     * @param array $params
1822
     * @return string
1823
     */
1824
    protected function getBackLink($fallback = null, $params = [])
1825
    {
1826
        $back_link = Session::get($this->getBackLinkSessionKey(), $fallback);
1827
        return $back_link ?? moduleRoute($this->moduleName, $this->routePrefix, 'index', $params);
1828
    }
1829
1830
    /**
1831
     * @return string
1832
     */
1833
    protected function getBackLinkSessionKey()
1834
    {
1835
        return $this->moduleName . ($this->submodule ? $this->submoduleParentId ?? '' : '') . '_back_link';
1836
    }
1837
1838
    /**
1839
     * @param int $id
1840
     * @param array $params
1841
     * @return \Illuminate\Http\RedirectResponse
1842
     */
1843
    protected function redirectToForm($id, $params = [])
1844
    {
1845
        Session::put($this->moduleName . '_retain', true);
1846
1847
        return Redirect::to(moduleRoute(
1848
            $this->moduleName,
1849
            $this->routePrefix,
1850
            'edit',
1851
            array_filter($params) + [Str::singular($this->moduleName) => $id]
1852
        ));
1853
    }
1854
1855
    /**
1856
     * @param string $message
1857
     * @return \Illuminate\Http\JsonResponse
1858
     */
1859
    protected function respondWithSuccess($message)
1860
    {
1861
        return $this->respondWithJson($message, FlashLevel::SUCCESS);
1862
    }
1863
1864
    /**
1865
     * @param string $redirectUrl
1866
     * @return \Illuminate\Http\JsonResponse
1867
     */
1868
    protected function respondWithRedirect($redirectUrl)
1869
    {
1870
        return Response::json([
1871
            'redirect' => $redirectUrl,
1872
        ]);
1873
    }
1874
1875
    /**
1876
     * @param string $message
1877
     * @return \Illuminate\Http\JsonResponse
1878
     */
1879
    protected function respondWithError($message)
1880
    {
1881
        return $this->respondWithJson($message, FlashLevel::ERROR);
1882
    }
1883
1884
    /**
1885
     * @param string $message
1886
     * @param mixed $variant
1887
     * @return \Illuminate\Http\JsonResponse
1888
     */
1889
    protected function respondWithJson($message, $variant)
1890
    {
1891
        return Response::json([
1892
            'message' => $message,
1893
            'variant' => $variant,
1894
        ]);
1895
    }
1896
1897
    /**
1898
     * @param array $input
1899
     * @return void
1900
     */
1901
    protected function fireEvent($input = [])
1902
    {
1903
        fireCmsEvent('cms-module.saved', $input);
1904
    }
1905
1906
    /**
1907
     * @return Collection
1908
     */
1909
    public function getRepeaterList()
1910
    {
1911
        return app(BlockCollection::class)->getRepeaterList()->mapWithKeys(function ($repeater) {
1912
            return [$repeater['name'] => $repeater];
1913
        });
1914
    }
1915
}
1916