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

ModuleController   F

Complexity

Total Complexity 217

Size/Duplication

Total Lines 1661
Duplicated Lines 0 %

Test Coverage

Coverage 79.6%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 699
c 5
b 0
f 0
dl 0
loc 1661
ccs 515
cts 647
cp 0.796
rs 1.901
wmc 217

68 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
namespace A17\Twill\Http\Controllers\Admin;
4
5
use A17\Twill\Helpers\FlashLevel;
6
use A17\Twill\Services\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
    ];
89
90
    /**
91
     * Relations to eager load for the index view
92
     *
93
     * @var array
94
     */
95
    protected $indexWith = [];
96
97
    /**
98
     * Relations to eager load for the form view.
99
     *
100
     * @var array
101
     */
102
    protected $formWith = [];
103
104
    /**
105
     * Relation count to eager load for the form view.
106
     *
107
     * @var array
108
     */
109
    protected $formWithCount = [];
110
111
    /**
112
     * Additional filters for the index view.
113
     *
114
     * To automatically have your filter added to the index view use the following convention:
115
     * suffix the key containing the list of items to show in the filter by 'List' and
116
     * name it the same as the filter you defined in this array.
117
     *
118
     * Example: 'fCategory' => 'category_id' here and 'fCategoryList' in indexData()
119
     * By default, this will run a where query on the category_id column with the value
120
     * of fCategory if found in current request parameters. You can intercept this behavior
121
     * from your repository in the filter() function.
122
     *
123
     * @var array
124
     */
125
    protected $filters = [];
126
127
    /**
128
     * Additional links to display in the listing filter
129
     *
130
     * @var array
131
     */
132
    protected $filterLinks = [];
133
134
    /**
135
     * Filters that are selected by default in the index view.
136
     *
137
     * Example: 'filter_key' => 'default_filter_value'
138
     *
139
     * @var array
140
     */
141
    protected $filtersDefaultOptions = [];
142
143
    /**
144
     * Default orders for the index view.
145
     *
146
     * @var array
147
     */
148
    protected $defaultOrders = [
149
        'created_at' => 'desc',
150
    ];
151
152
    /**
153
     * @var int
154
     */
155
    protected $perPage = 20;
156
157
    /**
158
     * Name of the index column to use as name column.
159
     *
160
     * @var string
161
     */
162
    protected $titleColumnKey = 'title';
163
164
    /**
165
     * Attribute to use as title in forms.
166
     *
167
     * @var string
168
     */
169
    protected $titleFormKey;
170
171
    /**
172
     * Feature field name if the controller is using the feature route (defaults to "featured").
173
     *
174
     * @var string
175
     */
176
    protected $featureField = 'featured';
177
178
    /**
179
     * Indicates if this module is edited through a parent module.
180
     *
181
     * @var bool
182
     */
183
    protected $submodule = false;
184
185
    /**
186
     * @var int|null
187
     */
188
    protected $submoduleParentId = null;
189
190
    /**
191
     * Can be used in child classes to disable the content editor (full screen block editor).
192
     *
193
     * @var bool
194
     */
195
    protected $disableEditor = false;
196
197
    /**
198
     * @var array
199
     */
200
    protected $indexOptions;
201
202
    /**
203
     * @var array
204
     */
205
    protected $indexColumns;
206
207
    /**
208
     * @var array
209
     */
210
    protected $browserColumns;
211
212
    /**
213
     * @var string
214
     */
215
    protected $permalinkBase;
216
217
    /**
218
     * @var array
219
     */
220
    protected $defaultFilters;
221
222
    /**
223
     * @var string
224
     */
225
    protected $viewPrefix;
226
227
    /**
228
     * @var string
229
     */
230
    protected $previewView;
231
232
    /**
233
     * List of permissions keyed by a request field. Can be used to prevent unauthorized field updates.
234
     *
235
     * @var array
236
     */
237
    protected $fieldsPermissions = [];
238
239 40
    public function __construct(Application $app, Request $request)
240
    {
241 40
        parent::__construct();
242 40
        $this->app = $app;
243 40
        $this->request = $request;
244
245 40
        $this->setMiddlewarePermission();
246
247 40
        $this->modelName = $this->getModelName();
248 40
        $this->routePrefix = $this->getRoutePrefix();
249 40
        $this->namespace = $this->getNamespace();
250 40
        $this->repository = $this->getRepository();
251 40
        $this->viewPrefix = $this->getViewPrefix();
252 40
        $this->modelTitle = $this->getModelTitle();
253
254
        /*
255
         * Default filters for the index view
256
         * By default, the search field will run a like query on the title field
257
         */
258 40
        if (!isset($this->defaultFilters)) {
259 26
            $this->defaultFilters = [
260 26
                'search' => ($this->moduleHas('translations') ? '' : '%') . $this->titleColumnKey,
261
            ];
262
        }
263
264
        /*
265
         * Apply any filters that are selected by default
266
         */
267 40
        $this->applyFiltersDefaultOptions();
268
269
        /*
270
         * Available columns of the index view
271
         */
272 40
        if (!isset($this->indexColumns)) {
273 13
            $this->indexColumns = [
274 13
                $this->titleColumnKey => [
275 13
                    'title' => ucfirst($this->titleColumnKey),
276 13
                    'field' => $this->titleColumnKey,
277
                    'sort' => true,
278
                ],
279
            ];
280
        }
281
282
        /*
283
         * Available columns of the browser view
284
         */
285 40
        if (!isset($this->browserColumns)) {
286 40
            $this->browserColumns = [
287 40
                $this->titleColumnKey => [
288 40
                    'title' => ucfirst($this->titleColumnKey),
289 40
                    'field' => $this->titleColumnKey,
290
                ],
291
            ];
292
        }
293 40
    }
294
295
    /**
296
     * @return void
297
     */
298 40
    protected function setMiddlewarePermission()
299
    {
300 40
        $this->middleware('can:list', ['only' => ['index', 'show']]);
301 40
        $this->middleware('can:edit', ['only' => ['store', 'edit', 'update']]);
302 40
        $this->middleware('can:duplicate', ['only' => ['duplicate']]);
303 40
        $this->middleware('can:publish', ['only' => ['publish', 'feature', 'bulkPublish', 'bulkFeature']]);
304 40
        $this->middleware('can:reorder', ['only' => ['reorder']]);
305 40
        $this->middleware('can:delete', ['only' => ['destroy', 'bulkDelete', 'restore', 'bulkRestore', 'forceDelete', 'bulkForceDelete', 'restoreRevision']]);
306 40
    }
307
308
    /**
309
     * @param int|null $parentModuleId
310
     * @return array|\Illuminate\View\View
311
     */
312 6
    public function index($parentModuleId = null)
313
    {
314 6
        $this->submodule = isset($parentModuleId);
315 6
        $this->submoduleParentId = $parentModuleId;
316
317 6
        $indexData = $this->getIndexData($this->submodule ? [
318
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
319 6
        ] : []);
320
321 6
        if ($this->request->ajax()) {
322 3
            return $indexData + ['replaceUrl' => true];
323
        }
324
325 3
        if ($this->request->has('openCreate') && $this->request->get('openCreate')) {
326
            $indexData += ['openCreate' => true];
327
        }
328
329 3
        $view = Collection::make([
330 3
            "$this->viewPrefix.index",
331 3
            "twill::$this->moduleName.index",
332 3
            "twill::layouts.listing",
333
        ])->first(function ($view) {
334 3
            return View::exists($view);
335 3
        });
336
337 3
        return View::make($view, $indexData);
338
    }
339
340
    /**
341
     * @return \Illuminate\Http\JsonResponse
342
     */
343 2
    public function browser()
344
    {
345 2
        return Response::json($this->getBrowserData());
346
    }
347
348
    /**
349
     * @param int|null $parentModuleId
350
     * @return \Illuminate\Http\JsonResponse
351
     */
352 21
    public function store($parentModuleId = null)
353
    {
354 21
        $input = $this->validateFormRequest()->all();
355 21
        $optionalParent = $parentModuleId ? [$this->getParentModuleForeignKey() => $parentModuleId] : [];
356
357 21
        $item = $this->repository->create($input + $optionalParent);
358
359 21
        activity()->performedOn($item)->log('created');
360
361 21
        $this->fireEvent($input);
362
363 21
        Session::put($this->moduleName . '_retain', true);
364
365 21
        if ($this->getIndexOption('editInModal')) {
366 2
            return $this->respondWithSuccess('Content saved. All good!');
367
        }
368
369 19
        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...
370
            $params = [
371
                Str::singular(explode('.', $this->moduleName)[0]) => $parentModuleId,
372
                Str::singular(explode('.', $this->moduleName)[1]) => $item->id,
373
            ];
374
        } else {
375
            $params = [
376 19
                Str::singular($this->moduleName) => $item->id,
377
            ];
378
        }
379
380 19
        return $this->respondWithRedirect(moduleRoute(
381 19
            $this->moduleName,
382 19
            $this->routePrefix,
383 19
            'edit',
384
            $params
385
        ));
386
    }
387
388
    /**
389
     * @param int|$id
390
     * @param int|null $submoduleId
391
     * @return \Illuminate\Http\RedirectResponse
392
     */
393 1
    public function show($id, $submoduleId = null)
394
    {
395 1
        if ($this->getIndexOption('editInModal')) {
396
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
397
        }
398
399 1
        return $this->redirectToForm($submoduleId ?? $id);
400
    }
401
402
    /**
403
     * @param int $id
404
     * @param int|null $submoduleId
405
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
406
     */
407 5
    public function edit($id, $submoduleId = null)
408
    {
409 5
        $this->submodule = isset($submoduleId);
410 5
        $this->submoduleParentId = $id;
411
412 5
        if ($this->getIndexOption('editInModal')) {
413 2
            return $this->request->ajax()
414 1
            ? Response::json($this->modalFormData($submoduleId ?? $id))
415 2
            : Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
416
        }
417
418 3
        $this->setBackLink();
419
420 3
        $view = Collection::make([
421 3
            "$this->viewPrefix.form",
422 3
            "twill::$this->moduleName.form",
423 3
            "twill::layouts.form",
424
        ])->first(function ($view) {
425 3
            return View::exists($view);
426 3
        });
427
428 3
        return View::make($view, $this->form($submoduleId ?? $id));
429
    }
430
431
    /**
432
     * @param int $id
433
     * @param int|null $submoduleId
434
     * @return \Illuminate\Http\JsonResponse
435
     */
436 7
    public function update($id, $submoduleId = null)
437
    {
438 7
        $this->submodule = isset($submoduleId);
439 7
        $this->submoduleParentId = $id;
440
441 7
        $item = $this->repository->getById($submoduleId ?? $id);
442 7
        $input = $this->request->all();
443
444 7
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
445
            return $this->respondWithRedirect(moduleRoute(
446
                $this->moduleName,
447
                $this->routePrefix,
448
                'edit',
449
                [Str::singular($this->moduleName) => $id]
450
            ));
451
        } else {
452 7
            $formRequest = $this->validateFormRequest();
453
454 7
            $this->repository->update($submoduleId ?? $id, $formRequest->all());
455
456 7
            activity()->performedOn($item)->log('updated');
457
458 7
            $this->fireEvent();
459
460 7
            if (isset($input['cmsSaveType'])) {
461 7
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
462
                    return $this->respondWithRedirect($this->getBackLink());
463 7
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
464
                    return $this->respondWithRedirect(moduleRoute(
465
                        $this->moduleName,
466
                        $this->routePrefix,
467
                        'index',
468
                        ['openCreate' => true]
469
                    ));
470 7
                } elseif ($input['cmsSaveType'] === 'restore') {
471
                    Session::flash('status', "Revision restored.");
472
473
                    return $this->respondWithRedirect(moduleRoute(
474
                        $this->moduleName,
475
                        $this->routePrefix,
476
                        'edit',
477
                        [Str::singular($this->moduleName) => $id]
478
                    ));
479
                }
480
            }
481
482 7
            if ($this->moduleHas('revisions')) {
483 6
                return Response::json([
484 6
                    'message' => 'Content saved. All good!',
485
                    'variant' => FlashLevel::SUCCESS,
486 6
                    'revisions' => $item->revisionsArray(),
487
                ]);
488
            }
489
490 1
            return $this->respondWithSuccess('Content saved. All good!');
491
        }
492
    }
493
494
    /**
495
     * @param int $id
496
     * @return \Illuminate\View\View
497
     */
498 1
    public function preview($id)
499
    {
500 1
        if ($this->request->has('revisionId')) {
501
            $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

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

504
            /** @scrutinizer ignore-call */ 
505
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
505
        }
506
507 1
        if ($this->request->has('activeLanguage')) {
508
            App::setLocale($this->request->get('activeLanguage'));
509
        }
510
511 1
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
512
513 1
        return View::exists($previewView) ? View::make($previewView, array_replace([
514 1
            'item' => $item,
515 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

515
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
516 1
            'moduleName' => Str::singular($this->moduleName),
517
        ]);
518
    }
519
520
    /**
521
     * @param int $id
522
     * @return \Illuminate\View\View
523
     */
524 2
    public function restoreRevision($id)
525
    {
526 2
        if ($this->request->has('revisionId')) {
527 1
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
528 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...
529 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...
530
        } else {
531 1
            throw new NotFoundHttpException();
532
        }
533
534 1
        $this->setBackLink();
535
536 1
        $view = Collection::make([
537 1
            "$this->viewPrefix.form",
538 1
            "twill::$this->moduleName.form",
539 1
            "twill::layouts.form",
540
        ])->first(function ($view) {
541 1
            return View::exists($view);
542 1
        });
543
544 1
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
545 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

545
        /** @scrutinizer ignore-call */ 
546
        $date = $revision->created_at->toDayDateTimeString();
Loading history...
546
547 1
        Session::flash('restoreMessage', "You are currently editing an older revision of this content (saved by $revision->byUser on $date). Make changes if needed and click restore to save a new revision.");
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...
548
549 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

549
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
550
    }
551
552
    /**
553
     * @return \Illuminate\Http\JsonResponse
554
     */
555 2
    public function publish()
556
    {
557
        try {
558 2
            if ($this->repository->updateBasic($this->request->get('id'), [
559 2
                'published' => !$this->request->get('active'),
560
            ])) {
561 2
                activity()->performedOn(
562 2
                    $this->repository->getById($this->request->get('id'))
563 1
                )->log(
564 1
                    ($this->request->get('active') ? 'un' : '') . 'published'
565
                );
566
567 1
                $this->fireEvent();
568
569 1
                return $this->respondWithSuccess(
570 1
                    $this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'published!'
571
                );
572
            }
573 1
        } catch (\Exception $e) {
574 1
            \Log::error($e);
575
        }
576
577 1
        return $this->respondWithError(
578 1
            $this->modelTitle . ' was not published. Something wrong happened!'
579
        );
580
    }
581
582
    /**
583
     * @return \Illuminate\Http\JsonResponse
584
     */
585
    public function bulkPublish()
586
    {
587
        try {
588
            if ($this->repository->updateBasic(explode(',', $this->request->get('ids')), [
589
                'published' => $this->request->get('publish'),
590
            ])) {
591
                $this->fireEvent();
592
593
                return $this->respondWithSuccess(
594
                    $this->modelTitle . ' items ' . ($this->request->get('publish') ? '' : 'un') . 'published!'
595
                );
596
            }
597
        } catch (\Exception $e) {
598
            \Log::error($e);
599
        }
600
601
        return $this->respondWithError(
602
            $this->modelTitle . ' items were not published. Something wrong happened!'
603
        );
604
    }
605
606
    /**
607
     * @param int $id
608
     * @param int|null $submoduleId
609
     * @return \Illuminate\Http\JsonResponse
610
     */
611
    public function duplicate($id, $submoduleId = null)
612
    {
613
614
        $item = $this->repository->getById($submoduleId ?? $id);
615
        if ($newItem = $this->repository->duplicate($submoduleId ?? $id)) {
616
            $this->fireEvent();
617
            activity()->performedOn($item)->log('duplicated');
618
619
            return Response::json([
620
                'message' => $this->modelTitle . ' duplicated with Success!',
621
                'variant' => FlashLevel::SUCCESS,
622
                'redirect' => moduleRoute(
623
                    $this->moduleName,
624
                    $this->routePrefix,
625
                    'edit',
626
                    array_filter([Str::singular($this->moduleName) => $newItem->id])
627
                ),
628
            ]);
629
        }
630
631
        return $this->respondWithError($this->modelTitle . ' was not duplicated. Something wrong happened!');
632
    }
633
634
    /**
635
     * @param int $id
636
     * @param int|null $submoduleId
637
     * @return \Illuminate\Http\JsonResponse
638
     */
639 2
    public function destroy($id, $submoduleId = null)
640
    {
641 2
        $item = $this->repository->getById($submoduleId ?? $id);
642 2
        if ($this->repository->delete($submoduleId ?? $id)) {
643 2
            $this->fireEvent();
644 2
            activity()->performedOn($item)->log('deleted');
645 2
            return $this->respondWithSuccess($this->modelTitle . ' moved to trash!');
646
        }
647
648
        return $this->respondWithError($this->modelTitle . ' was not moved to trash. Something wrong happened!');
649
    }
650
651
    /**
652
     * @return \Illuminate\Http\JsonResponse
653
     */
654
    public function bulkDelete()
655
    {
656
        if ($this->repository->bulkDelete(explode(',', $this->request->get('ids')))) {
657
            $this->fireEvent();
658
            return $this->respondWithSuccess($this->modelTitle . ' items moved to trash!');
659
        }
660
661
        return $this->respondWithError($this->modelTitle . ' items were not moved to trash. Something wrong happened!');
662
    }
663
664
    /**
665
     * @return \Illuminate\Http\JsonResponse
666
     */
667
    public function forceDelete()
668
    {
669
        if ($this->repository->forceDelete($this->request->get('id'))) {
670
            $this->fireEvent();
671
            return $this->respondWithSuccess($this->modelTitle . ' destroyed!');
672
        }
673
674
        return $this->respondWithError($this->modelTitle . ' was not destroyed. Something wrong happened!');
675
    }
676
677
    /**
678
     * @return \Illuminate\Http\JsonResponse
679
     */
680
    public function bulkForceDelete()
681
    {
682
        if ($this->repository->bulkForceDelete(explode(',', $this->request->get('ids')))) {
683
            $this->fireEvent();
684
            return $this->respondWithSuccess($this->modelTitle . ' items destroyed!');
685
        }
686
687
        return $this->respondWithError($this->modelTitle . ' items were not destroyed. Something wrong happened!');
688
    }
689
690
    /**
691
     * @return \Illuminate\Http\JsonResponse
692
     */
693 2
    public function restore()
694
    {
695 2
        if ($this->repository->restore($this->request->get('id'))) {
696 1
            $this->fireEvent();
697 1
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
698 1
            return $this->respondWithSuccess($this->modelTitle . ' restored!');
699
        }
700
701 1
        return $this->respondWithError($this->modelTitle . ' was not restored. Something wrong happened!');
702
    }
703
704
    /**
705
     * @return \Illuminate\Http\JsonResponse
706
     */
707
    public function bulkRestore()
708
    {
709
        if ($this->repository->bulkRestore(explode(',', $this->request->get('ids')))) {
710
            $this->fireEvent();
711
            return $this->respondWithSuccess($this->modelTitle . ' items restored!');
712
        }
713
714
        return $this->respondWithError($this->modelTitle . ' items were not restored. Something wrong happened!');
715
    }
716
717
    /**
718
     * @return \Illuminate\Http\JsonResponse
719
     */
720 2
    public function feature()
721
    {
722 2
        if (($id = $this->request->get('id'))) {
723 2
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
724 2
            $featured = !$this->request->get('active');
725
726 2
            if ($this->repository->isUniqueFeature()) {
727
                if ($featured) {
728
                    $this->repository->updateBasic(null, [$featuredField => false]);
729
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
730
                }
731
            } else {
732 2
                $this->repository->updateBasic($id, [$featuredField => $featured]);
733
            }
734
735 2
            activity()->performedOn(
736 2
                $this->repository->getById($id)
737 1
            )->log(
738 1
                ($this->request->get('active') ? 'un' : '') . 'featured'
739
            );
740
741 1
            $this->fireEvent();
742 1
            return $this->respondWithSuccess($this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'featured!');
743
        }
744
745
        return $this->respondWithError($this->modelTitle . ' was not featured. Something wrong happened!');
746
    }
747
748
    /**
749
     * @return \Illuminate\Http\JsonResponse
750
     */
751
    public function bulkFeature()
752
    {
753
        if (($ids = explode(',', $this->request->get('ids')))) {
754
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
755
            $featured = $this->request->get('feature') ?? true;
756
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
757
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
758
            $this->fireEvent();
759
            return $this->respondWithSuccess($this->modelTitle . ' items ' . ($this->request->get('feature') ? '' : 'un') . 'featured!');
760
        }
761
762
        return $this->respondWithError($this->modelTitle . ' items were not featured. Something wrong happened!');
763
    }
764
765
    /**
766
     * @return \Illuminate\Http\JsonResponse
767
     */
768 2
    public function reorder()
769
    {
770 2
        if (($values = $this->request->get('ids')) && !empty($values)) {
771 2
            $this->repository->setNewOrder($values);
772 1
            $this->fireEvent();
773 1
            return $this->respondWithSuccess($this->modelTitle . ' order changed!');
774
        }
775
776
        return $this->respondWithError($this->modelTitle . ' order was not changed. Something wrong happened!');
777
    }
778
779
    /**
780
     * @return \Illuminate\Http\JsonResponse
781
     */
782 1
    public function tags()
783
    {
784 1
        $query = $this->request->input('q');
785 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

785
        /** @scrutinizer ignore-call */ 
786
        $tags = $this->repository->getTags($query);
Loading history...
786
787
        return Response::json(['items' => $tags->map(function ($tag) {
788
            return $tag->name;
789 1
        })], 200);
790
    }
791
792
    /**
793
     * @param array $prependScope
794
     * @return array
795
     */
796 6
    protected function getIndexData($prependScope = [])
797
    {
798 6
        $scopes = $this->filterScope($prependScope);
799 6
        $items = $this->getIndexItems($scopes);
800
801
        $data = [
802 6
            'tableData' => $this->getIndexTableData($items),
803 6
            'tableColumns' => $this->getIndexTableColumns($items),
804 6
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
805 6
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
806 6
            'hiddenFilters' => array_keys(Arr::except($this->filters, array_keys($this->defaultFilters))),
807 6
            'filterLinks' => $this->filterLinks ?? [],
808 6
            'maxPage' => method_exists($items, 'lastPage') ? $items->lastPage() : 1,
809 6
            'defaultMaxPage' => method_exists($items, 'total') ? ceil($items->total() / $this->perPage) : 1,
810 6
            'offset' => method_exists($items, 'perPage') ? $items->perPage() : count($items),
811 6
            'defaultOffset' => $this->perPage,
812 6
        ] + $this->getIndexUrls($this->moduleName, $this->routePrefix);
813
814 6
        $baseUrl = $this->getPermalinkBaseUrl();
815
816
        $options = [
817 6
            'moduleName' => $this->moduleName,
818 6
            'reorder' => $this->getIndexOption('reorder'),
819 6
            'create' => $this->getIndexOption('create'),
820 6
            'duplicate' => $this->getIndexOption('duplicate'),
821 6
            'translate' => $this->moduleHas('translations'),
822 6
            'translateTitle' => $this->titleIsTranslatable(),
823 6
            'permalink' => $this->getIndexOption('permalink'),
824 6
            'bulkEdit' => $this->getIndexOption('bulkEdit'),
825 6
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
826 6
            'baseUrl' => $baseUrl,
827 6
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
828
        ];
829
830 6
        return array_replace_recursive($data + $options, $this->indexData($this->request));
831
    }
832
833
    /**
834
     * @param Request $request
835
     * @return array
836
     */
837 5
    protected function indexData($request)
838
    {
839 5
        return [];
840
    }
841
842
    /**
843
     * @param array $scopes
844
     * @param bool $forcePagination
845
     * @return \Illuminate\Database\Eloquent\Collection
846
     */
847 12
    protected function getIndexItems($scopes = [], $forcePagination = false)
848
    {
849 12
        return $this->transformIndexItems($this->repository->get(
850 12
            $this->indexWith,
851
            $scopes,
852 12
            $this->orderScope(),
853 12
            $this->request->get('offset') ?? $this->perPage ?? 50,
854
            $forcePagination
855
        ));
856
    }
857
858
    /**
859
     * @param \Illuminate\Database\Eloquent\Collection $items
860
     * @return \Illuminate\Database\Eloquent\Collection
861
     */
862 10
    protected function transformIndexItems($items)
863
    {
864 10
        return $items;
865
    }
866
867
    /**
868
     * @param \Illuminate\Database\Eloquent\Collection $items
869
     * @return array
870
     */
871 6
    protected function getIndexTableData($items)
872
    {
873 6
        $translated = $this->moduleHas('translations');
874
        return $items->map(function ($item) use ($translated) {
875
            $columnsData = Collection::make($this->indexColumns)->mapWithKeys(function ($column) use ($item) {
876 3
                return $this->getItemColumnData($item, $column);
877 3
            })->toArray();
878
879 3
            $name = $columnsData[$this->titleColumnKey];
880
881 3
            if (empty($name)) {
882
                if ($this->moduleHas('translations')) {
883
                    $fallBackTranslation = $item->translations()->where('active', true)->first();
884
885
                    if (isset($fallBackTranslation->{$this->titleColumnKey})) {
886
                        $name = $fallBackTranslation->{$this->titleColumnKey};
887
                    }
888
                }
889
890
                $name = $name ?? ('Missing ' . $this->titleColumnKey);
891
            }
892
893 3
            unset($columnsData[$this->titleColumnKey]);
894
895 3
            $itemIsTrashed = method_exists($item, 'trashed') && $item->trashed();
896 3
            $itemCanDelete = $this->getIndexOption('delete') && ($item->canDelete ?? true);
897 3
            $canEdit = $this->getIndexOption('edit');
898 3
            $canDuplicate = $this->getIndexOption('duplicate');
899
900 3
            return array_replace([
901 3
                'id' => $item->id,
902 3
                'name' => $name,
903 3
                'publish_start_date' => $item->publish_start_date,
904 3
                'publish_end_date' => $item->publish_end_date,
905 3
                'edit' => $canEdit ? $this->getModuleRoute($item->id, 'edit') : null,
906 3
                'duplicate' => $canDuplicate ? $this->getModuleRoute($item->id, 'duplicate') : null,
907 3
                'delete' => $itemCanDelete ? $this->getModuleRoute($item->id, 'destroy') : null,
908 3
            ] + ($this->getIndexOption('editInModal') ? [
909 1
                'editInModal' => $this->getModuleRoute($item->id, 'edit'),
910 1
                'updateUrl' => $this->getModuleRoute($item->id, 'update'),
911 3
            ] : []) + ($this->getIndexOption('publish') && ($item->canPublish ?? true) ? [
912 3
                'published' => $item->published,
913 3
            ] : []) + ($this->getIndexOption('feature') && ($item->canFeature ?? true) ? [
914
                'featured' => $item->{$this->featureField},
915 3
            ] : []) + (($this->getIndexOption('restore') && $itemIsTrashed) ? [
916
                'deleted' => true,
917 3
            ] : []) + ($translated ? [
918 3
                'languages' => $item->getActiveLanguages(),
919 3
            ] : []) + $columnsData, $this->indexItemData($item));
920 6
        })->toArray();
921
    }
922
923
    /**
924
     * @param \A17\Twill\Models\Model $item
925
     * @return array
926
     */
927 2
    protected function indexItemData($item)
928
    {
929 2
        return [];
930
    }
931
932
    /**
933
     * @param \A17\Twill\Models\Model $item
934
     * @param array $column
935
     * @return array
936
     */
937 5
    protected function getItemColumnData($item, $column)
938
    {
939 5
        if (isset($column['thumb']) && $column['thumb']) {
940 2
            if (isset($column['present']) && $column['present']) {
941
                return [
942
                    'thumbnail' => $item->presentAdmin()->{$column['presenter']},
943
                ];
944
            } else {
945 2
                $variant = isset($column['variant']);
946 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...
947 2
                $crop = $variant ? $column['variant']['crop'] : head(array_keys(head($item->mediasParams)));
948 2
                $params = $variant && isset($column['variant']['params'])
949
                ? $column['variant']['params']
950 2
                : ['w' => 80, 'h' => 80, 'fit' => 'crop'];
951
952
                return [
953 2
                    'thumbnail' => $item->cmsImage($role, $crop, $params),
954
                ];
955
            }
956
        }
957
958 5
        if (isset($column['nested']) && $column['nested']) {
959
            $field = $column['nested'];
960
            $nestedCount = $item->{$column['nested']}->count();
961
            $value = '<a href="';
962
            $value .= moduleRoute("$this->moduleName.$field", $this->routePrefix, 'index', [$item->id]);
963
            $value .= '">' . $nestedCount . " " . (strtolower($nestedCount > 1
964
                ? Str::plural($column['title'])
965
                : Str::singular($column['title']))) . '</a>';
966
        } else {
967 5
            $field = $column['field'];
968 5
            $value = $item->$field;
969
        }
970
971 5
        if (isset($column['relationship'])) {
972
            $field = $column['relationship'] . ucfirst($column['field']);
973
            $value = Arr::get($item, "{$column['relationship']}.{$column['field']}");
974 5
        } elseif (isset($column['present']) && $column['present']) {
975
            $value = $item->presentAdmin()->{$column['field']};
976
        }
977
978
        return [
979 5
            "$field" => $value,
980
        ];
981
    }
982
983
    /**
984
     * @param \Illuminate\Database\Eloquent\Collection $items
985
     * @return array
986
     */
987 6
    protected function getIndexTableColumns($items)
0 ignored issues
show
Unused Code introduced by
The parameter $items is not used and could be removed. ( Ignorable by Annotation )

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

987
    protected function getIndexTableColumns(/** @scrutinizer ignore-unused */ $items)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
988
    {
989 6
        $tableColumns = [];
990 6
        $visibleColumns = $this->request->get('columns') ?? false;
991
992 6
        if (isset(Arr::first($this->indexColumns)['thumb'])
993 6
            && Arr::first($this->indexColumns)['thumb']
994
        ) {
995 4
            array_push($tableColumns, [
996 4
                'name' => 'thumbnail',
997 4
                'label' => 'Thumbnail',
998 4
                'visible' => $visibleColumns ? in_array('thumbnail', $visibleColumns) : true,
999
                'optional' => true,
1000
                'sortable' => false,
1001
            ]);
1002 4
            array_shift($this->indexColumns);
1003
        }
1004
1005 6
        if ($this->getIndexOption('feature')) {
1006
            array_push($tableColumns, [
1007
                'name' => 'featured',
1008
                'label' => 'Featured',
1009
                'visible' => true,
1010
                'optional' => false,
1011
                'sortable' => false,
1012
            ]);
1013
        }
1014
1015 6
        if ($this->getIndexOption('publish')) {
1016 6
            array_push($tableColumns, [
1017 6
                'name' => 'published',
1018
                'label' => 'Published',
1019
                'visible' => true,
1020
                'optional' => false,
1021
                'sortable' => false,
1022
            ]);
1023
        }
1024
1025 6
        array_push($tableColumns, [
1026 6
            'name' => 'name',
1027 6
            'label' => $this->indexColumns[$this->titleColumnKey]['title'] ?? 'Name',
1028
            'visible' => true,
1029
            'optional' => false,
1030 6
            'sortable' => $this->getIndexOption('reorder') ? false : ($this->indexColumns[$this->titleColumnKey]['sort'] ?? false),
1031
        ]);
1032
1033 6
        unset($this->indexColumns[$this->titleColumnKey]);
1034
1035 6
        foreach ($this->indexColumns as $column) {
1036 4
            $columnName = isset($column['relationship'])
1037
            ? $column['relationship'] . ucfirst($column['field'])
1038 4
            : (isset($column['nested']) ? $column['nested'] : $column['field']);
1039
1040 4
            array_push($tableColumns, [
1041 4
                'name' => $columnName,
1042 4
                'label' => $column['title'],
1043 4
                'visible' => $visibleColumns ? in_array($columnName, $visibleColumns) : ($column['visible'] ?? true),
1044 4
                'optional' => $column['optional'] ?? true,
1045 4
                'sortable' => $this->getIndexOption('reorder') ? false : ($column['sort'] ?? false),
1046 4
                'html' => $column['html'] ?? false,
1047
            ]);
1048
        }
1049
1050 6
        if ($this->moduleHas('translations')) {
1051 5
            array_push($tableColumns, [
1052 5
                'name' => 'languages',
1053 5
                'label' => twillTrans('twill::lang.listing.languages'),
1054 5
                'visible' => $visibleColumns ? in_array('languages', $visibleColumns) : true,
1055
                'optional' => true,
1056
                'sortable' => false,
1057
            ]);
1058
        }
1059
1060 6
        return $tableColumns;
1061
    }
1062
1063
    /**
1064
     * @param \Illuminate\Database\Eloquent\Collection $items
1065
     * @param array $scopes
1066
     * @return array
1067
     */
1068 5
    protected function getIndexTableMainFilters($items, $scopes = [])
1069
    {
1070 5
        $statusFilters = [];
1071
1072 5
        $scope = ($this->submodule ? [
1073
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
1074 5
        ] : []) + $scopes;
1075
1076 5
        array_push($statusFilters, [
1077 5
            'name' => twillTrans('twill::lang.listing.filter.all-items'),
1078 5
            'slug' => 'all',
1079 5
            'number' => $this->repository->getCountByStatusSlug('all', $scope),
1080
        ]);
1081
1082 5
        if ($this->moduleHas('revisions') && $this->getIndexOption('create')) {
1083 5
            array_push($statusFilters, [
1084 5
                'name' => twillTrans('twill::lang.listing.filter.mine'),
1085 5
                'slug' => 'mine',
1086 5
                'number' => $this->repository->getCountByStatusSlug('mine', $scope),
1087
            ]);
1088
        }
1089
1090 5
        if ($this->getIndexOption('publish')) {
1091 5
            array_push($statusFilters, [
1092 5
                'name' => twillTrans('twill::lang.listing.filter.published'),
1093 5
                'slug' => 'published',
1094 5
                'number' => $this->repository->getCountByStatusSlug('published', $scope),
1095
            ], [
1096 5
                'name' => twillTrans('twill::lang.listing.filter.draft'),
1097 5
                'slug' => 'draft',
1098 5
                'number' => $this->repository->getCountByStatusSlug('draft', $scope),
1099
            ]);
1100
        }
1101
1102 5
        if ($this->getIndexOption('restore')) {
1103 5
            array_push($statusFilters, [
1104 5
                'name' => twillTrans('twill::lang.listing.filter.trash'),
1105 5
                'slug' => 'trash',
1106 5
                'number' => $this->repository->getCountByStatusSlug('trash', $scope),
1107
            ]);
1108
        }
1109
1110 5
        return $statusFilters;
1111
    }
1112
1113
    /**
1114
     * @param string $moduleName
1115
     * @param string $routePrefix
1116
     * @return array
1117
     */
1118 6
    protected function getIndexUrls($moduleName, $routePrefix)
0 ignored issues
show
Unused Code introduced by
The parameter $moduleName is not used and could be removed. ( Ignorable by Annotation )

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

1118
    protected function getIndexUrls(/** @scrutinizer ignore-unused */ $moduleName, $routePrefix)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $routePrefix is not used and could be removed. ( Ignorable by Annotation )

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

1118
    protected function getIndexUrls($moduleName, /** @scrutinizer ignore-unused */ $routePrefix)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1119
    {
1120 6
        return Collection::make([
1121 6
            'store',
1122
            'publish',
1123
            'bulkPublish',
1124
            'restore',
1125
            'bulkRestore',
1126
            'forceDelete',
1127
            'bulkForceDelete',
1128
            'reorder',
1129
            'feature',
1130
            'bulkFeature',
1131
            'bulkDelete',
1132
        ])->mapWithKeys(function ($endpoint) {
1133
            return [
1134 6
                $endpoint . 'Url' => $this->getIndexOption($endpoint) ? moduleRoute(
1135 6
                    $this->moduleName, $this->routePrefix, $endpoint,
1136 6
                    $this->submodule ? [$this->submoduleParentId] : []
1137
                ) : null,
1138
            ];
1139 6
        })->toArray();
1140
    }
1141
1142
    /**
1143
     * @param string $option
1144
     * @return bool
1145
     */
1146 29
    protected function getIndexOption($option)
1147
    {
1148
        return once(function () use ($option) {
1149
            $customOptionNamesMapping = [
1150 29
                'store' => 'create',
1151
            ];
1152
1153 29
            $option = array_key_exists($option, $customOptionNamesMapping) ? $customOptionNamesMapping[$option] : $option;
1154
1155
            $authorizableOptions = [
1156 29
                'create' => 'edit',
1157
                'edit' => 'edit',
1158
                'publish' => 'publish',
1159
                'feature' => 'feature',
1160
                'reorder' => 'reorder',
1161
                'delete' => 'delete',
1162
                'duplicate' => 'duplicate',
1163
                'restore' => 'delete',
1164
                'forceDelete' => 'delete',
1165
                'bulkForceDelete' => 'delete',
1166
                'bulkPublish' => 'publish',
1167
                'bulkRestore' => 'delete',
1168
                'bulkFeature' => 'feature',
1169
                'bulkDelete' => 'delete',
1170
                'bulkEdit' => 'edit',
1171
                'editInModal' => 'edit',
1172
            ];
1173
1174 29
            $authorized = array_key_exists($option, $authorizableOptions) ? Auth::guard('twill_users')->user()->can($authorizableOptions[$option]) : true;
1175 29
            return ($this->indexOptions[$option] ?? $this->defaultIndexOptions[$option] ?? false) && $authorized;
1176 29
        });
1177
    }
1178
1179
    /**
1180
     * @param array $prependScope
1181
     * @return array
1182
     */
1183 2
    protected function getBrowserData($prependScope = [])
1184
    {
1185 2
        if ($this->request->has('except')) {
1186
            $prependScope['exceptIds'] = $this->request->get('except');
1187
        }
1188
1189 2
        $scopes = $this->filterScope($prependScope);
1190 2
        $items = $this->getBrowserItems($scopes);
1191 2
        $data = $this->getBrowserTableData($items);
1192
1193 2
        return array_replace_recursive(['data' => $data], $this->indexData($this->request));
1194
    }
1195
1196
    /**
1197
     * @param \Illuminate\Database\Eloquent\Collection $items
1198
     * @return array
1199
     */
1200 2
    protected function getBrowserTableData($items)
1201
    {
1202 2
        $withImage = $this->moduleHas('medias');
1203
1204
        return $items->map(function ($item) use ($withImage) {
1205
            $columnsData = Collection::make($this->browserColumns)->mapWithKeys(function ($column) use ($item) {
1206 2
                return $this->getItemColumnData($item, $column);
1207 2
            })->toArray();
1208
1209 2
            $name = $columnsData[$this->titleColumnKey];
1210 2
            unset($columnsData[$this->titleColumnKey]);
1211
1212
            return [
1213 2
                'id' => $item->id,
1214 2
                'name' => $name,
1215 2
                'edit' => moduleRoute($this->moduleName, $this->routePrefix, 'edit', $item->id),
1216 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

1216
                'endpointType' => $this->repository->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
1217 2
            ] + $columnsData + ($withImage && !array_key_exists('thumbnail', $columnsData) ? [
1218 2
                'thumbnail' => $item->defaultCmsImage(['w' => 100, 'h' => 100]),
1219 2
            ] : []);
1220 2
        })->toArray();
1221
    }
1222
1223
    /**
1224
     * @param array $scopes
1225
     * @return \Illuminate\Database\Eloquent\Collection
1226
     */
1227 2
    protected function getBrowserItems($scopes = [])
1228
    {
1229 2
        return $this->getIndexItems($scopes, true);
1230
    }
1231
1232
    /**
1233
     * @param array $prepend
1234
     * @return array
1235
     */
1236 12
    protected function filterScope($prepend = [])
1237
    {
1238 12
        $scope = [];
1239
1240 12
        $requestFilters = $this->getRequestFilters();
1241
1242 12
        $this->filters = array_merge($this->filters, $this->defaultFilters);
1243
1244 12
        if (array_key_exists('status', $requestFilters)) {
1245 1
            switch ($requestFilters['status']) {
1246 1
                case 'published':
1247 1
                    $scope['published'] = true;
1248 1
                    break;
1249
                case 'draft':
1250
                    $scope['draft'] = true;
1251
                    break;
1252
                case 'trash':
1253
                    $scope['onlyTrashed'] = true;
1254
                    break;
1255
                case 'mine':
1256
                    $scope['mine'] = true;
1257
                    break;
1258
            }
1259
1260 1
            unset($requestFilters['status']);
1261
        }
1262
1263 12
        foreach ($this->filters as $key => $field) {
1264 12
            if (array_key_exists($key, $requestFilters)) {
1265 2
                $value = $requestFilters[$key];
1266 2
                if ($value == 0 || !empty($value)) {
1267
                    // add some syntaxic sugar to scope the same filter on multiple columns
1268 2
                    $fieldSplitted = explode('|', $field);
1269 2
                    if (count($fieldSplitted) > 1) {
1270
                        $requestValue = $requestFilters[$key];
1271
                        Collection::make($fieldSplitted)->each(function ($scopeKey) use (&$scope, $requestValue) {
1272
                            $scope[$scopeKey] = $requestValue;
1273
                        });
1274
                    } else {
1275 2
                        $scope[$field] = $requestFilters[$key];
1276
                    }
1277
                }
1278
            }
1279
        }
1280
1281 12
        return $prepend + $scope;
1282
    }
1283
1284
    /**
1285
     * @return array
1286
     */
1287 7
    protected function getRequestFilters()
1288
    {
1289 7
        if ($this->request->has('search')) {
1290
            return ['search' => $this->request->get('search')];
1291
        }
1292
1293 7
        return json_decode($this->request->get('filter'), true) ?? [];
1294
    }
1295
1296
    /**
1297
     * @return void
1298
     */
1299 40
    protected function applyFiltersDefaultOptions()
1300
    {
1301 40
        if (!count($this->filtersDefaultOptions) || $this->request->has('search')) {
1302 40
            return;
1303
        }
1304
1305
        $filters = $this->getRequestFilters();
1306
1307
        foreach ($this->filtersDefaultOptions as $filterName => $defaultOption) {
1308
            if (!isset($filters[$filterName])) {
1309
                $filters[$filterName] = $defaultOption;
1310
            }
1311
        }
1312
1313
        $this->request->merge(['filter' => json_encode($filters)]);
1314
    }
1315
1316
    /**
1317
     * @return array
1318
     */
1319 12
    protected function orderScope()
1320
    {
1321 12
        $orders = [];
1322 12
        if ($this->request->has('sortKey') && $this->request->has('sortDir')) {
1323 1
            if (($key = $this->request->get('sortKey')) == 'name') {
1324
                $sortKey = $this->titleColumnKey;
1325 1
            } elseif (!empty($key)) {
1326 1
                $sortKey = $key;
1327
            }
1328
1329 1
            if (isset($sortKey)) {
1330 1
                $orders[$this->indexColumns[$sortKey]['sortKey'] ?? $sortKey] = $this->request->get('sortDir');
1331
            }
1332
        }
1333
1334
        // don't apply default orders if reorder is enabled
1335 12
        $reorder = $this->getIndexOption('reorder');
1336 12
        $defaultOrders = ($reorder ? [] : ($this->defaultOrders ?? []));
1337
1338 12
        return $orders + $defaultOrders;
1339
    }
1340
1341
    /**
1342
     * @param int $id
1343
     * @param \A17\Twill\Models\Model|null $item
1344
     * @return array
1345
     */
1346 4
    protected function form($id, $item = null)
1347
    {
1348 4
        $item = $item ?? $this->repository->getById($id, $this->formWith, $this->formWithCount);
1349
1350 4
        $fullRoutePrefix = 'admin.' . ($this->routePrefix ? $this->routePrefix . '.' : '') . $this->moduleName . '.';
1351 4
        $previewRouteName = $fullRoutePrefix . 'preview';
1352 4
        $restoreRouteName = $fullRoutePrefix . 'restoreRevision';
1353
1354 4
        $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...
1355
1356
        $data = [
1357 4
            'item' => $item,
1358 4
            'moduleName' => $this->moduleName,
1359 4
            'routePrefix' => $this->routePrefix,
1360 4
            'titleFormKey' => $this->titleFormKey ?? $this->titleColumnKey,
1361 4
            '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...
1362 4
            'translate' => $this->moduleHas('translations'),
1363 4
            'permalink' => $this->getIndexOption('permalink'),
1364 4
            'form_fields' => $this->repository->getFormFields($item),
1365 4
            'baseUrl' => $baseUrl,
1366 4
            'permalinkPrefix' => $this->getPermalinkPrefix($baseUrl),
1367 4
            'saveUrl' => $this->getModuleRoute($item->id, 'update'),
1368 4
            'editor' => Config::get('twill.enabled.block-editor') && $this->moduleHas('blocks') && !$this->disableEditor,
1369 4
            'blockPreviewUrl' => Route::has('admin.blocks.preview') ? URL::route('admin.blocks.preview') : '#',
1370 4
            'availableRepeaters' => $this->getRepeaterList()->toJson(),
1371 4
            'revisions' => $this->moduleHas('revisions') ? $item->revisionsArray() : null,
1372 4
        ] + (Route::has($previewRouteName) ? [
1373 4
            '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

1373
            'previewUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'preview', /** @scrutinizer ignore-type */ $item->id),
Loading history...
1374 4
        ] : [])
1375 4
             + (Route::has($restoreRouteName) ? [
1376 4
            'restoreUrl' => moduleRoute($this->moduleName, $this->routePrefix, 'restoreRevision', $item->id),
1377 4
        ] : []);
1378
1379 4
        return array_replace_recursive($data, $this->formData($this->request));
1380
    }
1381
1382
    /**
1383
     * @param int $id
1384
     * @return array
1385
     */
1386 1
    protected function modalFormData($id)
1387
    {
1388 1
        $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1389 1
        $fields = $this->repository->getFormFields($item);
1390 1
        $data = [];
1391
1392 1
        if ($this->moduleHas('translations') && isset($fields['translations'])) {
1393 1
            foreach ($fields['translations'] as $fieldName => $fieldValue) {
1394 1
                $data['fields'][] = [
1395 1
                    'name' => $fieldName,
1396 1
                    'value' => $fieldValue,
1397
                ];
1398
            }
1399
1400 1
            $data['languages'] = $item->getActiveLanguages();
1401
1402 1
            unset($fields['translations']);
1403
        }
1404
1405 1
        foreach ($fields as $fieldName => $fieldValue) {
1406 1
            $data['fields'][] = [
1407 1
                'name' => $fieldName,
1408 1
                'value' => $fieldValue,
1409
            ];
1410
        }
1411
1412 1
        return array_replace_recursive($data, $this->formData($this->request));
1413
    }
1414
1415
    /**
1416
     * @param Request $request
1417
     * @return array
1418
     */
1419 4
    protected function formData($request)
1420
    {
1421 4
        return [];
1422
    }
1423
1424
    /**
1425
     * @param Request $item
1426
     * @return array
1427
     */
1428 1
    protected function previewData($item)
0 ignored issues
show
Unused Code introduced by
The parameter $item is not used and could be removed. ( Ignorable by Annotation )

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

1428
    protected function previewData(/** @scrutinizer ignore-unused */ $item)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1429
    {
1430 1
        return [];
1431
    }
1432
1433
    /**
1434
     * @return \A17\Twill\Http\Requests\Admin\Request
1435
     */
1436 21
    protected function validateFormRequest()
1437
    {
1438
        $unauthorizedFields = Collection::make($this->fieldsPermissions)->filter(function ($permission, $field) {
0 ignored issues
show
Unused Code introduced by
The parameter $field is not used and could be removed. ( Ignorable by Annotation )

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

1438
        $unauthorizedFields = Collection::make($this->fieldsPermissions)->filter(function ($permission, /** @scrutinizer ignore-unused */ $field) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1439 4
            return Auth::guard('twill_users')->user()->cannot($permission);
1440 21
        })->keys();
1441
1442
        $unauthorizedFields->each(function ($field) {
1443
            $this->request->offsetUnset($field);
1444 21
        });
1445
1446 21
        return App::make("$this->namespace\Http\Requests\Admin\\" . $this->modelName . "Request");
1447
    }
1448
1449
    /**
1450
     * @return string
1451
     */
1452 40
    protected function getNamespace()
1453
    {
1454 40
        return $this->namespace ?? Config::get('twill.namespace');
1455
    }
1456
1457
    /**
1458
     * @return string
1459
     */
1460 40
    protected function getRoutePrefix()
1461
    {
1462 40
        if ($this->request->route() != null) {
1463 39
            $routePrefix = ltrim(str_replace(Config::get('twill.admin_app_path'), '', $this->request->route()->getPrefix()), "/");
1464 39
            return str_replace("/", ".", $routePrefix);
1465
        }
1466
1467 1
        return '';
1468
    }
1469
1470
    /**
1471
     * @return string
1472
     */
1473 40
    protected function getModelName()
1474
    {
1475 40
        return $this->modelName ?? ucfirst(Str::singular($this->moduleName));
1476
    }
1477
1478
    /**
1479
     * @return \A17\Twill\Repositories\ModuleRepository
1480
     */
1481 40
    protected function getRepository()
1482
    {
1483 40
        return App::make("$this->namespace\Repositories\\" . $this->modelName . "Repository");
1484
    }
1485
1486
    /**
1487
     * @return string
1488
     */
1489 40
    protected function getViewPrefix()
1490
    {
1491 40
        return "admin.$this->moduleName";
1492
    }
1493
1494
    /**
1495
     * @return string
1496
     */
1497 40
    protected function getModelTitle()
1498
    {
1499 40
        return camelCaseToWords($this->modelName);
1500
    }
1501
1502
    /**
1503
     * @return string
1504
     */
1505
    protected function getParentModuleForeignKey()
1506
    {
1507
        return Str::singular(explode('.', $this->moduleName)[0]) . '_id';
1508
    }
1509
1510
    /**
1511
     * @return string
1512
     */
1513 10
    protected function getPermalinkBaseUrl()
1514
    {
1515 10
        return $this->request->getScheme() . '://' . Config::get('app.url') . '/'
1516 10
            . ($this->moduleHas('translations') ? '{language}/' : '')
1517 10
            . ($this->moduleHas('revisions') ? '{preview}/' : '')
1518 10
            . ($this->permalinkBase ?? $this->moduleName)
1519 10
            . (isset($this->permalinkBase) && empty($this->permalinkBase) ? '' : '/');
1520
    }
1521
1522
    /**
1523
     * @param string $baseUrl
1524
     * @return string
1525
     */
1526 10
    protected function getPermalinkPrefix($baseUrl)
1527
    {
1528 10
        return rtrim(str_replace(['http://', 'https://', '{preview}/', '{language}/'], '', $baseUrl), "/") . '/';
1529
    }
1530
1531
    /**
1532
     * @param int $id
1533
     * @param string $action
1534
     * @return string
1535
     */
1536 7
    protected function getModuleRoute($id, $action)
1537
    {
1538 7
        return moduleRoute(
1539 7
            $this->moduleName,
1540 7
            $this->routePrefix,
1541
            $action,
1542 7
            array_merge($this->submodule ? [$this->submoduleParentId] : [], [$id])
1543
        );
1544
    }
1545
1546
    /**
1547
     * @param string $behavior
1548
     * @return bool
1549
     */
1550 29
    protected function moduleHas($behavior)
1551
    {
1552 29
        return $this->repository->hasBehavior($behavior);
1553
    }
1554
1555
    /**
1556
     * @return bool
1557
     */
1558 6
    protected function titleIsTranslatable()
1559
    {
1560 6
        return $this->repository->isTranslatable(
1561 6
            $this->titleColumnKey
1562
        );
1563
    }
1564
1565
    /**
1566
     * @param string|null $back_link
1567
     * @param array $params
1568
     * @return void
1569
     */
1570 4
    protected function setBackLink($back_link = null, $params = [])
1571
    {
1572 4
        if (!isset($back_link)) {
1573 4
            if (($back_link = Session::get($this->getBackLinkSessionKey())) == null) {
1574 4
                $back_link = $this->request->headers->get('referer') ?? moduleRoute(
1575 4
                    $this->moduleName,
1576 4
                    $this->routePrefix,
1577 4
                    'index',
1578
                    $params
1579
                );
1580
            }
1581
        }
1582
1583 4
        if (!Session::get($this->moduleName . '_retain')) {
1584 1
            Session::put($this->getBackLinkSessionKey(), $back_link);
1585
        } else {
1586 3
            Session::put($this->moduleName . '_retain', false);
1587
        }
1588 4
    }
1589
1590
    /**
1591
     * @param string|null $fallback
1592
     * @param array $params
1593
     * @return string
1594
     */
1595
    protected function getBackLink($fallback = null, $params = [])
1596
    {
1597
        $back_link = Session::get($this->getBackLinkSessionKey(), $fallback);
1598
        return $back_link ?? moduleRoute($this->moduleName, $this->routePrefix, 'index', $params);
1599
    }
1600
1601
    /**
1602
     * @return string
1603
     */
1604 4
    protected function getBackLinkSessionKey()
1605
    {
1606 4
        return $this->moduleName . ($this->submodule ? $this->submoduleParentId ?? '' : '') . '_back_link';
1607
    }
1608
1609
    /**
1610
     * @param int $id
1611
     * @param array $params
1612
     * @return \Illuminate\Http\RedirectResponse
1613
     */
1614 1
    protected function redirectToForm($id, $params = [])
1615
    {
1616 1
        Session::put($this->moduleName . '_retain', true);
1617
1618 1
        return Redirect::to(moduleRoute(
1619 1
            $this->moduleName,
1620 1
            $this->routePrefix,
1621 1
            'edit',
1622 1
            array_filter($params) + [Str::singular($this->moduleName) => $id]
1623
        ));
1624
    }
1625
1626
    /**
1627
     * @param string $message
1628
     * @return \Illuminate\Http\JsonResponse
1629
     */
1630 8
    protected function respondWithSuccess($message)
1631
    {
1632 8
        return $this->respondWithJson($message, FlashLevel::SUCCESS);
1633
    }
1634
1635
    /**
1636
     * @param string $redirectUrl
1637
     * @return \Illuminate\Http\JsonResponse
1638
     */
1639 19
    protected function respondWithRedirect($redirectUrl)
1640
    {
1641 19
        return Response::json([
1642 19
            'redirect' => $redirectUrl,
1643
        ]);
1644
    }
1645
1646
    /**
1647
     * @param string $message
1648
     * @return \Illuminate\Http\JsonResponse
1649
     */
1650 2
    protected function respondWithError($message)
1651
    {
1652 2
        return $this->respondWithJson($message, FlashLevel::ERROR);
1653
    }
1654
1655
    /**
1656
     * @param string $message
1657
     * @param mixed $variant
1658
     * @return \Illuminate\Http\JsonResponse
1659
     */
1660 10
    protected function respondWithJson($message, $variant)
1661
    {
1662 10
        return Response::json([
1663 10
            'message' => $message,
1664 10
            'variant' => $variant,
1665
        ]);
1666
    }
1667
1668
    /**
1669
     * @param array $input
1670
     * @return void
1671
     */
1672 21
    protected function fireEvent($input = [])
1673
    {
1674 21
        fireCmsEvent('cms-module.saved', $input);
1675 21
    }
1676
1677
    /**
1678
     * @return Collection
1679
     */
1680 4
    public function getRepeaterList()
1681
    {
1682
        return app(BlockCollection::class)->getRepeaterList()->mapWithKeys(function ($repeater) {
1683 4
            return [$repeater['name'] => $repeater];
1684 4
        });
1685
    }
1686
}
1687