<?php

/**
 * Danltn | http://danltn.com/
 * MSN: msn@danltn.com
 * Email: daniel@neville.tk
 * Started: 19/6/2008 16:01 (UK) 
 * Tested: No
 * PHP 4/5: 5
 * No warranty is given to code used
 */

/**
 * PingFM_Wrapper
 * 
 * @package   PingFM_Wrappers
 * @url       http://danltn.com
 * @desc      Package which allows easy interaction with the Ping.fm service
 * @author    Daniel Neville
 * @copyright 2008
 * @version   0.2b
 * @access    public
 * @license   GPL
 */
class PingFM_Wrapper
{
    
/** Your API */
    
public $dev_api;
    
/** Users API */
    
public $user_api;

    
/** Actual reply code */
    
public $reply;
    
/** XML parsed with SimpleXML */
    
public $xml;
    
/** Details array returned, this is what you should use */
    
public $details;

    
/** Acceptable methods for API usage **/
    
static public $methods = array( 'validate''services''triggers''latest''tpost''post' );
    
/** Acceptable methods to use if we're using the post method **/
    
static public $post_methods = array( 'blog''microblog''status' );

    
/** All the URLs for different services **/
    
const VALIDATE_URL 'http://api.ping.fm/v1/user.validate';
    const 
SERVICES_URL 'http://api.ping.fm/v1/user.services';
    const 
TRIGGERS_URL 'http://api.ping.fm/v1/user.triggers';
    const 
LATEST_URL 'http://api.ping.fm/v1/user.latest';
    const 
TPOST_URL 'http://api.ping.fm/v1/user.tpost';
    const 
POST_URL 'http://api.ping.fm/v1/user.post';

    
/**
     * PingFM_Wrapper::__construct()
     * 
     * @param  mixed  $dev_api   Your developer API
     * @param  mixed  $user_api  The users API
     * @param  bool   $check     Should we run checks to make sure all the extensions are loaded?
     * @return void
     */
    
public function __construct$dev_api null$user_api null$check true )
    {
        
/** Construct class, runs on initialization **/
        
if ( $check )
        {
            
/** If the check parametre is set **/
            
if ( !extension_loaded('curl'/** Is the cURL extension loaded? **/ )
            {
                throw new 
Exception'You don\'t have cURL loaded.' );
                
/** If not, throw an exception **/
            
}
            if ( !
extension_loaded('SimpleXML'/** Is the SimpleXML extension loaded? **/ )
            {
                throw new 
Exception'You don\'t have the SimpleXML lib loaded.' );
                
/** If not, throw an exception **/
            
}
        }
        
/** if $dev_api and $user_api aren't the default values of null, set them in the class. **/
        
if ( !is_null($dev_api) ) $this->dev_api $dev_api;
        if ( !
is_null($user_api) ) $this->user_api $user_api;
    }

    
/**
     * PingFM_Wrapper::act()
     * 
     * @param string  $method             What we're carrying, this is taken care of with the PingFM_Functions extension
     * @param mixed   $extra_post_fields  Any extra fields required, these are taken care of with the PingFM_Functions extension
     * @return bool   $status               Whether the action completed successfully or not
     */
    
public function act$method ''$extra_post_fields = array( /** You'll need to fill this in if you're using a method that needs more that just the API codes */ ) )
    {
        if ( !
in_array($methodself::$methods) ) throw new Exception'The selected method: ' $method ' is not supported by Ping.fm' );
        
/** Throw an error if the method isn't supported */
        
$extra_post_fields $this->_check_fields$method$extra_post_fields );
        
/** Check all the fields are correct, if they're not fix them if we can. Otherwise we'll throw an exception (see method _check_fields() for more details.) */
        
$post array_merge( array('api_key' => $this->dev_api'user_app_key' => $this->user_api), $extra_post_fields );
        
/** Merge the APIs with any extra post fields */
        
$ch curl_init();
        
/** Start a cURL instance called $ch */
        
curl_setopt$chCURLOPT_POSTtrue );
        
/** We will be sending POST fields, tell cURL that we are */
        
curl_setopt$chCURLOPT_POSTFIELDS$post );
        
/** Attach the POST fields themselves */
        
curl_setopt$chCURLOPT_RETURNTRANSFERtrue );
        
/** Don't output to the browser */
        
curl_setopt$chCURLOPT_URLconstant('self::' strtoupper($method) . '_URL') );
        
/** What URL should we post to? */
        
$this->reply curl_exec$ch );
        
/** Get the raw reply, set it to $this->reply for access */
        
curl_close$ch );
        
/** Free up the resources */
        
$this->_parse_reply$method );
        
/** Parse it, see method below */
        
return ( $this->xml->attributes()->status == 'OK' or $this->xml->attributes()->status == '' ) ? true false;
        
/** Sometimes it glitches and a status isn't returned, if this happens, or it equals 'OK', return true. Else return false. */
    
}

    
/**
     * PingFM_Wrapper::_parse_reply()
     * 
     * @param   string $method  What method are we parsing for.
     * @return  bool   true     Always true upon success
     */
    
protected function _parse_reply$method )
    {
        if ( !
$this->reply ) throw new Exception'Raw reply could not be found.' );
        
$this->xml $xml simplexml_load_string$this->reply );
        
/** Parse the raw reply, set it to XML for access from within this function, and to $this->xml if the programmer wants to access it later */
        
if ( $xml->attributes()->status == 'FAIL' ) throw new Exception$this->xml->message );
        
$details['transaction'] = ( string )$xml->transaction;
        switch ( 
$method ):
            case 
'validate':
            case 
'post':
            case 
'tpost':
                
/** Nothing to do! - All it returns is the method and the transaction ID. */
                
break;
                
/** End validate, post, tpost */
            
case 'services':
                
$details['number_of_services'] = ( int )count$xml->services->service );
                if ( 
$details['number_of_services'] > )
                {
                    foreach ( 
$xml->services->service as $service )
                    {
                        
$name = ( string )$service->attributes()->id;
                        
$details['services'][$name]['name'] = ( string )$service->attributes()->name;
                        
$method = ( string )$service->methods;
                        
$methods = ( strstr($method',') ) ? explode','$method ) : $method;
                        if ( 
is_array($methods) )
                        {
                            foreach ( 
$methods as $method )
                            {
                                
$details['services'][$name]['methods'][] = ( string )$method;
                            }
                        }
                        else
                            if ( 
is_string($methods) )
                            {
                                
$details['services'][$name]['methods'][] = ( string )$method;
                            }
                        
$details['services'][$name]['number_of_methods'] = count$details['services'][$name]['methods'] );
                    }
                }
                break;
                
/** End services */
            
case 'triggers':
                
$details['number_of_triggers'] = ( int )count$xml->triggers->trigger );
                if ( 
$details['number_of_triggers'] > )
                {
                    foreach ( 
$xml->triggers->trigger as $trigger )
                    {
                        
$name = ( string )$trigger->attributes()->id;
                        
$details['triggers'][$name]['name'] = ( string )$trigger->attributes()->id;
                        
$details['triggers'][$name]['method'] = ( string )$trigger->attributes()->method;
                        
$details['triggers'][$name]['number_of_affected_services'] = count$trigger->services->service );
                        if ( 
$details['triggers'][$name]['number_of_affected_services'] > )
                        {
                            foreach ( 
$trigger->services->service as $service )
                            {
                                
$id = ( string )$service->attributes()->id;
                                
$details['triggers'][$name]['services'][$id] = ( string )$service->attributes()->name;
                            }
                        }
                    }
                }
                break;
                
/** End triggers */
            
case 'latest':
                
$details['number_of_messages_returned'] = ( int )count$xml->messages->message );
                if ( 
$details['number_of_messages_returned'] > )
                {
                    foreach ( 
$xml->messages->message as $message )
                    {
                        
$id = ( string )$message->attributes()->id;
                        
$details['messages'][$id]['id'] = ( string )$id;
                        
$details['messages'][$id]['method'] = ( string )$message->attributes()->method;
                        
$details['messages'][$id]['date']['unix'] = ( string )$message->date->attributes()->unix;
                        
$details['messages'][$id]['date']['rfc'] = ( string )$message->date->attributes()->rfc;
                        
$details['messages'][$id]['body'] = base64_decode( (string )$message->content->body );
                        
$details['messages'][$id]['number_of_services'] = ( int )count$message->services->service );
                        if ( 
$details['messages'][$id]['number_of_services'] > )
                        {
                            foreach ( 
$message->services->service as $service )
                            {
                                
$sid = ( string )$service->attributes()->id;
                                
$details['messages'][$id]['services'][$sid] = ( string )$service->attributes()->name;
                            }
                        }
                    }
                }
                break;
                
/** End latest */
        
endswitch;
        
$this->details $details;
        return 
true;
    }

    
/**
     * PingFM_Wrapper::_check_fields()
     * 
     * @param  string $method  This is used within the class, no need to use this.
     * @param  string $fields  This is used within the class, no need to use this.
     * @return array  $fields  Fixed fields
     */
    
protected function _check_fields$method$fields )
    {
        if ( 
in_array($method, array('validate''services''triggers')) ) return array();
        
/** These don't require extra post fields */
        
if($method == 'message') throw new Exception('message method has been deprecated as of 21st June 2008.');
        switch ( 
$method ):
            case 
'latest':
                
/** Start latest */
                
if ( !empty($fields['limit']) ) /** It's optional, only enforce it if it's an non-empty value */
                
{
                    
$fields['limit'] = intval$fields['limit'] );
                    
/** Has to be an integer */
                    
if ( $fields['limit'] < /** If it's less than 0 (aka Invalid)... */
                    
{
                        
$fields['limit'] = 25;
                        
/** Set it to the default: 25 */
                    
}
                }
                if ( !empty(
$fields['order']) ) /** As above */
                
{
                    
$fields['order'] = strtoupper$fields['order'] );
                    
/** It takes ASC or DESC values, make it uppercase just in case */
                    
if ( $fields['order'] != 'ASC' and $fields['order'] != 'DESC' /** If it's not one of the acceptable values */
                    
{
                        
$fields['order'] = 'DESC';
                        
/** Then set it to the default */
                    
}
                }
                break;
                
/** End latest */
           
case 'post':
                
/** Start post */
                
if ( empty($fields['post_method']) ) throw new Exception'post_method is not set.' );
                
/** If post_method is not set, or it is not an acceptable value. Throw an error showing acceptable values */
                
$fields['post_method'] = strtolower$fields['post_method'] );
                
/** Make it lower */
                
if ( !in_array($fields['post_method'], self::$post_methods) ) throw new Exception'post_method is not an acceptable value: ' implode(', '$this->$post_methods) );
                if ( empty(
$fields['body']) ) throw new Exception'No body set' );
                if ( 
$fields['post_method'] == 'status' /** If it's a status */
                
{
                    
$fields['body'] = substrstrip_tags($fields['body']), 0200 );
                    
/** Max 200 chars, tags are stripped - Reflected in above code */
                    
if ( isset($fields['title']) ) unset( $fields['title'] );
                    
/** It's a status, so we don't need the title, remove it */
                
}
                
/** End status */
                
if ( $fields['post_method'] == 'blog' /** If it's a blog */
                
{
                    if ( empty(
$fields['title']) ) throw new Exception'A title is required for blogging.' );
                    
/** 'blog' option requires a title to be set. */
                    
$fields['title'] = strip_tags$fields['title'] );
                    
/** Can't have tags in the title itself */
                
}
                
/** End blog */
                
if ( $fields['post_method'] == 'microblog' )
                {
                    
/** If it's a microblog */
                    
$fields['body'] = substrstrip_tags($fields['body']), 0140 );
                    
/** Max 140 chars, tags are stripped - Reflected in above code */
                    
if ( isset($fields['title']) ) unset( $fields['title'] );
                    
/** It's a microblog, so we don't need the title, remove it */
                
}
                
/** End microblog */
                
if ( is_array($fields['service']) ) /** If it's an array */
                
{
                    
$fields['service'] = array_map'trim'$fields['service'] );
                    
/** Remove all excess spaces */
                    
$fields['service'] = implode','$fields['service'] );
                    
/** And implode with comma */
                
} elseif ( is_string($fields['service']) ) /** If it's a string */
                
{
                    if ( 
strlen($fields['service']) < /** If it's a zero length string... */
                    
{
                        unset( 
$fields['service'] );
                        
/** Then we don't want it - it's optional */
                    
}
                }
                if ( 
$fields['debug'] ) $fields['debug'] = 1;
                
/** If debug is equal to true, set it to 1 so we don't post data */
                
else  unset( $fields['debug'] );
                
/** Otherwise it's optional... unset it */
                
break;
                
/** End message */
            
case 'tpost':
                
/** Start tpost */
                
if ( empty($fields['trigger']) ) throw new Exception'No trigger set.' );
                
/** You have to set a trigger, otherwise throw an exception */
                
if ( empty($fields['body']) ) throw new Exception'No body set.' );
                
/** You have to set a body, otherwise throw an exception */
                
if ( $fields['debug'] ) $fields['debug'] = 1;
                
/** If debug is equal to true, set it to 1 so we don't post data */
                
else  unset( $fields['debug'] );
                
/** Otherwise it's optional... unset it */
                
break;
                
/** End tpost */
        
endswitch;
        return 
$fields;
    }
}

/**
 * PingFM_Functions
 * 
 * @package   PingFM_Wrappers
 * @url       http://danltn.com
 * @desc      Set of methods which allow easy access to the Wrapper class
 * @author    Daniel Neville
 * @copyright 2008
 * @version   0.2b
 * @access    public
 * @license   GPL
 */
class PingFM_Functions extends PingFM_Wrapper
{
    
/**
     * PingFM_Functions::__construct()
     * 
     * @param  mixed $dev_api
     * @param  mixed $user_api
     * @param  bool  $check
     * @return void
     */
    
public function __construct$dev_api null$user_api null$check true )
    {
        
parent::__construct$dev_api$user_api$check );
    }

    
/**
     * PingFM_Functions::validate()
     * 
     * @return bool $success Whether it ran successfully or not
     */
    
public function validate()
    {
        return 
$this->act'validate' );
    }

    
/**
     * PingFM_Functions::services()
     * 
     * @return bool $success Whether it ran successfully or not
     */
    
public function services()
    {
        return 
$this->act'services' );
    }

    
/**
     * PingFM_Functions::triggers()
     * 
     * @return bool $success Whether it ran successfully or not
     */
    
public function triggers()
    {
        return 
$this->act'triggers' );
    }

    
/**
     * PingFM_Functions::latest()
     * 
     * @param  integer $limit   How many records should we return
     * @param  string  $order   What order (ASC or DESC)
     * @return bool    $success Whether it ran successfully or not
     */
    
public function latest$limit 25$order 'DESC' )
    {
        return 
$this->act'latest', array('limit' => $limit'order' => $order) );
    }

    
/**
     * PingFM_Functions::post()
     * 
     * @param  string  $post_method  What post method are we using?
     * @param  string  $body         Body of the post request
     * @param  string  $title        Title (optional but required for blog)
     * @param  mixed   $service      Service to affect (optional)
     * @param  integer $debug        Should we actually post, or is this just testing (1 for testing)
     * @return bool    $success      Whether it ran successfully or not
     */
    
public function post$post_method$body$title ''$service = array(''), $debug )
    {
        return 
$this->act'post', array('body' => $body'post_method' => $post_method'title' => $title'service' => $service'debug' => $debug) );
    }

    
/**
     * PingFM_Functions::tpost()
     * 
     * @param  string  $trigger What trigger shall will use
     * @param  string  $body    Body of the post request
     * @param  string  $title   Title (optional but required for blog)
     * @param  integer $debug   Should we actually post, or is this just testing (1 for testing)
     * @return bool    $success Whether it ran successfully or not
     */
    
public function tpost$trigger$body$title ''$debug )
    {
        return 
$this->act'tpost', array('trigger' => $trigger'body' => $body'title' => $title'debug' => $debug) );
    }
}

?>