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