* @copyright Copyright (C) 2006, Stefan Kuhlins, Gimahhot * @version 1.0 * @package sk_gimahhot */ error_reporting(E_ALL); // assertions on for debugging assert_options(ASSERT_ACTIVE, 1); assert_options(ASSERT_BAIL, 1); // assertions off for production #assert_options(ASSERT_ACTIVE, 0); // In case of errors we want $php_errormsg to be set by PHP. ini_set('track_errors', true); /** * The charset must match the charset of all program files. * Because heredoc does not work with constants we declare a variable. * @global string $charset */ $charset = 'ISO-8859-1'; #$charset = 'UTF-8'; /** * Name your program for the HTTP user agent field. * @global string $userAgent */ $userAgent = 'Gimahhot/1.0 (http://www.gimahhot.de/)'; /** * Timeout in seconds for network communications. */ define('TIMEOUT', 30); ############################################################### # There should be no need to change anything below this line. # ############################################################### /** * Exception for input/output errors * @package sk_gimahhot */ class IOException extends Exception {} /** * Exception wrapper for http server errors, e.g. HTTP code not OK * @package sk_gimahhot */ class ServerException extends Exception {} /** * Exception for internal errors that have to be corrected by programmers. * @package sk_gimahhot */ class InternalException extends Exception {} /** * Exception for XML parse errors * @package sk_gimahhot */ class XmlParserException extends Exception { /** * @var array array of LibXMLError objects */ private $libxml_errors; /** * @param string $message error message * @param array $libxml_errors array of LibXMLError objects from xml parser */ public function XmlParserException($message, $libxml_errors) { $this->libxml_errors = $libxml_errors; parent::__construct($message.' '.construct_xml_error_message($this->libxml_errors), 0); } public function __toString() { return __CLASS__ . ': ' . construct_xml_error_message($this->libxml_errors); } /** * @return array of LibXMLError objects */ public function getXmlErrors() { return $this->libxml_errors; } } /** * Manages an order transaction and holds the necessary data. * * Usage: * * $order_transaction = new OrderTransaction($doc, $url); * try { * $order_transaction->post(); * $OrderStatus = $order_transaction->getOrderStatus(); * // ... work with $OrderStatus * } catch (Exception $e) { * // ... error handling * } * * @package sk_gimahhot */ class OrderTransaction { /** * @var int id of this object in the database */ private $id; /** * @var string xml document to post */ private $doc; /** * @var string url to post the document to */ private $url; /** * @var object SimpleXMLElement that holds the order status xml document sent by the shop */ private $orderStatus; /** * @var array array of LibXMLError objects from xml parser */ private $xml_errors; /** * @var int start of body in the http {@link answer} */ private $startOfBody; /** * @var string complete http answer by the shop server */ private $answer; /** * @var int http status code of the answer by the shop server */ private $http_status_code; /** * @var string http status message of the answer by the shop server */ private $http_status_message; /** * @param string $doc xml document to post * @param string $url url to post the document to */ public function OrderTransaction($doc, $url) { $this->doc = $doc; $this->url = $url; } /** * @param int $id id of this object in the database */ public function setId($id) { $this->id = $id; } /** * @return int id of this object in the database */ public function getId() { return $this->id; } /** * Parses body as xml document. * @return object SimpleXMLElement SimpleXMLElement object that holds the order status xml document sent by the shop * @throws XmlParserException */ public function getOrderStatus() { if (!isset($this->OrderStatus)) { // enable our own error handling libxml_use_internal_errors(true); $this->OrderStatus = simplexml_load_string($this->getBody()); if (!$this->OrderStatus) { $this->xml_errors = libxml_get_errors(); $ex = new XmlParserException('Errors in XML document.', $this->xml_errors); libxml_clear_errors(); throw $ex; } } return $this->OrderStatus; } /** * @return string complete http answer by the shop server */ public function getAnswer() { return $this->answer; } /** * @return string http headers sent by the shop server */ public function getHeader() { assert('isset($this->answer) && isset($this->startOfBody)'); if ($this->startOfBody > 0) { return substr($this->answer, 0, $this->startOfBody); } return $this->answer; } /** * @return string http headers sent by the shop server */ public function getBody() { assert('isset($this->answer) && isset($this->startOfBody)'); if ($this->startOfBody > 0) { return substr($this->answer, $this->startOfBody); } return ''; } /** * @return int http status code of the answer by the shop server */ public function getHttpStatusCode() { return $this->http_status_code; } /** * @return string http status message of the answer by the shop server */ public function getHttpStatusMessage() { return $this->http_status_message; } /** * @return array array of LibXMLError objects from xml parser */ public function getXmlErrors() { return $this->xml_errors; } /** * Post the given document to the URL, get the answer and prepare a SimpleXML object for it. * Throws exceptions in case of errors. * @throws ServerException * @throws IOException * @throws InternalException */ public function post() { $handle = sk_send_document($this->doc, $this->url); $this->answer = sk_get_http_answer($handle); // ignore non-critical errors here @fclose($handle); if ($this->answer == '') throw new ServerException('Server sent nothing'); $this->startOfBody = sk_find_start_of_http_body($this->answer); $parts = sk_parse_http_status($this->getHeader()); #$http_status_line = $parts[0]; $this->http_status_code = $parts[1]; $this->http_status_message = $parts[2]; if ($this->http_status_code != 200) throw new ServerException($this->http_status_message, $this->http_status_code); if ($this->answer == '') throw new ServerException('Shop server sent no document'); } } ############################################# /** * Sends document to url. * @global string charset for the document * @param string $doc document to send * @param string $url url to send document to * @return resource handle for connection to url * @throws IOException * @access private */ function sk_send_document($doc, $url) { // reduce the URL to its component parts for fsockopen() and HTTP POST $url_parts = parse_url($url); $host = $url_parts['host']; $path = $url_parts['path']; if (isset($url_parts['user']) && isset($url_parts['pass'])) $cred = base64_encode($url_parts['user'].':'.$url_parts['pass']); else $cred = ''; // https or http (case-sensitive; if required expand) if ($url_parts['scheme'] == 'https') $prot = 'ssl://'; else $prot = 'tcp://'; // for http if (isset($url_parts['port'])) $port = $url_parts['port']; else if ($url_parts['scheme'] == 'https') $port = 443; // default port for https else $port = 80; // default port for http // post xml document to web server $sock = fsockopen($prot.$host, $port, $errno, $errstr, TIMEOUT); if ($sock === FALSE) throw new IOException($errstr, $errno); global $charset; $len = strlen($doc); $request = << 0) $p += 4; else { $p = strpos($answer, "\n\n"); if ($p > 0) $p += 2; else throw new InternalException('HTTP delimiter between header and body not found!'); } return $p; } /** * Reads and extracts the http body from stream. * @param string $header http header * @return array array of strings with 0=status line, 1=code and 2=message * @throws InternalException * @access private */ function sk_parse_http_status($header) { if (!preg_match('#^\s*HTTP/\d\.\d\s+(\d+)\s+(.+)#i', $header, $matches)) { throw new InternalException('Could not find HTTP status line!'); } return $matches; } /** * Construct an error message based on the xml error objecs. * @param array $libxml_errors array of LibXMLError objects * @return string error message * @access private */ function construct_xml_error_message($libxml_errors) { $s = ''; $n = count($libxml_errors); if ($n > 1) $s = 'Detected '.count($libxml_errors).' errors.'; else if ($n == 1) $s = 'Detected 1 error.'; foreach ($libxml_errors as $error) { $s .= "\n\t"; switch ($error->level) { case LIBXML_ERR_WARNING: $s .= "Warning"; break; case LIBXML_ERR_ERROR: $s .= "Error"; break; case LIBXML_ERR_FATAL: $s .= "Fatal Error"; break; } $s .= ' '.$error->code.': '; $s .= trim($error->message) . ', Line ' . $error->line . ', Column ' . $error->column; } return $s; } /** * Constructs an error message for the given error object and xml document. * Useful for displaying errors during testing and debugging. * @param object LibXMLError $error LibXMLError error object * @param string $xml_doc xml document * @return string error message * @access private */ function construct_xml_error_message_extended($error, $xml_doc) { $xml = explode("\n", $xml_doc); $return = $xml[$error->line - 1] . "\n"; $return .= str_repeat('-', $error->column) . "^\n"; switch ($error->level) { case LIBXML_ERR_WARNING: $return .= "Warning $error->code: "; break; case LIBXML_ERR_ERROR: $return .= "Error $error->code: "; break; case LIBXML_ERR_FATAL: $return .= "Fatal Error $error->code: "; break; } $return .= trim($error->message) . "\n Line: $error->line" . "\n Column: $error->column"; if ($error->file) $return .= "\n File: $error->file"; return "$return\n\n--------------------------------------------\n\n"; } ?>