HasQuickSearch.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. namespace Dcat\Admin\Grid\Concerns;
  3. use Dcat\Admin\Grid\Column;
  4. use Dcat\Admin\Grid\Tools;
  5. use Illuminate\Support\Collection;
  6. use Dcat\Admin\Grid\Model;
  7. use Illuminate\Support\Str;
  8. /**
  9. * @property Collection $columns
  10. * @property Tools $tools
  11. *
  12. * @method Model model()
  13. */
  14. trait HasQuickSearch
  15. {
  16. /**
  17. * @var array|string|\Closure
  18. */
  19. protected $search;
  20. /**
  21. * @var Tools\QuickSearch
  22. */
  23. protected $quickSearch;
  24. /**
  25. * @param array|string|\Closure
  26. *
  27. * @return Tools\QuickSearch
  28. */
  29. public function quickSearch($search = null)
  30. {
  31. if (func_num_args() > 1) {
  32. $this->search = func_get_args();
  33. } else {
  34. $this->search = $search;
  35. }
  36. if ($this->quickSearch) {
  37. return $this->quickSearch;
  38. }
  39. return tap(new Tools\QuickSearch(), function ($search) {
  40. $search->setGrid($this);
  41. $this->quickSearch = $search;
  42. });
  43. }
  44. /**
  45. * @param string $gridName
  46. */
  47. protected function setQuickSearchQueryName($gridName)
  48. {
  49. if ($this->quickSearch) {
  50. $this->quickSearch->setQueryName($gridName.'__search');
  51. }
  52. }
  53. /**
  54. * @return Tools\QuickSearch
  55. */
  56. public function getQuickSearch()
  57. {
  58. return $this->quickSearch;
  59. }
  60. /**
  61. * @return \Illuminate\View\View|string
  62. */
  63. public function renderQuickSearch()
  64. {
  65. if (! $this->quickSearch) {
  66. return '';
  67. }
  68. return $this->quickSearch->render();
  69. }
  70. /**
  71. * Apply the search query to the query.
  72. *
  73. * @return mixed|void
  74. */
  75. public function applyQuickSearch()
  76. {
  77. if (! $this->quickSearch) {
  78. return;
  79. }
  80. if (! $query = request()->get($this->quickSearch->getQueryName())) {
  81. return;
  82. }
  83. if ($this->search instanceof \Closure) {
  84. return call_user_func($this->search, $this->model(), $query);
  85. }
  86. if (is_string($this->search)) {
  87. $this->search = [$this->search];
  88. }
  89. if (is_array($this->search)) {
  90. foreach ($this->search as $column) {
  91. $this->addWhereLikeBinding($column, true, '%' . $query . '%');
  92. }
  93. } elseif (is_null($this->search)) {
  94. $this->addWhereBindings($query);
  95. }
  96. }
  97. /**
  98. * Add where bindings.
  99. *
  100. * @param string $query
  101. */
  102. protected function addWhereBindings($query)
  103. {
  104. $queries = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($query));
  105. if (! $queries = $this->parseQueryBindings($queries)) {
  106. $this->addWhereBasicBinding($this->getKeyName(), false, '=', '___');
  107. return;
  108. }
  109. foreach ($queries as list($column, $condition, $or)) {
  110. if (preg_match('/(?<not>!?)\((?<values>.+)\)/', $condition, $match) !== 0) {
  111. $this->addWhereInBinding($column, $or, (bool)$match['not'], $match['values']);
  112. continue;
  113. }
  114. if (preg_match('/\[(?<start>.*?),(?<end>.*?)]/', $condition, $match) !== 0) {
  115. $this->addWhereBetweenBinding($column, $or, $match['start'], $match['end']);
  116. continue;
  117. }
  118. if (preg_match('/(?<function>date|time|day|month|year),(?<value>.*)/', $condition, $match) !== 0) {
  119. $this->addWhereDatetimeBinding($column, $or, $match['function'], $match['value']);
  120. continue;
  121. }
  122. if (preg_match('/(?<pattern>%[^%]+%)/', $condition, $match) !== 0) {
  123. $this->addWhereLikeBinding($column, $or, $match['pattern']);
  124. continue;
  125. }
  126. if (preg_match('/(?<pattern>[^%]+%)/', $condition, $match) !== 0) {
  127. $this->addWhereLikeBinding($column, $or, $match['pattern']);
  128. continue;
  129. }
  130. if (preg_match('/\/(?<value>.*)\//', $condition, $match) !== 0) {
  131. $this->addWhereBasicBinding($column, $or, 'REGEXP', $match['value']);
  132. continue;
  133. }
  134. if (preg_match('/(?<operator>>=?|<=?|!=|%){0,1}(?<value>.*)/', $condition, $match) !== 0) {
  135. $this->addWhereBasicBinding($column, $or, $match['operator'], $match['value']);
  136. continue;
  137. }
  138. }
  139. }
  140. /**
  141. * Parse quick query bindings.
  142. *
  143. * @param array $queries
  144. *
  145. * @return array
  146. */
  147. protected function parseQueryBindings(array $queries)
  148. {
  149. $columnMap = $this->columns->mapWithKeys(function (Column $column) {
  150. $label = $column->getLabel();
  151. $name = $column->getName();
  152. return [$label => $name, $name => $name];
  153. });
  154. return collect($queries)->map(function ($query) use ($columnMap) {
  155. $segments = explode(':', $query, 2);
  156. if (count($segments) != 2) {
  157. return;
  158. }
  159. $or = false;
  160. list($column, $condition) = $segments;
  161. if (Str::startsWith($column, '|')) {
  162. $or = true;
  163. $column = substr($column, 1);
  164. }
  165. $column = $columnMap[$column] ?? null;
  166. if (!$column) {
  167. return;
  168. }
  169. return [$column, $condition, $or];
  170. })->filter()->toArray();
  171. }
  172. /**
  173. * Add where like binding to model query.
  174. *
  175. * @param string $column
  176. * @param bool $or
  177. * @param string $pattern
  178. */
  179. protected function addWhereLikeBinding(?string $column, ?bool $or, ?string $pattern)
  180. {
  181. $likeOperator = 'like';
  182. $method = $or ? 'orWhere' : 'where';
  183. $this->model()->{$method}($column, $likeOperator, $pattern);
  184. }
  185. /**
  186. * Add where date time function binding to model query.
  187. *
  188. * @param string $column
  189. * @param bool $or
  190. * @param string $function
  191. * @param string $value
  192. */
  193. protected function addWhereDatetimeBinding(?string $column, ?bool $or, ?string $function, ?string $value)
  194. {
  195. $method = ($or ? 'orWhere' : 'where') . ucfirst($function);
  196. $this->model()->$method($column, $value);
  197. }
  198. /**
  199. * Add where in binding to the model query.
  200. *
  201. * @param string $column
  202. * @param bool $or
  203. * @param bool $not
  204. * @param string $values
  205. */
  206. protected function addWhereInBinding(?string $column, ?bool $or, ?bool $not, ?string $values)
  207. {
  208. $values = explode(',', $values);
  209. foreach ($values as $key => $value) {
  210. if ($value === 'NULL') {
  211. $values[$key] = null;
  212. }
  213. }
  214. $where = $or ? 'orWhere' : 'where';
  215. $method = $where . ($not ? 'NotIn' : 'In');
  216. $this->model()->$method($column, $values);
  217. }
  218. /**
  219. * Add where between binding to the model query.
  220. *
  221. * @param string $column
  222. * @param bool $or
  223. * @param string $start
  224. * @param string $end
  225. */
  226. protected function addWhereBetweenBinding(?string $column, ?bool $or, ?string $start, ?string $end)
  227. {
  228. $method = $or ? 'orWhereBetween' : 'whereBetween';
  229. $this->model()->$method($column, [$start, $end]);
  230. }
  231. /**
  232. * Add where basic binding to the model query.
  233. *
  234. * @param string $column
  235. * @param bool $or
  236. * @param string $operator
  237. * @param string $value
  238. */
  239. protected function addWhereBasicBinding(?string $column, ?bool $or, ?string $operator, ?string $value)
  240. {
  241. $method = $or ? 'orWhere' : 'where';
  242. $operator = $operator ?: '=';
  243. if ($operator == '%') {
  244. $operator = 'like';
  245. $value = "%{$value}%";
  246. }
  247. if ($value === 'NULL') {
  248. $value = null;
  249. }
  250. if (Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
  251. $value = substr($value, 1, -1);
  252. }
  253. $this->model()->{$method}($column, $operator, $value);
  254. }
  255. }