--- /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;
+ }
+
+}
+