Field.php 18 KB

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