<?php

$plugin_info 
= array(
                        
'pi_name'            => 'Twitter Timeline',
                        
'pi_version'        => '1.2',
                        
'pi_author'            => 'Derek Jones',
                        
'pi_author_url'        => 'http://expressionengine.com/',
                        
'pi_description'    => 'Allows you to display information from Twitter timelines',
                        
'pi_usage'            => Twitter_timeline::usage()
                    );
                    
/**
 * Twitter_timeline Class
 *
 * @package            ExpressionEngine
 * @category        Plugin
 * @author            Derek Jones
 * @link            http://expressionengine.com/downloads/details/twitter_timeline/
 *
 * Auto_link and link_usernames added by Pascal Kriete
 */
class Twitter_timeline {
    
    var 
$return_data;
    var 
$base_url        'http://twitter.com/statuses/';
    var 
$cache_name        'twitter_timeline_cache';
    var 
$cache_expired    FALSE;
    var 
$refresh        15;        // Period between cache refreshes, in minutes
    
var $limit            20;
    var 
$statuses        = array();
    var 
$user            '';
    var 
$password        '';
    var 
$months            = array('Jan''Feb''Mar''Apr''May''Jun''Jul''Aug''Sep''Oct''Nov''Dec');
    
    
/**
     * Constructor
     *
     */
    
function Twitter_timeline()
    {
        global 
$TMPL;

        
/** ---------------------------------------
        /**  Fetch parameters
        /** ---------------------------------------*/
        
        
$this->refresh = (($refresh $TMPL->fetch_param('twitter_refresh')) === FALSE) ? $this->refresh $refresh;
        
$this->limit = (($limit $TMPL->fetch_param('limit')) === FALSE) ? $this->limit $limit;
        
$this->user = (($user $TMPL->fetch_param('user')) === FALSE) ? '' $user;
        
$this->password = (($password $TMPL->fetch_param('password')) === FALSE) ? '' $password;

        if (
$timeline $TMPL->fetch_param('type'))
        {
            
$types = array('public''friends''user');
        
            if ( ! 
in_array($timeline$types))
            {
                
$TMPL->log_item("Twitter Timeline error: Invalid timeline type");
                
$this->return_data '';
                return;
            }
        }
        else
        {
            
$timeline 'public';
        }
        
        if (
$timeline != 'public' AND ($this->user == '' OR $this->password == ''))
        {
            
$TMPL->log_item("Twitter Timeline error: no user specified for non-public timeline");
            
$this->return_data '';
            return;
        }
        
        
$log_extra = ($this->user != '') ? "For User {$this->user}" "";
        
$TMPL->log_item("Using '{$timeline}' Twitter Timeline {$log_extra}");
        
        
/** ---------------------------------------
        /**  Fetch the XML from Twitter
        /** ---------------------------------------*/
        
        
$url $this->base_url.$timeline.'_timeline.xml';

        if ((
$rawxml $this->_check_cache($url.$this->user)) === FALSE)
        {
            
$this->cache_expired TRUE;
            
$TMPL->log_item("Fetching Twitter timeline remotely");
            
            if ( 
function_exists('curl_init'))
            {
                
$rawxml $this->_curl_fetch($url); 
            }
            else
            {
                
$rawxml $this->_fsockopen_fetch($url);
            }        
        }
        
        if (
$rawxml == '' OR substr($rawxml05) != "<?xml")
        {
            
$TMPL->log_item("Twitter Timeline error: Unable to retreive statuses from Twitter.com");
            
$this->return_data '';
            return;
        }
        
        
/** ---------------------------------------
        /**  Write cache
        /** ---------------------------------------*/
        
        
if ($this->cache_expired == TRUE)
        {
            
$this->_write_cache($rawxml$url.$this->user);            
        }
        
        
/** ---------------------------------------
        /**  Parse the XML with the EE XML Parser class
        /** ---------------------------------------*/
        
        
if ( ! class_exists('EE_XMLparser'))
        {
            require 
PATH_CORE.'core.xmlparser'.EXT;
        }

        
$XML = new EE_XMLparser;
        
        
// valid XML?
        
if (($xml_obj $XML->parse_xml($rawxml)) === FALSE)
        {
            
$TMPL->log_item("Twitter Timeline error: Unable to retreive statuses from Twitter.com");
            
$this->return_data '';
            return;
        }
        
        
// Check for error response
        
if ($xml_obj->children[0]->tag == 'error')
        {
            
$TMPL->log_item("Twitter Timeline error: ".$xml_obj->children[0]->value);
            
$this->return_data '';
            return;
        }
        

        
$this->_load_statuses($xml_obj);

        
/** ---------------------------------------
        /**  Go go go!
        /** ---------------------------------------*/
        
        
$this->return_data $this->_parse_statuses();
    }

    
// --------------------------------------------------------------------

    /**
     * Parse Statuses
     *
     * Parses the Twitter Status messages
     *
     * @access    public
     * @return    string
     */
    
function _parse_statuses()
    {
        global 
$FNS$LOC$PREFS$SESS$TMPL;
        
        
$output '';
        
$created_at = array();
        
        
// parse created_at date variables outside of the loop to save processing
        
if (preg_match_all("/".LD.'created_at'."\s+format=(\042|\047)([^\\1]*?)\\1".RD."/s"$TMPL->tagdata$matches))
        {
            for (
$i 0$i count($matches['0']); $i++)
            {
                
$matches['0'][$i] = str_replace(array(LD,RD), ''$matches['0'][$i]);
                
$created_at[$matches['0'][$i]] = $LOC->fetch_date_params($matches['2'][$i]);
            }
        }

        
// @ADDED
        
$auto_link $TMPL->fetch_param('auto_link');
        
$auto_link = ($auto_link && strtolower($auto_link) == 'true') ? TRUE FALSE;
        
        
$link_usernames $TMPL->fetch_param('link_usernames');
        
$link_usernames = ($link_usernames && strtolower($link_usernames) == 'true') ? TRUE FALSE;

        
$count 0;

        foreach(
$this->statuses as $key => $val)
        {
            
$tagdata $TMPL->tagdata;
            
$count++;

            if (
$count $this->limit)
            {
                return 
$output;
            }
            
            
// allows {count} variable to be parsed
            
$val['count'] = $count;
            
            
/** ---------------------------------------
            /**  Prep conditionals
            /** ---------------------------------------*/
            
            
$cond     $val;
            
$tagdata $FNS->prep_conditionals($tagdata$cond['user']);
            unset(
$cond['user']);
            
$tagdata $FNS->prep_conditionals($tagdata$cond);
            
            
            foreach(
$TMPL->var_single as $var_key => $var_val)
            {
                
/** ----------------------------------------
                /**  parse {switch} variable
                /** ----------------------------------------*/
                
                
if (preg_match("/^switch\s*=.+/i"$var_key))
                {
                    
$sparam $FNS->assign_parameters($var_key);
                    
                    
$sw '';
                    
                    if (isset(
$sparam['switch']))
                    {
                        
$sopt explode("|"$sparam['switch']);

                        
$sw $sopt[($count-count($sopt)) % count($sopt)];
                    }
                    
                    
$tagdata $TMPL->swap_var_single($var_key$sw$tagdata);
                }
                
                
/** ---------------------------------------
                /**  parse {created_at}
                /** ---------------------------------------*/
                                
                
if (isset($created_at[$var_key]))
                {
                    
$human_time $this->_parse_twitter_date($this->statuses[$key]['created_at']);
                    
                    
// We already have GMT so we need $LOC->convert_human_date_to_gmt to
                    // NOT do any localization.  Fib the Session userdata for sec.
                    
$timezone $SESS->userdata['timezone'];
                    
$dst $SESS->userdata['daylight_savings'];                
                    
$SESS->userdata['timezone'] = 'UTC';
                    
$SESS->userdata['daylight_savings'] = 'n';

                    
$date $LOC->convert_human_date_to_gmt($human_time);
                    
                    
// reset Session userdata to original values
                    
$SESS->userdata['timezone'] = $timezone;
                    
$SESS->userdata['daylight_savings'] = $dst;
                    
                    foreach (
$created_at[$var_key] as $dvar)
                    {
                        
$var_val str_replace($dvar$LOC->convert_timestamp($dvar$dateTRUE), $var_val);
                    }
                    
                    
$tagdata $TMPL->swap_var_single($var_key$var_val$tagdata);
                }
                
                
/** ---------------------------------------
                /**  Parse {status_relative_date}
                /** ---------------------------------------*/
                
                
if ($var_key == 'status_relative_date')
                {
                    
$human_time $this->_parse_twitter_date($this->statuses[$key]['created_at']);
                                        
                    
$date $LOC->set_server_time($LOC->timestamp_to_gmt($human_time));
                    
                    
$tagdata $TMPL->swap_var_single($var_key$LOC->format_timespan($LOC->now $date), $tagdata);
                }
                
                
// is the variable a primary key of the status?
                
if (isset($val[$var_key]))
                {
                    
// ADDED
                    
if ($auto_link && $var_key == 'text')
                    {
                        
$val[$var_key] = $this->_auto_link($val[$var_key]);
                    }
                    
                    if (
$link_usernames && $var_key == 'text')
                    {
                        
$val[$var_key] = $this->_link_usernames($val[$var_key]);
                    }
                    
                    
$tagdata $TMPL->swap_var_single($var_key$val[$var_key], $tagdata);    
                }
                elseif (isset(
$val['user'][$var_key])) // is the variable part of the user information?
                
{
                    
$tagdata $TMPL->swap_var_single($var_key$val['user'][$var_key], $tagdata);
                }
                else
                {
                    
$tagdata $TMPL->swap_var_single($var_key''$tagdata);
                }
            }
            
            
$output .= $tagdata;
        }
        
        return 
$output;
    }

    
// --------------------------------------------------------------------
    
    /**
     * Load Statuses
     *
     * Load Twitter status information from XML object
     *
     * @access    public
     * @param    EE XML Parser object
     * @return    void
     */
    
function _load_statuses($xml_obj)
    {
        foreach(
$xml_obj->children as $key => $status)
        {
            foreach(
$status->children as $ckey => $item)
            {
                if (
$item->tag != 'user')
                {
                    
$this->statuses[$key][$item->tag] = $item->value;                    
                }
                else
                {
                    foreach(
$item->children as $ukey => $uitem)
                    {
                        
// rename user id so it doesn't conflict with status message id
                        
$uitem->tag = ($uitem->tag == 'id') ? 'user_id' $uitem->tag;
                        
                        
$this->statuses[$key][$item->tag][$uitem->tag] = $uitem->value;
                    }
                }
            }
        }
    }

    
// --------------------------------------------------------------------
    
    /**
     * Check Cache
     *
     * Check for cached data
     *
     * @access    public
     * @param    string
     * @return    mixed - string if pulling from cache, FALSE if not
     */
    
function _check_cache($url)
    {    
        global 
$TMPL;
            
        
/** ---------------------------------------
        /**  Check for cache directory
        /** ---------------------------------------*/
        
        
$dir PATH_CACHE.$this->cache_name.'/';
        
        if ( ! @
is_dir($dir))
        {
            return 
FALSE;
        }

        
$file $dir.md5($url);
        
        if ( ! 
file_exists($file) OR ! ($fp = @fopen($file'rb')))
        {
            return 
FALSE;
        }
               
        
flock($fpLOCK_SH);
                    
        
$cache = @fread($fpfilesize($file));
                    
        
flock($fpLOCK_UN);
        
        
fclose($fp);
        
        
$eol strpos($cache"\n");
        
        
$timestamp substr($cache0$eol);
        
$cache trim((substr($cache$eol)));
        
        if (
time() > ($timestamp + ($this->refresh 60)))
        {
            return 
FALSE;
        }
        
        
$TMPL->log_item("Twitter timeline retreived from cache");
        
        return 
$cache;
    }

    
// --------------------------------------------------------------------
    
    /**
     * Write Cache
     *
     * Write the cached data
     *
     * @access    public
     * @param    string
     * @return    void
     */
    
function _write_cache($data$url)
    {
        
/** ---------------------------------------
        /**  Check for cache directory
        /** ---------------------------------------*/
        
        
$dir PATH_CACHE.$this->cache_name.'/';

        if ( ! @
is_dir($dir))
        {
            if ( ! @
mkdir($dir0777))
            {
                return 
FALSE;
            }
            
            @
chmod($dir0777);            
        }
        
        
// add a timestamp to the top of the file
        
$data time()."\n".$data;
        
        
/** ---------------------------------------
        /**  Write the cached data
        /** ---------------------------------------*/
        
        
$file $dir.md5($url);
    
        if ( ! 
$fp = @fopen($file'wb'))
        {
            return 
FALSE;
        }

        
flock($fpLOCK_EX);
        
fwrite($fp$data);
        
flock($fpLOCK_UN);
        
fclose($fp);
        
        @
chmod($file0777);        
    }

    
// --------------------------------------------------------------------
    
    /**
     * curl Fetch
     *
     * Fetch Twitter statuses using cURL
     *
     * @access    public
     * @param    string
     * @return    string
     */
    
function _curl_fetch($url)
    {
        
$ch curl_init(); 
        
curl_setopt($chCURLOPT_URL$url); 
        
curl_setopt($chCURLOPT_RETURNTRANSFER1);
        
curl_setopt($chCURLOPT_FRESH_CONNECT1);
        
curl_setopt($chCURLOPT_USERPWD"{$this->user}:{$this->password}");

        
$data curl_exec($ch);
        
        
curl_close($ch);

        return 
$data;
    }

    
// --------------------------------------------------------------------
    
    /**
     * fsockopen Fetch
     *
     * Fetch Twitter statuses using fsockopen
     *
     * @access    public
     * @param    string
     * @return    string
     */
    
function _fsockopen_fetch($url)
    {
        
$target parse_url($url);

        
$data '';

        
$fp fsockopen($target['host'], 80$error_num$error_str8); 

        if (
is_resource($fp))
        {
            
fputs($fp"GET {$url} HTTP/1.0\r\n");
            
fputs($fp"Host: {$target['host']}\r\n");
            
fputs($fp"Authorization: Basic ".base64_encode("$this->user:$this->password")."\r\n");
            
fputs($fp"User-Agent: EE/EllisLab PHP/" phpversion() . "\r\n\r\n");

            
$headers TRUE;

            while( ! 
feof($fp))
            {
                
$line fgets($fp4096);

                if (
$headers === FALSE)
                {
                    
$data .= $line;
                }
                elseif (
trim($line) == '')
                {
                    
$headers FALSE;
                }
            }

            
fclose($fp); 
        }
        
        return 
$data;
    }

    
// --------------------------------------------------------------------
    
    /**
     * Parse Twitter Date
     *
     * Reformats Twitter's dates to a standard human time notation
     * Twitter's dates are in the format: Fri Apr 13 15:34:45 +0000 2007
     * Returns in YYYY-MM-DD HH:MM:SS
     * 
     * @access    public
     * @param    string
     * @return    string
     */
    
function _parse_twitter_date($str)
    {
        
$parts explode(' '$str);
        
$month array_keys($this->months$parts[1]);
        
$mm sprintf("%02s"$month[0] + 1);
        
        return 
"{$parts[5]}-{$mm}-{$parts[2]} {$parts[3]}";

    }
    
    
// --------------------------------------------------------------------
    
    /**
     * Auto Link
     *
     * Finds all urls in the status message and wraps them in html anchor tags.
     *
     * Taken from CodeIgniter's auto_link function
     * @see http://codeigniter.com/user_guide/helpers/url_helper.html
     * 
     * @access    public
     * @param    string
     * @return    string
     */
    
function _auto_link($str)
    {
        if (
preg_match_all("#(^|\s|\()((http(s?)://)|(www\.))(\w+[^\s\)\<]+)#i"$str$matches))
        {
            for (
$i 0$i sizeof($matches['0']); $i++)
            {
                
$period '';
                if (
preg_match("|\.$|"$matches['6'][$i]))
                {
                    
$period '.';
                    
$matches['6'][$i] = substr($matches['6'][$i], 0, -1);
                }
    
                
$str str_replace($matches['0'][$i],
                                    
$matches['1'][$i].'<a href="http'.
                                    
$matches['4'][$i].'://'.
                                    
$matches['5'][$i].
                                    
$matches['6'][$i].'">http'.
                                    
$matches['4'][$i].'://'.
                                    
$matches['5'][$i].
                                    
$matches['6'][$i].'</a>'.
                                    
$period$str);
            }
        }
        
        return 
$str;

    }
    
    
// --------------------------------------------------------------------
    
    /**
     * Link Usernames
     *
     * Finds all @usernames and links them to http://twitter.com/username
     * 
     * @access    public
     * @param    string
     * @return    string
     */
    
function _link_usernames($str)
    {
        
// (beginning|whitespace|punctuation|closing_html_tag|)@(alphanum)(whitespace|punctuation|opening_html_tag)
        
$pattern "#(^|\s|\(|\{|\,|\.|\:|\>)@{1}(\w+)(\s|\)|\(|\{|\}|\,|\.|\:|\<)#i";
        
        
// Turn it into a link
        
return preg_replace($pattern'$1@<a href="http://twitter.com/$2">$2</a>$3'$str);
    }

    
// --------------------------------------------------------------------
    
    /**
     * Usage
     *
     * Plugin Usage
     *
     * @access    public
     * @return    string
     */
    
function usage()
    {
        
ob_start(); 
        
?>
        ------------------
        EXAMPLE USAGE:
        ------------------
        
        {exp:twitter_timeline type="friends" user="screen_name"}
        <div class="tweet">
            <div class="date">{created_at format="%m-%d %g:%i"}</div>
            <div class="author">
                <div class="icon"><img src="{profile_image_url}" width="48" height="48" alt="profile image" /></div>
                {name}
            </div>
            <div class="status">{text}</div>
        </div>
        {/exp:twitter_timeline}
        
        ------------------
        PARAMETERS:
        ------------------
        
        type="user"
        - Type of timeline.  Either "user", "friends", or "public".  Default is "public".
        
        user="email@example.com"
        - Email address or user name of Twitter user.  Required for "user" or "friends" timelines
        
        password="password1"
        - Password of the Twitter user.  Required for "user" or "friends" timelines
        
        limit="5"
        - Number of status messages to limit to.  Default (and maximum value) is 20.
        
        twitter_refresh="20"
        - Time (in minutes) of cache interval for the requested Twitter timeline.  Defaults to 15.
        
        auto_link="true"
        - Automatically converts urls in status messages to html anchor tags.
        
        link_usernames="true"
        - Automatically convert @usernames into twitter.com/username links
        
        ------------------
        VARIABLES:
        ------------------
        
        {count}
        {switch="one|two|three"}
        {created_at format="%m-%d-%Y"}
        {status_relative_date}
        {text}
        {id}
        {user_id}
        {name}
        {screen_name}
        {location}
        {description}
        {profile_image_url}
        {url}
        {protected}
        
        ------------------
        TROUBLESHOOTING:
        ------------------
        
        All error messages are logged in the Template Parsing Log.  If you have no output, or unexpected output, enable the Template Parsing Log in your Output and Debugging Preferences.
        
        Even though this tag has its own caching mechanism for retrieving the timeline from Twitter.com, do not forget that you can further increase performance by using Tag Caching: http://expressionengine.com/docs/general/caching.html#tag_caching
        
        ------------------
        CHANGELOG:
        ------------------        
        
        Version 1.2 - added ability to use multiple Twitter Timeline tags on a page, with separate caches
        Version 1.1.1 - improved handling for situations where Twitter's server is down
        Version 1.1 - added error handling for Twitter XML error responses
        
        <?php
        $buffer 
ob_get_contents();

        
ob_end_clean(); 

        return 
$buffer;
    }

    
// --------------------------------------------------------------------
    
}
// END Twitter_timeline Class

/* End of file  pi.twitter_timeline.php */
/* Location: ./system/plugins/pi.twitter_timeline.php */