Helper.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. <?php
  2. namespace Dcat\Admin\Support;
  3. use Dcat\Admin\Grid;
  4. use Illuminate\Contracts\Support\Arrayable;
  5. use Illuminate\Contracts\Support\Htmlable;
  6. use Illuminate\Contracts\Support\Jsonable;
  7. use Illuminate\Contracts\Support\Renderable;
  8. use Illuminate\Http\Request;
  9. use Illuminate\Support\Arr;
  10. use Illuminate\Support\Collection;
  11. use Illuminate\Support\Facades\Artisan;
  12. use Illuminate\Support\Facades\File;
  13. use Illuminate\Support\Str;
  14. use Symfony\Component\Process\Process;
  15. class Helper
  16. {
  17. /**
  18. * @var array
  19. */
  20. public static $fileTypes = [
  21. 'image' => 'png|jpg|jpeg|tmp|gif',
  22. 'word' => 'doc|docx',
  23. 'excel' => 'xls|xlsx|csv',
  24. 'powerpoint' => 'ppt|pptx',
  25. 'pdf' => 'pdf',
  26. 'code' => 'php|js|java|python|ruby|go|c|cpp|sql|m|h|json|html|aspx',
  27. 'archive' => 'zip|tar\.gz|rar|rpm',
  28. 'txt' => 'txt|pac|log|md',
  29. 'audio' => 'mp3|wav|flac|3pg|aa|aac|ape|au|m4a|mpc|ogg',
  30. 'video' => 'mkv|rmvb|flv|mp4|avi|wmv|rm|asf|mpeg',
  31. ];
  32. /**
  33. * 更新扩展配置.
  34. *
  35. * @param array $config
  36. *
  37. * @return bool
  38. */
  39. public static function updateExtensionConfig(array $config)
  40. {
  41. $files = app('files');
  42. $result = (bool) $files->put(config_path('admin-extensions.php'), self::exportArrayPhp($config));
  43. if ($result && is_file(base_path('bootstrap/cache/config.php'))) {
  44. Artisan::call('config:cache');
  45. }
  46. config(['admin-extensions' => $config]);
  47. return $result;
  48. }
  49. /**
  50. * 把给定的值转化为数组.
  51. *
  52. * @param $value
  53. * @param bool $filter
  54. *
  55. * @return array
  56. */
  57. public static function array($value, bool $filter = true): array
  58. {
  59. if (! $value) {
  60. return [];
  61. }
  62. if ($value instanceof \Closure) {
  63. $value = $value();
  64. }
  65. if (is_array($value)) {
  66. } elseif ($value instanceof Jsonable) {
  67. $value = json_decode($value->toJson(), true);
  68. } elseif ($value instanceof Arrayable) {
  69. $value = $value->toArray();
  70. } elseif (is_string($value)) {
  71. $array = null;
  72. try {
  73. $array = json_decode($value, true);
  74. } catch (\Throwable $e) {
  75. }
  76. $value = is_array($array) ? $array : explode(',', $value);
  77. } else {
  78. $value = (array) $value;
  79. }
  80. return $filter ? array_filter($value, function ($v) {
  81. return $v !== '' && $v !== null;
  82. }) : $value;
  83. }
  84. /**
  85. * 把给定的值转化为字符串.
  86. *
  87. * @param string|Grid|\Closure|Renderable|Htmlable $value
  88. * @param array $params
  89. * @param object $newThis
  90. *
  91. * @return string
  92. */
  93. public static function render($value, $params = [], $newThis = null): string
  94. {
  95. if (is_string($value)) {
  96. return $value;
  97. }
  98. if ($value instanceof \Closure) {
  99. $newThis && ($value = $value->bindTo($newThis));
  100. $value = $value(...(array) $params);
  101. }
  102. if ($value instanceof Grid) {
  103. return (string) $value->render();
  104. }
  105. if ($value instanceof Renderable) {
  106. return (string) $value->render();
  107. }
  108. if ($value instanceof Htmlable) {
  109. return (string) $value->toHtml();
  110. }
  111. return (string) $value;
  112. }
  113. /**
  114. * @param array $attributes
  115. *
  116. * @return string
  117. */
  118. public static function buildHtmlAttributes($attributes)
  119. {
  120. $html = '';
  121. foreach ((array) $attributes as $key => &$value) {
  122. if (is_array($value)) {
  123. $value = implode(' ', $value);
  124. }
  125. if (is_numeric($key)) {
  126. $key = $value;
  127. }
  128. $element = '';
  129. if ($value !== null) {
  130. $element = $key.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8').'"';
  131. }
  132. $html .= $element;
  133. }
  134. return $html;
  135. }
  136. /**
  137. * @param string $url
  138. * @param array $query
  139. *
  140. * @return string
  141. */
  142. public static function urlWithQuery(?string $url, array $query = [])
  143. {
  144. if (! $url || ! $query) {
  145. return $url;
  146. }
  147. $array = explode('?', $url);
  148. $url = $array[0];
  149. parse_str($array[1] ?? '', $originalQuery);
  150. return $url.'?'.http_build_query(array_merge($originalQuery, $query));
  151. }
  152. /**
  153. * @param string $url
  154. * @param string|array|Arrayable $keys
  155. *
  156. * @return string
  157. */
  158. public static function urlWithoutQuery($url, $keys)
  159. {
  160. if (! Str::contains($url, '?') || ! $keys) {
  161. return $url;
  162. }
  163. if ($keys instanceof Arrayable) {
  164. $keys = $keys->toArray();
  165. }
  166. $keys = (array) $keys;
  167. $urlInfo = parse_url($url);
  168. parse_str($urlInfo['query'], $query);
  169. Arr::forget($query, $keys);
  170. $baseUrl = explode('?', $url)[0];
  171. return $query
  172. ? $baseUrl.'?'.http_build_query($query)
  173. : $baseUrl;
  174. }
  175. /**
  176. * @param Arrayable|array|string $keys
  177. *
  178. * @return string
  179. */
  180. public static function fullUrlWithoutQuery($keys)
  181. {
  182. return static::urlWithoutQuery(request()->fullUrl(), $keys);
  183. }
  184. /**
  185. * @param string $url
  186. * @param string|array $keys
  187. *
  188. * @return bool
  189. */
  190. public static function urlHasQuery(string $url, $keys)
  191. {
  192. $value = explode('?', $url);
  193. if (empty($value[1])) {
  194. return false;
  195. }
  196. parse_str($value[1], $query);
  197. foreach ((array) $keys as $key) {
  198. if (Arr::has($query, $key)) {
  199. return true;
  200. }
  201. }
  202. return false;
  203. }
  204. /**
  205. * 匹配请求路径.
  206. *
  207. * @example
  208. * Helper::matchRequestPath(admin_base_path('auth/user'))
  209. * Helper::matchRequestPath(admin_base_path('auth/user*'))
  210. * Helper::matchRequestPath(admin_base_path('auth/user/* /edit'))
  211. * Helper::matchRequestPath('GET,POST:auth/user')
  212. *
  213. * @param string $path
  214. * @param null|string $current
  215. *
  216. * @return bool
  217. */
  218. public static function matchRequestPath($path, ?string $current = null)
  219. {
  220. $request = request();
  221. $current = $current ?: $request->decodedPath();
  222. if (Str::contains($path, ':')) {
  223. [$methods, $path] = explode(':', $path);
  224. $methods = array_map('strtoupper', explode(',', $methods));
  225. if (! empty($methods) && ! in_array($request->method(), $methods)) {
  226. return false;
  227. }
  228. }
  229. // 判断路由名称
  230. if ($request->routeIs($path)) {
  231. return true;
  232. }
  233. if (! Str::contains($path, '*')) {
  234. return $path === $current;
  235. }
  236. $path = str_replace(['*', '/'], ['([0-9a-z-_,])*', "\/"], $path);
  237. return preg_match("/$path/i", $current);
  238. }
  239. /**
  240. * 生成层级数据.
  241. *
  242. * @param array $nodes
  243. * @param int $parentId
  244. * @param string|null $primaryKeyName
  245. * @param string|null $parentKeyName
  246. * @param string|null $childrenKeyName
  247. *
  248. * @return array
  249. */
  250. public static function buildNestedArray(
  251. $nodes = [],
  252. $parentId = 0,
  253. ?string $primaryKeyName = null,
  254. ?string $parentKeyName = null,
  255. ?string $childrenKeyName = null
  256. ) {
  257. $branch = [];
  258. $primaryKeyName = $primaryKeyName ?: 'id';
  259. $parentKeyName = $parentKeyName ?: 'parent_id';
  260. $childrenKeyName = $childrenKeyName ?: 'children';
  261. $parentId = is_numeric($parentId) ? (int) $parentId : $parentId;
  262. foreach ($nodes as $node) {
  263. $pk = Arr::get($node, $parentKeyName);
  264. $pk = is_numeric($pk) ? (int) $pk : $pk;
  265. if ($pk === $parentId) {
  266. $children = static::buildNestedArray(
  267. $nodes,
  268. Arr::get($node, $primaryKeyName),
  269. $primaryKeyName,
  270. $parentKeyName,
  271. $childrenKeyName
  272. );
  273. if ($children) {
  274. $node[$childrenKeyName] = $children;
  275. }
  276. $branch[] = $node;
  277. }
  278. }
  279. return $branch;
  280. }
  281. /**
  282. * @param string $name
  283. * @param string $symbol
  284. *
  285. * @return mixed
  286. */
  287. public static function slug(string $name, string $symbol = '-')
  288. {
  289. $text = preg_replace_callback('/([A-Z])/', function (&$text) use ($symbol) {
  290. return $symbol.strtolower($text[1]);
  291. }, $name);
  292. return str_replace('_', $symbol, ltrim($text, $symbol));
  293. }
  294. /**
  295. * @param array $array
  296. * @param int $level
  297. *
  298. * @return string
  299. */
  300. public static function exportArray(array &$array, $level = 1)
  301. {
  302. $start = '[';
  303. $end = ']';
  304. $txt = "$start\n";
  305. foreach ($array as $k => &$v) {
  306. if (is_array($v)) {
  307. $pre = is_string($k) ? "'$k' => " : "$k => ";
  308. $txt .= str_repeat(' ', $level * 4).$pre.static::exportArray($v, $level + 1).",\n";
  309. continue;
  310. }
  311. $t = $v;
  312. if ($v === true) {
  313. $t = 'true';
  314. } elseif ($v === false) {
  315. $t = 'false';
  316. } elseif ($v === null) {
  317. $t = 'null';
  318. } elseif (is_string($v)) {
  319. $v = str_replace("'", "\\'", $v);
  320. $t = "'$v'";
  321. }
  322. $pre = is_string($k) ? "'$k' => " : "$k => ";
  323. $txt .= str_repeat(' ', $level * 4)."{$pre}{$t},\n";
  324. }
  325. return $txt.str_repeat(' ', ($level - 1) * 4).$end;
  326. }
  327. /**
  328. * @param array $array
  329. *
  330. * @return string
  331. */
  332. public static function exportArrayPhp(array $array)
  333. {
  334. return "<?php \nreturn ".static::exportArray($array).";\n";
  335. }
  336. /**
  337. * 删除数组中的元素.
  338. *
  339. * @param array $array
  340. * @param mixed $value
  341. */
  342. public static function deleteByValue(&$array, $value)
  343. {
  344. $value = (array) $value;
  345. foreach ($array as $index => $item) {
  346. if (in_array($item, $value)) {
  347. unset($array[$index]);
  348. }
  349. }
  350. }
  351. /**
  352. * 颜色转亮.
  353. *
  354. * @param string $color
  355. * @param int $amt
  356. *
  357. * @return string
  358. */
  359. public static function colorLighten(string $color, int $amt)
  360. {
  361. if (! $amt) {
  362. return $color;
  363. }
  364. $hasPrefix = false;
  365. if (mb_strpos($color, '#') === 0) {
  366. $color = mb_substr($color, 1);
  367. $hasPrefix = true;
  368. }
  369. [$red, $blue, $green] = static::colorToRBG($color, $amt);
  370. return ($hasPrefix ? '#' : '').dechex($green + ($blue << 8) + ($red << 16));
  371. }
  372. /**
  373. * 颜色转暗.
  374. *
  375. * @param string $color
  376. * @param int $amt
  377. *
  378. * @return string
  379. */
  380. public static function colorDarken(string $color, int $amt)
  381. {
  382. return static::colorLighten($color, -$amt);
  383. }
  384. /**
  385. * 颜色透明度.
  386. *
  387. * @param string $color
  388. * @param float|string $alpha
  389. *
  390. * @return string
  391. */
  392. public static function colorAlpha(string $color, $alpha)
  393. {
  394. if ($alpha >= 1) {
  395. return $color;
  396. }
  397. if (mb_strpos($color, '#') === 0) {
  398. $color = mb_substr($color, 1);
  399. }
  400. [$red, $blue, $green] = static::colorToRBG($color);
  401. return "rgba($red, $blue, $green, $alpha)";
  402. }
  403. /**
  404. * @param string $color
  405. * @param int $amt
  406. *
  407. * @return array
  408. */
  409. public static function colorToRBG(string $color, int $amt = 0)
  410. {
  411. $format = function ($value) {
  412. if ($value > 255) {
  413. return 255;
  414. }
  415. if ($value < 0) {
  416. return 0;
  417. }
  418. return $value;
  419. };
  420. $num = hexdec($color);
  421. $red = $format(($num >> 16) + $amt);
  422. $blue = $format((($num >> 8) & 0x00FF) + $amt);
  423. $green = $format(($num & 0x0000FF) + $amt);
  424. return [$red, $blue, $green];
  425. }
  426. /**
  427. * 验证扩展包名称.
  428. *
  429. * @param string $name
  430. *
  431. * @return int
  432. */
  433. public static function validateExtensionName($name)
  434. {
  435. return preg_match('/^[\w\-_]+\/[\w\-_]+$/', $name);
  436. }
  437. /**
  438. * Get file icon.
  439. *
  440. * @param string $file
  441. *
  442. * @return string
  443. */
  444. public static function getFileIcon($file = '')
  445. {
  446. $extension = File::extension($file);
  447. foreach (static::$fileTypes as $type => $regex) {
  448. if (preg_match("/^($regex)$/i", $extension) !== 0) {
  449. return "fa fa-file-{$type}-o";
  450. }
  451. }
  452. return 'fa fa-file-o';
  453. }
  454. /**
  455. * 判断是否是ajax请求.
  456. *
  457. * @param Request $request
  458. *
  459. * @return bool
  460. */
  461. public static function isAjaxRequest(?Request $request = null)
  462. {
  463. /* @var Request $request */
  464. $request = $request ?: request();
  465. return $request->ajax() && ! $request->pjax();
  466. }
  467. /**
  468. * 判断是否是IE浏览器.
  469. *
  470. * @return false|int
  471. */
  472. public static function isIEBrowser()
  473. {
  474. return (bool) preg_match('/Mozilla\/5\.0 \(Windows NT 10\.0; WOW64; Trident\/7\.0; rv:[0-9\.]*\) like Gecko/i', $_SERVER['HTTP_USER_AGENT'] ?? '');
  475. }
  476. /**
  477. * 判断是否QQ浏览器.
  478. *
  479. * @return bool
  480. */
  481. public static function isQQBrowser()
  482. {
  483. return mb_strpos(mb_strtolower($_SERVER['HTTP_USER_AGENT'] ?? ''), 'qqbrowser') !== false;
  484. }
  485. /**
  486. * @param string $url
  487. *
  488. * @return void
  489. */
  490. public static function setPreviousUrl($url)
  491. {
  492. session()->flash('admin.prev.url', static::urlWithoutQuery((string) $url, '_pjax'));
  493. }
  494. /**
  495. * @return string
  496. */
  497. public static function getPreviousUrl()
  498. {
  499. return (string) (session()->get('admin.prev.url') ? url(session()->get('admin.prev.url')) : url()->previous());
  500. }
  501. /**
  502. * @param mixed $command
  503. * @param int $timeout
  504. * @param null $input
  505. * @param null $cwd
  506. *
  507. * @return Process
  508. */
  509. public static function process($command, $timeout = 100, $input = null, $cwd = null)
  510. {
  511. $parameters = [
  512. $command,
  513. $cwd,
  514. [],
  515. $input,
  516. $timeout,
  517. ];
  518. return is_string($command)
  519. ? Process::fromShellCommandline(...$parameters)
  520. : new Process(...$parameters);
  521. }
  522. /**
  523. * 判断两个值是否相等.
  524. *
  525. * @param $value1
  526. * @param $value2
  527. *
  528. * @return bool
  529. */
  530. public static function equal($value1, $value2)
  531. {
  532. if ($value1 === null || $value2 === null) {
  533. return false;
  534. }
  535. if (! is_scalar($value1) || ! is_scalar($value2)) {
  536. return $value1 === $value2;
  537. }
  538. return (string) $value1 === (string) $value2;
  539. }
  540. /**
  541. * Limit the number of characters in a string.
  542. *
  543. * @param string $value
  544. * @param int $limit
  545. * @param string $end
  546. * @return string
  547. */
  548. public static function strLimit($value, $limit = 100, $end = '...')
  549. {
  550. if (mb_strlen($value, 'UTF-8') <= $limit) {
  551. return $value;
  552. }
  553. return rtrim(mb_substr($value, 0, $limit, 'UTF-8')).$end;
  554. }
  555. /**
  556. * 获取类名或对象的文件路径.
  557. *
  558. * @param string|object $class
  559. *
  560. * @return string
  561. *
  562. * @throws \ReflectionException
  563. */
  564. public static function guessClassFileName($class)
  565. {
  566. if (is_object($class)) {
  567. $class = get_class($class);
  568. }
  569. if (class_exists($class)) {
  570. return (new \ReflectionClass($class))->getFileName();
  571. }
  572. $class = trim($class, '\\');
  573. $composer = Composer::parse(base_path('composer.json'));
  574. $map = collect($composer->autoload['psr-4'] ?? [])->mapWithKeys(function ($path, $namespace) {
  575. $namespace = trim($namespace, '\\').'\\';
  576. return [$namespace => [$namespace, $path]];
  577. })->sortBy(function ($_, $namespace) {
  578. return strlen($namespace);
  579. }, SORT_REGULAR, true);
  580. $prefix = explode($class, '\\')[0];
  581. if ($map->isEmpty()) {
  582. if (Str::startsWith($class, 'App\\')) {
  583. $values = ['App\\', 'app/'];
  584. }
  585. } else {
  586. $values = $map->filter(function ($_, $k) use ($class) {
  587. return Str::startsWith($class, $k);
  588. })->first();
  589. }
  590. if (empty($values)) {
  591. $values = [$prefix.'\\', self::slug($prefix).'/'];
  592. }
  593. [$namespace, $path] = $values;
  594. return base_path(str_replace([$namespace, '\\'], [$path, '/'], $class)).'.php';
  595. }
  596. /**
  597. * Is input data is has-one relation.
  598. *
  599. * @param Collection $fields
  600. * @param array $input
  601. */
  602. public static function prepareHasOneRelation(Collection $fields, array &$input)
  603. {
  604. $relations = [];
  605. $fields->each(function ($field) use (&$relations) {
  606. $column = $field->column();
  607. if (is_array($column)) {
  608. foreach ($column as $v) {
  609. if (Str::contains($v, '.')) {
  610. $first = explode('.', $v)[0];
  611. $relations[$first] = null;
  612. }
  613. }
  614. return;
  615. }
  616. if (Str::contains($column, '.')) {
  617. $first = explode('.', $column)[0];
  618. $relations[$first] = null;
  619. }
  620. });
  621. foreach ($relations as $first => $v) {
  622. if (isset($input[$first])) {
  623. $input = array_merge($input, Arr::dot([$first => $input[$first]]));
  624. }
  625. }
  626. }
  627. }