| Current Path : /home/rtorresani/www/vendor/magento/module-webapi/Model/Soap/Wsdl/ |
| Current File : //home/rtorresani/www/vendor/magento/module-webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Webapi\Model\Soap\Wsdl;
use Laminas\Soap\Wsdl;
use Laminas\Soap\Wsdl\ComplexTypeStrategy\AbstractComplexTypeStrategy;
/**
* Magento-specific Complex type strategy for WSDL auto discovery.
*/
class ComplexTypeStrategy extends AbstractComplexTypeStrategy
{
/**
* Array item key value for element.
*/
public const ARRAY_ITEM_KEY_NAME = 'item';
/**
* Appinfo nodes namespace.
*/
public const APP_INF_NS = 'inf';
/**
* @var \Magento\Framework\Reflection\TypeProcessor
*/
protected $_typeProcessor;
/**
* Resources configuration data.
*
* @var array
*/
protected $_data;
/**
* Construct strategy with config helper.
*
* @param \Magento\Framework\Reflection\TypeProcessor $typeProcessor
*/
public function __construct(\Magento\Framework\Reflection\TypeProcessor $typeProcessor)
{
$this->_typeProcessor = $typeProcessor;
}
/**
* Return DOM Document
*
* @return \DomDocument
*/
protected function _getDom()
{
return $this->getContext()->toDomDocument();
}
/**
* Add complex type.
*
* @param string $type
* @param array $parentCallInfo array of callInfo from parent complex type
* @return string
* @throws \InvalidArgumentException
*/
public function addComplexType($type, $parentCallInfo = [])
{
if (($soapType = $this->scanRegisteredTypes($type)) !== null) {
return $soapType;
}
$soapType = Wsdl::TYPES_NS . ':' . $type;
// Register type here to avoid recursion
$this->getContext()->addType($type, $soapType);
$complexType = $this->_getDom()->createElement(Wsdl::XSD_NS . ':complexType');
$complexType->setAttribute('name', $type);
$typeData = $this->_typeProcessor->getTypeData($type);
if (isset($typeData['documentation'])) {
$this->addAnnotation($complexType, $typeData['documentation']);
}
if (isset($typeData['parameters']) && is_array($typeData['parameters'])) {
$callInfo = isset($typeData['callInfo']) ? $typeData['callInfo'] : $parentCallInfo;
$sequence = $this->_processParameters($typeData['parameters'], $callInfo);
$complexType->appendChild($sequence);
}
$this->getContext()->getSchema()->appendChild($complexType);
return $soapType;
}
/**
* Process type parameters and create complex type sequence.
*
* @param array $parameters
* @param array $callInfo
* @return \DOMElement
*/
protected function _processParameters($parameters, $callInfo)
{
$sequence = $this->_getDom()->createElement(Wsdl::XSD_NS . ':sequence');
foreach ($parameters as $parameterName => $parameterData) {
$parameterType = $parameterData['type'];
$element = $this->_getDom()->createElement(Wsdl::XSD_NS . ':element');
$element->setAttribute('name', $parameterName);
$isRequired = isset($parameterData['required']) && $parameterData['required'];
$default = isset($parameterData['default']) ? $parameterData['default'] : null;
$this->_revertRequiredCallInfo($isRequired, $callInfo);
if ($this->_typeProcessor->isArrayType($parameterType)) {
$this->_processArrayParameter($parameterType, $callInfo);
$element->setAttribute(
'type',
Wsdl::TYPES_NS . ':' . $this->_typeProcessor->translateArrayTypeName($parameterType)
);
if (!$isRequired) {
$element->setAttribute('minOccurs', 0);
}
} else {
$this->_processParameter($element, $isRequired, $parameterData, $parameterType, $callInfo);
}
if (isset($parameterData['documentation'])) {
$this->addAnnotation($element, $parameterData['documentation'], $default, $callInfo);
}
$sequence->appendChild($element);
}
return $sequence;
}
/**
* Process parameter and declare complex type if necessary.
*
* @param \DOMElement $element
* @param boolean $isRequired
* @param array $parameterData
* @param string $parameterType
* @param array $callInfo
* @return void
*/
protected function _processParameter(\DOMElement $element, $isRequired, $parameterData, $parameterType, $callInfo)
{
$element->setAttribute('minOccurs', $isRequired ? 1 : 0);
$maxOccurs = isset($parameterData['isArray']) && $parameterData['isArray'] ? 'unbounded' : 1;
$element->setAttribute('maxOccurs', $maxOccurs);
if ($this->_typeProcessor->isTypeSimple($parameterType) || $this->_typeProcessor->isTypeAny($parameterType)) {
$typeNs = Wsdl::XSD_NS;
} else {
$typeNs = Wsdl::TYPES_NS;
$this->addComplexType($parameterType, $callInfo);
}
$element->setAttribute('type', $typeNs . ':' . $parameterType);
}
/**
* Process array of types.
*
* @param string $type
* @param array $callInfo
* @return void
*/
protected function _processArrayParameter($type, $callInfo = [])
{
$arrayItemType = $this->_typeProcessor->getArrayItemType($type);
$arrayTypeName = $this->_typeProcessor->translateArrayTypeName($type);
if (!$this->_typeProcessor->isTypeSimple($arrayItemType) && !$this->_typeProcessor->isTypeAny($arrayItemType)) {
$this->addComplexType($arrayItemType, $callInfo);
}
$arrayTypeParameters = [
self::ARRAY_ITEM_KEY_NAME => [
'type' => $arrayItemType,
'required' => false,
'isArray' => true,
'documentation' => sprintf('An item of %s.', $arrayTypeName),
],
];
$arrayTypeData = [
'documentation' => sprintf('An array of %s items.', $arrayItemType),
'parameters' => $arrayTypeParameters,
];
$this->_typeProcessor->setTypeData($arrayTypeName, $arrayTypeData);
$this->addComplexType($arrayTypeName, $callInfo);
}
/**
* Revert required call info data if needed.
*
* @param bool $isRequired
* @param array $callInfo
* @return void
*/
protected function _revertRequiredCallInfo($isRequired, &$callInfo)
{
if (!$isRequired) {
if (isset($callInfo['requiredInput']['yes'])) {
$callInfo['requiredInput']['no']['calls'] = $callInfo['requiredInput']['yes']['calls'];
unset($callInfo['requiredInput']['yes']);
}
if (isset($callInfo['returned']['always'])) {
$callInfo['returned']['conditionally']['calls'] = $callInfo['returned']['always']['calls'];
unset($callInfo['returned']['always']);
}
}
}
/**
* Generate annotation data for WSDL.
*
* Convert all {key:value} from documentation into appinfo nodes.
* Override default callInfo values if defined in parameter documentation.
*
* @param \DOMElement $element
* @param string $documentation parameter documentation string
* @param string|null $default
* @param array $callInfo
* @return void
*/
public function addAnnotation(\DOMElement $element, $documentation, $default = null, $callInfo = [])
{
$annotationNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':annotation');
$elementType = $this->_getElementType($element);
$appInfoNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':appinfo');
$appInfoNode->setAttributeNS(
Wsdl::XML_NS_URI,
Wsdl::XML_NS . ':' . self::APP_INF_NS,
(string) $this->getContext()->getTargetNamespace()
);
$this->_processDefaultValueAnnotation($elementType, $default, $appInfoNode);
$this->_processElementType($elementType, $documentation, $appInfoNode);
if ($documentation && preg_match_all('/{([a-z]+):(.+)}/Ui', $documentation, $matches)) {
$count = count($matches[0]);
for ($i = 0; $i < $count; $i++) {
$appinfoTag = $matches[0][$i];
$tagName = $matches[1][$i];
$tagValue = $matches[2][$i];
switch ($tagName) {
case 'callInfo':
$this->processCallInfo($callInfo, $tagValue);
break;
case 'seeLink':
$this->_processSeeLink($appInfoNode, $tagValue);
break;
case 'docInstructions':
$this->_processDocInstructions($appInfoNode, $tagValue);
break;
default:
$nodeValue = $tagValue !== null ? trim($tagValue) : '';
$simpleTextNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $tagName);
$simpleTextNode->appendChild($this->_getDom()->createTextNode($nodeValue));
$appInfoNode->appendChild($simpleTextNode);
break;
}
$documentation = str_replace($appinfoTag, '', $documentation);
}
}
$this->_processCallInfo($appInfoNode, $callInfo);
$documentationNode = $this->_getDom()->createElement(Wsdl::XSD_NS . ':documentation');
$documentationText = $documentation ? trim($documentation) : '';
$documentationNode->appendChild($this->_getDom()->createTextNode($documentationText));
$annotationNode->appendChild($documentationNode);
$annotationNode->appendChild($appInfoNode);
$element->appendChild($annotationNode);
}
/**
* Process different element types.
*
* @param string $elementType
* @param string $documentation
* @param \DOMElement $appInfoNode
* @return void
*/
protected function _processElementType($elementType, $documentation, \DOMElement $appInfoNode)
{
if ($elementType == 'int') {
$this->_processRequiredAnnotation('min', $documentation, $appInfoNode);
$this->_processRequiredAnnotation('max', $documentation, $appInfoNode);
}
if ($elementType == 'string') {
$this->_processRequiredAnnotation('maxLength', $documentation, $appInfoNode);
}
if ($this->_typeProcessor->isArrayType($elementType)) {
$natureOfTypeNode = $this->_getDom()->createElement(self::APP_INF_NS . ':natureOfType');
$natureOfTypeNode->appendChild($this->_getDom()->createTextNode('array'));
$appInfoNode->appendChild($natureOfTypeNode);
}
}
/**
* Process default value annotation.
*
* @param string $elementType
* @param string $default
* @param \DOMElement $appInfoNode
* @return void
*/
protected function _processDefaultValueAnnotation($elementType, $default, \DOMElement $appInfoNode)
{
if ($elementType == 'boolean') {
$default = (bool)$default ? 'true' : 'false';
}
if ($default) {
$defaultNode = $this->_getDom()->createElement(self::APP_INF_NS . ':default');
$defaultNode->appendChild($this->_getDom()->createTextNode($default));
$appInfoNode->appendChild($defaultNode);
}
}
/**
* Retrieve element type.
*
* @param \DOMElement $element
* @return string|null
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
protected function _getElementType(\DOMElement $element)
{
$elementType = null;
if ($element->hasAttribute('type')) {
list($typeNs, $elementType) = explode(':', $element->getAttribute('type'));
}
return $elementType;
}
/**
* Check if there is given annotation in documentation, and if not - create an empty one.
*
* @param string $annotation
* @param string $documentation
* @param \DOMElement $appInfoNode
* @return void
*/
protected function _processRequiredAnnotation($annotation, $documentation, \DOMElement $appInfoNode)
{
if (!$documentation || !preg_match("/{{$annotation}:.+}/Ui", $documentation)) {
$annotationNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $annotation);
$appInfoNode->appendChild($annotationNode);
}
}
/**
* Process 'callInfo' appinfo tag.
*
* @param \DOMElement $appInfoNode
* @param array $callInfo
* @return void
*/
protected function _processCallInfo(\DOMElement $appInfoNode, $callInfo)
{
if (!empty($callInfo)) {
foreach ($callInfo as $direction => $conditions) {
foreach ($conditions as $condition => $info) {
$callInfoNode = $this->_getDom()->createElement(self::APP_INF_NS . ':callInfo');
if (isset($info['allCallsExcept'])) {
$allExceptNode = $this->_getDom()->createElement(self::APP_INF_NS . ':allCallsExcept');
$allExceptNode->appendChild($this->_getDom()->createTextNode($info['allCallsExcept']));
$callInfoNode->appendChild($allExceptNode);
} elseif (isset($info['calls'])) {
foreach ($info['calls'] as $callName) {
$callNode = $this->_getDom()->createElement(self::APP_INF_NS . ':callName');
$callNode->appendChild($this->_getDom()->createTextNode($callName));
$callInfoNode->appendChild($callNode);
}
}
$directionNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $direction);
$directionNode->appendChild($this->_getDom()->createTextNode(ucfirst($condition)));
$callInfoNode->appendChild($directionNode);
$appInfoNode->appendChild($callInfoNode);
}
}
}
}
/**
* Process 'docInstructions' appinfo tag.
*
* @param \DOMElement $appInfoNode
* @param string $tagValue
* @return void
*/
protected function _processDocInstructions(\DOMElement $appInfoNode, $tagValue)
{
if ($tagValue && preg_match('/(input|output):(.+)/', $tagValue, $docMatches)) {
$docInstructionsNode = $this->_getDom()->createElement(self::APP_INF_NS . ':docInstructions');
$directionNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $docMatches[1]);
$directionValueNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $docMatches[2]);
$directionNode->appendChild($directionValueNode);
$docInstructionsNode->appendChild($directionNode);
$appInfoNode->appendChild($docInstructionsNode);
}
}
/**
* Process 'seeLink' appinfo tag.
*
* @param \DOMElement $appInfoNode
* @param string $tagValue
* @return void
*/
protected function _processSeeLink(\DOMElement $appInfoNode, $tagValue)
{
if ($tagValue && preg_match('|([http://]?.+):(.+):(.+)|i', $tagValue, $matches)) {
$seeLink = ['url' => $matches[1], 'title' => $matches[2], 'for' => $matches[3]];
$seeLinkNode = $this->_getDom()->createElement(self::APP_INF_NS . ':seeLink');
foreach (['url', 'title', 'for'] as $subNodeName) {
if (isset($seeLink[$subNodeName])) {
$seeLinkSubNode = $this->_getDom()->createElement(self::APP_INF_NS . ':' . $subNodeName);
$seeLinkSubNode->appendChild($this->_getDom()->createTextNode($seeLink[$subNodeName]));
$seeLinkNode->appendChild($seeLinkSubNode);
}
}
$appInfoNode->appendChild($seeLinkNode);
}
}
/**
* Delete callName if it's already defined in some direction group.
*
* @param array &$callInfo
* @param string $callName
* @return void
*/
protected function _overrideCallInfoName(&$callInfo, $callName)
{
foreach ($callInfo as $direction => &$callInfoData) {
foreach ($callInfoData as $condition => &$data) {
if (isset($data['calls'])) {
$foundCallNameIndex = array_search($callName, $data['calls']);
if ($foundCallNameIndex !== false) {
unset($data['calls'][$foundCallNameIndex]);
if (empty($data['calls'])) {
unset($callInfo[$direction][$condition]);
}
break;
}
}
}
}
}
/**
* Process CallInfo data
*
* @param array $callInfo
* @param string $tagValue
*/
private function processCallInfo(array &$callInfo, string $tagValue): void
{
$callInfoRegExp = '/([a-z].+):(returned|requiredInput):(yes|no|always|conditionally)/i';
if (preg_match($callInfoRegExp, $tagValue)) {
list($callName, $direction, $condition) = explode(':', $tagValue);
$condition = $condition !== null ? strtolower($condition) : '';
if ($callName && preg_match('/allCallsExcept\(([a-zA-Z].+)\)/', $callName, $calls)) {
$callInfo[$direction][$condition] = [
'allCallsExcept' => $calls[1],
];
} elseif (!isset($callInfo[$direction][$condition]['allCallsExcept'])) {
$this->_overrideCallInfoName($callInfo, $callName);
$callInfo[$direction][$condition]['calls'][] = $callName;
}
}
}
}