PageRenderTime 51ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/phrets.php

https://github.com/wgcdesigns/PHRETS
PHP | 1799 lines | 1062 code | 199 blank | 538 comment | 175 complexity | 085f40b8dbab59c053a13ad92407b099 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. class phRETS {
  3. /**
  4. * PHRETS - PHP library for RETS
  5. * version 1.0.1
  6. * http://troda.com/projects/phrets/
  7. * Copyright (C) 2007-2012 Troy Davisson
  8. * please submit problem or error reports to https://github.com/troydavisson/PHRETS/issues
  9. *
  10. * All rights reserved.
  11. * Permission is hereby granted, free of charge, to use, copy or modify this software. Use at your own risk.
  12. *
  13. * This library is divided into 2 sections: high level and low level
  14. * High level: Helpful functions that take much of the burden out of processing RETS data
  15. * Low level: Framework for communicating with a RETS server. High level functions sit on top of these
  16. *
  17. */
  18. public $capability_url = array();
  19. private $ch;
  20. private $server_hostname;
  21. private $server_port;
  22. private $server_protocol;
  23. private $server_version;
  24. private $server_software;
  25. private $static_headers = array();
  26. private $server_information = array();
  27. private $cookie_file = "";
  28. private $debug_file = "rets_debug.txt";
  29. private $debug_mode;
  30. private $allowed_capabilities = array(
  31. "Action" => 1,
  32. "ChangePassword" => 1,
  33. "GetObject" => 1,
  34. "Login" => 1,
  35. "LoginComplete" => 1,
  36. "Logout" => 1,
  37. "Search" => 1,
  38. "GetMetadata" => 1,
  39. "ServerInformation" => 1,
  40. "Update" => 1,
  41. "PostObject" => 1,
  42. "GetPayloadList" => 1
  43. );
  44. private $last_request = array();
  45. private $auth_support_basic = false;
  46. private $auth_support_digest = false;
  47. private $last_response_headers = array();
  48. private $last_response_headers_raw = "";
  49. private $last_remembered_header = "";
  50. private $compression_enabled = false;
  51. private $ua_pwd = "";
  52. private $ua_auth = false;
  53. private $request_id = "";
  54. private $disable_follow_location = false;
  55. private $force_basic_authentication = false;
  56. private $use_interealty_ua_auth = false;
  57. private $int_result_pointer = 0;
  58. private $error_info = array();
  59. private $last_request_url;
  60. private $last_server_response;
  61. private $session_id;
  62. private $catch_last_response = false;
  63. private $disable_encoding_fix = false;
  64. private $offset_support = false;
  65. private $override_offset_protection = false;
  66. public function phRETS() { }
  67. public function GetLastServerResponse() {
  68. return $this->last_server_response;
  69. }
  70. public function FirewallTest() {
  71. $google = $this->FirewallTestConn("google.com", 80);
  72. $crt80 = $this->FirewallTestConn("demo.crt.realtors.org", 80);
  73. $crt6103 = $this->FirewallTestConn("demo.crt.realtors.org", 6103);
  74. $flexmls80 = $this->FirewallTestConn("retsgw.flexmls.com", 80);
  75. $flexmls6103 = $this->FirewallTestConn("retsgw.flexmls.com", 6103);
  76. if (!$google && !$crt80 && !$crt6103 && !$flexmls80 && !$flexmls6103) {
  77. echo "Firewall Result: All tests failed. Possible causes:";
  78. echo "<ol>";
  79. echo "<li>Firewall is blocking your outbound connections</li>";
  80. echo "<li>You aren't connected to the internet</li>";
  81. echo "</ol>";
  82. return false;
  83. }
  84. if (!$crt6103 && !$flexmls6103) {
  85. echo "Firewall Result: All port 6103 tests failed. ";
  86. echo "Likely cause: Firewall is blocking your outbound connections on port 6103.";
  87. return false;
  88. }
  89. if ($google && $crt6103 && $crt80 && $flexmls6103 && $flexmls80) {
  90. echo "Firewall Result: All tests passed.";
  91. return true;
  92. }
  93. if (($crt6103 && !$flexmls6103) || (!$crt6103 && $flexmls6103)) {
  94. echo "Firewall Result: At least one port 6103 test passed. ";
  95. echo "Likely cause: One of the test servers might be down but connections on port 80 and port 6103 should work.";
  96. return true;
  97. }
  98. if (!$google || !$crt80 || !$flexmls80) {
  99. echo "Firewall Result: At least one port 80 test failed. ";
  100. echo "Likely cause: One of the test servers might be down.";
  101. return true;
  102. }
  103. echo "Firewall Results: Unable to guess the issue. See individual test results above.";
  104. return false;
  105. }
  106. private function FirewallTestConn($hostname, $port = 6103) {
  107. $fp = @fsockopen($hostname, $port, $errno, $errstr, 5);
  108. if (!$fp) {
  109. echo "Firewall Test: {$hostname}:{$port} FAILED<br>\n";
  110. return false;
  111. }
  112. else {
  113. @fclose($fp);
  114. echo "Firewall Test: {$hostname}:{$port} GOOD<br>\n";
  115. return true;
  116. }
  117. }
  118. public function GetObject($resource, $type, $id, $photo_number = '*', $location = 0) {
  119. $this->reset_error_info();
  120. $return_photos = array();
  121. if (empty($resource)) {
  122. die("Resource parameter is required for GetObject() request.");
  123. }
  124. if (empty($type)) {
  125. die("Type parameter is required for GetObject() request.");
  126. }
  127. if (empty($id)) {
  128. die("ID parameter is required for GetObject() request.");
  129. }
  130. if (empty($this->capability_url['GetObject'])) {
  131. die("GetObject() called but unable to find GetObject location. Failed login?\n");
  132. }
  133. $send_id = "";
  134. $send_numb = "";
  135. // check if $photo_number needs fixing
  136. if (strpos($photo_number, ',') !== false) {
  137. // change the commas to colons for the request
  138. $photo_number = preg_replace('/\,/', ':', $photo_number);
  139. }
  140. if (strpos($photo_number, ':') !== false) {
  141. // photo number contains multiple objects
  142. // chopping and cleaning
  143. $requested_numbers = explode(":", $photo_number);
  144. if (is_array($requested_numbers)) {
  145. foreach ($requested_numbers as $numb) {
  146. $numb = trim($numb);
  147. if (!empty($numb) || $numb == "0") {
  148. $send_numb .= "{$numb}:";
  149. }
  150. }
  151. }
  152. $send_numb = preg_replace('/\:$/', '', $send_numb);
  153. }
  154. else {
  155. $send_numb = trim($photo_number);
  156. }
  157. if (strpos($id, ',') !== false) {
  158. // id contains multiple objects.
  159. // chopping and combining with photo_number
  160. $requested_ids = explode(",", $id);
  161. if (is_array($requested_ids)) {
  162. foreach ($requested_ids as $req_id) {
  163. $req_id = trim($req_id);
  164. if (!empty($req_id) && $req_id != "0") {
  165. $send_id .= "{$req_id}:{$send_numb},";
  166. }
  167. }
  168. }
  169. $send_id = preg_replace('/\,$/', '', $send_id);
  170. }
  171. else {
  172. $send_id = trim($id).':'.$send_numb;
  173. }
  174. // make request
  175. $result = $this->RETSRequest($this->capability_url['GetObject'],
  176. array(
  177. 'Resource' => $resource,
  178. 'Type' => $type,
  179. 'ID' => $send_id,
  180. 'Location' => $location
  181. )
  182. );
  183. if (!$result) {
  184. return false;
  185. }
  186. list($headers, $body) = $result;
  187. // fix case issue if exists
  188. if (isset($this->last_response_headers['Content-type']) && !isset($this->last_response_headers['Content-Type'])) {
  189. $this->last_response_headers['Content-Type'] = $this->last_response_headers['Content-type'];
  190. }
  191. if (!isset($this->last_response_headers['Content-Type'])) {
  192. $this->last_response_headers['Content-Type'] = "";
  193. }
  194. // check what type of response came back
  195. if (strpos($this->last_response_headers['Content-Type'], 'multipart') !== false) {
  196. // help bad responses be more multipart compliant
  197. $body = "\r\n{$body}\r\n";
  198. // multipart
  199. preg_match('/boundary\=\"(.*?)\"/', $this->last_response_headers['Content-Type'], $matches);
  200. if (isset($matches[1])) {
  201. $boundary = $matches[1];
  202. }
  203. else {
  204. preg_match('/boundary\=(.*?)(\s|$|\;)/', $this->last_response_headers['Content-Type'], $matches);
  205. $boundary = $matches[1];
  206. }
  207. // strip quotes off of the boundary
  208. $boundary = preg_replace('/^\"(.*?)\"$/', '\1', $boundary);
  209. // clean up the body to remove a reamble and epilogue
  210. $body = preg_replace('/^(.*?)\r\n--'.$boundary.'\r\n/', "\r\n--{$boundary}\r\n", $body);
  211. // make the last one look like the rest for easier parsing
  212. $body = preg_replace('/\r\n--'.$boundary.'--/', "\r\n--{$boundary}\r\n", $body);
  213. // cut up the message
  214. $multi_parts = array();
  215. $multi_parts = explode("\r\n--{$boundary}\r\n", $body);
  216. // take off anything that happens before the first boundary (the preamble)
  217. array_shift($multi_parts);
  218. // take off anything after the last boundary (the epilogue)
  219. array_pop($multi_parts);
  220. // go through each part of the multipart message
  221. foreach ($multi_parts as $part) {
  222. // default to processing headers
  223. $on_headers = true;
  224. $on_body = false;
  225. $first_body_found = false;
  226. $this_photo = array();
  227. // go through the multipart chunk line-by-line
  228. $body_parts = array();
  229. $body_parts = explode("\r\n", $part);
  230. $this_photo['Data'] = "";
  231. foreach ($body_parts as $line) {
  232. if (empty($line) && $on_headers == true) {
  233. // blank line. switching to processing a body and moving on
  234. $on_headers = false;
  235. $on_body = true;
  236. continue;
  237. }
  238. if ($on_headers == true) {
  239. // non blank line and we're processing headers so save the header
  240. $header = null;
  241. $value = null;
  242. if (strpos($line, ':') !== false) {
  243. @list($header, $value) = explode(':', $line, 2);
  244. }
  245. $header = trim($header);
  246. $value = trim($value);
  247. if (!empty($header)) {
  248. if ($header == "Description") {
  249. // for servers where the implementors didn't read the next word in the RETS spec.
  250. // 'Description' is the BNF term. Content-Description is the correct header.
  251. // fixing for sanity
  252. $header = "Content-Description";
  253. }
  254. // fix case issue if exists
  255. if ($header == "Content-type") {
  256. $header = "Content-Type";
  257. }
  258. $this_photo[$header] = $value;
  259. }
  260. }
  261. if ($on_body == true) {
  262. if ($first_body_found == true) {
  263. // here again because a linebreak in the body section which was cut out in the explode
  264. // add the CRLF back
  265. $this_photo['Data'] .= "\r\n";
  266. }
  267. // non blank line and we're processing a body so save the line as part of Data
  268. $first_body_found = true;
  269. $this_photo['Data'] .= $line;
  270. }
  271. }
  272. // done with parsing out the multipart response
  273. // check for errors and finish up
  274. $this_photo['Success'] = true; // assuming for now
  275. if (strpos($this_photo['Content-Type'], 'xml') !== false) {
  276. // this multipart might include a RETS error
  277. $xml = $this->ParseXMLResponse($this_photo['Data']);
  278. if ($xml['ReplyCode'] == 0 || empty($this_photo['Data'])) {
  279. // success but no body
  280. $this_photo['Success'] = true;
  281. }
  282. else {
  283. // RETS error in this multipart section
  284. $this_photo['Success'] = false;
  285. $this_photo['ReplyCode'] = "{$xml['ReplyCode']}";
  286. $this_photo['ReplyText'] = "{$xml['ReplyText']}";
  287. }
  288. }
  289. // add information about this multipart to the returned array
  290. $return_photos[] = $this_photo;
  291. }
  292. }
  293. else {
  294. // all we know is that the response wasn't a multipart so it's either a single photo or error
  295. $this_photo = array();
  296. $this_photo['Success'] = true; // assuming for now
  297. if (isset($this->last_response_headers['Content-ID'])) {
  298. $this_photo['Content-ID'] = $this->last_response_headers['Content-ID'];
  299. }
  300. if (isset($this->last_response_headers['Object-ID'])) {
  301. $this_photo['Object-ID'] = $this->last_response_headers['Object-ID'];
  302. }
  303. if (isset($this->last_response_headers['Content-Type'])) {
  304. $this_photo['Content-Type'] = $this->last_response_headers['Content-Type'];
  305. }
  306. if (isset($this->last_response_headers['MIME-Version'])) {
  307. $this_photo['MIME-Version'] = $this->last_response_headers['MIME-Version'];
  308. }
  309. if (isset($this->last_response_headers['Location'])) {
  310. $this_photo['Location'] = $this->last_response_headers['Location'];
  311. }
  312. if (isset($this->last_response_headers['Preferred'])) {
  313. $this_photo['Preferred'] = $this->last_response_headers['Preferred'];
  314. }
  315. if (isset($this->last_response_headers['Description'])) {
  316. if (!empty($this->last_response_headers['Description'])) {
  317. // for servers where the implementors didn't read the next word in the RETS spec.
  318. // 'Description' is the BNF term. Content-Description is the correct header.
  319. // fixing for sanity
  320. $this_photo['Content-Description'] = $this->last_response_headers['Description'];
  321. }
  322. }
  323. if (isset($this->last_response_headers['Content-Description'])) {
  324. $this_photo['Content-Description'] = $this->last_response_headers['Content-Description'];
  325. }
  326. $this_photo['Length'] = strlen($body);
  327. $this_photo['Data'] = $body;
  328. if (isset($this->last_response_headers['Content-Type'])) {
  329. if (strpos($this->last_response_headers['Content-Type'], 'xml') !== false) {
  330. // RETS error maybe?
  331. $xml = $this->ParseXMLResponse($body);
  332. if ($xml['ReplyCode'] == 0 || empty($body)) {
  333. // false alarm. we're good
  334. $this_photo['Success'] = true;
  335. }
  336. else {
  337. // yes, RETS error
  338. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  339. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  340. $this_photo['ReplyCode'] = "{$xml['ReplyCode']}";
  341. $this_photo['ReplyText'] = "{$xml['ReplyText']}";
  342. $this_photo['Success'] = false;
  343. }
  344. }
  345. }
  346. // add information about this photo to the returned array
  347. $return_photos[] = $this_photo;
  348. }
  349. // return everything
  350. return $return_photos;
  351. }
  352. public function IsMaxrowsReached($pointer_id = "") {
  353. if (empty($pointer_id)) {
  354. $pointer_id = $this->int_result_pointer;
  355. }
  356. return $this->search_data[$pointer_id]['maxrows_reached'];
  357. }
  358. public function TotalRecordsFound($pointer_id = "") {
  359. if (empty($pointer_id)) {
  360. $pointer_id = $this->int_result_pointer;
  361. }
  362. return $this->search_data[$pointer_id]['total_records_found'];
  363. }
  364. public function NumRows($pointer_id = "") {
  365. if (empty($pointer_id)) {
  366. $pointer_id = $this->int_result_pointer;
  367. }
  368. return $this->search_data[$pointer_id]['last_search_returned'];
  369. }
  370. public function SearchGetFields($pointer_id) {
  371. if (!empty($pointer_id)) {
  372. return $this->search_data[$pointer_id]['column_names'];
  373. }
  374. else {
  375. return false;
  376. }
  377. }
  378. public function FreeResult($pointer_id) {
  379. if (!empty($pointer_id)) {
  380. unset($this->search_data[$pointer_id]['data']);
  381. unset($this->search_data[$pointer_id]['delimiter_character']);
  382. unset($this->search_data[$pointer_id]['column_names']);
  383. return true;
  384. }
  385. else {
  386. return false;
  387. }
  388. }
  389. public function FetchRow($pointer_id) {
  390. $this_row = false;
  391. if (!empty($pointer_id)) {
  392. if (isset($this->search_data[$pointer_id]['data'])) {
  393. $field_data = current($this->search_data[$pointer_id]['data']);
  394. next($this->search_data[$pointer_id]['data']);
  395. }
  396. if (!empty($field_data)) {
  397. $this_row = array();
  398. // split up DATA row on delimiter found earlier
  399. $field_data = preg_replace("/^{$this->search_data[$pointer_id]['delimiter_character']}/", "", $field_data);
  400. $field_data = preg_replace("/{$this->search_data[$pointer_id]['delimiter_character']}\$/", "", $field_data);
  401. $field_data = explode($this->search_data[$pointer_id]['delimiter_character'], $field_data);
  402. foreach ($this->search_data[$pointer_id]['column_names'] as $key => $name) {
  403. // assign each value to it's name retrieved in the COLUMNS earlier
  404. $this_row[$name] = $field_data[$key];
  405. }
  406. }
  407. }
  408. return $this_row;
  409. }
  410. public function SearchQuery($resource, $class, $query = "", $optional_params = array()) {
  411. $this->reset_error_info();
  412. if (empty($resource)) {
  413. die("Resource parameter is required in SearchQuery() request.");
  414. }
  415. if (empty($class)) {
  416. die("Class parameter is required in SearchQuery() request.");
  417. }
  418. if (empty($this->capability_url['Search'])) {
  419. die("SearchQuery() called but unable to find Search location. Failed login?\n");
  420. }
  421. $this->int_result_pointer++;
  422. $this->search_data[$this->int_result_pointer]['last_search_returned'] = 0;
  423. $this->search_data[$this->int_result_pointer]['total_records_found'] = 0;
  424. $this->search_data[$this->int_result_pointer]['column_names'] = "";
  425. $this->search_data[$this->int_result_pointer]['delimiter_character'] = "";
  426. $this->search_data[$this->int_result_pointer]['search_requests'] = 0;
  427. // setup request arguments
  428. $search_arguments = array();
  429. $search_arguments['SearchType'] = $resource;
  430. $search_arguments['Class'] = $class;
  431. // due to a lack of forward-thinking, reversing a previous decision
  432. // check if the query passed is missing the outer parenthesis
  433. // if so, add them
  434. if (empty($query)) {
  435. // do nothing. http://retsdoc.onconfluence.com/display/rcpcenter/RCP+80+-+Optional+Query
  436. }
  437. elseif ($query == "*" || preg_match('/^\((.*)\)$/', $query)) {
  438. $search_arguments['Query'] = $query;
  439. }
  440. else {
  441. $search_arguments['Query'] = '('.$query.')';
  442. }
  443. if (isset($search_arguments['Query'])) {
  444. $search_arguments['QueryType'] = "DMQL2";
  445. }
  446. if (!empty($optional_params['QueryType'])) {
  447. $search_arguments['QueryType'] = $optional_params['QueryType'];
  448. }
  449. // setup additional, optional request arguments
  450. $search_arguments['Count'] = empty($optional_params['Count']) ? 1 : $optional_params['Count'];
  451. $search_arguments['Format'] = empty($optional_params['Format']) ? "COMPACT-DECODED" : $optional_params['Format'];
  452. $search_arguments['Limit'] = empty($optional_params['Limit']) ? 99999999 : $optional_params['Limit'];
  453. if (isset($optional_params['Offset'])) {
  454. $search_arguments['Offset'] = $optional_params['Offset'];
  455. }
  456. elseif ($this->offset_support && empty($optional_params['Offset'])) {
  457. // start auto-offset looping with Offset at 1
  458. $search_arguments['Offset'] = 1;
  459. }
  460. else { }
  461. if (!empty($optional_params['Select'])) {
  462. $search_arguments['Select'] = $optional_params['Select'];
  463. }
  464. if (!empty($optional_params['RestrictedIndicator'])) {
  465. $search_arguments['RestrictedIndicator'] = $optional_params['RestrictedIndicator'];
  466. }
  467. $search_arguments['StandardNames'] = empty($optional_params['StandardNames']) ? 0 : $optional_params['StandardNames'];
  468. $continue_searching = true; // Keep searching if MAX ROWS is reached and offset_support is true
  469. while ($continue_searching) {
  470. $this->search_data[$this->int_result_pointer]['maxrows_reached'] = false;
  471. $this->search_data[$this->int_result_pointer]['search_requests']++;
  472. if ($this->search_data[$this->int_result_pointer]['search_requests'] == 300 && !$this->override_offset_protection) {
  473. // this call for SearchQuery() has resulted in X number of search requests
  474. // which is considered excessive. stopping the process in order to prevent
  475. // abuse against the server. almost ALWAYS happens when the user thinks Offset
  476. // is supported by the server when it's actually NOT supported
  477. $this->set_error_info("phrets", -1, "Last SearchQuery() has resulted in 300+ requests to the server. Stopping to prevent abuse");
  478. return false;
  479. }
  480. // make request
  481. $result = $this->RETSRequest($this->capability_url['Search'], $search_arguments);
  482. if (!$result) {
  483. return false;
  484. }
  485. list($headers, $body) = $result;
  486. $body = $this->fix_encoding($body);
  487. $xml = $this->ParseXMLResponse($body);
  488. if (!$xml) {
  489. return false;
  490. }
  491. // log replycode and replytext for reference later
  492. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  493. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  494. if ($xml['ReplyCode'] != 0) {
  495. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  496. return false;
  497. }
  498. if (isset($xml->DELIMITER)) {
  499. // delimiter found so we have at least a COLUMNS row to parse
  500. $delimiter_character = chr("{$xml->DELIMITER->attributes()->value}");
  501. $this->search_data[$this->int_result_pointer]['delimiter_character'] = $delimiter_character;
  502. $column_names = "{$xml->COLUMNS[0]}";
  503. $column_names = preg_replace("/^{$delimiter_character}/", "", $column_names);
  504. $column_names = preg_replace("/{$delimiter_character}\$/", "", $column_names);
  505. $this->search_data[$this->int_result_pointer]['column_names'] = explode($delimiter_character, $column_names);
  506. }
  507. if (isset($xml->DATA)) {
  508. foreach ($xml->DATA as $key) {
  509. $field_data = "{$key}";
  510. // split up DATA row on delimiter found earlier
  511. $this->search_data[$this->int_result_pointer]['data'][] = $field_data;
  512. $this->search_data[$this->int_result_pointer]['last_search_returned']++;
  513. }
  514. }
  515. if (isset($xml->MAXROWS)) {
  516. // MAXROWS tag found. the RETS server withheld records.
  517. // if the server supports Offset, more requests can be sent to page through results
  518. // until this tag isn't found anymore.
  519. $this->search_data[$this->int_result_pointer]['maxrows_reached'] = true;
  520. }
  521. if (isset($xml->COUNT)) {
  522. // found the record count returned. save it
  523. $this->search_data[$this->int_result_pointer]['total_records_found'] = "{$xml->COUNT->attributes()->Records}";
  524. }
  525. if (isset($xml)) {
  526. unset($xml);
  527. }
  528. if ($this->IsMaxrowsReached($this->int_result_pointer) && $this->offset_support) {
  529. $continue_searching = true;
  530. $search_arguments['Offset'] = $this->NumRows($this->int_result_pointer) + 1;
  531. }
  532. else {
  533. $continue_searching = false;
  534. }
  535. }
  536. return $this->int_result_pointer;
  537. }
  538. public function Search($resource, $class, $query = "", $optional_params = array()) {
  539. $data_table = array();
  540. $int_result_pointer = $this->SearchQuery($resource, $class, $query, $optional_params);
  541. while ($row = $this->FetchRow($int_result_pointer)) {
  542. $data_table[] = $row;
  543. }
  544. return $data_table;
  545. }
  546. public function GetAllLookupValues($resource) {
  547. $this->reset_error_info();
  548. if (empty($resource)) {
  549. die("Resource parameter is required in GetAllLookupValues() request.");
  550. }
  551. if (empty($this->capability_url['GetMetadata'])) {
  552. die("GetAllLookupValues() called but unable to find GetMetadata location. Failed login?\n");
  553. }
  554. // make request
  555. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  556. array(
  557. 'Type' => 'METADATA-LOOKUP_TYPE',
  558. 'ID' => $resource.':*',
  559. 'Format' => 'STANDARD-XML'
  560. )
  561. );
  562. if (!$result) {
  563. return false;
  564. }
  565. list($headers, $body) = $result;
  566. $xml = $this->ParseXMLResponse($body);
  567. if (!$xml) {
  568. return false;
  569. }
  570. if ($xml['ReplyCode'] != 0) {
  571. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  572. return false;
  573. }
  574. $this_table = array();
  575. // parse XML into a nice array
  576. if ($xml->METADATA && $xml->METADATA->{'METADATA-LOOKUP_TYPE'}) {
  577. foreach ($xml->METADATA->{'METADATA-LOOKUP_TYPE'} as $key) {
  578. if (!empty($key->attributes()->Lookup)) {
  579. $this_lookup = array();
  580. $lookup_xml_array = array();
  581. if (!empty($key->LookupType)) {
  582. $lookup_xml_array = $key->LookupType;
  583. }
  584. else {
  585. $lookup_xml_array = $key->Lookup;
  586. }
  587. if (is_object($lookup_xml_array)) {
  588. foreach ($lookup_xml_array as $look) {
  589. $metadataentryid = isset($look->MetadataEntryID) ? "{$look->MetadataEntryID}" : "";
  590. $value = isset($look->Value) ? "{$look->Value}" : "";
  591. $shortvalue = isset($look->ShortValue) ? "{$look->ShortValue}" : "";
  592. $longvalue = isset($look->LongValue) ? "{$look->LongValue}" : "";
  593. $this_lookup[] = array(
  594. 'MetadataEntryID' => $metadataentryid,
  595. 'Value' => $value,
  596. 'ShortValue' => $shortvalue,
  597. 'LongValue' => $longvalue
  598. );
  599. }
  600. }
  601. $this_table[] = array('Lookup' => "{$key->attributes()->Lookup}", 'Values' => $this_lookup);
  602. }
  603. }
  604. }
  605. // return the big array
  606. return $this_table;
  607. }
  608. public function GetLookupValues($resource, $lookupname) {
  609. $this->reset_error_info();
  610. if (empty($resource)) {
  611. die("Resource parameter is required in GetLookupValues() request.");
  612. }
  613. if (empty($lookupname)) {
  614. die("Lookup Name parameter is required in GetLookupValues() request.");
  615. }
  616. if (empty($this->capability_url['GetMetadata'])) {
  617. die("GetLookupValues() called but unable to find GetMetadata location. Failed login?\n");
  618. }
  619. // make request
  620. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  621. array(
  622. 'Type' => 'METADATA-LOOKUP_TYPE',
  623. 'ID' => $resource.':'.$lookupname,
  624. 'Format' => 'STANDARD-XML'
  625. )
  626. );
  627. if (!$result) {
  628. return false;
  629. }
  630. list($headers, $body) = $result;
  631. $xml = $this->ParseXMLResponse($body);
  632. if (!$xml) {
  633. return false;
  634. }
  635. if ($xml['ReplyCode'] != 0) {
  636. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  637. return false;
  638. }
  639. $this_table = array();
  640. // parse XML into a nice array
  641. if ($xml->METADATA && $xml->METADATA->{'METADATA-LOOKUP_TYPE'}) {
  642. $lookup_xml_array = array();
  643. if (!empty($xml->METADATA->{'METADATA-LOOKUP_TYPE'}->LookupType)) {
  644. $lookup_xml_array = $xml->METADATA->{'METADATA-LOOKUP_TYPE'}->LookupType;
  645. }
  646. else {
  647. $lookup_xml_array = $xml->METADATA->{'METADATA-LOOKUP_TYPE'}->Lookup;
  648. }
  649. if (is_object($lookup_xml_array)) {
  650. foreach ($lookup_xml_array as $key) {
  651. if (isset($key->Value)) {
  652. $metadataentryid = isset($key->MetadataEntryID) ? "{$key->MetadataEntryID}" : "";
  653. $value = isset($key->Value) ? "{$key->Value}" : "";
  654. $shortvalue = isset($key->ShortValue) ? "{$key->ShortValue}" : "";
  655. $longvalue = isset($key->LongValue) ? "{$key->LongValue}" : "";
  656. $this_table[] = array(
  657. 'MetadataEntryID' => $metadataentryid,
  658. 'Value' => $value,
  659. 'ShortValue' => $shortvalue,
  660. 'LongValue' => $longvalue
  661. );
  662. }
  663. }
  664. }
  665. }
  666. // return the big array
  667. return $this_table;
  668. }
  669. public function GetMetadataResources($id = 0) {
  670. $this->reset_error_info();
  671. if (empty($this->capability_url['GetMetadata'])) {
  672. die("GetMetadataResources() called but unable to find GetMetadata location. Failed login?\n");
  673. }
  674. // make request
  675. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  676. array(
  677. 'Type' => 'METADATA-RESOURCE',
  678. 'ID' => $id,
  679. 'Format' => 'STANDARD-XML'
  680. )
  681. );
  682. if (!$result) {
  683. return false;
  684. }
  685. list($headers, $body) = $result;
  686. $xml = $this->ParseXMLResponse($body);
  687. if (!$xml) {
  688. return false;
  689. }
  690. if ($xml['ReplyCode'] != 0) {
  691. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  692. return false;
  693. }
  694. $this_resource = array();
  695. // parse XML into a nice array
  696. if ($xml->METADATA) {
  697. foreach ($xml->METADATA->{'METADATA-RESOURCE'}->Resource as $key => $value) {
  698. $this_resource["{$value->ResourceID}"] = array(
  699. 'ResourceID' => "{$value->ResourceID}",
  700. 'StandardName'=>"{$value->StandardName}",
  701. 'VisibleName' => "{$value->VisibleName}",
  702. 'Description' => "{$value->Description}",
  703. 'KeyField' => "{$value->KeyField}",
  704. 'ClassCount' => "{$value->ClassCount}",
  705. 'ClassVersion' => "{$value->ClassVersion}",
  706. 'ClassDate' => "{$value->ClassDate}",
  707. 'ObjectVersion' => "{$value->ObjectVersion}",
  708. 'ObjectDate' => "{$value->ObjectDate}",
  709. 'SearchHelpVersion' => "{$value->SearchHelpVersion}",
  710. 'SearchHelpDate' => "{$value->SearchHelpDate}",
  711. 'EditMaskVersion' => "{$value->EditMaskVersion}",
  712. 'EditMaskDate' => "{$value->EditMaskDate}",
  713. 'LookupVersion' => "{$value->LookupVersion}",
  714. 'LookupDate' => "{$value->LookupDate}",
  715. 'UpdateHelpVersion' => "{$value->UpdateHelpVersion}",
  716. 'UpdateHelpDate' => "{$value->UpdateHelpDate}",
  717. 'ValidationExpressionVersion' => "{$value->ValidationExpressionVersion}",
  718. 'ValidationExpressionDate' => "{$value->ValidationExpressionDate}",
  719. 'ValidationLookupVersion' => "{$value->ValidationLookupVersion}",
  720. 'ValidationLookupDate' => "{$value->ValidationLookupDate}",
  721. 'ValidationExternalVersion' => "{$value->ValidationExternalVersion}",
  722. 'ValidationExternalDate' => "{$value->ValidationExternalDate}"
  723. );
  724. }
  725. }
  726. // send back array
  727. return $this_resource;
  728. }
  729. public function GetMetadataInfo($id = 0) {
  730. if (empty($this->capability_url['GetMetadata'])) {
  731. die("GetMetadataInfo() called but unable to find GetMetadata location. Failed login?\n");
  732. }
  733. return $this->GetMetadataResources($id);
  734. }
  735. public function GetMetadataTable($resource, $class) {
  736. $this->reset_error_info();
  737. $id = $resource.':'.$class;
  738. if (empty($resource)) {
  739. die("Resource parameter is required in GetMetadata() request.");
  740. }
  741. if (empty($class)) {
  742. die("Class parameter is required in GetMetadata() request.");
  743. }
  744. if (empty($this->capability_url['GetMetadata'])) {
  745. die("GetMetadataTable() called but unable to find GetMetadata location. Failed login?\n");
  746. }
  747. // request specific metadata
  748. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  749. array(
  750. 'Type' => 'METADATA-TABLE',
  751. 'ID' => $id,
  752. 'Format' => 'STANDARD-XML'
  753. )
  754. );
  755. if (!$result) {
  756. return false;
  757. }
  758. list($headers, $body) = $result;
  759. $xml = $this->ParseXMLResponse($body);
  760. if (!$xml) {
  761. return false;
  762. }
  763. // log replycode and replytext for reference later
  764. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  765. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  766. if ($xml['ReplyCode'] != 0) {
  767. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  768. return false;
  769. }
  770. $this_table = array();
  771. // parse XML into a nice array
  772. if ($xml->METADATA) {
  773. foreach ($xml->METADATA->{'METADATA-TABLE'}->Field as $key) {
  774. $this_table[] = array(
  775. 'SystemName' => "{$key->SystemName}",
  776. 'StandardName' => "{$key->StandardName}",
  777. 'LongName' => "{$key->LongName}",
  778. 'DBName' => "{$key->DBName}",
  779. 'ShortName' => "{$key->ShortName}",
  780. 'MaximumLength' => "{$key->MaximumLength}",
  781. 'DataType' => "{$key->DataType}",
  782. 'Precision' => "{$key->Precision}",
  783. 'Searchable' => "{$key->Searchable}",
  784. 'Interpretation' => "{$key->Interpretation}",
  785. 'Alignment' => "{$key->Alignment}",
  786. 'UseSeparator' => "{$key->UseSeparator}",
  787. 'EditMaskID' => "{$key->EditMaskID}",
  788. 'LookupName' => "{$key->LookupName}",
  789. 'MaxSelect' => "{$key->MaxSelect}",
  790. 'Units' => "{$key->Units}",
  791. 'Index' => "{$key->Index}",
  792. 'Minimum' => "{$key->Minimum}",
  793. 'Maximum' => "{$key->Maximum}",
  794. 'Default' => "{$key->Default}",
  795. 'Required' => "{$key->Required}",
  796. 'SearchHelpID' => "{$key->SearchHelpID}",
  797. 'Unique' => "{$key->Unique}",
  798. 'MetadataEntryID' => "{$key->MetadataEntryID}",
  799. 'ModTimeStamp' => "{$key->ModTimeStamp}",
  800. 'ForeignKeyName' => "{$key->ForiengKeyName}",
  801. 'ForeignField' => "{$key->ForeignField}",
  802. 'InKeyIndex' => "{$key->InKeyIndex}"
  803. );
  804. }
  805. }
  806. // return the big array
  807. return $this_table;
  808. }
  809. public function GetMetadata($resource, $class) {
  810. if (empty($this->capability_url['GetMetadata'])) {
  811. die("GetMetadata() called but unable to find GetMetadata location. Failed login?\n");
  812. }
  813. return $this->GetMetadataTable($resource, $class);
  814. }
  815. public function GetMetadataObjects($id) {
  816. $this->reset_error_info();
  817. if (empty($id)) {
  818. die("ID parameter is required in GetMetadataObjects() request.");
  819. }
  820. if (empty($this->capability_url['GetMetadata'])) {
  821. die("GetMetadataObjects() called but unable to find GetMetadata location. Failed login?\n");
  822. }
  823. // request basic metadata information
  824. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  825. array(
  826. 'Type' => 'METADATA-OBJECT',
  827. 'ID' => $id,
  828. 'Format' => 'STANDARD-XML'
  829. )
  830. );
  831. if (!$result) {
  832. return false;
  833. }
  834. list($headers, $body) = $result;
  835. $xml = $this->ParseXMLResponse($body);
  836. if (!$xml) {
  837. return false;
  838. }
  839. // log replycode and replytext for reference later
  840. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  841. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  842. if ($xml['ReplyCode'] != 0) {
  843. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  844. return false;
  845. }
  846. $return_data = array();
  847. if (isset($xml->METADATA->{'METADATA-OBJECT'})) {
  848. // parse XML into a nice array
  849. foreach ($xml->METADATA->{'METADATA-OBJECT'} as $key => $value) {
  850. foreach ($value->Object as $key) {
  851. if (!empty($key->ObjectType)) {
  852. $return_data[] = array(
  853. 'MetadataEntryID' => "{$key->MetadataEntryID}",
  854. 'VisibleName' => "{$key->VisibleName}",
  855. 'ObjectTimeStamp' => "{$key->ObjectTimeStamp}",
  856. 'ObjectCount' => "{$key->ObjectCount}",
  857. 'ObjectType' => "{$key->ObjectType}",
  858. 'StandardName' => "{$key->StandardName}",
  859. 'MimeType' => "{$key->MimeType}",
  860. 'Description' => "{$key->Description}"
  861. );
  862. }
  863. }
  864. }
  865. }
  866. // send back array
  867. return $return_data;
  868. }
  869. public function GetMetadataClasses($id) {
  870. $this->reset_error_info();
  871. if (empty($id)) {
  872. die("ID parameter is required in GetMetadataClasses() request.");
  873. }
  874. if (empty($this->capability_url['GetMetadata'])) {
  875. die("GetMetadataClasses() called but unable to find GetMetadata location. Failed login?\n");
  876. }
  877. // request basic metadata information
  878. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  879. array(
  880. 'Type' => 'METADATA-CLASS',
  881. 'ID' => $id,
  882. 'Format' => 'STANDARD-XML'
  883. )
  884. );
  885. if (!$result) {
  886. return false;
  887. }
  888. list($headers, $body) = $result;
  889. $xml = $this->ParseXMLResponse($body);
  890. if (!$xml) {
  891. return false;
  892. }
  893. // log replycode and replytext for reference later
  894. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  895. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  896. if ($xml['ReplyCode'] != 0) {
  897. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  898. return false;
  899. }
  900. $return_data = array();
  901. // parse XML into a nice array
  902. if ($xml->METADATA) {
  903. foreach ($xml->METADATA->{'METADATA-CLASS'} as $key => $value) {
  904. foreach ($value->Class as $key) {
  905. if (!empty($key->ClassName)) {
  906. $return_data[] = array(
  907. 'ClassName' => "{$key->ClassName}",
  908. 'VisibleName' => "{$key->VisibleName}",
  909. 'StandardName' => "{$key->StandardName}",
  910. 'Description' => "{$key->Description}",
  911. 'TableVersion' => "{$key->TableVersion}",
  912. 'TableDate' => "{$key->TableDate}",
  913. 'UpdateVersion' => "{$key->UpdateVersion}",
  914. 'UpdateDate' => "{$key->UpdateDate}",
  915. 'ClassTimeStamp' => "{$key->ClassTimeStamp}",
  916. 'DeletedFlagField' => "{$key->DeletedFlagField}",
  917. 'DeletedFlagValue' => "{$key->DeletedFlagValue}",
  918. 'HasKeyIndex' => "{$key->HasKeyIndex}"
  919. );
  920. }
  921. }
  922. }
  923. }
  924. // send back array
  925. return $return_data;
  926. }
  927. public function GetMetadataTypes($id = 0) {
  928. $this->reset_error_info();
  929. if (empty($this->capability_url['GetMetadata'])) {
  930. die("GetMetadataTypes() called but unable to find GetMetadata location. Failed login?\n");
  931. }
  932. // request basic metadata information
  933. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  934. array(
  935. 'Type' => 'METADATA-CLASS',
  936. 'ID' => $id,
  937. 'Format' => 'STANDARD-XML'
  938. )
  939. );
  940. if (!$result) {
  941. return false;
  942. }
  943. list($headers, $body) = $result;
  944. $xml = $this->ParseXMLResponse($body);
  945. if (!$xml) {
  946. return false;
  947. }
  948. // log replycode and replytext for reference later
  949. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  950. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  951. if ($xml['ReplyCode'] != 0) {
  952. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  953. return false;
  954. }
  955. $return_data = array();
  956. // parse XML into a nice array
  957. if ($xml->METADATA) {
  958. foreach ($xml->METADATA->{'METADATA-CLASS'} as $key => $value) {
  959. $resource = $value['Resource'];
  960. $this_resource = array();
  961. foreach ($value->Class as $key) {
  962. if (!empty($key->ClassName)) {
  963. $this_resource[] = array(
  964. 'ClassName' => "{$key->ClassName}",
  965. 'VisibleName' => "{$key->VisibleName}",
  966. 'StandardName' => "{$key->StandardName}",
  967. 'Description' => "{$key->Description}",
  968. 'TableVersion' => "{$key->TableVersion}",
  969. 'TableDate' => "{$key->TableDate}",
  970. 'UpdateVersion' => "{$key->UpdateVersion}",
  971. 'UpdateDate' => "{$key->UpdateDate}"
  972. );
  973. }
  974. }
  975. // prepare 2-deep array
  976. $return_data[] = array('Resource' => "{$resource}", 'Data' => $this_resource);
  977. }
  978. }
  979. // send back array
  980. return $return_data;
  981. }
  982. public function GetServerSoftware() {
  983. return $this->server_software;
  984. }
  985. public function GetServerVersion() {
  986. return $this->server_version;
  987. }
  988. public function CheckAuthSupport($type = "") {
  989. if ($type == "basic") {
  990. return $this->auth_support_basic;
  991. }
  992. if ($type == "digest") {
  993. return $this->auth_support_digest;
  994. }
  995. $this->set_error_info("phrets", -1, "Unknown auth type requested.");
  996. return false;
  997. }
  998. public function GetAllTransactions() {
  999. // read through capability_urls read during the Login and return
  1000. $transactions = array();
  1001. if (is_array($this->capability_url)) {
  1002. foreach ($this->capability_url as $key => $value) {
  1003. $transactions[] = $key;
  1004. }
  1005. }
  1006. return $transactions;
  1007. }
  1008. public function LastRequestURL() {
  1009. return $this->last_request_url;
  1010. }
  1011. public function GetLoginURL() {
  1012. // see if the saved Login URL has a hostname included.
  1013. // if not, make it based on the URL given in the Connect() call
  1014. $parse_results = parse_url($this->capability_url['Login'], PHP_URL_HOST);
  1015. if (empty($parse_results)) {
  1016. // login transaction gave a relative path for this action
  1017. $request_url = $this->server_protocol.'://'.$this->server_hostname.':'.$this->server_port.''.$this->capability_url['Login'];
  1018. }
  1019. else {
  1020. // login transaction gave an absolute path for this action
  1021. $request_url = $this->capability_url['Login'];
  1022. }
  1023. if (empty($request_url)) {
  1024. $this->set_error_info("phrets", -1, "Unable to find a login URL. Did initial login fail?");
  1025. return false;
  1026. }
  1027. return $request_url;
  1028. }
  1029. public function GetServerInformation() {
  1030. $this->reset_error_info();
  1031. if (empty($this->capability_url['GetMetadata'])) {
  1032. die("GetServerInformation() called but unable to find GetMetadata location. Failed login?\n");
  1033. }
  1034. // request server information
  1035. $result = $this->RETSRequest($this->capability_url['GetMetadata'],
  1036. array(
  1037. 'Type' => 'METADATA-SYSTEM',
  1038. 'ID' => 0,
  1039. 'Format' => 'STANDARD-XML'
  1040. )
  1041. );
  1042. if (!$result) {
  1043. return false;
  1044. }
  1045. list($headers, $body) = $result;
  1046. $xml = $this->ParseXMLResponse($body);
  1047. if (!$xml) {
  1048. return false;
  1049. }
  1050. if ($xml['ReplyCode'] != 0) {
  1051. $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}");
  1052. return false;
  1053. }
  1054. $system_id = "";
  1055. $system_description = "";
  1056. $system_comments = "";
  1057. if ($this->is_server_version("1_5_or_below")) {
  1058. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->System->SystemID)) {
  1059. $system_id = "{$xml->METADATA->{'METADATA-SYSTEM'}->System->SystemID}";
  1060. }
  1061. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->System->SystemDescription)) {
  1062. $system_description = "{$xml->METADATA->{'METADATA-SYSTEM'}->System->SystemDescription}";
  1063. }
  1064. $timezone_offset = "";
  1065. }
  1066. else {
  1067. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemID)) {
  1068. $system_id = "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemID}";
  1069. }
  1070. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemDescription)) {
  1071. $system_description = "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemDescription}";
  1072. }
  1073. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->TimeZoneOffset)) {
  1074. $timezone_offset = "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->TimeZoneOffset}";
  1075. }
  1076. }
  1077. if (isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->Comments)) {
  1078. $system_comments = "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->Comments}";
  1079. }
  1080. return array(
  1081. 'SystemID' => $system_id,
  1082. 'SystemDescription' => $system_description,
  1083. 'TimeZoneOffset' => $timezone_offset,
  1084. 'Comments' => $system_comments
  1085. );
  1086. }
  1087. public function Disconnect() {
  1088. $this->reset_error_info();
  1089. if (empty($this->capability_url['Logout'])) {
  1090. die("Disconnect() called but unable to find Logout location. Failed login?\n");
  1091. }
  1092. // make request
  1093. $result = $this->RETSRequest($this->capability_url['Logout']);
  1094. if (!$result) {
  1095. return false;
  1096. }
  1097. list($headers,$body) = $result;
  1098. // close cURL connection
  1099. curl_close($this->ch);
  1100. if ($this->debug_mode == true) {
  1101. // close cURL debug log file handler
  1102. fclose($this->debug_log);
  1103. }
  1104. if (file_exists($this->cookie_file)) {
  1105. @unlink($this->cookie_file);
  1106. }
  1107. return true;
  1108. }
  1109. public function Connect($login_url, $username, $password, $ua_pwd = "") {
  1110. $this->reset_error_info();
  1111. if (empty($login_url)) {
  1112. die("PHRETS: Login URL missing from Connect()");
  1113. }
  1114. if (empty($username)) {
  1115. die("PHRETS: Username missing from Connect()");
  1116. }
  1117. if (empty($password)) {
  1118. die("PHRETS: Password missing from Connect()");
  1119. }
  1120. if (empty($this->static_headers['RETS-Version'])) {
  1121. $this->AddHeader("RETS-Version", "RETS/1.5");
  1122. }
  1123. if (empty($this->static_headers['User-Agent'])) {
  1124. $this->AddHeader("User-Agent", "PHRETS/1.0");
  1125. }
  1126. if (empty($this->static_headers['Accept']) && $this->static_headers['RETS-Version'] == "RETS/1.5") {
  1127. $this->AddHeader("Accept", "*/*");
  1128. }
  1129. // chop up Login URL to use for later requests
  1130. $url_parts = parse_url($login_url);
  1131. $this->server_hostname = $url_parts['host'];
  1132. $this->server_port = (empty($url_parts['port'])) ? 80 : $url_parts['port'];
  1133. $this->server_protocol = $url_parts['scheme'];
  1134. $this->capability_url['Login'] = $url_parts['path'];
  1135. if (isset($url_parts['query']) && !empty($url_parts['query'])) {
  1136. $this->capability_url['Login'] .= "?{$url_parts['query']}";
  1137. }
  1138. $this->username = $username;
  1139. $this->password = $password;
  1140. if (!empty($ua_pwd)) {
  1141. // force use of RETS 1.7 User-Agent Authentication
  1142. $this->ua_auth = true;
  1143. $this->ua_pwd = $ua_pwd;
  1144. }
  1145. if (empty($this->cookie_file)) {
  1146. $this->cookie_file = tempnam("", "phrets");
  1147. }
  1148. @touch($this->cookie_file);
  1149. if (!is_writable($this->cookie_file)) {
  1150. $this->set_error_info("phrets", -1, "Cookie file \"{$this->cookie_file}\" cannot be written to. Must be an absolute path and must be writable");
  1151. return false;
  1152. }
  1153. // start cURL magic
  1154. $this->ch = curl_init();
  1155. curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, array(&$this, 'read_custom_curl_headers'));
  1156. if ($this->debug_mode == true) {
  1157. // open file handler to be used by cURL debug log
  1158. $this->debug_log = @fopen($this->debug_file, 'a');
  1159. if ($this->debug_log) {
  1160. curl_setopt($this->ch, CURLOPT_VERBOSE, 1);
  1161. curl_setopt($this->ch, CURLOPT_STDERR, $this->debug_log);
  1162. }
  1163. else {
  1164. echo "Unable to save debug log to {$this->debug_file}\n";
  1165. }
  1166. }
  1167. curl_setopt($this->ch, CURLOPT_HEADER, false);
  1168. if ($this->force_basic_authentication == true) {
  1169. curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  1170. }
  1171. else {
  1172. curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC);
  1173. }
  1174. if ($this->disable_follow_location != true) {
  1175. curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
  1176. }
  1177. curl_setopt($this->ch, CURLOPT_USERPWD, $this->username.":".$this->password);
  1178. curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
  1179. curl_setopt($this->ch, CURLOPT_COOKIEFILE, $this->cookie_file);
  1180. curl_setopt($this->ch, CURLOPT_TIMEOUT, 0);
  1181. curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
  1182. // make request to Login transaction
  1183. $result = $this->RETSRequest($this->capability_url['Login']);
  1184. if (!$result) {
  1185. return false;
  1186. }
  1187. list($headers,$body) = $result;
  1188. // parse body response
  1189. $xml = $this->ParseXMLResponse($body);
  1190. if (!$xml) {
  1191. return false;
  1192. }
  1193. // log replycode and replytext for reference later
  1194. $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}";
  1195. $this->last_request['ReplyText'] = "{$xml['ReplyText']}";
  1196. // chop up login response
  1197. // if multiple parts of the login response aren't found splitting on \r\n, redo using just \n
  1198. $login_response = array();
  1199. if ($this->server_version == "RETS/1.0") {
  1200. if (isset($xml)) {
  1201. $login_response = explode("\r\n", $xml);
  1202. if (empty($login_response[3])) {
  1203. $login_response = explode("\n", $xml);
  1204. }
  1205. }
  1206. }
  1207. else {
  1208. if (isset($xml->{'RETS-RESPONSE'})) {
  1209. $login_response = explode("\r\n", $xml->{'RETS-RESPONSE'});
  1210. if (empty($login_response[3])) {
  1211. $login_response = explode("\n", $xml->{'RETS-RESPONSE'});
  1212. }
  1213. }
  1214. }
  1215. // parse login response. grab all capability URLs known and ones that begin with X-
  1216. // otherwise, it's a piece of server information to save for reference
  1217. foreach ($login_response as $line) {
  1218. $name = null;
  1219. $value = null;
  1220. if (strpos($line, '=') !== false) {
  1221. @list($name,$value) = explode("=", $line, 2);
  1222. }
  1223. $name = trim($name);
  1224. $value = trim($value);
  1225. if (!empty($name) && !empty($value)) {
  1226. if (isset($this->allowed_capabilities[$name]) || preg_match('/^X\-/', $name) == true) {
  1227. $this->capability_url[$name] = $value;
  1228. }
  1229. else {
  1230. $this->server_information[$name] = $value;
  1231. }
  1232. }
  1233. }
  1234. // if 'Action' capability URL is provided, we MUST request it following the successful Login
  1235. if (isset($this->capability_url['Action']) && !empty($this->capability_url['Action'])) {
  1236. $result = $this->RETSRequest($this->capability_url['Action']);
  1237. if (!$result) {
  1238. return false;
  1239. }
  1240. list($headers,$body) = $result;
  1241. }
  1242. if ($this->compression_enabled == true) {
  1243. curl_setopt($this->ch, CURLOPT_ENCODING, "gzip");
  1244. }
  1245. if ($this->last_request['ReplyCode'] == 0) {
  1246. return true;
  1247. }
  1248. else {
  1249. $this->set_error_info("rets", $this->last_request['ReplyCode'], $this->last_request['ReplyText']);
  1250. return false;
  1251. }
  1252. }
  1253. public function LastRequest() {
  1254. // return replycode and replytext from last request
  1255. return $this->last_request;
  1256. }
  1257. public function AddHeader($name, $value) {
  1258. // add static header for cURL requests
  1259. $this->static_headers[$name] = $value;
  1260. return true;
  1261. }
  1262. public function DeleteHeader($name) {
  1263. // delete static header from cURL requests
  1264. unset($this->static_headers[$name]);
  1265. return true;
  1266. }
  1267. public function ParseXMLResponse($data = "") {
  1268. $this->reset_error_info();
  1269. if (!empty($data)) {
  1270. // parse XML function. ability to replace SimpleXML with something later fairly easily
  1271. $xml = @simplexml_load_string($data);
  1272. if (!is_object($xml)) {
  1273. $this->set_error_info("xml", -1, "XML parsing error: {$data}");
  1274. return false;
  1275. }
  1276. return $xml;
  1277. }
  1278. else {
  1279. $this->set_error_info("xml", -1, "XML parsing error. No data to parse");
  1280. return false;
  1281. }
  1282. }
  1283. public function RETSRequest($action, $parameters = "") {
  1284. $this->reset_error_info();
  1285. $this->last_response_headers = array();
  1286. $this->last_response_headers_raw = "";
  1287. $this->last_remembered_header = "";
  1288. // exposed raw RETS request function. used internally and externally
  1289. if (empty($action)) {
  1290. die("RETSRequest called but Action passed has no value. Failed login?\n");
  1291. }
  1292. $parse_results = parse_url($action, PHP_URL_HOST);
  1293. if (empty($parse_results)) {
  1294. // login transaction gave a relative path for this action
  1295. $request_url = $this->server_protocol.'://'.$this->server_hostname.':'.$this->server_port.''.$action;
  1296. }
  1297. else {
  1298. // login transaction gave an absolute path for this action
  1299. $request_url = $action;
  1300. }
  1301. // build query string from arguments
  1302. $request_arguments = "";
  1303. if (is_array($parameters)) {
  1304. $request_arguments = http_build_query($parameters);
  1305. }
  1306. // build entire URL if needed
  1307. if (!empty($request_arguments)) {
  1308. $request_url = $request_url .'?'. $request_arguments;
  1309. }
  1310. // build headers to pass in cURL
  1311. $request_headers = "";
  1312. if (is_array($this->static_headers)) {
  1313. foreach ($this->static_headers as $key => $value) {
  1314. $request_headers .= "{$key}: {$value}\r\n";
  1315. }
  1316. }
  1317. if ($this->ua_auth == true) {
  1318. $session_id_to_calculate_with = "";
  1319. // calculate RETS-UA-Authorization header
  1320. $ua_a1 = md5($this->static_headers['User-Agent'] .':'. $this->ua_pwd);
  1321. $session_id_to_calculate_with = ($this->use_interealty_ua_auth == true) ? "" : $this->session_id;
  1322. $ua_dig_resp = md5(trim($ua_a1) .':'. trim($this->request_id) .':'. trim($session_id_to_calculate_with) .':'. trim($this->static_headers['RETS-Version']));
  1323. $request_headers .= "RETS-UA-Authorization: Digest {$ua_dig_resp}\r\n";
  1324. }
  1325. $this->last_request_url = $request_url;
  1326. curl_setopt($this->ch, CURLOPT_URL, $request_url);
  1327. curl_setopt($this->ch, CURLOPT_HTTPHEADER, array(trim($request_headers)));
  1328. // do it
  1329. $response_body = curl_exec($this->ch);
  1330. $response_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
  1331. if ($this->debug_mode == true) {
  1332. fwrite($this->debug_log, $response_body ."\n");
  1333. }
  1334. if ($this->catch_last_response == true) {
  1335. $this->last_server_response = $this->last_response_headers_raw . $response_body;
  1336. }
  1337. if (isset($this->last_response_headers['WWW-Authenticate'])) {
  1338. if (strpos($this->last_response_headers['WWW-Authenticate'], 'Basic') !== false) {
  1339. $this->auth_support_basic = true;
  1340. }
  1341. if (strpos($this->last_response_headers['WWW-Authenticate'], 'Digest') !== false) {
  1342. $this->auth_support_digest = true;
  1343. }
  1344. }
  1345. if (isset($this->last_response_headers['RETS-Version'])) {
  1346. $this->server_version = $this->last_response_headers['RETS-Version'];
  1347. }
  1348. if (isset($this->last_response_headers['Server'])) {
  1349. $this->server_software = $this->last_response_headers['Server'];
  1350. }
  1351. if (isset($this->last_response_headers['Set-Cookie'])) {
  1352. if (preg_match('/RETS-Session-ID\=(.*?)(\;|\s+|$)/', $this->last_response_headers['Set-Cookie'], $matches)) {
  1353. $this->session_id = $matches[1];
  1354. }
  1355. }
  1356. if ($response_code != 200) {
  1357. $this->set_error_info("http", $response_code, $response_body);
  1358. return false;
  1359. }

Large files files are truncated, but you can click here to view the full file