Passed
Pull Request — 2.x (#727)
by Bekzat
16:53
created

ModuleController::validateFormRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1.0019

Importance

Changes 0
Metric Value
cc 1
eloc 6
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 11
ccs 7
cts 8
cp 0.875
crap 1.0019
rs 10
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 3
        ])->first(function ($view) {
335 3
            return View::exists($view);
336 3
        });
337
338 3
        return View::make($view, $indexData);
339
    }
340
341
    /**
342
     * @return \Illuminate\Http\JsonResponse
343
     */
344 2
    public function browser()
345
    {
346 2
        return Response::json($this->getBrowserData());
347
    }
348
349
    /**
350
     * @param int|null $parentModuleId
351
     * @return \Illuminate\Http\JsonResponse
352
     */
353 25
    public function store($parentModuleId = null)
354
    {
355 25
        $input = $this->validateFormRequest()->all();
356 25
        $optionalParent = $parentModuleId ? [$this->getParentModuleForeignKey() => $parentModuleId] : [];
357
358 25
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
359
            return $this->respondWithRedirect(moduleRoute(
360
                $this->moduleName,
361
                $this->routePrefix,
362
                'create'
363
            ));
364
        }
365
366 25
        $item = $this->repository->create($input + $optionalParent);
367
368 25
        activity()->performedOn($item)->log('created');
369
370 25
        $this->fireEvent($input);
371
372 25
        Session::put($this->moduleName . '_retain', true);
373
374 25
        if ($this->getIndexOption('editInModal')) {
375 3
            return $this->respondWithSuccess(twillTrans('twill::lang.publisher.save-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang.publisher.save-success') can also be of type array and array; however, parameter $message of A17\Twill\Http\Controlle...r::respondWithSuccess() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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