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

ModuleController::getFormRequestClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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

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

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

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

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

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

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

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

550
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
551
        }
552
    }
553
554
    /**
555 1
     * @param int $id
556
     * @return \Illuminate\View\View
557 1
     */
558
    public function preview($id)
559
    {
560 1
        if ($this->request->has('revisionId')) {
561 1
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
0 ignored issues
show
Bug introduced by
The method previewForRevision() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

845
        /** @scrutinizer ignore-call */ 
846
        $tags = $this->repository->getTags($query);
Loading history...
846 1
847
        return Response::json(['items' => $tags->map(function ($tag) {
848
            return $tag->name;
849
        })], 200);
850
    }
851
852
    /**
853 6
     * @param array $prependScope
854
     * @return array
855 6
     */
856 6
    protected function getIndexData($prependScope = [])
857
    {
858
        $scopes = $this->filterScope($prependScope);
859 6
        $items = $this->getIndexItems($scopes);
860 6
861 6
        $data = [
862 6
            'tableData' => $this->getIndexTableData($items),
863 6
            'tableColumns' => $this->getIndexTableColumns($items),
864 6
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
865 6
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
866 6
            'hiddenFilters' => array_keys(Arr::except($this->filters, array_keys($this->defaultFilters))),
867 6
            'filterLinks' => $this->filterLinks ?? [],
868 6
            'maxPage' => method_exists($items, 'lastPage') ? $items->lastPage() : 1,
869 6
            'defaultMaxPage' => method_exists($items, 'total') ? ceil($items->total() / $this->perPage) : 1,
870
            'offset' => method_exists($items, 'perPage') ? $items->perPage() : count($items),
871 6
            'defaultOffset' => $this->perPage,
872
        ] + $this->getIndexUrls($this->moduleName, $this->routePrefix);
873
874 6
        $baseUrl = $this->getPermalinkBaseUrl();
875 6
876 6
        $options = [
877 6
            'moduleName' => $this->moduleName,
878 6
            'skipCreateModal' => $this->getIndexOption('skipCreateModal'),
879 6
            'reorder' => $this->getIndexOption('reorder'),
880 6
            'create' => $this->getIndexOption('create'),
881 6
            'duplicate' => $this->getIndexOption('duplicate'),
882 6
            'translate' => $this->moduleHas('translations'),
883 6
            'translateTitle' => $this->titleIsTranslatable(),
884 6
            'permalink' => $this->getIndexOption('permalink'),
885 6
            'bulkEdit' => $this->getIndexOption('bulkEdit'),
886
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
887
            'baseUrl' => $baseUrl,
888 6
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
889
        ];
890
891
        return array_replace_recursive($data + $options, $this->indexData($this->request));
892
    }
893
894
    /**
895 5
     * @param Request $request
896
     * @return array
897 5
     */
898
    protected function indexData($request)
899
    {
900
        return [];
901
    }
902
903
    /**
904
     * @param array $scopes
905 12
     * @param bool $forcePagination
906
     * @return \Illuminate\Database\Eloquent\Collection
907 12
     */
908 12
    protected function getIndexItems($scopes = [], $forcePagination = false)
909
    {
910 12
        return $this->transformIndexItems($this->repository->get(
911 12
            $this->indexWith,
912
            $scopes,
913
            $this->orderScope(),
914
            $this->request->get('offset') ?? $this->perPage ?? 50,
915
            $forcePagination
916
        ));
917
    }
918
919
    /**
920 10
     * @param \Illuminate\Database\Eloquent\Collection $items
921
     * @return \Illuminate\Database\Eloquent\Collection
922 10
     */
923
    protected function transformIndexItems($items)
924
    {
925
        return $items;
926
    }
927
928
    /**
929 6
     * @param \Illuminate\Database\Eloquent\Collection $items
930
     * @return array
931 6
     */
932
    protected function getIndexTableData($items)
933
    {
934 3
        $translated = $this->moduleHas('translations');
935 3
        return $items->map(function ($item) use ($translated) {
936
            $columnsData = Collection::make($this->indexColumns)->mapWithKeys(function ($column) use ($item) {
937 3
                return $this->getItemColumnData($item, $column);
938
            })->toArray();
939 3
940
            $name = $columnsData[$this->titleColumnKey];
941
942
            if (empty($name)) {
943
                if ($this->moduleHas('translations')) {
944
                    $fallBackTranslation = $item->translations()->where('active', true)->first();
945
946
                    if (isset($fallBackTranslation->{$this->titleColumnKey})) {
947
                        $name = $fallBackTranslation->{$this->titleColumnKey};
948
                    }
949
                }
950
951 3
                $name = $name ?? ('Missing ' . $this->titleColumnKey);
952
            }
953 3
954 3
            unset($columnsData[$this->titleColumnKey]);
955 3
956 3
            $itemIsTrashed = method_exists($item, 'trashed') && $item->trashed();
957
            $itemCanDelete = $this->getIndexOption('delete') && ($item->canDelete ?? true);
958 3
            $canEdit = $this->getIndexOption('edit');
959 3
            $canDuplicate = $this->getIndexOption('duplicate');
960 3
961 3
            return array_replace([
962 3
                'id' => $item->id,
963 3
                'name' => $name,
964 3
                'publish_start_date' => $item->publish_start_date,
965 3
                'publish_end_date' => $item->publish_end_date,
966 3
                'edit' => $canEdit ? $this->getModuleRoute($item->id, 'edit') : null,
967 1
                'duplicate' => $canDuplicate ? $this->getModuleRoute($item->id, 'duplicate') : null,
968 1
                'delete' => $itemCanDelete ? $this->getModuleRoute($item->id, 'destroy') : null,
969 3
            ] + ($this->getIndexOption('editInModal') ? [
970 3
                'editInModal' => $this->getModuleRoute($item->id, 'edit'),
971 3
                'updateUrl' => $this->getModuleRoute($item->id, 'update'),
972
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
973 3
                'published' => $item->published,
974
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
975 3
                'featured' => $item->{$this->featureField},
976
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
977 3
                'deleted' => true,
978 3
            ] : []) + (($this->getIndexOption('forceDelete') && $itemIsTrashed) ? [
979 3
                'destroyable' => true,
980 6
            ] : []) + ($translated ? [
981
                'languages' => $item->getActiveLanguages(),
982
            ] : []) + $columnsData, $this->indexItemData($item));
983
        })->toArray();
984
    }
985
986
    /**
987 2
     * @param \A17\Twill\Models\Model $item
988
     * @return array
989 2
     */
990
    protected function indexItemData($item)
991
    {
992
        return [];
993
    }
994
995
    /**
996
     * @param \A17\Twill\Models\Model $item
997 5
     * @param array $column
998
     * @return array
999 5
     */
1000 2
    protected function getItemColumnData($item, $column)
1001
    {
1002
        if (isset($column['thumb']) && $column['thumb']) {
1003
            if (isset($column['present']) && $column['present']) {
1004
                return [
1005 2
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
1006 2
                ];
1007 2
            } else {
1008 2
                $variant = isset($column['variant']);
1009
                $role = $variant ? $column['variant']['role'] : head(array_keys($item->mediasParams));
0 ignored issues
show
Bug introduced by
The property mediasParams does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

Loading history...
1010 2
                $crop = $variant ? $column['variant']['crop'] : head(array_keys(head($item->mediasParams)));
1011
                $params = $variant && isset($column['variant']['params'])
1012
                ? $column['variant']['params']
1013 2
                : ['w' => 80, 'h' => 80, 'fit' => 'crop'];
1014
1015
                return [
1016
                    'thumbnail' => $item->cmsImage($role, $crop, $params),
1017
                ];
1018 5
            }
1019
        }
1020
1021
        if (isset($column['nested']) && $column['nested']) {
1022
            $field = $column['nested'];
1023
            $nestedCount = $item->{$column['nested']}->count();
1024
            $value = '<a href="';
1025
            $value .= moduleRoute("$this->moduleName.$field", $this->routePrefix, 'index', [$item->id]);
1026
            $value .= '">' . $nestedCount . " " . (strtolower($nestedCount > 1
1027 5
                ? Str::plural($column['title'])
1028 5
                : Str::singular($column['title']))) . '</a>';
1029
        } else {
1030
            $field = $column['field'];
1031 5
            $value = $item->$field;
1032
        }
1033
1034 5
        if (isset($column['relationship'])) {
1035
            $field = $column['relationship'] . ucfirst($column['field']);
1036
            $value = Arr::get($item, "{$column['relationship']}.{$column['field']}");
1037
        } elseif (isset($column['present']) && $column['present']) {
1038
            $value = $item->presentAdmin()->{$column['field']};
1039 5
        }
1040
1041
        return [
1042
            "$field" => $value,
1043
        ];
1044
    }
1045
1046
    /**
1047 6
     * @param \Illuminate\Database\Eloquent\Collection $items
1048
     * @return array
1049 6
     */
1050 6
    protected function getIndexTableColumns($items)
1051
    {
1052 6
        $tableColumns = [];
1053 6
        $visibleColumns = $this->request->get('columns') ?? false;
1054
1055 4
        if (isset(Arr::first($this->indexColumns)['thumb'])
1056 4
            && Arr::first($this->indexColumns)['thumb']
1057 4
        ) {
1058 4
            array_push($tableColumns, [
1059
                'name' => 'thumbnail',
1060
                'label' => twillTrans('twill::lang.listing.columns.thumbnail'),
1061
                'visible' => $visibleColumns ? in_array('thumbnail', $visibleColumns) : true,
1062 4
                'optional' => true,
1063
                'sortable' => false,
1064
            ]);
1065 6
            array_shift($this->indexColumns);
1066
        }
1067
1068
        if ($this->getIndexOption('feature')) {
1069
            array_push($tableColumns, [
1070
                'name' => 'featured',
1071
                'label' => twillTrans('twill::lang.listing.columns.featured'),
1072
                'visible' => true,
1073
                'optional' => false,
1074
                'sortable' => false,
1075 6
            ]);
1076 6
        }
1077 6
1078 6
        if ($this->getIndexOption('publish')) {
1079
            array_push($tableColumns, [
1080
                'name' => 'published',
1081
                'label' => twillTrans('twill::lang.listing.columns.published'),
1082
                'visible' => true,
1083
                'optional' => false,
1084
                'sortable' => false,
1085 6
            ]);
1086 6
        }
1087 6
1088
        array_push($tableColumns, [
1089
            'name' => 'name',
1090 6
            'label' => $this->indexColumns[$this->titleColumnKey]['title'] ?? twillTrans('twill::lang.listing.columns.name'),
1091
            'visible' => true,
1092
            'optional' => false,
1093 6
            'sortable' => $this->getIndexOption('reorder') ? false : ($this->indexColumns[$this->titleColumnKey]['sort'] ?? false),
1094
        ]);
1095 6
1096 4
        unset($this->indexColumns[$this->titleColumnKey]);
1097
1098 4
        foreach ($this->indexColumns as $column) {
1099
            $columnName = isset($column['relationship'])
1100 4
            ? $column['relationship'] . ucfirst($column['field'])
1101 4
            : (isset($column['nested']) ? $column['nested'] : $column['field']);
1102 4
1103 4
            array_push($tableColumns, [
1104 4
                'name' => $columnName,
1105 4
                'label' => $column['title'],
1106 4
                'visible' => $visibleColumns ? in_array($columnName, $visibleColumns) : ($column['visible'] ?? true),
1107
                'optional' => $column['optional'] ?? true,
1108
                'sortable' => $this->getIndexOption('reorder') ? false : ($column['sort'] ?? false),
1109
                'html' => $column['html'] ?? false,
1110 6
            ]);
1111 5
        }
1112 5
1113 5
        if ($this->moduleHas('translations')) {
1114 5
            array_push($tableColumns, [
1115
                'name' => 'languages',
1116
                'label' => twillTrans('twill::lang.listing.languages'),
1117
                'visible' => $visibleColumns ? in_array('languages', $visibleColumns) : true,
1118
                'optional' => true,
1119
                'sortable' => false,
1120 6
            ]);
1121
        }
1122
1123
        return $tableColumns;
1124
    }
1125
1126
    /**
1127
     * @param \Illuminate\Database\Eloquent\Collection $items
1128 5
     * @param array $scopes
1129
     * @return array
1130 5
     */
1131
    protected function getIndexTableMainFilters($items, $scopes = [])
1132 5
    {
1133
        $statusFilters = [];
1134 5
1135
        $scope = ($this->submodule ? [
1136 5
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
1137 5
        ] : []) + $scopes;
1138 5
1139 5
        array_push($statusFilters, [
1140
            'name' => twillTrans('twill::lang.listing.filter.all-items'),
1141
            'slug' => 'all',
1142 5
            'number' => $this->repository->getCountByStatusSlug('all', $scope),
1143 5
        ]);
1144 5
1145 5
        if ($this->moduleHas('revisions') && $this->getIndexOption('create')) {
1146 5
            array_push($statusFilters, [
1147
                'name' => twillTrans('twill::lang.listing.filter.mine'),
1148
                'slug' => 'mine',
1149
                'number' => $this->repository->getCountByStatusSlug('mine', $scope),
1150 5
            ]);
1151 5
        }
1152 5
1153 5
        if ($this->getIndexOption('publish')) {
1154 5
            array_push($statusFilters, [
1155
                'name' => twillTrans('twill::lang.listing.filter.published'),
1156 5
                'slug' => 'published',
1157 5
                'number' => $this->repository->getCountByStatusSlug('published', $scope),
1158 5
            ], [
1159
                'name' => twillTrans('twill::lang.listing.filter.draft'),
1160
                'slug' => 'draft',
1161
                'number' => $this->repository->getCountByStatusSlug('draft', $scope),
1162 5
            ]);
1163 5
        }
1164 5
1165 5
        if ($this->getIndexOption('restore')) {
1166 5
            array_push($statusFilters, [
1167
                'name' => twillTrans('twill::lang.listing.filter.trash'),
1168
                'slug' => 'trash',
1169
                'number' => $this->repository->getCountByStatusSlug('trash', $scope),
1170 5
            ]);
1171
        }
1172
1173
        return $statusFilters;
1174
    }
1175
1176
    /**
1177
     * @param string $moduleName
1178 6
     * @param string $routePrefix
1179
     * @return array
1180 6
     */
1181 6
    protected function getIndexUrls($moduleName, $routePrefix)
1182
    {
1183
        return Collection::make([
1184
            'create',
1185
            'store',
1186
            'publish',
1187
            'bulkPublish',
1188
            'restore',
1189
            'bulkRestore',
1190
            'forceDelete',
1191
            'bulkForceDelete',
1192
            'reorder',
1193
            'feature',
1194
            'bulkFeature',
1195 6
            'bulkDelete',
1196 6
        ])->mapWithKeys(function ($endpoint) {
1197 6
            return [
1198
                $endpoint . 'Url' => $this->getIndexOption($endpoint) ? moduleRoute(
1199
                    $this->moduleName, $this->routePrefix, $endpoint,
1200 6
                    $this->submodule ? [$this->submoduleParentId] : []
1201
                ) : null,
1202
            ];
1203
        })->toArray();
1204
    }
1205
1206
    /**
1207 33
     * @param string $option
1208
     * @return bool
1209
     */
1210
    protected function getIndexOption($option)
1211 33
    {
1212
        return once(function () use ($option) {
1213
            $customOptionNamesMapping = [
1214 33
                'store' => 'create',
1215
            ];
1216
1217 33
            $option = array_key_exists($option, $customOptionNamesMapping) ? $customOptionNamesMapping[$option] : $option;
1218
1219
            $authorizableOptions = [
1220
                'create' => 'edit',
1221
                'edit' => 'edit',
1222
                'publish' => 'publish',
1223
                'feature' => 'feature',
1224
                'reorder' => 'reorder',
1225
                'delete' => 'delete',
1226
                'duplicate' => 'duplicate',
1227
                'restore' => 'delete',
1228
                'forceDelete' => 'delete',
1229
                'bulkForceDelete' => 'delete',
1230
                'bulkPublish' => 'publish',
1231
                'bulkRestore' => 'delete',
1232
                'bulkFeature' => 'feature',
1233
                'bulkDelete' => 'delete',
1234
                'bulkEdit' => 'edit',
1235
                'editInModal' => 'edit',
1236 33
                'skipCreateModal' => 'edit',
1237 33
            ];
1238 33
1239
            $authorized = array_key_exists($option, $authorizableOptions) ? Auth::guard('twill_users')->user()->can($authorizableOptions[$option]) : true;
1240
            return ($this->indexOptions[$option] ?? $this->defaultIndexOptions[$option] ?? false) && $authorized;
1241
        });
1242
    }
1243
1244
    /**
1245 2
     * @param array $prependScope
1246
     * @return array
1247 2
     */
1248
    protected function getBrowserData($prependScope = [])
1249
    {
1250
        if ($this->request->has('except')) {
1251 2
            $prependScope['exceptIds'] = $this->request->get('except');
1252 2
        }
1253 2
1254
        $scopes = $this->filterScope($prependScope);
1255 2
        $items = $this->getBrowserItems($scopes);
1256
        $data = $this->getBrowserTableData($items);
1257
1258
        return array_replace_recursive(['data' => $data], $this->indexData($this->request));
1259
    }
1260
1261
    /**
1262 2
     * @param \Illuminate\Database\Eloquent\Collection $items
1263
     * @return array
1264 2
     */
1265
    protected function getBrowserTableData($items)
1266
    {
1267
        $withImage = $this->moduleHas('medias');
1268 2
1269 2
        return $items->map(function ($item) use ($withImage) {
1270
            $columnsData = Collection::make($this->browserColumns)->mapWithKeys(function ($column) use ($item) {
1271 2
                return $this->getItemColumnData($item, $column);
1272 2
            })->toArray();
1273
1274
            $name = $columnsData[$this->titleColumnKey];
1275 2
            unset($columnsData[$this->titleColumnKey]);
1276 2
1277 2
            return [
1278 2
                'id' => $item->id,
1279 2
                'name' => $name,
1280 2
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', $item->id),
1281 2
                'endpointType' => $this->repository->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

1417
            /** @scrutinizer ignore-call */ 
1418
            $item = $this->repository->newInstance();
Loading history...
1418 5
        }
1419 5
1420
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1421 5
        $previewRouteName = $fullRoutePrefix . 'preview';
1422
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1423
1424 5
        $baseUrl = $item->urlWithoutSlug ?? $this->getPermalinkBaseUrl();
0 ignored issues
show
Bug introduced by
The property urlWithoutSlug does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

Loading history...
1425 5
1426 5
        $data = [
1427 5
            'item' => $item,
1428 5
            'moduleName' => $this->moduleName,
1429 5
            'routePrefix' => $this->routePrefix,
1430 5
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1431 5
            'publish' => $item->canPublish ?? true,
0 ignored issues
show
Bug introduced by
The property canPublish does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

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

Loading history...
1432 5
            'publishDate24Hr' => Config::get('twill.publish_date_24h') ?? false,
1433 5
            'publishDateFormat' => Config::get('twill.publish_date_format') ?? null,
1434 5
            'publishDateDisplayFormat' => Config::get('twill.publish_date_display_format') ?? null,
1435 5
            'translate' => $this->moduleHas('translations'),
1436 5
            'translateTitle' => $this->titleIsTranslatable(),
1437 5
            'permalink' => $this->getIndexOption('permalink'),
1438 5
            'createWithoutModal' => !$item->id && $this->getIndexOption('skipCreateModal'),
1439 5
            'form_fields' => $this->repository->getFormFields($item),
1440 5
            'baseUrl' => $baseUrl,
1441 5
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1442 5
            'saveUrl' => $item->id ? $this->getModuleRoute($item->id, 'update') : moduleRoute($this->moduleName, $this->routePrefix, 'store', [$this->submoduleParentId]),
1443 5
            'editor' => Config::get('twill.enabled.block-editor') && $this->moduleHas('blocks') && !$this->disableEditor,
1444 5
            'blockPreviewUrl' => Route::has('admin.blocks.preview') ? URL::route('admin.blocks.preview') : '#',
1445 5
            'availableRepeaters' => $this->getRepeaterList()->toJson(),
1446 5
            'revisions' => $this->moduleHas('revisions') ? $item->revisionsArray() : null,
1447 5
        ] + (Route::has($previewRouteName) && $item->id ? [
1448 5
            'previewUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'preview', $item->id),
0 ignored issues
show
Bug introduced by
$item->id of type integer is incompatible with the type array expected by parameter $parameters of moduleRoute(). ( Ignorable by Annotation )

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

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