Fault.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <?php
  2. /**
  3. * Magento-specific SOAP fault.
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Webapi\Model\Soap;
  9. use Magento\Framework\App\State;
  10. class Fault
  11. {
  12. const FAULT_REASON_INTERNAL = 'Internal Error.';
  13. /**#@+
  14. * Fault codes that are used in SOAP faults.
  15. */
  16. const FAULT_CODE_SENDER = 'Sender';
  17. const FAULT_CODE_RECEIVER = 'Receiver';
  18. /**#@+
  19. * Nodes that can appear in Detail node of SOAP fault.
  20. */
  21. const NODE_DETAIL_PARAMETERS = 'Parameters';
  22. const NODE_DETAIL_WRAPPED_ERRORS = 'WrappedErrors';
  23. const NODE_DETAIL_WRAPPED_EXCEPTION = 'WrappedException';
  24. /* Note that parameter node must be unique in scope of all complex types declared in WSDL */
  25. const NODE_DETAIL_PARAMETER = 'GenericFaultParameter';
  26. const NODE_DETAIL_PARAMETER_KEY = 'key';
  27. const NODE_DETAIL_PARAMETER_VALUE = 'value';
  28. const NODE_DETAIL_WRAPPED_ERROR = 'WrappedError';
  29. const NODE_DETAIL_WRAPPED_ERROR_MESSAGE = 'message';
  30. const NODE_DETAIL_WRAPPED_ERROR_PARAMETERS = 'parameters';
  31. const NODE_DETAIL_WRAPPED_ERROR_PARAMETER = 'parameter';
  32. const NODE_DETAIL_WRAPPED_ERROR_KEY = 'key';
  33. const NODE_DETAIL_WRAPPED_ERROR_VALUE = 'value';
  34. const NODE_DETAIL_TRACE = 'Trace';
  35. const NODE_DETAIL_WRAPPER = 'GenericFault';
  36. /**#@-*/
  37. /**
  38. * @var string
  39. */
  40. protected $_soapFaultCode;
  41. /**
  42. * Parameters are extracted from exception and can be inserted into 'Detail' node as 'Parameters'.
  43. *
  44. * @var array
  45. */
  46. protected $_parameters = [];
  47. /**
  48. * Wrapped errors are extracted from exception and can be inserted into 'Detail' node as 'WrappedErrors'.
  49. *
  50. * @var array
  51. */
  52. protected $_wrappedErrors = [];
  53. /**
  54. * Fault name is used for details wrapper node name generation.
  55. *
  56. * @var string
  57. */
  58. protected $_faultName = '';
  59. /**
  60. * Details that are used to generate 'Detail' node of SoapFault.
  61. *
  62. * @var array
  63. */
  64. protected $_details = [];
  65. /**
  66. * @var \Magento\Framework\App\RequestInterface
  67. */
  68. protected $_request;
  69. /**
  70. * @var Server
  71. */
  72. protected $_soapServer;
  73. /**
  74. * @var \Magento\Framework\Locale\ResolverInterface
  75. */
  76. protected $_localeResolver;
  77. /**
  78. * @var \Magento\Framework\App\State
  79. */
  80. protected $appState;
  81. /**
  82. * @var null|string
  83. */
  84. protected $stackTrace;
  85. /**
  86. * @var string
  87. */
  88. protected $message;
  89. /**
  90. * @param \Magento\Framework\App\RequestInterface $request
  91. * @param Server $soapServer
  92. * @param \Magento\Framework\Webapi\Exception $exception
  93. * @param \Magento\Framework\Locale\ResolverInterface $localeResolver
  94. * @param State $appState
  95. */
  96. public function __construct(
  97. \Magento\Framework\App\RequestInterface $request,
  98. Server $soapServer,
  99. \Magento\Framework\Webapi\Exception $exception,
  100. \Magento\Framework\Locale\ResolverInterface $localeResolver,
  101. State $appState
  102. ) {
  103. $this->_soapFaultCode = $exception->getOriginator();
  104. $this->_parameters = $exception->getDetails();
  105. $this->_wrappedErrors = $exception->getErrors();
  106. $this->stackTrace = $exception->getStackTrace() ?: $exception->getTraceAsString();
  107. $this->message = $exception->getMessage();
  108. $this->_request = $request;
  109. $this->_soapServer = $soapServer;
  110. $this->_localeResolver = $localeResolver;
  111. $this->appState = $appState;
  112. }
  113. /**
  114. * Render exception as XML.
  115. *
  116. * @return string
  117. */
  118. public function toXml()
  119. {
  120. if ($this->appState->getMode() == State::MODE_DEVELOPER) {
  121. $this->addDetails([self::NODE_DETAIL_TRACE => "<![CDATA[{$this->stackTrace}]]>"]);
  122. }
  123. if ($this->getParameters()) {
  124. $this->addDetails([self::NODE_DETAIL_PARAMETERS => $this->getParameters()]);
  125. }
  126. if ($this->getWrappedErrors()) {
  127. $this->addDetails([self::NODE_DETAIL_WRAPPED_ERRORS => $this->getWrappedErrors()]);
  128. }
  129. return $this->getSoapFaultMessage($this->getMessage(), $this->getSoapCode(), $this->getDetails());
  130. }
  131. /**
  132. * Retrieve additional details about current fault.
  133. *
  134. * @return array
  135. */
  136. public function getParameters()
  137. {
  138. return $this->_parameters;
  139. }
  140. /**
  141. * Retrieve wrapped errors about current fault.
  142. *
  143. * @return array
  144. */
  145. public function getWrappedErrors()
  146. {
  147. return $this->_wrappedErrors;
  148. }
  149. /**
  150. * Add details about current fault.
  151. *
  152. * @param array $details Associative array containing details about current fault
  153. * @return $this
  154. */
  155. public function addDetails($details)
  156. {
  157. $this->_details = array_merge($this->_details, $details);
  158. return $this;
  159. }
  160. /**
  161. * Retrieve additional details about current fault.
  162. *
  163. * @return array
  164. */
  165. public function getDetails()
  166. {
  167. return $this->_details;
  168. }
  169. /**
  170. * Retrieve SOAP fault code.
  171. *
  172. * @return string
  173. */
  174. public function getSoapCode()
  175. {
  176. return $this->_soapFaultCode;
  177. }
  178. /**
  179. * Retrieve SOAP fault language.
  180. *
  181. * @return string
  182. */
  183. public function getLanguage()
  184. {
  185. return \Locale::getPrimaryLanguage($this->_localeResolver->getLocale());
  186. }
  187. /**
  188. * @return string
  189. */
  190. public function getMessage()
  191. {
  192. return $this->message;
  193. }
  194. /**
  195. * Generate SOAP fault message in XML format.
  196. *
  197. * @param string $reason Human-readable explanation of the fault
  198. * @param string $code SOAP fault code
  199. * @param array|null $details Detailed reason message(s)
  200. * @return string
  201. */
  202. public function getSoapFaultMessage($reason, $code, $details = null)
  203. {
  204. $detailXml = $this->_generateDetailXml($details);
  205. $language = $this->getLanguage();
  206. $detailsNamespace = !empty($detailXml)
  207. ? 'xmlns:m="' . urlencode($this->_soapServer->generateUri(true)) . '"'
  208. : '';
  209. $reason = htmlentities($reason);
  210. $message = <<<FAULT_MESSAGE
  211. <?xml version="1.0" encoding="utf-8" ?>
  212. <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" $detailsNamespace>
  213. <env:Body>
  214. <env:Fault>
  215. <env:Code>
  216. <env:Value>env:$code</env:Value>
  217. </env:Code>
  218. <env:Reason>
  219. <env:Text xml:lang="$language">$reason</env:Text>
  220. </env:Reason>
  221. $detailXml
  222. </env:Fault>
  223. </env:Body>
  224. </env:Envelope>
  225. FAULT_MESSAGE;
  226. return $message;
  227. }
  228. /**
  229. * Generate 'Detail' node content.
  230. *
  231. * In case when fault name is undefined, no 'Detail' node is generated.
  232. *
  233. * @param array $details
  234. * @return string
  235. */
  236. protected function _generateDetailXml($details)
  237. {
  238. $detailsXml = '';
  239. if (is_array($details) && !empty($details)) {
  240. $detailsXml = $this->_convertDetailsToXml($details);
  241. if ($detailsXml) {
  242. $errorDetailsNode = self::NODE_DETAIL_WRAPPER;
  243. $detailsXml = "<env:Detail><m:{$errorDetailsNode}>"
  244. . $detailsXml . "</m:{$errorDetailsNode}></env:Detail>";
  245. } else {
  246. $detailsXml = '';
  247. }
  248. }
  249. return $detailsXml;
  250. }
  251. /**
  252. * Recursively convert details array into XML structure.
  253. *
  254. * @param array $details
  255. * @return string
  256. */
  257. protected function _convertDetailsToXml($details)
  258. {
  259. $detailsXml = '';
  260. foreach ($details as $detailNode => $detailValue) {
  261. $detailNode = htmlspecialchars($detailNode);
  262. if (is_numeric($detailNode)) {
  263. continue;
  264. }
  265. switch ($detailNode) {
  266. case self::NODE_DETAIL_TRACE:
  267. if (is_string($detailValue) || is_numeric($detailValue)) {
  268. $detailsXml .= "<m:{$detailNode}>" . htmlspecialchars($detailValue) . "</m:{$detailNode}>";
  269. }
  270. break;
  271. case self::NODE_DETAIL_PARAMETERS:
  272. $detailsXml .= $this->_getParametersXml($detailValue);
  273. break;
  274. case self::NODE_DETAIL_WRAPPED_ERRORS:
  275. $detailsXml .= $this->_getWrappedErrorsXml($detailValue);
  276. break;
  277. }
  278. }
  279. return $detailsXml;
  280. }
  281. /**
  282. * Generate XML for parameters.
  283. *
  284. * @param array $parameters
  285. * @return string
  286. */
  287. protected function _getParametersXml($parameters)
  288. {
  289. $result = '';
  290. if (!is_array($parameters)) {
  291. return $result;
  292. }
  293. $paramsXml = '';
  294. foreach ($parameters as $parameterName => $parameterValue) {
  295. if ((is_string($parameterName) || is_numeric($parameterName))
  296. && (is_string($parameterValue) || is_numeric($parameterValue))
  297. ) {
  298. $keyNode = self::NODE_DETAIL_PARAMETER_KEY;
  299. $valueNode = self::NODE_DETAIL_PARAMETER_VALUE;
  300. $parameterNode = self::NODE_DETAIL_PARAMETER;
  301. if (is_numeric($parameterName)) {
  302. $parameterName++;
  303. }
  304. $paramsXml .= "<m:$parameterNode><m:$keyNode>$parameterName</m:$keyNode><m:$valueNode>"
  305. . htmlspecialchars($parameterValue) . "</m:$valueNode></m:$parameterNode>";
  306. }
  307. }
  308. if (!empty($paramsXml)) {
  309. $parametersNode = self::NODE_DETAIL_PARAMETERS;
  310. $result = "<m:$parametersNode>" . $paramsXml . "</m:$parametersNode>";
  311. }
  312. return $result;
  313. }
  314. /**
  315. * Generate XML for wrapped errors.
  316. *
  317. * @param array $wrappedErrors
  318. * @return string
  319. */
  320. protected function _getWrappedErrorsXml($wrappedErrors)
  321. {
  322. $result = '';
  323. if (!is_array($wrappedErrors)) {
  324. return $result;
  325. }
  326. $errorsXml = '';
  327. foreach ($wrappedErrors as $error) {
  328. $errorsXml .= $this->_generateErrorNodeXML($error);
  329. }
  330. if (!empty($errorsXml)) {
  331. $wrappedErrorsNode = self::NODE_DETAIL_WRAPPED_ERRORS;
  332. $result = "<m:$wrappedErrorsNode>" . $errorsXml . "</m:$wrappedErrorsNode>";
  333. }
  334. return $result;
  335. }
  336. /**
  337. * Generate XML for a particular error node.
  338. *
  339. * @param array $error
  340. * @return string
  341. */
  342. protected function _generateErrorNodeXML($error)
  343. {
  344. $wrappedErrorNode = self::NODE_DETAIL_WRAPPED_ERROR;
  345. $messageNode = self::NODE_DETAIL_WRAPPED_ERROR_MESSAGE;
  346. $parameters = $error->getParameters();
  347. $rawMessage = $error->getRawMessage();
  348. $xml = "<m:$wrappedErrorNode><m:$messageNode>$rawMessage</m:$messageNode>";
  349. if (!empty($parameters)) {
  350. $parametersNode = self::NODE_DETAIL_WRAPPED_ERROR_PARAMETERS;
  351. $xml .= "<m:$parametersNode>";
  352. foreach ($parameters as $key => $value) {
  353. $parameterNode = self::NODE_DETAIL_WRAPPED_ERROR_PARAMETER;
  354. $keyNode = self::NODE_DETAIL_PARAMETER_KEY;
  355. $valueNode = self::NODE_DETAIL_WRAPPED_ERROR_VALUE;
  356. $xml .= "<m:$parameterNode>" .
  357. "<m:$keyNode>$key</m:$keyNode><m:$valueNode>$value</m:$valueNode>" .
  358. "</m:$parameterNode>";
  359. }
  360. $xml .= "</m:$parametersNode>";
  361. }
  362. $xml .= "</m:$wrappedErrorNode>";
  363. return $xml;
  364. }
  365. }