Passed
Pull Request — 2.x (#779)
by Quentin
16:59 queued 11:16
created

ModuleController::getIndexUrls()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 25
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
344
345 3
        $indexData = $this->getIndexData($this->submodule ? [
346
            $this->getParentModuleForeignKey() => $this->submoduleParentId,
347
        ] : []);
348
349
        if ($this->request->ajax()) {
350
            return $indexData + ['replaceUrl' => true];
351 2
        }
352
353 2
        if ($this->request->has('openCreate') && $this->request->get('openCreate')) {
354
            $indexData += ['openCreate' => true];
355
        }
356
357
        $view = Collection::make([
358
            "$this->viewPrefix.index",
359
            "twill::$this->moduleName.index",
360 25
            "twill::layouts.listing",
361
        ])->first(function ($view) {
362 25
            return View::exists($view);
363 25
        });
364
365 25
        return View::make($view, $indexData);
366
    }
367
368
    /**
369
     * @return \Illuminate\Http\JsonResponse
370
     */
371
    public function browser()
372
    {
373 25
        return Response::json($this->getBrowserData());
374
    }
375 25
376
    /**
377 25
     * @param int|null $parentModuleId
378
     * @return \Illuminate\Http\JsonResponse
379 25
     */
380
    public function store($parentModuleId = null)
381 25
    {
382 3
        $parentModuleId = $this->getParentModuleIdFromRequest($this->request) ?? $parentModuleId;
383
384
        $input = $this->validateFormRequest()->all();
385 22
        $optionalParent = $parentModuleId ? [$this->getParentModuleForeignKey() => $parentModuleId] : [];
386
387
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
388
            return $this->respondWithRedirect(moduleRoute(
389
                $this->moduleName,
390
                $this->routePrefix,
391
                'create'
392 22
            ));
393
        }
394
395
        $item = $this->repository->create($input + $optionalParent);
396 22
397
        activity()->performedOn($item)->log('created');
398
399
        $this->fireEvent($input);
400 22
401
        Session::put($this->moduleName . '_retain', true);
402
403
        if ($this->getIndexOption('editInModal')) {
404
            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

404
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
405
        }
406
407
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-close')) {
408 22
            return $this->respondWithRedirect($this->getBackLink());
409 22
        }
410 22
411 22
        if (isset($input['cmsSaveType']) && Str::endsWith($input['cmsSaveType'], '-new')) {
412
            return $this->respondWithRedirect(moduleRoute(
413
                $this->moduleName,
414
                $this->routePrefix,
415
                'create'
416
            ));
417
        }
418
419
        return $this->respondWithRedirect(moduleRoute(
420
            $this->moduleName,
421 1
            $this->routePrefix,
422
            'edit',
423 1
            [Str::singular(last(explode('.', $this->moduleName))) => $item[$this->identifierColumnKey]]
424
        ));
425
    }
426
427 1
    /**
428
     * @param Request $request
429
     * @param int|$id
430
     * @param int|null $submoduleId
431
     * @return \Illuminate\Http\RedirectResponse
432
     */
433
    public function show($id, $submoduleId = null)
434
    {
435 6
        if ($this->getIndexOption('editInModal')) {
436
            return Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
437 6
        }
438 6
439
        return $this->redirectToForm($this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
0 ignored issues
show
Bug introduced by
It seems like $this->getParentModuleId... ?? $submoduleId ?? $id can also be of type string; however, parameter $id of A17\Twill\Http\Controlle...oller::redirectToForm() does only seem to accept integer, 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

439
        return $this->redirectToForm(/** @scrutinizer ignore-type */ $this->getParentModuleIdFromRequest($this->request) ?? $submoduleId ?? $id);
Loading history...
440 6
    }
441 2
442 1
    /**
443 2
    * @param int $id
444
    * @param int|null $submoduleId
445
    * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
446 4
    */
447
    public function edit($id, $submoduleId = null)
448 4
    {
449 4
        $params = $this->request->route()->parameters();
450 4
451 4
        $this->submodule = count($params) > 1;
452 4
        $this->submoduleParentId = $this->submodule
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->submodule ? $this... ?? $id : head($params) can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
453 4
            ? $this->getParentModuleIdFromRequest($this->request) ?? $id
454 4
            : head($params);
455
456 4
        $id = last($params);
457
458
        if ($this->getIndexOption('editInModal')) {
459
            return $this->request->ajax()
460
            ? Response::json($this->modalFormData($id))
461
            : Redirect::to(moduleRoute($this->moduleName, $this->routePrefix, 'index'));
462
        }
463
464
        $this->setBackLink();
465
466
        $view = Collection::make([
467
            "$this->viewPrefix.form",
468
            "twill::$this->moduleName.form",
469
            "twill::layouts.form",
470
        ])->first(function ($view) {
471
            return View::exists($view);
472
        });
473
474
        return View::make($view, $this->form($id));
475
    }
476
477
    /**
478
     * @param int $parentModuleId
479
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
480
     */
481
    public function create($parentModuleId = null)
482
    {
483
        if (!$this->getIndexOption('skipCreateModal')) {
484
            return Redirect::to(moduleRoute(
485
                $this->moduleName,
486
                $this->routePrefix,
487
                'index',
488
                ['openCreate' => true]
489
            ));
490
        }
491
492
        $parentModuleId = $this->getParentModuleIdFromRequest($this->request) ?? $parentModuleId;
493 8
494
        $this->submodule = isset($parentModuleId);
495 8
        $this->submoduleParentId = $parentModuleId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parentModuleId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
496 8
497
        $view = Collection::make([
498 8
            "$this->viewPrefix.form",
499 8
            "twill::$this->moduleName.form",
500
            "twill::layouts.form",
501 8
        ])->first(function ($view) {
502
            return View::exists($view);
503
        });
504
505
        return View::make($view, $this->form(null));
506
    }
507
508
    /**
509 8
     * @param int $id
510
     * @param int|null $submoduleId
511 8
     * @return \Illuminate\Http\JsonResponse
512
     */
513 8
    public function update($id, $submoduleId = null)
514
    {
515 8
        $params = $this->request->route()->parameters();
516
517 8
        $submoduleParentId = $this->getParentModuleIdFromRequest($this->request) ?? $id;
518 8
        $this->submodule = isset($submoduleParentId);
519
        $this->submoduleParentId = $submoduleParentId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $submoduleParentId can also be of type string. However, the property $submoduleParentId is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
520 8
521
        $id = last($params);
522
523
        $item = $this->repository->getById($id);
524
        $input = $this->request->all();
525
526
        if (isset($input['cmsSaveType']) && $input['cmsSaveType'] === 'cancel') {
527
            return $this->respondWithRedirect(moduleRoute(
528
                $this->moduleName,
529
                $this->routePrefix,
530
                'edit',
531
                [Str::singular($this->moduleName) => $id]
532
            ));
533
        } else {
534 8
            $formRequest = $this->validateFormRequest();
535
536
            $this->repository->update($id, $formRequest->all());
537
538
            activity()->performedOn($item)->log('updated');
539
540
            $this->fireEvent();
541
542
            if (isset($input['cmsSaveType'])) {
543
                if (Str::endsWith($input['cmsSaveType'], '-close')) {
544
                    return $this->respondWithRedirect($this->getBackLink());
545
                } elseif (Str::endsWith($input['cmsSaveType'], '-new')) {
546 8
                    if ($this->getIndexOption('skipCreateModal')) {
547 7
                        return $this->respondWithRedirect(moduleRoute(
548 7
                            $this->moduleName,
549 7
                            $this->routePrefix,
550 7
                            'create'
551
                        ));
552
                    }
553
                    return $this->respondWithRedirect(moduleRoute(
554 1
                        $this->moduleName,
555
                        $this->routePrefix,
556
                        'index',
557
                        ['openCreate' => true]
558
                    ));
559
                } elseif ($input['cmsSaveType'] === 'restore') {
560
                    Session::flash('status', twillTrans('twill::lang.publisher.restore-success'));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang....isher.restore-success') can also be of type array and array; however, parameter $message of Illuminate\Support\Facades\Session::flash() 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

560
                    Session::flash('status', /** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.restore-success'));
Loading history...
561
562 1
                    return $this->respondWithRedirect(moduleRoute(
563
                        $this->moduleName,
564 1
                        $this->routePrefix,
565
                        'edit',
566
                        [Str::singular($this->moduleName) => $id]
567 1
                    ));
568 1
                }
569
            }
570
571 1
            if ($this->moduleHas('revisions')) {
572
                return Response::json([
573
                    'message' => twillTrans('twill::lang.publisher.save-success'),
574
                    'variant' => FlashLevel::SUCCESS,
575 1
                    'revisions' => $item->revisionsArray(),
576
                ]);
577 1
            }
578 1
579 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

579
            return $this->respondWithSuccess(/** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.save-success'));
Loading history...
580 1
        }
581
    }
582
583
    /**
584
     * @param int $id
585
     * @return \Illuminate\View\View
586
     */
587
    public function preview($id)
588 2
    {
589
        if ($this->request->has('revisionId')) {
590 2
            $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

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

593
            /** @scrutinizer ignore-call */ 
594
            $item = $this->repository->preview($id, $formRequest->all());
Loading history...
594
        }
595 1
596
        if ($this->request->has('activeLanguage')) {
597
            App::setLocale($this->request->get('activeLanguage'));
598 1
        }
599
600 1
        $previewView = $this->previewView ?? (Config::get('twill.frontend.views_path', 'site') . '.' . Str::singular($this->moduleName));
601 1
602 1
        return View::exists($previewView) ? View::make($previewView, array_replace([
603 1
            'item' => $item,
604 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

604
        ], $this->previewData(/** @scrutinizer ignore-type */ $item))) : View::make('twill::errors.preview', [
Loading history...
605 1
            'moduleName' => Str::singular($this->moduleName),
606 1
        ]);
607
    }
608 1
609 1
    /**
610
     * @param int $id
611 1
     * @return \Illuminate\View\View
612
     */
613 1
    public function restoreRevision($id)
614
    {
615
        if ($this->request->has('revisionId')) {
616
            $item = $this->repository->previewForRevision($id, $this->request->get('revisionId'));
617
            $item[$this->identifierColumnKey] = $id;
618
            $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...
619 2
        } else {
620
            throw new NotFoundHttpException();
621
        }
622 2
623 2
        $this->setBackLink();
624
625 2
        $view = Collection::make([
626 2
            "$this->viewPrefix.form",
627 1
            "twill::$this->moduleName.form",
628 1
            "twill::layouts.form",
629
        ])->first(function ($view) {
630
            return View::exists($view);
631 1
        });
632
633 1
        $revision = $item->revisions()->where('id', $this->request->get('revisionId'))->first();
634 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

634
        /** @scrutinizer ignore-call */ 
635
        $date = $revision->created_at->toDayDateTimeString();
Loading history...
635
636
        Session::flash('restoreMessage', twillTrans('twill::lang.publisher.restore-message', ['user' => $revision->byUser, 'date' => $date]));
0 ignored issues
show
Bug introduced by
It seems like twillTrans('twill::lang....User, 'date' => $date)) can also be of type array and array; however, parameter $message of Illuminate\Support\Facades\Session::flash() 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

636
        Session::flash('restoreMessage', /** @scrutinizer ignore-type */ twillTrans('twill::lang.publisher.restore-message', ['user' => $revision->byUser, 'date' => $date]));
Loading history...
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...
637 1
638 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

638
        return View::make($view, $this->form($id, /** @scrutinizer ignore-type */ $item));
Loading history...
639
    }
640
641 1
    /**
642 1
     * @return \Illuminate\Http\JsonResponse
643
     */
644
    public function publish()
645
    {
646
        try {
647
            if ($this->repository->updateBasic($this->request->get('id'), [
648
                'published' => !$this->request->get('active'),
649
            ])) {
650
                activity()->performedOn(
651
                    $this->repository->getById($this->request->get('id'))
652
                )->log(
653
                    ($this->request->get('active') ? 'un' : '') . 'published'
654
                );
655
656
                $this->fireEvent();
657
658
                return $this->respondWithSuccess(
659
                    $this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'published!'
660
                );
661
            }
662
        } catch (\Exception $e) {
663
            \Log::error($e);
664
        }
665
666
        return $this->respondWithError(
667
            $this->modelTitle . ' was not published. Something wrong happened!'
668
        );
669
    }
670
671
    /**
672
     * @return \Illuminate\Http\JsonResponse
673
     */
674
    public function bulkPublish()
675
    {
676
        try {
677
            if ($this->repository->updateBasic(explode(',', $this->request->get('ids')), [
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() 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

677
            if ($this->repository->updateBasic(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')), [
Loading history...
678
                'published' => $this->request->get('publish'),
679
            ])) {
680
                $this->fireEvent();
681
682
                return $this->respondWithSuccess(
683
                    $this->modelTitle . ' items ' . ($this->request->get('publish') ? '' : 'un') . 'published!'
684
                );
685
            }
686
        } catch (\Exception $e) {
687
            \Log::error($e);
688
        }
689
690
        return $this->respondWithError(
691
            $this->modelTitle . ' items were not published. Something wrong happened!'
692
        );
693
    }
694
695
    /**
696
     * @param int $id
697
     * @param int|null $submoduleId
698
     * @return \Illuminate\Http\JsonResponse
699
     */
700
    public function duplicate($id, $submoduleId = null)
701
    {
702
        $params = $this->request->route()->parameters();
703 2
704
        $id = last($params);
705 2
706 2
        $item = $this->repository->getById($id);
707 2
        if ($newItem = $this->repository->duplicate($id, $this->titleColumnKey)) {
708 2
            $this->fireEvent();
709 2
            activity()->performedOn($item)->log('duplicated');
710
711
            return Response::json([
712
                'message' => $this->modelTitle . ' duplicated with Success!',
713
                'variant' => FlashLevel::SUCCESS,
714
                'redirect' => moduleRoute(
715
                    $this->moduleName,
716
                    $this->routePrefix,
717
                    'edit',
718
                    array_filter([Str::singular($this->moduleName) => $newItem->id])
719
                ),
720
            ]);
721
        }
722
723
        return $this->respondWithError($this->modelTitle . ' was not duplicated. Something wrong happened!');
724
    }
725
726
    /**
727
     * @param int $id
728
     * @param int|null $submoduleId
729
     * @return \Illuminate\Http\JsonResponse
730
     */
731
    public function destroy($id, $submoduleId = null)
732
    {
733
        $params = $this->request->route()->parameters();
734
735
        $id = last($params);
736
737
        $item = $this->repository->getById($id);
738
        if ($this->repository->delete($id)) {
739
            $this->fireEvent();
740
            activity()->performedOn($item)->log('deleted');
741
            return $this->respondWithSuccess($this->modelTitle . ' moved to trash!');
742
        }
743
744
        return $this->respondWithError($this->modelTitle . ' was not moved to trash. Something wrong happened!');
745
    }
746
747
    /**
748
     * @return \Illuminate\Http\JsonResponse
749
     */
750
    public function bulkDelete()
751
    {
752
        if ($this->repository->bulkDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() 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

752
        if ($this->repository->bulkDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
753
            $this->fireEvent();
754
            return $this->respondWithSuccess($this->modelTitle . ' items moved to trash!');
755
        }
756
757 2
        return $this->respondWithError($this->modelTitle . ' items were not moved to trash. Something wrong happened!');
758
    }
759 2
760 1
    /**
761 1
     * @return \Illuminate\Http\JsonResponse
762 1
     */
763
    public function forceDelete()
764
    {
765 1
        if ($this->repository->forceDelete($this->request->get('id'))) {
766
            $this->fireEvent();
767
            return $this->respondWithSuccess($this->modelTitle . ' destroyed!');
768
        }
769
770
        return $this->respondWithError($this->modelTitle . ' was not destroyed. Something wrong happened!');
771
    }
772
773
    /**
774
     * @return \Illuminate\Http\JsonResponse
775
     */
776
    public function bulkForceDelete()
777
    {
778
        if ($this->repository->bulkForceDelete(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() 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

778
        if ($this->repository->bulkForceDelete(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
779
            $this->fireEvent();
780
            return $this->respondWithSuccess($this->modelTitle . ' items destroyed!');
781
        }
782
783
        return $this->respondWithError($this->modelTitle . ' items were not destroyed. Something wrong happened!');
784 2
    }
785
786 2
    /**
787 2
     * @return \Illuminate\Http\JsonResponse
788 2
     */
789
    public function restore()
790 2
    {
791
        if ($this->repository->restore($this->request->get('id'))) {
792
            $this->fireEvent();
793
            activity()->performedOn($this->repository->getById($this->request->get('id')))->log('restored');
794
            return $this->respondWithSuccess($this->modelTitle . ' restored!');
795
        }
796 2
797
        return $this->respondWithError($this->modelTitle . ' was not restored. Something wrong happened!');
798
    }
799 2
800 2
    /**
801 1
     * @return \Illuminate\Http\JsonResponse
802 1
     */
803
    public function bulkRestore()
804
    {
805 1
        if ($this->repository->bulkRestore(explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() 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

805
        if ($this->repository->bulkRestore(explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
806 1
            $this->fireEvent();
807
            return $this->respondWithSuccess($this->modelTitle . ' items restored!');
808
        }
809
810
        return $this->respondWithError($this->modelTitle . ' items were not restored. Something wrong happened!');
811
    }
812
813
    /**
814
     * @return \Illuminate\Http\JsonResponse
815
     */
816
    public function feature()
817
    {
818
        if (($id = $this->request->get('id'))) {
819
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
820
            $featured = !$this->request->get('active');
821
822
            if ($this->repository->isUniqueFeature()) {
823
                if ($featured) {
824
                    $this->repository->updateBasic(null, [$featuredField => false]);
825
                    $this->repository->updateBasic($id, [$featuredField => $featured]);
826
                }
827
            } else {
828
                $this->repository->updateBasic($id, [$featuredField => $featured]);
829
            }
830
831
            activity()->performedOn(
832 4
                $this->repository->getById($id)
833
            )->log(
834 4
                ($this->request->get('active') ? 'un' : '') . 'featured'
835 4
            );
836 3
837 3
            $this->fireEvent();
838
            return $this->respondWithSuccess($this->modelTitle . ' ' . ($this->request->get('active') ? 'un' : '') . 'featured!');
839
        }
840
841
        return $this->respondWithError($this->modelTitle . ' was not featured. Something wrong happened!');
842
    }
843
844
    /**
845
     * @return \Illuminate\Http\JsonResponse
846 1
     */
847
    public function bulkFeature()
848 1
    {
849 1
        if (($ids = explode(',', $this->request->get('ids')))) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('ids') can also be of type null; however, parameter $string of explode() 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

849
        if (($ids = explode(',', /** @scrutinizer ignore-type */ $this->request->get('ids')))) {
Loading history...
850
            $featuredField = $this->request->get('featureField') ?? $this->featureField;
851 1
            $featured = $this->request->get('feature') ?? true;
852
            // we don't need to check if unique feature since bulk operation shouldn't be allowed in this case
853 1
            $this->repository->updateBasic($ids, [$featuredField => $featured]);
854
            $this->fireEvent();
855
            return $this->respondWithSuccess($this->modelTitle . ' items ' . ($this->request->get('feature') ? '' : 'un') . 'featured!');
856
        }
857
858
        return $this->respondWithError($this->modelTitle . ' items were not featured. Something wrong happened!');
859
    }
860 6
861
    /**
862 6
     * @return \Illuminate\Http\JsonResponse
863 6
     */
864
    public function reorder()
865
    {
866 6
        if (($values = $this->request->get('ids')) && !empty($values)) {
867 6
            $this->repository->setNewOrder($values);
868 6
            $this->fireEvent();
869 6
            return $this->respondWithSuccess($this->modelTitle . ' order changed!');
870 6
        }
871 6
872 6
        return $this->respondWithError($this->modelTitle . ' order was not changed. Something wrong happened!');
873 6
    }
874 6
875 6
    /**
876 6
     * @return \Illuminate\Http\JsonResponse
877
     */
878 6
    public function tags()
879
    {
880
        $query = $this->request->input('q');
881 6
        $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

881
        /** @scrutinizer ignore-call */ 
882
        $tags = $this->repository->getTags($query);
Loading history...
882 6
883 6
        return Response::json(['items' => $tags->map(function ($tag) {
884 6
            return $tag->name;
885 6
        })], 200);
886 6
    }
887 6
888 6
    /**
889 6
     * @param array $prependScope
890 6
     * @return array
891 6
     */
892 6
    protected function getIndexData($prependScope = [])
893
    {
894
        $scopes = $this->filterScope($prependScope);
895 6
        $items = $this->getIndexItems($scopes);
896
897
        $data = [
898
            'tableData' => $this->getIndexTableData($items),
899
            'tableColumns' => $this->getIndexTableColumns($items),
900
            'tableMainFilters' => $this->getIndexTableMainFilters($items),
901
            'filters' => json_decode($this->request->get('filter'), true) ?? [],
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('filter') can also be of type null; however, parameter $json of json_decode() 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

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

1320
                'endpointType' => $this->repository->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
1321
            ] + $columnsData + ($withImage && !array_key_exists('thumbnail', $columnsData) ? [
1322
                'thumbnail' => $item->defaultCmsImage(['w' => 100, 'h' => 100]),
1323
            ] : []);
1324
        })->toArray();
1325
    }
1326
1327
    /**
1328
     * @param array $scopes
1329 1
     * @return \Illuminate\Database\Eloquent\Collection
1330
     */
1331
    protected function getBrowserItems($scopes = [])
1332 12
    {
1333 12
        return $this->getIndexItems($scopes, true);
1334 2
    }
1335 2
1336
    /**
1337 2
     * @param array $prepend
1338 2
     * @return array
1339
     */
1340
    protected function filterScope($prepend = [])
1341
    {
1342
        $scope = [];
1343
1344 2
        $requestFilters = $this->getRequestFilters();
1345
1346
        $this->filters = array_merge($this->filters, $this->defaultFilters);
1347
1348
        if (array_key_exists('status', $requestFilters)) {
1349
            switch ($requestFilters['status']) {
1350 12
                case 'published':
1351
                    $scope['published'] = true;
1352
                    break;
1353
                case 'draft':
1354
                    $scope['draft'] = true;
1355
                    break;
1356 7
                case 'trash':
1357
                    $scope['onlyTrashed'] = true;
1358 7
                    break;
1359
                case 'mine':
1360
                    $scope['mine'] = true;
1361
                    break;
1362 7
            }
1363
1364
            unset($requestFilters['status']);
1365
        }
1366
1367
        foreach ($this->filters as $key => $field) {
1368 44
            if (array_key_exists($key, $requestFilters)) {
1369
                $value = $requestFilters[$key];
1370 44
                if ($value == 0 || !empty($value)) {
1371 44
                    // add some syntaxic sugar to scope the same filter on multiple columns
1372
                    $fieldSplitted = explode('|', $field);
1373
                    if (count($fieldSplitted) > 1) {
1374
                        $requestValue = $requestFilters[$key];
1375
                        Collection::make($fieldSplitted)->each(function ($scopeKey) use (&$scope, $requestValue) {
1376
                            $scope[$scopeKey] = $requestValue;
1377
                        });
1378
                    } else {
1379
                        $scope[$field] = $requestFilters[$key];
1380
                    }
1381
                }
1382
            }
1383
        }
1384
1385
        return $prepend + $scope;
1386
    }
1387
1388 12
    /**
1389
     * @return array
1390 12
     */
1391 12
    protected function getRequestFilters()
1392 1
    {
1393
        if ($this->request->has('search')) {
1394 1
            return ['search' => $this->request->get('search')];
1395 1
        }
1396
1397
        return json_decode($this->request->get('filter'), true) ?? [];
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('filter') can also be of type null; however, parameter $json of json_decode() 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

1397
        return json_decode(/** @scrutinizer ignore-type */ $this->request->get('filter'), true) ?? [];
Loading history...
1398 1
    }
1399 1
1400
    /**
1401
     * @return void
1402
     */
1403
    protected function applyFiltersDefaultOptions()
1404 12
    {
1405 12
        if (!count($this->filtersDefaultOptions) || $this->request->has('search')) {
1406
            return;
1407 12
        }
1408
1409
        $filters = $this->getRequestFilters();
1410
1411
        foreach ($this->filtersDefaultOptions as $filterName => $defaultOption) {
1412
            if (!isset($filters[$filterName])) {
1413
                $filters[$filterName] = $defaultOption;
1414
            }
1415 5
        }
1416
1417
        $this->request->merge(['filter' => json_encode($filters)]);
1418 5
    }
1419 4
1420 1
    /**
1421
     * @return array
1422
     */
1423
    protected function orderScope()
1424 5
    {
1425 5
        $orders = [];
1426 5
        if ($this->request->has('sortKey') && $this->request->has('sortDir')) {
1427
            if (($key = $this->request->get('sortKey')) == 'name') {
1428 5
                $sortKey = $this->titleColumnKey;
1429
            } elseif (!empty($key)) {
1430
                $sortKey = $key;
1431 5
            }
1432 5
1433 5
            if (isset($sortKey)) {
1434 5
                $orders[$this->indexColumns[$sortKey]['sortKey'] ?? $sortKey] = $this->request->get('sortDir');
1435 5
            }
1436 5
        }
1437 5
1438 5
        // don't apply default orders if reorder is enabled
1439 5
        $reorder = $this->getIndexOption('reorder');
1440 5
        $defaultOrders = ($reorder ? [] : ($this->defaultOrders ?? []));
1441 5
1442 5
        return $orders + $defaultOrders;
1443 5
    }
1444 5
1445 5
    /**
1446 5
     * @param int $id
1447 5
     * @param \A17\Twill\Models\Model|null $item
1448 5
     * @return array
1449 5
     */
1450 5
    protected function form($id, $item = null)
1451 5
    {
1452 5
        if (!$item && $id) {
1453 5
            $item = $this->repository->getById($id, $this->formWith, $this->formWithCount);
1454 5
        } elseif (!$item && !$id) {
1455 5
            $item = $this->repository->newInstance();
0 ignored issues
show
Bug introduced by
The method newInstance() does not exist on A17\Twill\Repositories\ModuleRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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