vendor/sulu/sulu/src/Sulu/Component/Rest/ListBuilder/Metadata/ListXmlLoader.php line 119

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Sulu.
  4. *
  5. * (c) Sulu GmbH
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Sulu\Component\Rest\ListBuilder\Metadata;
  11. use Sulu\Component\Rest\ListBuilder\FieldDescriptorInterface;
  12. use Sulu\Component\Util\XmlUtil;
  13. use Symfony\Component\Config\Util\XmlUtils;
  14. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  15. /**
  16. * Parses data from xml and returns general list-builder metadata.
  17. */
  18. class ListXmlLoader
  19. {
  20. public function __construct(private ParameterBagInterface $parameterBag)
  21. {
  22. }
  23. public function load($resource)
  24. {
  25. $listMetadata = new ListMetadata();
  26. $cwd = \getcwd();
  27. // Necessary only for Windows, no effect on linux. Mute errors for PHP with chdir disabled to avoid E_WARNINGs
  28. @\chdir(\dirname($resource));
  29. $xmlDocument = XmlUtils::loadFile(
  30. $resource,
  31. function(\DOMDocument $dom) use ($resource) {
  32. $dom->documentURI = $resource;
  33. $dom->xinclude();
  34. return @$dom->schemaValidate(__DIR__ . '/Resources/schema/list-2.0.xsd');
  35. }
  36. );
  37. // Necessary only for Windows, no effect on linux. Mute errors for PHP with chdir disabled to avoid E_WARNINGs
  38. @\chdir($cwd);
  39. $xpath = new \DOMXPath($xmlDocument);
  40. $xpath->registerNamespace('x', 'http://schemas.sulu.io/list-builder/list');
  41. $listMetadata->setResource($resource);
  42. $listMetadata->setKey($xpath->query('/x:list/x:key')->item(0)->nodeValue);
  43. foreach ($xpath->query('/x:list/x:properties/x:*') as $propertyNode) {
  44. $listMetadata->addPropertyMetadata($this->loadPropertyMetadata($xpath, $propertyNode));
  45. }
  46. return $listMetadata;
  47. }
  48. /**
  49. * Extracts attributes from dom-node to create a new property-metadata object.
  50. *
  51. * @return AbstractPropertyMetadata
  52. */
  53. private function loadPropertyMetadata(\DOMXPath $xpath, \DOMNode $propertyNode)
  54. {
  55. $propertyMetadata = null;
  56. $propertyMetadata = match ($propertyNode->nodeName) {
  57. 'concatenation-property' => $this->loadConcatenationPropertyMetadata($xpath, $propertyNode),
  58. 'identity-property' => $this->loadIdentityPropertyMetadata($xpath, $propertyNode),
  59. 'group-concat-property' => $this->loadGroupConcatPropertyMetadata($xpath, $propertyNode),
  60. 'case-property' => $this->loadCasePropertyMetadata($xpath, $propertyNode),
  61. 'count-property' => $this->loadCountPropertyMetadata($xpath, $propertyNode),
  62. 'property' => $this->loadSinglePropertyMetadata($xpath, $propertyNode),
  63. default => throw new \InvalidArgumentException(\sprintf(
  64. 'The tag "%s" cannot be handled by this loader',
  65. $propertyNode->nodeName
  66. )),
  67. };
  68. if (null !== $translation = XmlUtil::getValueFromXPath('@translation', $xpath, $propertyNode)) {
  69. $propertyMetadata->setTranslation($translation);
  70. }
  71. $propertyMetadata->setVisibility(
  72. XmlUtil::getValueFromXPath(
  73. '@visibility',
  74. $xpath,
  75. $propertyNode,
  76. FieldDescriptorInterface::VISIBILITY_NO
  77. )
  78. );
  79. $propertyMetadata->setSearchability(
  80. XmlUtil::getValueFromXPath(
  81. '@searchability',
  82. $xpath,
  83. $propertyNode,
  84. FieldDescriptorInterface::SEARCHABILITY_NEVER
  85. )
  86. );
  87. $propertyMetadata->setSortable(
  88. XmlUtil::getBooleanValueFromXPath('@sortable', $xpath, $propertyNode, true)
  89. );
  90. $propertyMetadata->setWidth(
  91. XmlUtil::getValueFromXPath(
  92. '@width',
  93. $xpath,
  94. $propertyNode,
  95. FieldDescriptorInterface::WIDTH_AUTO
  96. )
  97. );
  98. if (null !== $type = XmlUtil::getValueFromXPath('x:transformer/@type', $xpath, $propertyNode)) {
  99. $propertyMetadata->setType($type);
  100. } elseif (null !== $type = XmlUtil::getValueFromXPath('@type', $xpath, $propertyNode)) {
  101. @trigger_deprecation('sulu/sulu', '2.2', 'Attribute "type" of list property should not be used anymore! Use "<transformer type="..."/>" inside of property instead.');
  102. $propertyMetadata->setType($type);
  103. }
  104. $transformerParamNodes = $xpath->query('x:transformer/x:params', $propertyNode);
  105. if (\count($transformerParamNodes) > 0) {
  106. $propertyMetadata->setTransformerTypeParameters(
  107. $this->getParameters(
  108. $xpath,
  109. $transformerParamNodes->item(0) // There can only be one transformer node
  110. )
  111. );
  112. }
  113. $propertyMetadata->setFilterType((string) XmlUtil::getValueFromXPath('x:filter/@type', $xpath, $propertyNode));
  114. $filterParamNodes = $xpath->query('x:filter/x:params', $propertyNode);
  115. if (\count($filterParamNodes) > 0) {
  116. $propertyMetadata->setFilterTypeParameters(
  117. $this->getParameters(
  118. $xpath,
  119. $filterParamNodes->item(0) // There can only be one filter node
  120. )
  121. );
  122. }
  123. return $propertyMetadata;
  124. }
  125. private function loadIdentityPropertyMetadata(\DOMXPath $xpath, \DOMElement $propertyNode)
  126. {
  127. $field = $this->getField($xpath, $propertyNode);
  128. $propertyMetadata = new IdentityPropertyMetadata(
  129. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  130. );
  131. $propertyMetadata->setField($field);
  132. return $propertyMetadata;
  133. }
  134. private function loadCasePropertyMetadata(\DOMXPath $xpath, \DOMElement $propertyNode)
  135. {
  136. $propertyMetadata = new CasePropertyMetadata(
  137. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  138. );
  139. foreach ($xpath->query('x:field', $propertyNode) as $fieldNode) {
  140. if (null === $case = $this->getField($xpath, $fieldNode)) {
  141. continue;
  142. }
  143. $propertyMetadata->addCase($case);
  144. }
  145. return $propertyMetadata;
  146. }
  147. private function loadGroupConcatPropertyMetadata(\DOMXPath $xpath, \DOMElement $propertyNode)
  148. {
  149. $field = $this->getField($xpath, $propertyNode);
  150. $propertyMetadata = new GroupConcatPropertyMetadata(
  151. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  152. );
  153. $propertyMetadata->setField($field);
  154. $propertyMetadata->setGlue((string) XmlUtil::getValueFromXPath('@glue', $xpath, $propertyNode, ' '));
  155. $propertyMetadata->setDistinct(XmlUtil::getBooleanValueFromXPath('@distinct', $xpath, $propertyNode) ?? false);
  156. return $propertyMetadata;
  157. }
  158. private function loadSinglePropertyMetadata(\DOMXPath $xpath, \DOMNode $propertyNode)
  159. {
  160. $field = $this->getField($xpath, $propertyNode);
  161. $propertyMetadata = new SinglePropertyMetadata(
  162. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  163. );
  164. $propertyMetadata->setField($field);
  165. return $propertyMetadata;
  166. }
  167. private function loadCountPropertyMetadata(\DOMXPath $xpath, \DOMNode $propertyNode)
  168. {
  169. $field = $this->getField($xpath, $propertyNode);
  170. $propertyMetadata = new CountPropertyMetadata(
  171. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  172. );
  173. $propertyMetadata->setField($field);
  174. $propertyMetadata->setDistinct(XmlUtil::getBooleanValueFromXPath('@distinct', $xpath, $propertyNode) ?? false);
  175. return $propertyMetadata;
  176. }
  177. private function loadConcatenationPropertyMetadata(\DOMXPath $xpath, \DOMNode $propertyNode)
  178. {
  179. $propertyMetadata = new ConcatenationPropertyMetadata(
  180. (string) XmlUtil::getValueFromXPath('@name', $xpath, $propertyNode)
  181. );
  182. $propertyMetadata->setGlue((string) (XmlUtil::getValueFromXPath('@glue', $xpath, $propertyNode) ?? ' '));
  183. foreach ($xpath->query('x:field', $propertyNode) as $fieldNode) {
  184. if (null === $field = $this->getField($xpath, $fieldNode)) {
  185. continue;
  186. }
  187. $propertyMetadata->addField($field);
  188. }
  189. return $propertyMetadata;
  190. }
  191. /**
  192. * Extracts filter type parameters from dom-node.
  193. *
  194. * @return ?array
  195. */
  196. protected function getParameters(\DOMXPath $xpath, \DOMNode $filterNode)
  197. {
  198. $parameters = [];
  199. foreach ($xpath->query('x:param', $filterNode) as $paramNode) {
  200. $name = XmlUtil::getValueFromXPath('@name', $xpath, $paramNode);
  201. $type = XmlUtil::getValueFromXPath('@type', $xpath, $paramNode);
  202. if ('collection' === $type) {
  203. $parameters[$name] = $this->getParameters($xpath, $paramNode);
  204. } else {
  205. $value = $this->parameterBag->resolveValue(
  206. \trim((string) XmlUtil::getValueFromXPath('@value', $xpath, $paramNode))
  207. );
  208. if (null === $name) {
  209. $parameters[] = 'expression' === $type ? ['type' => $type, 'value' => $value] : $value;
  210. } else {
  211. $parameters[$name] = 'expression' === $type ? ['type' => $type, 'value' => $value] : $value;
  212. }
  213. }
  214. }
  215. if (\count($parameters) > 0) {
  216. return $parameters;
  217. }
  218. return null;
  219. }
  220. private function getField(\DOMXPath $xpath, \DOMElement $fieldNode)
  221. {
  222. if (null !== $reference = XmlUtil::getValueFromXPath('@property-ref', $xpath, $fieldNode)) {
  223. $nodeList = $xpath->query(\sprintf('/x:list/x:properties/x:*[@name="%s"]', $reference));
  224. if (0 === $nodeList->length) {
  225. throw new \Exception(\sprintf('Rest metadata doctrine field reference "%s" was not found.', $reference));
  226. }
  227. return $this->getField($xpath, $nodeList->item(0));
  228. }
  229. if (null === ($fieldName = XmlUtil::getValueFromXPath('x:field-name', $xpath, $fieldNode))
  230. || null === ($entityName = XmlUtil::getValueFromXPath('x:entity-name', $xpath, $fieldNode))
  231. ) {
  232. return;
  233. }
  234. $field = new FieldMetadata($this->resolveParameter($fieldName), $this->resolveParameter($entityName));
  235. $joinsNodeList = $xpath->query('x:joins', $fieldNode);
  236. if ($joinsNodeList->length > 0) {
  237. $this->getJoinsMetadata($xpath, $joinsNodeList->item(0), $field);
  238. }
  239. return $field;
  240. }
  241. private function getJoinsMetadata(\DOMXPath $xpath, \DOMElement $joinsNode, FieldMetadata $field)
  242. {
  243. if (null !== $reference = XmlUtil::getValueFromXPath('@ref', $xpath, $joinsNode)) {
  244. $nodeList = $xpath->query(\sprintf('/x:list/x:joins[@name="%s"]', $reference));
  245. if (0 === $nodeList->length) {
  246. throw new \Exception(\sprintf('Rest metadata doctrine joins reference "%s" was not found.', $reference));
  247. }
  248. $this->getJoinsMetadata($xpath, $nodeList->item(0), $field);
  249. }
  250. foreach ($xpath->query('x:join', $joinsNode) as $joinNode) {
  251. $field->addJoin($this->getJoinMetadata($xpath, $joinNode));
  252. }
  253. }
  254. protected function getJoinMetadata(\DOMXPath $xpath, \DOMElement $joinNode)
  255. {
  256. $joinMetadata = new JoinMetadata();
  257. if (null !== $fieldName = XmlUtil::getValueFromXPath('x:field-name', $xpath, $joinNode)) {
  258. $joinMetadata->setEntityField($this->resolveParameter($fieldName));
  259. }
  260. if (null !== $entityName = XmlUtil::getValueFromXPath('x:entity-name', $xpath, $joinNode)) {
  261. $joinMetadata->setEntityName($this->resolveParameter($entityName));
  262. }
  263. if (null !== $condition = XmlUtil::getValueFromXPath('x:condition', $xpath, $joinNode)) {
  264. $joinMetadata->setCondition($this->resolveParameter($condition));
  265. }
  266. if (null !== $conditionMethod = XmlUtil::getValueFromXPath('x:condition-method', $xpath, $joinNode)) {
  267. /** @var 'ON'|'WITH' $conditionMethod */
  268. $joinMetadata->setConditionMethod($this->resolveParameter($conditionMethod));
  269. }
  270. if (null !== $method = XmlUtil::getValueFromXPath('x:method', $xpath, $joinNode)) {
  271. /** @var 'LEFT'|'INNER'|'RIGHT' $method */
  272. $joinMetadata->setMethod($method);
  273. }
  274. return $joinMetadata;
  275. }
  276. private function resolveParameter($value)
  277. {
  278. return $this->parameterBag->resolveValue($value);
  279. }
  280. }