Filter.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. <?php
  2. namespace Dcat\Admin\Grid;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Grid\Filter\AbstractFilter;
  5. use Dcat\Admin\Grid\Filter\Between;
  6. use Dcat\Admin\Grid\Filter\Date;
  7. use Dcat\Admin\Grid\Filter\Day;
  8. use Dcat\Admin\Grid\Filter\EndWith;
  9. use Dcat\Admin\Grid\Filter\Equal;
  10. use Dcat\Admin\Grid\Filter\Group;
  11. use Dcat\Admin\Grid\Filter\Gt;
  12. use Dcat\Admin\Grid\Filter\Hidden;
  13. use Dcat\Admin\Grid\Filter\Ilike;
  14. use Dcat\Admin\Grid\Filter\In;
  15. use Dcat\Admin\Grid\Filter\Layout\Layout;
  16. use Dcat\Admin\Grid\Filter\Like;
  17. use Dcat\Admin\Grid\Filter\Lt;
  18. use Dcat\Admin\Grid\Filter\Month;
  19. use Dcat\Admin\Grid\Filter\Newline;
  20. use Dcat\Admin\Grid\Filter\Ngt;
  21. use Dcat\Admin\Grid\Filter\Nlt;
  22. use Dcat\Admin\Grid\Filter\NotEqual;
  23. use Dcat\Admin\Grid\Filter\NotIn;
  24. use Dcat\Admin\Grid\Filter\Scope;
  25. use Dcat\Admin\Grid\Filter\StartWith;
  26. use Dcat\Admin\Grid\Filter\Where;
  27. use Dcat\Admin\Grid\Filter\Year;
  28. use Dcat\Admin\Traits\BuilderEvents;
  29. use Illuminate\Contracts\Support\Arrayable;
  30. use Illuminate\Contracts\Support\Renderable;
  31. use Illuminate\Support\Arr;
  32. use Illuminate\Support\Str;
  33. use Illuminate\Support\Collection;
  34. use Illuminate\Support\Facades\Input;
  35. /**
  36. * Class Filter.
  37. *
  38. * @method Equal equal($column, $label = '')
  39. * @method NotEqual notEqual($column, $label = '')
  40. * @method Like like($column, $label = '')
  41. * @method Ilike ilike($column, $label = '')
  42. * @method StartWith startWith($column, $label = '')
  43. * @method EndWith endWith($column, $label = '')
  44. * @method Gt gt($column, $label = '')
  45. * @method Lt lt($column, $label = '')
  46. * @method Ngt ngt($column, $label = '')
  47. * @method Nlt nlt($column, $label = '')
  48. * @method Between between($column, $label = '')
  49. * @method In in($column, $label = '')
  50. * @method NotIn notIn($column, $label = '')
  51. * @method Where where($colum, $callback, $label = '')
  52. * @method Date date($column, $label = '')
  53. * @method Day day($column, $label = '')
  54. * @method Month month($column, $label = '')
  55. * @method Year year($column, $label = '')
  56. * @method Hidden hidden($name, $value)
  57. * @method Group group($column, $builder = null, $label = '')
  58. * @method Newline newline()
  59. */
  60. class Filter implements Renderable
  61. {
  62. use BuilderEvents;
  63. /**
  64. * Ignore value.
  65. *
  66. * @var string
  67. */
  68. const IGNORE_VALUE = '___';
  69. /**
  70. * @var array
  71. */
  72. protected static $supports = [];
  73. /**
  74. * @var array
  75. */
  76. protected static $defaultFilters = [
  77. 'equal' => Equal::class,
  78. 'notEqual' => NotEqual::class,
  79. 'ilike' => Ilike::class,
  80. 'like' => Like::class,
  81. 'startWith' => StartWith::class,
  82. 'endWith' => EndWith::class,
  83. 'gt' => Gt::class,
  84. 'lt' => Lt::class,
  85. 'ngt' => Ngt::class,
  86. 'nlt' => Nlt::class,
  87. 'between' => Between::class,
  88. 'group' => Group::class,
  89. 'where' => Where::class,
  90. 'in' => In::class,
  91. 'notIn' => NotIn::class,
  92. 'date' => Date::class,
  93. 'day' => Day::class,
  94. 'month' => Month::class,
  95. 'year' => Year::class,
  96. 'hidden' => Hidden::class,
  97. 'newline' => Newline::class,
  98. ];
  99. /**
  100. * @var Model
  101. */
  102. protected $model;
  103. /**
  104. * @var array
  105. */
  106. protected $filters = [];
  107. /**
  108. * Action of search form.
  109. *
  110. * @var string
  111. */
  112. protected $action;
  113. /**
  114. * @var string
  115. */
  116. protected $view = 'admin::filter.container';
  117. /**
  118. * @var string
  119. */
  120. protected $filterID = 'filter-box';
  121. /**
  122. * @var string
  123. */
  124. protected $name = '';
  125. /**
  126. * @var bool
  127. */
  128. public $expand = false;
  129. /**
  130. * @var Collection
  131. */
  132. protected $scopes;
  133. /**
  134. * @var Layout
  135. */
  136. protected $layout;
  137. /**
  138. * Primary key of giving model.
  139. *
  140. * @var mixed
  141. */
  142. protected $primaryKey;
  143. /**
  144. * @var string
  145. */
  146. protected $style = 'padding:18px 15px 8px';
  147. /**
  148. * @var bool
  149. */
  150. protected $disableResetButton = false;
  151. /**
  152. * @var string
  153. */
  154. protected $border = 'border-top:1px solid #f4f4f4;';
  155. /**
  156. * @var string
  157. */
  158. protected $containerClass = '';
  159. /**
  160. * @var bool
  161. */
  162. protected $disableCollapse = false;
  163. /**
  164. * @var array
  165. */
  166. protected $inputs;
  167. /**
  168. * Create a new filter instance.
  169. *
  170. * @param Model $model
  171. */
  172. public function __construct(Model $model)
  173. {
  174. $this->model = $model;
  175. $this->primaryKey = $model->getKeyName();
  176. $this->filterID = $this->generateFilterId();
  177. $this->initLayout();
  178. $this->scopes = new Collection();
  179. $this->callResolving();
  180. }
  181. /**
  182. * Initialize filter layout.
  183. */
  184. protected function initLayout()
  185. {
  186. $this->layout = new Filter\Layout\Layout($this);
  187. }
  188. /**
  189. * @return string
  190. */
  191. protected function generateFilterId()
  192. {
  193. return 'filter-box-'.Str::random(8);
  194. }
  195. /**
  196. * Set action of search form.
  197. *
  198. * @param string $action
  199. *
  200. * @return $this
  201. */
  202. public function setAction($action)
  203. {
  204. $this->action = $action;
  205. return $this;
  206. }
  207. /**
  208. * @return $this
  209. */
  210. public function withoutInputBorder()
  211. {
  212. $this->containerClass = 'input-no-border';
  213. return $this;
  214. }
  215. /**
  216. * @param bool $disabled
  217. * @return $this
  218. */
  219. public function disableCollapse(bool $disabled = true)
  220. {
  221. $this->disableCollapse = $disabled;
  222. return $this;
  223. }
  224. /**
  225. * @param bool $disabled
  226. * @return $this
  227. */
  228. public function disableResetButton(bool $disabled = true)
  229. {
  230. $this->disableResetButton = $disabled;
  231. return $this;
  232. }
  233. /**
  234. * Get input data.
  235. *
  236. * @param string $key
  237. * @param null $value
  238. *
  239. * @return array|mixed
  240. */
  241. public function input($key = null, $default = null)
  242. {
  243. $inputs = $this->getInputs();
  244. if ($key === null) {
  245. return $inputs;
  246. }
  247. return Arr::get($inputs, $key, $default);
  248. }
  249. /**
  250. * Get grid model.
  251. *
  252. * @return Model
  253. */
  254. public function getModel()
  255. {
  256. return $this->model;
  257. }
  258. /**
  259. * Get grid.
  260. *
  261. * @return \Dcat\Admin\Grid
  262. */
  263. public function getGrid()
  264. {
  265. return $this->model->getGrid();
  266. }
  267. /**
  268. * Set ID of search form.
  269. *
  270. * @param string $filterID
  271. *
  272. * @return $this
  273. */
  274. public function setFilterID($filterID)
  275. {
  276. $this->filterID = $filterID;
  277. return $this;
  278. }
  279. /**
  280. * Get filter ID.
  281. *
  282. * @return string
  283. */
  284. public function getFilterID()
  285. {
  286. return $this->filterID;
  287. }
  288. /**
  289. * @param $name
  290. *
  291. * @return $this
  292. */
  293. public function setName($name)
  294. {
  295. $this->name = $name;
  296. $this->setFilterID("{$this->name}-{$this->filterID}");
  297. return $this;
  298. }
  299. /**
  300. * @return string
  301. */
  302. public function getName()
  303. {
  304. return $this->name;
  305. }
  306. public function withoutBorder()
  307. {
  308. return $this->withBorder('');
  309. }
  310. public function withBorder($border = null)
  311. {
  312. $this->border = is_null($border) ? 'border-top:1px solid #f4f4f4;' : $border;
  313. return $this;
  314. }
  315. /**
  316. * Remove filter by column.
  317. *
  318. * @param string|array $column
  319. */
  320. public function removeFilter($column)
  321. {
  322. $this->filters = array_filter($this->filters, function (AbstractFilter $filter) use (&$column) {
  323. if (is_array($column)) {
  324. return ! in_array($filter->getColumn(), $column);
  325. }
  326. return $filter->getColumn() != $column;
  327. });
  328. }
  329. /**
  330. * @return array
  331. */
  332. public function getInputs()
  333. {
  334. if (!is_null($this->inputs)) {
  335. return $this->inputs;
  336. }
  337. $this->inputs = Arr::dot(Input::all());
  338. $this->inputs = array_filter($this->inputs, function ($input) {
  339. return $input !== '' && !is_null($input) && $input !== static::IGNORE_VALUE;
  340. });
  341. $this->sanitizeInputs($this->inputs);
  342. return $this->inputs;
  343. }
  344. /**
  345. * Get all conditions of the filters.
  346. *
  347. * @return array
  348. */
  349. public function conditions()
  350. {
  351. $inputs = $this->getInputs();
  352. if (empty($inputs)) {
  353. return [];
  354. }
  355. $params = [];
  356. foreach ($inputs as $key => $value) {
  357. Arr::set($params, $key, $value);
  358. }
  359. $conditions = [];
  360. foreach ($this->filters() as $filter) {
  361. $conditions[] = $filter->condition($params);
  362. }
  363. return tap(array_filter($conditions), function ($conditions) {
  364. if (!empty($conditions)) {
  365. $this->expand();
  366. }
  367. });
  368. }
  369. /**
  370. * @param $inputs
  371. *
  372. * @return array
  373. */
  374. protected function sanitizeInputs(&$inputs)
  375. {
  376. if (!$this->name) {
  377. return $inputs;
  378. }
  379. $inputs = collect($inputs)->filter(function ($input, $key) {
  380. return Str::startsWith($key, "{$this->name}_");
  381. })->mapWithKeys(function ($val, $key) {
  382. $key = str_replace("{$this->name}_", '', $key);
  383. return [$key => $val];
  384. })->toArray();
  385. }
  386. /**
  387. * Add a filter to grid.
  388. *
  389. * @param AbstractFilter $filter
  390. *
  391. * @return AbstractFilter
  392. */
  393. protected function addFilter(AbstractFilter $filter)
  394. {
  395. $this->layout->addFilter($filter);
  396. $filter->setParent($this);
  397. return $this->filters[] = $filter;
  398. }
  399. /**
  400. * Use a custom filter.
  401. *
  402. * @param AbstractFilter $filter
  403. *
  404. * @return AbstractFilter
  405. */
  406. public function use(AbstractFilter $filter)
  407. {
  408. return $this->addFilter($filter);
  409. }
  410. /**
  411. * Get all filters.
  412. *
  413. * @return AbstractFilter[]
  414. */
  415. public function filters()
  416. {
  417. return $this->filters;
  418. }
  419. /**
  420. * @param string $key
  421. * @param string $label
  422. *
  423. * @return Scope
  424. */
  425. public function scope($key, $label = '')
  426. {
  427. $scope = new Scope($key, $label);
  428. $this->scopes->push($scope);
  429. return $scope;
  430. }
  431. /**
  432. * Get all filter scopes.
  433. *
  434. * @return Collection
  435. */
  436. public function getScopes()
  437. {
  438. return $this->scopes;
  439. }
  440. /**
  441. * Get current scope.
  442. *
  443. * @return Scope|null
  444. */
  445. public function getCurrentScope()
  446. {
  447. $key = request(Scope::QUERY_NAME);
  448. return $this->scopes->first(function ($scope) use ($key) {
  449. return $scope->key == $key;
  450. });
  451. }
  452. /**
  453. * Get the name of current scope.
  454. *
  455. * @return string
  456. */
  457. public function getCurrentScopeName()
  458. {
  459. return request(Scope::QUERY_NAME);
  460. }
  461. /**
  462. * Get scope conditions.
  463. *
  464. * @return array
  465. */
  466. protected function scopeConditions()
  467. {
  468. if ($scope = $this->getCurrentScope()) {
  469. return $scope->condition();
  470. }
  471. return [];
  472. }
  473. /**
  474. * Expand filter container.
  475. *
  476. * @return $this
  477. */
  478. public function expand()
  479. {
  480. $this->expand = true;
  481. return $this;
  482. }
  483. /**
  484. * Execute the filter with conditions.
  485. *
  486. * @param bool $toArray
  487. *
  488. * @return array|Collection|mixed
  489. */
  490. public function execute($toArray = true)
  491. {
  492. $conditions = array_merge(
  493. $this->conditions(),
  494. $this->scopeConditions()
  495. );
  496. return $this->model->addConditions($conditions)->buildData($toArray);
  497. }
  498. /**
  499. * @param string $top
  500. * @param string $right
  501. * @param string $bottom
  502. * @param string $left
  503. * @return Filter
  504. */
  505. public function padding($top = '15px', $right = '15px', $bottom = '5px', $left = '')
  506. {
  507. return $this->style("padding:$top $right $bottom $left");
  508. }
  509. /**
  510. *
  511. * @param string $style
  512. * @return $this
  513. */
  514. public function style(?string $style)
  515. {
  516. $this->style = $style;
  517. return $this;
  518. }
  519. public function resetPosition()
  520. {
  521. return $this->style('padding:0;left:-4px;');
  522. }
  523. public function hiddenResetButtonText()
  524. {
  525. Admin::style(".{$this->containerClass} a.reset .hidden-xs{display:none}");
  526. return $this;
  527. }
  528. /**
  529. * Get the string contents of the filter view.
  530. *
  531. * @return \Illuminate\View\View|string
  532. */
  533. public function render()
  534. {
  535. if (empty($this->filters)) {
  536. return '';
  537. }
  538. $this->callComposing();
  539. return view($this->view)->with([
  540. 'action' => $this->action ?: $this->urlWithoutFilters(),
  541. 'layout' => $this->layout,
  542. 'filterID' => $this->disableCollapse ? '' : $this->filterID,
  543. 'expand' => $this->expand,
  544. 'style' => $this->style,
  545. 'border' => $this->border,
  546. 'containerClass' => $this->containerClass,
  547. 'disableResetButton' => $this->disableResetButton,
  548. ])->render();
  549. }
  550. /**
  551. * Get url without filter queryString.
  552. *
  553. * @return string
  554. */
  555. public function urlWithoutFilters()
  556. {
  557. /** @var Collection $columns */
  558. $columns = collect($this->filters)->map->getColumn()->flatten();
  559. $pageKey = 'page';
  560. if ($gridName = $this->model->getGrid()->getName()) {
  561. $pageKey = "{$gridName}_{$pageKey}";
  562. }
  563. $columns->push($pageKey);
  564. $groupNames = collect($this->filters)->filter(function ($filter) {
  565. return $filter instanceof Group;
  566. })->map(function (AbstractFilter $filter) {
  567. return "{$filter->getId()}_group";
  568. });
  569. return $this->fullUrlWithoutQuery(
  570. $columns->merge($groupNames)
  571. );
  572. }
  573. /**
  574. * Get url without scope queryString.
  575. *
  576. * @return string
  577. */
  578. public function urlWithoutScopes()
  579. {
  580. return $this->fullUrlWithoutQuery(Scope::QUERY_NAME);
  581. }
  582. /**
  583. * Get full url without query strings.
  584. *
  585. * @param Arrayable|array|string $keys
  586. *
  587. * @return string
  588. */
  589. protected function fullUrlWithoutQuery($keys)
  590. {
  591. if ($keys instanceof Arrayable) {
  592. $keys = $keys->toArray();
  593. }
  594. $keys = (array) $keys;
  595. $request = request();
  596. $query = $request->query();
  597. Arr::forget($query, $keys);
  598. $question = $request->getBaseUrl().$request->getPathInfo() == '/' ? '/?' : '?';
  599. return count($request->query()) > 0
  600. ? $request->url().$question.http_build_query($query)
  601. : $request->fullUrl();
  602. }
  603. /**
  604. * Generate a filter object and add to grid.
  605. *
  606. * @param string $method
  607. * @param array $arguments
  608. *
  609. * @return AbstractFilter|$this
  610. */
  611. public function __call($method, $arguments)
  612. {
  613. if (!empty(static::$supports[$method])) {
  614. $class = static::$supports[$method];
  615. if (!is_subclass_of($class, AbstractFilter::class)) {
  616. throw new \InvalidArgumentException("The class [{$class}] must be a type of ".AbstractFilter::class.'.');
  617. }
  618. return $this->addFilter(new $class(...$arguments));
  619. }
  620. if (isset(static::$defaultFilters[$method])) {
  621. return $this->addFilter(new static::$defaultFilters[$method](...$arguments));
  622. }
  623. return $this;
  624. }
  625. /**
  626. * @param string $name
  627. * @param string $filterClass
  628. */
  629. public static function extend($name, $filterClass)
  630. {
  631. static::$supports[$name] = $filterClass;
  632. }
  633. /**
  634. * @return array
  635. */
  636. public static function getExtensions()
  637. {
  638. return static::$supports;
  639. }
  640. }