Passed
Pull Request — 2.x (#729)
by Antonio Carlos
06:06
created

ModuleController   F

Complexity

Total Complexity 242

Size/Duplication

Total Lines 1768
Duplicated Lines 0 %

Test Coverage

Coverage 76.69%

Importance

Changes 7
Bugs 0 Features 1
Metric Value
eloc 759
dl 0
loc 1768
ccs 556
cts 725
cp 0.7669
rs 1.841
c 7
b 0
f 1
wmc 242

71 Methods

Rating   Name   Duplication   Size   Complexity  
A respondWithSuccess() 0 3 1
A bulkFeature() 0 12 3
A restore() 0 9 2
A setBackLink() 0 17 4
A getBrowserData() 0 11 2
A titleIsTranslatable() 0 4 1
A indexItemData() 0 3 1
A bulkDelete() 0 8 2
A redirectToForm() 0 9 1
A preview() 0 19 4
A transformIndexItems() 0 3 1
A getBrowserTableData() 0 21 3
A indexData() 0 3 1
A getIndexOption() 0 31 4
A getIndexData() 0 36 4
A getBrowserItems() 0 3 1
A __construct() 0 51 5
A getIndexUrls() 0 23 3
A getFormRequestClass() 0 9 2
A index() 0 26 5
A getRepositoryClass() 0 8 2
D getIndexTableData() 0 52 19
A getParentModuleForeignKey() 0 3 1
B orderScope() 0 20 7
B store() 0 53 10
A getRepository() 0 3 1
A respondWithRedirect() 0 4 1
A getRoutePrefix() 0 8 2
A getRepeaterList() 0 4 1
B filterScope() 0 46 11
A edit() 0 22 3
A tags() 0 8 1
A restoreRevision() 0 26 2
A destroy() 0 10 2
A bulkPublish() 0 18 4
A reorder() 0 9 3
A create() 0 22 2
A getModelName() 0 3 1
A setMiddlewarePermission() 0 8 1
A getModuleRoute() 0 7 2
A validateFormRequest() 0 11 1
A duplicate() 0 21 2
A bulkRestore() 0 8 2
A getIndexItems() 0 8 1
A forceDelete() 0 8 2
A browser() 0 3 1
A fireEvent() 0 3 1
B update() 0 62 9
A modalFormData() 0 27 5
C getIndexTableColumns() 0 74 14
A publish() 0 24 5
A applyFiltersDefaultOptions() 0 15 5
A show() 0 7 2
A feature() 0 26 6
A moduleHas() 0 3 1
A getBackLink() 0 4 1
A respondWithError() 0 3 1
A formData() 0 3 1
C getItemColumnData() 0 43 15
A getNamespace() 0 3 1
A getBackLinkSessionKey() 0 3 2
B getIndexTableMainFilters() 0 43 6
A bulkForceDelete() 0 8 2
F form() 0 44 16
A getViewPrefix() 0 9 2
A getPermalinkPrefix() 0 3 1
A respondWithJson() 0 5 1
A getModelTitle() 0 3 1
A previewData() 0 3 1
A getRequestFilters() 0 7 2
A getPermalinkBaseUrl() 0 13 6

How to fix   Complexity   

Complex Class

Complex classes like ModuleController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModuleController, and based on these observations, apply Extract Interface, too.

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

378
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
379
        }
380
381 25
        if ($parentModuleId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentModuleId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
382
            $params = [
383
                Str::singular(explode('.', $this->moduleName)[0]) => $parentModuleId,
384
                Str::singular(explode('.', $this->moduleName)[1]) => $item->id,
385
            ];
386
        } else {
387
            $params = [
388 25
                Str::singular($this->moduleName) => $item->id,
389
            ];
390
        }
391
392 25
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-close')) {
393
            return $this->respondWithRedirect($this->getBackLink());
394
        }
395
396 25
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-new')) {
397
            return $this->respondWithRedirect(moduleRoute(
398
                $this->moduleName,
399
                $this->routePrefix,
400
                'create'
401
            ));
402
        }
403
404 25
        return $this->respondWithRedirect(moduleRoute(
405 25
            $this->moduleName,
406 25
            $this->routePrefix,
407 25
            'edit',
408
            $params
409
        ));
410
    }
411
412
    /**
413
     * @param int|$id
414
     * @param int|null $submoduleId
415
     * @return \Illuminate\Http\RedirectResponse
416
     */
417 1
    public function show($id, $submoduleId = null)
418
    {
419 1
        if ($this->getIndexOption('editInModal')) {
420
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
421
        }
422
423 1
        return $this->redirectToForm($submoduleId ?? $id);
424
    }
425
426
    /**
427
     * @param int $id
428
     * @param int|null $submoduleId
429
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
430
     */
431 6
    public function edit($id, $submoduleId = null)
432
    {
433 6
        $this->submodule = isset($submoduleId);
434 6
        $this->submoduleParentId = $id;
435
436 6
        if ($this->getIndexOption('editInModal')) {
437 2
            return $this->request->ajax()
438 1
            ? Response::json($this->modalFormData($submoduleId ?? $id))
439 2
            : Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
440
        }
441
442 4
        $this->setBackLink();
443
444 4
        $view = Collection::make([
445 4
            "$this->viewPrefix.form",
446 4
            "twill::$this->moduleName.form",
447 4
            "twill::layouts.form",
448 4
        ])->first(function ($view) {
449 4
            return View::exists($view);
450 4
        });
451
452 4
        return View::make($view, $this->form($submoduleId ?? $id));
453
    }
454
455
    /**
456
     * @param int $id
457
     * @param int|null $submoduleId
458
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
459
     */
460
    public function create($parentModuleId = null)
461
    {
462
        if (!$this->getIndexOption('skipCreateModal')) {
463
            return Redirect::to(moduleRoute(
464
                $this->moduleName,
465
                $this->routePrefix,
466
                'index',
467
                ['openCreate' => true]
468
            ));
469
        }
470
        $this->submodule = isset($parentModuleId);
471
        $this->submoduleParentId = $parentModuleId;
472
473
        $view = Collection::make([
474
            "$this->viewPrefix.form",
475
            "twill::$this->moduleName.form",
476
            "twill::layouts.form",
477
        ])->first(function ($view) {
478
            return View::exists($view);
479
        });
480
481
        return View::make($view, $this->form(null));
482
    }
483
484
    /**
485
     * @param int $id
486
     * @param int|null $submoduleId
487
     * @return \Illuminate\Http\JsonResponse
488
     */
489 8
    public function update($id, $submoduleId = null)
490
    {
491 8
        $this->submodule = isset($submoduleId);
492 8
        $this->submoduleParentId = $id;
493
494 8
        $item = $this->repository->getById($submoduleId ?? $id);
495 8
        $input = $this->request->all();
496
497 8
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
498
            return $this->respondWithRedirect(moduleRoute(
499
                $this->moduleName,
500
                $this->routePrefix,
501
                'edit',
502
                [Str::singular($this->moduleName) => $id]
503
            ));
504
        } else {
505 8
            $formRequest = $this->validateFormRequest();
506
507 8
            $this->repository->update($submoduleId ?? $id, $formRequest->all());
508
509 8
            activity()->performedOn($item)->log('updated');
510
511 8
            $this->fireEvent();
512
513 8
            if (isset($input['cmsSaveType'])) {
514 8
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
515
                    return $this->respondWithRedirect($this->getBackLink());
516 8
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
517
                    if ($this->getIndexOption('skipCreateModal')) {
518
                        return $this->respondWithRedirect(moduleRoute(
519
                            $this->moduleName,
520
                            $this->routePrefix,
521
                            'create'
522
                        ));
523
                    }
524
                    return $this->respondWithRedirect(moduleRoute(
525
                        $this->moduleName,
526
                        $this->routePrefix,
527
                        'index',
528
                        ['openCreate' => true]
529
                    ));
530 8
                } elseif ($input['cmsSaveType'] === 'restore') {
531
                    Session::flash('status', twillTrans('twill::lang.publisher.restore-success'));
532
533
                    return $this->respondWithRedirect(moduleRoute(
534
                        $this->moduleName,
535
                        $this->routePrefix,
536
                        'edit',
537
                        [Str::singular($this->moduleName) => $id]
538
                    ));
539
                }
540
            }
541
542 8
            if ($this->moduleHas('revisions')) {
543 7
                return Response::json([
544 7
                    'message' => twillTrans('twill::lang.publisher.save-success'),
545 7
                    'variant' => FlashLevel::SUCCESS,
546 7
                    'revisions' => $item->revisionsArray(),
547
                ]);
548
            }
549
550 1
            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

550
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
551
        }
552
    }
553
554
    /**
555
     * @param int $id
556
     * @return \Illuminate\View\View
557
     */
558 1
    public function preview($id)
559
    {
560 1
        if ($this->request->has('revisionId')) {
561
            $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

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

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

564
            /** @scrutinizer ignore-call */ 
565
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
565
        }
566
567 1
        if ($this->request->has('activeLanguage')) {
568
            App::setLocale($this->request->get('activeLanguage'));
569
        }
570
571 1
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
572
573 1
        return View::exists($previewView) ? View::make($previewView, array_replace([
574 1
            'item' => $item,
575 1
        ], $this->previewData($item))) : View::make('twill::errors.preview', [
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $item of A17\Twill\Http\Controlle...ntroller::previewData() does only seem to accept Illuminate\Http\Request, maybe add an additional type check? ( Ignorable by Annotation )

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

575
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
576 1
            'moduleName' => Str::singular($this->moduleName),
577
        ]);
578
    }
579
580
    /**
581
     * @param int $id
582
     * @return \Illuminate\View\View
583
     */
584 2
    public function restoreRevision($id)
585
    {
586 2
        if ($this->request->has('revisionId')) {
587 1
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
588 1
            $item->id = $id;
0 ignored issues
show
Bug introduced by
The property id does not seem to exist on Illuminate\Database\Eloquent\Builder.
Loading history...
589 1
            $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...
590
        } else {
591 1
            throw new NotFoundHttpException();
592
        }
593
594 1
        $this->setBackLink();
595
596 1
        $view = Collection::make([
597 1
            "$this->viewPrefix.form",
598 1
            "twill::$this->moduleName.form",
599 1
            "twill::layouts.form",
600 1
        ])->first(function ($view) {
601 1
            return View::exists($view);
602 1
        });
603
604 1
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
605 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 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

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

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

Loading history...
608
609 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

609
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
610
    }
611
612
    /**
613
     * @return \Illuminate\Http\JsonResponse
614
     */
615 3
    public function publish()
616
    {
617
        try {
618 3
            if ($this->repository->updateBasic($this->request->get('id'), [
619 3
                'published' => !$this->request->get('active'),
620
            ])) {
621 3
                activity()->performedOn(
622 3
                    $this->repository->getById($this->request->get('id'))
623 2
                )->log(
624 2
                    ($this->request->get('active') ? 'un' : '') . 'published'
625
                );
626
627 2
                $this->fireEvent();
628
629 2
                return $this->respondWithSuccess(
630 2
                    $this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'published!'
631
                );
632
            }
633 1
        } catch (\Exception $e) {
634 1
            \Log::error($e);
635
        }
636
637 1
        return $this->respondWithError(
638 1
            $this->modelTitle . ' was not published. Something wrong happened!'
639
        );
640
    }
641
642
    /**
643
     * @return \Illuminate\Http\JsonResponse
644
     */
645
    public function bulkPublish()
646
    {
647
        try {
648
            if ($this->repository->updateBasic(explode(',', $this->request->get('ids')), [
649
                'published' => $this->request->get('publish'),
650
            ])) {
651
                $this->fireEvent();
652
653
                return $this->respondWithSuccess(
654
                    $this->modelTitle . ' items ' . ($this->request->get('publish') ? '' : 'un') . 'published!'
655
                );
656
            }
657
        } catch (\Exception $e) {
658
            \Log::error($e);
659
        }
660
661
        return $this->respondWithError(
662
            $this->modelTitle . ' items were not published. Something wrong happened!'
663
        );
664
    }
665
666
    /**
667
     * @param int $id
668
     * @param int|null $submoduleId
669
     * @return \Illuminate\Http\JsonResponse
670
     */
671
    public function duplicate($id, $submoduleId = null)
672
    {
673
674
        $item = $this->repository->getById($submoduleId ?? $id);
675
        if ($newItem = $this->repository->duplicate($submoduleId ?? $id, $this->titleColumnKey)) {
676
            $this->fireEvent();
677
            activity()->performedOn($item)->log('duplicated');
678
679
            return Response::json([
680
                'message' => $this->modelTitle . ' duplicated with Success!',
681
                'variant' => FlashLevel::SUCCESS,
682
                'redirect' => moduleRoute(
683
                    $this->moduleName,
684
                    $this->routePrefix,
685
                    'edit',
686
                    array_filter([Str::singular($this->moduleName) => $newItem->id])
687
                ),
688
            ]);
689
        }
690
691
        return $this->respondWithError($this->modelTitle . ' was not duplicated. Something wrong happened!');
692
    }
693
694
    /**
695
     * @param int $id
696
     * @param int|null $submoduleId
697
     * @return \Illuminate\Http\JsonResponse
698
     */
699 2
    public function destroy($id, $submoduleId = null)
700
    {
701 2
        $item = $this->repository->getById($submoduleId ?? $id);
702 2
        if ($this->repository->delete($submoduleId ?? $id)) {
703 2
            $this->fireEvent();
704 2
            activity()->performedOn($item)->log('deleted');
705 2
            return $this->respondWithSuccess($this->modelTitle . ' moved to trash!');
706
        }
707
708
        return $this->respondWithError($this->modelTitle . ' was not moved to trash. Something wrong happened!');
709
    }
710
711
    /**
712
     * @return \Illuminate\Http\JsonResponse
713
     */
714
    public function bulkDelete()
715
    {
716
        if ($this->repository->bulkDelete(explode(',', $this->request->get('ids')))) {
717
            $this->fireEvent();
718
            return $this->respondWithSuccess($this->modelTitle . ' items moved to trash!');
719
        }
720
721
        return $this->respondWithError($this->modelTitle . ' items were not moved to trash. Something wrong happened!');
722
    }
723
724
    /**
725
     * @return \Illuminate\Http\JsonResponse
726
     */
727
    public function forceDelete()
728
    {
729
        if ($this->repository->forceDelete($this->request->get('id'))) {
730
            $this->fireEvent();
731
            return $this->respondWithSuccess($this->modelTitle . ' destroyed!');
732
        }
733
734
        return $this->respondWithError($this->modelTitle . ' was not destroyed. Something wrong happened!');
735
    }
736
737
    /**
738
     * @return \Illuminate\Http\JsonResponse
739
     */
740
    public function bulkForceDelete()
741
    {
742
        if ($this->repository->bulkForceDelete(explode(',', $this->request->get('ids')))) {
743
            $this->fireEvent();
744
            return $this->respondWithSuccess($this->modelTitle . ' items destroyed!');
745
        }
746
747
        return $this->respondWithError($this->modelTitle . ' items were not destroyed. Something wrong happened!');
748
    }
749
750
    /**
751
     * @return \Illuminate\Http\JsonResponse
752
     */
753 2
    public function restore()
754
    {
755 2
        if ($this->repository->restore($this->request->get('id'))) {
756 1
            $this->fireEvent();
757 1
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
758 1
            return $this->respondWithSuccess($this->modelTitle . ' restored!');
759
        }
760
761 1
        return $this->respondWithError($this->modelTitle . ' was not restored. Something wrong happened!');
762
    }
763
764
    /**
765
     * @return \Illuminate\Http\JsonResponse
766
     */
767
    public function bulkRestore()
768
    {
769
        if ($this->repository->bulkRestore(explode(',', $this->request->get('ids')))) {
770
            $this->fireEvent();
771
            return $this->respondWithSuccess($this->modelTitle . ' items restored!');
772
        }
773
774
        return $this->respondWithError($this->modelTitle . ' items were not restored. Something wrong happened!');
775
    }
776
777
    /**
778
     * @return \Illuminate\Http\JsonResponse
779
     */
780 2
    public function feature()
781
    {
782 2
        if (($id = $this->request->get('id'))) {
783 2
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
784 2
            $featured = !$this->request->get('active');
785
786 2
            if ($this->repository->isUniqueFeature()) {
787
                if ($featured) {
788
                    $this->repository->updateBasic(null, [$featuredField => false]);
789
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
790
                }
791
            } else {
792 2
                $this->repository->updateBasic($id, [$featuredField => $featured]);
793
            }
794
795 2
            activity()->performedOn(
796 2
                $this->repository->getById($id)
797 1
            )->log(
798 1
                ($this->request->get('active') ? 'un' : '') . 'featured'
799
            );
800
801 1
            $this->fireEvent();
802 1
            return $this->respondWithSuccess($this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'featured!');
803
        }
804
805
        return $this->respondWithError($this->modelTitle . ' was not featured. Something wrong happened!');
806
    }
807
808
    /**
809
     * @return \Illuminate\Http\JsonResponse
810
     */
811
    public function bulkFeature()
812
    {
813
        if (($ids = explode(',', $this->request->get('ids')))) {
814
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
815
            $featured = $this->request->get('feature') ?? true;
816
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
817
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
818
            $this->fireEvent();
819
            return $this->respondWithSuccess($this->modelTitle . ' items ' . ($this->request->get('feature') ? '' : 'un') . 'featured!');
820
        }
821
822
        return $this->respondWithError($this->modelTitle . ' items were not featured. Something wrong happened!');
823
    }
824
825
    /**
826
     * @return \Illuminate\Http\JsonResponse
827
     */
828 4
    public function reorder()
829
    {
830 4
        if (($values = $this->request->get('ids')) && !empty($values)) {
831 4
            $this->repository->setNewOrder($values);
832 3
            $this->fireEvent();
833 3
            return $this->respondWithSuccess($this->modelTitle . ' order changed!');
834
        }
835
836
        return $this->respondWithError($this->modelTitle . ' order was not changed. Something wrong happened!');
837
    }
838
839
    /**
840
     * @return \Illuminate\Http\JsonResponse
841
     */
842 1
    public function tags()
843
    {
844 1
        $query = $this->request->input('q');
845 1
        $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

845
        /** @scrutinizer ignore-call */ 
846
        $tags = $this->repository->getTags($query);
Loading history...
846
847 1
        return Response::json(['items' => $tags->map(function ($tag) {
848
            return $tag->name;
849 1
        })], 200);
850
    }
851
852
    /**
853
     * @param array $prependScope
854
     * @return array
855
     */
856 8
    protected function getIndexData($prependScope = [])
857
    {
858 8
        $scopes = $this->filterScope($prependScope);
859 8
        $items = $this->getIndexItems($scopes);
860
861
        $data = [
862 8
            'tableData' => $this->getIndexTableData($items),
863 8
            'tableColumns' => $this->getIndexTableColumns($items),
864 8
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
865 8
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
866 8
            'hiddenFilters' => array_keys(Arr::except($this->filters, array_keys($this->defaultFilters))),
867 8
            'filterLinks' => $this->filterLinks ?? [],
868 8
            'maxPage' => method_exists($items, 'lastPage') ? $items->lastPage() : 1,
869 8
            'defaultMaxPage' => method_exists($items, 'total') ? ceil($items->total() / $this->perPage) : 1,
870 8
            'offset' => method_exists($items, 'perPage') ? $items->perPage() : count($items),
871 8
            'defaultOffset' => $this->perPage,
872 8
        ] + $this->getIndexUrls($this->moduleName, $this->routePrefix);
873
874 8
        $baseUrl = $this->getPermalinkBaseUrl();
875
876
        $options = [
877 8
            'moduleName' => $this->moduleName,
878 8
            'skipCreateModal' => $this->getIndexOption('skipCreateModal'),
879 8
            'reorder' => $this->getIndexOption('reorder'),
880 8
            'create' => $this->getIndexOption('create'),
881 8
            'duplicate' => $this->getIndexOption('duplicate'),
882 8
            'translate' => $this->moduleHas('translations'),
883 8
            'translateTitle' => $this->titleIsTranslatable(),
884 8
            'permalink' => $this->getIndexOption('permalink'),
885 8
            'bulkEdit' => $this->getIndexOption('bulkEdit'),
886 8
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
887 8
            'baseUrl' => $baseUrl,
888 8
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
889
        ];
890
891 8
        return array_replace_recursive($data + $options, $this->indexData($this->request));
892
    }
893
894
    /**
895
     * @param Request $request
896
     * @return array
897
     */
898 7
    protected function indexData($request)
899
    {
900 7
        return [];
901
    }
902
903
    /**
904
     * @param array $scopes
905
     * @param bool $forcePagination
906
     * @return \Illuminate\Database\Eloquent\Collection
907
     */
908 14
    protected function getIndexItems($scopes = [], $forcePagination = false)
909
    {
910 14
        return $this->transformIndexItems($this->repository->get(
911 14
            $this->indexWith,
912
            $scopes,
913 14
            $this->orderScope(),
914 14
            $this->request->get('offset') ?? $this->perPage ?? 50,
915
            $forcePagination
916
        ));
917
    }
918
919
    /**
920
     * @param \Illuminate\Database\Eloquent\Collection $items
921
     * @return \Illuminate\Database\Eloquent\Collection
922
     */
923 12
    protected function transformIndexItems($items)
924
    {
925 12
        return $items;
926
    }
927
928
    /**
929
     * @param \Illuminate\Database\Eloquent\Collection $items
930
     * @return array
931
     */
932 8
    protected function getIndexTableData($items)
933
    {
934 8
        $translated = $this->moduleHas('translations');
935 8
        return $items->map(function ($item) use ($translated) {
936 4
            $columnsData = Collection::make($this->indexColumns)->mapWithKeys(function ($column) use ($item) {
937 4
                return $this->getItemColumnData($item, $column);
938 4
            })->toArray();
939
940 4
            $name = $columnsData[$this->titleColumnKey];
941
942 4
            if (empty($name)) {
943
                if ($this->moduleHas('translations')) {
944
                    $fallBackTranslation = $item->translations()->where('active', true)->first();
945
946
                    if (isset($fallBackTranslation->{$this->titleColumnKey})) {
947
                        $name = $fallBackTranslation->{$this->titleColumnKey};
948
                    }
949
                }
950
951
                $name = $name ?? ('Missing ' . $this->titleColumnKey);
952
            }
953
954 4
            unset($columnsData[$this->titleColumnKey]);
955
956 4
            $itemIsTrashed = method_exists($item, 'trashed') && $item->trashed();
957 4
            $itemCanDelete = $this->getIndexOption('delete') && ($item->canDelete ?? true);
958 4
            $canEdit = $this->getIndexOption('edit');
959 4
            $canDuplicate = $this->getIndexOption('duplicate');
960
961 4
            return array_replace([
962 4
                'id' => $item->id,
963 4
                'name' => $name,
964 4
                'publish_start_date' => $item->publish_start_date,
965 4
                'publish_end_date' => $item->publish_end_date,
966 4
                'edit' => $canEdit ? $this->getModuleRoute($item->id, 'edit') : null,
967 4
                'duplicate' => $canDuplicate ? $this->getModuleRoute($item->id, 'duplicate') : null,
968 4
                'delete' => $itemCanDelete ? $this->getModuleRoute($item->id, 'destroy') : null,
969 4
            ] + ($this->getIndexOption('editInModal') ? [
970 1
                'editInModal' => $this->getModuleRoute($item->id, 'edit'),
971 1
                'updateUrl' => $this->getModuleRoute($item->id, 'update'),
972 4
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
973 4
                'published' => $item->published,
974 4
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
975
                'featured' => $item->{$this->featureField},
976 4
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
977
                'deleted' => true,
978 4
            ] : []) + (($this->getIndexOption('forceDelete') && $itemIsTrashed) ? [
979
                'destroyable' => true,
980 4
            ] : []) + ($translated ? [
981 4
                'languages' => $item->getActiveLanguages(),
982 4
            ] : []) + $columnsData, $this->indexItemData($item));
983 8
        })->toArray();
984
    }
985
986
    /**
987
     * @param \A17\Twill\Models\Model $item
988
     * @return array
989
     */
990 3
    protected function indexItemData($item)
991
    {
992 3
        return [];
993
    }
994
995
    /**
996
     * @param \A17\Twill\Models\Model $item
997
     * @param array $column
998
     * @return array
999
     */
1000 6
    protected function getItemColumnData($item, $column)
1001
    {
1002 6
        if (isset($column['thumb']) && $column['thumb']) {
1003 2
            if (isset($column['present']) && $column['present']) {
1004
                return [
1005
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
1006
                ];
1007
            } else {
1008 2
                $variant = isset($column['variant']);
1009 2
                $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...
1010 2
                $crop = $variant ? $column['variant']['crop'] : head(array_keys(head($item->mediasParams)));
1011 2
                $params = $variant && isset($column['variant']['params'])
1012
                ? $column['variant']['params']
1013 2
                : ['w' => 80, 'h' => 80, 'fit' => 'crop'];
1014
1015
                return [
1016 2
                    'thumbnail' => $item->cmsImage($role, $crop, $params),
1017
                ];
1018
            }
1019
        }
1020
1021 6
        if (isset($column['nested']) && $column['nested']) {
1022
            $field = $column['nested'];
1023
            $nestedCount = $item->{$column['nested']}->count();
1024
            $value = '<a href="';
1025
            $value .= moduleRoute("$this->moduleName.$field", $this->routePrefix, 'index', [$item->id]);
1026
            $value .= '">' . $nestedCount . " " . (strtolower($nestedCount > 1
1027
                ? Str::plural($column['title'])
1028
                : Str::singular($column['title']))) . '</a>';
1029
        } else {
1030 6
            $field = $column['field'];
1031 6
            $value = $item->$field;
1032
        }
1033
1034 6
        if (isset($column['relationship'])) {
1035
            $field = $column['relationship'] . ucfirst($column['field']);
1036
            $value = Arr::get($item, "{$column['relationship']}.{$column['field']}");
1037 6
        } elseif (isset($column['present']) && $column['present']) {
1038
            $value = $item->presentAdmin()->{$column['field']};
1039
        }
1040
1041
        return [
1042 6
            "$field" => $value,
1043
        ];
1044
    }
1045
1046
    /**
1047
     * @param \Illuminate\Database\Eloquent\Collection $items
1048
     * @return array
1049
     */
1050 8
    protected function getIndexTableColumns($items)
1051
    {
1052 8
        $tableColumns = [];
1053 8
        $visibleColumns = $this->request->get('columns') ?? false;
1054
1055 8
        if (isset(Arr::first($this->indexColumns)['thumb'])
1056 8
            && Arr::first($this->indexColumns)['thumb']
1057
        ) {
1058 4
            array_push($tableColumns, [
1059 4
                'name' => 'thumbnail',
1060 4
                'label' => twillTrans('twill::lang.listing.columns.thumbnail'),
1061 4
                'visible' => $visibleColumns ? in_array('thumbnail', $visibleColumns) : true,
1062
                'optional' => true,
1063
                'sortable' => false,
1064
            ]);
1065 4
            array_shift($this->indexColumns);
1066
        }
1067
1068 8
        if ($this->getIndexOption('feature')) {
1069
            array_push($tableColumns, [
1070
                'name' => 'featured',
1071
                'label' => twillTrans('twill::lang.listing.columns.featured'),
1072
                'visible' => true,
1073
                'optional' => false,
1074
                'sortable' => false,
1075
            ]);
1076
        }
1077
1078 8
        if ($this->getIndexOption('publish')) {
1079 8
            array_push($tableColumns, [
1080 8
                'name' => 'published',
1081 8
                'label' => twillTrans('twill::lang.listing.columns.published'),
1082
                'visible' => true,
1083
                'optional' => false,
1084
                'sortable' => false,
1085
            ]);
1086
        }
1087
1088 8
        array_push($tableColumns, [
1089 8
            'name' => 'name',
1090 8
            'label' => $this->indexColumns[$this->titleColumnKey]['title'] ?? twillTrans('twill::lang.listing.columns.name'),
1091
            'visible' => true,
1092
            'optional' => false,
1093 8
            'sortable' => $this->getIndexOption('reorder') ? false : ($this->indexColumns[$this->titleColumnKey]['sort'] ?? false),
1094
        ]);
1095
1096 8
        unset($this->indexColumns[$this->titleColumnKey]);
1097
1098 8
        foreach ($this->indexColumns as $column) {
1099 4
            $columnName = isset($column['relationship'])
1100
            ? $column['relationship'] . ucfirst($column['field'])
1101 4
            : (isset($column['nested']) ? $column['nested'] : $column['field']);
1102
1103 4
            array_push($tableColumns, [
1104 4
                'name' => $columnName,
1105 4
                'label' => $column['title'],
1106 4
                'visible' => $visibleColumns ? in_array($columnName, $visibleColumns) : ($column['visible'] ?? true),
1107 4
                'optional' => $column['optional'] ?? true,
1108 4
                'sortable' => $this->getIndexOption('reorder') ? false : ($column['sort'] ?? false),
1109 4
                'html' => $column['html'] ?? false,
1110
            ]);
1111
        }
1112
1113 8
        if ($this->moduleHas('translations')) {
1114 7
            array_push($tableColumns, [
1115 7
                'name' => 'languages',
1116 7
                'label' => twillTrans('twill::lang.listing.languages'),
1117 7
                'visible' => $visibleColumns ? in_array('languages', $visibleColumns) : true,
1118
                'optional' => true,
1119
                'sortable' => false,
1120
            ]);
1121
        }
1122
1123 8
        return $tableColumns;
1124
    }
1125
1126
    /**
1127
     * @param \Illuminate\Database\Eloquent\Collection $items
1128
     * @param array $scopes
1129
     * @return array
1130
     */
1131 7
    protected function getIndexTableMainFilters($items, $scopes = [])
1132
    {
1133 7
        $statusFilters = [];
1134
1135 7
        $scope = ($this->submodule ? [
1136
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
1137 7
        ] : []) + $scopes;
1138
1139 7
        array_push($statusFilters, [
1140 7
            'name' => twillTrans('twill::lang.listing.filter.all-items'),
1141 7
            'slug' => 'all',
1142 7
            'number' => $this->repository->getCountByStatusSlug('all', $scope),
1143
        ]);
1144
1145 7
        if ($this->moduleHas('revisions') && $this->getIndexOption('create')) {
1146 7
            array_push($statusFilters, [
1147 7
                'name' => twillTrans('twill::lang.listing.filter.mine'),
1148 7
                'slug' => 'mine',
1149 7
                'number' => $this->repository->getCountByStatusSlug('mine', $scope),
1150
            ]);
1151
        }
1152
1153 7
        if ($this->getIndexOption('publish')) {
1154 7
            array_push($statusFilters, [
1155 7
                'name' => twillTrans('twill::lang.listing.filter.published'),
1156 7
                'slug' => 'published',
1157 7
                'number' => $this->repository->getCountByStatusSlug('published', $scope),
1158
            ], [
1159 7
                'name' => twillTrans('twill::lang.listing.filter.draft'),
1160 7
                'slug' => 'draft',
1161 7
                'number' => $this->repository->getCountByStatusSlug('draft', $scope),
1162
            ]);
1163
        }
1164
1165 7
        if ($this->getIndexOption('restore')) {
1166 7
            array_push($statusFilters, [
1167 7
                'name' => twillTrans('twill::lang.listing.filter.trash'),
1168 7
                'slug' => 'trash',
1169 7
                'number' => $this->repository->getCountByStatusSlug('trash', $scope),
1170
            ]);
1171
        }
1172
1173 7
        return $statusFilters;
1174
    }
1175
1176
    /**
1177
     * @param string $moduleName
1178
     * @param string $routePrefix
1179
     * @return array
1180
     */
1181 8
    protected function getIndexUrls($moduleName, $routePrefix)
1182
    {
1183 8
        return Collection::make([
1184 8
            'create',
1185
            'store',
1186
            'publish',
1187
            'bulkPublish',
1188
            'restore',
1189
            'bulkRestore',
1190
            'forceDelete',
1191
            'bulkForceDelete',
1192
            'reorder',
1193
            'feature',
1194
            'bulkFeature',
1195
            'bulkDelete',
1196 8
        ])->mapWithKeys(function ($endpoint) {
1197
            return [
1198 8
                $endpoint . 'Url' => $this->getIndexOption($endpoint) ? moduleRoute(
1199 8
                    $this->moduleName, $this->routePrefix, $endpoint,
1200 8
                    $this->submodule ? [$this->submoduleParentId] : []
1201
                ) : null,
1202
            ];
1203 8
        })->toArray();
1204
    }
1205
1206
    /**
1207
     * @param string $option
1208
     * @return bool
1209
     */
1210 37
    protected function getIndexOption($option)
1211
    {
1212 37
        return once(function () use ($option) {
1213
            $customOptionNamesMapping = [
1214 37
                'store' => 'create',
1215
            ];
1216
1217 37
            $option = array_key_exists($option, $customOptionNamesMapping) ? $customOptionNamesMapping[$option] : $option;
1218
1219
            $authorizableOptions = [
1220 37
                'create' => 'edit',
1221
                'edit' => 'edit',
1222
                'publish' => 'publish',
1223
                'feature' => 'feature',
1224
                'reorder' => 'reorder',
1225
                'delete' => 'delete',
1226
                'duplicate' => 'duplicate',
1227
                'restore' => 'delete',
1228
                'forceDelete' => 'delete',
1229
                'bulkForceDelete' => 'delete',
1230
                'bulkPublish' => 'publish',
1231
                'bulkRestore' => 'delete',
1232
                'bulkFeature' => 'feature',
1233
                'bulkDelete' => 'delete',
1234
                'bulkEdit' => 'edit',
1235
                'editInModal' => 'edit',
1236
                'skipCreateModal' => 'edit',
1237
            ];
1238
1239 37
            $authorized = array_key_exists($option, $authorizableOptions) ? Auth::guard('twill_users')->user()->can($authorizableOptions[$option]) : true;
1240 37
            return ($this->indexOptions[$option] ?? $this->defaultIndexOptions[$option] ?? false) && $authorized;
1241 37
        });
1242
    }
1243
1244
    /**
1245
     * @param array $prependScope
1246
     * @return array
1247
     */
1248 2
    protected function getBrowserData($prependScope = [])
1249
    {
1250 2
        if ($this->request->has('except')) {
1251
            $prependScope['exceptIds'] = $this->request->get('except');
1252
        }
1253
1254 2
        $scopes = $this->filterScope($prependScope);
1255 2
        $items = $this->getBrowserItems($scopes);
1256 2
        $data = $this->getBrowserTableData($items);
1257
1258 2
        return array_replace_recursive(['data' => $data], $this->indexData($this->request));
1259
    }
1260
1261
    /**
1262
     * @param \Illuminate\Database\Eloquent\Collection $items
1263
     * @return array
1264
     */
1265 2
    protected function getBrowserTableData($items)
1266
    {
1267 2
        $withImage = $this->moduleHas('medias');
1268
1269 2
        return $items->map(function ($item) use ($withImage) {
1270 2
            $columnsData = Collection::make($this->browserColumns)->mapWithKeys(function ($column) use ($item) {
1271 2
                return $this->getItemColumnData($item, $column);
1272 2
            })->toArray();
1273
1274 2
            $name = $columnsData[$this->titleColumnKey];
1275 2
            unset($columnsData[$this->titleColumnKey]);
1276
1277
            return [
1278 2
                'id' => $item->id,
1279 2
                'name' => $name,
1280 2
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', $item->id),
1281 2
                '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

1281
                'endpointType' => $this->repository->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
1282 2
            ] + $columnsData + ($withImage && !array_key_exists('thumbnail', $columnsData) ? [
1283 2
                'thumbnail' => $item->defaultCmsImage(['w' => 100, 'h' => 100]),
1284 2
            ] : []);
1285 2
        })->toArray();
1286
    }
1287
1288
    /**
1289
     * @param array $scopes
1290
     * @return \Illuminate\Database\Eloquent\Collection
1291
     */
1292 2
    protected function getBrowserItems($scopes = [])
1293
    {
1294 2
        return $this->getIndexItems($scopes, true);
1295
    }
1296
1297
    /**
1298
     * @param array $prepend
1299
     * @return array
1300
     */
1301 14
    protected function filterScope($prepend = [])
1302
    {
1303 14
        $scope = [];
1304
1305 14
        $requestFilters = $this->getRequestFilters();
1306
1307 14
        $this->filters = array_merge($this->filters, $this->defaultFilters);
1308
1309 14
        if (array_key_exists('status', $requestFilters)) {
1310 1
            switch ($requestFilters['status']) {
1311 1
                case 'published':
1312 1
                    $scope['published'] = true;
1313 1
                    break;
1314
                case 'draft':
1315
                    $scope['draft'] = true;
1316
                    break;
1317
                case 'trash':
1318
                    $scope['onlyTrashed'] = true;
1319
                    break;
1320
                case 'mine':
1321
                    $scope['mine'] = true;
1322
                    break;
1323
            }
1324
1325 1
            unset($requestFilters['status']);
1326
        }
1327
1328 14
        foreach ($this->filters as $key => $field) {
1329 14
            if (array_key_exists($key, $requestFilters)) {
1330 2
                $value = $requestFilters[$key];
1331 2
                if ($value == 0 || !empty($value)) {
1332
                    // add some syntaxic sugar to scope the same filter on multiple columns
1333 2
                    $fieldSplitted = explode('|', $field);
1334 2
                    if (count($fieldSplitted) > 1) {
1335
                        $requestValue = $requestFilters[$key];
1336
                        Collection::make($fieldSplitted)->each(function ($scopeKey) use (&$scope, $requestValue) {
1337
                            $scope[$scopeKey] = $requestValue;
1338
                        });
1339
                    } else {
1340 2
                        $scope[$field] = $requestFilters[$key];
1341
                    }
1342
                }
1343
            }
1344
        }
1345
1346 14
        return $prepend + $scope;
1347
    }
1348
1349
    /**
1350
     * @return array
1351
     */
1352 9
    protected function getRequestFilters()
1353
    {
1354 9
        if ($this->request->has('search')) {
1355
            return ['search' => $this->request->get('search')];
1356
        }
1357
1358 9
        return json_decode($this->request->get('filter'), true) ?? [];
1359
    }
1360
1361
    /**
1362
     * @return void
1363
     */
1364 48
    protected function applyFiltersDefaultOptions()
1365
    {
1366 48
        if (!count($this->filtersDefaultOptions) || $this->request->has('search')) {
1367 48
            return;
1368
        }
1369
1370
        $filters = $this->getRequestFilters();
1371
1372
        foreach ($this->filtersDefaultOptions as $filterName => $defaultOption) {
1373
            if (!isset($filters[$filterName])) {
1374
                $filters[$filterName] = $defaultOption;
1375
            }
1376
        }
1377
1378
        $this->request->merge(['filter' => json_encode($filters)]);
1379
    }
1380
1381
    /**
1382
     * @return array
1383
     */
1384 14
    protected function orderScope()
1385
    {
1386 14
        $orders = [];
1387 14
        if ($this->request->has('sortKey') && $this->request->has('sortDir')) {
1388 1
            if (($key = $this->request->get('sortKey')) == 'name') {
1389
                $sortKey = $this->titleColumnKey;
1390 1
            } elseif (!empty($key)) {
1391 1
                $sortKey = $key;
1392
            }
1393
1394 1
            if (isset($sortKey)) {
1395 1
                $orders[$this->indexColumns[$sortKey]['sortKey'] ?? $sortKey] = $this->request->get('sortDir');
1396
            }
1397
        }
1398
1399
        // don't apply default orders if reorder is enabled
1400 14
        $reorder = $this->getIndexOption('reorder');
1401 14
        $defaultOrders = ($reorder ? [] : ($this->defaultOrders ?? []));
1402
1403 14
        return $orders + $defaultOrders;
1404
    }
1405
1406
    /**
1407
     * @param int $id
1408
     * @param \A17\Twill\Models\Model|null $item
1409
     * @return array
1410
     */
1411 5
    protected function form($id, $item = null)
1412
    {
1413
1414 5
        if (!$item && $id) {
1415 4
            $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1416 1
        } elseif (!$item && !$id) {
1417
            $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

1417
            /** @scrutinizer ignore-call */ 
1418
            $item = $this->repository->newInstance();
Loading history...
1418
        }
1419
1420 5
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1421 5
        $previewRouteName = $fullRoutePrefix . 'preview';
1422 5
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1423
1424 5
        $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...
1425
1426
        $data = [
1427 5
            'item' => $item,
1428 5
            'moduleName' => $this->moduleName,
1429 5
            'routePrefix' => $this->routePrefix,
1430 5
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1431 5
            '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...
1432 5
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1433 5
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1434 5
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1435 5
            'translate' => $this->moduleHas('translations'),
1436 5
            'translateTitle' => $this->titleIsTranslatable(),
1437 5
            'permalink' => $this->getIndexOption('permalink'),
1438 5
            'createWithoutModal' => !$item->id && $this->getIndexOption('skipCreateModal'),
1439 5
            'form_fields' => $this->repository->getFormFields($item),
1440 5
            'baseUrl' => $baseUrl,
1441 5
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1442 5
            'saveUrl' => $item->id ? $this->getModuleRoute($item->id, 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
1443 5
            'editor' => Config::get('twill.enabled.block-editor') && $this->moduleHas('blocks') && !$this->disableEditor,
1444 5
            'blockPreviewUrl' => Route::has('admin.blocks.preview') ? URL::route('admin.blocks.preview') : '#',
1445 5
            'availableRepeaters' => $this->getRepeaterList()->toJson(),
1446 5
            'revisions' => $this->moduleHas('revisions') ? $item->revisionsArray() : null,
1447 5
        ] + (Route::has($previewRouteName) && $item->id ? [
1448 5
            'previewUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'preview', $item->id),
0 ignored issues
show
Bug introduced by
$item->id of type integer 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

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