WordPress.org

Make WordPress Core

Ticket #7644: atom.diff

File atom.diff, 57.0 KB (added by cavemonkey50, 10 years ago)

The shared Atom API library. If committing MT before TypePad, this should be included as well.

  • class-atom-api.php

     
     1<?php
     2
     3/****************************************************************************\
     4                         PHP Atom API Package
     5                             version 1.0
     6
     7         Includes AtomAPI,  AtomFeed,  AtomEntry and AtomRequest
     8
     9                     Written by Beau Lebens,  2005
     10                       beau <at> dentedreality.com <dot> au
     11              http://www.dentedreality.com.au/phpatomapi/
     12
     13
     14   More Atom Info;
     15     AtomEnabled: http://www.atomenabled.org/
     16     Atom Wiki: http://www.intertwingly.net/wiki/pie/FrontPage
     17     API Spec: http://www.atomenabled.org/developers/api/atom-api-spec.php
     18   
     19   Blog-Specific API Details
     20     Blogger: http://code.blogspot.com/archives/atom-docs.html
     21     TypePad: http://sixapart.com/developers/atom/typepad/
     22
     23   TODO
     24     - Proper date handling (GMT,  local etc)
     25     - AtomEntry::from_xml() needs work on author info handling - sample?
     26     - Complete test suite/application?
     27
     28        @copyright (c) 2005, Beau Lebens
     29        @license http://www.opensource.org/licenses/bsd-license.php  BSD
     30\****************************************************************************/
     31
     32// Error Numbers
     33define('ATOMAPI_ENDPOINT_INVALID',         1);
     34define('ATOMAPI_AUTHTYPE_INVALID',         2);
     35define('ATOMAPI_AUTHENTICATION_FAILED',         3);
     36define('ATOMAPI_NO_FEEDS',             4);
     37define('ATOMAPI_NO_XML',             5);
     38define('ATOMAPI_FEED_INIT_FAILED',         6);
     39define('ATOMAPI_METHOD_INVALID',         7);
     40define('ATOMAPI_PAYLOAD_INVALID',         8);
     41define('ATOMAPI_REQUEST_INVALID',         9);
     42define('ATOMAPI_CURL_ERROR',             10);
     43
     44// Error Strings
     45$ATOMAPI_ERROR_STRINGS = array(1=>'The specified AtomAPI endpoint (or request URL) is invalid. Please enter a complete URL,  starting with \'http\'.',
     46                            2=>'The PHPAtomAPI Package currently only supports \'Basic\' and \'WSSE\' authentication models. Please specify one of them.',
     47                            3=>'Authentication against the AtomAPI endpoint failed. Please check your username/password.',
     48                            4=>'No feeds were located at the specified AtomAPI endpoint.',
     49                            5=>'No feeds were initiated because there was no XML available to parse.',
     50                            6=>'No feeds were initiated because the FeedURI did not respond as expected.',
     51                            7=>'An invalid method was supplied to an AtomRequest object. Methods available are GET,  PUT,  POST and DELETE',
     52                            8=>'The payload supplied to an AtomRequest object appears to be something other than a string. Please only supply a string as payload.',
     53                            9=>'An AtomRequest object was initiated incorrectly,  and is missing either a URI or a Method.',
     54                            10=>'cURL failed to perform the required request within an AtomRequest.');
     55
     56class AtomAPI {
     57    var $endpoint;
     58    var $authtype;
     59    var $auth;
     60    var $feeds;
     61    var $feed_objs;
     62    var $err_no;
     63   
     64    /**
     65    * @return AtomAPI/FALSE
     66    * @param String $endpoint
     67    * @param String $username
     68    * @param String $password
     69    * @param String $authtype
     70    * @desc Creates an AtomAPI Object,  verifying authentication details by attempting to load a list of feeds. $authtype should be 'Basic' or 'WSSE'
     71    */
     72    function AtomAPI($endpoint,  $username,  $password,  $authtype) {
     73        // Abort if the endpoint is missing
     74        if ($endpoint === false || !strlen($endpoint)) {
     75            $this->err_no = ATOMAPI_ENDPOINT_INVALID;
     76            return false;
     77        }
     78       
     79        // Set the endpoint and authtype,  then make sure they worked,  or die
     80        $this->set_endpoint($endpoint);
     81        if (!$this->get_endpoint()) {
     82            return false;
     83        }
     84        $this->set_authtype($authtype,  $username,  $password);
     85        if (!$this->get_auth()) {
     86            return false;
     87        }
     88       
     89        // Verify authentication (and load feeds)
     90        return $this->verify_auth();
     91    }
     92   
     93    /**
     94    * @return void
     95    * @param String $uri
     96    * @desc Set the endpoint for accessing this AtomAPI. Must be a fully-qualified URI starting with 'http'
     97    */
     98    function set_endpoint($uri) {
     99        if (substr($uri,  0,  4) == 'http') {
     100            $this->endpoint = $uri;
     101        }
     102        else {
     103            $this->err_no = ATOMAPI_ENDPOINT_INVALID;
     104        }
     105    }
     106   
     107    /**
     108    * @return String/FALSE
     109    * @desc Return the endpoint set for this AtomAPI or FALSE if unset
     110    */
     111    function get_endpoint() {
     112        if (isset($this->endpoint)) {
     113            return $this->endpoint;
     114        }
     115        else {
     116            return false;
     117        }
     118    }
     119   
     120    /**
     121    * @return void
     122    * @param String $type
     123    * @param String $user
     124    * @param String $pass
     125    * @desc Creates the authentication object for this Atom API,  based on the type specified. Currently supports WSSE and Basic as $type
     126    */
     127    function set_authtype($type,  $user,  $pass) {
     128        $this->authtype = $type;
     129       
     130        if ($type == 'Basic') {
     131        //  require_once(dirname(__FILE__) . '/class.basicauth.php');
     132            $this->auth = new BasicAuth($user,  $pass);
     133        }
     134        else if ($type == 'WSSE') {
     135        //  require_once(dirname(__FILE__) . '/class.wsse.php');
     136            $this->auth = new WSSE($user,  $pass);
     137        }
     138        else {
     139            $this->err_no = ATOMAPI_AUTHTYPE_INVALID;
     140        }
     141    }
     142   
     143    /**
     144    * @return String
     145    * @desc Returns the current authentication model (Basic or WSSE)
     146    */
     147    function get_authtype() {
     148        return $this->authtype;
     149    }
     150   
     151    /**
     152    * @return Boolean
     153    * @desc Verifies authentication details by attempting to access the endpoint. If successful,  initiates the list of available feeds and returns true,  otherwise returns false.
     154    */
     155    function verify_auth() {
     156        // Test authentication by accessing the endpoint with the auth object
     157        $ar = new AtomRequest('GET',  $this->get_endpoint(),  $this->auth,  '');
     158        $response = $ar->exec();
     159       
     160        // Looks ok,  return the Object
     161        if ($response == 200) {
     162            $this->get_feeds($ar->get_response());
     163            return true;
     164        }
     165        // Not a 200 Ok response - authetication failed,  or endpoint incorrect
     166        else {
     167            $this->err_no = ATOMAPI_AUTHENTICATION_FAILED;
     168            return false;
     169        }
     170    }
     171   
     172    /**
     173    * @return AuthenticationObject
     174    * @desc Returns an authentication object (either WSSE or Basic)
     175    */
     176    function get_auth() {
     177        return $this->auth;
     178    }
     179   
     180    /**
     181    * @return Array/String
     182    * @desc Returns an array containing the details of the feeds listed at the endpoint. On failure,  returns the HTTPCode as a string instead
     183    */
     184    function get_feeds($xml = false) {
     185        // Feeds already fetched? Then just return the array again
     186        if (is_array($this->feeds)) {
     187            return $this->feeds;
     188        }
     189        // First time the feeds are requested,  so get the endpoint and build out the array
     190        else {
     191                        // Create an AtomRequest pointing to the AtomAPI endpoint (where we get a list of available feeds)
     192                        $ar = new AtomRequest('GET',  $this->endpoint,  $this->auth,  '');
     193                        $code = $ar->exec();
     194                        if ($code) {
     195                            $feeds_raw = $ar->get_response();
     196                                return $feeds_raw;
     197                        }
     198                        else {
     199                            return false;
     200                        }
     201        }
     202    }
     203   
     204    /**
     205    * @return AtomFeed
     206    * @param Int $id
     207    * @desc Creates (if required) and returns an AtomFeed object,  based on an $id for the feeds array (pulled from endpoint)
     208    */
     209    function get_feed($id) {
     210        // Build the feeds array if it's not already initiated
     211        if (!sizeof($this->feeds)) {
     212            $this->get_feeds();
     213        }
     214       
     215        // And create this AtomFeed if it's not there
     216        if (!is_object($this->feed_objs[$id])) {
     217            $this->feed_objs[$id] = new AtomFeed($this->feeds[$id]['service.feed'],  $this->get_auth());
     218        }
     219       
     220        // Return false if there's still no object
     221        if (!is_object($this->feed_objs[$id])) {
     222            return false;
     223        }
     224        // Return the requested AtomFeed
     225        else {
     226            return $this->feed_objs[$id];
     227        }
     228    }
     229   
     230    /**
     231    * @return Array/String
     232    * @param Int $id
     233    * @desc Creates an AtomFeed object for the details specified in $id,  and gets the entries available. Stores the object internally for later
     234    */
     235    function get_entries($id) {
     236        // If the AtomFeed where entries are requested from doesn't exist,  create it first
     237        if (!is_object($this->feed_objs[$id])) {
     238            $this->feed_objs[$id] = new AtomFeed($this->feeds[$id]['service.feed'],  $this->auth);
     239        }
     240       
     241        // Return false if there's still no object
     242        if (!is_object($this->feed_objs[$id])) {
     243            return false;
     244        }
     245        // Request all entries available from this AtomFeed and return whatever comes back
     246        else {
     247            $entries = $this->feed_objs[$id]->get_entries();
     248            return $entries;
     249        }
     250    }
     251   
     252    /**
     253    * @return Int/FALSE
     254    * @desc Returns the last error registered in this class,  or FALSE if none
     255    */
     256    function error() {
     257        if (isset($this->err_no) && is_int($this->err_no)) {
     258            return $this->err_no;
     259        }
     260        else {
     261            return false;
     262        }
     263    }
     264}
     265
     266
     267
     268class AtomFeed {
     269    var $endpoint;
     270    var $auth;
     271    var $title = array();
     272    var $author = array();
     273    var $version;
     274    var $links = array();
     275    var $tagline = array();
     276    var $id;
     277    var $generator = array();
     278    var $info = array();
     279    var $modified;
     280    var $entries = array();
     281    var $err_no;
     282   
     283    /**
     284    * @return AtomFeed
     285    * @param String $endpoint
     286    * @param AuthObj $auth
     287    * @param String $xml
     288    * @desc Creates an AtomFeed object,  based on the details specified. If $xml is provided,  then it is parsed to populate the object,  otherwise the endpoint is accessed and it is populated from the response XML. If all values are false or omitted,  then an empty AtomFeed is created,  and awaits the use of set_*() functions to set it up manually.
     289    */
     290    function AtomFeed($endpoint = false,  $auth = false,  $xml = false) {
     291        // If they are set,  then store the endpoint and auth object
     292        if ($endpoint && $auth) {
     293            $this->set_endpoint($endpoint);
     294            $this->set_auth($auth);
     295        }
     296       
     297        // If the object was created with XML,  populate it with parsed values
     298        if ($xml !== false) {
     299            $this->from_xml($xml);
     300        }
     301        // No XML? If there's an endpoint and auth object,  then get some XML to populate with
     302        else if ($endpoint && $auth) {
     303            $res = $this->init();
     304            // >= 300 is not a good http status code to get back
     305            if ($res >= 300) {
     306                return false;
     307            }
     308        }
     309        else {
     310            $this->err_no = ATOMAPI_FEED_INIT_FAILED;
     311            return false;
     312        }
     313    }
     314   
     315    /**
     316    * @return void
     317    * @param AuthObj $auth
     318    * @desc Sets the authentication object to use for this AtomFeed
     319    */
     320    function set_auth($auth) {
     321        if (is_object($auth)) {
     322            $this->auth = $auth;
     323        }
     324        else {
     325            $this->err_no = ATOMAPI_AUTHTYPE_INVALID;
     326        }
     327    }
     328   
     329    /**
     330    * @return AuthObj/FALSE
     331    * @desc Returns the currently set authentication object for this AtomFeed,  or FALSE if not set
     332    */
     333    function get_auth() {
     334        if (is_object($this->auth)) {
     335            return $this->auth;
     336        }
     337        else {
     338            return false;
     339        }
     340    }
     341   
     342    /**
     343    * @return void
     344    * @param String $uri
     345    * @desc Set the endpoint for accessing this AtomFeed. Must be a fully-qualified URI starting with 'http'
     346    */
     347    function set_endpoint($uri) {
     348        if (substr($uri,  0,  4) == 'http') {
     349            $this->endpoint = $uri;
     350        }
     351        else {
     352            $this->err_no = ATOMAPI_ENDPOINT_INVALID;
     353        }
     354    }
     355   
     356    /**
     357    * @return String/FALSE
     358    * @desc Returns the endpoint for this AtomFeed if specified,  or FALSE if not
     359    */
     360    function get_endpoint() {
     361        if (isset($this->endpoint) && strlen($this->endpoint)) {
     362            return $this->endpoint;
     363        }
     364        else {
     365            return false;
     366        }
     367    }
     368   
     369    /**
     370    * @return void
     371    * @param String $v
     372    * @desc Set the version number for this AtomFeed (goes in the <feed> element)
     373    */
     374    function set_version($v) {
     375        $this->version = $v;
     376    }
     377   
     378    /**
     379    * @return String
     380    * @desc Returns the version number specified for this AtomFeed in the <feed> element
     381    */
     382    function get_version() {
     383        if (isset($this->version)) {
     384            return $this->version;
     385        }
     386        else {
     387            return false;
     388        }
     389    }
     390   
     391    /**
     392    * @return void
     393    * @param String $i
     394    * @desc Sets the <id> element contents for the AtomFeed.
     395    */
     396    function set_id($i) {
     397        $this->id = $i;
     398    }
     399   
     400    /**
     401    * @return String
     402    * @desc Returns the contents of the <id> element for this AtomFeed
     403    */
     404    function get_id() {
     405        if (isset($this->id)) {
     406            return $this->id;
     407        }
     408        else {
     409            return false;
     410        }
     411    }
     412   
     413    /**
     414    * @return void
     415    * @param Array/String $title
     416    * @desc Sets the title for this AtomFeed.
     417    */
     418    function set_title($title) {
     419        if (is_array($title)) {
     420            foreach ($title as $key=>$val) {
     421                if (strlen($key) && strlen($val)) {
     422                    $this->title[$key] = $val;
     423                }
     424            }
     425        }
     426        else if (is_string($title)) {
     427            $this->title['title'] = $title;
     428        }
     429    }
     430   
     431    /**
     432    * @return Array/String/FALSE
     433    * @param String $elem
     434    * @desc Returns either a specific element of the title,  or the entire array of data if none is specified.
     435    */
     436    function get_title($elem = false) {
     437        if ($elem === false) {
     438            return $this->title;
     439        }
     440        else {
     441            if (in_array($elem,  array_keys($this->title))) {
     442                return $this->title[$elem];
     443            }
     444            else {
     445                return false;
     446            }
     447        }
     448    }
     449   
     450    /**
     451    * @return void
     452    * @param Array/String $tagline
     453    * @desc Sets the details of the tagline for this AtomFeed
     454    */
     455    function set_tagline($tagline) {
     456        if (is_array($tagline)) {
     457            foreach ($tagline as $key=>$val) {
     458                $this->tagline[$key] = $val;
     459            }
     460        }
     461        else if (is_string($tagline)) {
     462            $this->tagline['tagline'] = $tagline;
     463        }
     464    }
     465   
     466    /**
     467    * @return Array/String/FALSE
     468    * @param String $elem
     469    * @desc Returns either a specific element of the tagline,  or the entire array of data if none is specified.
     470    */
     471    function get_tagline($elem = false) {
     472        if ($elem === false) {
     473            return $this->tagline;
     474        }
     475        else {
     476            if (in_array($elem,  array_keys($this->tagline))) {
     477                return $this->tagline[$elem];
     478            }
     479            else {
     480                return false;
     481            }
     482        }
     483    }
     484   
     485    /**
     486    * @return void
     487    * @param Array $gen
     488    * @desc Sets the details of the generator for this AtomFeed.
     489    */
     490    function set_generator($gen) {
     491        if (is_array($gen)) {
     492            foreach ($gen as $key=>$val) {
     493                $this->generator[$key] = $val;
     494            }
     495        }
     496    }
     497   
     498    /**
     499    * @return void
     500    * @param String $date
     501    * @desc Set the modified date for this feed
     502    */
     503    function set_modified($date) {
     504        if (preg_match('/(\d{4})-(\d{2})-(\d{2}).(\d{2}):(\d{2}):(\d{2})-(\d{2}):(\d{2})/',  $date,  $parts) || preg_match('/(\d{4})-(\d{2})-(\d{2}).(\d{2}):(\d{2}):(\d{2})Z/',  $date,  $parts)) {
     505            $this->modified = $date;
     506        }
     507    }
     508   
     509    /**
     510    * @return String/FALSE
     511    * @desc Get the modified date for this feed,  or FALSE if it's not set
     512    */
     513    function get_modified() {
     514        if (isset($this->modified) && strlen($this->modified)) {
     515            return $this->modified;
     516        }
     517    }
     518   
     519    /**
     520    * @return void
     521    * @param Array/String $info
     522    * @desc Set the info element details. If a string is passed in,  then defaults to the info contents
     523    */
     524    function set_info($info) {
     525        if (is_array($info)) {
     526            foreach ($info as $key=>$val) {
     527                $this->info[$key] = $val;
     528            }
     529        }
     530        else {
     531            $this->info['info'] = $info;
     532        }
     533    }
     534   
     535    /**
     536    * @return Array/String/FALSE
     537    * @desc Returns either the requested individual attribute of the info element,  the entire array,  or FALSE if it's not set.
     538    */
     539    function get_info($elem = false) {
     540        if ($elem === false) {
     541            return $this->info;
     542        }
     543        else {
     544            if (in_array($elem,  array_keys($this->info))) {
     545                return $this->info[$elem];
     546            }
     547            else {
     548                return false;
     549            }
     550        }
     551    }
     552   
     553    /**
     554    * @return Array/String/FALSE
     555    * @param String $elem
     556    * @desc Returns either a specific element of the generator,  or the entire array of data if none is specified.
     557    */
     558    function get_generator($elem = false) {
     559        if ($elem === false) {
     560            return $this->generator;
     561        }
     562        else {
     563            if (in_array($elem,  array_keys($this->generator))) {
     564                return $this->generator[$elem];
     565            }
     566            else {
     567                return false;
     568            }
     569        }
     570    }
     571   
     572    /**
     573    * @return void
     574    * @param String $href
     575    * @param String $rel
     576    * @param String $title
     577    * @param String $type
     578    * @desc Adds a link to the links array with the details as per the associative array passed in. Requires href,  rel,  title and type
     579    */
     580    function add_link($href,  $rel,  $title = '',  $type) {
     581        if (strlen($href) && strlen($rel) && strlen($type)) {
     582            $this->links[] = array('href'=>$href,  'rel'=>$rel,  'title'=>$title,  'type'=>$type);
     583        }
     584    }
     585   
     586    /**
     587    * @return Array
     588    * @param String $key
     589    * @param String $val
     590    * @desc Filters the <link>s of this AtomFeed for ones where the $key matches the $val and returns an array of them,  otherwise returns all links
     591    */
     592    function get_links($key = false,  $val = false) {
     593        // If key and val are specified,  then filter the array for entries where the specified key == the value
     594        if ($key !== false && $val !== false) {
     595            $out = array();
     596            foreach ($this->links as $link) {
     597                if ($link[$key] == $val) {
     598                    $out[] = $link;
     599                }
     600            }
     601        }
     602        else {
     603            $out = $this->links;
     604        }
     605       
     606        return $out;
     607    }
     608   
     609    /**
     610    * @return Array/FALSE
     611    * @desc Returns an array containing AtomEntries for each entry in this AtomFeed,  or FALSE if none are set
     612    */
     613    function get_entries() {
     614        if (sizeof($this->entries)) {
     615            return $this->entries;
     616        }
     617        else {
     618            return false;
     619        }
     620    }
     621   
     622    /**
     623    * @return void
     624    * @param AtomEntry $entry
     625    * @desc Add an AtomEntry object to the array of entries that make up this AtomFeed
     626    */
     627    function add_entry($entry) {
     628        if (is_object($entry)) {
     629            $this->entries[] = $entry;
     630        }
     631    }
     632   
     633    /**
     634    * @return TRUE/Int on error
     635    * @desc Initiates the AtomFeed object from a URI by loading the data,  then parsing it as XML. Returns an HTTP response code on error,  or TRUE if successful.
     636    */
     637    function init() {
     638        $ar = new AtomRequest('GET',  $this->get_endpoint(),  $this->get_auth());
     639        $code = $ar->exec();
     640       
     641        // Successfully retrieved feed,  now process it out
     642        if ($code == 200) {
     643            $this->from_xml($ar->get_response());
     644            return true;
     645        }
     646        // Not a clean result,  return the error
     647        else {
     648            $this->err_no = ATOMAPI_FEED_INIT_FAILED;
     649            return $code;
     650        }
     651    }
     652   
     653    /**
     654    * @return Array/FALSE
     655    * @param String $str
     656    * @desc Parses an XML/HTML string and returns an associative array containing key/value pairs
     657    */
     658    function extract_attribs($str) {
     659        $out = array();
     660        if (preg_match_all('/([^ =]+)="([^"]*)"/si',  $str,  $attribs)) {
     661            foreach ($attribs[1] as $c=>$key) {
     662                $out[$key] = $attribs[2][$c];
     663            }
     664        }
     665        return $out;
     666    }
     667   
     668    /**
     669    * @return void
     670    * @param String $xml
     671    * @desc Populates the variables of the AtomFeed,  based on a complete XML representation of it.
     672    */
     673    function from_xml($xml) {
     674        $orig_xml = $xml;
     675       
     676        // Strip down the XML to just the part we want to work with
     677        if (preg_match('/(<feed.*)<entry/sUi',  $xml,  $feed_xml)) {
     678            $xml = $feed_xml[1];
     679        }
     680       
     681        // ATOM FEED VERSION
     682        if (preg_match('/<feed[^>]+version="([^"]*)"/is',  $xml,  $ver)) {
     683            $this->set_version($ver[1]);
     684        }
     685       
     686        // TITLE
     687        if (preg_match_all('/<title([^>]*)>(.*)<\/title>/sUi',  $xml,  $title)) {
     688            $title_attribs = $this->extract_attribs($title[1][0]);
     689            $title = array('title'=>$title[2][0]);
     690            $title = array_merge_recursive($title,  $title_attribs);
     691            $this->set_title($title);
     692        }
     693       
     694        // TAGLINE
     695        if (preg_match_all('/<tagline([^>]*)>(.*)<\/tagline>/sUi',  $xml,  $tagline)) {
     696            $tagline_attribs = $this->extract_attribs($tagline[1][0]);
     697            $tagline = array('tagline'=>$tagline[2][0]);
     698            $tagline = array_merge_recursive($tagline,  $tagline_attribs);
     699            $this->set_tagline($tagline);
     700        }
     701       
     702        // ID
     703        if (preg_match('/<id>([^<]*)<\/id>/is',  $xml,  $id)) {
     704            $this->set_id($id[1]);
     705        }
     706       
     707        // INFO
     708        if (preg_match('/<info([^>]+)>(.*)<\/info>/is',  $xml,  $info)) {
     709            $info_attribs = $this->extract_attribs($info[1]);
     710            $info = array('info'=>$info[2]);
     711            $info = array_merge_recursive($info,  $info_attribs);
     712            $this->set_info($info);
     713        }
     714       
     715        // MODIFIED
     716        if (preg_match('/<modified>([^<]*)<\/modified>/is',  $xml,  $modified)) {
     717            $this->set_modified($modified[1]);
     718        }
     719       
     720        // GENERATOR
     721        if (preg_match_all('/<generator([^>]*)>(.*)<\/generator>/sUi',  $xml,  $generator)) {
     722            $generator_attribs = $this->extract_attribs($generator[1][0]);
     723            $generator = array('generator'=>$generator[2][0]);
     724            $generator = array_merge_recursive($generator,  $generator_attribs);
     725            $this->set_generator($generator);
     726        }
     727       
     728        // LINKS
     729        if (preg_match_all('/<link([^>]+)>/Ui',  $xml,  $links)) {
     730            foreach ($links[1] as $link) {
     731                $link = $this->extract_attribs($link);
     732                $this->add_link($link['href'],  $link['rel'],  $link['title'],  $link['type']);
     733            }
     734        }
     735       
     736        // Handle all of the entries,  creating AtomEntry objects and linking them
     737        preg_match_all('/(<entry[^>]*>.*<\/entry>)/sUi',  $orig_xml,  $entries_raw);
     738        if (strlen($entries_raw[0][0])) {
     739            foreach ($entries_raw[1] as $e=>$entry) {
     740                $ae = new AtomEntry();
     741                $ae->from_xml($entry);
     742                if ($ae) {
     743                    $this->add_entry($ae);
     744                }
     745            }
     746        }
     747    }
     748   
     749    /**
     750    * @return String
     751    * @desc Returns an XML/String representation of the entire AtomFeed,  including header values and all AtomEntries found in $this->entries.
     752    */
     753    function to_xml() {
     754        $xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n\n";
     755       
     756        $xml .= '<feed';
     757       
     758        // VERSION
     759        $ver = $this->get_version();
     760        if ($ver) {
     761            $xml .= ' version="' . $ver . '"';
     762        }
     763       
     764        $xml .= ' xmlns="http://purl.org/atom/ns#">' . "\n";
     765       
     766        // LINKS
     767        $links = $this->get_links();
     768        if (sizeof($links)) {
     769            foreach ($links as $link) {
     770                $xml .= '<link';
     771                foreach ($link as $attrib=>$val) {
     772                     if (strlen($attrib) && strlen($val)) {
     773                         $xml .= ' ' . $attrib . '="' . utf8_encode($val) . '"';
     774                     }
     775                }
     776                $xml .= '/>' . "\n";
     777            }
     778        }
     779       
     780        // TITLE
     781        $title = $this->get_title();
     782        if (sizeof($title)) {
     783            $xml .= '<title';
     784            foreach ($title as $key=>$val) {
     785                if ($key != 'title') {
     786                    $xml .= ' ' . $key . '="' . $val . '"';
     787                }
     788            }
     789            $xml .= '>' . utf8_encode($title['title']) . '</title>' . "\n";
     790        }
     791       
     792        // TAGLINE
     793        $tag = $this->get_tagline();
     794        if (sizeof($tag)) {
     795            $xml .= '<tagline';
     796            foreach ($tag as $key=>$val) {
     797                if ($key != 'tagline') {
     798                    $xml .= ' ' . $key . '="' . $val . '"';
     799                }
     800            }
     801            $xml .= '>' . utf8_encode($tag['tagline']) . '</tagline>' . "\n";
     802        }
     803       
     804        // ID
     805        $id = $this->get_id();
     806        if ($id) {
     807            $xml .= '<id>' . $id . '</id>' . "\n";
     808        }
     809       
     810        // MODIFIED
     811        $mod = $this->get_modified();
     812        if ($mod) {
     813            $xml .= '<modified>' . $mod . '</modified>' . "\n";
     814        }
     815       
     816       
     817        // GENERATOR
     818        $gen = $this->get_generator();
     819        if (is_array($gen) && sizeof($gen)) {
     820            $xml .= '<generator url="' . $gen['url'] . '" version="' . $gen['version'] . '">' . $gen['generator'] . '</generator>' . "\n";
     821        }
     822       
     823        // INFO
     824        $info = $this->get_info();
     825        if (sizeof($info)) {
     826            $xml .= '<info';
     827            foreach ($info as $key=>$val) {
     828                if ($key != 'info') {
     829                    $xml .= ' ' . $key . '="' . $val . '"';
     830                }
     831            }
     832            $xml .= '>' . utf8_encode($info['info']) . '</info>' . "\n";
     833        }
     834       
     835        // ENTRIES
     836        $entries = $this->get_entries();
     837        if ($entries) {
     838            foreach ($entries as $entry) {
     839                $xml .= $entry->to_xml('FEED');
     840            }
     841        }
     842       
     843        $xml .= '</feed>';
     844       
     845        return $xml;
     846    }
     847   
     848    /**
     849    * @return Int/FALSE
     850    * @desc Returns the last error registered in this class,  or FALSE if none
     851    */
     852    function error() {
     853        if (isset($this->err_no) && is_int($this->err_no)) {
     854            return $this->err_no;
     855        }
     856        else {
     857            return false;
     858        }
     859    }   
     860}
     861
     862
     863
     864class AtomEntry {
     865    var $links = array();
     866    var $title = array();
     867    var $content = array();
     868    var $author = array();
     869    var $generator = array();
     870    var $issued;
     871    var $modified;
     872    var $created;
     873    var $id;
     874    var $err_no;
     875   
     876    /**
     877    * @return AtomEntry
     878    * @param Array $title
     879    * @param Array $content
     880    * @param Array $author
     881    * @param String $issued
     882    * @param String $modified
     883    * @param String $created
     884    * @desc Creates an AtomEntry,  with optional initial values.
     885    */
     886    function AtomEntry($title = array(),  $content = array()) {
     887        $this->set_title($title);
     888        $this->set_content($content);
     889       
     890        // Default timezone is that of the current server (for dates)
     891        $this->tz = date('O');
     892    }
     893   
     894    /**
     895    * @return void
     896    * @param Array $title
     897    * @desc Sets the title details for the Entry,  including some defaults if ommitted. FORMAT: array('title'=>$title);
     898    */
     899    function set_title($title) {
     900        if (is_array($title)) {
     901            foreach ($title as $key=>$val) {
     902                if (strlen($key) && strlen($val)) {
     903                    $this->title[$key] = $val;
     904                }
     905            }
     906        }
     907        else if (is_string($title)) {
     908            $this->title['title'] = $title;
     909        }
     910    }
     911   
     912    /**
     913    * @return String/Array/FALSE
     914    * @param String $elem
     915    * @desc Returns either the requested value,  or all details of the $title
     916    */
     917    function get_title($elem = false) {
     918        if ($elem === false) {
     919            return $this->title;
     920        }
     921        else {
     922            if (in_array($elem,  array_keys($this->title))) {
     923                return $this->title[$elem];
     924            }
     925            else {
     926                return false;
     927            }
     928        }
     929    }
     930   
     931    /**
     932    * @return void
     933    * @param Array $content
     934    * @desc Sets the content details for the Entry,  including some defaults if ommitted. FORMAT: array('content'=>$content); Also removes default <DIV> from Blogger entries to avoid PUT problems
     935    */
     936    function set_content($content) {
     937        if (is_array($content)) {
     938            foreach ($content as $key=>$val) {
     939                $this->content[$key] = $val;
     940            }
     941        }
     942        else if (is_string($content)) {
     943            $this->content['content'] = $content;
     944        }
     945    }
     946   
     947    /**
     948    * @return Array/String/FALSE
     949    * @param String $elem
     950    * @desc Returns the contents of this entry - including attributes of the content element
     951    */
     952    function get_content($elem = false) {
     953        if ($elem === false) {
     954            return $this->content;
     955        }
     956        else {
     957            if (in_array($elem,  array_keys($this->content))) {
     958                return $this->content[$elem];
     959            }
     960            else {
     961                return false;
     962            }
     963        }
     964    }
     965   
     966    /**
     967    * @return void
     968    * @param Array $author
     969    * @desc Set all details relating to the author (name,  email,  url)
     970    */
     971    function set_author($author) {
     972        if (is_array($author)) {
     973            foreach ($author as $key=>$val) {
     974                $this->author[$key] = $val;
     975            }
     976        }
     977        else if (is_string($author)) {
     978            $this->author['name'] = $author;
     979        }
     980    }
     981   
     982    /**
     983    * @return Array/String/FALSE
     984    * @param String $elem
     985    * @desc Returns author details - either item requested,  or all.
     986    */
     987    function get_author($elem = false) {
     988        if ($elem === false) {
     989            return $this->author;
     990        }
     991        else {
     992            if (in_array($elem,  array_keys($this->author))) {
     993                return $this->author[$elem];
     994            }
     995            else {
     996                return false;
     997            }
     998        }
     999    }
     1000   
     1001    /**
     1002    * @return void
     1003    * @param String $date
     1004    * @desc Set the <issued> date,  after sanitization
     1005    */
     1006    function set_issued($date) {
     1007        $this->issued = $this->sanitize_date($date);
     1008    }
     1009   
     1010    /**
     1011    * @return String
     1012    * @desc Returns the <issued> date for the entry
     1013    */
     1014    function get_issued() {
     1015        if (isset($this->issued)) {
     1016            return $this->issued;
     1017        }
     1018        else {
     1019            return false;
     1020        }
     1021    }
     1022   
     1023    /**
     1024    * @return void
     1025    * @param String $date
     1026    * @desc Set the <modified> date,  after sanitization
     1027    */
     1028    function set_modified($date) {
     1029        $this->modified = $this->sanitize_date($date);
     1030    }
     1031   
     1032    /**
     1033    * @return String/FALSE
     1034    * @desc Returns the <modified> date for the entry
     1035    */
     1036    function get_modified() {
     1037        if (isset($this->modified)) {
     1038            return $this->modified;
     1039        }
     1040        else {
     1041            return false;
     1042        }
     1043    }
     1044   
     1045    /**
     1046    * @return void
     1047    * @param String $date
     1048    * @desc Set the <created> date,  after sanitization
     1049    */
     1050    function set_created($date) {
     1051        $this->created = $this->sanitize_date($date);
     1052    }
     1053   
     1054    /**
     1055    * @return String/FALSE
     1056    * @desc Returns the <created> date for the entry
     1057    */
     1058    function get_created() {
     1059        if (isset($this->created)) {
     1060            return $this->created;
     1061        }
     1062        else {
     1063            return false;
     1064        }
     1065    }
     1066   
     1067    /**
     1068    * @return void
     1069    * @param String $id
     1070    * @desc Set the unique <id> for the Entry
     1071    */
     1072    function set_id($id) {
     1073        $this->id = $id;
     1074    }
     1075   
     1076    /**
     1077    * @return String/FALSE
     1078    * @desc Returns the unique <id> for this Entry
     1079    */
     1080    function get_id() {
     1081        if (isset($this->id)) {
     1082            return $this->id;
     1083        }
     1084        else {
     1085            return false;
     1086        }
     1087    }
     1088   
     1089    /**
     1090    * @return void
     1091    * @param String $href
     1092    * @param String $rel
     1093    * @param String $title
     1094    * @param String $type
     1095    * @desc Adds a <link> to the array for this Entry. href,  type,  title and rel are all required
     1096    */
     1097    function add_link($href,  $rel,  $title,  $type) {
     1098        if (strlen($href) && strlen($rel) && strlen($type)) {
     1099            $this->links[] = array('href'=>$href,  'rel'=>$rel,  'title'=>$title,  'type'=>$type);
     1100        }
     1101    }
     1102   
     1103    /**
     1104    * @return Array
     1105    * @param String $key
     1106    * @param String $val
     1107    * @desc Returns the links where $key matches $val if specified,  or all links if both ommitted
     1108    */
     1109    function get_links($key = false,  $val = false) {
     1110        // If key and val are specified,  then filter the array for entries where the specified key == the value
     1111        if ($key !== false && $val !== false) {
     1112            $out = array();
     1113            foreach ($this->links as $link) {
     1114                if ($link[$key] == $val) {
     1115                    $out[] = $link;
     1116                }
     1117            }
     1118        }
     1119        else {
     1120            $out = $this->links;
     1121        }
     1122       
     1123        return $out;
     1124    }
     1125   
     1126    function sanitize_date($date) {
     1127        if ($date == '' || $date === false) {
     1128            return false;
     1129        }
     1130        else if (!stristr($date,  'T') || !stristr($date,  'Z')) {
     1131            if (preg_match('/(\d{4})-(\d{2})-(\d{2}).(\d{2}):(\d{2}):(\d{2})-(\d{2}):(\d{2})/',  $date,  $parts)) {
     1132                return $date;
     1133            }
     1134            else if (preg_match('/(\d{4})-(\d{2})-(\d{2}).(\d{2}):(\d{2}):(\d{2})/',  $date,  $parts)) {
     1135                return $date;
     1136            }
     1137            return false;
     1138        }
     1139        else {
     1140            return $date;
     1141        }
     1142    }
     1143   
     1144    function atom_date($date,  $tz) {
     1145        if ($date == '' || $date === false) {
     1146            preg_match('/(\+|-)(\d{2}):(\d{2})/',  $tz,  $tz_parts);
     1147            return gmdate('Y-m-d\TH:i:s\Z',  mktime(eval("date('H') " . $tz_parts[1] . " " . $tz_parts[2] . ";"),  eval("date('i') " . $tz_parts[1] . " " . $tz_parts[3] . ";"),  date('s'),  date('m'),  date('d'),  date('Y')));
     1148        }
     1149        else {
     1150            return $date;
     1151        }
     1152    }
     1153   
     1154    /**
     1155    * @return Array
     1156    * @param String $str
     1157    * @desc Parses an XML/HTML string and returns an associative array containing key/value pairs
     1158    */
     1159    function extract_attribs($str) {
     1160        $out = array();
     1161        if (preg_match_all('/([^ =]+)="([^"]*)"/si',  $str,  $attribs)) {
     1162            foreach ($attribs[1] as $c=>$key) {
     1163                $out[$key] = $attribs[2][$c];
     1164            }
     1165        }
     1166               
     1167        return $out;
     1168    }
     1169   
     1170    /**
     1171    * @return void
     1172    * @param String $xml
     1173    * @desc Builds out the details of the object by parsing an XML <entry>
     1174    */
     1175    function from_xml($xml) {
     1176        // Parse an XML <entry> into its elements and then set them to this Object
     1177        // LINKS
     1178        if (preg_match_all('/<link([^>]+)>/Ui',  $xml,  $links)) {
     1179            foreach ($links[1] as $link) {
     1180                $link = $this->extract_attribs($link);
     1181                $this->add_link($link['href'],  $link['rel'],  $link['title'],  $link['type']);
     1182            }
     1183        }
     1184       
     1185        // AUTHOR----------------------------------------------------------------------------------------------*
     1186        // Needs to handle the other elements properly - but need a sample to work from!
     1187        if (preg_match('/<author>\s*(<([^>]+)>(.*)<\/\2>)*\s*<\/author>/sUi',  $xml,  $author)) {
     1188            $this->set_author(array($author[2]=>$author[3]));
     1189        }
     1190       
     1191        // DATES
     1192        if (preg_match('/<issued>([^>]*)<\/issued>/i',  $xml,  $date)) {
     1193            $this->set_issued($date[1]);
     1194        }
     1195       
     1196        if (preg_match('/<modified>([^>]*)<\/modified>/i',  $xml,  $date)) {
     1197            $this->set_modified($date[1]);
     1198        }
     1199       
     1200        if (preg_match('/<created>([^>]*)<\/created>/i',  $xml,  $date)) {
     1201            $this->set_created($date[1]);
     1202        }
     1203       
     1204        // ID
     1205        if (preg_match('/<id>([^>]*)<\/id>/i',  $xml,  $id)) {
     1206            $this->set_id($id[1]);
     1207        }
     1208       
     1209        // TITLE
     1210        if (preg_match_all('/<title([^>]*)>(.*)<\/title>/sUi',  $xml,  $title)) {
     1211            $title_attribs = $this->extract_attribs($title[1][0]);
     1212            $title = array('title'=>$title[2][0]);
     1213            $title = array_merge_recursive($title,  $title_attribs);
     1214            $this->set_title($title);
     1215        }
     1216       
     1217        // CONTENT
     1218        if (preg_match_all('/<content([^>]*)>(.*)<\/content>/sUi',  $xml,  $content)) {
     1219            $content_attribs = $this->extract_attribs($content[1][0]);
     1220            $content = array('content'=>$content[2][0]);
     1221            $content = array_merge_recursive($content,  $content_attribs);
     1222            $this->set_content($content);
     1223        }
     1224    }
     1225   
     1226    /**
     1227    * @return String
     1228    * @param String $purpose
     1229    * @desc Creates an XML representation of this Entry. $purpose is [PUT|POST|FEED] and defines what you're going to do with this XML. $purpose modifies the elements included according to the Atom spec.
     1230    */
     1231    function to_xml($purpose = 'POST') {
     1232        $xml = '';
     1233       
     1234        // XML prolog if PUT or POST
     1235        if ($purpose == 'PUT' || $purpose == 'POST') {
     1236            $xml .= '<?xml version="1.0" encoding="utf-8"?>' . "\n";
     1237        }
     1238
     1239        // Start XML packet
     1240        $xml .= '<entry xmlns="http://purl.org/atom/ns#">' . "\n";
     1241       
     1242        // GENERATOR
     1243        if ($purpose == 'PUT' || $purpose == 'POST') {
     1244            $xml .= '<generator version="0.1" url="http://www.dentedreality.com.au/phpatomapi/">Dented Reality PHP Atom API</generator>' . "\n";
     1245        }
     1246       
     1247        // LINKS
     1248        $links = $this->get_links();
     1249        if (sizeof($links)) {
     1250            foreach ($links as $link) {
     1251                $xml .= '<link';
     1252                foreach ($link as $attrib=>$val) {
     1253                     if (strlen($attrib) && strlen($val)) {
     1254                         $xml .= ' ' . $attrib . '="' . utf8_encode($val) . '"';
     1255                     }
     1256                }
     1257                $xml .= '/>' . "\n";
     1258            }
     1259        }
     1260       
     1261        // AUTHOR
     1262        $author = $this->get_author();
     1263        if (sizeof($author)) {
     1264            $xml .= '<author>';
     1265            foreach ($author as $key=>$val) {
     1266                if (strlen($key) && strlen($val)) {
     1267                    $xml .= '<' . $key .'>' . utf8_encode($val) . '</' . $key . '>' . "\n";
     1268                }
     1269            }
     1270            $xml .= '</author>' . "\n";
     1271        }
     1272       
     1273        // DATES (issued,  created,  modified)
     1274        if (strlen($this->get_issued())) {
     1275            $xml .= '<issued>' . $this->get_issued() . '</issued>' . "\n";
     1276        }
     1277       
     1278        // Modified and Id,  only if not creating a POST entry
     1279        if ($purpose != 'POST' && $purpose != 'PUT') {
     1280            if (strlen($this->get_created())) {
     1281                $xml .= '<created>' . $this->get_created() . '</created>' . "\n";
     1282            }
     1283            if (strlen($this->get_modified())) {
     1284                $xml .= '<modified>' . $this->get_modified() . '</modified>' . "\n";
     1285            }
     1286           
     1287            // ID element
     1288            $xml .= '<id>' . $this->id . '</id>' . "\n";
     1289        }
     1290       
     1291        // TITLE
     1292        $title = $this->get_title();
     1293        if (is_array($title)) {
     1294            $xml .= '<title';
     1295            foreach ($title as $key=>$val) {
     1296                if ($key != 'title') {
     1297                    $xml .= ' ' . $key . '="' . $val . '"';
     1298                }
     1299            }
     1300            $xml .= '>' . utf8_encode($title['title']) . '</title>' . "\n";
     1301        }
     1302       
     1303        // CONTENT
     1304        $content = $this->get_content();
     1305        if (is_array($content)) {
     1306            $xml .= '<content';
     1307            foreach ($content as $key=>$val) {
     1308                if ($key != 'content') {
     1309                    $xml .= ' ' . $key . '="' . $val . '"';
     1310                }
     1311            }
     1312            $xml .= '>' . utf8_encode($content['content']) . '</content>' . "\n";
     1313        }
     1314       
     1315        $xml .= '</entry>' . "\n\n";
     1316       
     1317        return $xml;
     1318    }
     1319   
     1320    /**
     1321    * @return Int/FALSE
     1322    * @desc Returns the last error registered in this class,  or FALSE if none
     1323    */
     1324    function error() {
     1325        if (isset($this->err_no) && is_int($this->err_no)) {
     1326            return $this->err_no;
     1327        }
     1328        else {
     1329            return false;
     1330        }
     1331    }
     1332}
     1333
     1334
     1335
     1336class AtomRequest {
     1337    var $method;
     1338    var $uri;
     1339    var $auth;
     1340    var $payload;
     1341    var $response;
     1342    var $httpcode;
     1343    var $err_no;
     1344   
     1345    /**
     1346    * @return AtomRequest
     1347    * @param String $method
     1348    * @param String $uri
     1349    * @param AuthObject $auth
     1350    * @param String $payload
     1351    * @desc Creates an object with functions for performing standard Atom requests. $method should be [GET|PUT|POST|DELETE],  $uri is the endpoint/uri to execute the request against,  $auth is an authentication object (BasicAuth or WSSE) and $payload is the XML of the request,  if required (POST|PUT)
     1352    */
     1353    function AtomRequest($method,  $uri,  $auth,  $payload = false) {
     1354        $this->set_method($method);
     1355        $this->set_uri($uri);
     1356        $this->set_auth($auth);
     1357        $this->set_payload($payload);
     1358       
     1359        if ($this->error()) {
     1360            return false;
     1361        }
     1362    }
     1363   
     1364    /**
     1365    * @return void
     1366    * @param String $uri
     1367    * @desc Set the URI to execute this AtomRequest against. Should be a fully-qualified URL and start with 'http'
     1368    */
     1369    function set_uri($uri) {
     1370        if (substr($uri,  0,  4) == 'http') {
     1371            $this->uri = $uri;
     1372        }
     1373        else {
     1374            $this->err_no = ATOMAPI_ENDPOINT_INVALID;
     1375        }
     1376    }
     1377   
     1378    /**
     1379    * @return String/FALSE on error
     1380    * @desc Returns the URI stored for this AtomRequest to operate against,  or FALSE if not set yet
     1381    */
     1382    function get_uri() {
     1383        if (isset($this->uri) && strlen($this->uri)) {
     1384            return $this->uri;
     1385        }
     1386        else {
     1387            return false;
     1388        }
     1389    }
     1390   
     1391    /**
     1392    * @return void
     1393    * @param AuthObj $auth
     1394    * @desc Set the authentication object to use for this AtomRequest
     1395    */
     1396    function set_auth($auth) {
     1397        if (is_object($auth)) {
     1398            $this->auth = $auth;
     1399        }
     1400        else {
     1401            $this->err_no = ATOMAPI_AUTHTYPE_INVALID;
     1402        }
     1403    }
     1404   
     1405    /**
     1406    * @return void
     1407    * @param String $method
     1408    * @desc Sets the method of this AtomRequest. Allowed values are [GET|PUT|POST|DELETE]
     1409    */
     1410    function set_method($method) {
     1411        if (in_array($method,  array('GET',  'PUT',  'POST',  'DELETE'))) {
     1412            $this->method = $method;
     1413        }
     1414        else {
     1415            $this->err_no = ATOMAPI_METHOD_INVALID;
     1416        }
     1417    }
     1418   
     1419    /**
     1420    * @return String/FALSE on error
     1421    * @desc Returns the current $method set for this AtomRequest. Should be [GET|PUT|POST|DELETE]. Returns FALSE if it is unset.
     1422    */
     1423    function get_method() {
     1424        if (isset($this->method) && strlen($this->method)) {
     1425            return $this->method;
     1426        }
     1427        else {
     1428            return false;
     1429        }
     1430    }
     1431   
     1432    /**
     1433    * @return void
     1434    * @param String $xml
     1435    * @desc Set the payload (body) of the AtomRequest. Should be an XML string,  and is only required for POST and PUT operations
     1436    */
     1437    function set_payload($xml) {
     1438        if (is_string($xml)) {
     1439            $this->payload = $xml;
     1440        }
     1441        else {
     1442            $this->err_no = ATOMAPI_PAYLOAD_INVALID;
     1443        }
     1444    }
     1445   
     1446    /**
     1447    * @return String/FALSE on error
     1448    * @desc Returns the current stored payload,  which should be an XML package,  or FALSE if it is not set yet.
     1449    */
     1450    function get_payload() {
     1451        if (isset($this->payload)) {
     1452            return $this->payload;
     1453        }
     1454        else {
     1455            return false;
     1456        }
     1457    }
     1458   
     1459    /**
     1460    * @return Int/FALSE on error
     1461    * @desc Executes the AtomRequest on the URI specified in the object. Returns the HTTP response code,  or FALSE on a serious error
     1462    */
     1463    function exec() {
     1464        // Confirm minimum requirements (method,  uri)
     1465        if (!$this->get_uri() || !$this->get_method()) {
     1466            $this->err_no = ATOMAPI_REQUEST_INVALID;
     1467            return;
     1468        }
     1469       
     1470        // Common cURL configuration directives
     1471        $ch = curl_init();
     1472        curl_setopt($ch,  CURLOPT_URL,  $this->get_uri());
     1473        curl_setopt($ch,  CURLOPT_USERAGENT,  "Dented Reality PHP Atom API Library v0.1");
     1474        curl_setopt($ch,  CURLOPT_SSL_VERIFYPEER,  0); // Don't stress about SSL validity
     1475        curl_setopt($ch,  CURLOPT_RETURNTRANSFER,  1); // Return the response,  don't output it
     1476
     1477                // Rebuild the nonce each request
     1478                $this->auth->rebuild();
     1479       
     1480        // Handle configuration of specific options for certain request types
     1481        switch ($this->get_method()) {
     1482            case 'POST' :
     1483                // Authentication header and content-type (required for Blogger.com)
     1484                $headers = $this->auth->get_header(true);
     1485                $headers[] = 'Content-type: application/xml';
     1486                curl_setopt($ch,  CURLOPT_HTTPHEADER,  $headers);
     1487               
     1488                // Configuring post contents (should be an XML payload)
     1489                curl_setopt($ch,  CURLOPT_POST,  1);
     1490                curl_setopt($ch,  CURLOPT_POSTFIELDSIZE,  strlen($this->get_payload()));
     1491                curl_setopt($ch,  CURLOPT_POSTFIELDS,  $this->get_payload());
     1492                break;
     1493            case 'PUT' :
     1494                // PUT requires the payload to be written to file,  and passed as a file pointer
     1495                $put = tmpfile();
     1496                fputs($put,  $this->get_payload());
     1497                rewind($put);
     1498               
     1499                // Authentication headers and content-type (for Blogger.com)
     1500                $headers = $this->auth->get_header(true);
     1501                $headers[] = 'Content-type: application/xml';
     1502                curl_setopt($ch,  CURLOPT_HTTPHEADER,  $headers);
     1503               
     1504                // Performing a PUT operation,  requires file pointer and size of payload
     1505                curl_setopt($ch,  CURLOPT_PUT,  1);
     1506                curl_setopt($ch,  CURLOPT_INFILE,  $put);
     1507                curl_setopt($ch,  CURLOPT_INFILESIZE,  strlen($this->get_payload()));
     1508                break;
     1509            case 'DELETE' :
     1510                // Simple DELETE request (authenticated)
     1511                curl_setopt($ch,  CURLOPT_CUSTOMREQUEST,  'DELETE');
     1512                curl_setopt($ch,  CURLOPT_HTTPHEADER,  $this->auth->get_header(true));
     1513                break;
     1514            case 'GET' :
     1515            default :
     1516                // Straight GET,  with authentication headers
     1517                curl_setopt($ch,  CURLOPT_HTTPHEADER,  $this->auth->get_header(true));
     1518        }
     1519       
     1520        // Execute cURL session and handle results
     1521        $this->response = curl_exec($ch);
     1522       
     1523        if (curl_errno($ch)) {
     1524            $this->err_no = ATOMAPI_CURL_ERROR;
     1525            curl_close($ch);
     1526            return;
     1527        }
     1528        else {
     1529            // Set the HTTPcode of the response into this object,  then return it
     1530            $this->set_httpcode(curl_getinfo($ch,  CURLINFO_HTTP_CODE));
     1531            curl_close($ch);
     1532            return $this->get_httpcode();
     1533        }
     1534    }
     1535   
     1536    /**
     1537    * @return String/FALSE on error
     1538    * @desc Returns the response content from the exec()'d AtomRequest,  or FALSE if not set yet
     1539    */
     1540    function get_response() {
     1541        if (isset($this->response)) {
     1542            return $this->response;
     1543        }
     1544        else {
     1545            return false;
     1546        }
     1547    }
     1548   
     1549    /**
     1550    * @return void
     1551    * @param Int $code
     1552    * @desc Internal function,  used to update the http status code returned when the AtomRequest is exec()'d
     1553    */
     1554    function set_httpcode($code) {
     1555        if (strlen($code) && is_int($code)) {
     1556            $this->httpcode = $code;
     1557        }
     1558        else {
     1559            $this->httpcode = false;
     1560        }
     1561    }
     1562   
     1563    /**
     1564    * @return Int/FALSE on error
     1565    * @desc Returns the last stored http status code for this AtomRequest
     1566    */
     1567    function get_httpcode() {
     1568        if (isset($this->httpcode) && strlen($this->httpcode)) {
     1569            return $this->httpcode;
     1570        }
     1571        else {
     1572            return false;
     1573        }
     1574    }
     1575   
     1576    /**
     1577    * @return Int/FALSE
     1578    * @desc Returns the last error registered in this class,  or FALSE if none
     1579    */
     1580    function error() {
     1581        if (isset($this->err_no) && is_int($this->err_no)) {
     1582            return $this->err_no;
     1583        }
     1584        else {
     1585            return false;
     1586        }
     1587    }   
     1588}
     1589
     1590
     1591
     1592
     1593class BasicAuth {
     1594        var $username;
     1595        var $password;
     1596       
     1597        /**
     1598        * @return BasicAuth
     1599        * @param String $username
     1600        * @param String $password
     1601        * @desc Constructor - sets variables only
     1602        */
     1603        function BasicAuth($username, $password) {
     1604                if (strlen($username)) {
     1605                        $this->username = $username;
     1606                }
     1607               
     1608                if (strlen($password)) {
     1609                        $this->password = $password;
     1610                }
     1611        }
     1612       
     1613        /**
     1614        * @return String or Array
     1615        * @param Boolean $array TRUE to return an array suitable for cURL, FALSE/NULL for a string
     1616        * @desc Returns the HTTP headers required for sending Basic Authentication information
     1617        */
     1618        function get_header($array = false) {
     1619                // Array for use in CURLOPT_HTTPHEADER
     1620                if ($array) {
     1621                        return array("Authorization: Basic " . base64_encode($this->username . ':' . $this->password));
     1622                }
     1623                // String for manual request construction
     1624                else {
     1625                        return 'Authorization: Basic ' . base64_encode($this->username . ':' . $this->password) . "\r\n";
     1626                }
     1627        }
     1628       
     1629        /**
     1630        * @return String/FALSE
     1631        * @desc Returns username if set, or FALSE
     1632        */
     1633        function get_username() {
     1634                if (isset($this->username)) {
     1635                        return $this->username;
     1636                }
     1637                else {
     1638                        return false;
     1639                }
     1640        }
     1641       
     1642        /**
     1643        * @return String/FALSE
     1644        * @desc Returns password if set, or FALSE
     1645        */
     1646        function get_password() {
     1647                if (isset($this->password)) {
     1648                        return $this->password;
     1649                }
     1650                else {
     1651                        return false;
     1652                }
     1653        }
     1654}
     1655
     1656
     1657
     1658class WSSE {
     1659    var $username  = '';
     1660    var $password  = '';
     1661    var $profile   = 'UsernameToken';
     1662    var $timestamp = '';
     1663    var $digest    = '';
     1664    var $nonce     = '';
     1665   
     1666    /**
     1667    * @return WSSE
     1668    * @param String $username
     1669    * @param String $password
     1670    * @desc Creates a WSSE object which can give back a digest,  header string etc
     1671     */
     1672    function WSSE($username,  $password,  $profile = false) {
     1673        // If no profile,  use default
     1674        if ($profile !== false) {
     1675            $this->profile = $profile;
     1676        }
     1677       
     1678        // Set username and password
     1679        $this->set_username($username);
     1680        $this->set_password($password);
     1681       
     1682        // Calculate digest (requires nonce calculation)
     1683        $this->rebuild();
     1684       
     1685        // return compiled object
     1686        return $this;
     1687    }
     1688   
     1689    /**
     1690    * @return void
     1691    * @param String $username
     1692    * @desc Sets the username for this WSSE Object
     1693     */
     1694    function set_username($username) {
     1695        $this->username = $username;
     1696    }
     1697   
     1698    /**
     1699    * @return void
     1700    * @param String $password
     1701    * @desc Sets the password for this WSSE Object
     1702     */
     1703    function set_password($password) {
     1704        $this->password = $password;
     1705    }
     1706   
     1707    /**
     1708    * @return void
     1709    * @param String $profile
     1710    * @desc Sets the profile of the authentication method. Used in the header output. Defaults to UsernameToken.
     1711     */
     1712    function set_profile($profile) {
     1713        $this->profile = $profile;
     1714    }
     1715   
     1716    /**
     1717    * @return void
     1718    * @desc Creates a digest of the password,  using the nonce and timestamp
     1719     */
     1720    function set_digest() {
     1721        $digest = base64_encode(pack("H*",  sha1($this->get_nonce()
     1722                                . $this->get_timestamp()
     1723                                . $this->get_password())));
     1724        $this->digest = $digest;
     1725    }
     1726   
     1727    /**
     1728    * @return void
     1729    * @desc Makes an attempt at creating a randomised string for use as a nonce
     1730     */
     1731    function set_nonce() {
     1732        $mt = substr(microtime(),  2,  8);
     1733        $md5 = md5($mt);
     1734        $seed = $mt . rand(rand(100,  2000),  4000) . $md5;
     1735        $nonce = md5($seed);
     1736        $this->nonce = $nonce;
     1737    }
     1738   
     1739    /**
     1740    * @return void
     1741    * @desc Creates a timestamp for the object,  W3DTF Format
     1742    */
     1743    function set_timestamp() {
     1744        $this->timestamp = gmdate('Y-m-d\TH:i:s\Z');
     1745    }
     1746   
     1747
     1748    /**
     1749    * @return String/Array
     1750    * @param Boolean $array TRUE to return an array,  FALSE to return a string
     1751    * @desc Returns a WSSE header for use in cURL or socket requests. If $array is TRUE,  then returns an array suitable for cURL CUSTOMHEADER
     1752    */
     1753    function get_header($array = false) {
     1754        if ($array) {
     1755            return array('Authorization: WSSE profile="' . $this->get_profile() . '"',
     1756                          'X-WSSE: ' . $this->get_profile() . ' Username="' . $this->get_username() . '",  '
     1757                        . 'PasswordDigest="' . $this->digest . '",  Nonce="' . base64_encode($this->get_nonce()) . '",  '
     1758                        . 'Created="' . $this->get_timestamp() . '"');
     1759        }
     1760        else {
     1761            return 'Authorization: WSSE profile="' . $this->get_profile() . '"' . "\r\n"
     1762                    . 'X-WSSE: ' . $this->get_profile() . ' Username="' . $this->get_username() . '",  '
     1763                    . 'PasswordDigest="' . $this->digest . '",  Nonce="' . base64_encode($this->get_nonce()) . '",  '
     1764                    . 'Created="' . $this->get_timestamp() . '"' . "\r\n";
     1765        }
     1766    }
     1767   
     1768    /**
     1769    * @return String
     1770    * @desc Returns plain string containing the username
     1771     */
     1772    function get_username() {
     1773        if (isset($this->username)) {
     1774            return $this->username;
     1775        }
     1776        else {
     1777            return false;
     1778        }
     1779    }
     1780   
     1781    /**
     1782    * @return String
     1783    * @desc Returns plain string containing the password
     1784     */
     1785    function get_password() {
     1786        if (isset($this->password)) {
     1787            return $this->password;
     1788        }
     1789        else {
     1790            return false;
     1791        }
     1792    }
     1793   
     1794    /**
     1795    * @return String
     1796    * @desc Retrieve the nonce (or false if not set)
     1797     */
     1798    function get_nonce() {
     1799        if ($this->nonce != '') {
     1800            return $this->nonce;
     1801        }
     1802        else {
     1803            return false;
     1804        }
     1805    }
     1806   
     1807    /**
     1808    * @return String
     1809    * @desc Retrieve the profile (or false if not set)
     1810     */
     1811    function get_profile() {
     1812        if ($this->profile != '') {
     1813            return $this->profile;
     1814        }
     1815        else {
     1816            return false;
     1817        }
     1818    }
     1819   
     1820    /**
     1821    * @return Timestamp
     1822    * @desc Get the timestamp being used,  or false if not set
     1823     */
     1824    function get_timestamp() {
     1825        if ($this->timestamp != '') {
     1826            return $this->timestamp;
     1827        }
     1828        else {
     1829            return false;
     1830        }
     1831    }
     1832   
     1833    /**
     1834    * @return String
     1835    * @desc Retrieve the digest (or false if not set)
     1836     */
     1837    function get_digest() {
     1838        if ($this->digest != '') {
     1839            return $this->digest;
     1840        }
     1841        else {
     1842            return false;
     1843        }
     1844    }
     1845   
     1846    /**
     1847    * @return void
     1848    * @desc Recreate dynamic components,  effectively does everything
     1849    *       required to rebuild the digest
     1850     */
     1851    function rebuild() {
     1852        $this->set_timestamp();
     1853        $this->set_nonce();
     1854        $this->set_digest();
     1855    }
     1856}
     1857
     1858?>
     1859 No newline at end of file