Form.php 39 KB

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