PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/magento/module-webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 451 lines | 281 code | 30 blank | 140 comment | 40 complexity | 51599fc2a0853006ff615320ab13ba34 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Webapi\Model\Soap\Wsdl;
  7. use Zend\Soap\Wsdl;
  8. use Zend\Soap\Wsdl\ComplexTypeStrategy\AbstractComplexTypeStrategy;
  9. /**
  10. * Magento-specific Complex type strategy for WSDL auto discovery.
  11. */
  12. class ComplexTypeStrategy extends AbstractComplexTypeStrategy
  13. {
  14. /**
  15. * Array item key value for element.
  16. */
  17. const ARRAY_ITEM_KEY_NAME = 'item';
  18. /**
  19. * Appinfo nodes namespace.
  20. */
  21. const APP_INF_NS = 'inf';
  22. /** @var \Magento\Framework\Reflection\TypeProcessor */
  23. protected $_typeProcessor;
  24. /**
  25. * Resources configuration data.
  26. *
  27. * @var array
  28. */
  29. protected $_data;
  30. /**
  31. * Construct strategy with config helper.
  32. *
  33. * @param \Magento\Framework\Reflection\TypeProcessor $typeProcessor
  34. */
  35. public function __construct(\Magento\Framework\Reflection\TypeProcessor $typeProcessor)
  36. {
  37. $this->_typeProcessor = $typeProcessor;
  38. }
  39. /**
  40. * Return DOM Document
  41. *
  42. * @return \DomDocument
  43. */
  44. protected function _getDom()
  45. {
  46. return $this->getContext()->toDomDocument();
  47. }
  48. /**
  49. * Add complex type.
  50. *
  51. * @param string $type
  52. * @param array $parentCallInfo array of callInfo from parent complex type
  53. * @return string
  54. * @throws \InvalidArgumentException
  55. */
  56. public function addComplexType($type, $parentCallInfo = [])
  57. {
  58. if (($soapType = $this->scanRegisteredTypes($type)) !== null) {
  59. return $soapType;
  60. }
  61. $soapType = Wsdl::TYPES_NS . ':' . $type;
  62. // Register type here to avoid recursion
  63. $this->getContext()->addType($type, $soapType);
  64. $complexType = $this->_getDom()->createElement(Wsdl::XSD_NS . ':complexType');
  65. $complexType->setAttribute('name', $type);
  66. $typeData = $this->_typeProcessor->getTypeData($type);
  67. if (isset($typeData['documentation'])) {
  68. $this->addAnnotation($complexType, $typeData['documentation']);
  69. }
  70. if (isset($typeData['parameters']) && is_array($typeData['parameters'])) {
  71. $callInfo = isset($typeData['callInfo']) ? $typeData['callInfo'] : $parentCallInfo;
  72. $sequence = $this->_processParameters($typeData['parameters'], $callInfo);
  73. $complexType->appendChild($sequence);
  74. }
  75. $this->getContext()->getSchema()->appendChild($complexType);
  76. return $soapType;
  77. }
  78. /**
  79. * Process type parameters and create complex type sequence.
  80. *
  81. * @param array $parameters
  82. * @param array $callInfo
  83. * @return \DOMElement
  84. */
  85. protected function _processParameters($parameters, $callInfo)
  86. {
  87. $sequence = $this->_getDom()->createElement(Wsdl::XSD_NS . ':sequence');
  88. foreach ($parameters as $parameterName => $parameterData) {
  89. $parameterType = $parameterData['type'];
  90. $element = $this->_getDom()->createElement(Wsdl::XSD_NS . ':element');
  91. $element->setAttribute('name', $parameterName);
  92. $isRequired = isset($parameterData['required']) && $parameterData['required'];
  93. $default = isset($parameterData['default']) ? $parameterData['default'] : null;
  94. $this->_revertRequiredCallInfo($isRequired, $callInfo);
  95. if ($this->_typeProcessor->isArrayType($parameterType)) {
  96. $this->_processArrayParameter($parameterType, $callInfo);
  97. $element->setAttribute(
  98. 'type',
  99. Wsdl::TYPES_NS . ':' . $this->_typeProcessor->translateArrayTypeName($parameterType)
  100. );
  101. if (!$isRequired) {
  102. $element->setAttribute('minOccurs', 0);
  103. }
  104. } else {
  105. $this->_processParameter($element, $isRequired, $parameterData, $parameterType, $callInfo);
  106. }
  107. $this->addAnnotation($element, $parameterData['documentation'], $default, $callInfo);
  108. $sequence->appendChild($element);
  109. }
  110. return $sequence;
  111. }
  112. /**
  113. * Process parameter and declare complex type if necessary.
  114. *
  115. * @param \DOMElement $element
  116. * @param boolean $isRequired
  117. * @param array $parameterData
  118. * @param string $parameterType
  119. * @param array $callInfo
  120. * @return void
  121. */
  122. protected function _processParameter(\DOMElement $element, $isRequired, $parameterData, $parameterType, $callInfo)
  123. {
  124. $element->setAttribute('minOccurs', $isRequired ? 1 : 0);
  125. $maxOccurs = isset($parameterData['isArray']) && $parameterData['isArray'] ? 'unbounded' : 1;
  126. $element->setAttribute('maxOccurs', $maxOccurs);
  127. if ($this->_typeProcessor->isTypeSimple($parameterType) || $this->_typeProcessor->isTypeAny($parameterType)) {
  128. $typeNs = Wsdl::XSD_NS;
  129. } else {
  130. $typeNs = Wsdl::TYPES_NS;
  131. $this->addComplexType($parameterType, $callInfo);
  132. }
  133. $element->setAttribute('type', $typeNs . ':' . $parameterType);
  134. }
  135. /**
  136. * Process array of types.
  137. *
  138. * @param string $type
  139. * @param array $callInfo
  140. * @return void
  141. */
  142. protected function _processArrayParameter($type, $callInfo = [])
  143. {
  144. $arrayItemType = $this->_typeProcessor->getArrayItemType($type);
  145. $arrayTypeName = $this->_typeProcessor->translateArrayTypeName($type);
  146. if (!$this->_typeProcessor->isTypeSimple($arrayItemType) && !$this->_typeProcessor->isTypeAny($arrayItemType)) {
  147. $this->addComplexType($arrayItemType, $callInfo);
  148. }
  149. $arrayTypeParameters = [
  150. self::ARRAY_ITEM_KEY_NAME => [
  151. 'type' => $arrayItemType,
  152. 'required' => false,
  153. 'isArray' => true,
  154. 'documentation' => sprintf('An item of %s.', $arrayTypeName),
  155. ],
  156. ];
  157. $arrayTypeData = [
  158. 'documentation' => sprintf('An array of %s items.', $arrayItemType),
  159. 'parameters' => $arrayTypeParameters,
  160. ];
  161. $this->_typeProcessor->setTypeData($arrayTypeName, $arrayTypeData);
  162. $this->addComplexType($arrayTypeName, $callInfo);
  163. }
  164. /**
  165. * Revert required call info data if needed.
  166. *
  167. * @param bool $isRequired
  168. * @param array &$callInfo
  169. * @return void
  170. */
  171. protected function _revertRequiredCallInfo($isRequired, &$callInfo)
  172. {
  173. if (!$isRequired) {
  174. if (isset($callInfo['requiredInput']['yes'])) {
  175. $callInfo['requiredInput']['no']['calls'] = $callInfo['requiredInput']['yes']['calls'];
  176. unset($callInfo['requiredInput']['yes']);
  177. }
  178. if (isset($callInfo['returned']['always'])) {
  179. $callInfo['returned']['conditionally']['calls'] = $callInfo['returned']['always']['calls'];
  180. unset($callInfo['returned']['always']);
  181. }
  182. }
  183. }
  184. /**
  185. * Generate annotation data for WSDL.
  186. *
  187. * Convert all {key:value} from documentation into appinfo nodes.
  188. * Override default callInfo values if defined in parameter documentation.
  189. *
  190. * @param \DOMElement $element
  191. * @param string $documentation parameter documentation string
  192. * @param string|null $default
  193. * @param array $callInfo
  194. * @return void
  195. */
  196. public function addAnnotation(\DOMElement $element, $documentation, $default = null, $callInfo = [])
  197. {
  198. $annotationNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':annotation');
  199. $elementType = $this->_getElementType($element);
  200. $appInfoNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':appinfo');
  201. $appInfoNode->setAttributeNS(
  202. Wsdl::XML_NS_URI,
  203. Wsdl::XML_NS . ':' . self::APP_INF_NS,
  204. $this->getContext()->getTargetNamespace()
  205. );
  206. $this->_processDefaultValueAnnotation($elementType, $default, $appInfoNode);
  207. $this->_processElementType($elementType, $documentation, $appInfoNode);
  208. if (preg_match_all('/{([a-z]+):(.+)}/Ui', $documentation, $matches)) {
  209. for ($i = 0; $i < count($matches[0]); $i++) {
  210. $appinfoTag = $matches[0][$i];
  211. $tagName = $matches[1][$i];
  212. $tagValue = $matches[2][$i];
  213. switch ($tagName) {
  214. case 'callInfo':
  215. $callInfoRegExp = '/([a-z].+):(returned|requiredInput):(yes|no|always|conditionally)/i';
  216. if (preg_match($callInfoRegExp, $tagValue)) {
  217. list($callName, $direction, $condition) = explode(':', $tagValue);
  218. $condition = strtolower($condition);
  219. if (preg_match('/allCallsExcept\(([a-zA-Z].+)\)/', $callName, $calls)) {
  220. $callInfo[$direction][$condition] = [
  221. 'allCallsExcept' => $calls[1],
  222. ];
  223. } elseif (!isset($callInfo[$direction][$condition]['allCallsExcept'])) {
  224. $this->_overrideCallInfoName($callInfo, $callName);
  225. $callInfo[$direction][$condition]['calls'][] = $callName;
  226. }
  227. }
  228. break;
  229. case 'seeLink':
  230. $this->_processSeeLink($appInfoNode, $tagValue);
  231. break;
  232. case 'docInstructions':
  233. $this->_processDocInstructions($appInfoNode, $tagValue);
  234. break;
  235. default:
  236. $nodeValue = trim($tagValue);
  237. $simpleTextNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $tagName);
  238. $simpleTextNode->appendChild($this->_getDom()->createTextNode($nodeValue));
  239. $appInfoNode->appendChild($simpleTextNode);
  240. break;
  241. }
  242. $documentation = str_replace($appinfoTag, '', $documentation);
  243. }
  244. }
  245. $this->_processCallInfo($appInfoNode, $callInfo);
  246. $documentationNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':documentation');
  247. $documentationText = trim($documentation);
  248. $documentationNode->appendChild($this->_getDom()->createTextNode($documentationText));
  249. $annotationNode->appendChild($documentationNode);
  250. $annotationNode->appendChild($appInfoNode);
  251. $element->appendChild($annotationNode);
  252. }
  253. /**
  254. * Process different element types.
  255. *
  256. * @param string $elementType
  257. * @param string $documentation
  258. * @param \DOMElement $appInfoNode
  259. * @return void
  260. */
  261. protected function _processElementType($elementType, $documentation, \DOMElement $appInfoNode)
  262. {
  263. if ($elementType == 'int') {
  264. $this->_processRequiredAnnotation('min', $documentation, $appInfoNode);
  265. $this->_processRequiredAnnotation('max', $documentation, $appInfoNode);
  266. }
  267. if ($elementType == 'string') {
  268. $this->_processRequiredAnnotation('maxLength', $documentation, $appInfoNode);
  269. }
  270. if ($this->_typeProcessor->isArrayType($elementType)) {
  271. $natureOfTypeNode = $this->_getDom()->createElement(self::APP_INF_NS . ':natureOfType');
  272. $natureOfTypeNode->appendChild($this->_getDom()->createTextNode('array'));
  273. $appInfoNode->appendChild($natureOfTypeNode);
  274. }
  275. }
  276. /**
  277. * Process default value annotation.
  278. *
  279. * @param string $elementType
  280. * @param string $default
  281. * @param \DOMElement $appInfoNode
  282. * @return void
  283. */
  284. protected function _processDefaultValueAnnotation($elementType, $default, \DOMElement $appInfoNode)
  285. {
  286. if ($elementType == 'boolean') {
  287. $default = (bool)$default ? 'true' : 'false';
  288. }
  289. if ($default) {
  290. $defaultNode = $this->_getDom()->createElement(self::APP_INF_NS . ':default');
  291. $defaultNode->appendChild($this->_getDom()->createTextNode($default));
  292. $appInfoNode->appendChild($defaultNode);
  293. }
  294. }
  295. /**
  296. * Retrieve element type.
  297. *
  298. * @param \DOMElement $element
  299. * @return string|null
  300. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  301. */
  302. protected function _getElementType(\DOMElement $element)
  303. {
  304. $elementType = null;
  305. if ($element->hasAttribute('type')) {
  306. list($typeNs, $elementType) = explode(':', $element->getAttribute('type'));
  307. }
  308. return $elementType;
  309. }
  310. /**
  311. * Check if there is given annotation in documentation, and if not - create an empty one.
  312. *
  313. * @param string $annotation
  314. * @param string $documentation
  315. * @param \DOMElement $appInfoNode
  316. * @return void
  317. */
  318. protected function _processRequiredAnnotation($annotation, $documentation, \DOMElement $appInfoNode)
  319. {
  320. if (!preg_match("/{{$annotation}:.+}/Ui", $documentation)) {
  321. $annotationNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $annotation);
  322. $appInfoNode->appendChild($annotationNode);
  323. }
  324. }
  325. /**
  326. * Process 'callInfo' appinfo tag.
  327. *
  328. * @param \DOMElement $appInfoNode
  329. * @param array $callInfo
  330. * @return void
  331. */
  332. protected function _processCallInfo(\DOMElement $appInfoNode, $callInfo)
  333. {
  334. if (!empty($callInfo)) {
  335. foreach ($callInfo as $direction => $conditions) {
  336. foreach ($conditions as $condition => $info) {
  337. $callInfoNode = $this->_getDom()->createElement(self::APP_INF_NS . ':callInfo');
  338. if (isset($info['allCallsExcept'])) {
  339. $allExceptNode = $this->_getDom()->createElement(self::APP_INF_NS . ':allCallsExcept');
  340. $allExceptNode->appendChild($this->_getDom()->createTextNode($info['allCallsExcept']));
  341. $callInfoNode->appendChild($allExceptNode);
  342. } elseif (isset($info['calls'])) {
  343. foreach ($info['calls'] as $callName) {
  344. $callNode = $this->_getDom()->createElement(self::APP_INF_NS . ':callName');
  345. $callNode->appendChild($this->_getDom()->createTextNode($callName));
  346. $callInfoNode->appendChild($callNode);
  347. }
  348. }
  349. $directionNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $direction);
  350. $directionNode->appendChild($this->_getDom()->createTextNode(ucfirst($condition)));
  351. $callInfoNode->appendChild($directionNode);
  352. $appInfoNode->appendChild($callInfoNode);
  353. }
  354. }
  355. }
  356. }
  357. /**
  358. * Process 'docInstructions' appinfo tag.
  359. *
  360. * @param \DOMElement $appInfoNode
  361. * @param string $tagValue
  362. * @return void
  363. */
  364. protected function _processDocInstructions(\DOMElement $appInfoNode, $tagValue)
  365. {
  366. if (preg_match('/(input|output):(.+)/', $tagValue, $docMatches)) {
  367. $docInstructionsNode = $this->_getDom()->createElement(self::APP_INF_NS . ':docInstructions');
  368. $directionNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $docMatches[1]);
  369. $directionValueNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $docMatches[2]);
  370. $directionNode->appendChild($directionValueNode);
  371. $docInstructionsNode->appendChild($directionNode);
  372. $appInfoNode->appendChild($docInstructionsNode);
  373. }
  374. }
  375. /**
  376. * Process 'seeLink' appinfo tag.
  377. *
  378. * @param \DOMElement $appInfoNode
  379. * @param string $tagValue
  380. * @return void
  381. */
  382. protected function _processSeeLink(\DOMElement $appInfoNode, $tagValue)
  383. {
  384. if (preg_match('|([http://]?.+):(.+):(.+)|i', $tagValue, $matches)) {
  385. $seeLink = ['url' => $matches[1], 'title' => $matches[2], 'for' => $matches[3]];
  386. $seeLinkNode = $this->_getDom()->createElement(self::APP_INF_NS . ':seeLink');
  387. foreach (['url', 'title', 'for'] as $subNodeName) {
  388. if (isset($seeLink[$subNodeName])) {
  389. $seeLinkSubNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $subNodeName);
  390. $seeLinkSubNode->appendChild($this->_getDom()->createTextNode($seeLink[$subNodeName]));
  391. $seeLinkNode->appendChild($seeLinkSubNode);
  392. }
  393. }
  394. $appInfoNode->appendChild($seeLinkNode);
  395. }
  396. }
  397. /**
  398. * Delete callName if it's already defined in some direction group.
  399. *
  400. * @param array &$callInfo
  401. * @param string $callName
  402. * @return void
  403. */
  404. protected function _overrideCallInfoName(&$callInfo, $callName)
  405. {
  406. foreach ($callInfo as $direction => &$callInfoData) {
  407. foreach ($callInfoData as $condition => &$data) {
  408. if (isset($data['calls'])) {
  409. $foundCallNameIndex = array_search($callName, $data['calls']);
  410. if ($foundCallNameIndex !== false) {
  411. unset($data['calls'][$foundCallNameIndex]);
  412. if (empty($data['calls'])) {
  413. unset($callInfo[$direction][$condition]);
  414. }
  415. break;
  416. }
  417. }
  418. }
  419. }
  420. }
  421. }