Cleaner.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Search\Request;
  7. use Magento\Framework\Exception\StateException;
  8. use Magento\Framework\Search\Request\Aggregation\StatusInterface as AggregationStatus;
  9. use Magento\Framework\Phrase;
  10. /**
  11. * @api
  12. * @since 100.0.2
  13. */
  14. class Cleaner
  15. {
  16. /**
  17. * @var array
  18. */
  19. private $requestData;
  20. /**
  21. * @var array
  22. */
  23. private $mappedQueries;
  24. /**
  25. * @var array
  26. */
  27. private $mappedFilters;
  28. /**
  29. * @var AggregationStatus
  30. */
  31. private $aggregationStatus;
  32. /**
  33. * Cleaner constructor
  34. *
  35. * @param AggregationStatus $aggregationStatus
  36. */
  37. public function __construct(AggregationStatus $aggregationStatus)
  38. {
  39. $this->aggregationStatus = $aggregationStatus;
  40. }
  41. /**
  42. * Clean not binder queries and filters
  43. *
  44. * @param array $requestData
  45. * @return array
  46. */
  47. public function clean(array $requestData)
  48. {
  49. $this->clear();
  50. $this->requestData = $requestData;
  51. $this->cleanQuery($requestData['query']);
  52. $this->cleanAggregations();
  53. $requestData = $this->requestData;
  54. $this->clear();
  55. if (empty($requestData['queries']) && empty($requestData['filters'])) {
  56. throw new EmptyRequestDataException(
  57. new Phrase("The request query and filters aren't set. Verify the query and filters and try again.")
  58. );
  59. }
  60. return $requestData;
  61. }
  62. /**
  63. * Clear don't bind queries
  64. *
  65. * @param string $queryName
  66. * @return void
  67. * @throws StateException
  68. * @throws \Exception
  69. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  70. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  71. */
  72. private function cleanQuery($queryName)
  73. {
  74. if (!isset($this->requestData['queries'][$queryName])) {
  75. throw new \Exception('Query ' . $queryName . ' does not exist');
  76. } elseif (in_array($queryName, $this->mappedQueries)) {
  77. throw new StateException(
  78. new Phrase('A cycle was found. The "%1" query is already used in the request hierarchy.', [$queryName])
  79. );
  80. }
  81. $this->mappedQueries[] = $queryName;
  82. $query = $this->requestData['queries'][$queryName];
  83. switch ($query['type']) {
  84. case QueryInterface::TYPE_BOOL:
  85. $queryReference = $this->processQueryReference($query['queryReference']);
  86. if (empty($queryReference)) {
  87. unset($this->requestData['queries'][$queryName]);
  88. } else {
  89. $this->requestData['queries'][$queryName]['queryReference'] = array_values($queryReference);
  90. }
  91. break;
  92. case QueryInterface::TYPE_MATCH:
  93. if (!array_key_exists('is_bind', $query)) {
  94. unset($this->requestData['queries'][$queryName]);
  95. }
  96. break;
  97. case QueryInterface::TYPE_FILTER:
  98. if (isset($query['queryReference'][0])) {
  99. $fQueryName = $query['queryReference'][0]['ref'];
  100. $this->cleanQuery($fQueryName);
  101. if (!isset($this->requestData['queries'][$fQueryName])) {
  102. unset($this->requestData['queries'][$queryName]);
  103. }
  104. } elseif (isset($query['filterReference'][0])) {
  105. $filterName = $query['filterReference'][0]['ref'];
  106. $this->cleanFilter($filterName);
  107. if (!isset($this->requestData['filters'][$filterName])) {
  108. unset($this->requestData['queries'][$queryName]);
  109. }
  110. } else {
  111. throw new \Exception('Reference is not provided');
  112. }
  113. break;
  114. default:
  115. throw new \InvalidArgumentException('Invalid query type');
  116. }
  117. }
  118. /**
  119. * Clean aggregations if we don't need to process them
  120. *
  121. * @return void
  122. */
  123. private function cleanAggregations()
  124. {
  125. if (!$this->aggregationStatus->isEnabled()) {
  126. $this->requestData['aggregations'] = [];
  127. } else {
  128. if (array_key_exists('aggregations', $this->requestData) && is_array($this->requestData['aggregations'])) {
  129. foreach ($this->requestData['aggregations'] as $aggregationName => $aggregationValue) {
  130. switch ($aggregationValue['type']) {
  131. case 'dynamicBucket':
  132. if (is_string($aggregationValue['method'])
  133. && preg_match('/^\$(.+)\$$/si', $aggregationValue['method'])
  134. ) {
  135. unset($this->requestData['aggregations'][$aggregationName]);
  136. }
  137. }
  138. }
  139. }
  140. }
  141. }
  142. /**
  143. * Clear don't bind filters
  144. *
  145. * @param string $filterName
  146. * @return void
  147. * @throws StateException
  148. * @throws \Exception
  149. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  150. */
  151. private function cleanFilter($filterName)
  152. {
  153. if (!isset($this->requestData['filters'][$filterName])) {
  154. throw new \Exception('Filter ' . $filterName . ' does not exist');
  155. } elseif (in_array($filterName, $this->mappedFilters)) {
  156. throw new StateException(
  157. new Phrase(
  158. 'A cycle was found. The "%1" filter is already used in the request hierarchy.',
  159. [$filterName]
  160. )
  161. );
  162. }
  163. $this->mappedFilters[] = $filterName;
  164. $filter = $this->requestData['filters'][$filterName];
  165. switch ($filter['type']) {
  166. case FilterInterface::TYPE_WILDCARD:
  167. case FilterInterface::TYPE_TERM:
  168. if (!array_key_exists('is_bind', $filter)) {
  169. unset($this->requestData['filters'][$filterName]);
  170. }
  171. break;
  172. case FilterInterface::TYPE_RANGE:
  173. $keys = ['from', 'to'];
  174. foreach ($keys as $key) {
  175. if (isset($filter[$key]) && preg_match('/^\$(.+)\$$/si', $filter[$key])) {
  176. unset($this->requestData['filters'][$filterName][$key]);
  177. }
  178. }
  179. $filterKeys = array_keys($this->requestData['filters'][$filterName]);
  180. if (count(array_diff($keys, $filterKeys)) == count($keys)) {
  181. unset($this->requestData['filters'][$filterName]);
  182. }
  183. break;
  184. case FilterInterface::TYPE_BOOL:
  185. $filterReference = $this->processFilterReference($filter['filterReference']);
  186. if (empty($filterReference)) {
  187. unset($this->requestData['filters'][$filterName]);
  188. } else {
  189. $this->requestData['filters'][$filterName]['filterReference'] = array_values($filterReference);
  190. }
  191. break;
  192. default:
  193. throw new \InvalidArgumentException('Invalid filter type');
  194. }
  195. }
  196. /**
  197. * Aggregate Queries by clause
  198. *
  199. * @param array $queryReference
  200. * @return array
  201. */
  202. private function processQueryReference($queryReference)
  203. {
  204. foreach ($queryReference as $key => $value) {
  205. $this->cleanQuery($value['ref']);
  206. if (!isset($this->requestData['queries'][$value['ref']])) {
  207. unset($queryReference[$key]);
  208. }
  209. }
  210. return $queryReference;
  211. }
  212. /**
  213. * Aggregate Filters by clause
  214. *
  215. * @param array $filterReference
  216. * @return array
  217. */
  218. private function processFilterReference($filterReference)
  219. {
  220. foreach ($filterReference as $key => $value) {
  221. $this->cleanFilter($value['ref']);
  222. if (!isset($this->requestData['filters'][$value['ref']])) {
  223. unset($filterReference[$key]);
  224. }
  225. }
  226. return $filterReference;
  227. }
  228. /**
  229. * Clear variables to default status
  230. *
  231. * @return void
  232. */
  233. private function clear()
  234. {
  235. $this->mappedQueries = [];
  236. $this->mappedFilters = [];
  237. $this->requestData = [];
  238. }
  239. }