From 4f4e8a556bf398b474745a8f58840a333ae5f983 Mon Sep 17 00:00:00 2001 From: David Kullmann Date: Mon, 7 Nov 2011 12:19:44 -0500 Subject: [PATCH] adding phRets library and non-working shell thing --- libs/rets.php | 141 ++++ vendors/phrets.php | 1610 +++++++++++++++++++++++++++++++++++++++ vendors/shells/rets.php | 19 + 3 files changed, 1770 insertions(+) create mode 100644 libs/rets.php create mode 100644 vendors/phrets.php create mode 100644 vendors/shells/rets.php diff --git a/libs/rets.php b/libs/rets.php new file mode 100644 index 0000000..2b821cf --- /dev/null +++ b/libs/rets.php @@ -0,0 +1,141 @@ + 'vendors'.DS.'phrets.php')); + +/** + * Whatever + */ +define('RETS_VERSION_HEADER', 'RETS-Version'); +define('USERAGENT_HEADER', 'User-Agent'); + +/** + * Class for connecting via phRETS and forwarding methods to phRETS + * + * @package rets.lib.rets + * @author David Kullmann + */ +class RETS { + + /** + * phRETS object + * + * @author David Kullmann + */ + public static $phRETS; + + /** + * Configuration options + * + * @author David Kullmann + */ + public static $config; + + /** + * Connection status + * + * @author David Kullmann + */ + public static $connection; + + /** + * Available RETS resources and their class information + * + * @author David Kullmann + */ + public static $resources; + + /** + * Get the config and initialize the phRETS object + * + * @author David Kullmann + */ + public function __construct() { + if (empty(self::$phRETS)) { + self::$phRETS = new phRETS; + } + self::$config = Configure::read('RETS'); + if (!is_array(self::$config)) { + throw new Exception('Configure::read("RETS") was not able to load your settings'); + } + } + + /** + * Connect via phRETS and save the connection + * + * @param string $options + * @return void + * @author David Kullmann + */ + public function connect($options = array()) { + $options = array_merge(self::$config, $options); + + if (!empty($options['rets_version'])) { + self::$phRETS->AddHeader(RETS_VERSION_HEADER, $options['rets_version']); + } + + if (!empty($options['useragent'])) { + self::$phRETS->AddHeader(USERAGENT_HEADER, $options['useragent']); + } + + self::$connection = self::$phRETS->Connect( + $options['login_url'], + $options['username'], + $options['password'] + ); + + if (!self::$connection) { + $exception = self::$phRETS->error(); + throw new Exception($exception['text']); + } + + self::$resources = Set::combine(self::$phRETS->GetMetadataTypes(), '{n}.Resource', '{n}.Data.0'); + } + + /** + * Disconnect phRETS and unset the connection + * + * @return void + * @author David Kullmann + */ + public function disconnect() { + self::$phRETS->Disconnect(); + self::$connection = false; + } + + /** + * Forward methods to phRETS + * + * @param string $method + * @param string $params + * @return void + * @author David Kullmann + */ + public function __call($method, $params) { + try { + return call_user_func_array(array(self::$phRETS, $method), $params); + } catch (Exception $e) { + error_log($e); + } + } + + /** + * Forward static methods to phRETS + * + * @param string $method + * @param string $params + * @return void + * @author David Kullmann + */ + public static function __callstatic($method, $params) { + try { + return call_user_func_array(array(self::$phRETS, $method), $params); + } catch (Exception $e) { + error_log($e); + } + } +} +?> \ No newline at end of file diff --git a/vendors/phrets.php b/vendors/phrets.php new file mode 100644 index 0000000..0d66892 --- /dev/null +++ b/vendors/phrets.php @@ -0,0 +1,1610 @@ + 1, + "ChangePassword" => 1, + "GetObject" => 1, + "Login" => 1, + "LoginComplete" => 1, + "Logout" => 1, + "Search" => 1, + "GetMetadata" => 1, + "ServerInformation" => 1, + "Update" => 1 + ); + private $last_request = array(); + private $auth_support_basic = false; + private $auth_support_digest = false; + private $last_response_headers = array(); + private $last_response_headers_raw = ""; + private $compression_enabled = false; + private $ua_pwd = ""; + private $ua_auth = false; + private $request_id = ""; + private $disable_follow_location = false; + private $force_basic_authentication = false; + private $use_interealty_ua_auth = false; + private $int_result_pointer = 0; + private $error_info = array(); + private $last_request_url; + private $last_server_response; + private $session_id; + private $catch_last_response = false; + private $disable_encoding_fix = false; + private $offset_support = false; + private $override_offset_protection = false; + + + + public function phRETS() { } + + + public function GetLastServerResponse() { + return $this->last_server_response; + } + + + public function FirewallTest() { + $google = $this->FirewallTestConn("google.com", 80); + $crt80 = $this->FirewallTestConn("demo.crt.realtors.org", 80); + $crt6103 = $this->FirewallTestConn("demo.crt.realtors.org", 6103); + $flexmls80 = $this->FirewallTestConn("retsgw.flexmls.com", 80); + $flexmls6103 = $this->FirewallTestConn("retsgw.flexmls.com", 6103); + + if (!$google && !$crt80 && !$crt6103 && !$flexmls80 && !$flexmls6103) { + echo "Firewall Result: All tests failed. Possible causes:"; + echo "
    "; + echo "
  1. Firewall is blocking your outbound connections
  2. "; + echo "
  3. You aren't connected to the internet
  4. "; + echo "
"; + return false; + } + + if (!$crt6103 && !$flexmls6103) { + echo "Firewall Result: All port 6103 tests failed. "; + echo "Likely cause: Firewall is blocking your outbound connections on port 6103."; + return false; + } + + if ($google && $crt6103 && $crt80 && $flexmls6103 && $flexmls80) { + echo "Firewall Result: All tests passed."; + return true; + } + + if (($crt6103 && !$flexmls6103) || (!$crt6103 && $flexmls6103)) { + echo "Firewall Result: At least one port 6103 test passed. "; + echo "Likely cause: One of the test servers might be down but connections on port 80 and port 6103 should work."; + return true; + } + + if (!$google || !$crt80 || !$flexmls80) { + echo "Firewall Result: At least one port 80 test failed. "; + echo "Likely cause: One of the test servers might be down."; + return true; + } + + echo "Firewall Results: Unable to guess the issue. See individual test results above."; + return false; + + } + + + private function FirewallTestConn($hostname, $port = 6103) { + $fp = @fsockopen($hostname, $port, $errno, $errstr, 5); + + if (!$fp) { + echo "Firewall Test: {$hostname}:{$port} FAILED
\n"; + return false; + } + else { + @fclose($fp); + echo "Firewall Test: {$hostname}:{$port} GOOD
\n"; + return true; + } + + } + + + public function GetObject($resource, $type, $id, $photo_number = '*', $location = 0) { + $this->reset_error_info(); + $return_photos = array(); + + if (empty($resource)) { + die("Resource parameter is required for GetObject() request."); + } + if (empty($type)) { + die("Type parameter is required for GetObject() request."); + } + if (empty($id)) { + die("ID parameter is required for GetObject() request."); + } + if (empty($this->capability_url['GetObject'])) { + die("GetObject() called but unable to find GetObject location. Failed login?\n"); + } + + $send_id = ""; + $send_numb = ""; + + // check if $photo_number needs fixing + if (preg_match('/\,/', $photo_number)) { + // change the commas to colons for the request + $photo_number = preg_replace('/\,/', ':', $photo_number); + } + + if (preg_match('/\:/', $photo_number)) { + // photo number contains multiple objects + // chopping and cleaning + $requested_numbers = explode(":", $photo_number); + if (is_array($requested_numbers)) { + foreach ($requested_numbers as $numb) { + $numb = trim($numb); + if (!empty($numb) || $numb == "0") { + $send_numb .= "{$numb}:"; + } + } + } + $send_numb = preg_replace('/\:$/', '', $send_numb); + } + else { + $send_numb = trim($photo_number); + } + + if (preg_match('/\,/', $id)) { + // id contains multiple objects. + // chopping and combining with photo_number + $requested_ids = explode(",", $id); + if (is_array($requested_ids)) { + foreach ($requested_ids as $req_id) { + $req_id = trim($req_id); + if (!empty($req_id) && $req_id != "0") { + $send_id .= "{$req_id}:{$send_numb},"; + } + } + } + $send_id = preg_replace('/\,$/', '', $send_id); + } + else { + $send_id = trim($id).':'.$send_numb; + } + + // make request + $result = $this->RETSRequest($this->capability_url['GetObject'], array('Resource' => $resource, 'Type' => $type, 'ID' => $send_id, 'Location' => $location)); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + // fix case issue if exists + if (isset($this->last_response_headers['Content-type']) && !isset($this->last_response_headers['Content-Type'])) { + $this->last_response_headers['Content-Type'] = $this->last_response_headers['Content-type']; + } + + if (!isset($this->last_response_headers['Content-Type'])) { + $this->last_response_headers['Content-Type'] = ""; + } + + // check what type of response came back + if (preg_match('/multipart/', $this->last_response_headers['Content-Type'])) { + + // help bad responses be more multipart compliant + $body = "\r\n{$body}\r\n"; + + // multipart + preg_match('/boundary\=\"(.*?)\"/', $this->last_response_headers['Content-Type'], $matches); + if (isset($matches[1])) { + $boundary = $matches[1]; + } + else { + preg_match('/boundary\=(.*?)(\s|$|\;)/', $this->last_response_headers['Content-Type'], $matches); + $boundary = $matches[1]; + } + // strip quotes off of the boundary + $boundary = preg_replace('/^\"(.*?)\"$/', '\1', $boundary); + + // clean up the body to remove a reamble and epilogue + $body = preg_replace('/^(.*?)\r\n--'.$boundary.'\r\n/', "\r\n--{$boundary}\r\n", $body); + // make the last one look like the rest for easier parsing + $body = preg_replace('/\r\n--'.$boundary.'--/', "\r\n--{$boundary}\r\n", $body); + + // cut up the message + $multi_parts = array(); + $multi_parts = explode("\r\n--{$boundary}\r\n", $body); + // take off anything that happens before the first boundary (the preamble) + array_shift($multi_parts); + // take off anything after the last boundary (the epilogue) + array_pop($multi_parts); + + // go through each part of the multipart message + foreach ($multi_parts as $part) { + // default to processing headers + $on_headers = true; + $on_body = false; + $first_body_found = false; + $this_photo = array(); + + // go through the multipart chunk line-by-line + $body_parts = array(); + $body_parts = explode("\r\n", $part); + $this_photo['Data'] = ""; + foreach ($body_parts as $line) { + if (empty($line) && $on_headers == true) { + // blank line. switching to processing a body and moving on + $on_headers = false; + $on_body = true; + continue; + } + if ($on_headers == true) { + // non blank line and we're processing headers so save the header + @list($header,$value) = explode(':',$line,2); + $header = trim($header); + $value = trim($value); + if (!empty($header)) { + if ($header == "Description") { + // for servers where the implementors didn't read the next word in the RETS spec. + // 'Description' is the BNF term. Content-Description is the correct header. + // fixing for sanity + $header = "Content-Description"; + } + // fix case issue if exists + if ($header == "Content-type") { + $header = "Content-Type"; + } + $this_photo[$header] = $value; + } + } + if ($on_body == true) { + if ($first_body_found == true) { + // here again because a linebreak in the body section which was cut out in the explode + // add the CRLF back + $this_photo['Data'] .= "\r\n"; + } + // non blank line and we're processing a body so save the line as part of Data + $first_body_found = true; + $this_photo['Data'] .= $line; + } + } + // done with parsing out the multipart response + // check for errors and finish up + + $this_photo['Success'] = true; // assuming for now + + if (preg_match('/xml/', $this_photo['Content-Type'])) { + // this multipart might include a RETS error + $xml = $this->ParseXMLResponse($this_photo['Data']); + + if ($xml['ReplyCode'] == 0 || empty($this_photo['Data'])) { + // success but no body + $this_photo['Success'] = true; + } + else { + // RETS error in this multipart section + $this_photo['Success'] = false; + $this_photo['ReplyCode'] = "{$xml['ReplyCode']}"; + $this_photo['ReplyText'] = "{$xml['ReplyText']}"; + } + } + + // add information about this multipart to the returned array + $return_photos[] = $this_photo; + } + } + else { + // all we know is that the response wasn't a multipart so it's either a single photo or error + $this_photo = array(); + + $this_photo['Success'] = true; // assuming for now + if (isset($this->last_response_headers['Content-ID'])) { + $this_photo['Content-ID'] = $this->last_response_headers['Content-ID']; + } + if (isset($this->last_response_headers['Object-ID'])) { + $this_photo['Object-ID'] = $this->last_response_headers['Object-ID']; + } + if (isset($this->last_response_headers['Content-Type'])) { + $this_photo['Content-Type'] = $this->last_response_headers['Content-Type']; + } + if (isset($this->last_response_headers['MIME-Version'])) { + $this_photo['MIME-Version'] = $this->last_response_headers['MIME-Version']; + } + if (isset($this->last_response_headers['Location'])) { + $this_photo['Location'] = $this->last_response_headers['Location']; + } + if (isset($this->last_response_headers['Preferred'])) { + $this_photo['Preferred'] = $this->last_response_headers['Preferred']; + } + + if (isset($this->last_response_headers['Description'])) { + if (!empty($this->last_response_headers['Description'])) { + // for servers where the implementors didn't read the next word in the RETS spec. + // 'Description' is the BNF term. Content-Description is the correct header. + // fixing for sanity + $this_photo['Content-Description'] = $this->last_response_headers['Description']; + } + } + if (isset($this->last_response_headers['Content-Description'])) { + $this_photo['Content-Description'] = $this->last_response_headers['Content-Description']; + } + + $this_photo['Length'] = strlen($body); + $this_photo['Data'] = $body; + + if (isset($this->last_response_headers['Content-Type'])) { + if (preg_match('/xml/', $this->last_response_headers['Content-Type'])) { + // RETS error maybe? + $xml = $this->ParseXMLResponse($body); + + if ($xml['ReplyCode'] == 0 || empty($body)) { + // false alarm. we're good + $this_photo['Success'] = true; + } + else { + // yes, RETS error + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + $this_photo['ReplyCode'] = "{$xml['ReplyCode']}"; + $this_photo['ReplyText'] = "{$xml['ReplyText']}"; + $this_photo['Success'] = false; + } + } + } + + // add information about this photo to the returned array + $return_photos[] = $this_photo; + } + + // return everything + return $return_photos; + } + + + public function IsMaxrowsReached($pointer_id = "") { + if (empty($pointer_id)) { + $pointer_id = $this->int_result_pointer; + } + return $this->search_data[$pointer_id]['maxrows_reached']; + } + + + public function TotalRecordsFound($pointer_id = "") { + if (empty($pointer_id)) { + $pointer_id = $this->int_result_pointer; + } + return $this->search_data[$pointer_id]['total_records_found']; + } + + + public function NumRows($pointer_id = "") { + if (empty($pointer_id)) { + $pointer_id = $this->int_result_pointer; + } + return $this->search_data[$pointer_id]['last_search_returned']; + } + + + public function SearchGetFields($pointer_id) { + if (!empty($pointer_id)) { + return $this->search_data[$pointer_id]['column_names']; + } + else { + return false; + } + } + + + public function FreeResult($pointer_id) { + if (!empty($pointer_id)) { + unset($this->search_data[$pointer_id]['data']); + unset($this->search_data[$pointer_id]['delimiter_character']); + unset($this->search_data[$pointer_id]['column_names']); + return true; + } + else { + return false; + } + } + + + public function FetchRow($pointer_id) { + + $this_row = false; + + if (!empty($pointer_id)) { + + if (isset($this->search_data[$pointer_id]['data'])) { + $field_data = current($this->search_data[$pointer_id]['data']); + next($this->search_data[$pointer_id]['data']); + } + + if (!empty($field_data)) { + $this_row = array(); + + // split up DATA row on delimiter found earlier + $field_data = preg_replace("/^{$this->search_data[$pointer_id]['delimiter_character']}/", "", $field_data); + $field_data = preg_replace("/{$this->search_data[$pointer_id]['delimiter_character']}\$/", "", $field_data); + $field_data = explode($this->search_data[$pointer_id]['delimiter_character'], $field_data); + + foreach ($this->search_data[$pointer_id]['column_names'] as $key => $name) { + // assign each value to it's name retrieved in the COLUMNS earlier + $this_row[$name] = $field_data[$key]; + } + } + } + + return $this_row; + + } + + + public function SearchQuery($resource, $class, $query = "", $optional_params = array()) { + $this->reset_error_info(); + + if (empty($resource)) { + die("Resource parameter is required in SearchQuery() request."); + } + if (empty($class)) { + die("Class parameter is required in SearchQuery() request."); + } + if (empty($this->capability_url['Search'])) { + die("SearchQuery() called but unable to find Search location. Failed login?\n"); + } + + $this->int_result_pointer++; + $this->search_data[$this->int_result_pointer]['last_search_returned'] = 0; + $this->search_data[$this->int_result_pointer]['total_records_found'] = 0; + $this->search_data[$this->int_result_pointer]['column_names'] = ""; + $this->search_data[$this->int_result_pointer]['delimiter_character'] = ""; + $this->search_data[$this->int_result_pointer]['search_requests'] = 0; + + // setup request arguments + $search_arguments = array(); + + $search_arguments['SearchType'] = $resource; + $search_arguments['Class'] = $class; + + // due to a lack of forward-thinking, reversing a previous decision + // check if the query passed is missing the outer parenthesis + // if so, add them + if (empty($query)) { + // do nothing. http://retsdoc.onconfluence.com/display/rcpcenter/RCP+80+-+Optional+Query + } + elseif ($query == "*" || preg_match('/^\((.*)\)$/', $query)) { + $search_arguments['Query'] = $query; + } + else { + $search_arguments['Query'] = '('.$query.')'; + } + + + if (isset($search_arguments['Query'])) { + $search_arguments['QueryType'] = "DMQL2"; + } + + // setup additional, optional request arguments + $search_arguments['Count'] = empty($optional_params['Count']) ? 1 : $optional_params['Count']; + $search_arguments['Format'] = empty($optional_params['Format']) ? "COMPACT-DECODED" : $optional_params['Format']; + $search_arguments['Limit'] = empty($optional_params['Limit']) ? 99999999 : $optional_params['Limit']; + + if (!empty($optional_params['Offset'])) { + $search_arguments['Offset'] = $optional_params['Offset']; + } + elseif ($this->offset_support && empty($optional_params['Offset'])) { + // start auto-offset looping with Offset at 1 + $search_arguments['Offset'] = 1; + } + else { } + + if (!empty($optional_params['Select'])) { + $search_arguments['Select'] = $optional_params['Select']; + } + if (!empty($optional_params['RestrictedIndicator'])) { + $search_arguments['RestrictedIndicator'] = $optional_params['RestrictedIndicator']; + } + + $search_arguments['StandardNames'] = empty($optional_params['StandardNames']) ? 0 : $optional_params['StandardNames']; + + $continue_searching = true; // Keep searching if MAX ROWS is reached and offset_support is true + while ($continue_searching) { + + $this->search_data[$this->int_result_pointer]['maxrows_reached'] = false; + $this->search_data[$this->int_result_pointer]['search_requests']++; + + if ($this->search_data[$this->int_result_pointer]['search_requests'] == 300 && !$this->override_offset_protection) { + // this call for SearchQuery() has resulted in X number of search requests + // which is considered excessive. stopping the process in order to prevent + // abuse against the server. almost ALWAYS happens when the user thinks Offset + // is supported by the server when it's actually NOT supported + $this->set_error_info("phrets", -1, "Last SearchQuery() has resulted in 300+ requests to the server. Stopping to prevent abuse"); + return false; + } + + // make request + $result = $this->RETSRequest($this->capability_url['Search'], $search_arguments); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $body = $this->fix_encoding($body); + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + if (isset($xml->DELIMITER)) { + // delimiter found so we have at least a COLUMNS row to parse + $delimiter_character = chr("{$xml->DELIMITER->attributes()->value}"); + $this->search_data[$this->int_result_pointer]['delimiter_character'] = $delimiter_character; + $column_names = "{$xml->COLUMNS[0]}"; + $column_names = preg_replace("/^{$delimiter_character}/", "", $column_names); + $column_names = preg_replace("/{$delimiter_character}\$/", "", $column_names); + $this->search_data[$this->int_result_pointer]['column_names'] = explode($delimiter_character, $column_names); + } + + if (isset($xml->DATA)) { + foreach ($xml->DATA as $key) { + $field_data = "{$key}"; + // split up DATA row on delimiter found earlier + $this->search_data[$this->int_result_pointer]['data'][] = $field_data; + $this->search_data[$this->int_result_pointer]['last_search_returned']++; + } + } + + if (isset($xml->MAXROWS)) { + // MAXROWS tag found. the RETS server withheld records. + // if the server supports Offset, more requests can be sent to page through results + // until this tag isn't found anymore. + $this->search_data[$this->int_result_pointer]['maxrows_reached'] = true; + } + + if (isset($xml->COUNT)) { + // found the record count returned. save it + $this->search_data[$this->int_result_pointer]['total_records_found'] = "{$xml->COUNT->attributes()->Records}"; + } + + if (isset($xml)) { + unset($xml); + } + + if ($this->IsMaxrowsReached($this->int_result_pointer) && $this->offset_support) { + $continue_searching = true; + $search_arguments['Offset'] = $this->NumRows($this->int_result_pointer) + 1; + } + else { + $continue_searching = false; + } + } + + return $this->int_result_pointer; + } + + + public function Search($resource, $class, $query = "", $optional_params = array()) { + $data_table = array(); + + $int_result_pointer = $this->SearchQuery($resource, $class, $query, $optional_params); + + while ($row = $this->FetchRow($int_result_pointer)) { + $data_table[] = $row; + } + + return $data_table; + } + + + public function GetAllLookupValues($resource) { + $this->reset_error_info(); + + if (empty($resource)) { + die("Resource parameter is required in GetAllLookupValues() request."); + } + if (empty($this->capability_url['GetMetadata'])) { + die("GetAllLookupValues() called but unable to find GetMetadata location. Failed login?\n"); + } + + // make request + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-LOOKUP_TYPE', 'ID' => $resource.':*', 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $this_table = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + + foreach ($xml->METADATA->{'METADATA-LOOKUP_TYPE'} as $key) { + if (!empty($key->attributes()->Lookup)) { + $this_lookup = array(); + + $lookup_xml_array = array(); + if ($this->server_version == "RETS/1.7.2") { + $lookup_xml_array = $key->LookupType; + } + else { + $lookup_xml_array = $key->Lookup; + } + + foreach ($lookup_xml_array as $look) { + $metadataentryid = isset($look->MetadataEntryID) ? "{$look->MetadataEntryID}" : ""; + $value = isset($look->Value) ? "{$look->Value}" : ""; + $shortvalue = isset($look->ShortValue) ? "{$look->ShortValue}" : ""; + $longvalue = isset($look->LongValue) ? "{$look->LongValue}" : ""; + + $this_lookup[] = array('MetadataEntryID' => $metadataentryid, 'Value' => $value, 'ShortValue' => $shortvalue, + 'LongValue' => $longvalue); + } + $this_table[] = array('Lookup' => "{$key->attributes()->Lookup}", 'Values' => $this_lookup); + } + } + } + + // return the big array + return $this_table; + } + + + public function GetLookupValues($resource, $lookupname) { + $this->reset_error_info(); + + if (empty($resource)) { + die("Resource parameter is required in GetLookupValues() request."); + } + if (empty($lookupname)) { + die("Lookup Name parameter is required in GetLookupValues() request."); + } + if (empty($this->capability_url['GetMetadata'])) { + die("GetLookupValues() called but unable to find GetMetadata location. Failed login?\n"); + } + + // make request + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-LOOKUP_TYPE', 'ID' => $resource.':'.$lookupname, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $this_table = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + + $lookup_xml_array = array(); + if ($this->server_version == "RETS/1.7.2") { + $lookup_xml_array = $xml->METADATA->{'METADATA-LOOKUP_TYPE'}->LookupType; + } + else { + $lookup_xml_array = $xml->METADATA->{'METADATA-LOOKUP_TYPE'}->Lookup; + } + + foreach ($lookup_xml_array as $key) { + if (isset($key->Value)) { + + $metadataentryid = isset($key->MetadataEntryID) ? "{$key->MetadataEntryID}" : ""; + $value = isset($key->Value) ? "{$key->Value}" : ""; + $shortvalue = isset($key->ShortValue) ? "{$key->ShortValue}" : ""; + $longvalue = isset($key->LongValue) ? "{$key->LongValue}" : ""; + + $this_table[] = array('MetadataEntryID' => $metadataentryid, 'Value' => $value, 'ShortValue' => $shortvalue, + 'LongValue' => $longvalue); + } + } + } + + // return the big array + return $this_table; + } + + + public function GetMetadataResources($id = 0) { + $this->reset_error_info(); + + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataResources() called but unable to find GetMetadata location. Failed login?\n"); + } + + // make request + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-RESOURCE', 'ID' => $id, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $this_resource = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + foreach ($xml->METADATA->{'METADATA-RESOURCE'}->Resource as $key => $value) { + $this_resource["{$value->ResourceID}"] = array('ResourceID' => "{$value->ResourceID}", 'StandardName'=>"{$value->StandardName}", 'VisibleName' => "{$value->VisibleName}", + 'Description' => "{$value->Description}", 'KeyField' => "{$value->KeyField}", 'ClassCount' => "{$value->ClassCount}", + 'ClassVersion' => "{$value->ClassVersion}", 'ClassDate' => "{$value->ClassDate}", 'ObjectVersion' => "{$value->ObjectVersion}", + 'ObjectDate' => "{$value->ObjectDate}", 'SearchHelpVersion' => "{$value->SearchHelpVersion}", + 'SearchHelpDate' => "{$value->SearchHelpDate}", 'EditMaskVersion' => "{$value->EditMaskVersion}", + 'EditMaskDate' => "{$value->EditMaskDate}", 'LookupVersion' => "{$value->LookupVersion}", 'LookupDate' => "{$value->LookupDate}", + 'UpdateHelpVersion' => "{$value->UpdateHelpVersion}", 'UpdateHelpDate' => "{$value->UpdateHelpDate}", + 'ValidationExpressionVersion' => "{$value->ValidationExpressionVersion}", 'ValidationExpressionDate' => "{$value->ValidationExpressionDate}", + 'ValidationLookupVersion' => "{$value->ValidationLookupVersion}", 'ValidationLookupDate' => "{$value->ValidationLookupDate}", + 'ValidationExternalVersion' => "{$value->ValidationExternalVersion}", 'ValidationExternalDate' => "{$value->ValidationExternalDate}"); + } + } + + // send back array + return $this_resource; + } + + + public function GetMetadataInfo($id = 0) { + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataInfo() called but unable to find GetMetadata location. Failed login?\n"); + } + return $this->GetMetadataResources($id); + } + + + public function GetMetadataTable($resource, $class) { + $this->reset_error_info(); + + $id = $resource.':'.$class; + if (empty($resource)) { + die("Resource parameter is required in GetMetadata() request."); + } + if (empty($class)) { + die("Class parameter is required in GetMetadata() request."); + } + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataTable() called but unable to find GetMetadata location. Failed login?\n"); + } + + // request specific metadata + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-TABLE', 'ID' => $id, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $this_table = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + foreach ($xml->METADATA->{'METADATA-TABLE'}->Field as $key) { + $this_table[] = array('SystemName' => "{$key->SystemName}", 'StandardName' => "{$key->StandardName}", 'LongName' => "{$key->LongName}", 'DBName' => "{$key->DBName}", + 'ShortName' => "{$key->ShortName}", 'MaximumLength' => "{$key->MaximumLength}", 'DataType' => "{$key->DataType}", 'Precision' => "{$key->Precision}", + 'Searchable' => "{$key->Searchable}", 'Interpretation' => "{$key->Interpretation}", 'Alignment' => "{$key->Alignment}", + 'UseSeparator' => "{$key->UseSeparator}", 'EditMaskID' => "{$key->EditMaskID}", 'LookupName' => "{$key->LookupName}", + 'MaxSelect' => "{$key->MaxSelect}", 'Units' => "{$key->Units}", 'Index' => "{$key->Index}", 'Minimum' => "{$key->Minimum}", + 'Maximum' => "{$key->Maximum}", 'Default' => "{$key->Default}", 'Required' => "{$key->Required}", 'SearchHelpID' => "{$key->SearchHelpID}", + 'Unique' => "{$key->Unique}", 'MetadataEntryID' => "{$key->MetadataEntryID}", 'ModTimeStamp' => "{$key->ModTimeStamp}", + 'ForeignKeyName' => "{$key->ForiengKeyName}", 'ForeignField' => "{$key->ForeignField}", 'InKeyIndex' => "{$key->InKeyIndex}"); + } + } + + // return the big array + return $this_table; + } + + + public function GetMetadata($resource, $class) { + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadata() called but unable to find GetMetadata location. Failed login?\n"); + } + return $this->GetMetadataTable($resource, $class); + } + + + public function GetMetadataObjects($id) { + $this->reset_error_info(); + + if (empty($id)) { + die("ID parameter is required in GetMetadataObjects() request."); + } + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataObjects() called but unable to find GetMetadata location. Failed login?\n"); + } + + // request basic metadata information + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-OBJECT', 'ID' => $id, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $return_data = array(); + + if (isset($xml->METADATA->{'METADATA-OBJECT'})) { + // parse XML into a nice array + foreach ($xml->METADATA->{'METADATA-OBJECT'} as $key => $value) { + foreach ($value->Object as $key) { + if (!empty($key->ObjectType)) { + $return_data[] = array('MetadataEntryID' => "{$key->MetadataEntryID}", 'VisibleName' => "{$key->VisibleName}", 'ObjectTimeStamp' => "{$key->ObjectTimeStamp}", + 'ObjectCount' => "{$key->ObjectCount}", 'ObjectType' => "{$key->ObjectType}", 'StandardName' => "{$key->StandardName}", + 'MimeType' => "{$key->MimeType}", 'Description' => "{$key->Description}"); + } + } + } + } + + // send back array + return $return_data; + } + + + public function GetMetadataClasses($id) { + $this->reset_error_info(); + + if (empty($id)) { + die("ID parameter is required in GetMetadataClasses() request."); + } + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataClasses() called but unable to find GetMetadata location. Failed login?\n"); + } + + // request basic metadata information + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-CLASS', 'ID' => $id, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $return_data = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + foreach ($xml->METADATA->{'METADATA-CLASS'} as $key => $value) { + foreach ($value->Class as $key) { + if (!empty($key->ClassName)) { + $return_data[] = array('ClassName' => "{$key->ClassName}", 'VisibleName' => "{$key->VisibleName}", 'StandardName' => "{$key->StandardName}", + 'Description' => "{$key->Description}", 'TableVersion' => "{$key->TableVersion}", 'TableDate' => "{$key->TableDate}", + 'UpdateVersion' => "{$key->UpdateVersion}", 'UpdateDate' => "{$key->UpdateDate}", 'ClassTimeStamp' => "{$key->ClassTimeStamp}", + 'DeletedFlagField' => "{$key->DeletedFlagField}", 'DeletedFlagValue' => "{$key->DeletedFlagValue}", + 'HasKeyIndex' => "{$key->HasKeyIndex}" ); + } + } + } + } + + // send back array + return $return_data; + } + + + public function GetMetadataTypes($id = 0) { + $this->reset_error_info(); + + if (empty($this->capability_url['GetMetadata'])) { + die("GetMetadataTypes() called but unable to find GetMetadata location. Failed login?\n"); + } + + // request basic metadata information + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-CLASS', 'ID' => $id, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + $return_data = array(); + + // parse XML into a nice array + if ($xml->METADATA) { + foreach ($xml->METADATA->{'METADATA-CLASS'} as $key => $value) { + $resource = $value['Resource']; + $this_resource = array(); + foreach ($value->Class as $key) { + if (!empty($key->ClassName)) { + $this_resource[] = array('ClassName' => "{$key->ClassName}", 'VisibleName' => "{$key->VisibleName}", 'StandardName' => "{$key->StandardName}", + 'Description' => "{$key->Description}", 'TableVersion' => "{$key->TableVersion}", 'TableDate' => "{$key->TableDate}", + 'UpdateVersion' => "{$key->UpdateVersion}", 'UpdateDate' => "{$key->UpdateDate}"); + } + } + + // prepare 2-deep array + $return_data[] = array('Resource' => "{$resource}", 'Data' => $this_resource); + } + } + + // send back array + return $return_data; + } + + + public function GetServerSoftware() { + return $this->server_software; + } + + + public function GetServerVersion() { + return $this->server_version; + } + + + public function CheckAuthSupport($type = "") { + if ($type == "basic") { + return $this->auth_support_basic; + } + if ($type == "digest") { + return $this->auth_support_digest; + } + $this->set_error_info("phrets", -1, "Unknown auth type requested."); + return false; + } + + + public function GetAllTransactions() { + // read through capability_urls read during the Login and return + $transactions = array(); + if (is_array($this->capability_url)) { + foreach ($this->capability_url as $key => $value) { + $transactions[] = $key; + } + } + return $transactions; + } + + + public function LastRequestURL() { + return $this->last_request_url; + } + + + public function GetLoginURL() { + // see if the saved Login URL has a hostname included. + // if not, make it based on the URL given in the Connect() call + $parse_results = parse_url($this->capability_url['Login'], PHP_URL_HOST); + if (empty($parse_results)) { + // login transaction gave a relative path for this action + $request_url = $this->server_protocol.'://'.$this->server_hostname.':'.$this->server_port.''.$this->capability_url['Login']; + } + else { + // login transaction gave an absolute path for this action + $request_url = $this->capability_url['Login']; + } + if (empty($request_url)) { + $this->set_error_info("phrets", -1, "Unable to find a login URL. Did initial login fail?"); + return false; + } + return $request_url; + } + + + public function GetServerInformation() { + $this->reset_error_info(); + + if (empty($this->capability_url['GetMetadata'])) { + die("GetServerInformation() called but unable to find GetMetadata location. Failed login?\n"); + } + + // request server information + $result = $this->RETSRequest($this->capability_url['GetMetadata'], array('Type' => 'METADATA-SYSTEM', 'ID' => 0, 'Format' => 'STANDARD-XML')); + if (!$result) { + return false; + } + list($headers, $body) = $result; + + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + if ($xml['ReplyCode'] != 0) { + $this->set_error_info("rets", "{$xml['ReplyCode']}", "{$xml['ReplyText']}"); + return false; + } + + + if ($this->is_server_version("1_5_or_below")) { + $system_id = isset($xml->METADATA->{'METADATA-SYSTEM'}->System->SystemID) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->System->SystemID}" : ""; + $system_description = isset($xml->METADATA->{'METADATA-SYSTEM'}->System->SystemDescription) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->System->SystemDescription}" : ""; + $timezone_offset = ""; + } + else { + $system_id = isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemID) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemID}" : ""; + $system_description = isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemDescription) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->SystemDescription}" : ""; + $timezone_offset = isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->TimeZoneOffset) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->attributes()->TimeZoneOffset}" : ""; + } + + $system_comments = isset($xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->Comments) ? "{$xml->METADATA->{'METADATA-SYSTEM'}->SYSTEM->Comments}" : ""; + + return array('SystemID' => $system_id, 'SystemDescription' => $system_description, 'TimeZoneOffset' => $timezone_offset, 'Comments' => $system_comments); + } + + + public function Disconnect() { + $this->reset_error_info(); + + if (empty($this->capability_url['Logout'])) { + die("Disconnect() called but unable to find Logout location. Failed login?\n"); + } + + // make request + $result = $this->RETSRequest($this->capability_url['Logout']); + if (!$result) { + return false; + } + list($headers,$body) = $result; + + // close cURL connection + curl_close($this->ch); + + if ($this->debug_mode == true) { + // close cURL debug log file handler + fclose($this->debug_log); + } + + if (file_exists($this->cookie_file)) { + @unlink($this->cookie_file); + } + + return true; + + } + + + public function Connect($login_url, $username, $password, $ua_pwd = "") { + $this->reset_error_info(); + + if (empty($login_url)) { + die("PHRETS: Login URL missing from Connect()"); + } + if (empty($username)) { + die("PHRETS: Username missing from Connect()"); + } + if (empty($password)) { + die("PHRETS: Password missing from Connect()"); + } + if (empty($this->static_headers['RETS-Version'])) { + $this->AddHeader("RETS-Version", "RETS/1.5"); + } + if (empty($this->static_headers['User-Agent'])) { + $this->AddHeader("User-Agent", "PHRETS/1.0"); + } + if (empty($this->static_headers['Accept']) && $this->static_headers['RETS-Version'] == "RETS/1.5") { + $this->AddHeader("Accept", "*/*"); + } + + // chop up Login URL to use for later requests + $url_parts = parse_url($login_url); + $this->server_hostname = $url_parts['host']; + $this->server_port = (empty($url_parts['port'])) ? 80 : $url_parts['port']; + $this->server_protocol = $url_parts['scheme']; + + $this->capability_url['Login'] = $url_parts['path']; + + if (isset($url_parts['query']) && !empty($url_parts['query'])) { + $this->capability_url['Login'] .= "?{$url_parts['query']}"; + } + + $this->username = $username; + $this->password = $password; + + if (!empty($ua_pwd)) { + // force use of RETS 1.7 User-Agent Authentication + $this->ua_auth = true; + $this->ua_pwd = $ua_pwd; + } + + if (empty($this->cookie_file)) { + $this->cookie_file = tempnam("", "phrets"); + } + + @touch($this->cookie_file); + + if (!is_writable($this->cookie_file)) { + $this->set_error_info("phrets", -1, "Cookie file \"{$this->cookie_file}\" cannot be written to. Must be an absolute path and must be writable"); + return false; + } + + // start cURL magic + $this->ch = curl_init(); + curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, array(&$this, 'read_custom_curl_headers')); + if ($this->debug_mode == true) { + // open file handler to be used by cURL debug log + $this->debug_log = fopen($this->debug_file, 'a'); + + curl_setopt($this->ch, CURLOPT_VERBOSE, 1); + curl_setopt($this->ch, CURLOPT_STDERR, $this->debug_log); + } + curl_setopt($this->ch, CURLOPT_HEADER, false); + if ($this->force_basic_authentication == true) { + curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + } + else { + curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC); + } + if ($this->disable_follow_location != true) { + curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1); + } + curl_setopt($this->ch, CURLOPT_USERPWD, $this->username.":".$this->password); + curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->ch, CURLOPT_COOKIEFILE, $this->cookie_file); + + // make request to Login transaction + $result = $this->RETSRequest($this->capability_url['Login']); + if (!$result) { + return false; + } + + list($headers,$body) = $result; + + // parse body response + $xml = $this->ParseXMLResponse($body); + if (!$xml) { + return false; + } + + // log replycode and replytext for reference later + $this->last_request['ReplyCode'] = "{$xml['ReplyCode']}"; + $this->last_request['ReplyText'] = "{$xml['ReplyText']}"; + + // chop up login response + // if multiple parts of the login response aren't found splitting on \r\n, redo using just \n + $login_response = array(); + + if ($this->server_version == "RETS/1.0") { + if (isset($xml)) { + $login_response = explode("\r\n", $xml); + if (empty($login_response[3])) { + $login_response = explode("\n", $xml); + } + } + } + elseif (isset($xml->{'RETS-RESPONSE'})) { + $login_response = explode("\r\n", $xml->{'RETS-RESPONSE'}); + if (empty($login_response[3])) { + $login_response = explode("\n", $xml->{'RETS-RESPONSE'}); + } + } elseif (!empty($xml[0])) { + $login_response = preg_split('/\R/', $xml[0]); + } + + // parse login response. grab all capability URLs known and ones that begin with X- + // otherwise, it's a piece of server information to save for reference + foreach ($login_response as $line) { + @list($name,$value) = @explode("=", $line, 2); + $name = trim($name); + $value = trim($value); + if (!empty($name) && !empty($value)) { + if (isset($this->allowed_capabilities[$name]) || preg_match('/^X\-/', $name) == true) { + $this->capability_url[$name] = $value; + } + else { + $this->server_information[$name] = $value; + } + } + } + + // if 'Action' capability URL is provided, we MUST request it following the successful Login + if (isset($this->capability_url['Action']) && !empty($this->capability_url['Action'])) { + $result = $this->RETSRequest($this->capability_url['Action']); + if (!$result) { + return false; + } + list($headers,$body) = $result; + } + + if ($this->compression_enabled == true) { + curl_setopt($this->ch, CURLOPT_ENCODING, "gzip"); + } + + if ($this->last_request['ReplyCode'] == 0) { + return true; + } + else { + $this->set_error_info("rets", $this->last_request['ReplyCode'], $this->last_request['ReplyText']); + return false; + } + + } + + + public function LastRequest() { + // return replycode and replytext from last request + return $this->last_request; + } + + + public function AddHeader($name, $value) { + // add static header for cURL requests + $this->static_headers[$name] = $value; + return true; + } + + + public function DeleteHeader($name) { + // delete static header from cURL requests + unset($this->static_headers[$name]); + return true; + } + + + public function ParseXMLResponse($data = "") { + $this->reset_error_info(); + + if (!empty($data)) { + // parse XML function. ability to replace SimpleXML with something later fairly easily + $xml = @simplexml_load_string($data); + if (!is_object($xml)) { + $this->set_error_info("xml", -1, "XML parsing error: {$data}"); + $this->err = "XML Parsing error: {$data}"; + return false; + } + return $xml; + } + else { + $this->set_error_info("xml", -1, "XML parsing error. No data to parse"); + return false; + } + } + + + public function RETSRequest($action, $parameters = "") { + $this->reset_error_info(); + + $this->err = ""; + $this->last_response_headers = array(); + $this->last_response_headers_raw = ""; + // exposed raw RETS request function. used internally and externally + + if (empty($action)) { + die ("RETSRequest called but Action passed has no value. Failed login?\n"); + } + + $parse_results = parse_url($action, PHP_URL_HOST); + if (empty($parse_results)) { + // login transaction gave a relative path for this action + $request_url = $this->server_protocol.'://'.$this->server_hostname.':'.$this->server_port.''.$action; + } + else { + // login transaction gave an absolute path for this action + $request_url = $action; + } + + // build query string from arguments + $request_arguments = ""; + if (is_array($parameters)) { + $request_arguments .= "?"; + foreach ($parameters as $key => $value) { + $request_arguments .= "{$key}=".urlencode($value)."&"; + } + $request_arguments = preg_replace('/\&$/', '', $request_arguments); + } + + // build entire URL + $request_url = $request_url.$request_arguments; + + // build headers to pass in cURL + $request_headers = ""; + if (is_array($this->static_headers)) { + foreach ($this->static_headers as $key => $value) { + $request_headers .= "{$key}: {$value}\r\n"; + } + } + + if ($this->ua_auth == true) { + $session_id_to_calculate_with = ""; + + // calculate RETS-UA-Authorization header + $ua_a1 = md5($this->static_headers['User-Agent'] .':'. $this->ua_pwd); + $session_id_to_calculate_with = ($this->use_interealty_ua_auth == true) ? "" : $this->session_id; + $ua_dig_resp = md5(trim($ua_a1) .':'. trim($this->request_id) .':'. trim($session_id_to_calculate_with) .':'. trim($this->static_headers['RETS-Version'])); + $request_headers .= "RETS-UA-Authorization: Digest {$ua_dig_resp}\r\n"; + } + + $this->last_request_url = $request_url; + curl_setopt($this->ch, CURLOPT_URL, $request_url); + + curl_setopt($this->ch, CURLOPT_HTTPHEADER, array($request_headers)); + // do it + $response_body = curl_exec($this->ch); + $response_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); + + if ($this->debug_mode == true) { + fwrite($this->debug_log, $response_body ."\n"); + } + + if ($this->catch_last_response == true) { + $this->last_server_response = $this->last_response_headers_raw . $response_body; + } + + if (isset($this->last_response_headers['WWW-Authenticate'])) { + if (preg_match('/Basic/', $this->last_response_headers['WWW-Authenticate'])) { + $this->auth_support_basic = true; + } + if (preg_match('/Digest/', $this->last_response_headers['WWW-Authenticate'])) { + $this->auth_support_digest = true; + } + } + + if (isset($this->last_response_headers['RETS-Version'])) { + $this->server_version = $this->last_response_headers['RETS-Version']; + } + + if (isset($this->last_response_headers['Server'])) { + $this->server_software = $this->last_response_headers['Server']; + } + + if (isset($this->last_response_headers['Set-Cookie'])) { + if (preg_match('/RETS-Session-ID\=(.*?)(\;|\s+|$)/', $this->last_response_headers['Set-Cookie'], $matches)) { + $this->session_id = $matches[1]; + } + } + + if ($response_code != 200) { + $this->set_error_info("http", $response_code, $response_body); + return false; + } + + // return raw headers and body + return array($this->last_response_headers_raw, $response_body); + } + + + private function read_custom_curl_headers($handle, $call_string) { + static $last_remembered_header; + $this->last_response_headers_raw .= $call_string; + $header = ""; + $value = ""; + @list($header,$value) = explode(': ',$call_string,2); + $header = trim($header); + $value = trim($value); + if (!empty($header)) { + // new header + $this->last_response_headers[$header] = $value; + $last_remembered_header = $header; + } + else { + // continuation of last header. append to previous + $this->last_response_headers[$last_remembered_header] .= $call_string; + } + + return strlen($call_string); + } + + + public function Error() { + if (isset($this->error_info['type']) && !empty($this->error_info['type'])) { + return $this->error_info; + } + else { + return false; + } + } + + + private function set_error_info($type, $code, $text) { + $this->error_info['type'] = $type; + $this->error_info['code'] = $code; + $this->error_info['text'] = $text; + return true; + } + + + private function reset_error_info() { + $this->error_info['type'] = ""; + $this->error_info['code'] = ""; + $this->error_info['text'] = ""; + return true; + } + + + private function is_server_version($check_version) { + if ($check_version == "1_5_or_below") { + if ($this->GetServerVersion() == "RETS/1.5" || $this->GetServerVersion() == "RETS/1.0") { + return true; + } + else { + return false; + } + } + if ($check_version == "1_7_or_higher") { + if ($this->GetServerVersion() == "RETS/1.7" || $this->GetServerVersion() == "RETS/1.7.1" || $this->GetServerVersion() == "RETS/1.7.2") { + return true; + } + else { + return false; + } + } + return false; + } + + + private function fix_encoding($in_str) { + if ($this->disable_encoding_fix == true) { + return $in_str; + } + + $in_str = preg_replace('/\&\s/', '& ', $in_str); + $cur_encoding = mb_detect_encoding($in_str); + if ($cur_encoding == "UTF-8" && mb_check_encoding($in_str, "UTF-8")) { + return $in_str; + } + else { + return utf8_encode($in_str); + } + } + + + public function ServerDetail($detail) { + if (isset($this->server_information[$detail])) { + return $this->server_information[$detail]; + } + else { + return ""; + } + } + + + public function SetParam($name, $value) { + switch ($name) { + case "cookie_file": + $this->cookie_file = $value; + break; + case "debug_file": + $this->debug_file = $value; + break; + case "debug_mode": + $this->debug_mode = $value; + break; + case "compression_enabled": + $this->compression_enabled = $value; + break; + case "force_ua_authentication": + $this->ua_auth = $value; + break; + case "disable_follow_location": + $this->disable_follow_location = $value; + break; + case "force_basic_authentication": + $this->force_basic_authentication = $value; + break; + case "use_interealty_ua_auth": + $this->use_interealty_ua_auth = $value; + break; + case "catch_last_response": + $this->catch_last_response = $value; + break; + case "disable_encoding_fix": + $this->disable_encoding_fix = $value; + break; + case "offset_support": + $this->offset_support = $value; + break; + case "override_offset_protection": + $this->override_offset_protection = $value; + break; + default: + return false; + } + + return true; + } + + +} + + + + +?> \ No newline at end of file diff --git a/vendors/shells/rets.php b/vendors/shells/rets.php new file mode 100644 index 0000000..64155a9 --- /dev/null +++ b/vendors/shells/rets.php @@ -0,0 +1,19 @@ +RETS = new RETS(); + try { + $this->RETS->connect(); + } catch (Exception $e) { + $this->out(sprintf('Unable to connect to RETS server: %s', $e->getMessage())); + } + + $this->out('Connected to RETS.'); + + } +} +?> \ No newline at end of file