Filter.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  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. $this->border = '';
  309. return $this;
  310. }
  311. public function withBorder()
  312. {
  313. $this->border = 'border-top:1px solid #f4f4f4;';
  314. return $this;
  315. }
  316. /**
  317. * Remove filter by column.
  318. *
  319. * @param mixed $id
  320. */
  321. public function removeFilter($column)
  322. {
  323. $this->filters = array_filter($this->filters, function (AbstractFilter $filter) use ($column) {
  324. return $filter->getColumn() != $column;
  325. });
  326. }
  327. /**
  328. * @return array
  329. */
  330. public function getInputs()
  331. {
  332. if (!is_null($this->inputs)) {
  333. return $this->inputs;
  334. }
  335. $this->inputs = Arr::dot(Input::all());
  336. $this->inputs = array_filter($this->inputs, function ($input) {
  337. return $input !== '' && !is_null($input) && $input !== static::IGNORE_VALUE;
  338. });
  339. $this->sanitizeInputs($this->inputs);
  340. return $this->inputs;
  341. }
  342. /**
  343. * Get all conditions of the filters.
  344. *
  345. * @return array
  346. */
  347. public function conditions()
  348. {
  349. $inputs = $this->getInputs();
  350. if (empty($inputs)) {
  351. return [];
  352. }
  353. $params = [];
  354. foreach ($inputs as $key => $value) {
  355. Arr::set($params, $key, $value);
  356. }
  357. $conditions = [];
  358. foreach ($this->filters() as $filter) {
  359. $conditions[] = $filter->condition($params);
  360. }
  361. return tap(array_filter($conditions), function ($conditions) {
  362. if (!empty($conditions)) {
  363. $this->expand();
  364. }
  365. });
  366. }
  367. /**
  368. * @param $inputs
  369. *
  370. * @return array
  371. */
  372. protected function sanitizeInputs(&$inputs)
  373. {
  374. if (!$this->name) {
  375. return $inputs;
  376. }
  377. $inputs = collect($inputs)->filter(function ($input, $key) {
  378. return Str::startsWith($key, "{$this->name}_");
  379. })->mapWithKeys(function ($val, $key) {
  380. $key = str_replace("{$this->name}_", '', $key);
  381. return [$key => $val];
  382. })->toArray();
  383. }
  384. /**
  385. * Add a filter to grid.
  386. *
  387. * @param AbstractFilter $filter
  388. *
  389. * @return AbstractFilter
  390. */
  391. protected function addFilter(AbstractFilter $filter)
  392. {
  393. $this->layout->addFilter($filter);
  394. $filter->setParent($this);
  395. return $this->filters[] = $filter;
  396. }
  397. /**
  398. * Use a custom filter.
  399. *
  400. * @param AbstractFilter $filter
  401. *
  402. * @return AbstractFilter
  403. */
  404. public function use(AbstractFilter $filter)
  405. {
  406. return $this->addFilter($filter);
  407. }
  408. /**
  409. * Get all filters.
  410. *
  411. * @return AbstractFilter[]
  412. */
  413. public function filters()
  414. {
  415. return $this->filters;
  416. }
  417. /**
  418. * @param string $key
  419. * @param string $label
  420. *
  421. * @return Scope
  422. */
  423. public function scope($key, $label = '')
  424. {
  425. $scope = new Scope($key, $label);
  426. $this->scopes->push($scope);
  427. return $scope;
  428. }
  429. /**
  430. * Get all filter scopes.
  431. *
  432. * @return Collection
  433. */
  434. public function getScopes()
  435. {
  436. return $this->scopes;
  437. }
  438. /**
  439. * Get current scope.
  440. *
  441. * @return Scope|null
  442. */
  443. public function getCurrentScope()
  444. {
  445. $key = request(Scope::QUERY_NAME);
  446. return $this->scopes->first(function ($scope) use ($key) {
  447. return $scope->key == $key;
  448. });
  449. }
  450. /**
  451. * Get the name of current scope.
  452. *
  453. * @return string
  454. */
  455. public function getCurrentScopeName()
  456. {
  457. return request(Scope::QUERY_NAME);
  458. }
  459. /**
  460. * Get scope conditions.
  461. *
  462. * @return array
  463. */
  464. protected function scopeConditions()
  465. {
  466. if ($scope = $this->getCurrentScope()) {
  467. return $scope->condition();
  468. }
  469. return [];
  470. }
  471. /**
  472. * Expand filter container.
  473. *
  474. * @return $this
  475. */
  476. public function expand()
  477. {
  478. $this->expand = true;
  479. return $this;
  480. }
  481. /**
  482. * Execute the filter with conditions.
  483. *
  484. * @param bool $toArray
  485. *
  486. * @return array|Collection|mixed
  487. */
  488. public function execute($toArray = true)
  489. {
  490. $conditions = array_merge(
  491. $this->conditions(),
  492. $this->scopeConditions()
  493. );
  494. return $this->model->addConditions($conditions)->buildData($toArray);
  495. }
  496. /**
  497. * @param string $top
  498. * @param string $right
  499. * @param string $bottom
  500. * @param string $left
  501. * @return Filter
  502. */
  503. public function padding($top = '15px', $right = '15px', $bottom = '5px', $left = '')
  504. {
  505. return $this->style("padding:$top $right $bottom $left");
  506. }
  507. /**
  508. *
  509. * @param string $style
  510. * @return $this
  511. */
  512. public function style(?string $style)
  513. {
  514. $this->style = $style;
  515. return $this;
  516. }
  517. public function resetPosition()
  518. {
  519. return $this->style('padding:0;left:-4px;');
  520. }
  521. public function hiddenResetButtonText()
  522. {
  523. Admin::style(".{$this->containerClass} a.reset .hidden-xs{display:none}");
  524. return $this;
  525. }
  526. /**
  527. * Get the string contents of the filter view.
  528. *
  529. * @return \Illuminate\View\View|string
  530. */
  531. public function render()
  532. {
  533. if (empty($this->filters)) {
  534. return '';
  535. }
  536. $this->callComposing();
  537. return view($this->view)->with([
  538. 'action' => $this->action ?: $this->urlWithoutFilters(),
  539. 'layout' => $this->layout,
  540. 'filterID' => $this->disableCollapse ? '' : $this->filterID,
  541. 'expand' => $this->expand,
  542. 'style' => $this->style,
  543. 'border' => $this->border,
  544. 'containerClass' => $this->containerClass,
  545. 'disableResetButton' => $this->disableResetButton,
  546. ])->render();
  547. }
  548. /**
  549. * Get url without filter queryString.
  550. *
  551. * @return string
  552. */
  553. public function urlWithoutFilters()
  554. {
  555. /** @var Collection $columns */
  556. $columns = collect($this->filters)->map->getColumn()->flatten();
  557. $pageKey = 'page';
  558. if ($gridName = $this->model->getGrid()->getName()) {
  559. $pageKey = "{$gridName}_{$pageKey}";
  560. }
  561. $columns->push($pageKey);
  562. $groupNames = collect($this->filters)->filter(function ($filter) {
  563. return $filter instanceof Group;
  564. })->map(function (AbstractFilter $filter) {
  565. return "{$filter->getId()}_group";
  566. });
  567. return $this->fullUrlWithoutQuery(
  568. $columns->merge($groupNames)
  569. );
  570. }
  571. /**
  572. * Get url without scope queryString.
  573. *
  574. * @return string
  575. */
  576. public function urlWithoutScopes()
  577. {
  578. return $this->fullUrlWithoutQuery(Scope::QUERY_NAME);
  579. }
  580. /**
  581. * Get full url without query strings.
  582. *
  583. * @param Arrayable|array|string $keys
  584. *
  585. * @return string
  586. */
  587. protected function fullUrlWithoutQuery($keys)
  588. {
  589. if ($keys instanceof Arrayable) {
  590. $keys = $keys->toArray();
  591. }
  592. $keys = (array) $keys;
  593. $request = request();
  594. $query = $request->query();
  595. Arr::forget($query, $keys);
  596. $question = $request->getBaseUrl().$request->getPathInfo() == '/' ? '/?' : '?';
  597. return count($request->query()) > 0
  598. ? $request->url().$question.http_build_query($query)
  599. : $request->fullUrl();
  600. }
  601. /**
  602. * Generate a filter object and add to grid.
  603. *
  604. * @param string $method
  605. * @param array $arguments
  606. *
  607. * @return AbstractFilter|$this
  608. */
  609. public function __call($method, $arguments)
  610. {
  611. if (!empty(static::$supports[$method])) {
  612. $class = static::$supports[$method];
  613. if (!is_subclass_of($class, AbstractFilter::class)) {
  614. throw new \InvalidArgumentException("The class [{$class}] must be a type of ".AbstractFilter::class.'.');
  615. }
  616. return $this->addFilter(new $class(...$arguments));
  617. }
  618. if (isset(static::$defaultFilters[$method])) {
  619. return $this->addFilter(new static::$defaultFilters[$method](...$arguments));
  620. }
  621. return $this;
  622. }
  623. /**
  624. * @param string $name
  625. * @param string $filterClass
  626. */
  627. public static function extend($name, $filterClass)
  628. {
  629. static::$supports[$name] = $filterClass;
  630. }
  631. /**
  632. * @return array
  633. */
  634. public static function getExtensions()
  635. {
  636. return static::$supports;
  637. }
  638. }