Passed
Push — 2.x ( c00759...e3c99f )
by Quentin
07:55
created

ModuleController::store()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 53
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 15.4569

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 32
nc 16
nop 1
dl 0
loc 53
ccs 18
cts 29
cp 0.6207
crap 15.4569
rs 7.6666
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

559
            /** @scrutinizer ignore-call */ 
560
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
560
        }
561
562 1
        if ($this->request->has('activeLanguage')) {
563
            App::setLocale($this->request->get('activeLanguage'));
564
        }
565
566 1
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
567
568 1
        return View::exists($previewView) ? View::make($previewView, array_replace([
569 1
            'item' => $item,
570 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

570
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
571 1
            'moduleName' => Str::singular($this->moduleName),
572
        ]);
573
    }
574
575
    /**
576
     * @param int $id
577
     * @return \Illuminate\View\View
578
     */
579 2
    public function restoreRevision($id)
580
    {
581 2
        if ($this->request->has('revisionId')) {
582 1
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
583 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...
584 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...
585
        } else {
586 1
            throw new NotFoundHttpException();
587
        }
588
589 1
        $this->setBackLink();
590
591 1
        $view = Collection::make([
592 1
            "$this->viewPrefix.form",
593 1
            "twill::$this->moduleName.form",
594 1
            "twill::layouts.form",
595
        ])->first(function ($view) {
596 1
            return View::exists($view);
597 1
        });
598
599 1
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
600 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

600
        /** @scrutinizer ignore-call */ 
601
        $date = $revision->created_at->toDayDateTimeString();
Loading history...
601
602 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...
603
604 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

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

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

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

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

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