Location: PHPKode > scripts > Blip API > blip-api/blipapi.php
<?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
Return current item: Blip API