--- /dev/null
+/**
+ * Facebook client javascript.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde
+ */
+
+var Horde_Facebook = Class.create({
+
+ oldest: '',
+ newest: '',
+ opts: {},
+
+ /**
+ * opts.spinner
+ * opts.input
+ * opts.refreshrate
+ * opts.content
+ * opts.endpoint,
+ * opts.notifications
+ * opts.getmore
+ * opts.button
+ * opts.instance
+ *
+ *
+ */
+ initialize: function(opts)
+ {
+ this.opts = Object.extend({
+ refreshrate: 300
+ }, opts);
+
+ this.getNewEntries();
+ $(this.opts.getmore).observe('click', function() { this.getOlderEntries(); return false; }.bind(this));
+ $(this.opts.button).observe('click', function() { this.updateStatus(); return false; }.bind(this));
+ },
+
+ /**
+ * Update FB status.
+ *
+ * @param string statusText The new status text.
+ * @param string inputNode The DOM Element for the input box.
+ *
+ * @return void
+ */
+ updateStatus: function()
+ {
+ $(this.opts.spinner).toggle();
+ params = new Object();
+ params.actionID = 'updateStatus';
+ params.statusText = $F(this.opts.input);
+ new Ajax.Updater({success:'currentStatus'},
+ this.opts.endpoint,
+ {
+ method: 'post',
+ parameters: params,
+ onComplete: function() {
+ $(this.opts.input).value = '';
+ $(this.opts.spinner).toggle()
+ },
+ onFailure: function() {$(this.opts.spinner).toggle()}
+ }
+ );
+ },
+
+ addLike: function(post_id)
+ {
+ $(this.opts.spinner).toggle();
+ var params = {
+ actionID: 'addLike',
+ post_id: post_id
+ };
+ new Ajax.Updater(
+ {success:'fb' + post_id},
+ this.opts.endpoint,
+ {
+ method: 'post',
+ parameters: params,
+ onComplete: function() {$(this.opts.spinner).toggle()}.bind(this),
+ onFailure: function() {$(this.opts.spinner).toggle()}.bind(this)
+ }
+ );
+
+ return false;
+ },
+
+ getOlderEntries: function() {
+ var params = {
+ 'actionID': 'getStream',
+ 'newest': this.oldest,
+ 'instance': this.opts.instance
+ };
+ new Ajax.Request(this.opts.endpoint, {
+ method: 'post',
+ parameters: params,
+ onSuccess: this._getOlderEntriesCallback.bind(this),
+ onFailure: function() {
+ $(this.opts.spinner).toggle();
+ }
+ });
+ },
+
+ _getOlderEntriesCallback: function(response)
+ {
+ var content = response.responseJSON.c;
+ this.oldest = response.responseJSON.o;
+ var h = $(this.opts.content).scrollHeight
+ $(this.opts.content).insert(content);
+ $(this.opts.content).scrollTop = h;
+ },
+
+ getNewEntries: function()
+ {
+ var params = {
+ 'actionID': 'getStream',
+ 'notifications': this.opts.notifications,
+ 'oldest': this.oldest,
+ 'newest': this.newest,
+ 'instance': this.opts.instance
+ };
+ new Ajax.Request(this.opts.endpoint, {
+ method: 'post',
+ parameters: params,
+ onSuccess: this._getNewEntriesCallback.bind(this),
+ onFailure: function() {
+ $(this.opts.spinner).toggle();
+ }
+ });
+ },
+
+ _getNewEntriesCallback: function(response)
+ {
+ $(this.opts.content).insert({ 'top': response.responseJSON.c });
+ $(this.opts.notifications).update(response.responseJSON.nt);
+
+ this.newest = response.responseJSON.n;
+ if (!this.oldest) {
+ this.oldest = response.responseJSON.o;
+ }
+ }
+
+});
/**
* Whether this block has changing content.
+ *
+ * Set this to false, since we handle the updates via AJAX on our own.
*/
- var $updateable = true;
+ var $updateable = false;
/**
* @var string
*/
public function __construct($params = array(), $row = null, $col = null)
{
- $GLOBALS['injector']->addBinder('Facebook', new Horde_Core_Binder_Facebook());
try {
- $this->_facebook = $GLOBALS['injector']->getInstance('Facebook');
+ $this->_facebook = $GLOBALS['injector']->getInstance('Horde_Service_Facebook');
} catch (Horde_Exception $e) {
return $e->getMessage();
}
- $this->_fbp = unserialize($GLOBALS['prefs']->getValue('facebook'));
+ /* Authenticate the client */
+ $this->_fbp = unserialize($GLOBALS['prefs']->getValue('facebook'));
if (!empty($this->_fbp['sid'])) {
$this->_facebook->auth->setUser($this->_fbp['uid'], $this->_fbp['sid'], 0);
}
parent::__construct($params, $row, $col);
}
+ /**
+ *
+ * @return array
+ */
function _params()
{
$filters = array();
if (!empty($this->_fbp['sid'])) {
- // Use a raw fql query to reduce the amount of data that is
- // returned.
$fql = 'SELECT filter_key, name FROM stream_filter WHERE uid="'
. $this->_fbp['uid'] . '"';
-
try {
$stream_filters = $this->_facebook->fql->run($fql);
foreach ($stream_filters as $filter) {
*/
function _content()
{
+ $instance = md5(mt_rand());
$csslink = $GLOBALS['registry']->get('themesuri', 'horde') . '/facebook.css';
$endpoint = Horde::url('services/facebook.php', true);
- $spinner = '$(\'loading\')';
- $html = <<<EOF
- <script type="text/javascript">
- function updateStatus(statusText, inputNode)
- {
- {$spinner}.toggle();
- params = new Object();
- params.actionID = 'updateStatus';
- params.statusText = statusText;
- new Ajax.Updater({success:'currentStatus'},
- '$endpoint',
- {
- method: 'post',
- parameters: params,
- onComplete: function() {inputNode.value = '';{$spinner}.toggle()},
- onFailure: function() {{$spinner}.toggle()}
- }
- );
- }
- function addLike(post_id)
- {
- {$spinner}.toggle();
- params = new Object();
- params.actionID = 'addLike';
- params.post_id = post_id;
- new Ajax.Updater({success:'fb' + post_id},
- '$endpoint',
- {
- method: 'post',
- parameters: params,
- onComplete: function() {{$spinner}.toggle()},
- onFailure: function() {{$spinner}.toggle()}
- }
- );
-
- return false;
- }
-
- </script>
-EOF;
-
+ $html = '';
+
+ /* Add the client javascript / initialize it */
+ Horde::addScriptFile('facebookclient.js');
+ $script = <<<EOT
+ var Horde = window.Horde || {};
+ Horde['{$instance}_facebook'] = new Horde_Facebook({
+ spinner: '{$instance}_loading',
+ endpoint: '{$endpoint}',
+ content: '{$instance}_fbcontent',
+ status: '{$instance}_currentStatus',
+ notifications: '{$instance}_fbnotifications',
+ getmore: '{$instance}_getmore',
+ 'input': '{$instance}_newStatus',
+ 'button': '{$instance}_button',
+ instance: '{$instance}'
+ });
+EOT;
+ Horde::addInlineScript($script, 'dom');
+
+ /* Init facebook driver, exit early if no prefs exist */
$facebook = $this->_facebook;
$fbp = $this->_fbp;
-
- // If no prefs exist -------
if (empty($fbp['sid'])) {
return sprintf(_("You have not properly connected your Facebook account with Horde. You should check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
}
- // Get stream
- try {
- $stream = $facebook->streams->get('', array(), '', '', $this->_params['count'], $this->_params['filter']);
- } catch (Horde_Service_Facebook_Exception $e) {
- $html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
- $html .= sprintf(_("You can also check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
- return $html;
- }
+ /* Build the UI */
+ $html .= '<link href="' . $csslink . '" rel="stylesheet" type="text/css" />';
+ $html .= '<div style="padding-left: 8px;padding-right:8px;">';
- //Do we want notifications too?
+ /* Build the Notification Section */
if (!empty($this->_params['notifications'])) {
- try {
- $notifications = $facebook->notifications->get();
- } catch (Horde_Service_Facebook_Exception $e) {
- $html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
- $html .= sprintf(_("You can also check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
- return $html;
- }
- }
-
- $posts = $stream['posts'];
- $profiles = array();
- // Sure would be nice if fb returned these keyed properly...
- foreach ($stream['profiles'] as $profile) {
- $profiles[(string)$profile['id']] = $profile;
+ $html .= '<div class="fbinfobox" id="' . $instance . '_fbnotifications"></div>';
}
- // Bring in the Facebook CSS
- $html .= '<link href="' . $csslink . '" rel="stylesheet" type="text/css" />';
- $html .= '<div style="float:left;padding-left: 8px;padding-right:8px;">';
-
- // User's current status and input box to change it.
+ /* User's current status and input box to change it. */
$fql = 'SELECT first_name, last_name, status, pic_square_with_logo from user where uid=' . $fbp['uid'] . ' LIMIT 1';
try {
$status = $facebook->fql->run($fql);
} catch (Horde_Service_Facebook_Exception $e) {
- $html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
+ $html = sprintf(_("There was an error making the request: %s"), $e->getMessage());
$html .= sprintf(_("You can also check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
+
return $html;
}
$status = array_pop($status);
} else {
$class = '';
}
-
- if (!empty($notifications)) {
- $html .= '<div class="fbinfobox">' . _("New Messages:")
- . ' ' . $notifications['messages']['unread']
- . ' ' . _("Pokes:") . ' ' . $notifications['pokes']['unread']
- . ' ' . _("Friend Requests:") . ' ' . count($notifications['friend_requests'])
- . ' ' . _("Event Invites:") . ' ' . count($notifications['event_invites']) . '</div>';
- }
-
- $html .= '<div class="fbgreybox fbboxfont"><img style="float:left;" src="' . $status['pic_square_with_logo'] . '" /><div id="currentStatus" class="' . $class . '" style="margin-left:55px;">' . $status['status']['message'] . '</div>';
+ $html .= '<div class="fbgreybox fbboxfont">'
+ . '<img style="float:left;" src="' . $status['pic_square_with_logo'] . '" />'
+ . '<div id="' . $instance . '_currentStatus" class="' . $class . '" style="margin-left:55px;">'
+ . $status['status']['message']
+ . '</div>';
+
try {
- //TODO: We could probably cache this perm somehow - maybe in the session?
if ($facebook->users->hasAppPermission(Horde_Service_Facebook_Auth::EXTEND_PERMS_PUBLISHSTREAM)) {
- $html .= '<input style="width:100%;margin-top:4px;margin-bottom:4px;" type="text" class="fbinput" id="newStatus" name="newStatus" />'
- . '<div><a class="button" onclick="updateStatus($F(\'newStatus\'), $(\'newStatus\'));" href="#">' . _("Update") . '</a></div>'
- . Horde::img('loading.gif', '', array('id' => 'loading', 'style' => 'display:none;'));
+ $html .= '<input style="width:100%;margin-top:4px;margin-bottom:4px;" type="text" class="fbinput" id="' . $instance . '_newStatus" name="newStatus" />'
+ . '<div><a class="button" href="#" id="' . $instance . '_button">' . _("Update") . '</a></div>'
+ . Horde::img('loading.gif', '', array('id' => $instance. '_loading', 'style' => 'display:none;'));
}
} catch (Horde_Service_Facebook_Exception $e) {
$html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
return $html;
}
$html .= '</div>'; // Close the fbgreybox node that wraps the status
- // Build the stream feed.
- $html .= '<div style="height:' . (empty($this->_params['height']) ? 300 : $this->_params['height']) . 'px;overflow-y:auto;">';
- foreach ($posts as $post) {
- $html .= '<div class="fbstreamstory">';
- $html .= '<div class="fbstreampic"><img style="float:left;" src="' . $profiles[(string)$post['actor_id']]['pic_square'] . '" /></div>';
- // fbstreambody wraps all content except the actor's image. This
- // displays the actor's name and any message he/she added.
- $html .= ' <div class="fbstreambody">'
- . Horde::externalUrl($profiles[(string)$post['actor_id']]['url'], true)
- . $profiles[(string)$post['actor_id']]['name'] . '</a> '
- . (empty($post['message']) ? '' : $post['message']);
- // Parse any attachments
- if (!empty($post['attachment'])) {
- $html .= '<div class="fbattachment">';
- if (!empty($post['attachment']['media']) && count($post['attachment']['media'])) {
- $html .= '<div class="fbmedia' . (count($post['attachment']['media']) > 1 ? ' fbmediawide' : '') . '">';
- // Decide what mediaitem css class to use for padding and
- // display the media items.
- $multiple = false;
- $single = count($post['attachment']['media']) == 1;
- foreach ($post['attachment']['media'] as $item) {
- $link = Horde::externalUrl($item['href'], true);
- $img = '<img src="' . htmlspecialchars($item['src']) . '" />';
- if ($single) {
- $html .= '<div class="fbmediaitem fbmediaitemsingle">' . $link . $img . '</a></div>';
- } else {
- $html .= '<div class="fbmediaitem' . ($multiple ? ' fbmediaitemmultiple' : '') . '">' . $link . $img . '</a></div>';
- $multiple = true;
- }
- }
- $html .= '</div>'; // Close the fbmedia node
- }
-
- // Attachment properties.
- if (!empty($post['attachment']['name'])) {
- $link = Horde::externalUrl($post['attachment']['href'], true);
- $html .= '<div class="fbattachmenttitle">' . $link . $post['attachment']['name'] . '</a></div>';
- }
- if (!empty($post['attachment']['caption'])) {
- $html .= '<div class="fbattachmentcaption">' . $post['attachment']['caption'] . '</div>';
- }
- if (!empty($post['attachment']['description'])) {
- $html .= '<div class="fbattachmentcopy">' . $post['attachment']['description'] . '</div>';
- }
-
- $html .= '</div>'; // Close the fbattachemnt node.
- }
-
- // Build the likes string to display.
- if (empty($post['likes']['user_likes']) && !empty($post['likes']['can_like'])) {
- $like = '<a href="#" onclick="return addLike(\'' . $post['post_id'] . '\');">' . _("Like") . '</a>';
- } else {
- $like = '';
- }
- $html .= '<div class="fbstreaminfo">' . sprintf(_("Posted %s"), Horde_Date_Utils::relativeDateTime($post['created_time'], $GLOBALS['prefs']->getValue('date_format'), $GLOBALS['prefs']->getValue('twentyFour') ? "%H:%M %P" : "%I %M %P")) . ' ' . sprintf(_("Comments: %d"), $post['comments']['count']) . '</div>';
- $html .= '<div class="fbstreaminfo" id="fb' . $post['post_id'] . '">';
- if (!empty($post['likes']['user_likes']) && !empty($post['likes']['count'])) {
- $html .= sprintf(ngettext("You and %d other person likes this", "You and %d other people like this", $post['likes']['count'] - 1), $post['likes']['count'] - 1);
- } elseif (!empty($post['likes']['user_likes'])) {
- $html .= _("You like this");
- } elseif (!empty($post['likes']['count'])) {
- $html .= sprintf(ngettext("%d person likes this", "%d persons like this", $post['likes']['count']), $post['likes']['count']) . (!empty($like) ? ' ' . $like : '');
- } elseif (!empty($like)) {
- $html .= $like;
- }
- $html .= '</div>'; // Close the fbstreaminfo node that wraps the like
- $html .= '</div></div>'; // Close the fbstreambody, fbstreamstory nodes
- $html .= '<div class="fbcontentdivider"> </div>';
- }
- $html .= '</div></div>'; // fbbody end
+ // Build the stream feed.
+ $html .= '<div id="' . $instance . '_fbcontent" style="height:' . (empty($this->_params['height']) ? 300 : $this->_params['height']) . 'px;overflow-y:auto;"></div>';
+ $html .= '<div class="control fbgetmore"><a href="#" id="' . $instance . '_getmore">' . _("Get More") . '</a></div>';
+ $html .= '</div>'; // fbbody end
return $html;
}
header('Location: ' . $horde_url);
}
-// See why we are here.
+/* See why we are here. */
if ($token = Horde_Util::getFormData('auth_token')) {
+ /* Is this an authentication sequence? */
// Assume we are here for a successful authentication if we have a
// auth_token. It *must* be allowed to be in GET since that's how FB
// sends it. This is the *only* time we will be able to capture these values.
$url = Horde::url('services/prefs.php', true)->add(array('group' => 'facebook', 'app' => 'horde'));
header('Location: ' . $url);
}
+
} else {
- // Require the rest of the actions to be POST only since following them
- // could change the user's state.
+
+ /* We are here for an Action request */
$action = Horde_Util::getPost('actionID');
switch ($action) {
+ case 'getStream':
+ $fbp = unserialize($prefs->getValue('facebook'));
+ $facebook->auth->setUser($fbp['uid'], $fbp['sid']);
+ try {
+ $count = Horde_Util::getPost('count');
+ $filter = Horde_Util::getPost('filter');
+ $stream = $facebook->streams->get('', array(), Horde_Util::getPost('oldest'), Horde_Util::getPost('newest'), $count, $filter);
+ } catch (Horde_Service_Facebook_Exception $e) {
+ $html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
+ $html .= sprintf(_("You can also check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
+
+ return $html;
+ }
+
+ /* Do we want notifications too? */
+ $n_html = '';
+ if (Horde_Util::getPost('notifications')) {
+ try {
+ $notifications = $facebook->notifications->get();
+ $n_html = _("New Messages:")
+ . ' ' . $notifications['messages']['unread']
+ . ' ' . _("Pokes:") . ' ' . $notifications['pokes']['unread']
+ . ' ' . _("Friend Requests:") . ' ' . count($notifications['friend_requests'])
+ . ' ' . _("Event Invites:") . ' ' . count($notifications['event_invites']);
+ } catch (Horde_Service_Facebook_Exception $e) {
+ $html .= sprintf(_("There was an error making the request: %s"), $e->getMessage());
+ $html .= sprintf(_("You can also check your Facebook settings in your %s."), Horde::getServiceLink('options', 'horde')->add('group', 'facebook')->link() . _("preferences") . '</a>');
+
+ return $html;
+ }
+ }
+
+ /* Start parsing the posts */
+ $posts = $stream['posts'];
+ $profiles = array();
+ $newest = $posts[0]['created_time'];
+ $oldest = $posts[count($posts) -1]['created_time'];
+ $instance = Horde_Util::getPost('instance');
+
+ /* Sure would be nice if fb returned these keyed properly... */
+ foreach ($stream['profiles'] as $profile) {
+ $profiles[(string)$profile['id']] = $profile;
+ }
+
+ /* Build Horde_View for each story */
+ $html = '';
+ foreach ($posts as $post) {
+ $postView = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block'));
+ $postView->actorImgUrl = $profiles[(string)$post['actor_id']]['pic_square'];
+ $postView->actorProfileLink = Horde::externalUrl($profiles[(string)$post['actor_id']]['url'], true) . $profiles[(string)$post['actor_id']]['name'] . '</a>';
+ $postView->message = empty($post['message']) ? '' : $post['message'];
+ $postView->attachment = empty($post['attachment']) ? null : $post['attachment'];
+ $postView->likes = $post['likes'];
+ $postView->postId = $post['post_id'];
+ $postView->postInfo = sprintf(_("Posted %s"), Horde_Date_Utils::relativeDateTime($post['created_time'], $GLOBALS['prefs']->getValue('date_format'), $GLOBALS['prefs']->getValue('twentyFour') ? "%H:%M %P" : "%I %M %P")) . ' ' . sprintf(_("Comments: %d"), $post['comments']['count']);
+
+ /* Build the 'Likes' string. */
+ if (empty($post['likes']['user_likes']) && !empty($post['likes']['can_like'])) {
+ $like = '<a href="#" onclick="Horde[\'' . $instance . '_facebook\'].addLike(\'' . $post['post_id'] . '\');return false;">' . _("Like") . '</a>';
+ } else {
+ $like = '';
+ }
+ if (!empty($post['likes']['user_likes']) && !empty($post['likes']['count'])) {
+ $likes = sprintf(ngettext("You and %d other person likes this", "You and %d other people like this", $post['likes']['count'] - 1), $post['likes']['count'] - 1);
+ } elseif (!empty($post['likes']['user_likes'])) {
+ $likes = _("You like this");
+ } elseif (!empty($post['likes']['count'])) {
+ $likes = sprintf(ngettext("%d person likes this", "%d persons like this", $post['likes']['count']), $post['likes']['count']) . (!empty($like) ? ' ' . $like : '');
+ } else {
+ $likes = $like;
+ }
+ $postView->likesInfo = $likes;
+ $html .= $postView->render('facebook_story');
+ }
+
+ /* Build response structure */
+ $result = array(
+ 'o' => $oldest,
+ 'n' => $newest,
+ 'c' => $html,
+ 'nt' => $n_html
+ );
+ header('Content-Type: application/json');
+ echo Horde_Serialize::serialize($result, Horde_Serialize::JSON);
+ exit;
case 'updateStatus':
// Set the user's status
$fbp = unserialize($prefs->getValue('facebook'));