Your IP : 216.73.216.138


Current Path : /home/ereika83/www/wp-content/plugins/wd-twitter-feed/includes/Parser/
Upload File :
Current File : /home/ereika83/www/wp-content/plugins/wd-twitter-feed/includes/Parser/TweetsParser.php

<?php
/**
 * @package    @echo NAME
 * @date       @echo DATE
 * @version    @echo VERSION
 * @author     @echo AUTHOR_AND_EMAIL
 * @link       @echo URL
 * @copyright  @echo COPYRIGHT
 */
namespace TwitterFeed\Parser;

/**
 * Tweets Parser
 * 
 * This class handles all the requests to Twitter as well as the caching and 
 * handling of the data retrieved from Twitter. This class follows the singleton 
 * design pattern.
 * 
 * About the caching algorithm:
 * This is a lazy algorithm. It only makes a request if one is needed, and it 
 * gets the minimum number of tweets that satisfy the user's demands.
 * When a page is loaded, this algorithm checks to see if the time since the 
 * last Twitter request is exceeding the time specified by the user. If it does, 
 * a request to Twitter will be made. If there is more than one TweetsView
 * object within a page, the algorithm will check to see if another request is
 * needed by first checking the cache data to see if the data is already 
 * available there.
 */
class TweetsParser 
{
    // Only instance
    private static $instance = null;
    
    private $tokens;                        // Twitter credentials
    private $options;                       // Plugin options
    private $enable_caching;                // True if the user has enabled caching
    private $caching_frequency;             // The user specified threshold to make a new request
    private $request_made = false;          // True if a request has been made during this run
    private $cache;                         // Cache object
    private $debug;                         // Debug data
    
    /**
     * Constructor
     */
    private function __construct()
    {
        // Get the cache instance
        $this->cache = Cache::get_instance();    
        
        // Set the options
        global $twitterfeed_settings;
        $this->options = $twitterfeed_settings;
        
        // Set the tokens
        $this->tokens = array(
            'oauth_access_token'        => $this->options['oauth_access_token'],
            'oauth_access_token_secret' => $this->options['oauth_access_token_secret'],
            'consumer_key'              => $this->options['consumer_key'],
            'consumer_secret'           => $this->options['consumer_secret']
        );

        // Set the caching variables
        if('on' === $this->options['enable_caching']) {
            
            // Set the local variables
            $this->enable_caching = true;
            $this->caching_frequency = $this->options['caching_freq'];
            
            // Elapsed time from last call is greater 
            // than the specified caching frequency            
            if($this->cache->elapsed() > $this->caching_frequency)
            {
                // Clear the cache
                $this->cache->clear();
            }
        }
        
        // Debug
        $this->logger = \TwitterFeed\Logger::get_instance();
        $this->logger->info('Elapsed time: ' . $this->cache->elapsed());
        $this->logger->info('Caching Frequency: ' . $this->caching_frequency);
    }
    
    /**
     * Return an instance of this class
     * 
     * @return TweetsParser The only instance of this class
     */
    public static function get_instance() 
    {
        // If the single instance hasn't been set, set it now.
        if ( null == self::$instance ) {
            self::$instance = new static();
        }
        return self::$instance;
    }
    
    /**
     * Get the Tweets, either from Twitter.com
     * or from the cached data, if exits.
     * 
     * @return Tweets[] tweets array
     */
    public function getTweets( array $settings ) 
    {
        // No user was specified - bail early and throw an error
        if(($settings['resource'] == 'usertimeline' || $settings['resource'] == 'list') && !isset($settings['user']))
            throw new \Exception('No username was provided');
        
        // Must provide a search query for 'search' type resource
        if($settings['resource'] == 'search' && !isset($settings['query']))
            throw new \Exception('You must provide a search query when using "search" as a twitter resource');
        
        // Must provide a list name for 'list' type resource
        if($settings['resource'] == 'list' && !isset($settings['list']))
            throw new \Exception('You must provide a list name when using "list" as a twitter resource');
        
        // Must provide a list name for 'list' type resource
        if($settings['resource'] == 'single' && !isset($settings['id']))
            throw new \Exception('You must provide a tweet id when using "single" as a twitter resource');
        
        // No credentials were provided
        if( !$this->tokens['oauth_access_token'] || 
            !$this->tokens['oauth_access_token_secret'] ||
            !$this->tokens['consumer_key'] ||
            !$this->tokens['consumer_secret'])
            throw new \Exception('You did not provide the required Twitter tokens');
        
        // Fetch the feed
        $resp = $this->fetchFeed($settings);
        
        // Make sure Twitter returns some results
        if( 0 === count($resp) )
            throw new \Exception('Twitter API returned 0 results for your query.');
        
        return $resp;
    }
    
    /**
     * Fetch Feed
     * 
     * Fetches the feed and handles the errors. If the user 
     * has specified caching freuency, the function would 
     * claculate the elapsed time from last fetch and would 
     * use the last cached feed if the elapsed time is smaller 
     * than the specified chaching frequency. Otherwise, the 
     * function will make a request to Twitter.com to fetch 
     * the live data.
     * 
     * @return Tweets[] tweets array
     */
    private function fetchFeed(array $settings) 
    {
        // Create a new twitter resource
        $resource_class = '\\TwitterFeed\\Resource\\Resource_'.$settings['resource'];
        if( !class_exists($resource_class) )
        {
            throw new \RuntimeException('Invalid resource type: '.$settings['resource']);
        }
        $resource = new $resource_class($settings);

        // Debug
        $this->logger->info('Resource: ' . $settings['resource']);
        
        //-------------------
        // Caching is enabled
        //-------------------
        
        if($this->enable_caching) {
            $this->logger->info( 'Caching is enabled' );
            
            // Resource is not in cache
            if(!$resource->is_in_cache($settings['count'])) {
                
                // Debug
                $this->logger->info( 'Fetching new data' );
                                
                // Fetch new data
                $tweets = $this->toTweetArray($resource->perform_request($this->tokens));
                $resource->update_cache_data($tweets);

                // Notify that a request was made
                $this->request_made = true;
            }
            
            // Set the tweets
            $tweets = $resource->get_cached_data($settings['count']);
        }
        
        //--------------------
        // Caching not enabled
        //--------------------
        
        // Make a new request
        else {
            $this->logger->info( 'Caching not enabled' );
            $tweets = $this->toTweetArray($resource->perform_request($this->tokens));
        }

        // Get only the requested number of tweets
        $tweets = array_slice($tweets, 0, $settings['count']);
    
        // Return the Tweet[] array
        return $tweets;
    }
    
    /**
     * Convert Twitter 1.1 API data to an array of
     * Tweet objects
     * 
     * @param    array    $data    the data fetched from
     *                            Twitter.com
     * @throws    Exception        if the request returned errors
     * @return    Tweet            the Tweet array
     */
    private function toTweetArray( $data ) 
    {
        // Check for errors
        if( $data instanceof \stdClass && property_exists( $data, "errors" ) ) 
        {
            $message = "";
            foreach( $data->errors as $err )
            {
                $message .= $err->message . " (error code: " . $err->code . ").<br/>For more information, visit <a href=\"https://dev.twitter.com/overview/api/response-codes\">Error Codes & Responses</a>.";
            }
            
            throw new \Exception($message);
        }

        // Tweet[] array
        $tweets = array();
        $this->logger->info('crude tweets = ' . print_r($tweets, true));

        // Each tweet in the array
        foreach( $data as $key ) 
        {    
            // Take retweeted info if this is a retweet
            if( isset($key->retweeted_status) ) 
            {
                // Get retweeter user name
                $retweeter = $key->user->name;
                
                // Replace tweet with retweeted status
                $key = $key->retweeted_status;
            } 
            else 
            {
                $retweeter = null;
            }
            
            // Process tweet text
            $tweet_text = $this->remove_tweet_url( $key->text, $key->entities->urls );
            if( isset( $this->options['url_type'] ) && $this->options['url_type'] === 'expanded' )
            {
                $tweet_text = $this->expand_urls( $key->entities->urls, $tweet_text );
            }
            $tweet_text = utf8_encode( $tweet_text ); // Tweet text may contain special characters
            
            
            // Create a new Tweet object
            $tweet = new Tweet(array(
                'created_at'    => $key->created_at, 
                'image_url'     => $key->user->profile_image_url_https, 
                'screen_name'   => $key->user->screen_name, 
                'user_name'     => $key->user->name, 
                'tweet_text'    => $tweet_text, 
                'id_str'        => $key->id_str,
                'retweeter'     => $retweeter,
                'retweet_count' => $this->number_format($key->retweet_count),
                'favorite_count'=> $this->number_format($key->favorite_count),
                'media'         => $this->extract_media($key)
            ));
            
            // Add it to the collection
            $tweets[] = $tweet;
        }

        return $tweets;
    }
    
    private function extract_media( $tweet )
    {
        $media = array();
        // Images
        if(isset($tweet->entities->media))
        {
            foreach($tweet->entities->media as $_media) {
                $media[] = array(
                    'id' => $_media->id_str, // ID is used to check later if this is a thumbnail of a vine/video/gif and if so, remove it
                    'type' => $_media->type,
                    'url' => $_media->media_url_https,
                    'width' => $_media->sizes->large->w,
                    'height' => $_media->sizes->large->h
                );
            }
        }

        // Animated GIF
        if(isset($tweet->extended_entities->media))
        {
            foreach($tweet->extended_entities->media as $_media) {
                if($_media->type === 'animated_gif')
                {
                    $this->remove_medium( $media, $_media->id_str );
                    $media[] = array(
                        'type' => 'animated_gif',
                        'url' => $_media->video_info->variants[0]->url,
                        'width' => $_media->sizes->large->w,
                        'height' => $_media->sizes->large->h
                    );
                }
            }
        }

        // Vine & YouTube
        if(isset($tweet->entities->urls))
        {
            foreach($tweet->entities->urls as $url) 
            {
                // Vine
                if(strpos($url->display_url, 'vine.co') === 0)
                    $media[] = array(
                        'type' => 'vine',
                        'url' => $url->expanded_url,
                        'embed_url' => $url->expanded_url.'/embed/simple'
                    );

                // YouTube
                if(strpos($url->display_url, 'youtu.be') === 0)
                    $media[] = array(
                        'type' => 'youtube',
                        'url' => $url->expanded_url,
                        'embed_url' => 'https://www.youtube.com/embed/'.preg_replace('/https?:\/\/youtu\.be\//', '', $url->expanded_url)
                    );
            }
        }
        return $media == array() ? null : $media;
    }
    
    private function remove_medium( &$media, $id )
    {
        foreach( $media as $key => $medium )
        {
            if( $medium['id'] === $id )
            {
                unset($media[$key]);
                break;
            }
        }
    }
    
    private function number_format( $number )
    {
        $k = $number/1000;
        if( $k >= 1 ) return ((float)number_format($k,1)).'K';
        return $number;
    }
    
    private function expand_urls( $urls, $text )
    {
        foreach( $urls as $url )
        {
            $text = str_replace( $url->url, $url->expanded_url, $text );
        }
        return $text;
    }

    /**
     * Go through all URLs in the tweet text and remove them if they don't 
     * have a corresponding URL in the URL list
     */
    private function remove_tweet_url( $text, $urls )
    {
        preg_match_all('/https:\/\/t.co\/[\w\d]+/',$text,$matches);
        foreach( $matches[0] as $match )
        {
            $should_be_removed = true;

            foreach( $urls as $url ) 
            {
                if(0 === strpos($url->url, $match))
                {
                    $should_be_removed = false;
                }
            }

            if($should_be_removed) 
            {
                $text = str_replace( $match, '', $text );
            }
        }   
        return $text;
    }
}