ModuleController::form()   F
last analyzed

Complexity

Conditions 16
Paths 4608

Size

Total Lines 47
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 16.052

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 37
nc 4608
nop 2
dl 0
loc 47
ccs 16
cts 17
cp 0.9412
crap 16.052
rs 1.4
c 2
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

643
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
644
    }
645
646
    /**
647
     * @return \Illuminate\Http\JsonResponse
648
     */
649
    public function publish()
650
    {
651
        try {
652
            if ($this->repository->updateBasic($this->request->get('id'), [
653
                'published' => !$this->request->get('active'),
654
            ])) {
655
                activity()->performedOn(
656
                    $this->repository->getById($this->request->get('id'))
657
                )->log(
658
                    ($this->request->get('active') ? 'un' : '') . 'published'
659
                );
660
661
                $this->fireEvent();
662
663
                if ($this->request->get('active')) {
664
                    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

664
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.publish.unpublished', ['modelTitle' => $this->modelTitle]));
Loading history...
665
                } else {
666
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.publish.published', ['modelTitle' => $this->modelTitle]));
667
                }
668
            }
669
        } catch (\Exception $e) {
670
            \Log::error($e);
671
        }
672
673
        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

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

682
            if ($this->repository->updateBasic(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')), [
Loading history...
683
                'published' => $this->request->get('publish'),
684
            ])) {
685
                $this->fireEvent();
686
                if ($this->request->get('publish')) {
687
                    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

687
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-publish.published', ['modelTitle' => $this->modelTitle]));
Loading history...
688
                } else {
689
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-publish.unpublished', ['modelTitle' => $this->modelTitle]));
690
                }
691
            }
692
        } catch (\Exception $e) {
693
            \Log::error($e);
694
        }
695
        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

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

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

744
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
745
        }
746
747
        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

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

755
        if ($this->repository->bulkDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
756
            $this->fireEvent();
757 2
            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

757
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
758
        }
759 2
760 1
        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

760
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
761 1
    }
762 1
763
    /**
764
     * @return \Illuminate\Http\JsonResponse
765 1
     */
766
    public function forceDelete()
767
    {
768
        if ($this->repository->forceDelete($this->request->get('id'))) {
769
            $this->fireEvent();
770
            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

770
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.force-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
771
        }
772
773
        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

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

781
        if ($this->repository->bulkForceDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
782
            $this->fireEvent();
783
            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

783
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-force-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
784 2
        }
785
786 2
        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

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

797
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
798
        }
799 2
800 2
        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

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

808
        if ($this->repository->bulkRestore(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
809
            $this->fireEvent();
810
            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

810
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
811
        }
812
813
        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

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

843
                return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.featured.unfeatured', ['modelTitle' => $this->modelTitle]));
Loading history...
844
            } else {
845
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.featured.featured', ['modelTitle' => $this->modelTitle]));
846 1
            }
847
        }
848 1
849 1
        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

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

857
        if (($ids = explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
858
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
859
            $featured = $this->request->get('feature') ?? true;
860 6
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
861
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
862 6
            $this->fireEvent();
863 6
864
            if ($this->request->get('feature')) {
865
                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

865
                return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-featured.featured', ['modelTitle' => $this->modelTitle]));
Loading history...
866 6
            } else {
867 6
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-featured.unfeatured', ['modelTitle' => $this->modelTitle]));
868 6
            }
869 6
        }
870 6
871 6
        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

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

882
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.reorder.success', ['modelTitle' => $this->modelTitle]));
Loading history...
883 6
        }
884 6
885 6
        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

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

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

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

1026
                'edit' => $canEdit ? $this->getModuleRoute(/** @scrutinizer ignore-type */ $itemId, 'edit') : null,
Loading history...
1027
                'duplicate' => $canDuplicate ? $this->getModuleRoute($itemId, 'duplicate') : null,
1028
                'delete' => $itemCanDelete ? $this->getModuleRoute($itemId, 'destroy') : null,
1029
            ] + ($this->getIndexOption('editInModal') ? [
1030
                'editInModal' => $this->getModuleRoute($itemId, 'edit'),
1031
                'updateUrl' => $this->getModuleRoute($itemId, 'update'),
1032
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
1033
                'published' => $item->published,
1034 5
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
1035 5
                'featured' => $item->{$this->featureField},
1036
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
1037
                'deleted' => true,
1038 5
            ] : []) + (($this->getIndexOption('forceDelete') && $itemIsTrashed) ? [
1039
                'destroyable' => true,
1040
            ] : []) + ($translated ? [
1041 5
                'languages' => $item->getActiveLanguages(),
1042
            ] : []) + $columnsData, $this->indexItemData($item));
1043
        })->toArray();
1044
    }
1045
1046 5
    /**
1047
     * @param \A17\Twill\Models\Model $item
1048
     * @return array
1049
     */
1050
    protected function indexItemData($item)
1051
    {
1052
        return [];
1053
    }
1054 6
1055
    /**
1056 6
     * @param \A17\Twill\Models\Model $item
1057 6
     * @param array $column
1058
     * @return array
1059 6
     */
1060 6
    protected function getItemColumnData($item, $column)
1061
    {
1062 4
        if (isset($column['thumb']) && $column['thumb']) {
1063 4
            if (isset($column['present']) && $column['present']) {
1064 4
                return [
1065 4
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
1066
                ];
1067
            } else {
1068
                $variant = isset($column['variant']);
1069 4
                $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...
1070
                $crop = $variant ? $column['variant']['crop'] : head(array_keys(head($item->mediasParams)));
1071
                $params = $variant && isset($column['variant']['params'])
1072 6
                ? $column['variant']['params']
1073
                : ['w' => 80, 'h' => 80, 'fit' => 'crop'];
1074
1075
                return [
1076
                    'thumbnail' => $item->cmsImage($role, $crop, $params),
1077
                ];
1078
            }
1079
        }
1080
1081
        if (isset($column['nested']) && $column['nested']) {
1082 6
            $field = $column['nested'];
1083 6
            $nestedCount = $item->{$column['nested']}->count();
1084 6
            $module = Str::singular(last(explode('.', $this->moduleName)));
1085 6
            $value = '<a href="';
1086
            $value .= moduleRoute("$this->moduleName.$field", $this->routePrefix, 'index', [$module => $this->getItemIdentifier($item)]);
1087
            $value .= '">' . $nestedCount . " " . (strtolower(Str::plural($column['title'], $nestedCount))) . '</a>';
1088
        } else {
1089
            $field = $column['field'];
1090
            $value = $item->$field;
1091
        }
1092 6
1093 6
        if (isset($column['relationship'])) {
1094 6
            $field = $column['relationship'] . ucfirst($column['field']);
1095
1096
            $relation = $item->{$column['relationship']}();
1097 6
1098
            $value = collect($relation->get())
1099
                ->pluck($column['field'])
1100 6
                ->join(', ');
1101
        } elseif (isset($column['present']) && $column['present']) {
1102 6
            $value = $item->presentAdmin()->{$column['field']};
1103 4
        }
1104
1105 4
        if (isset($column['relatedBrowser']) && $column['relatedBrowser']) {
1106
            $field = 'relatedBrowser' . ucfirst($column['relatedBrowser']) . ucfirst($column['field']);
1107 4
            $value = $item->getRelated($column['relatedBrowser'])
1108 4
                ->pluck($column['field'])
1109 4
                ->join(', ');
1110 4
        }
1111 4
1112 4
        return [
1113 4
            "$field" => $value,
1114
        ];
1115
    }
1116
1117 6
    /**
1118 5
     * @param \A17\Twill\Models\Model $item
1119 5
     * @return int|string
1120 5
     */
1121 5
    protected function getItemIdentifier($item)
1122
    {
1123
        return $item->{$this->identifierColumnKey};
1124
    }
1125
1126
    /**
1127 6
     * @param \Illuminate\Database\Eloquent\Collection $items
1128
     * @return array
1129
     */
1130
    protected function getIndexTableColumns($items)
1131
    {
1132
        $tableColumns = [];
1133
        $visibleColumns = $this->request->get('columns') ?? false;
1134
1135 5
        if (isset(Arr::first($this->indexColumns)['thumb'])
1136
            && Arr::first($this->indexColumns)['thumb']
1137 5
        ) {
1138
            $tableColumns[] = [
1139 5
                'name' => 'thumbnail',
1140
                'label' => twillTrans('twill::lang.listing.columns.thumbnail'),
1141 5
                'visible' => $visibleColumns ? in_array('thumbnail', $visibleColumns) : true,
1142
                'optional' => true,
1143 5
                'sortable' => false,
1144 5
            ];
1145 5
            array_shift($this->indexColumns);
1146 5
        }
1147
1148
        if ($this->getIndexOption('feature')) {
1149 5
            $tableColumns[] = [
1150 5
                'name' => 'featured',
1151 5
                'label' => twillTrans('twill::lang.listing.columns.featured'),
1152 5
                'visible' => true,
1153 5
                'optional' => false,
1154
                'sortable' => false,
1155
            ];
1156
        }
1157 5
1158 5
        if ($this->getIndexOption('publish')) {
1159 5
            $tableColumns[] = [
1160 5
                'name' => 'published',
1161 5
                'label' => twillTrans('twill::lang.listing.columns.published'),
1162
                'visible' => true,
1163 5
                'optional' => false,
1164 5
                'sortable' => false,
1165 5
            ];
1166
        }
1167
1168
        $tableColumns[] = [
1169 5
            'name' => 'name',
1170 5
            'label' => $this->indexColumns[$this->titleColumnKey]['title'] ?? twillTrans('twill::lang.listing.columns.name'),
1171 5
            'visible' => true,
1172 5
            'optional' => false,
1173 5
            'sortable' => $this->getIndexOption('reorder') ? false : ($this->indexColumns[$this->titleColumnKey]['sort'] ?? false),
1174
        ];
1175
1176
        unset($this->indexColumns[$this->titleColumnKey]);
1177 5
1178
        foreach ($this->indexColumns as $column) {
1179
            if (isset($column['relationship'])) {
1180
                $columnName = $column['relationship'] . ucfirst($column['field']);
1181
            } elseif (isset($column['nested'])) {
1182
                $columnName = $column['nested'];
1183
            } elseif (isset($column['relatedBrowser'])) {
1184
                $columnName = 'relatedBrowser' . ucfirst($column['relatedBrowser']) . ucfirst($column['field']);
1185 6
            } else {
1186
                $columnName = $column['field'];
1187 6
            }
1188 6
1189
            $tableColumns[] = [
1190
                'name' => $columnName,
1191
                'label' => $column['title'],
1192
                'visible' => $visibleColumns ? in_array($columnName, $visibleColumns) : ($column['visible'] ?? true),
1193
                'optional' => $column['optional'] ?? true,
1194
                'sortable' => $this->getIndexOption('reorder') ? false : ($column['sort'] ?? false),
1195
                'html' => $column['html'] ?? false,
1196
            ];
1197
        }
1198
1199
        if ($this->getIndexOption('includeScheduledInList') && $this->repository->isFillable('publish_start_date')) {
0 ignored issues
show
Bug introduced by
The method isFillable() 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

1199
        if ($this->getIndexOption('includeScheduledInList') && $this->repository->/** @scrutinizer ignore-call */ isFillable('publish_start_date')) {
Loading history...
1200 6
            $tableColumns[] = [
1201
                'name' => 'publish_start_date',
1202 6
                'label' => twillTrans('twill::lang.listing.columns.published'),
1203 6
                'visible' => true,
1204 6
                'optional' => true,
1205
                'sortable' => true,
1206
            ];
1207 6
        }
1208
1209
        if ($this->moduleHas('translations') && count(getLocales()) > 1) {
1210
            $tableColumns[] = [
1211
                'name' => 'languages',
1212
                'label' => twillTrans('twill::lang.listing.languages'),
1213
                'visible' => $visibleColumns ? in_array('languages', $visibleColumns) : true,
1214 33
                'optional' => true,
1215
                'sortable' => false,
1216 33
            ];
1217
        }
1218 33
1219
        return $tableColumns;
1220
    }
1221 33
1222
    /**
1223
     * @param \Illuminate\Database\Eloquent\Collection $items
1224 33
     * @param array $scopes
1225
     * @return array
1226
     */
1227
    protected function getIndexTableMainFilters($items, $scopes = [])
1228
    {
1229
        $statusFilters = [];
1230
1231
        $scope = ($this->submodule ? [
1232
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
1233
        ] : []) + $scopes;
1234
1235
        $statusFilters[] = [
1236
            'name' => twillTrans('twill::lang.listing.filter.all-items'),
1237
            'slug' => 'all',
1238
            'number' => $this->repository->getCountByStatusSlug('all', $scope),
1239
        ];
1240
1241
        if ($this->moduleHas('revisions') && $this->getIndexOption('create')) {
1242
            $statusFilters[] = [
1243 33
                'name' => twillTrans('twill::lang.listing.filter.mine'),
1244 33
                'slug' => 'mine',
1245 33
                'number' => $this->repository->getCountByStatusSlug('mine', $scope),
1246
            ];
1247
        }
1248
1249
        if ($this->getIndexOption('publish')) {
1250
            $statusFilters[] = [
1251
                'name' => twillTrans('twill::lang.listing.filter.published'),
1252 2
                'slug' => 'published',
1253
                'number' => $this->repository->getCountByStatusSlug('published', $scope),
1254 2
            ];
1255
            $statusFilters[] = [
1256
                'name' => twillTrans('twill::lang.listing.filter.draft'),
1257
                'slug' => 'draft',
1258 2
                'number' => $this->repository->getCountByStatusSlug('draft', $scope),
1259 2
            ];
1260 2
        }
1261
1262 2
        if ($this->getIndexOption('restore')) {
1263
            $statusFilters[] = [
1264
                'name' => twillTrans('twill::lang.listing.filter.trash'),
1265
                'slug' => 'trash',
1266
                'number' => $this->repository->getCountByStatusSlug('trash', $scope),
1267
            ];
1268
        }
1269 2
1270
        return $statusFilters;
1271 2
    }
1272
1273 2
    /**
1274 2
     * @param string $moduleName
1275 2
     * @param string $routePrefix
1276 2
     * @return array
1277
     */
1278 2
    protected function getIndexUrls($moduleName, $routePrefix)
1279 2
    {
1280
        return Collection::make([
1281
            'create',
1282 2
            'store',
1283 2
            'publish',
1284 2
            'bulkPublish',
1285 2
            'restore',
1286 2
            'bulkRestore',
1287 2
            'forceDelete',
1288 2
            'bulkForceDelete',
1289 2
            'reorder',
1290
            'feature',
1291
            'bulkFeature',
1292
            'bulkDelete',
1293
        ])->mapWithKeys(function ($endpoint) {
1294
            return [
1295
                $endpoint . 'Url' => $this->getIndexOption($endpoint) ? moduleRoute(
1296 2
                    $this->moduleName,
1297
                    $this->routePrefix,
1298 2
                    $endpoint,
1299
                    $this->submodule ? [$this->submoduleParentId] : []
1300
                ) : null,
1301
            ];
1302
        })->toArray();
1303
    }
1304
1305 12
    /**
1306
     * @param string $option
1307 12
     * @return bool
1308
     */
1309 12
    protected function getIndexOption($option)
1310
    {
1311 12
        return once(function () use ($option) {
1312
            $customOptionNamesMapping = [
1313 12
                'store' => 'create',
1314 1
            ];
1315 1
1316 1
            $option = array_key_exists($option, $customOptionNamesMapping) ? $customOptionNamesMapping[$option] : $option;
1317 1
1318
            $authorizableOptions = [
1319
                'create' => 'edit',
1320
                'edit' => 'edit',
1321
                'publish' => 'publish',
1322
                'feature' => 'feature',
1323
                'reorder' => 'reorder',
1324
                'delete' => 'delete',
1325
                'duplicate' => 'duplicate',
1326
                'restore' => 'delete',
1327
                'forceDelete' => 'delete',
1328
                'bulkForceDelete' => 'delete',
1329 1
                'bulkPublish' => 'publish',
1330
                'bulkRestore' => 'delete',
1331
                'bulkFeature' => 'feature',
1332 12
                'bulkDelete' => 'delete',
1333 12
                'bulkEdit' => 'edit',
1334 2
                'editInModal' => 'edit',
1335 2
                'skipCreateModal' => 'edit',
1336
            ];
1337 2
1338 2
            $authorized = array_key_exists($option, $authorizableOptions) ? Auth::guard('twill_users')->user()->can($authorizableOptions[$option]) : true;
1339
            return ($this->indexOptions[$option] ?? $this->defaultIndexOptions[$option] ?? false) && $authorized;
1340
        });
1341
    }
1342
1343
    /**
1344 2
     * @param array $prependScope
1345
     * @return array
1346
     */
1347
    protected function getBrowserData($prependScope = [])
1348
    {
1349
        if ($this->request->has('except')) {
1350 12
            $prependScope['exceptIds'] = $this->request->get('except');
1351
        }
1352
1353
        $scopes = $this->filterScope($prependScope);
1354
        $items = $this->getBrowserItems($scopes);
1355
        $data = $this->getBrowserTableData($items);
1356 7
1357
        return array_replace_recursive(['data' => $data], $this->indexData($this->request));
1358 7
    }
1359
1360
    /**
1361
     * @param \Illuminate\Database\Eloquent\Collection $items
1362 7
     * @return array
1363
     */
1364
    protected function getBrowserTableData($items)
1365
    {
1366
        $withImage = $this->moduleHas('medias');
1367
1368 44
        return $items->map(function ($item) use ($withImage) {
1369
            $columnsData = Collection::make($this->browserColumns)->mapWithKeys(function ($column) use ($item) {
1370 44
                return $this->getItemColumnData($item, $column);
1371 44
            })->toArray();
1372
1373
            $name = $columnsData[$this->titleColumnKey];
1374
            unset($columnsData[$this->titleColumnKey]);
1375
1376
            return [
1377
                'id' => $this->getItemIdentifier($item),
1378
                'name' => $name,
1379
                '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

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

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

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

1515
            /** @scrutinizer ignore-call */ 
1516
            $item = $this->repository->newInstance();
Loading history...
1516
        }
1517 25
1518 4
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1519 25
        $previewRouteName = $fullRoutePrefix . 'preview';
1520
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1521 25
1522
        $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...
1523 25
        $localizedPermalinkBase = $this->getLocalizedPermalinkBase();
1524
1525 25
        $itemId = $this->getItemIdentifier($item);
1526
1527
        $data = [
1528
            'item' => $item,
1529
            'moduleName' => $this->moduleName,
1530
            'routePrefix' => $this->routePrefix,
1531 44
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1532
            '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...
1533 44
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1534
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1535
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1536
            'translate' => $this->moduleHas('translations'),
1537
            'translateTitle' => $this->titleIsTranslatable(),
1538
            'permalink' => $this->getIndexOption('permalink'),
1539 44
            'createWithoutModal' => !$itemId && $this->getIndexOption('skipCreateModal'),
1540
            'form_fields' => $this->repository->getFormFields($item),
1541 44
            'baseUrl' => $baseUrl,
1542 43
            'localizedPermalinkBase'=>$localizedPermalinkBase,
1543 43
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1544
            '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

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