Converter.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Indexer\Config;
  7. use Magento\Framework\Config\ConverterInterface;
  8. use Magento\Framework\Exception\ConfigurationMismatchException;
  9. use Magento\Framework\Phrase;
  10. class Converter implements ConverterInterface
  11. {
  12. /**
  13. * Convert dom node tree to array
  14. *
  15. * @param \DOMDocument $source
  16. * @return array
  17. * @throws \InvalidArgumentException
  18. */
  19. public function convert($source)
  20. {
  21. $output = [];
  22. $xpath = new \DOMXPath($source);
  23. $indexers = $xpath->evaluate('/config/indexer');
  24. /** @var $typeNode \DOMNode */
  25. foreach ($indexers as $indexerNode) {
  26. $data = [];
  27. $indexerId = $this->getAttributeValue($indexerNode, 'id');
  28. $data['indexer_id'] = $indexerId;
  29. $data['primary'] = $this->getAttributeValue($indexerNode, 'primary');
  30. $data['view_id'] = $this->getAttributeValue($indexerNode, 'view_id');
  31. $data['action_class'] = $this->getAttributeValue($indexerNode, 'class');
  32. $data['shared_index'] = $this->getAttributeValue($indexerNode, 'shared_index');
  33. $data['title'] = '';
  34. $data['description'] = '';
  35. $data['dependencies'] = [];
  36. /** @var $childNode \DOMNode */
  37. foreach ($indexerNode->childNodes as $childNode) {
  38. if ($childNode->nodeType != XML_ELEMENT_NODE) {
  39. continue;
  40. }
  41. /** @var $childNode \DOMElement */
  42. $data = $this->convertChild($childNode, $data);
  43. }
  44. $output[$indexerId] = $data;
  45. }
  46. $output = $this->sortByDependencies($output);
  47. return $output;
  48. }
  49. /**
  50. * Get attribute value
  51. *
  52. * @param \DOMNode $input
  53. * @param string $attributeName
  54. * @param mixed $default
  55. * @return null|string
  56. */
  57. protected function getAttributeValue(\DOMNode $input, $attributeName, $default = null)
  58. {
  59. $node = $input->attributes->getNamedItem($attributeName);
  60. return $node ? $node->nodeValue : $default;
  61. }
  62. /**
  63. * Convert child from dom to array
  64. *
  65. * @param \DOMElement $childNode
  66. * @param array $data
  67. * @return array
  68. */
  69. protected function convertChild(\DOMElement $childNode, $data)
  70. {
  71. $data['fieldsets'] = isset($data['fieldsets']) ? $data['fieldsets'] : [];
  72. switch ($childNode->nodeName) {
  73. case 'title':
  74. $data['title'] = $childNode->nodeValue;
  75. break;
  76. case 'description':
  77. $data['description'] = $childNode->nodeValue;
  78. break;
  79. case 'saveHandler':
  80. $data['saveHandler'] = $this->getAttributeValue($childNode, 'class');
  81. break;
  82. case 'structure':
  83. $data['structure'] = $this->getAttributeValue($childNode, 'class');
  84. break;
  85. case 'fieldset':
  86. $data = $this->convertFieldset($childNode, $data);
  87. break;
  88. case 'dependencies':
  89. $data = $this->convertDependencies($childNode, $data);
  90. break;
  91. }
  92. return $data;
  93. }
  94. /**
  95. * Convert fieldset
  96. *
  97. * @param \DOMElement $node
  98. * @param array $data
  99. * @return array
  100. * @SuppressWarnings(PHPMD.NPathComplexity)
  101. */
  102. protected function convertFieldset(\DOMElement $node, $data)
  103. {
  104. $data['fieldsets'] = isset($data['fieldsets']) ? $data['fieldsets'] : [];
  105. $data['fieldsets'][$this->getAttributeValue($node, 'name')] = [
  106. 'source' => $this->getAttributeValue($node, 'source'),
  107. 'name' => $this->getAttributeValue($node, 'name'),
  108. 'provider' => $this->getAttributeValue($node, 'provider'),
  109. 'fields' => [],
  110. ];
  111. foreach ($node->childNodes as $childNode) {
  112. if ($childNode->nodeType != XML_ELEMENT_NODE) {
  113. continue;
  114. }
  115. switch ($childNode->nodeName) {
  116. case 'field':
  117. $data['fieldsets'][$this->getAttributeValue($node, 'name')] = $this->convertField(
  118. $childNode,
  119. $data['fieldsets'][$this->getAttributeValue($node, 'name')]
  120. );
  121. break;
  122. case 'reference':
  123. $data['fieldsets'][$this->getAttributeValue($node, 'name')]['references'] =
  124. isset($data['fieldsets'][$this->getAttributeValue($node, 'name')]['references'])
  125. ? $data['fieldsets'][$this->getAttributeValue($node, 'name')]['references']
  126. : [];
  127. $data['fieldsets'][$this->getAttributeValue($node, 'name')]['references']
  128. [$this->getAttributeValue($childNode, 'fieldset')] =
  129. isset(
  130. $data['fieldsets'][$this->getAttributeValue($node, 'name')]
  131. ['references'][$this->getAttributeValue($childNode, 'fieldset')]
  132. )
  133. ? $data['fieldsets'][$this->getAttributeValue($node, 'name')]
  134. ['references'][$this->getAttributeValue($childNode, 'fieldset')]
  135. : [];
  136. $data['fieldsets'][$this->getAttributeValue($node, 'name')]['references']
  137. [$this->getAttributeValue($childNode, 'fieldset')] = [
  138. 'fieldset' => $this->getAttributeValue($childNode, 'fieldset'),
  139. 'from' => $this->getAttributeValue($childNode, 'from'),
  140. 'to' => $this->getAttributeValue($childNode, 'to'),
  141. ];
  142. $this->addVirtualField(
  143. $this->getAttributeValue($childNode, 'fieldset'),
  144. $this->getAttributeValue($childNode, 'to'),
  145. $data
  146. );
  147. $this->addVirtualField(
  148. $this->getAttributeValue($node, 'name'),
  149. $this->getAttributeValue($childNode, 'from'),
  150. $data
  151. );
  152. break;
  153. }
  154. }
  155. return $this->sorting($data);
  156. }
  157. /**
  158. * Convert dependencies node
  159. *
  160. * @param \DOMElement $node
  161. * @param array $data
  162. * @return array
  163. */
  164. private function convertDependencies(\DOMElement $node, array $data): array
  165. {
  166. $data['dependencies'] = $data['dependencies'] ?? [];
  167. /** @var $childNode \DOMNode */
  168. foreach ($node->childNodes as $childNode) {
  169. switch ($childNode->nodeName) {
  170. case 'indexer':
  171. $indexerId = $this->getAttributeValue($childNode, 'id');
  172. $data['dependencies'][] = $indexerId;
  173. break;
  174. }
  175. }
  176. return $data;
  177. }
  178. /**
  179. * Add virtual field
  180. *
  181. * @param string $fieldset
  182. * @param string $field
  183. * @param array $data
  184. * @return void
  185. */
  186. protected function addVirtualField($fieldset, $field, $data)
  187. {
  188. if (!isset($data['fieldsets'][$fieldset]['fields'][$field])) {
  189. $data['fieldsets'][$fieldset]['fields'][$field] = [
  190. 'type' => 'virtual',
  191. 'name' => $field,
  192. ];
  193. }
  194. }
  195. /**
  196. * Convert field
  197. *
  198. * @param \DOMElement $node
  199. * @param array $data
  200. * @return array
  201. */
  202. protected function convertField(\DOMElement $node, $data)
  203. {
  204. $data['fields'][$this->getAttributeValue($node, 'name')] = [
  205. 'name' => $this->getAttributeValue($node, 'name'),
  206. 'handler' => $this->getAttributeValue($node, 'handler'),
  207. 'origin' => $this->getAttributeValue($node, 'origin') ?: $this->getAttributeValue($node, 'name'),
  208. 'dataType' => $this->getAttributeValue($node, 'dataType'),
  209. 'type' => $node->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'type'),
  210. ];
  211. $data['fields'][$this->getAttributeValue($node, 'name')]['filters'] = [];
  212. /** @var $childNode \DOMNode */
  213. foreach ($node->childNodes as $childNode) {
  214. if ($childNode->nodeType != XML_ELEMENT_NODE) {
  215. continue;
  216. }
  217. $data['fields'][$this->getAttributeValue($node, 'name')]['filters'][]
  218. = $this->getAttributeValue($childNode, 'class');
  219. }
  220. return $data;
  221. }
  222. /**
  223. * Return node value translated if applicable
  224. *
  225. * @param \DOMNode $node
  226. * @return string
  227. * @deprecated 101.0.0
  228. */
  229. protected function getTranslatedNodeValue(\DOMNode $node)
  230. {
  231. $value = $node->nodeValue;
  232. if ($this->getAttributeValue($node, 'translate') == 'true') {
  233. $value = new \Magento\Framework\Phrase($value);
  234. }
  235. return $value;
  236. }
  237. /**
  238. * Sorting fieldset
  239. *
  240. * @param array $data
  241. * @return array
  242. */
  243. protected function sorting($data)
  244. {
  245. usort($data['fieldsets'], function ($current, $parent) use ($data) {
  246. if (!isset($current['references']) && $data['primary'] == $current['name']
  247. || isset($parent['references'][$current['name']])
  248. ) {
  249. return -1;
  250. } elseif (!isset($parent['references']) || isset($current['references'][$parent['name']])) {
  251. return 1;
  252. } else {
  253. return 0;
  254. }
  255. });
  256. return $data;
  257. }
  258. /**
  259. * Sort the list of indexers using "dependencies" node data.
  260. *
  261. * This method also sort data in the "dependencies" node of indexers.
  262. *
  263. * @param array $indexers
  264. * @return array
  265. */
  266. private function sortByDependencies(array $indexers): array
  267. {
  268. $expanded = [];
  269. foreach (array_keys($indexers) as $indexerId) {
  270. $expanded[] = [
  271. 'indexerId' => $indexerId,
  272. 'dependencies' => $this->expandDependencies($indexers, $indexerId),
  273. ];
  274. }
  275. /**
  276. * Used this algorithm of sorting not quicksort, because it guarantees
  277. * correct sequence of indexers with multiple dependencies.
  278. */
  279. $maxIndex = count($expanded) - 1;
  280. for ($i = 0; $i < $maxIndex; $i++) {
  281. for ($j = $i + 1; $j <= $maxIndex; $j++) {
  282. if (in_array($expanded[$j]['indexerId'], $expanded[$i]['dependencies'], true)) {
  283. $temp = $expanded[$i];
  284. $expanded[$i] = $expanded[$j];
  285. $expanded[$j] = $temp;
  286. }
  287. }
  288. }
  289. $orderedIndexerIds = array_map(
  290. function ($item) {
  291. return $item['indexerId'];
  292. },
  293. $expanded
  294. );
  295. $result = [];
  296. foreach ($orderedIndexerIds as $indexerId) {
  297. $result[$indexerId] = $indexers[$indexerId];
  298. $result[$indexerId]['dependencies'] = array_values(
  299. array_intersect($orderedIndexerIds, $result[$indexerId]['dependencies'] ?? [])
  300. );
  301. }
  302. return $result;
  303. }
  304. /**
  305. * Accumulate information about all transitive "dependencies" references.
  306. *
  307. * @param array $list
  308. * @param string $indexerId
  309. * @param array $accumulated
  310. * @return array
  311. * @throws ConfigurationMismatchException
  312. */
  313. private function expandDependencies(array $list, string $indexerId, array $accumulated = []): array
  314. {
  315. $accumulated[] = $indexerId;
  316. $result = $list[$indexerId]['dependencies'] ?? [];
  317. foreach ($result as $relatedIndexerId) {
  318. if (in_array($relatedIndexerId, $accumulated)) {
  319. throw new ConfigurationMismatchException(
  320. new Phrase(
  321. "Circular dependency references from '%indexerId' to '%relatedIndexerId'.",
  322. [
  323. 'indexerId' => $indexerId,
  324. 'relatedIndexerId' => $relatedIndexerId,
  325. ]
  326. )
  327. );
  328. }
  329. if (!isset($list[$relatedIndexerId])) {
  330. throw new ConfigurationMismatchException(
  331. new Phrase(
  332. "Dependency declaration '%relatedIndexerId' in "
  333. . "'%indexerId' to the non-existing indexer.",
  334. [
  335. 'indexerId' => $indexerId,
  336. 'relatedIndexerId' => $relatedIndexerId,
  337. ]
  338. )
  339. );
  340. }
  341. $relatedResult = $this->expandDependencies($list, $relatedIndexerId, $accumulated);
  342. $result = array_unique(array_merge($result, $relatedResult));
  343. }
  344. return $result;
  345. }
  346. }