These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Encore\Admin; |
||
4 | |||
5 | use Closure; |
||
6 | use Encore\Admin\Exception\Handler; |
||
7 | use Encore\Admin\Form\Builder; |
||
8 | use Encore\Admin\Form\Field; |
||
9 | use Encore\Admin\Form\HasHooks; |
||
10 | use Encore\Admin\Form\Row; |
||
11 | use Encore\Admin\Form\Tab; |
||
12 | use Illuminate\Contracts\Support\Renderable; |
||
13 | use Illuminate\Database\Eloquent\Model; |
||
14 | use Illuminate\Database\Eloquent\Relations; |
||
15 | use Illuminate\Database\Eloquent\SoftDeletes; |
||
16 | use Illuminate\Http\Request; |
||
17 | use Illuminate\Support\Arr; |
||
18 | use Illuminate\Support\Facades\DB; |
||
19 | use Illuminate\Support\MessageBag; |
||
20 | use Illuminate\Support\Str; |
||
21 | use Illuminate\Validation\Validator; |
||
22 | use Spatie\EloquentSortable\Sortable; |
||
23 | use Symfony\Component\HttpFoundation\Response; |
||
24 | |||
25 | /** |
||
26 | * Class Form. |
||
27 | * |
||
28 | * @method Field\Text text($column, $label = '') |
||
29 | * @method Field\Checkbox checkbox($column, $label = '') |
||
30 | * @method Field\Radio radio($column, $label = '') |
||
31 | * @method Field\Select select($column, $label = '') |
||
32 | * @method Field\MultipleSelect multipleSelect($column, $label = '') |
||
33 | * @method Field\Textarea textarea($column, $label = '') |
||
34 | * @method Field\Hidden hidden($column, $label = '') |
||
35 | * @method Field\Id id($column, $label = '') |
||
36 | * @method Field\Ip ip($column, $label = '') |
||
37 | * @method Field\Url url($column, $label = '') |
||
38 | * @method Field\Color color($column, $label = '') |
||
39 | * @method Field\Email email($column, $label = '') |
||
40 | * @method Field\Mobile mobile($column, $label = '') |
||
41 | * @method Field\Slider slider($column, $label = '') |
||
42 | * @method Field\File file($column, $label = '') |
||
43 | * @method Field\Image image($column, $label = '') |
||
44 | * @method Field\Date date($column, $label = '') |
||
45 | * @method Field\Datetime datetime($column, $label = '') |
||
46 | * @method Field\Time time($column, $label = '') |
||
47 | * @method Field\Year year($column, $label = '') |
||
48 | * @method Field\Month month($column, $label = '') |
||
49 | * @method Field\DateRange dateRange($start, $end, $label = '') |
||
50 | * @method Field\DateTimeRange datetimeRange($start, $end, $label = '') |
||
51 | * @method Field\TimeRange timeRange($start, $end, $label = '') |
||
52 | * @method Field\Number number($column, $label = '') |
||
53 | * @method Field\Currency currency($column, $label = '') |
||
54 | * @method Field\HasMany hasMany($relationName, $label = '', $callback) |
||
55 | * @method Field\SwitchField switch($column, $label = '') |
||
56 | * @method Field\Display display($column, $label = '') |
||
57 | * @method Field\Rate rate($column, $label = '') |
||
58 | * @method Field\Divider divider($title = '') |
||
59 | * @method Field\Password password($column, $label = '') |
||
60 | * @method Field\Decimal decimal($column, $label = '') |
||
61 | * @method Field\Html html($html, $label = '') |
||
62 | * @method Field\Tags tags($column, $label = '') |
||
63 | * @method Field\Icon icon($column, $label = '') |
||
64 | * @method Field\Embeds embeds($column, $label = '', $callback) |
||
65 | * @method Field\MultipleImage multipleImage($column, $label = '') |
||
66 | * @method Field\MultipleFile multipleFile($column, $label = '') |
||
67 | * @method Field\Captcha captcha($column, $label = '') |
||
68 | * @method Field\Listbox listbox($column, $label = '') |
||
69 | * @method Field\Table table($column, $label, $builder) |
||
70 | * @method Field\Timezone timezone($column, $label = '') |
||
71 | * @method Field\KeyValue keyValue($column, $label = '') |
||
72 | * @method Field\ListField list($column, $label = '') |
||
73 | */ |
||
74 | class Form implements Renderable |
||
75 | { |
||
76 | use HasHooks; |
||
77 | |||
78 | /** |
||
79 | * Remove flag in `has many` form. |
||
80 | */ |
||
81 | const REMOVE_FLAG_NAME = '_remove_'; |
||
82 | |||
83 | /** |
||
84 | * Eloquent model of the form. |
||
85 | * |
||
86 | * @var Model |
||
87 | */ |
||
88 | protected $model; |
||
89 | |||
90 | /** |
||
91 | * @var \Illuminate\Validation\Validator |
||
92 | */ |
||
93 | protected $validator; |
||
94 | |||
95 | /** |
||
96 | * @var Builder |
||
97 | */ |
||
98 | protected $builder; |
||
99 | |||
100 | /** |
||
101 | * Data for save to current model from input. |
||
102 | * |
||
103 | * @var array |
||
104 | */ |
||
105 | protected $updates = []; |
||
106 | |||
107 | /** |
||
108 | * Data for save to model's relations from input. |
||
109 | * |
||
110 | * @var array |
||
111 | */ |
||
112 | protected $relations = []; |
||
113 | |||
114 | /** |
||
115 | * Input data. |
||
116 | * |
||
117 | * @var array |
||
118 | */ |
||
119 | protected $inputs = []; |
||
120 | |||
121 | /** |
||
122 | * Available fields. |
||
123 | * |
||
124 | * @var array |
||
125 | */ |
||
126 | public static $availableFields = [ |
||
127 | 'button' => Field\Button::class, |
||
128 | 'checkbox' => Field\Checkbox::class, |
||
129 | 'color' => Field\Color::class, |
||
130 | 'currency' => Field\Currency::class, |
||
131 | 'date' => Field\Date::class, |
||
132 | 'dateRange' => Field\DateRange::class, |
||
133 | 'datetime' => Field\Datetime::class, |
||
134 | 'dateTimeRange' => Field\DatetimeRange::class, |
||
135 | 'datetimeRange' => Field\DatetimeRange::class, |
||
136 | 'decimal' => Field\Decimal::class, |
||
137 | 'display' => Field\Display::class, |
||
138 | 'divider' => Field\Divider::class, |
||
139 | 'embeds' => Field\Embeds::class, |
||
140 | 'email' => Field\Email::class, |
||
141 | 'file' => Field\File::class, |
||
142 | 'hasMany' => Field\HasMany::class, |
||
143 | 'hidden' => Field\Hidden::class, |
||
144 | 'id' => Field\Id::class, |
||
145 | 'image' => Field\Image::class, |
||
146 | 'ip' => Field\Ip::class, |
||
147 | 'mobile' => Field\Mobile::class, |
||
148 | 'month' => Field\Month::class, |
||
149 | 'multipleSelect' => Field\MultipleSelect::class, |
||
150 | 'number' => Field\Number::class, |
||
151 | 'password' => Field\Password::class, |
||
152 | 'radio' => Field\Radio::class, |
||
153 | 'rate' => Field\Rate::class, |
||
154 | 'select' => Field\Select::class, |
||
155 | 'slider' => Field\Slider::class, |
||
156 | 'switch' => Field\SwitchField::class, |
||
157 | 'text' => Field\Text::class, |
||
158 | 'textarea' => Field\Textarea::class, |
||
159 | 'time' => Field\Time::class, |
||
160 | 'timeRange' => Field\TimeRange::class, |
||
161 | 'url' => Field\Url::class, |
||
162 | 'year' => Field\Year::class, |
||
163 | 'html' => Field\Html::class, |
||
164 | 'tags' => Field\Tags::class, |
||
165 | 'icon' => Field\Icon::class, |
||
166 | 'multipleFile' => Field\MultipleFile::class, |
||
167 | 'multipleImage' => Field\MultipleImage::class, |
||
168 | 'captcha' => Field\Captcha::class, |
||
169 | 'listbox' => Field\Listbox::class, |
||
170 | 'table' => Field\Table::class, |
||
171 | 'timezone' => Field\Timezone::class, |
||
172 | 'keyValue' => Field\KeyValue::class, |
||
173 | 'list' => Field\ListField::class, |
||
174 | ]; |
||
175 | |||
176 | /** |
||
177 | * Form field alias. |
||
178 | * |
||
179 | * @var array |
||
180 | */ |
||
181 | public static $fieldAlias = []; |
||
182 | |||
183 | /** |
||
184 | * Ignored saving fields. |
||
185 | * |
||
186 | * @var array |
||
187 | */ |
||
188 | protected $ignored = []; |
||
189 | |||
190 | /** |
||
191 | * Collected field assets. |
||
192 | * |
||
193 | * @var array |
||
194 | */ |
||
195 | protected static $collectedAssets = []; |
||
196 | |||
197 | /** |
||
198 | * @var Form\Tab |
||
199 | */ |
||
200 | protected $tab = null; |
||
201 | |||
202 | /** |
||
203 | * Field rows in form. |
||
204 | * |
||
205 | * @var array |
||
206 | */ |
||
207 | public $rows = []; |
||
208 | |||
209 | /** |
||
210 | * @var bool |
||
211 | */ |
||
212 | protected $isSoftDeletes = false; |
||
213 | |||
214 | /** |
||
215 | * Initialization closure array. |
||
216 | * |
||
217 | * @var []Closure |
||
218 | */ |
||
219 | protected static $initCallbacks; |
||
220 | |||
221 | /** |
||
222 | * Create a new form instance. |
||
223 | * |
||
224 | * @param $model |
||
225 | * @param \Closure $callback |
||
226 | */ |
||
227 | public function __construct($model, Closure $callback = null) |
||
228 | { |
||
229 | $this->model = $model; |
||
230 | |||
231 | $this->builder = new Builder($this); |
||
232 | |||
233 | if ($callback instanceof Closure) { |
||
234 | $callback($this); |
||
235 | } |
||
236 | |||
237 | $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model)); |
||
238 | |||
239 | $this->callInitCallbacks(); |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Initialize with user pre-defined default disables, etc. |
||
244 | * |
||
245 | * @param Closure $callback |
||
246 | */ |
||
247 | public static function init(Closure $callback = null) |
||
248 | { |
||
249 | static::$initCallbacks[] = $callback; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Call the initialization closure array in sequence. |
||
254 | */ |
||
255 | protected function callInitCallbacks() |
||
256 | { |
||
257 | if (empty(static::$initCallbacks)) { |
||
258 | return; |
||
259 | } |
||
260 | |||
261 | foreach (static::$initCallbacks as $callback) { |
||
262 | call_user_func($callback, $this); |
||
263 | } |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * @param Field $field |
||
268 | * |
||
269 | * @return $this |
||
270 | */ |
||
271 | public function pushField(Field $field) |
||
272 | { |
||
273 | $field->setForm($this); |
||
274 | |||
275 | $this->builder->fields()->push($field); |
||
276 | |||
277 | return $this; |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * @return Model |
||
282 | */ |
||
283 | public function model() |
||
284 | { |
||
285 | return $this->model; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * @return Builder |
||
290 | */ |
||
291 | public function builder() |
||
292 | { |
||
293 | return $this->builder; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Generate a edit form. |
||
298 | * |
||
299 | * @param $id |
||
300 | * |
||
301 | * @return $this |
||
302 | */ |
||
303 | public function edit($id) |
||
304 | { |
||
305 | $this->builder->setMode(Builder::MODE_EDIT); |
||
306 | $this->builder->setResourceId($id); |
||
307 | |||
308 | $this->setFieldValue($id); |
||
309 | |||
310 | return $this; |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Use tab to split form. |
||
315 | * |
||
316 | * @param string $title |
||
317 | * @param Closure $content |
||
318 | * |
||
319 | * @return $this |
||
320 | */ |
||
321 | public function tab($title, Closure $content, $active = false) |
||
322 | { |
||
323 | $this->getTab()->append($title, $content, $active); |
||
324 | |||
325 | return $this; |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Get Tab instance. |
||
330 | * |
||
331 | * @return Tab |
||
332 | */ |
||
333 | public function getTab() |
||
334 | { |
||
335 | if (is_null($this->tab)) { |
||
336 | $this->tab = new Tab($this); |
||
337 | } |
||
338 | |||
339 | return $this->tab; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Destroy data entity and remove files. |
||
344 | * |
||
345 | * @param $id |
||
346 | * |
||
347 | * @return mixed |
||
348 | */ |
||
349 | public function destroy($id) |
||
350 | { |
||
351 | try { |
||
352 | if (($ret = $this->callDeleting($id)) instanceof Response) { |
||
353 | return $ret; |
||
354 | } |
||
355 | |||
356 | collect(explode(',', $id))->filter()->each(function ($id) { |
||
357 | $builder = $this->model()->newQuery(); |
||
358 | |||
359 | if ($this->isSoftDeletes) { |
||
360 | $builder = $builder->withTrashed(); |
||
361 | } |
||
362 | |||
363 | $model = $builder->with($this->getRelations())->findOrFail($id); |
||
364 | |||
365 | if ($this->isSoftDeletes && $model->trashed()) { |
||
366 | $this->deleteFiles($model, true); |
||
367 | $model->forceDelete(); |
||
368 | |||
369 | return; |
||
370 | } |
||
371 | |||
372 | $this->deleteFiles($model); |
||
373 | $model->delete(); |
||
374 | }); |
||
375 | |||
376 | if (($ret = $this->callDeleted()) instanceof Response) { |
||
377 | return $ret; |
||
378 | } |
||
379 | |||
380 | $response = [ |
||
381 | 'status' => true, |
||
382 | 'message' => trans('admin.delete_succeeded'), |
||
383 | ]; |
||
384 | } catch (\Exception $exception) { |
||
385 | $response = [ |
||
386 | 'status' => false, |
||
387 | 'message' => $exception->getMessage() ?: trans('admin.delete_failed'), |
||
388 | ]; |
||
389 | } |
||
390 | |||
391 | return response()->json($response); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Remove files in record. |
||
396 | * |
||
397 | * @param Model $model |
||
398 | * @param bool $forceDelete |
||
399 | */ |
||
400 | protected function deleteFiles(Model $model, $forceDelete = false) |
||
401 | { |
||
402 | // If it's a soft delete, the files in the data will not be deleted. |
||
403 | if (!$forceDelete && $this->isSoftDeletes) { |
||
404 | return; |
||
405 | } |
||
406 | |||
407 | $data = $model->toArray(); |
||
408 | |||
409 | $this->builder->fields()->filter(function ($field) { |
||
410 | return $field instanceof Field\File; |
||
411 | })->each(function (Field\File $file) use ($data) { |
||
412 | $file->setOriginal($data); |
||
413 | |||
414 | $file->destroy(); |
||
415 | }); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Store a new record. |
||
420 | * |
||
421 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse |
||
422 | */ |
||
423 | public function store() |
||
424 | { |
||
425 | $data = \request()->all(); |
||
426 | |||
427 | // Handle validation errors. |
||
428 | if ($validationMessages = $this->validationMessages($data)) { |
||
429 | return $this->responseValidationError($validationMessages); |
||
430 | } |
||
431 | |||
432 | if (($response = $this->prepare($data)) instanceof Response) { |
||
433 | return $response; |
||
434 | } |
||
435 | |||
436 | View Code Duplication | DB::transaction(function () { |
|
437 | $inserts = $this->prepareInsert($this->updates); |
||
438 | |||
439 | foreach ($inserts as $column => $value) { |
||
440 | $this->model->setAttribute($column, $value); |
||
441 | } |
||
442 | |||
443 | $this->model->save(); |
||
444 | |||
445 | $this->updateRelation($this->relations); |
||
446 | }); |
||
447 | |||
448 | if (($response = $this->callSaved()) instanceof Response) { |
||
449 | return $response; |
||
450 | } |
||
451 | |||
452 | if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) { |
||
453 | return $response; |
||
454 | } |
||
455 | |||
456 | return $this->redirectAfterStore(); |
||
457 | } |
||
458 | |||
459 | /** |
||
460 | * @param MessageBag $message |
||
461 | * |
||
462 | * @return $this|\Illuminate\Http\JsonResponse |
||
463 | */ |
||
464 | protected function responseValidationError(MessageBag $message) |
||
465 | { |
||
466 | if (\request()->ajax() && !\request()->pjax()) { |
||
467 | return response()->json([ |
||
468 | 'status' => false, |
||
469 | 'validation' => $message, |
||
470 | 'message' => $message->first(), |
||
471 | ]); |
||
472 | } |
||
473 | |||
474 | return back()->withInput()->withErrors($message); |
||
0 ignored issues
–
show
|
|||
475 | } |
||
476 | |||
477 | /** |
||
478 | * Get ajax response. |
||
479 | * |
||
480 | * @param string $message |
||
481 | * |
||
482 | * @return bool|\Illuminate\Http\JsonResponse |
||
483 | */ |
||
484 | protected function ajaxResponse($message) |
||
485 | { |
||
486 | $request = Request::capture(); |
||
487 | |||
488 | // ajax but not pjax |
||
489 | if ($request->ajax() && !$request->pjax()) { |
||
490 | return response()->json([ |
||
491 | 'status' => true, |
||
492 | 'message' => $message, |
||
493 | ]); |
||
494 | } |
||
495 | |||
496 | return false; |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * Prepare input data for insert or update. |
||
501 | * |
||
502 | * @param array $data |
||
503 | * |
||
504 | * @return mixed |
||
505 | */ |
||
506 | protected function prepare($data = []) |
||
507 | { |
||
508 | if (($response = $this->callSubmitted()) instanceof Response) { |
||
509 | return $response; |
||
510 | } |
||
511 | |||
512 | $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs); |
||
513 | |||
514 | if (($response = $this->callSaving()) instanceof Response) { |
||
515 | return $response; |
||
516 | } |
||
517 | |||
518 | $this->relations = $this->getRelationInputs($this->inputs); |
||
519 | |||
520 | $this->updates = Arr::except($this->inputs, array_keys($this->relations)); |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * Remove ignored fields from input. |
||
525 | * |
||
526 | * @param array $input |
||
527 | * |
||
528 | * @return array |
||
529 | */ |
||
530 | protected function removeIgnoredFields($input) |
||
531 | { |
||
532 | Arr::forget($input, $this->ignored); |
||
533 | |||
534 | return $input; |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * Get inputs for relations. |
||
539 | * |
||
540 | * @param array $inputs |
||
541 | * |
||
542 | * @return array |
||
543 | */ |
||
544 | protected function getRelationInputs($inputs = []) |
||
545 | { |
||
546 | $relations = []; |
||
547 | |||
548 | foreach ($inputs as $column => $value) { |
||
549 | if (!method_exists($this->model, $column)) { |
||
550 | continue; |
||
551 | } |
||
552 | |||
553 | $relation = call_user_func([$this->model, $column]); |
||
554 | |||
555 | if ($relation instanceof Relations\Relation) { |
||
556 | $relations[$column] = $value; |
||
557 | } |
||
558 | } |
||
559 | |||
560 | return $relations; |
||
561 | } |
||
562 | |||
563 | /** |
||
564 | * Handle update. |
||
565 | * |
||
566 | * @param int $id |
||
567 | * @param null $data |
||
568 | * |
||
569 | * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response |
||
570 | */ |
||
571 | public function update($id, $data = null) |
||
572 | { |
||
573 | $data = ($data) ?: request()->all(); |
||
574 | |||
575 | $isEditable = $this->isEditable($data); |
||
576 | |||
577 | if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) { |
||
578 | return $data; |
||
579 | } |
||
580 | |||
581 | /* @var Model $this->model */ |
||
582 | $builder = $this->model(); |
||
583 | |||
584 | if ($this->isSoftDeletes) { |
||
585 | $builder = $builder->withTrashed(); |
||
586 | } |
||
587 | |||
588 | $this->model = $builder->with($this->getRelations())->findOrFail($id); |
||
589 | |||
590 | $this->setFieldOriginalValue(); |
||
591 | |||
592 | // Handle validation errors. |
||
593 | if ($validationMessages = $this->validationMessages($data)) { |
||
594 | if (!$isEditable) { |
||
595 | return back()->withInput()->withErrors($validationMessages); |
||
596 | } |
||
597 | |||
598 | return response()->json(['errors' => Arr::dot($validationMessages->getMessages())], 422); |
||
599 | } |
||
600 | |||
601 | if (($response = $this->prepare($data)) instanceof Response) { |
||
602 | return $response; |
||
603 | } |
||
604 | |||
605 | View Code Duplication | DB::transaction(function () { |
|
606 | $updates = $this->prepareUpdate($this->updates); |
||
607 | |||
608 | foreach ($updates as $column => $value) { |
||
609 | /* @var Model $this->model */ |
||
610 | $this->model->setAttribute($column, $value); |
||
611 | } |
||
612 | |||
613 | $this->model->save(); |
||
614 | |||
615 | $this->updateRelation($this->relations); |
||
616 | }); |
||
617 | |||
618 | if (($result = $this->callSaved()) instanceof Response) { |
||
619 | return $result; |
||
620 | } |
||
621 | |||
622 | if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) { |
||
623 | return $response; |
||
624 | } |
||
625 | |||
626 | return $this->redirectAfterUpdate($id); |
||
627 | } |
||
628 | |||
629 | /** |
||
630 | * Get RedirectResponse after store. |
||
631 | * |
||
632 | * @return \Illuminate\Http\RedirectResponse |
||
633 | */ |
||
634 | protected function redirectAfterStore() |
||
635 | { |
||
636 | $resourcesPath = $this->resource(0); |
||
637 | |||
638 | $key = $this->model->getKey(); |
||
639 | |||
640 | return $this->redirectAfterSaving($resourcesPath, $key); |
||
641 | } |
||
642 | |||
643 | /** |
||
644 | * Get RedirectResponse after update. |
||
645 | * |
||
646 | * @param mixed $key |
||
647 | * |
||
648 | * @return \Illuminate\Http\RedirectResponse |
||
649 | */ |
||
650 | protected function redirectAfterUpdate($key) |
||
651 | { |
||
652 | $resourcesPath = $this->resource(-1); |
||
653 | |||
654 | return $this->redirectAfterSaving($resourcesPath, $key); |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Get RedirectResponse after data saving. |
||
659 | * |
||
660 | * @param string $resourcesPath |
||
661 | * @param string $key |
||
662 | * |
||
663 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector |
||
664 | */ |
||
665 | protected function redirectAfterSaving($resourcesPath, $key) |
||
666 | { |
||
667 | if (request('after-save') == 1) { |
||
668 | // continue editing |
||
669 | $url = rtrim($resourcesPath, '/')."/{$key}/edit"; |
||
670 | } elseif (request('after-save') == 2) { |
||
671 | // continue creating |
||
672 | $url = rtrim($resourcesPath, '/').'/create'; |
||
673 | } elseif (request('after-save') == 3) { |
||
674 | // view resource |
||
675 | $url = rtrim($resourcesPath, '/')."/{$key}"; |
||
676 | } else { |
||
677 | $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath; |
||
678 | } |
||
679 | |||
680 | admin_toastr(trans('admin.save_succeeded')); |
||
681 | |||
682 | return redirect($url); |
||
683 | } |
||
684 | |||
685 | /** |
||
686 | * Check if request is from editable. |
||
687 | * |
||
688 | * @param array $input |
||
689 | * |
||
690 | * @return bool |
||
691 | */ |
||
692 | protected function isEditable(array $input = []) |
||
693 | { |
||
694 | return array_key_exists('_editable', $input); |
||
695 | } |
||
696 | |||
697 | /** |
||
698 | * Handle updates for single column. |
||
699 | * |
||
700 | * @param int $id |
||
701 | * @param array $data |
||
702 | * |
||
703 | * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response |
||
704 | */ |
||
705 | protected function handleColumnUpdates($id, $data) |
||
706 | { |
||
707 | $data = $this->handleEditable($data); |
||
708 | |||
709 | $data = $this->handleFileDelete($data); |
||
710 | |||
711 | $data = $this->handleFileSort($data); |
||
712 | |||
713 | if ($this->handleOrderable($id, $data)) { |
||
714 | return response([ |
||
715 | 'status' => true, |
||
716 | 'message' => trans('admin.update_succeeded'), |
||
717 | ]); |
||
718 | } |
||
719 | |||
720 | return $data; |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * Handle editable update. |
||
725 | * |
||
726 | * @param array $input |
||
727 | * |
||
728 | * @return array |
||
729 | */ |
||
730 | protected function handleEditable(array $input = []) |
||
731 | { |
||
732 | if (array_key_exists('_editable', $input)) { |
||
733 | $name = $input['name']; |
||
734 | $value = $input['value']; |
||
735 | |||
736 | Arr::forget($input, ['pk', 'value', 'name']); |
||
737 | Arr::set($input, $name, $value); |
||
738 | } |
||
739 | |||
740 | return $input; |
||
741 | } |
||
742 | |||
743 | /** |
||
744 | * @param array $input |
||
745 | * |
||
746 | * @return array |
||
747 | */ |
||
748 | protected function handleFileDelete(array $input = []) |
||
749 | { |
||
750 | if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) { |
||
751 | $input[Field::FILE_DELETE_FLAG] = $input['key']; |
||
752 | unset($input['key']); |
||
753 | } |
||
754 | |||
755 | request()->replace($input); |
||
756 | |||
757 | return $input; |
||
758 | } |
||
759 | |||
760 | /** |
||
761 | * @param array $input |
||
762 | * |
||
763 | * @return array |
||
764 | */ |
||
765 | protected function handleFileSort(array $input = []) |
||
766 | { |
||
767 | if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) { |
||
768 | return $input; |
||
769 | } |
||
770 | |||
771 | $sorts = array_filter($input[Field::FILE_SORT_FLAG]); |
||
772 | |||
773 | if (empty($sorts)) { |
||
774 | return $input; |
||
775 | } |
||
776 | |||
777 | foreach ($sorts as $column => $order) { |
||
778 | $input[$column] = $order; |
||
779 | } |
||
780 | |||
781 | request()->replace($input); |
||
782 | |||
783 | return $input; |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * Handle orderable update. |
||
788 | * |
||
789 | * @param int $id |
||
790 | * @param array $input |
||
791 | * |
||
792 | * @return bool |
||
793 | */ |
||
794 | protected function handleOrderable($id, array $input = []) |
||
795 | { |
||
796 | if (array_key_exists('_orderable', $input)) { |
||
797 | $model = $this->model->find($id); |
||
798 | |||
799 | if ($model instanceof Sortable) { |
||
800 | $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown(); |
||
801 | |||
802 | return true; |
||
803 | } |
||
804 | } |
||
805 | |||
806 | return false; |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Update relation data. |
||
811 | * |
||
812 | * @param array $relationsData |
||
813 | * |
||
814 | * @return void |
||
815 | */ |
||
816 | protected function updateRelation($relationsData) |
||
817 | { |
||
818 | foreach ($relationsData as $name => $values) { |
||
819 | if (!method_exists($this->model, $name)) { |
||
820 | continue; |
||
821 | } |
||
822 | |||
823 | $relation = $this->model->$name(); |
||
824 | |||
825 | $oneToOneRelation = $relation instanceof Relations\HasOne |
||
826 | || $relation instanceof Relations\MorphOne |
||
827 | || $relation instanceof Relations\BelongsTo; |
||
828 | |||
829 | $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation); |
||
830 | |||
831 | if (empty($prepared)) { |
||
832 | continue; |
||
833 | } |
||
834 | |||
835 | switch (true) { |
||
836 | case $relation instanceof Relations\BelongsToMany: |
||
837 | case $relation instanceof Relations\MorphToMany: |
||
838 | if (isset($prepared[$name])) { |
||
839 | $relation->sync($prepared[$name]); |
||
840 | } |
||
841 | break; |
||
842 | case $relation instanceof Relations\HasOne: |
||
843 | |||
844 | $related = $this->model->$name; |
||
845 | |||
846 | // if related is empty |
||
847 | if (is_null($related)) { |
||
848 | $related = $relation->getRelated(); |
||
849 | $qualifiedParentKeyName = $relation->getQualifiedParentKeyName(); |
||
850 | $localKey = Arr::last(explode('.', $qualifiedParentKeyName)); |
||
851 | $related->{$relation->getForeignKeyName()} = $this->model->{$localKey}; |
||
852 | } |
||
853 | |||
854 | foreach ($prepared[$name] as $column => $value) { |
||
855 | $related->setAttribute($column, $value); |
||
856 | } |
||
857 | |||
858 | $related->save(); |
||
859 | break; |
||
860 | case $relation instanceof Relations\BelongsTo: |
||
861 | case $relation instanceof Relations\MorphTo: |
||
862 | |||
863 | $parent = $this->model->$name; |
||
864 | |||
865 | // if related is empty |
||
866 | if (is_null($parent)) { |
||
867 | $parent = $relation->getRelated(); |
||
868 | } |
||
869 | |||
870 | foreach ($prepared[$name] as $column => $value) { |
||
871 | $parent->setAttribute($column, $value); |
||
872 | } |
||
873 | |||
874 | $parent->save(); |
||
875 | |||
876 | // When in creating, associate two models |
||
877 | $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName'; |
||
878 | if (!$this->model->{$relation->{$foreignKeyMethod}()}) { |
||
879 | $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey(); |
||
880 | |||
881 | $this->model->save(); |
||
882 | } |
||
883 | |||
884 | break; |
||
885 | case $relation instanceof Relations\MorphOne: |
||
886 | $related = $this->model->$name; |
||
887 | if (is_null($related)) { |
||
888 | $related = $relation->make(); |
||
889 | } |
||
890 | foreach ($prepared[$name] as $column => $value) { |
||
891 | $related->setAttribute($column, $value); |
||
892 | } |
||
893 | $related->save(); |
||
894 | break; |
||
895 | case $relation instanceof Relations\HasMany: |
||
896 | case $relation instanceof Relations\MorphMany: |
||
897 | |||
898 | foreach ($prepared[$name] as $related) { |
||
899 | /** @var Relations\Relation $relation */ |
||
900 | $relation = $this->model()->$name(); |
||
901 | |||
902 | $keyName = $relation->getRelated()->getKeyName(); |
||
903 | |||
904 | $instance = $relation->findOrNew(Arr::get($related, $keyName)); |
||
905 | |||
906 | if ($related[static::REMOVE_FLAG_NAME] == 1) { |
||
907 | $instance->delete(); |
||
908 | |||
909 | continue; |
||
910 | } |
||
911 | |||
912 | Arr::forget($related, static::REMOVE_FLAG_NAME); |
||
913 | |||
914 | $instance->fill($related); |
||
915 | |||
916 | $instance->save(); |
||
917 | } |
||
918 | |||
919 | break; |
||
920 | } |
||
921 | } |
||
922 | } |
||
923 | |||
924 | /** |
||
925 | * Prepare input data for update. |
||
926 | * |
||
927 | * @param array $updates |
||
928 | * @param bool $oneToOneRelation If column is one-to-one relation. |
||
929 | * |
||
930 | * @return array |
||
931 | */ |
||
932 | protected function prepareUpdate(array $updates, $oneToOneRelation = false) |
||
933 | { |
||
934 | $prepared = []; |
||
935 | |||
936 | /** @var Field $field */ |
||
937 | foreach ($this->builder->fields() as $field) { |
||
938 | $columns = $field->column(); |
||
939 | |||
940 | // If column not in input array data, then continue. |
||
941 | if (!Arr::has($updates, $columns)) { |
||
942 | continue; |
||
943 | } |
||
944 | |||
945 | if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) { |
||
946 | continue; |
||
947 | } |
||
948 | |||
949 | $value = $this->getDataByColumn($updates, $columns); |
||
950 | |||
951 | $value = $field->prepare($value); |
||
952 | |||
953 | View Code Duplication | if (is_array($columns)) { |
|
954 | foreach ($columns as $name => $column) { |
||
955 | Arr::set($prepared, $column, $value[$name]); |
||
956 | } |
||
957 | } elseif (is_string($columns)) { |
||
958 | Arr::set($prepared, $columns, $value); |
||
959 | } |
||
960 | } |
||
961 | |||
962 | return $prepared; |
||
963 | } |
||
964 | |||
965 | /** |
||
966 | * @param string|array $columns |
||
967 | * @param bool $containsDot |
||
968 | * |
||
969 | * @return bool |
||
970 | */ |
||
971 | protected function isInvalidColumn($columns, $containsDot = false) |
||
972 | { |
||
973 | foreach ((array) $columns as $column) { |
||
974 | if ((!$containsDot && Str::contains($column, '.')) || |
||
975 | ($containsDot && !Str::contains($column, '.'))) { |
||
976 | return true; |
||
977 | } |
||
978 | } |
||
979 | |||
980 | return false; |
||
981 | } |
||
982 | |||
983 | /** |
||
984 | * Prepare input data for insert. |
||
985 | * |
||
986 | * @param $inserts |
||
987 | * |
||
988 | * @return array |
||
989 | */ |
||
990 | protected function prepareInsert($inserts) |
||
991 | { |
||
992 | if ($this->isHasOneRelation($inserts)) { |
||
993 | $inserts = Arr::dot($inserts); |
||
994 | } |
||
995 | |||
996 | foreach ($inserts as $column => $value) { |
||
997 | if (is_null($field = $this->getFieldByColumn($column))) { |
||
998 | unset($inserts[$column]); |
||
999 | continue; |
||
1000 | } |
||
1001 | |||
1002 | $inserts[$column] = $field->prepare($value); |
||
1003 | } |
||
1004 | |||
1005 | $prepared = []; |
||
1006 | |||
1007 | foreach ($inserts as $key => $value) { |
||
1008 | Arr::set($prepared, $key, $value); |
||
1009 | } |
||
1010 | |||
1011 | return $prepared; |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * Is input data is has-one relation. |
||
1016 | * |
||
1017 | * @param array $inserts |
||
1018 | * |
||
1019 | * @return bool |
||
1020 | */ |
||
1021 | protected function isHasOneRelation($inserts) |
||
1022 | { |
||
1023 | $first = current($inserts); |
||
1024 | |||
1025 | if (!is_array($first)) { |
||
1026 | return false; |
||
1027 | } |
||
1028 | |||
1029 | if (is_array(current($first))) { |
||
1030 | return false; |
||
1031 | } |
||
1032 | |||
1033 | return Arr::isAssoc($first); |
||
1034 | } |
||
1035 | |||
1036 | /** |
||
1037 | * Ignore fields to save. |
||
1038 | * |
||
1039 | * @param string|array $fields |
||
1040 | * |
||
1041 | * @return $this |
||
1042 | */ |
||
1043 | public function ignore($fields) |
||
1044 | { |
||
1045 | $this->ignored = array_merge($this->ignored, (array) $fields); |
||
1046 | |||
1047 | return $this; |
||
1048 | } |
||
1049 | |||
1050 | /** |
||
1051 | * @param array $data |
||
1052 | * @param string|array $columns |
||
1053 | * |
||
1054 | * @return array|mixed |
||
1055 | */ |
||
1056 | View Code Duplication | protected function getDataByColumn($data, $columns) |
|
1057 | { |
||
1058 | if (is_string($columns)) { |
||
1059 | return Arr::get($data, $columns); |
||
1060 | } |
||
1061 | |||
1062 | if (is_array($columns)) { |
||
1063 | $value = []; |
||
1064 | foreach ($columns as $name => $column) { |
||
1065 | if (!Arr::has($data, $column)) { |
||
1066 | continue; |
||
1067 | } |
||
1068 | $value[$name] = Arr::get($data, $column); |
||
1069 | } |
||
1070 | |||
1071 | return $value; |
||
1072 | } |
||
1073 | } |
||
1074 | |||
1075 | /** |
||
1076 | * Find field object by column. |
||
1077 | * |
||
1078 | * @param $column |
||
1079 | * |
||
1080 | * @return mixed |
||
1081 | */ |
||
1082 | protected function getFieldByColumn($column) |
||
1083 | { |
||
1084 | return $this->builder->fields()->first( |
||
1085 | function (Field $field) use ($column) { |
||
1086 | if (is_array($field->column())) { |
||
1087 | return in_array($column, $field->column()); |
||
1088 | } |
||
1089 | |||
1090 | return $field->column() == $column; |
||
1091 | } |
||
1092 | ); |
||
1093 | } |
||
1094 | |||
1095 | /** |
||
1096 | * Set original data for each field. |
||
1097 | * |
||
1098 | * @return void |
||
1099 | */ |
||
1100 | protected function setFieldOriginalValue() |
||
1101 | { |
||
1102 | // static::doNotSnakeAttributes($this->model); |
||
1103 | |||
1104 | $values = $this->model->toArray(); |
||
1105 | |||
1106 | $this->builder->fields()->each(function (Field $field) use ($values) { |
||
1107 | $field->setOriginal($values); |
||
1108 | }); |
||
1109 | } |
||
1110 | |||
1111 | /** |
||
1112 | * Set all fields value in form. |
||
1113 | * |
||
1114 | * @param $id |
||
1115 | * |
||
1116 | * @return void |
||
1117 | */ |
||
1118 | protected function setFieldValue($id) |
||
1119 | { |
||
1120 | $relations = $this->getRelations(); |
||
1121 | |||
1122 | $builder = $this->model(); |
||
1123 | |||
1124 | if ($this->isSoftDeletes) { |
||
1125 | $builder = $builder->withTrashed(); |
||
1126 | } |
||
1127 | |||
1128 | $this->model = $builder->with($relations)->findOrFail($id); |
||
1129 | |||
1130 | $this->callEditing(); |
||
1131 | |||
1132 | // static::doNotSnakeAttributes($this->model); |
||
1133 | |||
1134 | $data = $this->model->toArray(); |
||
1135 | |||
1136 | $this->builder->fields()->each(function (Field $field) use ($data) { |
||
1137 | if (!in_array($field->column(), $this->ignored)) { |
||
1138 | $field->fill($data); |
||
1139 | } |
||
1140 | }); |
||
1141 | } |
||
1142 | |||
1143 | /** |
||
1144 | * Add a fieldset to form. |
||
1145 | * |
||
1146 | * @param string $title |
||
1147 | * @param Closure $setCallback |
||
1148 | * |
||
1149 | * @return Field\Fieldset |
||
1150 | */ |
||
1151 | View Code Duplication | public function fieldset(string $title, Closure $setCallback) |
|
1152 | { |
||
1153 | $fieldset = new Field\Fieldset(); |
||
1154 | |||
1155 | $this->html($fieldset->start($title))->plain(); |
||
1156 | |||
1157 | $setCallback($this); |
||
1158 | |||
1159 | $this->html($fieldset->end())->plain(); |
||
1160 | |||
1161 | return $fieldset; |
||
1162 | } |
||
1163 | |||
1164 | /** |
||
1165 | * Don't snake case attributes. |
||
1166 | * |
||
1167 | * @param Model $model |
||
1168 | * |
||
1169 | * @return void |
||
1170 | */ |
||
1171 | protected static function doNotSnakeAttributes(Model $model) |
||
1172 | { |
||
1173 | $class = get_class($model); |
||
1174 | |||
1175 | $class::$snakeAttributes = false; |
||
1176 | } |
||
1177 | |||
1178 | /** |
||
1179 | * Get validation messages. |
||
1180 | * |
||
1181 | * @param array $input |
||
1182 | * |
||
1183 | * @return MessageBag|bool |
||
1184 | */ |
||
1185 | public function validationMessages($input) |
||
1186 | { |
||
1187 | $failedValidators = []; |
||
1188 | |||
1189 | /** @var Field $field */ |
||
1190 | View Code Duplication | foreach ($this->builder->fields() as $field) { |
|
1191 | if (!$validator = $field->getValidator($input)) { |
||
1192 | continue; |
||
1193 | } |
||
1194 | |||
1195 | if (($validator instanceof Validator) && !$validator->passes()) { |
||
1196 | $failedValidators[] = $validator; |
||
1197 | } |
||
1198 | } |
||
1199 | |||
1200 | $message = $this->mergeValidationMessages($failedValidators); |
||
1201 | |||
1202 | return $message->any() ? $message : false; |
||
1203 | } |
||
1204 | |||
1205 | /** |
||
1206 | * Merge validation messages from input validators. |
||
1207 | * |
||
1208 | * @param \Illuminate\Validation\Validator[] $validators |
||
1209 | * |
||
1210 | * @return MessageBag |
||
1211 | */ |
||
1212 | protected function mergeValidationMessages($validators) |
||
1213 | { |
||
1214 | $messageBag = new MessageBag(); |
||
1215 | |||
1216 | foreach ($validators as $validator) { |
||
1217 | $messageBag = $messageBag->merge($validator->messages()); |
||
1218 | } |
||
1219 | |||
1220 | return $messageBag; |
||
1221 | } |
||
1222 | |||
1223 | /** |
||
1224 | * Get all relations of model from callable. |
||
1225 | * |
||
1226 | * @return array |
||
1227 | */ |
||
1228 | public function getRelations() |
||
1229 | { |
||
1230 | $relations = $columns = []; |
||
1231 | |||
1232 | /** @var Field $field */ |
||
1233 | foreach ($this->builder->fields() as $field) { |
||
1234 | $columns[] = $field->column(); |
||
1235 | } |
||
1236 | |||
1237 | foreach (Arr::flatten($columns) as $column) { |
||
1238 | if (Str::contains($column, '.')) { |
||
1239 | list($relation) = explode('.', $column); |
||
1240 | |||
1241 | if (method_exists($this->model, $relation) && |
||
1242 | $this->model->$relation() instanceof Relations\Relation |
||
1243 | ) { |
||
1244 | $relations[] = $relation; |
||
1245 | } |
||
1246 | } elseif (method_exists($this->model, $column) && |
||
1247 | !method_exists(Model::class, $column) |
||
1248 | ) { |
||
1249 | $relations[] = $column; |
||
1250 | } |
||
1251 | } |
||
1252 | |||
1253 | return array_unique($relations); |
||
1254 | } |
||
1255 | |||
1256 | /** |
||
1257 | * Set action for form. |
||
1258 | * |
||
1259 | * @param string $action |
||
1260 | * |
||
1261 | * @return $this |
||
1262 | */ |
||
1263 | public function setAction($action) |
||
1264 | { |
||
1265 | $this->builder()->setAction($action); |
||
1266 | |||
1267 | return $this; |
||
1268 | } |
||
1269 | |||
1270 | /** |
||
1271 | * Set field and label width in current form. |
||
1272 | * |
||
1273 | * @param int $fieldWidth |
||
1274 | * @param int $labelWidth |
||
1275 | * |
||
1276 | * @return $this |
||
1277 | */ |
||
1278 | public function setWidth($fieldWidth = 8, $labelWidth = 2) |
||
1279 | { |
||
1280 | $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) { |
||
1281 | /* @var Field $field */ |
||
1282 | $field->setWidth($fieldWidth, $labelWidth); |
||
1283 | }); |
||
1284 | |||
1285 | $this->builder()->setWidth($fieldWidth, $labelWidth); |
||
1286 | |||
1287 | return $this; |
||
1288 | } |
||
1289 | |||
1290 | /** |
||
1291 | * Set view for form. |
||
1292 | * |
||
1293 | * @param string $view |
||
1294 | * |
||
1295 | * @return $this |
||
1296 | */ |
||
1297 | public function setView($view) |
||
1298 | { |
||
1299 | $this->builder()->setView($view); |
||
1300 | |||
1301 | return $this; |
||
1302 | } |
||
1303 | |||
1304 | /** |
||
1305 | * Set title for form. |
||
1306 | * |
||
1307 | * @param string $title |
||
1308 | * |
||
1309 | * @return $this |
||
1310 | */ |
||
1311 | public function setTitle($title = '') |
||
1312 | { |
||
1313 | $this->builder()->setTitle($title); |
||
1314 | |||
1315 | return $this; |
||
1316 | } |
||
1317 | |||
1318 | /** |
||
1319 | * Add a row in form. |
||
1320 | * |
||
1321 | * @param Closure $callback |
||
1322 | * |
||
1323 | * @return $this |
||
1324 | */ |
||
1325 | public function row(Closure $callback) |
||
1326 | { |
||
1327 | $this->rows[] = new Row($callback, $this); |
||
1328 | |||
1329 | return $this; |
||
1330 | } |
||
1331 | |||
1332 | /** |
||
1333 | * Tools setting for form. |
||
1334 | * |
||
1335 | * @param Closure $callback |
||
1336 | */ |
||
1337 | public function tools(Closure $callback) |
||
1338 | { |
||
1339 | $callback->call($this, $this->builder->getTools()); |
||
1340 | } |
||
1341 | |||
1342 | /** |
||
1343 | * @param Closure|null $callback |
||
1344 | * |
||
1345 | * @return Form\Tools |
||
1346 | */ |
||
1347 | public function header(Closure $callback = null) |
||
1348 | { |
||
1349 | if (func_num_args() == 0) { |
||
1350 | return $this->builder->getTools(); |
||
1351 | } |
||
1352 | |||
1353 | $callback->call($this, $this->builder->getTools()); |
||
1354 | } |
||
1355 | |||
1356 | /** |
||
1357 | * Indicates if current form page is creating. |
||
1358 | * |
||
1359 | * @return bool |
||
1360 | */ |
||
1361 | public function isCreating() |
||
1362 | { |
||
1363 | return Str::endsWith(\request()->route()->getName(), '.create'); |
||
1364 | } |
||
1365 | |||
1366 | /** |
||
1367 | * Indicates if current form page is editing. |
||
1368 | * |
||
1369 | * @return bool |
||
1370 | */ |
||
1371 | public function isEditing() |
||
1372 | { |
||
1373 | return Str::endsWith(\request()->route()->getName(), '.edit'); |
||
1374 | } |
||
1375 | |||
1376 | /** |
||
1377 | * Disable form submit. |
||
1378 | * |
||
1379 | * @param bool $disable |
||
1380 | * |
||
1381 | * @return $this |
||
1382 | * |
||
1383 | * @deprecated |
||
1384 | */ |
||
1385 | public function disableSubmit(bool $disable = true) |
||
1386 | { |
||
1387 | $this->builder()->getFooter()->disableSubmit($disable); |
||
1388 | |||
1389 | return $this; |
||
1390 | } |
||
1391 | |||
1392 | /** |
||
1393 | * Disable form reset. |
||
1394 | * |
||
1395 | * @param bool $disable |
||
1396 | * |
||
1397 | * @return $this |
||
1398 | * |
||
1399 | * @deprecated |
||
1400 | */ |
||
1401 | public function disableReset(bool $disable = true) |
||
1402 | { |
||
1403 | $this->builder()->getFooter()->disableReset($disable); |
||
1404 | |||
1405 | return $this; |
||
1406 | } |
||
1407 | |||
1408 | /** |
||
1409 | * Disable View Checkbox on footer. |
||
1410 | * |
||
1411 | * @param bool $disable |
||
1412 | * |
||
1413 | * @return $this |
||
1414 | */ |
||
1415 | public function disableViewCheck(bool $disable = true) |
||
1416 | { |
||
1417 | $this->builder()->getFooter()->disableViewCheck($disable); |
||
1418 | |||
1419 | return $this; |
||
1420 | } |
||
1421 | |||
1422 | /** |
||
1423 | * Disable Editing Checkbox on footer. |
||
1424 | * |
||
1425 | * @param bool $disable |
||
1426 | * |
||
1427 | * @return $this |
||
1428 | */ |
||
1429 | public function disableEditingCheck(bool $disable = true) |
||
1430 | { |
||
1431 | $this->builder()->getFooter()->disableEditingCheck($disable); |
||
1432 | |||
1433 | return $this; |
||
1434 | } |
||
1435 | |||
1436 | /** |
||
1437 | * Disable Creating Checkbox on footer. |
||
1438 | * |
||
1439 | * @param bool $disable |
||
1440 | * |
||
1441 | * @return $this |
||
1442 | */ |
||
1443 | public function disableCreatingCheck(bool $disable = true) |
||
1444 | { |
||
1445 | $this->builder()->getFooter()->disableCreatingCheck($disable); |
||
1446 | |||
1447 | return $this; |
||
1448 | } |
||
1449 | |||
1450 | /** |
||
1451 | * Footer setting for form. |
||
1452 | * |
||
1453 | * @param Closure $callback |
||
1454 | */ |
||
1455 | public function footer(Closure $callback = null) |
||
1456 | { |
||
1457 | if (func_num_args() == 0) { |
||
1458 | return $this->builder()->getFooter(); |
||
1459 | } |
||
1460 | |||
1461 | call_user_func($callback, $this->builder()->getFooter()); |
||
1462 | } |
||
1463 | |||
1464 | /** |
||
1465 | * Get current resource route url. |
||
1466 | * |
||
1467 | * @param int $slice |
||
1468 | * |
||
1469 | * @return string |
||
1470 | */ |
||
1471 | public function resource($slice = -2) |
||
1472 | { |
||
1473 | $segments = explode('/', trim(\request()->getUri(), '/')); |
||
1474 | |||
1475 | if ($slice != 0) { |
||
1476 | $segments = array_slice($segments, 0, $slice); |
||
1477 | } |
||
1478 | |||
1479 | return implode('/', $segments); |
||
1480 | } |
||
1481 | |||
1482 | /** |
||
1483 | * Render the form contents. |
||
1484 | * |
||
1485 | * @return string |
||
1486 | */ |
||
1487 | public function render() |
||
1488 | { |
||
1489 | try { |
||
1490 | return $this->builder->render(); |
||
1491 | } catch (\Exception $e) { |
||
1492 | return Handler::renderException($e); |
||
1493 | } |
||
1494 | } |
||
1495 | |||
1496 | /** |
||
1497 | * Get or set input data. |
||
1498 | * |
||
1499 | * @param string $key |
||
1500 | * @param null $value |
||
1501 | * |
||
1502 | * @return array|mixed |
||
1503 | */ |
||
1504 | public function input($key, $value = null) |
||
1505 | { |
||
1506 | if (is_null($value)) { |
||
1507 | return Arr::get($this->inputs, $key); |
||
1508 | } |
||
1509 | |||
1510 | return Arr::set($this->inputs, $key, $value); |
||
1511 | } |
||
1512 | |||
1513 | /** |
||
1514 | * Register custom field. |
||
1515 | * |
||
1516 | * @param string $abstract |
||
1517 | * @param string $class |
||
1518 | * |
||
1519 | * @return void |
||
1520 | */ |
||
1521 | public static function extend($abstract, $class) |
||
1522 | { |
||
1523 | static::$availableFields[$abstract] = $class; |
||
1524 | } |
||
1525 | |||
1526 | /** |
||
1527 | * Set form field alias. |
||
1528 | * |
||
1529 | * @param string $field |
||
1530 | * @param string $alias |
||
1531 | * |
||
1532 | * @return void |
||
1533 | */ |
||
1534 | public static function alias($field, $alias) |
||
1535 | { |
||
1536 | static::$fieldAlias[$alias] = $field; |
||
1537 | } |
||
1538 | |||
1539 | /** |
||
1540 | * Remove registered field. |
||
1541 | * |
||
1542 | * @param array|string $abstract |
||
1543 | */ |
||
1544 | public static function forget($abstract) |
||
1545 | { |
||
1546 | Arr::forget(static::$availableFields, $abstract); |
||
1547 | } |
||
1548 | |||
1549 | /** |
||
1550 | * Find field class. |
||
1551 | * |
||
1552 | * @param string $method |
||
1553 | * |
||
1554 | * @return bool|mixed |
||
1555 | */ |
||
1556 | public static function findFieldClass($method) |
||
1557 | { |
||
1558 | // If alias exists. |
||
1559 | if (isset(static::$fieldAlias[$method])) { |
||
1560 | $method = static::$fieldAlias[$method]; |
||
1561 | } |
||
1562 | |||
1563 | $class = Arr::get(static::$availableFields, $method); |
||
1564 | |||
1565 | if (class_exists($class)) { |
||
1566 | return $class; |
||
1567 | } |
||
1568 | |||
1569 | return false; |
||
1570 | } |
||
1571 | |||
1572 | /** |
||
1573 | * Collect assets required by registered field. |
||
1574 | * |
||
1575 | * @return array |
||
1576 | */ |
||
1577 | public static function collectFieldAssets() |
||
1578 | { |
||
1579 | if (!empty(static::$collectedAssets)) { |
||
1580 | return static::$collectedAssets; |
||
1581 | } |
||
1582 | |||
1583 | $css = collect(); |
||
1584 | $js = collect(); |
||
1585 | |||
1586 | foreach (static::$availableFields as $field) { |
||
1587 | if (!method_exists($field, 'getAssets')) { |
||
1588 | continue; |
||
1589 | } |
||
1590 | |||
1591 | $assets = call_user_func([$field, 'getAssets']); |
||
1592 | |||
1593 | $css->push(Arr::get($assets, 'css')); |
||
1594 | $js->push(Arr::get($assets, 'js')); |
||
1595 | } |
||
1596 | |||
1597 | return static::$collectedAssets = [ |
||
1598 | 'css' => $css->flatten()->unique()->filter()->toArray(), |
||
1599 | 'js' => $js->flatten()->unique()->filter()->toArray(), |
||
1600 | ]; |
||
1601 | } |
||
1602 | |||
1603 | /** |
||
1604 | * Getter. |
||
1605 | * |
||
1606 | * @param string $name |
||
1607 | * |
||
1608 | * @return array|mixed |
||
1609 | */ |
||
1610 | public function __get($name) |
||
1611 | { |
||
1612 | return $this->input($name); |
||
1613 | } |
||
1614 | |||
1615 | /** |
||
1616 | * Setter. |
||
1617 | * |
||
1618 | * @param string $name |
||
1619 | * @param mixed $value |
||
1620 | * |
||
1621 | * @return array |
||
1622 | */ |
||
1623 | public function __set($name, $value) |
||
1624 | { |
||
1625 | return Arr::set($this->inputs, $name, $value); |
||
1626 | } |
||
1627 | |||
1628 | /** |
||
1629 | * Generate a Field object and add to form builder if Field exists. |
||
1630 | * |
||
1631 | * @param string $method |
||
1632 | * @param array $arguments |
||
1633 | * |
||
1634 | * @return Field |
||
1635 | */ |
||
1636 | public function __call($method, $arguments) |
||
1637 | { |
||
1638 | if ($className = static::findFieldClass($method)) { |
||
1639 | $column = Arr::get($arguments, 0, ''); //[0]; |
||
1640 | |||
1641 | $element = new $className($column, array_slice($arguments, 1)); |
||
1642 | |||
1643 | $this->pushField($element); |
||
1644 | |||
1645 | return $element; |
||
1646 | } |
||
1647 | |||
1648 | admin_error('Error', "Field type [$method] does not exist."); |
||
1649 | |||
1650 | return new Field\Nullable(); |
||
1651 | } |
||
1652 | } |
||
1653 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.