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

src/Http/Controllers/Admin/ModuleController.php (1 issue)

Labels
Severity
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'));
376
        }
377
378 22
        if ($parentModuleId) {
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'));
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'));
557
        } else {
558 1
            $formRequest = $this->validateFormRequest();
559 1
            $item = $this->repository->preview($id, $formRequest->all());
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', [
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;
584 1
            $item->cmsRestoring = true;
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();
601
602 1
        Session::flash('restoreMessage', twillTrans('twill::lang.publisher.restore-message', ['user' => $revision->byUser, 'date' => $date]));
603
604 1
        return View::make($view, $this->form($id, $item));
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);
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));
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(),
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
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();
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,
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),
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