Files
server/usr/share/psa-horde/lib/Ajax/Application/TwitterHandler.php
2026-01-07 20:52:11 +01:00

291 lines
10 KiB
PHP

<?php
/**
* Copyright 2009-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL-2). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl.
*
* @author Michael J Rubinsky <mrubinsk.horde.org>
* @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 <mrubinsk.horde.org>
* @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 = '<a target="_blank" href="' . $link->url . '" title="' . $link->expanded_url . '">' . htmlspecialchars($link->display_url) . '</a>';
$map[$link->indices[0]] = array($link->indices[1], $replace);
}
if (!empty($tweet->entities->media)) {
foreach ($tweet->entities->media as $picture) {
$replace = '<a target="_blank" href="' . $picture->url . '" title="' . $picture->expanded_url . '">' . htmlentities($picture->display_url, ENT_COMPAT, 'UTF-8') . '</a>';
$map[$picture->indices[0]] = array($picture->indices[1], $replace);
$previews[] = ' <a href="#" onclick="return Horde[\'twitter' . $instance . '\'].showPreview(\'' . $picture->media_url . ':small\');"><img src="' . Horde_Themes::img('mime/image.png') . '" /></a>';
}
}
if (!empty($tweet->entities->user_mentions)) {
foreach ($tweet->entities->user_mentions as $user) {
$replace = ' <a target="_blank" title="' . $user->name . '" href="http://twitter.com/' . $user->screen_name . '">@' . htmlentities($user->screen_name, ENT_COMPAT, 'UTF-8') . '</a>';
$map[$user->indices[0]] = array($user->indices[1], $replace);
}
}
if (!empty($tweet->entities->hashtags)) {
foreach ($tweet->entities->hashtags as $hashtag) {
$replace = ' <a target="_blank" href="http://twitter.com/search?q=#' . urlencode($hashtag->text) . '">#' . htmlentities($hashtag->text, ENT_COMPAT, 'UTF-8') . '</a>';
$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'));
}
}
}