web/wp-content/plugins/twitter-tools/classes/aktt_tweet.php
changeset 194 32102edaa81b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/wp-content/plugins/twitter-tools/classes/aktt_tweet.php	Mon Nov 19 18:26:13 2012 +0100
@@ -0,0 +1,638 @@
+<?php
+
+class AKTT_Tweet {
+	var $post_id = null;
+	var $featured_image_id = null;
+	var $raw_data = null;
+		
+	/**
+	 * Set up the tweet with the ID from twitter
+	 *
+	 * @param mixed $data - tweet id or full tweet object from twitter API
+	 * @param bool $from_db - Whether to auto-populate this object from the DB
+	 */
+	function __construct($data, $from_db = false) {
+		if (is_object($data)) {
+			$this->populate_from_twitter_obj($data);
+		}
+		else {
+			// Assign the tweet ID to this object
+			$this->id = $data;
+			
+			// Flag to populate the object from the DB on construct
+			if ($from_db == true) {
+				$this->populate_from_db();
+			}
+		}
+	}
+	
+	
+	/**
+	 * Allows the object's properties to be populated from a post object in the DB
+	 *
+	 * @return void
+	 */
+	function populate_from_db() {
+		$post = $this->get_post(AKTT::$post_type);
+
+		if (is_wp_error($post) || empty($post)) {
+			return false;
+		}
+		
+		$this->post = $post;
+		$this->raw_data = get_post_meta($this->post->ID, '_aktt_tweet_raw_data', true);
+		$this->data = json_decode($this->raw_data);
+	}
+	
+	
+	/**
+	 * Populates the object from a twitter API response object
+	 *
+	 * @param object $tweet_obj 
+	 * @return void
+	 */
+	function populate_from_twitter_obj($tweet_obj) {
+		$this->data = $tweet_obj;
+		$this->raw_data = json_encode($tweet_obj);
+		$this->id = $tweet_obj->id_str;
+	}
+	
+	/**
+	 * Accessor function for tweet id
+	 *
+	 * @return string|null
+	 */
+	public function id() {
+		return (isset($this->data) ? $this->data->id_str : null);
+	}
+	
+	/**
+	 * Accessor function for tweet text shortened for post title
+	 *
+	 * @return string
+	 */
+	public function title() {
+		if (isset($this->data)) {
+			$title = trim(AKTT::substr($this->data->text, 0, 50));
+			if (AKTT::strlen($this->data->text) > 50) {
+				$title = $title.'...';
+			}
+		}
+		else {
+			$title = null;
+		}
+		return $title;
+	}
+	
+	/**
+	 * Accessor function for tweet text
+	 *
+	 * @return string
+	 */
+	public function content() {
+		if (isset($this->data) && isset($this->data->text)) {
+			return $this->data->text;
+		}
+		if (isset($this->post) && isset($this->post->post_content)) {
+			return $this->post->post_content;
+		}
+		return null;
+	}
+	
+	/**
+	 * Accessor function for tweet date/time
+	 *
+	 * @return string
+	 */
+	public function date() {
+		return (isset($this->data) ? $this->data->created_at : null);
+	}
+	
+	/**
+	 * Accessor function for tweet author's username
+	 *
+	 * @return string
+	 */
+	public function username() {
+		return (isset($this->data) ? $this->data->user->screen_name : null);
+	}
+	
+	/**
+	 * Accessor function for tweet reply-to username
+	 *
+	 * @return string
+	 */
+	public function reply_screen_name() {
+		return (isset($this->data) ? $this->data->in_reply_to_screen_name : null);
+	}
+	
+	/**
+	 * Accessor function for tweet reply-to tweet id
+	 *
+	 * @return string
+	 */
+	public function reply_id() {
+		return (isset($this->data) ? $this->data->in_reply_to_status_id_str : null);
+	}
+	
+	/**
+	 * Accessor function for tweet's hashtags
+	 *
+	 * @return string
+	 */
+	public function hashtags() {
+		return (isset($this->data) && isset($this->data->entities) ? $this->data->entities->hashtags : array());
+	}
+	
+	/**
+	 * Accessor function for tweet's mentions
+	 *
+	 * @return string
+	 */
+	public function mentions() {
+		return (isset($this->data) && isset($this->data->entities) ? $this->data->entities->user_mentions : array());
+	}
+	
+	/**
+	 * Accessor function for tweet's URLS
+	 *
+	 * @return string
+	 */
+	public function urls() {
+		return (isset($this->data) && isset($this->data->entities) ? $this->data->entities->urls : array());
+	}
+	
+	/**
+	 * Accessor function for tweet's status URL on Twitter
+	 *
+	 * @return string
+	 */
+	public function status_url() {
+		if ($username = $this->username() && $id = $this->id()) {
+			return AKTT::status_url($username, $id);
+		}
+		return null;
+	}
+	
+	/**
+	 * Takes the twitter date format and gets a timestamp from it
+	 *
+	 * @param string $date - "Fri Aug 05 20:33:38 +0000 2011"
+	 * @return int - timestamp
+	 */
+	static function twdate_to_time($date) {
+		$parts = explode(' ', $date);
+		return strtotime($parts[1].' '.$parts[2].', '.$parts[5].' '.$parts[3]);
+	}
+	
+	
+	/**
+	 * See if the tweet ID matches any tweet ID post meta value
+	 *
+	 * @return bool
+	 */
+	function exists() {
+		$test = $this->get_post(AKTT::$post_type);
+		return (bool) (count($test) > 0);
+	}
+	
+	function exists_by_guid() {
+		global $wpdb;
+		$guid = $this->guid();
+		if (empty($guid)) {
+			return false;
+		}
+		$count = $wpdb->get_var($wpdb->prepare("
+			SELECT COUNT(ID)
+			FROM $wpdb->posts
+			WHERE guid = %s
+		", $guid));
+		return (bool) $count;
+	}
+	
+	/**
+	 * Checks the posts to see if this tweet has been attached to 
+	 * any of them.
+	 *
+	 * @return bool
+	 */
+	function tweet_post_exists() {
+		$posts = $this->get_post('post');
+		return (bool) (count($posts) > 0);
+	}
+
+	
+	/**
+	 * Generate a GUID for WP post based on tweet ID.
+	 *
+	 * @return mixed
+	 */
+	static function guid_from_twid($tweet_id = null) {
+		return (empty($tweet_id) ? false : 'http://twitter-'.$tweet_id);
+	}
+
+
+	/**
+	 * Generate a GUID for WP post based on this objects' tweet ID.
+	 *
+	 * @uses guid_from_twid
+	 *
+	 * @return mixed
+	 */
+	function guid() {
+		return $this->guid_from_twid($this->id());
+	}
+	
+	
+	/**
+	 * Grabs the post from the DB
+	 * 
+	 * @uses get_posts
+	 *
+	 * @return obj|false 
+	 */
+	function get_post($post_type = null) {
+		if (isset($this->post)) {
+			$this->post_id = $this->post->ID;
+			return $this->post;
+		}
+		if (is_null($post_type)) {
+			$post_type = AKTT::$post_type;
+		}
+// TODO (future) - search by GUID instead?
+		$posts = get_posts(array(
+			'post_type' => $post_type,
+			'meta_key' => '_aktt_tweet_id',
+			'meta_value' => $this->id,
+		));
+		if (!is_array($posts)) {
+			return false;
+		}
+		else {
+			$this->post = array_shift($posts);
+			$this->post_id = $this->post->ID;
+			return $this->post;
+		}
+	}
+	
+
+	/**
+	 * Twitter data changed - users still expect anything starting with @ is a reply
+	 *
+	 * @return bool
+	 */
+	function is_reply() {
+		return (bool) (AKTT::substr($this->content(), 0, 1) == '@' || !empty($this->data->in_reply_to_screen_name));
+	}
+	
+
+	/**
+	 * Is this a retweet?
+	 *
+	 * @return bool
+	 */
+	function is_retweet() {
+		return (bool) (AKTT::substr($this->content(), 0, 2) == 'RT' || !empty($this->data->retweeted_status));
+	}
+	
+	
+	/**
+	 * Look up whether this tweet came from a broadcast
+	 *
+	 * @return bool
+	 */
+	function was_broadcast() {
+		$was_broadcast = false;
+		if (isset($this->data) && !empty($this->data->source)) {
+			$was_broadcast = (bool) (strpos($this->data->source, 'sopresto.mailchimp.com') !== false);
+		}
+		else {
+			$was_broadcast = (bool) (strpos($this->content(), home_url()) !== false);
+		}
+		return $was_broadcast;
+	}
+	
+	function link_entities($defer_to_anywhere = true) {
+		$entities = array();
+// mentions
+		$anywhere = Social::option('twitter_anywhere_api_key');
+		if (!$defer_to_anywhere || empty($anywhere) || is_feed()) {
+			foreach ($this->mentions() as $entity) {
+				$entities['start_'.str_pad($entity->indices[0], 5, '0', STR_PAD_LEFT)] = array(
+					'find' => $entity->screen_name,
+					'replace' => AKTT::profile_link($entity->screen_name),
+					'start' => $entity->indices[0],
+					'end' => $entity->indices[1],
+				);
+			}
+		}
+// hashtags
+		foreach ($this->hashtags() as $entity) {
+			$entities['start_'.str_pad($entity->indices[0], 5, '0', STR_PAD_LEFT)] = array(
+				'find' => $entity->text,
+				'replace' => AKTT::hashtag_link($entity->text),
+				'start' => $entity->indices[0],
+				'end' => $entity->indices[1],
+			);
+		}
+// URLs
+		foreach ($this->urls() as $entity) {
+			$entities['start_'.str_pad($entity->indices[0], 5, '0', STR_PAD_LEFT)] = array(
+				'find' => $entity->url,
+				'replace' => '<a href="'.esc_url($entity->expanded_url).'">'.esc_html($entity->display_url).'</a>',
+				'start' => $entity->indices[0],
+				'end' => $entity->indices[1],
+			);
+		}
+		ksort($entities);
+		$str = $this->content();
+		$diff = 0;
+		foreach ($entities as $entity) {
+			$start = $entity['start'] + $diff;
+			$end = $entity['end'] + $diff;
+// $log = array();
+// $log[] = 'diff: '.$diff;
+// $log[] = 'entity start: '.$entity['start'];
+// $log[] = 'entity start chars: '.AKTT::substr($this->content(), $entity['start'], 3);
+// $log[] = 'diff start: '.$start;
+// $log[] = 'diff start chars: '.AKTT::substr($str, $start, 3);
+// $log[] = 'entity end: '.$entity['end'];
+// $log[] = 'diff end: '.$end;
+// $log[] = 'find len: '.AKTT::strlen($entity['find']);
+// $log[] = 'find: '.htmlspecialchars($entity['find']);
+// $log[] = 'replace len: '.AKTT::strlen($entity['replace']);
+// $log[] = 'replace: '.htmlspecialchars($entity['replace']);
+// echo '<p>'.implode('<br>', $log).'</p>';
+			$str = AKTT::substr_replace($str, $entity['replace'], $start, ($end - $start));
+			$diff += AKTT::strlen($entity['replace']) - ($end - $start);
+		}
+		return $str;
+	}
+	
+	
+	/**
+	 * Parse tweet data and set taxonomies accordingly
+	 *
+	 * @param array $args 
+	 * @return void
+	 */
+	function set_taxonomies() {
+		if (empty($this->post_id)) {
+			return;
+		}
+		$tax_input = array(
+			'aktt_accounts' => array($this->username()),
+			'aktt_hashtags' => array(),
+			'aktt_mentions' => array(),
+			'aktt_types' => array(),
+		);
+		foreach ($this->hashtags() as $hashtag) {
+			$tax_input['aktt_hashtags'][] = $hashtag->text;
+		}
+		foreach ($this->mentions() as $mention) {
+			$tax_input['aktt_mentions'][] = $mention->screen_name;
+		}
+		$special = 0;
+		if ($this->is_reply()) {
+			$special++;
+			$tax_input['aktt_types'][] = 'reply';
+		}
+		else {
+			$tax_input['aktt_types'][] = 'not-a-reply';
+		}
+		if ($this->is_retweet()) {
+			$special++;
+			$tax_input['aktt_types'][] = 'retweet';
+		}
+		else {
+			$tax_input['aktt_types'][] = 'not-a-retweet';
+		}
+		if ($this->was_broadcast()) {
+			$special++;
+			$tax_input['aktt_types'][] = 'social-broadcast';
+		}
+		else {
+			$tax_input['aktt_types'][] = 'not-a-social-broadcast';
+		}
+		if (!$special) {
+			$tax_input['aktt_types'][] = 'status';
+		}
+		$tax_input = apply_filters('aktt_tweet_tax_input', $tax_input);
+		foreach ($tax_input as $tax => $terms) {
+			if (count($terms)) {
+				wp_set_post_terms($this->post_id, $terms, $tax);
+			}
+		}
+	}
+	
+
+	/**
+	 * Does this tweet have a photo?
+	 *
+	 * @return bool
+	 */
+	function has_image() {
+		return (
+			!empty($this->data->entities->media) && 
+			$this->data->entities->media[0]->type == 'photo'
+		);
+	}
+	
+
+	/**
+	 * Download and save tweet image
+	 *
+	 * @return mixed int|null
+	 */
+	function sideload_image() {
+		if ($this->has_image()) {
+			$url = $this->data->entities->media[0]->media_url;
+			$id = aktt_sideload_image($url, $this->post_id);
+			if (!is_wp_error($id)) {
+				return $id;
+			}
+		}
+		return null;
+	}
+	
+
+	/**
+	 * Creates an aktt_tweet post_type with its meta
+	 *
+	 * @param array $args 
+	 * @return void
+	 */
+	function add() {
+		$gmt_time = self::twdate_to_time($this->date());
+		// Build the post data
+		$data = apply_filters('aktt_tweet_add', array(
+			'post_title' => $this->title(),
+			'post_name' => $this->id(),
+			'post_content' => $this->content(),
+			'post_status' => 'publish',
+			'post_type' => AKTT::$post_type,
+			'post_date' => date('Y-m-d H:i:s', AKTT::gmt_to_wp_time($gmt_time)),
+			'post_date_gmt' => date('Y-m-d H:i:s', $gmt_time),
+			'guid' => $this->guid(),
+//			'tax_input' => $tax_input, // see below...
+		));
+		$post_id = wp_insert_post(addslashes_deep($data), true);
+		if (is_wp_error($post_id)) {
+			AKTT::log('WP_Error:: '.$post_id->get_error_message());
+			return false;
+		}
+		$this->post_id = $post_id;
+
+		// have to set up taxonomies after the insert in case we are in a context without
+		// a 'current user' - see: http://core.trac.wordpress.org/ticket/19373
+		$this->set_taxonomies();
+
+		// if there is a photo, add it
+		$this->featured_image_id = $this->sideload_image();
+		if (!empty($this->featured_image_id)) {
+			update_post_meta($this->post_id, '_thumbnail_id', $this->featured_image_id);
+		}
+		
+		update_post_meta($this->post_id, '_aktt_tweet_id', $this->id());
+		update_post_meta($this->post_id, '_aktt_tweet_raw_data', addslashes($this->raw_data));
+		
+		// Allow things to hook in here
+		do_action('AKTT_Tweet_added', $this);
+		
+		return true;
+	}
+	
+	
+	/**
+	 * Replace the raw Twitter data for a tweet
+	 *
+	 * @param stdClass $tweet_data 
+	 * @return bool
+	 */
+	function update_twitter_data($tweet_data) {
+		$this->data = $tweet_data;
+		$this->raw_data = json_encode($tweet_data);
+		$post = $this->get_post();
+		if ($post && !empty($post->ID)) {
+			if (update_post_meta($post->ID, '_aktt_tweet_raw_data', addslashes($this->raw_data))) {
+				delete_post_meta($post->ID, '_aktt_30_backfill_needed', 1);
+				$this->set_taxonomies();
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	
+	/**
+	 * Create blog post from tweet
+	 *
+	 * @param array $args 
+	 * @return bool
+	 */
+	function create_blog_post($args = array()) {
+		extract($args);
+		
+		// Add a space if we have a prefix
+		$title_prefix = empty($title_prefix) ? '' : trim($title_prefix).' ';
+
+		$post_content = $this->link_entities(false);
+		// Append image to post if there is one, can't set it as a featured image until after save
+		if (!empty($this->featured_image_id)) {
+			$size = apply_filters('aktt_featured_image_size', 'medium');
+			$post_content .= "\n\n".wp_get_attachment_image($this->featured_image_id, $size);
+		}
+		
+		$gmt_time = self::twdate_to_time($this->date());
+		
+		// Build the post data
+		$data = array(
+			'post_title' => $title_prefix.$this->title(),
+			'post_content' => $post_content,
+			'post_author' => $post_author,
+// see below
+// 			'tax_input' => array(
+// 				'category' => array($post_category),
+// 				'post_tag' => array_map('trim', explode(',', $post_tags)),
+// 			),
+			'post_status' => 'publish',
+			'post_type' => 'post',
+			'post_date' => date('Y-m-d H:i:s', AKTT::gmt_to_wp_time($gmt_time)),
+			'post_date_gmt' => date('Y-m-d H:i:s', $gmt_time),
+			'guid' => $this->guid().'-post'
+		);
+		$data = apply_filters('aktt_tweet_create_blog_post_data', $data);
+
+		// hook in here if you want to conditionally skip blog post creation
+		if (!apply_filters('aktt_tweet_create_blog_post', true, $data, $this)) {
+			return false;
+		}
+
+		$this->blog_post_id = wp_insert_post($data, true);
+		
+		if (is_wp_error($this->blog_post_id)) {
+			AKTT::log('WP_Error:: '.$this->blog_post_id->get_error_message());
+			return false;
+		}
+		
+		// have to set up taxonomies after the insert in case we are in a context without
+		// a 'current user' - see: http://core.trac.wordpress.org/ticket/19373
+		wp_set_object_terms($this->blog_post_id, intval($post_category), 'category');
+		wp_set_object_terms($this->blog_post_id, array_map('trim', explode(',', $post_tags)), 'post_tag');
+
+		// hook in here and return false to not set the format to "status", 
+		// or return another format to use that format instead of status
+		if ($post_format = apply_filters('aktt_tweet_create_blog_post_format', 'status', $data, $this)) {
+			set_post_format($this->blog_post_id, $post_format);
+		}
+		
+		if (!empty($this->featured_image_id)) {
+			update_post_meta($this->blog_post_id, '_thumbnail_id', $this->featured_image_id);
+		}
+
+		update_post_meta($this->blog_post_id, '_aktt_tweet_id', $this->id()); // twitter's tweet ID
+		update_post_meta($this->blog_post_id, '_aktt_tweet_post_id', $this->post_id); // twitter's post ID
+		
+		// Add it to the tweet's post_meta as well
+		update_post_meta($this->post_id, '_aktt_tweet_blog_post_id', $this->blog_post_id);
+		
+		// Let Social know to aggregate info about this post
+		$account = false;
+		foreach (AKTT::$accounts as $aktt_account) {
+			if ($aktt_account->social_acct->id() == $this->data->user->id_str) {
+				$account = $aktt_account->social_acct;
+				break;
+			}
+		}
+		if ($account) {
+			Social::instance()->add_broadcasted_id(
+				$this->blog_post_id,
+				'twitter',
+				$this->id(),
+				$this->content(),
+				$account,
+				null
+			);
+		}
+		
+		// Let the account know we were successful
+		return true;
+	}
+	
+	
+	/**
+	 * Gets the name of a tag from its ID
+	 *
+	 * @param int $tag_id 
+	 * @return string
+	 */
+	function get_tag_name($tag_id) {
+		$tag_name = get_term_field('name', $tag_id, 'post_tag', 'db');
+		if (is_wp_error($tag_name)) {
+			$tag_name = '';
+		}
+		return $tag_name;
+	}
+	
+}
+