Passed
Push — 2.x ( be0de6...9e463f )
by Quentin
12:23
created

ModuleController::getItemIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

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

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

407
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
408 22
        }
409 22
410 22
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-close')) {
411 22
            return $this->respondWithRedirect($this->getBackLink());
412
        }
413
414
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-new')) {
415
            return $this->respondWithRedirect(moduleRoute(
416
                $this->moduleName,
417
                $this->routePrefix,
418
                'create'
419
            ));
420
        }
421 1
422
        return $this->respondWithRedirect(moduleRoute(
423 1
            $this->moduleName,
424
            $this->routePrefix,
425
            'edit',
426
            [Str::singular(last(explode('.', $this->moduleName))) => $this->getItemIdentifier($item)]
427 1
        ));
428
    }
429
430
    /**
431
     * @param Request $request
432
     * @param int|$id
433
     * @param int|null $submoduleId
434
     * @return \Illuminate\Http\RedirectResponse
435 6
     */
436
    public function show($id, $submoduleId = null)
437 6
    {
438 6
        if ($this->getIndexOption('editInModal')) {
439
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
440 6
        }
441 2
442 1
        return $this->redirectToForm($this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
0 ignored issues
show
Bug introduced by
It seems like $this->getParentModuleId... ?? $submoduleId ?? $id can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::redirectToForm() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

442
        return $this->redirectToForm(/** @scrutinizer ignore-type */ $this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
Loading history...
443 2
    }
444
445
    /**
446 4
     * @param int $id
447
     * @param int|null $submoduleId
448 4
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
449 4
     */
450 4
    public function edit($id, $submoduleId = null)
451 4
    {
452 4
        $params = $this->request->route()->parameters();
453 4
454 4
        $this->submodule = count($params) > 1;
455
        $this->submoduleParentId = $this->submodule
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->submodule ? $this... ?? $id : head($params) can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
499 8
500
        $view = Collection::make([
501 8
            "$this->viewPrefix.form",
502
            "twill::$this->moduleName.form",
503
            "twill::layouts.form",
504
        ])->first(function ($view) {
505
            return View::exists($view);
506
        });
507
508
        return View::make($view, $this->form(null));
509 8
    }
510
511 8
    /**
512
     * @param int $id
513 8
     * @param int|null $submoduleId
514
     * @return \Illuminate\Http\JsonResponse
515 8
     */
516
    public function update($id, $submoduleId = null)
517 8
    {
518 8
        $params = $this->request->route()->parameters();
519
520 8
        $submoduleParentId = $this->getParentModuleIdFromRequest($this->request) ?? $id;
521
        $this->submodule = isset($submoduleParentId);
522
        $this->submoduleParentId = $submoduleParentId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $submoduleParentId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
523
524
        $id = last($params);
525
526
        $item = $this->repository->getById($id);
527
        $input = $this->request->all();
528
529
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
530
            return $this->respondWithRedirect(moduleRoute(
531
                $this->moduleName,
532
                $this->routePrefix,
533
                'edit',
534 8
                [Str::singular($this->moduleName) => $id]
535
            ));
536
        } else {
537
            $formRequest = $this->validateFormRequest();
538
539
            $this->repository->update($id, $formRequest->all());
540
541
            activity()->performedOn($item)->log('updated');
542
543
            $this->fireEvent();
544
545
            if (isset($input['cmsSaveType'])) {
546 8
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
547 7
                    return $this->respondWithRedirect($this->getBackLink());
548 7
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
549 7
                    if ($this->getIndexOption('skipCreateModal')) {
550 7
                        return $this->respondWithRedirect(moduleRoute(
551
                            $this->moduleName,
552
                            $this->routePrefix,
553
                            'create'
554 1
                        ));
555
                    }
556
                    return $this->respondWithRedirect(moduleRoute(
557
                        $this->moduleName,
558
                        $this->routePrefix,
559
                        'index',
560
                        ['openCreate' => true]
561
                    ));
562 1
                } elseif ($input['cmsSaveType'] === 'restore') {
563
                    Session::flash('status', twillTrans('twill::lang.publisher.restore-success'));
564 1
565
                    return $this->respondWithRedirect(moduleRoute(
566
                        $this->moduleName,
567 1
                        $this->routePrefix,
568 1
                        'edit',
569
                        [Str::singular($this->moduleName) => $id]
570
                    ));
571 1
                }
572
            }
573
574
            if ($this->moduleHas('revisions')) {
575 1
                return Response::json([
576
                    'message' => twillTrans('twill::lang.publisher.save-success'),
577 1
                    'variant' => FlashLevel::SUCCESS,
578 1
                    'revisions' => $item->revisionsArray(),
579 1
                ]);
580 1
            }
581
582
            return $this->respondWithSuccess(twillTrans('twill::lang.publisher.save-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.publisher.save-success') can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

593
            /** @scrutinizer ignore-call */ 
594
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
Loading history...
594
        } else {
595 1
            $formRequest = $this->validateFormRequest();
596
            $item = $this->repository->preview($id, $formRequest->all());
0 ignored issues
show
Bug introduced by
The method preview() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

596
            /** @scrutinizer ignore-call */ 
597
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
597
        }
598 1
599
        if ($this->request->has('activeLanguage')) {
600 1
            App::setLocale($this->request->get('activeLanguage'));
601 1
        }
602 1
603 1
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
604 1
605 1
        return View::exists($previewView) ? View::make($previewView, array_replace([
606 1
            'item' => $item,
607
        ], $this->previewData($item))) : View::make('twill::errors.preview', [
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $item of A17\Twill\Http\Controlle...ntroller::previewData() does only seem to accept Illuminate\Http\Request, maybe add an additional type check? ( Ignorable by Annotation )

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

607
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
608 1
            'moduleName' => Str::singular($this->moduleName),
609 1
        ]);
610
    }
611 1
612
    /**
613 1
     * @param int $id
614
     * @return \Illuminate\View\View
615
     */
616
    public function restoreRevision($id)
617
    {
618
        if ($this->request->has('revisionId')) {
619 2
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
620
            $item[$this->identifierColumnKey] = $id;
621
            $item->cmsRestoring = true;
0 ignored issues
show
Bug introduced by
The property cmsRestoring does not seem to exist on Illuminate\Database\Eloquent\Builder.
Loading history...
622 2
        } else {
623 2
            throw new NotFoundHttpException();
624
        }
625 2
626 2
        $this->setBackLink();
627 1
628 1
        $view = Collection::make([
629
            "$this->viewPrefix.form",
630
            "twill::$this->moduleName.form",
631 1
            "twill::layouts.form",
632
        ])->first(function ($view) {
633 1
            return View::exists($view);
634 1
        });
635
636
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
637 1
        $date = $revision->created_at->toDayDateTimeString();
0 ignored issues
show
Bug introduced by
The method toDayDateTimeString() does not exist on DateTime. It seems like you code against a sub-type of DateTime such as Nette\Utils\DateTime or Carbon\Carbon. ( Ignorable by Annotation )

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

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

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

Loading history...
640
641 1
        return View::make($view, $this->form($id, $item));
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $item of A17\Twill\Http\Controlle...oduleController::form() does only seem to accept A17\Twill\Models\Model|null, maybe add an additional type check? ( Ignorable by Annotation )

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

641
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
642 1
    }
643
644
    /**
645
     * @return \Illuminate\Http\JsonResponse
646
     */
647
    public function publish()
648
    {
649
        try {
650
            if ($this->repository->updateBasic($this->request->get('id'), [
651
                'published' => !$this->request->get('active'),
652
            ])) {
653
                activity()->performedOn(
654
                    $this->repository->getById($this->request->get('id'))
655
                )->log(
656
                    ($this->request->get('active') ? 'un' : '') . 'published'
657
                );
658
659
                $this->fireEvent();
660
661
                if ($this->request->get('active')) {
662
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.publish.unpublished', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

662
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.publish.unpublished', ['modelTitle' => $this->modelTitle]));
Loading history...
663
                } else {
664
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.publish.published', ['modelTitle' => $this->modelTitle]));
665
                }
666
            }
667
        } catch (\Exception $e) {
668
            \Log::error($e);
669
        }
670
671
        return $this->respondWithError(twillTrans('twill::lang.listing.publish.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

671
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.publish.error', ['modelTitle' => $this->modelTitle]));
Loading history...
672
    }
673
674
    /**
675
     * @return \Illuminate\Http\JsonResponse
676
     */
677
    public function bulkPublish()
678
    {
679
        try {
680
            if ($this->repository->updateBasic(explode(',', $this->request->get('ids')), [
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

680
            if ($this->repository->updateBasic(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')), [
Loading history...
681
                'published' => $this->request->get('publish'),
682
            ])) {
683
                $this->fireEvent();
684
                if ($this->request->get('publish')) {
685
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-publish.published', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

685
                    return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-publish.published', ['modelTitle' => $this->modelTitle]));
Loading history...
686
                } else {
687
                    return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-publish.unpublished', ['modelTitle' => $this->modelTitle]));
688
                }
689
            }
690
        } catch (\Exception $e) {
691
            \Log::error($e);
692
        }
693
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-publish.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

724
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.duplicate.error', ['modelTitle' => $this->modelTitle]));
Loading history...
725
    }
726
727
    /**
728
     * @param int $id
729
     * @param int|null $submoduleId
730
     * @return \Illuminate\Http\JsonResponse
731
     */
732
    public function destroy($id, $submoduleId = null)
733
    {
734
        $params = $this->request->route()->parameters();
735
736
        $id = last($params);
737
738
        $item = $this->repository->getById($id);
739
        if ($this->repository->delete($id)) {
740
            $this->fireEvent();
741
            activity()->performedOn($item)->log('deleted');
742
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

742
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
743
        }
744
745
        return $this->respondWithError(twillTrans('twill::lang.listing.delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

745
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
746
    }
747
748
    /**
749
     * @return \Illuminate\Http\JsonResponse
750
     */
751
    public function bulkDelete()
752
    {
753
        if ($this->repository->bulkDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

753
        if ($this->repository->bulkDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
754
            $this->fireEvent();
755
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

755
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
756
        }
757 2
758
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

758
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
759 2
    }
760 1
761 1
    /**
762 1
     * @return \Illuminate\Http\JsonResponse
763
     */
764
    public function forceDelete()
765 1
    {
766
        if ($this->repository->forceDelete($this->request->get('id'))) {
767
            $this->fireEvent();
768
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.force-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

768
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.force-delete.success', ['modelTitle' => $this->modelTitle]));
Loading history...
769
        }
770
771
        return $this->respondWithError(twillTrans('twill::lang.listing.force-delete.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

771
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.force-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
772
    }
773
774
    /**
775
     * @return \Illuminate\Http\JsonResponse
776
     */
777
    public function bulkForceDelete()
778
    {
779
        if ($this->repository->bulkForceDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

779
        if ($this->repository->bulkForceDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
780
            $this->fireEvent();
781
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-force-delete.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

784
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-force-delete.error', ['modelTitle' => $this->modelTitle]));
Loading history...
785
    }
786 2
787 2
    /**
788 2
     * @return \Illuminate\Http\JsonResponse
789
     */
790 2
    public function restore()
791
    {
792
        if ($this->repository->restore($this->request->get('id'))) {
793
            $this->fireEvent();
794
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
795
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.restore.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

795
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
796 2
        }
797
798
        return $this->respondWithError(twillTrans('twill::lang.listing.restore.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

798
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.restore.error', ['modelTitle' => $this->modelTitle]));
Loading history...
799 2
    }
800 2
801 1
    /**
802 1
     * @return \Illuminate\Http\JsonResponse
803
     */
804
    public function bulkRestore()
805 1
    {
806 1
        if ($this->repository->bulkRestore(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

806
        if ($this->repository->bulkRestore(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
807
            $this->fireEvent();
808
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-restore.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

808
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-restore.success', ['modelTitle' => $this->modelTitle]));
Loading history...
809
        }
810
811
        return $this->respondWithError(twillTrans('twill::lang.listing.bulk-restore.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

811
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-restore.error', ['modelTitle' => $this->modelTitle]));
Loading history...
812
    }
813
814
    /**
815
     * @return \Illuminate\Http\JsonResponse
816
     */
817
    public function feature()
818
    {
819
        if (($id = $this->request->get('id'))) {
820
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
821
            $featured = !$this->request->get('active');
822
823
            if ($this->repository->isUniqueFeature()) {
824
                if ($featured) {
825
                    $this->repository->updateBasic(null, [$featuredField => false]);
826
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
827
                }
828
            } else {
829
                $this->repository->updateBasic($id, [$featuredField => $featured]);
830
            }
831
832 4
            activity()->performedOn(
833
                $this->repository->getById($id)
834 4
            )->log(
835 4
                ($this->request->get('active') ? 'un' : '') . 'featured'
836 3
            );
837 3
838
            $this->fireEvent();
839
840
            if ($this->request->get('active')) {
841
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.featured.unfeatured', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

841
                return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.featured.unfeatured', ['modelTitle' => $this->modelTitle]));
Loading history...
842
            } else {
843
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.featured.featured', ['modelTitle' => $this->modelTitle]));
844
            }
845
        }
846 1
847
        return $this->respondWithError(twillTrans('twill::lang.listing.featured.error', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...ler::respondWithError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

847
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.featured.error', ['modelTitle' => $this->modelTitle]));
Loading history...
848 1
    }
849 1
850
    /**
851 1
     * @return \Illuminate\Http\JsonResponse
852
     */
853 1
    public function bulkFeature()
854
    {
855
        if (($ids = explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

855
        if (($ids = explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
856
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
857
            $featured = $this->request->get('feature') ?? true;
858
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
859
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
860 6
            $this->fireEvent();
861
862 6
            if ($this->request->get('feature')) {
863 6
                return $this->respondWithSuccess(twillTrans('twill::lang.listing.bulk-featured.featured', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

869
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.bulk-featured.error', ['modelTitle' => $this->modelTitle]));
Loading history...
870 6
    }
871 6
872 6
    /**
873 6
     * @return \Illuminate\Http\JsonResponse
874 6
     */
875 6
    public function reorder()
876 6
    {
877
        if (($values = $this->request->get('ids')) && !empty($values)) {
878 6
            $this->repository->setNewOrder($values);
879
            $this->fireEvent();
880
            return $this->respondWithSuccess(twillTrans('twill::lang.listing.reorder.success', ['modelTitle' => $this->modelTitle]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.... => $this->modelTitle)) can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

883
        return $this->respondWithError(/** @scrutinizer ignore-type */ twillTrans('twill::lang.listing.reorder.error', ['modelTitle' => $this->modelTitle]));
Loading history...
884 6
    }
885 6
886 6
    /**
887 6
     * @return \Illuminate\Http\JsonResponse
888 6
     */
889 6
    public function tags()
890 6
    {
891 6
        $query = $this->request->input('q');
892 6
        $tags = $this->repository->getTags($query);
0 ignored issues
show
Bug introduced by
The method getTags() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

892
        /** @scrutinizer ignore-call */ 
893
        $tags = $this->repository->getTags($query);
Loading history...
893
894
        return Response::json(['items' => $tags->map(function ($tag) {
895 6
            return $tag->name;
896
        })], 200);
897
    }
898
899
    /**
900
     * @return array
901
     */
902 5
    public function additionalTableActions()
903
    {
904 5
        return [];
905
    }
906
907
    /**
908
     * @param array $prependScope
909
     * @return array
910
     */
911
    protected function getIndexData($prependScope = [])
912 12
    {
913
        $scopes = $this->filterScope($prependScope);
914 12
        $items = $this->getIndexItems($scopes);
915 12
916
        $data = [
917 12
            'tableData' => $this->getIndexTableData($items),
918 12
            'tableColumns' => $this->getIndexTableColumns($items),
919
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
920
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('filter') can also be of type null; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1024
                'edit' => $canEdit ? $this->getModuleRoute(/** @scrutinizer ignore-type */ $itemId, 'edit') : null,
Loading history...
1025 5
                'duplicate' => $canDuplicate ? $this->getModuleRoute($itemId, 'duplicate') : null,
1026
                'delete' => $itemCanDelete ? $this->getModuleRoute($itemId, 'destroy') : null,
1027
            ] + ($this->getIndexOption('editInModal') ? [
1028
                'editInModal' => $this->getModuleRoute($itemId, 'edit'),
1029
                'updateUrl' => $this->getModuleRoute($itemId, 'update'),
1030
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
1031
                'published' => $item->published,
1032
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
1033
                'featured' => $item->{$this->featureField},
1034 5
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
1035 5
                'deleted' => true,
1036
            ] : []) + (($this->getIndexOption('forceDelete') && $itemIsTrashed) ? [
1037
                'destroyable' => true,
1038 5
            ] : []) + ($translated ? [
1039
                'languages' => $item->getActiveLanguages(),
1040
            ] : []) + $columnsData, $this->indexItemData($item));
1041 5
        })->toArray();
1042
    }
1043
1044
    /**
1045
     * @param \A17\Twill\Models\Model $item
1046 5
     * @return array
1047
     */
1048
    protected function indexItemData($item)
1049
    {
1050
        return [];
1051
    }
1052
1053
    /**
1054 6
     * @param \A17\Twill\Models\Model $item
1055
     * @param array $column
1056 6
     * @return array
1057 6
     */
1058
    protected function getItemColumnData($item, $column)
1059 6
    {
1060 6
        if (isset($column['thumb']) && $column['thumb']) {
1061
            if (isset($column['present']) && $column['present']) {
1062 4
                return [
1063 4
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
1064 4
                ];
1065 4
            } else {
1066
                $variant = isset($column['variant']);
1067
                $role = $variant ? $column['variant']['role'] : head(array_keys($item->mediasParams));
0 ignored issues
show
Bug introduced by
The property mediasParams does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

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

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

1365
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', /** @scrutinizer ignore-type */ $this->getItemIdentifier($item)),
Loading history...
1366
                'endpointType' => $this->repository->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

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

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

1501
            /** @scrutinizer ignore-call */ 
1502
            $item = $this->repository->newInstance();
Loading history...
1502
        }
1503
1504
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1505
        $previewRouteName = $fullRoutePrefix . 'preview';
1506
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1507 1
1508
        $baseUrl = $item->urlWithoutSlug ?? $this->getPermalinkBaseUrl();
0 ignored issues
show
Bug introduced by
The property urlWithoutSlug does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

Loading history...
1509 1
        $localizedPermalinkBase = $this->getLocalizedPermalinkBase();
1510
1511
        $itemId = $this->getItemIdentifier($item);
1512
1513
        $data = [
1514
            'item' => $item,
1515 25
            'moduleName' => $this->moduleName,
1516
            'routePrefix' => $this->routePrefix,
1517 25
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1518 4
            'publish' => $item->canPublish ?? true,
0 ignored issues
show
Bug introduced by
The property canPublish does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

Loading history...
1519 25
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1520
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1521 25
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1522
            'translate' => $this->moduleHas('translations'),
1523 25
            'translateTitle' => $this->titleIsTranslatable(),
1524
            'permalink' => $this->getIndexOption('permalink'),
1525 25
            'createWithoutModal' => !$itemId && $this->getIndexOption('skipCreateModal'),
1526
            'form_fields' => $this->repository->getFormFields($item),
1527
            'baseUrl' => $baseUrl,
1528
            'localizedPermalinkBase'=>$localizedPermalinkBase,
1529
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1530
            'saveUrl' => $itemId ? $this->getModuleRoute($itemId, 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
0 ignored issues
show
Bug introduced by
It seems like $itemId can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::getModuleRoute() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

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