Issues (389)

src/Admin/Traits/Crudify.php (1 issue)

1
<?php
2
3
namespace Arbory\Base\Admin\Traits;
4
5
use Admin;
6
use Arbory\Base\Admin\Exports\DataSetExport;
7
use Arbory\Base\Admin\Exports\ExportInterface;
8
use Arbory\Base\Admin\Exports\Type\ExcelExport;
9
use Arbory\Base\Admin\Exports\Type\JsonExport;
10
use Arbory\Base\Admin\Filter\FilterManager;
11
use Arbory\Base\Admin\Form;
12
use Arbory\Base\Admin\Grid;
13
use Arbory\Base\Admin\Grid\ExportBuilder;
14
use Arbory\Base\Admin\Layout;
15
use Arbory\Base\Admin\Layout\LayoutInterface;
16
use Arbory\Base\Admin\Module;
17
use Arbory\Base\Admin\Page;
18
use Arbory\Base\Admin\Tools\ToolboxMenu;
19
use Arbory\Base\Exceptions\ExportException;
20
use Exception;
21
use Illuminate\Database\Eloquent\Builder;
22
use Illuminate\Database\Eloquent\Model;
23
use Illuminate\Http\JsonResponse;
24
use Illuminate\Http\RedirectResponse;
25
use Illuminate\Http\Request;
26
use Illuminate\Http\Response;
27
use Illuminate\Routing\Redirector;
28
use Illuminate\Support\Facades\DB;
29
use Illuminate\Support\Str;
30
use Illuminate\View\View;
31
use Symfony\Component\HttpFoundation\BinaryFileResponse;
32
33
trait Crudify
34
{
35
    /**
36
     * @var array
37
     */
38
    protected static $exportTypes = [
39
        'xls' => ExcelExport::class,
40
        'json' => JsonExport::class,
41
    ];
42
43
    /**
44
     * @var Module
45
     */
46
    protected $module;
47
48
    /**
49
     * @return Model|Builder
50
     */
51
    public function resource()
52
    {
53
        $class = $this->resource;
54
55
        return new $class;
56
    }
57
58
    /**
59
     * @return Module
60
     */
61
    protected function module()
62
    {
63
        if ($this->module === null) {
64
            $this->module = Admin::modules()->findModuleByControllerClass(get_class($this));
65
        }
66
67
        return $this->module;
68
    }
69
70
    /**
71
     * @param  Form  $form
72
     * @param  Layout\FormLayoutInterface|null  $layout
73
     * @return Form
74
     */
75
    protected function form(Form $form, ?Layout\FormLayoutInterface $layout = null)
76
    {
77
        return $form;
78
    }
79
80
    /**
81
     * @param  Model  $model
82
     * @param  Layout\FormLayoutInterface|null  $layout
83
     * @return Form
84
     */
85
    protected function buildForm(Model $model, ?Layout\FormLayoutInterface $layout = null)
86
    {
87
        $form = new Form($model);
88
        $form->setModule($this->module());
89
        $form->setRenderer(new Form\Builder($form));
90
91
        $layout?->setForm($form);
92
93
        return $this->form($form, $layout) ?: $form;
94
    }
95
96
    /**
97
     * @param  Grid  $grid
98
     * @return Grid
99
     */
100
    public function grid(Grid $grid)
101
    {
102
        return $grid;
103
    }
104
105
    /**
106
     * @param  Model  $model
107
     * @return Grid
108
     */
109
    protected function buildGrid(Model $model)
110
    {
111
        $grid = new Grid($model);
112
        $grid->setModule($this->module());
113
        $grid->setRenderer(new Grid\Builder($grid));
114
        $grid->setFilterManager(app(FilterManager::class));
115
        $grid->setupFilter();
116
117
        return $this->grid($grid) ?: $grid;
118
    }
119
120
    /**
121
     * @param  Layout\LayoutManager  $manager
122
     * @return Layout
123
     */
124
    public function index(Layout\LayoutManager $manager)
125
    {
126
        $layout = $this->layout('grid');
127
128
        $layout->setGrid($this->buildGrid($this->resource()));
129
130
        $page = $manager->page(Page::class);
131
132
        $grid = $layout->getGrid();
133
134
        $bulkEditClass = $grid->hasTool('bulk-edit') ? ' bulk-edit-grid' : '';
135
136
        $page->setBreadcrumbs($this->module()->breadcrumbs());
137
        $page->use($layout);
138
        $page->bodyClass('controller-'.Str::slug($this->module()->name()).' view-index'.$bulkEditClass);
139
140
        return $page;
141
    }
142
143
    /**
144
     * @param $resourceId
145
     * @return RedirectResponse
146
     */
147
    public function show($resourceId)
148
    {
149
        return redirect($this->module()->url('edit', $resourceId));
150
    }
151
152
    /**
153
     * @param  Layout\LayoutManager  $manager
154
     * @return Layout
155
     */
156
    public function create(Layout\LayoutManager $manager)
157
    {
158
        $layout = $this->layout('form');
159
        $form = $this->buildForm($this->resource(), $layout);
160
161
        $page = $manager->page(Page::class);
162
163
        $page->use($layout);
164
        $page->bodyClass('controller-'.Str::slug($this->module()->name()).' view-edit');
165
166
        return $page;
167
    }
168
169
    /**
170
     * @param  Request  $request
171
     * @return RedirectResponse|Redirector
172
     */
173
    public function store(Request $request)
174
    {
175
        $layout = $this->layout('form');
176
        $form = $this->buildForm($this->resource(), $layout);
177
178
        $this->addFieldsToRequest($request, $form);
179
180
        $form->validate();
181
182
        if ($request->ajax()) {
183
            return response()->json(['ok']);
184
        }
185
186
        $form->store($request);
187
188
        return $this->getAfterCreateResponse($request, $form->getModel());
189
    }
190
191
    /**
192
     * @param  $resourceId
193
     * @param  Layout\LayoutManager  $manager
194
     * @return Layout
195
     */
196
    public function edit($resourceId, Layout\LayoutManager $manager)
197
    {
198
        $resource = $this->findOrNew($resourceId);
199
        $layout = $this->layout('form');
200
        $form = $this->buildForm($resource, $layout);
201
202
        $page = $manager->page(Page::class);
203
        $page->use($layout);
204
        $page->bodyClass('controller-'.Str::slug($this->module()->name()).' view-edit');
205
206
        return $page;
207
    }
208
209
    /**
210
     * @param  Request  $request
211
     * @param $resourceId
212
     * @return JsonResponse|RedirectResponse|Redirector
213
     */
214
    public function update(Request $request, $resourceId)
215
    {
216
        $resource = $this->findOrNew($resourceId);
217
        $layout = $this->layout('form');
218
        $form = $this->buildForm($resource, $layout);
219
220
        $layout->setForm($form);
221
222
        $this->addFieldsToRequest($request, $form);
223
224
        $form->validate();
225
226
        if ($request->ajax()) {
227
            return response()->json(['ok']);
228
        }
229
230
        $form->update($request);
231
232
        return $this->getAfterEditResponse($request, $form->getModel());
233
    }
234
235
    /**
236
     * @param $resourceId
237
     * @return RedirectResponse|Redirector
238
     */
239
    public function destroy($resourceId)
240
    {
241
        $resource = $this->resource()->findOrFail($resourceId);
242
        $layout = $this->layout('form');
243
244
        $this->buildForm($resource, $layout)->destroy();
245
246
        return redirect($this->module()->url('index'));
247
    }
248
249
    /**
250
     * @param  Request  $request
251
     * @param  string  $name
252
     * @return mixed
253
     */
254
    public function dialog(Request $request, string $name)
255
    {
256
        $method = Str::camel($name).'Dialog';
257
258
        if (! $name || ! method_exists($this, $method)) {
259
            app()->abort(Response::HTTP_NOT_FOUND);
260
        }
261
262
        return $this->{$method}($request);
263
    }
264
265
    /**
266
     * @param  string  $type
267
     * @return BinaryFileResponse
268
     *
269
     * @throws Exception
270
     */
271
    public function export(string $type): BinaryFileResponse
272
    {
273
        $grid = $this->buildGrid($this->resource());
274
        $grid->setRenderer(new ExportBuilder($grid));
275
        $grid->paginate(false);
276
277
        $grid->exportEnabled();
278
279
        /** @var DataSetExport $dataSet */
280
        $dataSet = $grid->render();
281
282
        $exporter = $this->getExporter($type, $dataSet);
283
284
        return $exporter->download($this->module()->name());
285
    }
286
287
    /**
288
     * @param  string  $type
289
     * @param  DataSetExport  $dataSet
290
     * @return ExportInterface
291
     *
292
     * @throws Exception
293
     */
294
    protected function getExporter(string $type, DataSetExport $dataSet): ExportInterface
295
    {
296
        if (! isset(self::$exportTypes[$type])) {
297
            throw new ExportException('Export Type not found - '.$type);
298
        }
299
300
        return new self::$exportTypes[$type]($dataSet);
301
    }
302
303
    /**
304
     * @param  Request  $request
305
     * @return string
306
     */
307
    protected function toolboxDialog(Request $request): string
308
    {
309
        $node = $this->findOrNew($request->get('id'));
310
311
        $toolbox = new ToolboxMenu($node);
312
313
        $this->toolbox($toolbox);
314
315
        return $toolbox->render();
316
    }
317
318
    /**
319
     * @param  ToolboxMenu  $tools
320
     */
321
    protected function toolbox(ToolboxMenu $tools): void
322
    {
323
        $model = $tools->model();
324
325
        $tools->add('edit', $this->url('edit', $model->getKey()));
326
        $tools->add(
327
            'delete',
328
            $this->url('dialog', ['dialog' => 'confirm_delete', 'id' => $model->getKey()])
329
        )->dialog()->danger();
330
    }
331
332
    /**
333
     * @param  Request  $request
334
     * @return View
335
     */
336
    protected function confirmDeleteDialog(Request $request)
337
    {
338
        $resourceId = $request->get('id');
339
        $model = $this->resource()->find($resourceId);
340
341
        return view('arbory::dialogs.confirm_delete', [
342
            'form_target' => $this->url('destroy', [$resourceId]),
343
            'list_url' => $this->url('index'),
344
            'object_name' => (string) $model,
345
        ]);
346
    }
347
348
    /**
349
     * @param  Request  $request
350
     * @param  string  $name
351
     * @return null
352
     */
353
    public function api(Request $request, string $name)
354
    {
355
        $method = Str::camel($name).'Api';
356
357
        if (! $name || ! method_exists($this, $method)) {
358
            app()->abort(Response::HTTP_NOT_FOUND);
359
360
            return;
361
        }
362
363
        return $this->{$method}($request);
364
    }
365
366
    /**
367
     * @param  string  $route
368
     * @param  array  $parameters
369
     * @return string
370
     */
371
    public function url(string $route, $parameters = [])
372
    {
373
        return $this->module()->url($route, $parameters);
374
    }
375
376
    /**
377
     * @param  mixed  $resourceId
378
     * @return Model
379
     */
380
    protected function findOrNew($resourceId): Model
381
    {
382
        /**
383
         * @var Model
384
         */
385
        $resource = $this->resource();
386
387
        if (method_exists($resource, 'bootSoftDeletes')) {
388
            $resource = $resource->withTrashed();
389
        }
390
391
        $resource = $resource->findOrNew($resourceId);
392
        $resource->setAttribute($resource->getKeyName(), $resourceId);
393
394
        return $resource;
395
    }
396
397
    /**
398
     * @param  Request  $request
399
     * @return string
400
     *
401
     * @throws Exception
402
     */
403
    public function slugGeneratorApi(Request $request)
404
    {
405
        /** @var \Illuminate\Database\Query\Builder $query */
406
        $slug = Str::slug($request->input('from'));
407
        $column = $request->input('column_name');
408
409
        $query = DB::table($request->input('model_table'))->where($column, $slug);
410
411
        if ($locale = $request->input('locale')) {
412
            $query->where('locale', $locale);
413
        }
414
415
        if ($objectId = $request->input('object_id')) {
416
            $query->where('id', '<>', $objectId);
417
        }
418
419
        if ($column && $query->exists()) {
420
            $slug .= '-'.random_int(0, 9999);
421
        }
422
423
        return $slug;
424
    }
425
426
    /**
427
     * @param  Request  $request
428
     * @param  Model  $model
429
     * @return RedirectResponse|Redirector
430
     */
431
    protected function getAfterEditResponse(Request $request, $model)
432
    {
433
        $defaultReturnUrl = $this->module()->url('index');
434
        $returnUrl = $request->has(Form::INPUT_RETURN_URL) ? $request->get(Form::INPUT_RETURN_URL) : $defaultReturnUrl;
435
436
        return redirect($request->has('save_and_return') ? $returnUrl : $request->url());
437
    }
438
439
    /**
440
     * @param  Request  $request
441
     * @param  Model  $model
442
     * @return RedirectResponse|Redirector
443
     */
444
    protected function getAfterCreateResponse(Request $request, $model)
445
    {
446
        $defaultReturnUrl = $this->module()->url('index');
447
        $returnUrl = $request->has(Form::INPUT_RETURN_URL) ? $request->get(Form::INPUT_RETURN_URL) : $defaultReturnUrl;
448
449
        $url = $this->url('edit', $model);
450
451
        return redirect($request->has('save_and_return') ? $returnUrl : $url);
452
    }
453
454
    /**
455
     * Creates a layout instance.
456
     *
457
     * @param  string  $component
458
     * @param  mixed  $with
459
     * @return LayoutInterface
460
     */
461
    protected function layout($component, $with = null)
462
    {
463
        $layouts = $this->layouts() ?: [];
464
465
        $class = $layouts[$component] ?? null;
466
467
        if (! $class && ! class_exists($class)) {
0 ignored issues
show
It seems like $class can also be of type null; however, parameter $class of class_exists() 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

467
        if (! $class && ! class_exists(/** @scrutinizer ignore-type */ $class)) {
Loading history...
468
            throw new ExportException("Layout class '{$class}' for '{$component}' does not exist");
469
        }
470
471
        return $with ? app()->makeWith($class, $with) : app()->make($class);
472
    }
473
474
    /**
475
     * Defined layouts.
476
     *
477
     * @return array
478
     */
479
    public function layouts()
480
    {
481
        return [
482
            'grid' => Grid\Layout::class,
483
            'form' => Form\Layout::class,
484
        ];
485
    }
486
487
    private function addFieldsToRequest($request, Form $form): void
488
    {
489
        // ensapsulate fields in array, as FieldSet itself
490
        // cannot be directly added to request
491
        $request->request->add(['fields' => [$form->fields()]]);
492
    }
493
}
494