Request.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. /**
  3. * REST API request.
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\Webapi\Rest;
  9. use Magento\Framework\Api\SimpleDataObjectConverter;
  10. use Magento\Framework\Phrase;
  11. class Request extends \Magento\Framework\Webapi\Request
  12. {
  13. /**#@+
  14. * HTTP methods supported by REST.
  15. */
  16. const HTTP_METHOD_GET = 'GET';
  17. const HTTP_METHOD_DELETE = 'DELETE';
  18. const HTTP_METHOD_PUT = 'PUT';
  19. const HTTP_METHOD_POST = 'POST';
  20. /**#@-*/
  21. /**
  22. * Character set which must be used in request.
  23. */
  24. const REQUEST_CHARSET = 'utf-8';
  25. const DEFAULT_ACCEPT = '*/*';
  26. /**
  27. * @var string
  28. */
  29. protected $_serviceName;
  30. /**
  31. * @var string
  32. */
  33. protected $_serviceType;
  34. /**
  35. * @var \Magento\Framework\Webapi\Rest\Request\DeserializerInterface
  36. */
  37. protected $_deserializer;
  38. /**
  39. * @var array
  40. */
  41. protected $_bodyParams;
  42. /**
  43. * @var \Magento\Framework\Webapi\Rest\Request\DeserializerFactory
  44. */
  45. protected $_deserializerFactory;
  46. /**
  47. * Initialize dependencies
  48. *
  49. * @param \Magento\Framework\Stdlib\Cookie\CookieReaderInterface $cookieReader
  50. * @param \Magento\Framework\Stdlib\StringUtils $converter
  51. * @param \Magento\Framework\App\AreaList $areaList
  52. * @param \Magento\Framework\Config\ScopeInterface $configScope
  53. * @param \Magento\Framework\Webapi\Rest\Request\DeserializerFactory $deserializerFactory
  54. * @param null|string $uri
  55. */
  56. public function __construct(
  57. \Magento\Framework\Stdlib\Cookie\CookieReaderInterface $cookieReader,
  58. \Magento\Framework\Stdlib\StringUtils $converter,
  59. \Magento\Framework\App\AreaList $areaList,
  60. \Magento\Framework\Config\ScopeInterface $configScope,
  61. \Magento\Framework\Webapi\Rest\Request\DeserializerFactory $deserializerFactory,
  62. $uri = null
  63. ) {
  64. parent::__construct($cookieReader, $converter, $areaList, $configScope, $uri);
  65. $this->_deserializerFactory = $deserializerFactory;
  66. }
  67. /**
  68. * Get request deserializer.
  69. *
  70. * @return \Magento\Framework\Webapi\Rest\Request\DeserializerInterface
  71. */
  72. protected function _getDeserializer()
  73. {
  74. if (null === $this->_deserializer) {
  75. $this->_deserializer = $this->_deserializerFactory->get($this->getContentType());
  76. }
  77. return $this->_deserializer;
  78. }
  79. /**
  80. * Retrieve accept types understandable by requester in a form of array sorted by quality in descending order.
  81. *
  82. * @return string[]
  83. */
  84. public function getAcceptTypes()
  85. {
  86. $qualityToTypes = [];
  87. $orderedTypes = [];
  88. foreach (preg_split('/,\s*/', $this->getHeader('Accept')) as $definition) {
  89. $typeWithQ = explode(';', $definition);
  90. $mimeType = trim(array_shift($typeWithQ));
  91. // check MIME type validity
  92. if (!preg_match('~^([0-9a-z*+\-]+)(?:/([0-9a-z*+\-\.]+))?$~i', $mimeType)) {
  93. continue;
  94. }
  95. $quality = '1.0';
  96. // default value for quality
  97. if ($typeWithQ) {
  98. $qAndValue = explode('=', $typeWithQ[0]);
  99. if (2 == count($qAndValue)) {
  100. $quality = $qAndValue[1];
  101. }
  102. }
  103. $qualityToTypes[$quality][$mimeType] = true;
  104. }
  105. krsort($qualityToTypes);
  106. foreach ($qualityToTypes as $typeList) {
  107. $orderedTypes += $typeList;
  108. }
  109. return empty($orderedTypes) ? [self::DEFAULT_ACCEPT] : array_keys($orderedTypes);
  110. }
  111. /**
  112. * Fetch data from HTTP Request body.
  113. *
  114. * @return array
  115. */
  116. public function getBodyParams()
  117. {
  118. if (null == $this->_bodyParams) {
  119. $this->_bodyParams = [];
  120. //avoid JSON decoding with empty string
  121. if ($this->getContent()) {
  122. $this->_bodyParams = (array)$this->_getDeserializer()->deserialize((string)$this->getContent());
  123. }
  124. }
  125. return $this->_bodyParams;
  126. }
  127. /**
  128. * Get Content-Type of request.
  129. *
  130. * @return string
  131. * @throws \Magento\Framework\Exception\InputException
  132. */
  133. public function getContentType()
  134. {
  135. $headerValue = $this->getHeader('Content-Type');
  136. if (!$headerValue) {
  137. throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is empty.'));
  138. }
  139. if (!preg_match('~^([a-z\d/\-+.]+)(?:; *charset=(.+))?$~Ui', $headerValue, $matches)) {
  140. throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is invalid.'));
  141. }
  142. // request encoding check if it is specified in header
  143. if (isset($matches[2]) && self::REQUEST_CHARSET != strtolower($matches[2])) {
  144. throw new \Magento\Framework\Exception\InputException(new Phrase('UTF-8 is the only supported charset.'));
  145. }
  146. return $matches[1];
  147. }
  148. /**
  149. * Retrieve current HTTP method.
  150. *
  151. * @return string
  152. * @throws \Magento\Framework\Exception\InputException
  153. */
  154. public function getHttpMethod()
  155. {
  156. if (!$this->isGet() && !$this->isPost() && !$this->isPut() && !$this->isDelete()) {
  157. throw new \Magento\Framework\Exception\InputException(new Phrase('Request method is invalid.'));
  158. }
  159. return $this->getMethod();
  160. }
  161. /**
  162. * Fetch and return parameter data from the request.
  163. *
  164. * @return array
  165. */
  166. public function getRequestData()
  167. {
  168. $requestBodyParams = [];
  169. $params = $this->getParams();
  170. $httpMethod = $this->getHttpMethod();
  171. if ($httpMethod == self::HTTP_METHOD_POST ||
  172. $httpMethod == self::HTTP_METHOD_PUT
  173. ) {
  174. $requestBodyParams = $this->getBodyParams();
  175. }
  176. return array_merge($requestBodyParams, $params);
  177. }
  178. /**
  179. * Override request body property value with matching url path parameter value
  180. *
  181. * This method assumes that webapi.xml url defines the substitution parameter as camelCase to the actual
  182. * snake case key described as part of the api contract. example: /:parentId/nestedResource/:entityId.
  183. * Here :entityId value will be used for overriding 'entity_id' property in the body.
  184. * Since Webapi framework allows both camelCase and snakeCase, either of them will be substituted for now.
  185. * If the request body is missing url path parameter as property, it will be added to the body.
  186. * This method works only requests with scalar properties at top level or properties of single object embedded
  187. * in the request body.
  188. * Only the last path parameter value will be substituted from the url in case of multiple parameters.
  189. *
  190. * @param array $urlPathParams url path parameters as array
  191. * @return array
  192. *
  193. * @deprecated 100.1.0
  194. * @see \Magento\Webapi\Controller\Rest\ParamsOverrider::overrideRequestBodyIdWithPathParam
  195. */
  196. protected function overrideRequestBodyIdWithPathParam($urlPathParams)
  197. {
  198. $requestBodyParams = $this->getBodyParams();
  199. $pathParamValue = end($urlPathParams);
  200. // Self apis should not be overridden
  201. if ($pathParamValue === 'me') {
  202. return $requestBodyParams;
  203. }
  204. $pathParamKey = key($urlPathParams);
  205. // Check if the request data is a top level object of body
  206. if (count($requestBodyParams) == 1 && is_array(end($requestBodyParams))) {
  207. $requestDataKey = key($requestBodyParams);
  208. $this->substituteParameters($requestBodyParams[$requestDataKey], $pathParamKey, $pathParamValue);
  209. } else { // Else parameters passed as scalar values in body will be overridden
  210. $this->substituteParameters($requestBodyParams, $pathParamKey, $pathParamValue);
  211. }
  212. return $requestBodyParams;
  213. }
  214. /**
  215. * Check presence for both camelCase and snake_case keys in array and substitute if either is present
  216. *
  217. * @param array $requestData
  218. * @param string $key
  219. * @param string $value
  220. * @return void
  221. * @deprecated 100.1.0
  222. * @see \Magento\Webapi\Controller\Rest\ParamsOverrider::substituteParameters
  223. */
  224. protected function substituteParameters(&$requestData, $key, $value)
  225. {
  226. $snakeCaseKey = SimpleDataObjectConverter::camelCaseToSnakeCase($key);
  227. $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
  228. if (isset($requestData[$camelCaseKey])) {
  229. $requestData[$camelCaseKey] = $value;
  230. } else {
  231. $requestData[$snakeCaseKey] = $value;
  232. }
  233. }
  234. }