Field.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. <?php
  2. namespace Dcat\Admin\Show;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Show;
  5. use Dcat\Admin\Support\Helper;
  6. use Dcat\Admin\Traits\HasBuilderEvents;
  7. use Dcat\Admin\Traits\HasVariables;
  8. use Dcat\Admin\Widgets\Dump;
  9. use Illuminate\Contracts\Support\Arrayable;
  10. use Illuminate\Contracts\Support\Renderable;
  11. use Illuminate\Support\Arr;
  12. use Illuminate\Support\Collection;
  13. use Illuminate\Support\Facades\Storage;
  14. use Illuminate\Support\Fluent;
  15. use Illuminate\Support\Str;
  16. use Illuminate\Support\Traits\Macroable;
  17. class Field implements Renderable
  18. {
  19. use HasBuilderEvents;
  20. use HasVariables;
  21. use Macroable {
  22. __call as macroCall;
  23. }
  24. /**
  25. * @var array
  26. */
  27. protected static $extendedFields = [];
  28. /**
  29. * @var string
  30. */
  31. protected $view = 'admin::show.field';
  32. /**
  33. * Name of column.
  34. *
  35. * @var string
  36. */
  37. protected $name;
  38. /**
  39. * Label of column.
  40. *
  41. * @var string
  42. */
  43. protected $label;
  44. /**
  45. * Escape field value or not.
  46. *
  47. * @var bool
  48. */
  49. protected $escape = true;
  50. /**
  51. * Field value.
  52. *
  53. * @var mixed
  54. */
  55. protected $value;
  56. /**
  57. * @var Collection
  58. */
  59. protected $showAs = [];
  60. /**
  61. * Parent show instance.
  62. *
  63. * @var Show
  64. */
  65. protected $parent;
  66. /**
  67. * Relation name.
  68. *
  69. * @var string
  70. */
  71. protected $relation;
  72. /**
  73. * If show contents in box.
  74. *
  75. * @var bool
  76. */
  77. protected $border = true;
  78. /**
  79. * @var int
  80. */
  81. protected $width = ['field' => 8, 'label' => 2];
  82. /**
  83. * Field constructor.
  84. *
  85. * @param string $name
  86. * @param string $label
  87. */
  88. public function __construct($name = '', $label = '')
  89. {
  90. $this->name = $name;
  91. $this->label = $this->formatLabel($label);
  92. $this->showAs = new Collection();
  93. $this->callResolving();
  94. }
  95. /**
  96. * Set parent show instance.
  97. *
  98. * @param Show $show
  99. *
  100. * @return $this
  101. */
  102. public function setParent(Show $show)
  103. {
  104. $this->parent = $show;
  105. return $this;
  106. }
  107. /**
  108. * Get name of this column.
  109. *
  110. * @return mixed
  111. */
  112. public function getName()
  113. {
  114. return $this->name;
  115. }
  116. /**
  117. * @param int $width
  118. *
  119. * @return $this|array
  120. */
  121. public function width(int $field, int $label = 2)
  122. {
  123. $this->width = [
  124. 'label' => $label,
  125. 'field' => $field,
  126. ];
  127. return $this;
  128. }
  129. /**
  130. * Format label.
  131. *
  132. * @param $label
  133. *
  134. * @return mixed
  135. */
  136. protected function formatLabel($label)
  137. {
  138. if ($label) {
  139. return $label;
  140. }
  141. $label = admin_trans_field($this->name);
  142. return str_replace('_', ' ', $label);
  143. }
  144. /**
  145. * Get label of the column.
  146. *
  147. * @return mixed
  148. */
  149. public function getLabel()
  150. {
  151. return $this->label;
  152. }
  153. /**
  154. * Field display callback.
  155. *
  156. * @param mixed $callable
  157. *
  158. * @return $this
  159. */
  160. public function as($callable, ...$params)
  161. {
  162. $this->showAs->push([$callable, $params]);
  163. return $this;
  164. }
  165. /**
  166. * Display field using array value map.
  167. *
  168. * @param array $values
  169. * @param null $default
  170. *
  171. * @return $this
  172. */
  173. public function using(array $values, $default = null)
  174. {
  175. return $this->as(function ($value) use ($values, $default) {
  176. if (is_null($value)) {
  177. return $default;
  178. }
  179. return Arr::get($values, $value, $default);
  180. });
  181. }
  182. /**
  183. * Show field as a image.
  184. *
  185. * @param string $server
  186. * @param int $width
  187. * @param int $height
  188. *
  189. * @return $this
  190. */
  191. public function image($server = '', $width = 200, $height = 200)
  192. {
  193. return $this->unescape()->as(function ($path) use ($server, $width, $height) {
  194. if (empty($path)) {
  195. return '';
  196. }
  197. $path = Helper::array($path);
  198. return collect($path)->transform(function ($path) use ($server, $width, $height) {
  199. if (url()->isValidUrl($path)) {
  200. $src = $path;
  201. } elseif ($server) {
  202. $src = rtrim($server, '/').'/'.ltrim($path, '/');
  203. } else {
  204. $disk = config('admin.upload.disk');
  205. if (config("filesystems.disks.{$disk}")) {
  206. $src = Storage::disk($disk)->url($path);
  207. } else {
  208. return '';
  209. }
  210. }
  211. return "<img data-action='preview-img' src='$src' style='max-width:{$width}px;max-height:{$height}px' class='img' />";
  212. })->implode('&nbsp;');
  213. });
  214. }
  215. /**
  216. * Show field as a file.
  217. *
  218. * @param string $server
  219. * @param bool $download
  220. *
  221. * @return Field
  222. */
  223. public function file($server = '', $download = true)
  224. {
  225. $field = $this;
  226. return $this->unescape()->as(function ($path) use ($server, $field) {
  227. if (empty($path)) {
  228. return '';
  229. }
  230. $path = Helper::array($path);
  231. $list = collect($path)->transform(function ($path) use ($server, $field) {
  232. $name = basename($path);
  233. $field->wrap(false);
  234. $size = $url = '';
  235. if (url()->isValidUrl($path)) {
  236. $url = $path;
  237. } elseif ($server) {
  238. $url = $server.$path;
  239. } else {
  240. $storage = Storage::disk(config('admin.upload.disk'));
  241. if ($storage->exists($path)) {
  242. $url = $storage->url($path);
  243. $size = ($storage->size($path) / 1000).'KB';
  244. }
  245. }
  246. if (! $url) {
  247. return '';
  248. }
  249. $icon = Helper::getFileIcon($name);
  250. return <<<HTML
  251. <li style="margin-bottom: 0;">
  252. <span class="mailbox-attachment-icon"><i class="{$icon}"></i></span>
  253. <div class="mailbox-attachment-info">
  254. <div class="mailbox-attachment-name">
  255. <i class="fa fa-paperclip"></i> {$name}
  256. </div>
  257. <span class="mailbox-attachment-size">
  258. {$size}&nbsp;
  259. <a href="{$url}" class="btn btn-white btn-xs pull-right" target="_blank"><i class="fa fa-cloud-download"></i></a>
  260. </span>
  261. </div>
  262. </li>
  263. HTML;
  264. })->implode('&nbsp;');
  265. return "<ul class=\"mailbox-attachments clearfix\">{$list}</ul>";
  266. });
  267. }
  268. /**
  269. * Show field as a link.
  270. *
  271. * @param string $href
  272. * @param string $target
  273. *
  274. * @return Field
  275. */
  276. public function link($href = '', $target = '_blank')
  277. {
  278. return $this->unescape()->as(function ($link) use ($href, $target) {
  279. $href = $href ?: $link;
  280. return "<a href='$href' target='{$target}'>{$link}</a>";
  281. });
  282. }
  283. /**
  284. * Show field as labels.
  285. *
  286. * @param string $style
  287. *
  288. * @return Field
  289. */
  290. public function label($style = 'primary')
  291. {
  292. $self = $this;
  293. return $this->unescape()->as(function ($value) use ($self, $style) {
  294. [$class, $background] = $self->formatStyle($style);
  295. return collect($value)->map(function ($name) use ($class, $background) {
  296. return "<span class='label bg-{$class}' $background>$name</span>";
  297. })->implode(' ');
  298. });
  299. }
  300. /**
  301. * Add a `dot` before column text.
  302. *
  303. * @param array $options
  304. * @param string $default
  305. *
  306. * @return $this
  307. */
  308. public function dot($options = [], $default = 'default')
  309. {
  310. return $this->unescape()->prepend(function ($_, $original) use ($options, $default) {
  311. $style = is_null($original) ? $default : Arr::get((array) $options, $original, $default);
  312. $style = $style === 'default' ? 'dark70' : $style;
  313. $background = Admin::color()->get($style, $style);
  314. return "<i class='fa fa-circle' style='font-size: 13px;color: {$background}'></i>&nbsp;&nbsp;";
  315. });
  316. }
  317. /**
  318. * Show field as badges.
  319. *
  320. * @param string $style
  321. *
  322. * @return Field
  323. */
  324. public function badge($style = 'blue')
  325. {
  326. $self = $this;
  327. return $this->unescape()->as(function ($value) use ($self, $style) {
  328. [$class, $background] = $self->formatStyle($style);
  329. return collect($value)->map(function ($name) use ($class, $background) {
  330. return "<span class='badge bg-{$class}' $background>$name</span>";
  331. })->implode(' ');
  332. });
  333. }
  334. /**
  335. * @param $style
  336. *
  337. * @return array
  338. */
  339. public function formatStyle($style)
  340. {
  341. $class = 'default';
  342. $background = '';
  343. if ($style !== 'default') {
  344. $class = '';
  345. $style = Admin::color()->get($style, $style);
  346. $background = "style='background:{$style}'";
  347. }
  348. return [$class, $background];
  349. }
  350. /**
  351. * Show field as json code.
  352. *
  353. * @return Field
  354. */
  355. public function json()
  356. {
  357. $field = $this;
  358. return $this->unescape()->as(function ($value) use ($field) {
  359. $content = is_string($value) ? json_decode($value, true) : $value;
  360. $field->wrap(false);
  361. return Dump::make($content);
  362. });
  363. }
  364. /**
  365. * @param string $val
  366. *
  367. * @return $this
  368. */
  369. public function prepend($val)
  370. {
  371. $name = $this->name;
  372. return $this->as(function ($v) use (&$val, $name) {
  373. if ($val instanceof \Closure) {
  374. $val = $val->call($this, $v, Arr::get($this, $name));
  375. }
  376. if (is_array($v)) {
  377. array_unshift($v, $val);
  378. return $v;
  379. } elseif ($v instanceof Collection) {
  380. return $v->prepend($val);
  381. }
  382. return $val.$v;
  383. });
  384. }
  385. /**
  386. * @param string $val
  387. *
  388. * @return $this
  389. */
  390. public function append($val)
  391. {
  392. $name = $this->name;
  393. return $this->as(function ($v) use (&$val, $name) {
  394. if ($val instanceof \Closure) {
  395. $val = $val->call($this, $v, Arr::get($this, $name));
  396. }
  397. if (is_array($v)) {
  398. array_push($v, $val);
  399. return $v;
  400. } elseif ($v instanceof Collection) {
  401. return $v->push($val);
  402. }
  403. return $v.$val;
  404. });
  405. }
  406. /**
  407. * Split a string by string.
  408. *
  409. * @param string $d
  410. *
  411. * @return $this
  412. */
  413. public function explode(string $d = ',')
  414. {
  415. return $this->as(function ($v) use ($d) {
  416. if (is_array($v) || $v instanceof Arrayable) {
  417. return $v;
  418. }
  419. return $v ? explode($d, $v) : [];
  420. });
  421. }
  422. /**
  423. * Render this column with the given view.
  424. *
  425. * @param string $view
  426. * @param array $data
  427. *
  428. * @return $this
  429. */
  430. public function view($view, array $data = [])
  431. {
  432. $name = $this->name;
  433. return $this->unescape()->as(function ($value) use ($view, $name, $data) {
  434. $model = $this;
  435. return view($view, array_merge(compact('model', 'value', 'name'), $data))->render();
  436. });
  437. }
  438. /**
  439. * Set escape or not for this field.
  440. *
  441. * @param bool $escape
  442. *
  443. * @return $this
  444. */
  445. public function escape($escape = true)
  446. {
  447. $this->escape = $escape;
  448. return $this;
  449. }
  450. /**
  451. * Unescape for this field.
  452. *
  453. * @return Field
  454. */
  455. public function unescape()
  456. {
  457. return $this->escape(false);
  458. }
  459. /**
  460. * @param Fluent|\Illuminate\Database\Eloquent\Model $model
  461. *
  462. * @return void
  463. */
  464. public function fill($model)
  465. {
  466. $this->value(Arr::get($model->toArray(), $this->name));
  467. }
  468. /**
  469. * Get or set value for this field.
  470. *
  471. * @param mixed $value
  472. *
  473. * @return $this|mixed
  474. */
  475. public function value($value = null)
  476. {
  477. if ($value === null) {
  478. return $this->value;
  479. }
  480. $this->value = $value;
  481. return $this;
  482. }
  483. /**
  484. * @return $this
  485. */
  486. public function wrap(bool $wrap = true)
  487. {
  488. $this->border = $wrap;
  489. return $this;
  490. }
  491. /**
  492. * @param string $color
  493. *
  494. * @return $this
  495. */
  496. public function bold($color = null)
  497. {
  498. $color = $color ?: Admin::color()->dark80();
  499. return $this->unescape()->as(function ($value) use ($color) {
  500. if (! $value) {
  501. return $value;
  502. }
  503. return "<b style='color: {$color}'>$value</b>";
  504. });
  505. }
  506. /**
  507. * Display field as boolean , `✓` for true, and `✗` for false.
  508. *
  509. * @param array $map
  510. * @param bool $default
  511. *
  512. * @return $this
  513. */
  514. public function bool(array $map = [], $default = false)
  515. {
  516. return $this->unescape()->as(function ($value) use ($map, $default) {
  517. $bool = empty($map) ? $value : Arr::get($map, $value, $default);
  518. return $bool ? '<i class="feather icon-check font-md-2 font-w-600 text-primary"></i>' : '<i class="feather icon-x font-md-1 font-w-600 text-70"></i>';
  519. });
  520. }
  521. /**
  522. * @param mixed $value
  523. * @param callable $callback
  524. *
  525. * @return $this|mixed
  526. */
  527. public function when($value, $callback)
  528. {
  529. if ($value) {
  530. return $callback($this, $value) ?: $this;
  531. }
  532. return $this;
  533. }
  534. /**
  535. * @param string $method
  536. * @param array $arguments
  537. *
  538. * @return $this
  539. */
  540. public function __call($method, $arguments = [])
  541. {
  542. if ($class = Arr::get(static::$extendedFields, $method)) {
  543. return $this->callExtendedField($class, $arguments);
  544. }
  545. if (static::hasMacro($method)) {
  546. return $this->macroCall($method, $arguments);
  547. }
  548. return $this->callSupportDisplayer($method, $arguments);
  549. }
  550. /**
  551. * Call extended field.
  552. *
  553. * @param string|AbstractField|\Closure $abstract
  554. * @param array $arguments
  555. *
  556. * @return Field
  557. */
  558. protected function callExtendedField($abstract, $arguments = [])
  559. {
  560. if ($abstract instanceof \Closure) {
  561. return $this->as($abstract, ...$arguments);
  562. }
  563. if (is_string($abstract) && class_exists($abstract)) {
  564. /** @var AbstractField $extend */
  565. $extend = new $abstract();
  566. }
  567. if ($abstract instanceof AbstractField) {
  568. /** @var AbstractField $extend */
  569. $extend = $abstract;
  570. }
  571. if (! isset($extend)) {
  572. admin_warning("[$abstract] is not a valid Show field.");
  573. return $this;
  574. }
  575. if (! $extend->escape) {
  576. $this->unescape();
  577. }
  578. $field = $this;
  579. return $this->as(function ($value) use ($extend, $field, $arguments) {
  580. if (! $extend->border) {
  581. $field->wrap(false);
  582. }
  583. $extend->setValue($value)->setModel($this);
  584. return $extend->render(...$arguments);
  585. });
  586. }
  587. /**
  588. * Call Illuminate/Support.
  589. *
  590. * @param string $abstract
  591. * @param array $arguments
  592. *
  593. * @return $this
  594. */
  595. protected function callSupportDisplayer($abstract, $arguments)
  596. {
  597. return $this->as(function ($value) use ($abstract, $arguments) {
  598. if (is_array($value) || $value instanceof Arrayable) {
  599. return call_user_func_array([collect($value), $abstract], $arguments);
  600. }
  601. if (is_string($value)) {
  602. return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
  603. }
  604. return $value;
  605. });
  606. }
  607. /**
  608. * Get all variables passed to field view.
  609. *
  610. * @return array
  611. */
  612. protected function defaultVariables()
  613. {
  614. return [
  615. 'content' => $this->value,
  616. 'escape' => $this->escape,
  617. 'label' => $this->getLabel(),
  618. 'wrapped' => $this->border,
  619. 'width' => $this->width,
  620. ];
  621. }
  622. /**
  623. * Render this field.
  624. *
  625. * @return string
  626. */
  627. public function render()
  628. {
  629. if ($this->showAs->isNotEmpty()) {
  630. $this->showAs->each(function ($callable) {
  631. [$callable, $params] = $callable;
  632. if (! $callable instanceof \Closure) {
  633. $this->value = $callable;
  634. return;
  635. }
  636. $this->value = $callable->call(
  637. $this->parent->model(),
  638. $this->value,
  639. ...$params
  640. );
  641. });
  642. }
  643. return view($this->view, $this->variables());
  644. }
  645. /**
  646. * Register custom field.
  647. *
  648. * @param string $abstract
  649. * @param string $class
  650. *
  651. * @return void
  652. */
  653. public static function extend($abstract, $class)
  654. {
  655. static::$extendedFields[$abstract] = $class;
  656. }
  657. /**
  658. * @return array
  659. */
  660. public static function extensions()
  661. {
  662. return static::$extendedFields;
  663. }
  664. }