-
Hoop, Bert Jan de authoredHoop, Bert Jan de authored
Webquery.php 17.52 KiB
<?php
namespace Library;
use GuzzleHttp\Psr7\Request;
class Webquery {
use \Library\LoggerTrait;
/**
* @var array log context
*/
protected $context = ['webquery'];
/**
* @var \GuzzleHttp\Client psr-7 http client
*/
protected $http_client;
/**
* @var string webquery host name, default: 'library.wur.nl'
*/
protected $host = 'library.wur.nl';
/**
* @var string webquery service name
*/
protected $service = '';
/**
* @var string webquery suffix name, default: 'xml'
*/
protected $suffix = 'xml';
/**
* @var string wq_ofs query parameter
*/
protected $wq_ofs;
/**
* @var string wq_max query parameter
*/
protected $wq_max;
/**
* @var query string
*/
protected $query;
/**
* @var string record element name
* if not set then the service name is used
*/
protected $record_name = '';
/**
* @var GuzzleHttp\Psr7\Response
*/
protected $response;
/**
* @var SimpleXMLElement parsed xml from response
*/
protected $resp_xml;
/**
* @var array with children and attributes from error element
*/
protected $resp_error;
public function __construct() {
}
/**
* setter for http_client
* @param \GuzzleHttp\Client $http_client
* @return $this
*/
public function set_http_client(\GuzzleHttp\Client $http_client) {
$this->http_client = $http_client;
return $this;
}
/**
* getter for http_client
* if no http_client is set a default http_client is created
* @return \GuzzleHttp\Client
*/
public function get_http_client() {
if (!isset($this->http_client)) {
$this->http_client = new \GuzzleHttp\Client();
}
return $this->http_client;
}
/**
* set host name for webquery url
* @param sting $host webquery host
* @return $this
*/
public function set_host($host) {
$this->host = $host;
return $this;
}
/**
* set webquery service name for url
* @param string $service webquery service
* @return $this
*/
public function set_service($service) {
$this->service = $service;
return $this;
}
/**
* set webquery suffix for url
* @param string $suffix webquery suffix
* @return $this
*/
public function set_suffix($suffix) {
$this->suffix = $suffix;
return $this;
}
/**
* set wq_ofs parameter for webquery search query
* @param string $wq_ofs webquery wq_ofs parameter
* @return $this
*/
public function set_wq_ofs($wq_ofs) {
$this->wq_ofs = $wq_ofs;
return $this;
}
/**
* set wq_max parameter for webquery search query
* @param string $wq_max webquery wq_max parameter
* @return $this
*/
public function set_wq_max($wq_max) {
$this->wq_max = $wq_max;
return $this;
}
/**
* set query string
* @param string $query query string or key/value array
* @return $this
*/
public function set_query($query) {
$this->query = $query;
return $this;
}
/**
* set name of record element
* @param string $record_name record element name
* @return $this
*/
public function set_record_name($record_name) {
$this->record_name = $record_name;
return $this;
}
/**
* get name of record element
* if record_name is not set return service name
* @return record name
*/
public function get_record_name() {
return isset($this->record_name) ? $this->record_name : $this->service;
}
/**
* create query string from query parameters
*
* @param type $query string or array with key/value pairs
* @param type $wq_ofs value for wq_ofs
* @param type $wq_max value for wq_max
* @return string
*/
public static function get_query_string($query, $wq_ofs = null, $wq_max = null) {
if (empty($query)) {
return "";
}
if (is_string($query)) {
$result = $query;
if (isset($wq_max)) {
$result = "wq_max=$wq_max&$result";
}
if (isset($wq_ofs)) {
$result = "wq_ofs=$wq_ofs&$result";
}
return $result;
}
if (is_array($query)) {
$result = [];
if (isset($wq_ofs)) {
array_push($result, "wq_ofs=$wq_ofs");
}
if (isset($wq_max)) {
array_push($result, "wq_max=$wq_max");
}
foreach ($query as $key => $value) {
# check for 'and not' exclamation mark before key
$is_wqnot = strpos($key, "!") === 0;
if ($is_wqnot) {
# remove exclamation mark from key
$key = substr($key, 1);
array_push($result, "wq_rel=AND+NOT");
}
# tread a single value as an array with one value
# one value is special case of key=val1&key=val2
$values = is_array($value) ? $value : [$value];
# surround multiple OR values with parentheses if rel = and not
$use_open_close = $is_wqnot && count($values) > 1;
if ($use_open_close) {
array_push($result, "wq_par=open");
}
foreach ($values as $v) {
array_push($result, "$key=".rawurlencode($v));
}
if ($use_open_close) {
array_push($result, "wq_par=close");
}
}
return implode("&", $result);
}
return "";
}
public function get_search_url() {
$qry = $this->get_query_string($this->query, $this->wq_ofs, $this->wq_max);
return "http://$this->host/WebQuery/$this->service/$this->suffix".(empty($qry) ? "" : "?$qry");
}
public function get_new_url() {
return "http://$this->host/WebQuery/$this->service/new_$this->suffix";
}
public function search() {
array_push($this->context, 'search');
$this->reset_response();
if (empty($this->service)) {
$this->error("search: service is not set", $this->context);
array_pop($this->context);
return $this;
}
$this->response = $this->http_client_get($this->get_search_url());
array_pop($this->context);
return $this;
}
/**
* run a search request with set request parameters
* continue searches and collect all records until no next element is found
* return all collected records from the search or false if any search fails
*/
public function search_all_records() {
array_push($this->context, 'search_all_records');
if ( ! $this->search()->is_success()) {
array_pop($this->context);
return false;
}
$records = $this->get_records();
while ($ofs = $this->get_next_ofs()) {
if ( ! $this->set_wq_ofs($ofs)->search()->is_success()) {
array_pop($this->context);
return false;
}
array_push($records, $this->get_records());
}
array_pop($this->context);
return $records;
}
/**
* return first record from response
*/
public function get_record() {
$xml = $this->get_resp_xml();
if (!isset($xml)) {
return false;
}
$record_name = $this->get_record_name();
$records = $xml->xpath("//$record_name");
if (empty($records)) {
return false;
}
return $records[0];
}
/**
* return all records from response
*/
public function get_records() {
$xml = $this->get_resp_xml();
if (!isset($xml)) {
return false;
}
$record_name = $this->get_record_name();
return $xml->xpath("//$record_name");
}
/**
* return value of the hits element in search response
* @return number of hits or false if there is no valid search response
*/
public function get_hits() {
$resp_xml = $this->get_resp_xml();
if (!isset($resp_xml)) {
return false;
}
$hits = $resp_xml->xpath("//hits");
return empty($hits) ? false : (string) $hits[0];
}
/**
* @return boolean true if response contains next element
*/
public function has_next() {
$xml = $this->get_resp_xml();
if (!isset($xml)) {
return false;
}
$next = $xml->xpath("//next");
return !empty($next);
}
/**
* return value of wq_ofs attribute of next element in search response
* @return value of next wq_ofs or false if there is no valid search response
*/
public function get_next_ofs() {
$resp_xml = $this->get_resp_xml();
if (!isset($resp_xml)) {
return false;
}
$next = $resp_xml->xpath("//next");
if (empty($next)) {
return false;
}
$attr = $next[0]->attributes();
return empty($attr['wq_ofs']) ? false : (string) $attr['wq_ofs'];
}
/**
* convert xml tree to x-www-form-urlencoded string
* @param SimpleXMLElement $xml xml tree
* @return string xml as x-www-form-urlencoded string
*/
public function xml_to_wwwform($xml) {
$result = [];
foreach($xml as $key=>$value) {
$result[] = "$key=".($value->count() > 0 ? '&'.$this->xml_to_wwwform($value) : urlencode($value));
}
return implode('&', $result);
}
/**
* create new record in webquery
* uses properties host, service and suffix for the url
* @param SimpleXMLElement $xml post data (xmol tree)
* @return boolean true on success
*/
public function create_record($xml) {
array_push($this->context, 'create_record');
$this->reset_response();
if (empty($this->service)) {
$this->error("create_record: service is not set", $this->context);
array_pop($this->context);
return false;
}
$url = $this->get_new_url();
$headers = ['Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'];
$body = $this->xml_to_wwwform($xml);
$this->debug("request url: $url", $this->context);
$this->debug("request data: $body", $this->context);
$request = new Request('POST', $url, $headers, $body);
$options = [
'timeout' => 2,
'http_errors' => false, // disable exceptions on 4xx and 5xx responses
];
try {
$this->response = $this->http_client->send($request, $options);
} catch (\GuzzleHttp\Exception\RequestException $ex) {
$this->error("exception posting to webquery: ".$ex->getMessage(), $this->context);
$this->resp_error = [
'status' => '500',
'code' => $ex->getCode(),
'message' => 'request exception: '.$ex->getMessage(),
];
array_pop($this->context);
return false;
}
$success = $this->is_success();
$code = $this->get_error_code();
$message = $this->get_error_message();
if ($success) {
$this->info("success, code=$code, message=$message, isn=".$this->get_isn(), $this->context);
} else {
$this->error("failed, code=$code, message=$message", $this->context);
}
array_pop($this->context);
return $success;
}
/**
* return success status of last webquery action
* @return boolean true on success
*/
public function is_success() {
if ($this->get_http_status() != 200) {
return false;
}
// check status attribute of webquery error element if available
$error_status = $this->get_error_status();
if (!empty($error_status) && $error_status != 200) {
return false;
}
// check code attribute of webquery error element if available
$error_code = $this->get_error_code();
if (!empty($error_code)) {
// check if code is known to be a success indication
return !empty(array_search($error_code, ["WQW_NO_HITS", "WQW_RECORD_NOT_FOUND", "WQW_UPDATE_OK", "WQW_UPDATE_NOCHANGE"]));
}
return true;
}
/**
* return the http status code of last webquery action
* @return string http status code
*/
public function get_http_status() {
return $this->response->getStatusCode();
}
/**
* return response object of last webquery action
* @return psr-7 response object
*/
public function get_response() {
return $this->response;
}
/**
* return response object of last webquery action
* @return psr-7 response object
*/
public function get_resp_xml() {
if (isset($this->response) && ! isset($this->resp_xml)) {
$this->resp_xml = $this->parse_xml($this->response->getBody());
if (empty($this->resp_xml)) {
$fake_resp = '<root><error status="500"><code>NO_XML_RESP</code><message>failed to parse xml response</message></error></root>';
$this->resp_xml = $this->parse_xml($fake_resp);
}
}
return $this->resp_xml;
}
/**
* return content of first webquery error element of last webquery action
* @return hash array with all webquery error attributes and elements
*/
public function get_error() {
if (isset($this->response) && empty($this->resp_error)) {
$xml = $this->get_resp_xml();
$error = $xml->error[0];
if (isset($error)) {
foreach ($error->attributes() as $attr => $value) {
$this->resp_error[$attr] = (string) $value;
}
foreach ($error->children() as $key => $value) {
$this->resp_error[$key] = (string) $value;
}
}
}
return $this->resp_error;
}
/**
* return value of given error property of last webquery action
* @param string $name webquery error property name (attribute or ellement
* @return string value or empty string if not set
*/
public function get_error_value($name) {
$error = $this->get_error();
return isset($error[$name]) ? $error[$name] : '';
}
/**
* return value of webquery error code of last webquery action
* @return string value of code error property or empty string
*/
public function get_error_code() {
return $this->get_error_value('code');
}
/**
* return value of webquery error status of last webquery action
* @return string value of status error property or empty string
*/
public function get_error_status() {
return $this->get_error_value('status');
}
/**
* return value of webquery error message of last webquery action
* the return message is a composite of the message, field and xmlerror properties
* @return string value of message error property or empty string
*/
public function get_error_message() {
$message = $this->get_error_value('message');
$field = $this->get_error_value('field');
if (!empty($field)) {
$message = "$message: $field";
}
$xmlerror = $this->get_error_value('xmlerror');
return empty($xmlerror) ? $message : "$message ($xmlerror)";
}
/**
* return value of webquery error isn of last webquery action
* isn is set even if webquery failed to create a record
* @return string value of isn error property or empty string
*/
public function get_isn() {
return $this->get_error_value('isn');
}
protected function reset_response() {
$this->response = null;
$this->resp_xml = null;
$this->resp_error = [];
}
public function http_client_get($url) {
$options = [
'timeout' => 2,
'http_errors' => false, // disable exceptions on 4xx and 5xx responses
];
try {
return $this->http_client->get($url, $options);
} catch (\GuzzleHttp\Exception\RequestException $ex) {
$this->error("exception get request to webquery: ".$ex->getMessage(), $this->context);
return new \GuzzleHttp\Psr7\Response(500, [], "<exception><error code=$ex->getCode()><message>$ex->getMessage()</message></error></exception>");
}
}
/**
* parse xml content from message
*
* @param $text
* @return SimpleXMLElement parsed xml or false on errors
*/
public function parse_xml($text) {
if (trim($text) == '') {
$this->error("xml string is empty", $this->context);
return false;
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($text);
$errors = libxml_get_errors();
libxml_clear_errors();
$error_count = count($errors);
if ($error_count > 0) {
$this->error("parsing xml string failed with $error_count errors (max. 10 listed", $this->context);
// only log first 10 error messages
foreach (array_slice($errors, 0, 9) as $error) {
$this->error('xml parse error: ' . print_r($error, true), $this->context);
}
return false;
}
return $xml;
}
}