* @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";
}
?>