<?php
/**
* Blip! (http://blip.pl) communication library.
*
* @author Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
* @version 0.02.15
* @version $Id: blipapi.php 39 2009-03-27 23:38:41Z urzenia $
* @copyright Copyright (c) 2007, Marcin Sztolcman
* @license http://opensource.org/licenses/gpl-license.php GNU Public License v.2
* @package blipapi
*/
/**
* Blip! (http://blip.pl) communication library.
*
* @author Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
* @version 0.02.15
* @version $Id: blipapi.php 39 2009-03-27 23:38:41Z urzenia $
* @copyright Copyright (c) 2007, Marcin Sztolcman
* @license http://opensource.org/licenses/gpl-license.php GNU Public License v.2
* @package blipapi
*/
if (!class_exists ('BlipApi')) {
interface IBlipApi_Command { }
/**
* Function registered for SPL autoloading - load required class
*
* @param array $class_name
*/
function BlipApi__autoload ($class_name) {
if (substr ($class_name, 0, 8) == 'BlipApi_') {
include strtolower ($class_name).'.php';
}
}
spl_autoload_register ('BlipApi__autoload');
/**
* Converts specified array of params to query string
*
* @param array $arr
* @return string
*/
function BlipApi__arr2qstr ($arr) {
$ret = array ();
foreach ($arr as $k => $v) {
$ret[] = sprintf ('%s=%s', $k, $v);
}
return implode ('&', $ret);
}
class BlipApi {
/**
* CURL handler
*
* @access protected
* @var resource
*/
protected $_ch;
/**
* Login to Blip!
*
* @access protected
* @var string
*/
protected $_login;
/**
* Password to Blip!
*
* @access protected
* @var string
*/
protected $_password;
/**
* Useragent
*
* @access protected
* @var string
*/
protected $_uagent = 'BlipApi.php/0.02.15 (http://blipapi.googlecode.com)';
/**
*
*
* @access protected
* @var string
*/
protected $_referer = 'http://urzenia.net';
/**
* URI to API host
*
* @access protected
* @var string
*/
protected $_root = 'http://api.blip.pl';
/**
* Mime type for "Accept" header in request
*
* @access protected
* @var string
*/
protected $_format = 'application/json';
/**
*
*
* @access protected
* @var string
*/
protected $_timeout = 10;
/**
* Debug mode flag
*
* @access protected
* @var bool
*/
protected $_debug = false;
/**
* Debug message type
*
* @access protected
* @var bool
*/
protected $_debug_tpl = array ('', '');
/**
* Headers to be sent
*
* @access protected
* @var array
*/
protected $_headers = array ();
/**
* Parser for JSON format
*
* This needs to contain name of the function for parsing JSON.
* Alternatively it may be an array with object and its method name:
* array ($json, 'decode')
*
* @access protected
* @var array
*/
protected $_parser = 'json_decode';
/**
* BlipApi constructor
*
* Initialize CURL handler ({@link $_ch}). Throws RuntimeException exception if no CURL extension found.
*
* @param string $login
* @param string $passwd
*/
public function __construct ($login=null, $passwd=null, $dont_connect=false) {
if (!function_exists ('curl_init')) {
throw new RuntimeException ('CURL missing!', -1);
}
$this->_login = $login;
$this->_password = $passwd;
# inicjalizujemy handler curla
$this->_ch = curl_init ($this->_root);
if (!$this->_ch) {
throw new RuntimeException ('CURL initialize error: '. curl_error ($this->_ch), curl_errno ($this->_ch));
}
# ustawiamy domyÅlne nagÅówki
$this->_headers = array (
'Accept' => $this->format,
'X-Blip-API' => '0.02',
);
# inicjalizujemy szablon dla debugow
$this->debug_html = false;
if (!$dont_connect) {
$this->connect ();
}
}
/**
* BlipApi destructor
*
* Close CURL handler, if active
*/
public function __destruct () {
if (is_resource ($this->_ch)) {
curl_close ($this->_ch);
}
}
/**
* Magic method to execute commands as their names - it makes all dirty job...
*
* @param string $fn name of command
* @param array $args arguments
* @access public
* @return return of {@link execute}
*/
public function __call ($fn, $args) {
# szukamy klasy i metody do uzycia
list ($class_name, $method_name) = split ('_', $fn, 2);
$class_name = 'BlipApi_'.ucfirst ($class_name);
$method_name = strtolower ($method_name);
if (!class_exists ($class_name) || !method_exists ($class_name, $method_name)) {
throw new InvalidArgumentException ('Command not found.', -1);
}
$this->_debug ('CMD: '. $class_name.'::'.$method_name);
# wywoÅujemy znalezionÄ
metodÄ aby pobrac dane dla requestu
list ($url, $http_method, $http_data, $opts) = call_user_func_array (array ($class_name, $method_name), $args);
# ustawiamy opcje dla konkretnego typu requestu
$http_method = strtolower ($http_method);
switch ($http_method) {
case 'post':
if (!isset ($opts['multipart']) || !$opts['multipart']) {
$http_data = BlipApi__arr2qstr ($http_data);
}
$curlopts = array (
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $http_data,
);
break;
case 'get':
$curlopts = array ( CURLOPT_HTTPGET => true );
break;
case 'put':
$curlopts = array ( CURLOPT_PUT => true,);
if (!$http_data) {
$curlopts[CURLOPT_HTTPHEADER] = array ('Content-Length' => 0);
}
break;
case 'delete':
$curlopts = array ( CURLOPT_CUSTOMREQUEST => 'DELETE' );
break;
default:
throw new UnexpectedValueException ('Unknown HTTP method.', -1);
}
$this->_debug ('METHOD: '. strtoupper ($http_method));
# ustawiamy url
$curlopts[CURLOPT_URL] = $this->_root . $url;
$headers_single = array ();
# jesli trzeba to dodajemy jednorazowe nagÅówki które mamy wysÅaÄ
if (isset ($curlopts[CURLOPT_HTTPHEADER])) {
$this->headers_set ($curlopts[CURLOPT_HTTPHEADER]);
$headers_names = array_keys ($curlopts[CURLOPT_HTTPHEADER]);
}
# nagÅówki do wysÅania
if ($this->_headers) {
$headers = array ();
foreach ($this->_headers as $k=>$v) {
$headers[] = sprintf ('%s: %s', $k, $v);
}
$curlopts[CURLOPT_HTTPHEADER] = $headers;
}
$this->_debug ('post2', print_r ($this->_headers, 1), print_r ($headers_names, 1));
$this->_debug ('DATA: '. print_r ($http_data, 1), 'CURLOPTS: '.print_r ($this->_debug_curlopts ($curlopts), 1));
if (!curl_setopt_array ($this->_ch, $curlopts)) {
throw new RuntimeException (curl_error ($this->_ch), curl_errno ($this->_ch));
}
# wykonujemy zapytanie
$reply = curl_exec ($this->_ch);
# usuwamy z zestawu naglowkow do wyslania te ktore mialy byc jednorazowe
if (isset($headers_names)) {
$this->headers_remove ($headers_names);
}
$this->_debug ('post3', print_r ($this->_headers, 1));
if (!$reply) {
throw new RuntimeException ('CURL Error: '. curl_error ($this->_ch), curl_errno ($this->_ch));
}
$this->_debug ($reply);
$reply = $this->__parse_reply ($reply);
if ($reply['status_code'] >= 400) {
throw new RuntimeException ($reply['status_body'], $reply['status_code']);
}
return $reply;
}
/**
* Setter for some options
*
* For specified keys, call proper __set_* method. Throws InvalidArgumentException exception when incorrect key was
* specified.
*
* @param string $key name of property to set
* @param mixed $value value of property
* @access public
*/
public function __set ($key, $value) {
if (!method_exists ($this, '__set_'.$key)) {
throw new InvalidArgumentException (sprintf ('Unknown param: "%s".', $key), -1);
}
return call_user_func (array ($this, '__set_'.$key), $value);
}
/**
* Getter for some options
*
* For specified keys, return them. Throws InvalidArgumentException exception when incorrect key was specified.
*
* @param string $key name of property to return
* @return mixed
* @access public
*/
public function __get ($key) {
if (!method_exists ($this, '__set_'.$key)) {
throw new InvalidArgumentException (sprintf ('Unknown param: "%s".', $key), -1);
}
$key = '_'.$key;
return $this->$key;
}
/**
* Setter for {@link $_debug} property
*
* @param bool $enable
* @access protected
*/
protected function __set_debug ($enable = null) {
$this->_debug = $enable ? true : false;
curl_setopt($this->_ch, CURLOPT_VERBOSE, $this->_debug);
}
/**
* Setter for {@link $_debug_html} property
*
* @param bool $enable
* @access protected
*/
protected function __set_debug_html ($enable = null) {
if ($enable) {
$this->_debug_tpl = array (
"<pre style='border: 1px solid black; padding: 4px;'><b>DEBUG MSG:</b>\n",
"</pre>\n",
);
}
else {
$this->_debug_tpl = array (
"DEBUG MSG:\n",
"\n",
);
}
}
/**
* Setter for {@link $_format} property
*
* Format have to be string in mime type format. In other case, there will be prepended 'application/' prefix.
*
* @param string $format
* @access protected
*/
protected function __set_format ($format) {
# jeÅli nie jest to peÅen typ mime, to doklejamy na poczÄ
tek 'application/'
if ($format && strpos ($format, '/') === false) {
$format = 'application/'. $format;
}
$this->_format = $format;
}
/**
* Setter for {@link $_uagent} property
*
* @param string $uagent
* @access protected
*/
protected function __set_uagent ($uagent) {
$this->_uagent = (string) $uagent;
curl_setopt ($this->_ch, CURLOPT_USERAGENT, $this->_uagent);
}
/**
* Setter for {@link $_referer} property
*
* @param string $referer
* @access protected
*/
protected function __set_referer ($referer) {
$this->_referer = (string) $referer;
curl_setopt ($this->_ch, CURLOPT_REFERER, $referer);
}
/**
* Setter for {@link $_parsers} property
*
* @params array $data key have to be content-type (i.e. application/json) and value - function name to execute (i.e. json_decode)
* @access protected
*/
protected function __set_parser ($data) {
$this->_parser = $data;
}
/**
* Setter for {@link $_timeout} property
*
* @param string $timeout
* @access protected
*/
protected function __set_timeout ($timeout) {
$this->_timeout = (int) $timeout;
curl_setopt ($this->_ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout);
}
/**
* Setter for {@link $_headers} property
*
* @param array|string $headers headers in format specified at {@link _parse_headers}
* @access protected
*/
protected function __set_headers ($headers) {
$this->_headers = $this->_parse_headers ($headers);
}
/**
* Parsing headers parameter to correct format
*
* Param $headers have to be an array, where key is header name, and value - header value, or string in
* 'Header-Name: Value'.
* Throws UnexpectedValueException of incorect type of $headers is given
*
* @param array|string $headers
* @access protected
*/
protected function _parse_headers ($headers) {
if (!$headers) {
$headers = array ();
}
else if (is_string ($headers) && preg_match ('/^(\w+):\s(.*)/', $headers, $match)) {
$headers = array ( $match[1] => $match[2] );
}
else if (!is_array ($headers)) {
throw new UnexpectedValueException (sprintf ('%s::$headers have to be an array or string, but %s given.',
__CLASS__,
gettype ($headers)), -1
);
}
return $headers;
}
/**
* Add or replace headers to be sent to remote server
*
* @param array|string $headers headers in format specified at {@link _parse_headers}
* @access public
* @return bool false if empty array specified
*/
public function headers_set ($headers) {
$headers = $this->_parse_headers ($headers);
if (!$headers) {
return false;
}
foreach ($headers as $k=>$v) {
$this->_headers[$k] = $v;
}
return true;
}
/**
* Remove specified header
*
* @param array|string $headers headers in format specified at {@link _parse_headers}
* @access public
* @return bool false if empty array specified
*/
public function headers_remove ($headers) {
$headers = $this->_parse_headers ($headers);
if (!$headers) {
return false;
}
foreach ($headers as $k=>$v) {
if (isset ($this->_headers[$k])) {
unset ($this->_headers[$k]);
}
}
return true;
}
/**
* Get headers set to sent
*
* $headers have to be:
* * array - with names of headers values to return
* * string - with single header name
* * null - if all headers have to be returned
*
* @param mixed $headers
* @access public
* @return array
*/
public function headers_get ($headers=null) {
if (is_null ($headers)) {
return $this->_headers;
}
else if (is_string ($headers)) {
$headers = array ($headers);
}
else if (!is_array ($headers)) {
throw new UnexpectedValueException ('Incorrect value specified.', -1);
}
$ret = array ();
foreach ($headers as $header) {
$ret[$header] = (isset ($this->_headers[$header])) ? $this->_headers[$header] : null;
}
return $ret;
}
/**
* Create connection with CURL, setts some CURL options etc
*
* Throws RuntimeException exception when CURL initialization has failed
*
* @param string $login as in {@link __construct}
* @param string $passwd as in {@link __construct}
* @access public
* @return bool always true
*/
public function connect ($login=null, $passwd=null) {
if (!is_null ($login)) {
$this->_login = (string) $login;
}
if (!is_null ($passwd)) {
$this->_password = (string) $passwd;
}
# standardowe opcje curla
$curlopts = array (
CURLOPT_USERAGENT => $this->uagent,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HEADER => true,
CURLOPT_HTTP200ALIASES => array (201, 204),
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_CONNECTTIMEOUT => 10,
);
# jeÅli podane login i hasÅo, to logujemy siÄ
if ($this->_login && !is_null ($this->_password)) {
$curlopts[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
$curlopts[CURLOPT_USERPWD] = sprintf ('%s:%s', $this->_login, $this->_password);
}
# ustawiamy opcje
curl_setopt_array ($this->_ch, $curlopts);
return true;
}
/**
* Execute command and parse reply
*
* Throws InvalidArgumentException exception when specified command does not exists, or RuntimeException
* when exists some CURL error or returned status code is greater or equal 400.
*
* Internally using magic method BlipApi::__call.
*
* @param string $command command to execute
* @param mixed $options,... options passed to proper command method (prefixed with _cmd__)
* @access public
* @return array like {@link __query}
*/
public function execute () {
if (!func_num_args ()) {
throw new InvalidArgumentException ('Command missing.', -1);
}
$args = func_get_args ();
$fn = array_shift ($args);
return call_user_func_array (array ($this, $fn), $args);
}
/**
* Print debug mesage if debug mode is enabled
*
* @param string $msg,... messages to print to stdout
* @access protected
* @return bool
*/
protected function _debug () {
if (!$this->_debug) {
return;
}
$args = func_get_args ();
echo $this->_debug_tpl[0];
foreach ($args as $i=>$arg) {
printf ("%d. %s\n", $i++, print_r ($arg, 1));
}
echo $this->_debug_tpl[1];
return 1;
}
/**
* Return array with CURLOPT_* constants values replaced by these names. For debugging purposes only.
*
* @param array $opts array with CURLOPTS_* as keys
* @return array the same as $opts, but keys are replaced by names of constants
* @access protected
*/
protected function _debug_curlopts ($opts) {
$copts = array ();
foreach (get_defined_constants () as $k => $v) {
if (strlen ($k) > 8 && substr ($k, 0, 8) == 'CURLOPT_') {
$copts[$v] = $k;
}
}
$ret = array ();
foreach ($opts as $k => $v) {
if (isset ($copts, $k)) {
$ret[$copts[$k]] = $v;
}
else {
$ret[$k] = $v;
}
}
return $ret;
}
/**
* Parse reply
*
* Throws BadFunctionCallException exception when specified parser was not found.
* Return array with keys
* * headers - (array) array of headers (keys are lowercased)
* * body - (mixed) body of response. If reply's mime type is found in {@link $_parser}, then contains reply of specified parser, in other case contains raw string reply.
* * body_parse - (bool) if true, content was successfully parsed by specified parser
* * status_code - (int) status code from server
* * status_body - (string) content of status
*
* @param string $reply
* @return array
* @access protected
*/
protected function __parse_reply ($reply) {
# rozdzielamy nagÅówki od treÅci
$reply = preg_split ("/\r?\n\r?\n/mu", $reply, 2);
$headers = $reply[0];
$body = isset ($reply[1]) ? $reply[1] : '';
# parsujemy nagÅówki
$headers = explode ("\n", $headers);
# usuwamy typ protokoÅu
$header_http = array_shift ($headers);
$headers_parsed = array ();
$header_name = '';
foreach ($headers as $header) {
if ($header[0] == ' ' || $header[0] == "\t") {
$headers_parsed[$header_name] .= trim ($header);
}
else {
$header = preg_split ('/\s*:\s*/', trim ($header), 2);
$header_name = strtolower ($header[0]);
$headers_parsed[$header_name] = $header[1];
}
}
$headers = &$headers_parsed;
# okreÅlamy kod statusu
if (
(isset ($headers['status']) && preg_match ('/(\d+)\s+(.*)/u', $headers['status'], $match))
||
(preg_match ('!HTTP/1\.[01]\s+(\d+)\s+([\w ]+)!', $header_http, $match))
) {
$status = array ( $match[1], $match[2] );
}
else {
$status = array (0, '');
}
# parsujemy treÅÄ odpowiedzi, jeÅli mamy odpowiedni parser
$body_parsed = false;
if (
(is_array ($this->parser) && isset ($this->parser[1]) && is_object ($this->parser[0]) &&
method_exists ($this->parser[0], $this->parser[1]))
||
function_exists ($this->parser)
) {
$body = call_user_func ($this->parser, $body);
$body_parsed = true;
}
else {
throw new BadFunctionCallException ('Specified parser not found.');
}
return array (
'headers' => $headers,
'body' => $body,
'body_parsed' => $body_parsed,
'status_code' => $status[0],
'status_body' => $status[1],
);
}
}
}
// vim: fdm=manual