From 26dfe1d2eef0e3ca0651735b927dbb2eb7458a7e Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Mon, 5 Jul 2010 17:45:19 -0400 Subject: [PATCH] Updates to Facebook client: be less obtrusive where we can, fix Like action. Use Horde_View for the Facebook stream. add Get More Entries action use the start and end parameters seperate out facebook js, retrieve info via ajax --- horde/js/facebookclient.js | 145 +++++++++++++++++ horde/lib/Block/fb_stream.php | 216 +++++++------------------- horde/services/facebook.php | 93 ++++++++++- horde/templates/block/facebook_story.html.php | 47 ++++++ 4 files changed, 335 insertions(+), 166 deletions(-) create mode 100644 horde/js/facebookclient.js create mode 100644 horde/templates/block/facebook_story.html.php diff --git a/horde/js/facebookclient.js b/horde/js/facebookclient.js new file mode 100644 index 000000000..5193869e9 --- /dev/null +++ b/horde/js/facebookclient.js @@ -0,0 +1,145 @@ +/** + * 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 + * @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; + } + } + +}); diff --git a/horde/lib/Block/fb_stream.php b/horde/lib/Block/fb_stream.php index 365454c31..b13dd9318 100644 --- a/horde/lib/Block/fb_stream.php +++ b/horde/lib/Block/fb_stream.php @@ -17,8 +17,10 @@ class Horde_Block_Horde_fb_stream extends Horde_Block { /** * 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 @@ -42,30 +44,31 @@ class Horde_Block_Horde_fb_stream extends Horde_Block { */ 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) { @@ -109,95 +112,53 @@ class Horde_Block_Horde_fb_stream extends Horde_Block { */ function _content() { + $instance = md5(mt_rand()); $csslink = $GLOBALS['registry']->get('themesuri', 'horde') . '/facebook.css'; $endpoint = Horde::url('services/facebook.php', true); - $spinner = '$(\'loading\')'; - $html = << - 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; - } - - -EOF; - + $html = ''; + + /* Add the client javascript / initialize it */ + Horde::addScriptFile('facebookclient.js'); + $script = <<_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") . ''); } - // 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") . ''); - return $html; - } + /* Build the UI */ + $html .= ''; + $html .= '
'; - //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") . ''); - 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 .= '
'; } - // Bring in the Facebook CSS - $html .= ''; - $html .= '
'; - - // 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") . ''); + return $html; } $status = array_pop($status); @@ -207,22 +168,17 @@ EOF; } else { $class = ''; } - - if (!empty($notifications)) { - $html .= '
' . _("New Messages:") - . ' ' . $notifications['messages']['unread'] - . ' ' . _("Pokes:") . ' ' . $notifications['pokes']['unread'] - . ' ' . _("Friend Requests:") . ' ' . count($notifications['friend_requests']) - . ' ' . _("Event Invites:") . ' ' . count($notifications['event_invites']) . '
'; - } - - $html .= '
' . $status['status']['message'] . '
'; + $html .= '
' + . '' + . '
' + . $status['status']['message'] + . '
'; + 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 .= '' - . '' - . Horde::img('loading.gif', '', array('id' => 'loading', 'style' => 'display:none;')); + $html .= '' + . '' + . 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()); @@ -230,78 +186,12 @@ EOF; return $html; } $html .= '
'; // Close the fbgreybox node that wraps the status - // Build the stream feed. - $html .= '
'; - foreach ($posts as $post) { - $html .= '
'; - $html .= '
'; - // fbstreambody wraps all content except the actor's image. This - // displays the actor's name and any message he/she added. - $html .= '
' - . Horde::externalUrl($profiles[(string)$post['actor_id']]['url'], true) - . $profiles[(string)$post['actor_id']]['name'] . ' ' - . (empty($post['message']) ? '' : $post['message']); - // Parse any attachments - if (!empty($post['attachment'])) { - $html .= '
'; - if (!empty($post['attachment']['media']) && count($post['attachment']['media'])) { - $html .= '
'; - // 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 = ''; - if ($single) { - $html .= '
' . $link . $img . '
'; - } else { - $html .= '
' . $link . $img . '
'; - $multiple = true; - } - } - $html .= '
'; // Close the fbmedia node - } - - // Attachment properties. - if (!empty($post['attachment']['name'])) { - $link = Horde::externalUrl($post['attachment']['href'], true); - $html .= '
' . $link . $post['attachment']['name'] . '
'; - } - if (!empty($post['attachment']['caption'])) { - $html .= '
' . $post['attachment']['caption'] . '
'; - } - if (!empty($post['attachment']['description'])) { - $html .= '
' . $post['attachment']['description'] . '
'; - } - - $html .= '
'; // Close the fbattachemnt node. - } - - // Build the likes string to display. - if (empty($post['likes']['user_likes']) && !empty($post['likes']['can_like'])) { - $like = '' . _("Like") . ''; - } else { - $like = ''; - } - $html .= '
' . 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']) . '
'; - $html .= '
'; - 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 .= '
'; // Close the fbstreaminfo node that wraps the like - $html .= '
'; // Close the fbstreambody, fbstreamstory nodes - $html .= '
 
'; - } - $html .= '
'; // fbbody end + // Build the stream feed. + $html .= '
'; + $html .= ''; + $html .= '
'; // fbbody end return $html; } diff --git a/horde/services/facebook.php b/horde/services/facebook.php index 87d73bf49..c3cb59ee8 100644 --- a/horde/services/facebook.php +++ b/horde/services/facebook.php @@ -20,8 +20,9 @@ try { 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. @@ -39,11 +40,97 @@ if ($token = Horde_Util::getFormData('auth_token')) { $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") . ''); + + 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") . ''); + + 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'] . ''; + $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 = '' . _("Like") . ''; + } 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')); diff --git a/horde/templates/block/facebook_story.html.php b/horde/templates/block/facebook_story.html.php new file mode 100644 index 000000000..72ce3ec6f --- /dev/null +++ b/horde/templates/block/facebook_story.html.php @@ -0,0 +1,47 @@ +actorImgUrl URI to the actor's pic. + * $this->actorProfileLink Full link Text to FB Profile + * $this->message The body of the post + * $this->attachement Attachement array + * $this->postId The postId + * $this->postInfo Text to display for post info (post time etc...) + * $this->likesInfo Text to display for the Like info (You and one other person etc...) + */ +?> +
+
+
+ actorProfileLink ?>
+ message) ? '' : $this->message;?> + attachment)):?> +
+ attachment['media']) && count($this->attachment['media'])):?> +
+ attachment['media'] as $item): ?> +
+ [image] +
+ +
+ + attachment['name'])):?> +
+ attachment['href'], true) . $this->attachment['name']?> +
+ + attachment['caption'])):?> +
attachment['caption']?>
+ + attachment['description'])):?> +
attachment['description']?>
+ +
+ +
postInfo?>
+
likesInfo?>
+
+
+
 
\ No newline at end of file -- 2.11.0