<?php
/**
 * Module Name: Twitter Widget
 * Module Description: Display the latest updates from a Twitter user inside your theme's widgets.
 * Sort Order: 20
 * First Introduced: 1.1
 */

/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

if ( !function_exists('wpcom_time_since') ) :
/*
 * Time since function taken from WordPress.com
 */

function wpcom_time_since( $original, $do_more = 0 ) {
	// array of time period chunks
	$chunks = array(
		array( 60 * 60 * 24 * 365 , 'year' ),
		array( 60 * 60 * 24 * 30 , 'month' ),
		array( 60 * 60 * 24 * 7, 'week' ),
		array( 60 * 60 * 24 , 'day' ),
		array( 60 * 60 , 'hour' ),
		array( 60 , 'minute' ),
	);

	$today = time();
	$since = $today - $original;

	for ( $i = 0, $j = count( $chunks ); $i < $j; $i++ ) {
		$seconds = $chunks[$i][0];
		$name = $chunks[$i][1];

		if ( ( $count = floor( $since / $seconds ) ) != 0 )
			break;
	}

	$print = ( $count == 1 ) ? '1 ' . $name : "$count {$name}s";

	if ( $i + 1 < $j ) {
		$seconds2 = $chunks[$i + 1][0];
		$name2 = $chunks[$i + 1][1];

		// add second item if it's greater than 0
		if ( ( ( $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 ) ) != 0 ) && $do_more )
			$print .= ( $count2 == 1 ) ? ', 1 ' . $name2 : ", $count2 {$name2}s";
	}
	return $print;
}
endif;

class Wickett_Twitter_Widget extends WP_Widget {

	function Wickett_Twitter_Widget() {
		$widget_ops = array( 'classname' => 'widget_twitter', 'description' => __( 'Display your tweets from Twitter', 'jetpack' ) );
		parent::WP_Widget( 'twitter', __( 'Twitter (Jetpack)', 'jetpack' ), $widget_ops );
	}

	function widget( $args, $instance ) {
		extract( $args );

		$account = trim( urlencode( $instance['account'] ) );
		if ( empty($account) ) return;
		$title = apply_filters( 'widget_title', $instance['title'] );
		if ( empty( $title ) ) $title = __( 'Twitter Updates', 'jetpack' );
		$show = absint( $instance['show'] );  // # of Updates to show
		if ( $show > 200 ) // Twitter paginates at 200 max tweets. update() should not have accepted greater than 20
			$show = 200;
		$hidereplies = (bool) $instance['hidereplies'];
		$include_retweets = (bool) $instance['includeretweets'];

		echo "{$before_widget}{$before_title}<a href='" . esc_url( "http://twitter.com/{$account}" ) . "'>" . esc_html($title) . "</a>{$after_title}";
 
		$tweets = get_transient( 'widget-twitter-' . $this->number );
		if ( false === $tweets ) {
			$params = array(
				'screen_name'=>$account, // Twitter account name
				'trim_user'=>true, // only basic user data (slims the result)
				'include_entities' => true
			);

			/**
			 * The exclude_replies parameter filters out replies on the server. If combined with count it only filters that number of tweets (not all tweets up to the requested count)
			 * If we are not filtering out replies then we should specify our requested tweet count
			 */
			if ( $hidereplies )
				$params['exclude_replies'] = true;
			else
				$params['count'] = $show;
			if ( $include_retweets )
				$params['include_rts'] = true;
			$twitter_json_url = esc_url_raw( 'http://api.twitter.com/1/statuses/user_timeline.json?' . http_build_query( $params ), array( 'http', 'https' ) );
			unset( $params );
			$response = wp_remote_get( $twitter_json_url, array( 'User-Agent' => 'WordPress.com Twitter Widget' ) );
			$response_code = wp_remote_retrieve_response_code( $response );
			if ( 200 == $response_code ) {
				$tweets = wp_remote_retrieve_body( $response );
				$tweets = json_decode( $tweets, true );
				$expire = 900; // 15 minutes
				if ( !is_array( $tweets ) || isset( $tweets['error'] ) ) {
					$tweets = 'error';
					$expire = 300; // 5 minutes
				} else {				
					set_transient( 'widget-twitter-backup-' . $this->number, $tweets, 86400 ); // 1 day
				}
			} else {
				$expire = 300; // 5 minutes
				$tweets = get_transient( 'widget-twitter-backup-' . $this->number );
				if ( !is_array( $tweets ) || isset( $tweets['error'] ) ) {
					$tweets = 'error';
					set_transient( 'widget-twitter-response-code-' . $this->number, $response_code, $expire );
				}
			}

			set_transient( 'widget-twitter-' . $this->number, $tweets, $expire );
		}

		if ( 'error' != $tweets ) :
			$before_timesince = ' ';
			if ( isset( $instance['beforetimesince'] ) && !empty( $instance['beforetimesince'] ) )
				$before_timesince = esc_html( $instance['beforetimesince'] );
			$before_tweet = '';
			if ( isset( $instance['beforetweet'] ) && !empty( $instance['beforetweet'] ) )
				$before_tweet = stripslashes( wp_filter_post_kses( $instance['beforetweet'] ) );

			echo '<ul class="tweets">' . "\n";

			$tweets_out = 0;

			foreach ( (array) $tweets as $tweet ) {
				if ( $tweets_out >= $show )
					break;

				if ( empty( $tweet['text'] ) )
					continue;

				$text = esc_html( $tweet['text'] );
					
				// expand t.co links
				if ( !empty( $tweet['entities']['urls'] ) ) {
					foreach ( $tweet['entities']['urls'] as $entity_url ) {
						if ( !empty( $entity_url['expanded_url'] ) ) {
							$expanded = '<a href="' . esc_url( $entity_url['expanded_url'] ) . '"> ' . esc_html( $entity_url['display_url'] ) . '</a>';
							$text = str_replace( $entity_url['url'], $expanded, $text );
						}
					}
				}

				$text = make_clickable( $text );

				/*
				 * Create links from plain text based on Twitter patterns
				 * @link http://github.com/mzsanford/twitter-text-rb/blob/master/lib/regex.rb Official Twitter regex
				 */
				$text = preg_replace_callback( '/(^|[^0-9A-Z&\/]+)(#|\xef\xbc\x83)([0-9A-Z_]*[A-Z_]+[a-z0-9_\xc0-\xd6\xd8-\xf6\xf8\xff]*)/iu',  array( $this, '_wpcom_widget_twitter_hashtag' ), $text );
				$text = preg_replace_callback( '/([^a-zA-Z0-9_]|^)([@\xef\xbc\xa0]+)([a-zA-Z0-9_]{1,20})(\/[a-zA-Z][a-zA-Z0-9\x80-\xff-]{0,79})?/u', array( $this, '_wpcom_widget_twitter_username' ), $text );
				if ( isset( $tweet['id_str'] ) )
					$tweet_id = urlencode( $tweet['id_str'] );
				else
					$tweet_id = urlencode( $tweet['id'] );
				$created_at = str_replace( '+0000', '', $tweet['created_at'] ) . ' UTC'; // Twitter's datetime format is strange, refactor for the sake of PHP4
				echo "<li>{$before_tweet}{$text}{$before_timesince}<a href=\"" . esc_url( "http://twitter.com/{$account}/statuses/{$tweet_id}" ) . '" class="timesince">' . str_replace( ' ', '&nbsp;', wpcom_time_since( strtotime( $created_at ) ) ) . "&nbsp;ago</a></li>\n";
				unset( $tweet_id );
				$tweets_out++;
			}

			echo "</ul>\n";
		else :
			if ( 401 == get_transient( 'widget-twitter-response-code-' . $this->number ) )
				echo '<p>' . wp_kses( sprintf( __( 'Error: Please make sure the Twitter account is <a href="%s">public</a>.', 'jetpack' ), 'http://support.twitter.com/forums/10711/entries/14016' ), array( 'a' => array( 'href' => true ) ) ) . '</p>';
			else
				echo '<p>' . esc_html__( 'Error: Twitter did not respond. Please wait a few minutes and refresh this page.', 'jetpack' ) . '</p>';
		endif;

		echo $after_widget;
	}

	function update( $new_instance, $old_instance ) {
		$instance = $old_instance;

		$instance['account'] = trim( strip_tags( stripslashes( $new_instance['account'] ) ) );
		$instance['account'] = str_replace( 'http://twitter.com/', '', $instance['account'] );
		$instance['account'] = str_replace( '/', '', $instance['account'] );
		$instance['account'] = str_replace( '@', '', $instance['account'] );
		$instance['account'] = str_replace( '#!', '', $instance['account'] ); // account for the Ajax URI
		$instance['title'] = strip_tags( stripslashes( $new_instance['title'] ) );
		$instance['show'] = absint( $new_instance['show'] );
		$instance['hidereplies'] = isset( $new_instance['hidereplies'] );
		$instance['includeretweets'] = isset( $new_instance['includeretweets'] );
		$instance['beforetimesince'] = $new_instance['beforetimesince'];

		delete_transient( 'widget-twitter-' . $this->number );
		delete_transient( 'widget-twitter-response-code-' . $this->number );

		return $instance;
	}

	function form( $instance ) {
		//Defaults
		$instance = wp_parse_args( (array) $instance, array( 'account' => '', 'title' => '', 'show' => 5, 'hidereplies' => false, 'includeretweets' => false, 'beforetimesince' => '' ) );

		$account = esc_attr( $instance['account'] );
		$title = esc_attr( $instance['title'] );
		$show = absint( $instance['show'] );
		if ( $show < 1 || 20 < $show )
			$show = 5;
		$hidereplies = (bool) $instance['hidereplies'];
		$include_retweets = (bool) $instance['includeretweets'];
		$before_timesince = esc_attr( $instance['beforetimesince'] );

		echo '<p><label for="' . $this->get_field_id( 'title' ) . '">' . esc_html__( 'Title:', 'jetpack' ) . '
		<input class="widefat" id="' . $this->get_field_id( 'title' ) . '" name="' . $this->get_field_name( 'title' ) . '" type="text" value="' . $title . '" />
		</label></p>
		<p><label for="' . $this->get_field_id( 'account' ) . '">' . esc_html__( 'Twitter username:', 'jetpack' ) . ' <a href="http://support.wordpress.com/widgets/twitter-widget/#twitter-username" target="_blank">( ? )</a>
		<input class="widefat" id="' . $this->get_field_id( 'account' ) . '" name="' . $this->get_field_name( 'account' ) . '" type="text" value="' . $account . '" />
		</label></p>
		<p><label for="' . $this->get_field_id( 'show' ) . '">' . esc_html__( 'Maximum number of tweets to show:', 'jetpack' ) . '
			<select id="' . $this->get_field_id( 'show' ) . '" name="' . $this->get_field_name( 'show' ) . '">';

		for ( $i = 1; $i <= 20; ++$i )
			echo "<option value='$i' " . ( $show == $i ? "selected='selected'" : '' ) . ">$i</option>";

		echo '		</select>
		</label></p>
		<p><label for="' . $this->get_field_id( 'hidereplies' ) . '"><input id="' . $this->get_field_id( 'hidereplies' ) . '" class="checkbox" type="checkbox" name="' . $this->get_field_name( 'hidereplies' ) . '"';
		if ( $hidereplies )
			echo ' checked="checked"';
		echo ' /> ' . esc_html__( 'Hide replies', 'jetpack' ) . '</label></p>';

		echo '<p><label for="' . $this->get_field_id( 'includeretweets' ) . '"><input id="' . $this->get_field_id( 'includeretweets' ) . '" class="checkbox" type="checkbox" name="' . $this->get_field_name( 'includeretweets' ) . '"';
		if ( $include_retweets )
			echo ' checked="checked"';
		echo ' /> ' . esc_html__( 'Include retweets', 'jetpack' ) . '</label></p>';

		echo '<p><label for="' . $this->get_field_id( 'beforetimesince' ) . '">' . esc_html__( 'Text to display between tweet and timestamp:', 'jetpack' ) . '
		<input class="widefat" id="' . $this->get_field_id( 'beforetimesince' ) . '" name="' . $this->get_field_name( 'beforetimesince' ) . '" type="text" value="' . $before_timesince . '" />
		</label></p>';
	}

	/**
	 * Link a Twitter user mentioned in the tweet text to the user's page on Twitter.
	 *
	 * @param array $matches regex match
	 * @return string Tweet text with inserted @user link
	 */
	function _wpcom_widget_twitter_username( $matches ) { // $matches has already been through wp_specialchars
		return "$matches[1]@<a href='" . esc_url( 'http://twitter.com/' . urlencode( $matches[3] ) ) . "'>$matches[3]</a>";
	}

	/**
	 * Link a Twitter hashtag with a search results page on Twitter.com
	 *
	 * @param array $matches regex match
	 * @return string Tweet text with inserted #hashtag link
	 */
	function _wpcom_widget_twitter_hashtag( $matches ) { // $matches has already been through wp_specialchars
		return "$matches[1]<a href='" . esc_url( 'http://twitter.com/search?q=%23' . urlencode( $matches[3] ) ) . "'>#$matches[3]</a>";
	}

}

add_action( 'widgets_init', 'wickett_twitter_widget_init' );
function wickett_twitter_widget_init() {
	register_widget( 'Wickett_Twitter_Widget' );
}
