modrest.class.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. <?php
  2. /*
  3. * This file is part of the MODX Revolution package.
  4. *
  5. * Copyright (c) MODX, LLC
  6. *
  7. * For complete copyright and license information, see the COPYRIGHT and LICENSE
  8. * files found in the top-level directory of this distribution.
  9. *
  10. */
  11. /**
  12. * REST Client service class with XML/JSON/QS support
  13. *
  14. * @package modx
  15. * @subpackage rest
  16. */
  17. class modRest {
  18. /** @var modX $modx */
  19. public $modx;
  20. /** @var array $config */
  21. public $config = array();
  22. /** @var mixed $handle The cURL resource handle. */
  23. public $handle;
  24. /** @var object $response Response body. */
  25. public $response;
  26. /** @var object $headers Parsed response header object */
  27. public $headers;
  28. /** @var object $info Response info object */
  29. public $info;
  30. /** @var string $error Response error string. */
  31. public $error;
  32. /** @var string $url The URL to query */
  33. public $url;
  34. /**
  35. * The modRest constructor
  36. *
  37. * @param modX $modx A reference to the modX instance
  38. * @param array $config An array of configuration options
  39. */
  40. public function __construct(modX &$modx,array $config = array()) {
  41. $this->modx =& $modx;
  42. $this->config = array_merge(array(
  43. 'addMethodParameter' => false,
  44. 'baseUrl' => null,
  45. 'curlOptions' => array(),
  46. 'defaultParameters' => array(),
  47. 'format' => null,
  48. 'headers' => array(),
  49. 'password' => null,
  50. 'suppressSuffix' => false,
  51. 'userAgent' => 'MODX RestClient/1.0.0',
  52. 'username' => null,
  53. ),$config);
  54. $this->modx->getService('lexicon','modLexicon');
  55. if ($this->modx->lexicon) {
  56. $this->modx->lexicon->load('rest');
  57. }
  58. }
  59. /**
  60. * @param string $key
  61. * @param mixed $value
  62. */
  63. public function setOption($key, $value){
  64. $this->config[$key] = $value;
  65. }
  66. /**
  67. * @param string $key
  68. * @param mixed $default
  69. * @return mixed
  70. */
  71. public function getOption($key,$default = null) {
  72. return array_key_exists($key,$this->config) ? $this->config[$key] : $default;
  73. }
  74. /**
  75. * @param string $url
  76. * @param array $parameters
  77. * @param array $headers
  78. * @return RestClientResponse
  79. */
  80. public function get($url, $parameters=array(), $headers=array()){
  81. return $this->execute($url, 'GET', $parameters, $headers);
  82. }
  83. /**
  84. * @param string $url
  85. * @param array $parameters
  86. * @param array $headers
  87. * @return RestClientResponse
  88. */
  89. public function post($url, $parameters=array(), $headers=array()){
  90. return $this->execute($url, 'POST', $parameters, $headers);
  91. }
  92. /**
  93. * @param string $url
  94. * @param array $parameters
  95. * @param array $headers
  96. * @return RestClientResponse
  97. */
  98. public function put($url, $parameters=array(), $headers=array()){
  99. if (!empty($this->config['addMethodParameter'])) {
  100. $parameters['_method'] = "PUT";
  101. }
  102. return $this->execute($url,'PUT',$parameters, $headers);
  103. }
  104. /**
  105. * @param string $url
  106. * @param array $parameters
  107. * @param array $headers
  108. * @return RestClientResponse
  109. */
  110. public function delete($url, $parameters=array(), $headers=array()){
  111. if (!empty($this->config['addMethodParameter'])) {
  112. $parameters['_method'] = "DELETE";
  113. }
  114. return $this->execute($url,'DELETE', $parameters, $headers);
  115. }
  116. /**
  117. * @param string $url
  118. * @param string $method
  119. * @param array $parameters
  120. * @param array $headers
  121. * @return RestClientResponse
  122. */
  123. protected function execute($url, $method='GET', $parameters=array(), $headers=array()){
  124. $request = new RestClientRequest($this->modx,$this->config);
  125. if (!empty($headers['rootNode'])) {
  126. $request->setRootNode($headers['rootNode']);
  127. }
  128. return $request->execute($url,$method,$parameters,$headers);
  129. }
  130. }
  131. /**
  132. * Request class for handling REST requests
  133. *
  134. * @package modx
  135. * @subpackage rest
  136. */
  137. class RestClientRequest {
  138. /** @var modX $modx */
  139. public $modx;
  140. /** @var array $config */
  141. public $config = array();
  142. /** @var string $url */
  143. public $url;
  144. /** @var string $method */
  145. public $method = 'GET';
  146. /** @var mixed $handle */
  147. public $handle;
  148. /** @var array $requestParameters */
  149. public $requestParameters = array();
  150. /** @var array $requestOptions */
  151. public $requestOptions = array();
  152. /** @var array $headers */
  153. public $headers = array();
  154. /** @var array $defaultRequestParameters */
  155. public $defaultRequestParameters = array();
  156. /** @var string $rootNode */
  157. public $rootNode = 'request';
  158. /**
  159. * The RestClientRequest constructor
  160. *
  161. * @param modX $modx A reference to the modX instance
  162. * @param array $config An array of configuration options
  163. */
  164. function __construct(modX &$modx,array $config = array()) {
  165. $this->modx =& $modx;
  166. $this->config = array_merge($this->config,$config);
  167. if (!empty($this->config['headers'])) {
  168. $this->setHeaders($this->config['headers']);
  169. }
  170. if (!empty($this->config['defaultParameters'])) {
  171. $this->defaultRequestParameters = $this->config['defaultParameters'];
  172. }
  173. $this->_setDefaultRequestOptions();
  174. }
  175. /**
  176. * @param string $key
  177. * @param mixed $value
  178. */
  179. public function setOption($key, $value){
  180. $this->config[$key] = $value;
  181. }
  182. /**
  183. * @param string $key
  184. * @param mixed $default
  185. * @return mixed
  186. */
  187. public function getOption($key,$default = null) {
  188. return array_key_exists($key,$this->config) ? $this->config[$key] : $default;
  189. }
  190. /**
  191. * Set the root node of the request. Only used for XML requests.
  192. * @param string $node
  193. */
  194. public function setRootNode($node) {
  195. $this->rootNode = $node;
  196. }
  197. /**
  198. * Set the request parameters for the request.
  199. * @param array $parameters
  200. */
  201. public function setRequestParameters(array $parameters) {
  202. $this->requestParameters = array_merge($this->defaultRequestParameters,$parameters);
  203. }
  204. /**
  205. * Set the HTTP headers on the request
  206. *
  207. * @param array $headers
  208. * @param bool $merge
  209. */
  210. public function setHeaders($headers = array(),$merge = false) {
  211. $this->headers = $merge ? array_merge($this->headers,$headers) : $headers;
  212. }
  213. /**
  214. * Execute the request, properly preparing it, setting the URL and sending the request via cURL
  215. *
  216. * @param string $path
  217. * @param string $method
  218. * @param array $parameters
  219. * @param array $headers
  220. * @return RestClientResponse
  221. */
  222. public function execute($path,$method = 'GET', $parameters = array(), $headers = array()) {
  223. $this->url = $path;
  224. $this->method = strtoupper($method);
  225. $this->setRequestParameters($parameters);
  226. if (!empty($headers)) $this->setHeaders($headers,true);
  227. $this->prepare();
  228. return $this->send();
  229. }
  230. /**
  231. * Prepare the request for sending
  232. */
  233. protected function prepare() {
  234. $this->prepareHandle();
  235. $this->prepareAuthentication();
  236. $this->prepareUrl();
  237. $this->preparePayload();
  238. $this->prepareHeaders();
  239. $this->prepareRequestOptions();
  240. }
  241. /**
  242. * Send the request over the wire
  243. * @return RestClientResponse
  244. */
  245. protected function send() {
  246. $this->modx->log(modX::LOG_LEVEL_INFO,'[Rest] Sending request to '.$this->url.' with parameters: '.print_r($this->requestParameters,true));
  247. curl_setopt_array($this->handle,$this->requestOptions);
  248. $result = curl_exec($this->handle);
  249. $headerSize = curl_getinfo($this->handle,CURLINFO_HEADER_SIZE);
  250. $response = new RestClientResponse($this->modx,$result,$headerSize,$this->config);
  251. $info = (object) curl_getinfo($this->handle,CURLINFO_HTTP_CODE);
  252. $response->setResponseInfo($info);
  253. $error = curl_error($this->handle);
  254. $response->setResponseError($error);
  255. curl_close($this->handle);
  256. return $response;
  257. }
  258. /**
  259. * Load the request handle
  260. * @return mixed
  261. */
  262. protected function prepareHandle() {
  263. $this->handle = curl_init();
  264. return $this->handle;
  265. }
  266. /**
  267. * Set any authentication options for this request
  268. */
  269. protected function prepareAuthentication() {
  270. $username = $this->getOption('username','');
  271. $password = $this->getOption('password','');
  272. if (!empty($username)) {
  273. $this->requestOptions[CURLOPT_USERPWD] = $username.(!empty($password) ? ':'.$password : '');
  274. }
  275. }
  276. /**
  277. * Set any HTTP headers and load them into the request options
  278. */
  279. protected function prepareHeaders() {
  280. if (!empty($this->headers)) {
  281. if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) {
  282. $this->requestOptions[CURLOPT_HTTPHEADER] = array();
  283. }
  284. foreach ($this->headers as $key => $value) {
  285. $this->requestOptions[CURLOPT_HTTPHEADER][] = sprintf("%s: %s", $key, $value);
  286. }
  287. }
  288. }
  289. /**
  290. * Prepare the URL, prefixing the baseUrl if set, and setting the format suffix, if wanted
  291. * @return mixed
  292. */
  293. protected function prepareUrl() {
  294. $format = $this->getOption('format','json');
  295. $suppressSuffix = $this->getOption('suppressSuffix',false);
  296. if (!empty($format) && !$suppressSuffix) {
  297. $this->url .= '.'.$format;
  298. }
  299. if ($this->method != 'POST' && count($this->requestParameters)){
  300. $this->url .= strpos($this->url, '?') ? '&' : '?';
  301. $this->url .= $this->_formatQuery($this->requestParameters);
  302. }
  303. $baseUrl = $this->getOption('baseUrl',false);
  304. if (!empty($baseUrl)) {
  305. if ((!empty($this->url) && $this->url[0] != '/') || substr($baseUrl, -1) != '/') {
  306. $this->url = '/' . $this->url;
  307. }
  308. $this->url = $baseUrl . $this->url;
  309. }
  310. $this->requestOptions[CURLOPT_URL] = $this->url;
  311. return $this->url;
  312. }
  313. /**
  314. * Prepare the payload of parameters to be sent with the request
  315. */
  316. protected function preparePayload() {
  317. if ($this->method != 'GET') {
  318. $format = $this->getOption('format','json');
  319. switch ($format) {
  320. case 'json':
  321. if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) $this->requestOptions[CURLOPT_HTTPHEADER] = array();
  322. $this->requestOptions[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json; charset=utf-8';
  323. if (!empty($this->requestParameters)) {
  324. $params = $this->requestParameters;
  325. if (!empty($this->config['useRootNodeInJSON'])) {
  326. $params = array($this->rootNode => $params);
  327. }
  328. $json = json_encode($params);
  329. $this->requestOptions[CURLOPT_POSTFIELDS] = $json;
  330. }
  331. break;
  332. case 'xml':
  333. if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) $this->requestOptions[CURLOPT_HTTPHEADER] = array();
  334. $this->requestOptions[CURLOPT_HTTPHEADER][] = 'Content-Type: application/xml; charset=utf-8';
  335. if (!empty($this->requestParameters)) {
  336. $xml = $this->toXml($this->requestParameters,$this->rootNode);
  337. $this->requestOptions[CURLOPT_POSTFIELDS] = $xml;
  338. }
  339. break;
  340. default:
  341. $this->requestOptions[CURLOPT_POSTFIELDS] = $this->_formatQuery($this->requestParameters);
  342. break;
  343. }
  344. }
  345. if ($this->method == 'POST') {
  346. $this->requestOptions[CURLOPT_POST] = true;
  347. } elseif ($this->method != 'GET') {
  348. $this->requestOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
  349. }
  350. }
  351. /**
  352. * Prepare the request options to be sent, setting them on the cURL handle
  353. */
  354. protected function prepareRequestOptions() {
  355. $curlOptions = $this->getOption('curlOptions');
  356. if (!empty($curlOptions) && is_array($curlOptions)) {
  357. foreach ($curlOptions as $key => $value) {
  358. $this->requestOptions[$key] = $value;
  359. }
  360. }
  361. }
  362. /**
  363. * Setup the default request options
  364. */
  365. private function _setDefaultRequestOptions() {
  366. $this->requestOptions = array(
  367. CURLOPT_HEADER => $this->getOption('header',true),
  368. CURLOPT_RETURNTRANSFER => $this->getOption('returnTransfer',true),
  369. CURLOPT_FOLLOWLOCATION => $this->getOption('followLocation',true),
  370. CURLOPT_TIMEOUT => $this->getOption('timeout',240),
  371. CURLOPT_USERAGENT => $this->getOption('userAgent'),
  372. CURLOPT_CONNECTTIMEOUT => $this->getOption('connectTimeout',0),
  373. CURLOPT_DNS_CACHE_TIMEOUT => $this->getOption('dnsCacheTimeout',120),
  374. CURLOPT_VERBOSE => $this->getOption('verbose',false),
  375. CURLOPT_SSL_VERIFYHOST => $this->getOption('sslVerifyhost',2),
  376. CURLOPT_SSL_VERIFYPEER => $this->getOption('sslVerifypeer',false),
  377. CURLOPT_COOKIE => $this->getOption('cookie',''),
  378. CURLOPT_COOKIEFILE => $this->getOption('cookieFile',''),
  379. CURLOPT_ENCODING => $this->getOption('encoding',''),
  380. CURLOPT_REFERER => $this->getOption('referer',''),
  381. CURLOPT_USERAGENT => $this->getOption('userAgent',''),
  382. CURLOPT_NETRC => $this->getOption('netrc',false),
  383. CURLOPT_HTTPPROXYTUNNEL => $this->getOption('httpProxyTunnel',false),
  384. CURLOPT_FRESH_CONNECT => $this->getOption('freshConnect',false),
  385. CURLOPT_FORBID_REUSE => $this->getOption('forbidReuse',false),
  386. CURLOPT_CRLF => $this->getOption('crlf',false),
  387. CURLOPT_AUTOREFERER => $this->getOption('autoreferer',false),
  388. CURLOPT_MAXREDIRS => $this->getOption('maxRedirs',3),
  389. );
  390. $proxy = $this->getOption('proxy',false);
  391. if (!empty($proxy)) {
  392. $this->requestOptions = array_merge($this->requestOptions,array(
  393. CURLOPT_PROXY => $proxy,
  394. CURLOPT_PROXYAUTH => $this->getOption('proxyAuth',CURLAUTH_BASIC),
  395. CURLOPT_PROXYPORT => $this->getOption('proxyPort',80),
  396. CURLOPT_PROXYTYPE => $this->getOption('proxyType',CURLPROXY_HTTP),
  397. ));
  398. $username = $this->getOption('proxyUsername');
  399. $password = $this->getOption('proxyPassword','');
  400. if (!empty($username)) {
  401. $this->requestOptions[CURLOPT_PROXYUSERPWD] = $username.':'.$password;
  402. }
  403. }
  404. }
  405. /**
  406. * Format an array of parameters into a query string
  407. * @param array $parameters
  408. * @return string
  409. */
  410. private function _formatQuery(array $parameters){
  411. $query = http_build_query($parameters);
  412. return rtrim($query);
  413. }
  414. /**
  415. * @param array $parameters
  416. * @param string $rootNode
  417. * @return string
  418. */
  419. public function toXml($parameters,$rootNode) {
  420. $doc = new DOMDocument("1.0",'UTF-8');
  421. $root = $doc->appendChild($doc->createElement($rootNode));
  422. $this->_populateXmlDoc($doc, $root, $parameters);
  423. return $doc->saveXML();
  424. }
  425. /**
  426. * @param DOMDocument $doc
  427. * @param DOMNode $node
  428. * @param array|DOMNode $parameters
  429. */
  430. protected function _populateXmlDoc(&$doc, &$node, &$parameters) {
  431. foreach ($parameters as $key => $val) {
  432. if (is_array($val)) {
  433. if (empty($val)) {
  434. continue;
  435. }
  436. $attribute_node = $node->appendChild($doc->createElement($key));
  437. foreach ($val as $child => $childValue) {
  438. if (is_null($child) || is_null($childValue)) {
  439. continue;
  440. } elseif (is_string($child) && !is_null($childValue)) {
  441. // e.g. "<items><property>1000</property></items>"
  442. $attribute_node->appendChild($doc->createElement($child, $childValue));
  443. } elseif (is_int($child) && !is_null($childValue)) {
  444. if (is_object($childValue)) {
  445. // e.g. "<items><item>...</item></items>"
  446. $this->_populateXmlDoc($doc, $attribute_node, $childValue);
  447. } elseif (substr($key, -1) == "s") {
  448. // e.g. "<items><item>gold</item><item>monthly</item></items>"
  449. $attribute_node->appendChild($doc->createElement(substr($key, 0, -1), $childValue));
  450. }
  451. }
  452. }
  453. } elseif (is_object($val)) {
  454. $this->_populateXmlDoc($doc,$node,$val);
  455. } else {
  456. $node->appendChild($doc->createElement($key, $val));
  457. }
  458. }
  459. }
  460. }
  461. /**
  462. * Response class for REST requests
  463. *
  464. * @package modx
  465. * @subpackage rest
  466. */
  467. class RestClientResponse {
  468. /** @var modX $modx */
  469. public $modx;
  470. /** @var array $config */
  471. public $config = array();
  472. /** @var string $response */
  473. public $response;
  474. /** @var int $headerSize */
  475. public $headerSize = 0;
  476. /** @var string $responseBody */
  477. public $responseBody;
  478. /** @var string $responseInfo */
  479. public $responseInfo;
  480. /** @var string $responseError */
  481. public $responseError;
  482. /** @var mixed $responseHeaders */
  483. public $responseHeaders;
  484. /**
  485. * Constructor for RestClientResponse class.
  486. *
  487. * @param modX $modx A reference to the modX instance
  488. * @param string $response The response data
  489. * @param int $headerSize The size of the response header, in bytes
  490. * @param array $config An array of configuration options
  491. */
  492. function __construct(modX &$modx,$response = '',$headerSize = 0,array $config = array()) {
  493. $this->modx =& $modx;
  494. $this->config = array_merge($this->config,$config);
  495. $this->response = $response;
  496. $this->headerSize = $headerSize;
  497. $this->setResponseBody($response);
  498. }
  499. /**
  500. * Set and parse the response body
  501. * @param string $result
  502. */
  503. public function setResponseBody($result) {
  504. $this->responseBody = $this->_parse($result);
  505. }
  506. /**
  507. * Set the response info
  508. * @param string $info
  509. */
  510. public function setResponseInfo($info) {
  511. $this->responseInfo = $info;
  512. }
  513. /**
  514. * Set the response error, if any
  515. * @param string $error
  516. */
  517. public function setResponseError($error) {
  518. $this->responseError = $error;
  519. }
  520. /**
  521. * Return the processed result based on the format the response was returned in
  522. * @return array
  523. */
  524. public function process() {
  525. switch ($this->config['format']) {
  526. case 'xml':
  527. $result = $this->fromXML($this->responseBody);
  528. break;
  529. case 'json':
  530. default:
  531. $result = $this->modx->fromJSON($this->responseBody);
  532. break;
  533. }
  534. return !empty($result) ? $result : array();
  535. }
  536. /**
  537. * Parse the result
  538. * @param string $result
  539. * @return string
  540. */
  541. public function _parse($result) {
  542. $headers = array();
  543. $httpVer = strtok($result, "\n");
  544. while($line = strtok("\n")){
  545. if(strlen(trim($line)) == 0) break;
  546. list($key, $value) = explode(':', $line, 2);
  547. $key = trim(strtolower(str_replace('-', '_', $key)));
  548. $value = trim($value);
  549. if(empty($headers[$key])){
  550. $headers[$key] = $value;
  551. }
  552. elseif(is_array($headers[$key])){
  553. $headers[$key][] = $value;
  554. }
  555. else {
  556. $headers[$key] = array($headers[$key], $value);
  557. }
  558. }
  559. $this->responseHeaders = (object) $headers;
  560. return substr($result,$this->headerSize);
  561. }
  562. /**
  563. * Convert JSON into an array
  564. *
  565. * @param string $data
  566. * @return array
  567. */
  568. protected function fromJSON($data) {
  569. return $this->modx->fromJSON($data);
  570. }
  571. /**
  572. * Convert XML into an array
  573. *
  574. * @param string|SimpleXMLElement $xml
  575. * @param mixed $attributesKey
  576. * @param mixed $childrenKey
  577. * @param mixed $valueKey
  578. * @return array
  579. */
  580. protected function fromXML($xml,$attributesKey=null,$childrenKey=null,$valueKey=null){
  581. if (is_string($xml)) {
  582. $xml = simplexml_load_string($xml);
  583. }
  584. if (empty($xml)) return '';
  585. if($childrenKey && !is_string($childrenKey)){$childrenKey = '@children';}
  586. if($attributesKey && !is_string($attributesKey)){$attributesKey = '@attributes';}
  587. if($valueKey && !is_string($valueKey)){$valueKey = '@values';}
  588. $return = array();
  589. $name = $xml->getName();
  590. $_value = trim((string)$xml);
  591. if(!strlen($_value)){$_value = null;};
  592. if($_value!==null){
  593. if($valueKey){$return[$valueKey] = $_value;}
  594. else{$return = $_value;}
  595. }
  596. $children = array();
  597. $first = true;
  598. foreach($xml->children() as $elementName => $child){
  599. $value = $this->fromXML($child,$attributesKey, $childrenKey,$valueKey);
  600. if(isset($children[$elementName])){
  601. if(is_array($children[$elementName])){
  602. if($first){
  603. $temp = $children[$elementName];
  604. unset($children[$elementName]);
  605. $children[$elementName][] = $temp;
  606. $first=false;
  607. }
  608. $children[$elementName][] = $value;
  609. }else{
  610. $children[$elementName] = array($children[$elementName],$value);
  611. }
  612. }
  613. else{
  614. $children[$elementName] = $value;
  615. }
  616. }
  617. if($children){
  618. if($childrenKey){$return[$childrenKey] = $children;}
  619. else{$return = array_merge($return,$children);}
  620. }
  621. $attributes = array();
  622. foreach($xml->attributes() as $name=>$value){
  623. $attributes[$name] = trim($value);
  624. }
  625. if($attributes){
  626. if($attributesKey){$return[$attributesKey] = $attributes;}
  627. else if(is_array($attributes) && is_array($return)) {
  628. $return = array_merge($return, $attributes);
  629. }
  630. }
  631. return $return;
  632. }
  633. }