* @category Horde * @license http://www.horde.org/licenses/lgpl LGPL-2 * @package Horde */ /** * Defines the AJAX actions used in the Twitter client. * * @author Michael J Rubinsky * @category Horde * @license http://www.horde.org/licenses/lgpl LGPL-2 * @package Horde */ class Horde_Ajax_Application_TwitterHandler extends Horde_Core_Ajax_Application_Handler { /** * Update the twitter timeline. * * @return array An hash containing the following keys: * - o: The id of the oldest tweet * - n: The id of the newest tweet * - c: The HTML content */ public function twitterUpdate() { global $conf; if (empty($conf['twitter']['enabled'])) { return _("Twitter not enabled."); } switch ($this->vars->actionID) { case 'getPage': return $this->_doTwitterGetPage(); } } /** * Retweet a tweet. Expects the following in $this->vars: * - tweetId: The tweet id to retweet. * - i: * * @return string The HTML to render the newly retweeted tweet. */ public function retweet() { $twitter = $this->_getTwitterObject(); try { $tweet = json_decode($twitter->statuses->retweet($this->vars->tweetId)); $html = $this->_buildTweet($tweet)->render('twitter_tweet'); return $html; } catch (Horde_Service_Twitter_Exception $e) { $this->_twitterError($e); } } /** * Favorite a tweet. Expects: * - tweetId: * * @return stdClass */ public function favorite() { $twitter = $this->_getTwitterObject(); try { return json_decode($twitter->favorites->create($this->vars->tweetId)); } catch (Horde_Service_Twitter_Exception $e) { $this->_twitterError($e); } } /** * Unfavorite a tweet. Expects: * - tweetId: */ public function unfavorite() { $twitter = $this->_getTwitterObject(); try { return json_decode($twitter->favorites->destroy($this->vars->tweetId)); } catch (Horde_Service_Twitter_Exception $e) { $this->_twitterError($e); } } /** * Update twitter status. Expects: * - inReplyTo: * - statusText: * * @return string The HTML text of the new tweet. */ public function updateStatus() { $twitter = $this->_getTwitterObject(); if ($inreplyTo = $this->vars->inReplyTo) { $params = array('in_reply_to_status_id', $inreplyTo); } else { $params = array(); } try { $tweet = json_decode($twitter->statuses->update($this->vars->statusText, $params)); return $this->_buildTweet($tweet)->render('twitter_tweet'); } catch (Horde_Service_Twitter_Exception $e) { $this->_twitterError($e); } } /** * * @return Horde_Service_Twitter */ protected function _getTwitterObject() { $twitter = $GLOBALS['injector']->getInstance('Horde_Service_Twitter'); $token = unserialize($GLOBALS['prefs']->getValue('twitter')); if (!empty($token['key']) && !empty($token['secret'])) { $auth_token = new Horde_Oauth_Token($token['key'], $token['secret']); $twitter->auth->setToken($auth_token); } return $twitter; } /** * Helper method to build a view object for a tweet. * * @param stdClass $tweet The tweet object. * * @return Horde_View The view object, populated with tweet data. */ protected function _buildTweet($tweet) { global $injector, $registry; $view = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block')); $view->addHelper('Tag'); $view->ajax_uri = $registry->getServiceLink('ajax', $registry->getApp()); $filter = $injector->getInstance('Horde_Core_Factory_TextFilter'); $instance = $this->vars->i; // Links and media $map = $previews = array(); foreach ($tweet->entities->urls as $link) { $replace = '' . htmlspecialchars($link->display_url) . ''; $map[$link->indices[0]] = array($link->indices[1], $replace); } if (!empty($tweet->entities->media)) { foreach ($tweet->entities->media as $picture) { $replace = '' . htmlentities($picture->display_url, ENT_COMPAT, 'UTF-8') . ''; $map[$picture->indices[0]] = array($picture->indices[1], $replace); $previews[] = ' '; } } if (!empty($tweet->entities->user_mentions)) { foreach ($tweet->entities->user_mentions as $user) { $replace = ' @' . htmlentities($user->screen_name, ENT_COMPAT, 'UTF-8') . ''; $map[$user->indices[0]] = array($user->indices[1], $replace); } } if (!empty($tweet->entities->hashtags)) { foreach ($tweet->entities->hashtags as $hashtag) { $replace = ' #' . htmlentities($hashtag->text, ENT_COMPAT, 'UTF-8') . ''; $map[$hashtag->indices[0]] = array($hashtag->indices[1], $replace); } } $body = ''; $pos = 0; while ($pos <= Horde_String::length($tweet->text) - 1) { if (!empty($map[$pos])) { $entity = $map[$pos]; $body .= $entity[1]; $pos = $entity[0]; } else { $body .= Horde_String::substr($tweet->text, $pos, 1); ++$pos; } } foreach ($previews as $preview) { $body .= $preview; } $view->body = $body; /* If this is a retweet, use the original author's profile info */ if (!empty($tweet->retweeted_status)) { $tweetObj = $tweet->retweeted_status; } else { $tweetObj = $tweet; } /* These are all referencing the *original* tweet */ $view->profileLink = Horde::externalUrl('http://twitter.com/' . htmlspecialchars($tweetObj->user->screen_name), true); $view->profileImg = $GLOBALS['browser']->usingSSLConnection() ? $tweetObj->user->profile_image_url_https : $tweetObj->user->profile_image_url; $view->authorName = '@' . htmlspecialchars($tweetObj->user->screen_name); $view->authorFullname = htmlspecialchars($tweetObj->user->name); $view->createdAt = $tweetObj->created_at; $view->clientText = $filter->filter($tweet->source, 'xss'); $view->tweet = $tweet; $view->instanceid = $instance; return $view; } /** * Helper method for getting a slice of tweets. * * Expects the following in $this->vars: * - max_id: * - since_id: * - i: * - mentions: * * @return [type] [description] */ protected function _doTwitterGetPage() { $twitter = $this->_getTwitterObject(); try { $params = array('include_entities' => 1); if ($max = $this->vars->max_id) { $params['max_id'] = $max; } elseif ($since = $this->vars->since_id) { $params['since_id'] = $since; } if ($this->vars->mentions) { $stream = Horde_Serialize::unserialize($twitter->statuses->mentions($params), Horde_Serialize::JSON); } else { $stream = Horde_Serialize::unserialize($twitter->statuses->homeTimeline($params), Horde_Serialize::JSON); } } catch (Horde_Service_Twitter_Exception $e) { $this->_twitterError($e); return; } if (count($stream)) { $newest = $stream[0]->id_str; } else { $newest = $params['since_id']; $oldest = 0; } $view = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block')); $view->addHelper('Tag'); $html = ''; foreach ($stream as $tweet) { /* Don't return the max_id tweet, since we already have it */ if (!empty($params['max_id']) && $params['max_id'] == $tweet->id_str) { continue; } $view = $this->_buildTweet($tweet); $oldest = $tweet->id_str; $html .= $view->render('twitter_tweet'); } $result = array( 'o' => $oldest, 'n' => $newest, 'c' => $html ); return $result; } protected function _twitterError($e) { global $notification; Horde::log($e, 'INFO'); $body = ($e instanceof Exception) ? $e->getMessage() : $e; if (($errors = json_decode($body, true)) && isset($errors['errors'])) { $errors = $errors['errors']; } else { $errors = array(array('message' => $body)); } $notification->push(_("Error connecting to Twitter. Details have been logged for the administrator."), 'horde.error', array('sticky')); foreach ($errors as $error) { $notification->push($error['message'], 'horde.error', array('sticky')); } } }