From: Zach Copley Date: Tue, 20 Oct 2009 23:32:30 +0000 (-0700) Subject: Extract out Facebook app stuff into a plugin X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=78e5a5980a21c04cfdacb993ca3c37bf65d21783;p=quix0rs-gnu-social.git Extract out Facebook app stuff into a plugin --- diff --git a/actions/facebookhome.php b/actions/facebookhome.php deleted file mode 100644 index 70f2052053..0000000000 --- a/actions/facebookhome.php +++ /dev/null @@ -1,275 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/lib/facebookaction.php'; - -class FacebookhomeAction extends FacebookAction -{ - - var $page = null; - - function prepare($argarray) - { - parent::prepare($argarray); - - $this->page = $this->trimmed('page'); - - if (!$this->page) { - $this->page = 1; - } - - return true; - } - - function handle($args) - { - parent::handle($args); - - // If the user has opted not to initially allow the app to have - // Facebook status update permission, store that preference. Only - // promt the user the first time she uses the app - if ($this->arg('skip') || $args['fb_sig_request_method'] == 'GET') { - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF, 'true'); - } - - if ($this->flink) { - - $this->user = $this->flink->getUser(); - - // If this is the first time the user has started the app - // prompt for Facebook status update permission - if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { - - if ($this->facebook->api_client->data_getUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { - $this->getUpdatePermission(); - return; - } - } - - // Make sure the user's profile box has the lastest notice - $notice = $this->user->getCurrentNotice(); - if ($notice) { - $this->updateProfileBox($notice); - } - - if ($this->arg('status_submit') == 'Send') { - $this->saveNewNotice(); - } - - // User is authenticated and has already been prompted once for - // Facebook status update permission? Then show the main page - // of the app - $this->showPage(); - - } else { - - // User hasn't authenticated yet, prompt for creds - $this->login(); - } - - } - - function login() - { - - $this->showStylesheets(); - - $nickname = common_canonical_nickname($this->trimmed('nickname')); - $password = $this->arg('password'); - - $msg = null; - - if ($nickname) { - - if (common_check_user($nickname, $password)) { - - $user = User::staticGet('nickname', $nickname); - - if (!$user) { - $this->showLoginForm(_("Server error - couldn't get user!")); - } - - $flink = DB_DataObject::factory('foreign_link'); - $flink->user_id = $user->id; - $flink->foreign_id = $this->fbuid; - $flink->service = FACEBOOK_SERVICE; - $flink->created = common_sql_now(); - $flink->set_flags(true, false, false, false); - - $flink_id = $flink->insert(); - - // XXX: Do some error handling here - - $this->setDefaults(); - - $this->getUpdatePermission(); - return; - - } else { - $msg = _('Incorrect username or password.'); - } - } - - $this->showLoginForm($msg); - $this->showFooter(); - - } - - function setDefaults() - { - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); - } - - function showNoticeForm() - { - $post_action = "$this->app_uri/index.php"; - - $notice_form = new FacebookNoticeForm($this, $post_action, null, - $post_action, $this->user); - $notice_form->show(); - } - - function title() - { - if ($this->page > 1) { - return sprintf(_("%s and friends, page %d"), $this->user->nickname, $this->page); - } else { - return sprintf(_("%s and friends"), $this->user->nickname); - } - } - - function showContent() - { - $notice = $this->user->noticeInbox(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); - - $nl = new NoticeList($notice, $this); - - $cnt = $nl->show(); - - $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, - $this->page, 'index.php', array('nickname' => $this->user->nickname)); - } - - function showNoticeList($notice) - { - - $nl = new NoticeList($notice, $this); - return $nl->show(); - } - - function getUpdatePermission() { - - $this->showStylesheets(); - - $this->elementStart('div', array('class' => 'facebook_guide')); - - $instructions = sprintf(_('If you would like the %s app to automatically update ' . - 'your Facebook status with your latest notice, you need ' . - 'to give it permission.'), $this->app_name); - - $this->elementStart('p'); - $this->element('span', array('id' => 'permissions_notice'), $instructions); - $this->elementEnd('p'); - - $this->elementStart('form', array('method' => 'post', - 'action' => "index.php", - 'id' => 'facebook-skip-permissions')); - - $this->elementStart('ul', array('id' => 'fb-permissions-list')); - $this->elementStart('li', array('id' => 'fb-permissions-item')); - - $next = urlencode("$this->app_uri/index.php"); - $api_key = common_config('facebook', 'apikey'); - - $auth_url = 'http://www.facebook.com/authorize.php?api_key=' . - $api_key . '&v=1.0&ext_perm=publish_stream&next=' . $next . - '&next_cancel=' . $next . '&submit=skip'; - - $this->elementStart('span', array('class' => 'facebook-button')); - $this->element('a', array('href' => $auth_url), - sprintf(_('Okay, do it!'), $this->app_name)); - $this->elementEnd('span'); - - $this->elementEnd('li'); - - $this->elementStart('li', array('id' => 'fb-permissions-item')); - $this->submit('skip', _('Skip')); - $this->elementEnd('li'); - $this->elementEnd('ul'); - - $this->elementEnd('form'); - $this->elementEnd('div'); - - } - - /** - * Generate pagination links - * - * @param boolean $have_before is there something before? - * @param boolean $have_after is there something after? - * @param integer $page current page - * @param string $action current action - * @param array $args rest of query arguments - * - * @return nothing - */ - function pagination($have_before, $have_after, $page, $action, $args=null) - { - - // Does a little before-after block for next/prev page - - // XXX: Fix so this uses common_local_url() if possible. - - if ($have_before || $have_after) { - $this->elementStart('div', array('class' => 'pagination')); - $this->elementStart('dl', null); - $this->element('dt', null, _('Pagination')); - $this->elementStart('dd', null); - $this->elementStart('ul', array('class' => 'nav')); - } - if ($have_before) { - $pargs = array('page' => $page-1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; - $this->elementStart('li', array('class' => 'nav_prev')); - $this->element('a', array('href' => "$action?page=$newargs[page]", 'rel' => 'prev'), - _('After')); - $this->elementEnd('li'); - } - if ($have_after) { - $pargs = array('page' => $page+1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; - $this->elementStart('li', array('class' => 'nav_next')); - $this->element('a', array('href' => "$action?page=$newargs[page]", 'rel' => 'next'), - _('Before')); - $this->elementEnd('li'); - } - if ($have_before || $have_after) { - $this->elementEnd('ul'); - $this->elementEnd('dd'); - $this->elementEnd('dl'); - $this->elementEnd('div'); - } - } - -} diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php deleted file mode 100644 index 6dfc9d6881..0000000000 --- a/actions/facebookinvite.php +++ /dev/null @@ -1,145 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/facebookaction.php'); - -class FacebookinviteAction extends FacebookAction -{ - - function handle($args) - { - parent::handle($args); - $this->showForm(); - } - - /** - * Wrapper for showing a page - * - * Stores an error and shows the page - * - * @param string $error Error, if any - * - * @return void - */ - - function showForm($error=null) - { - $this->error = $error; - $this->showPage(); - } - - /** - * Show the page content - * - * Either shows the registration form or, if registration was successful, - * instructions for using the site. - * - * @return void - */ - - function showContent() - { - if ($this->arg('ids')) { - $this->showSuccessContent(); - } else { - $this->showFormContent(); - } - } - - function showSuccessContent() - { - - $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), - common_config('site', 'name'))); - $this->element('p', null, _('Invitations have been sent to the following users:')); - - $friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to access the list? - - $this->elementStart('ul', array('id' => 'facebook-friends')); - - foreach ($friend_ids as $friend) { - $this->elementStart('li'); - $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); - $this->element('fb:name', array('uid' => $friend, - 'capitalize' => 'true')); - $this->elementEnd('li'); - } - - $this->elementEnd("ul"); - - } - - function showFormContent() - { - $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . - htmlentities(''); - - $this->elementStart('fb:request-form', array('action' => 'invite.php', - 'method' => 'post', - 'invite' => 'true', - 'type' => common_config('site', 'name'), - 'content' => $content)); - $this->hidden('invite', 'true'); - $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); - - $multi_params = array('showborder' => 'false'); - $multi_params['actiontext'] = $actiontext; - $multi_params['bypass'] = 'cancel'; - - // Get a list of users who are already using the app for exclusion - $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); - $exclude_ids_csv = null; - - // fbml needs these as a csv string, not an array - if ($exclude_ids) { - $exclude_ids_csv = implode(',', $exclude_ids); - $multi_params['exclude_ids'] = $exclude_ids_csv; - } - - $this->element('fb:multi-friend-selector', $multi_params); - $this->elementEnd('fb:request-form'); - - if ($exclude_ids) { - - $this->element('h2', null, sprintf(_('Friends already using %s:'), - common_config('site', 'name'))); - $this->elementStart('ul', array('id' => 'facebook-friends')); - - foreach ($exclude_ids as $friend) { - $this->elementStart('li'); - $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); - $this->element('fb:name', array('uid' => $friend, - 'capitalize' => 'true')); - $this->elementEnd('li'); - } - - $this->elementEnd("ul"); - } - } - - function title() - { - return sprintf(_('Send invitations')); - } - -} diff --git a/actions/facebooklogin.php b/actions/facebooklogin.php deleted file mode 100644 index 8ac2477ab4..0000000000 --- a/actions/facebooklogin.php +++ /dev/null @@ -1,101 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/facebookaction.php'); - -class FacebookinviteAction extends FacebookAction -{ - - function handle($args) - { - parent::handle($args); - - $this->error = $error; - - if ($this->flink) { - if (!$this->facebook->api_client->users_hasAppPermission('publish_stream') && - $this->facebook->api_client->data_getUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF) == 'true') { - - echo '

REDIRECT TO HOME

'; - } - } else { - $this->showPage(); - } - } - - - function showContent() - { - - // If the user has opted not to initially allow the app to have - // Facebook status update permission, store that preference. Only - // promt the user the first time she uses the app - if ($this->arg('skip')) { - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF, 'true'); - } - - if ($this->flink) { - - $this->user = $this->flink->getUser(); - - // If this is the first time the user has started the app - // prompt for Facebook status update permission - if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { - - if ($this->facebook->api_client->data_getUserPreference( - FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { - $this->getUpdatePermission(); - return; - } - } - - } else { - $this->showLoginForm(); - } - - } - - function showSuccessContent() - { - - - - } - - function showFormContent() - { - - - } - - function title() - { - return sprintf(_('Login')); - } - - function redirectHome() - { - - } - -} diff --git a/actions/facebookremove.php b/actions/facebookremove.php deleted file mode 100644 index ae231c0fb3..0000000000 --- a/actions/facebookremove.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/lib/facebookaction.php'; - -class FacebookremoveAction extends FacebookAction -{ - - function handle($args) - { - parent::handle($args); - - $secret = common_config('facebook', 'secret'); - - $sig = ''; - - ksort($_POST); - - foreach ($_POST as $key => $val) { - if (substr($key, 0, 7) == 'fb_sig_') { - $sig .= substr($key, 7) . '=' . $val; - } - } - - $sig .= $secret; - $verify = md5($sig); - - if ($verify == $this->arg('fb_sig')) { - - $flink = Foreign_link::getByForeignID($this->arg('fb_sig_user'), 2); - - common_debug("Removing foreign link to Facebook - local user ID: $flink->user_id, Facebook ID: $flink->foreign_id"); - - $result = $flink->delete(); - - if (!$result) { - common_log_db_error($flink, 'DELETE', __FILE__); - $this->serverError(_('Couldn\'t remove Facebook user.')); - return; - } - - } else { - # Someone bad tried to remove facebook link? - common_log(LOG_ERR, "Someone from $_SERVER[REMOTE_ADDR] " . - 'unsuccessfully tried to remove a foreign link to Facebook!'); - } - } - -} diff --git a/actions/facebooksettings.php b/actions/facebooksettings.php deleted file mode 100644 index b2b1d68071..0000000000 --- a/actions/facebooksettings.php +++ /dev/null @@ -1,157 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/lib/facebookaction.php'; - -class FacebooksettingsAction extends FacebookAction -{ - - function handle($args) - { - parent::handle($args); - $this->showPage(); - } - - /** - * Show the page content - * - * Either shows the registration form or, if registration was successful, - * instructions for using the site. - * - * @return void - */ - - function showContent() - { - if ($this->arg('save')) { - $this->saveSettings(); - } else { - $this->showForm(); - } - } - - function saveSettings() { - - $noticesync = $this->arg('noticesync'); - $replysync = $this->arg('replysync'); - $prefix = $this->trimmed('prefix'); - - $original = clone($this->flink); - $this->flink->set_flags($noticesync, $replysync, false, false); - $result = $this->flink->update($original); - - if ($prefix == '' || $prefix == '0') { - // Facebook bug: saving empty strings to prefs now fails - // http://bugs.developers.facebook.com/show_bug.cgi?id=7110 - $trimmed = $prefix . ' '; - } else { - $trimmed = substr($prefix, 0, 128); - } - $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, - $trimmed); - - if ($result === false) { - $this->showForm(_('There was a problem saving your sync preferences!')); - } else { - $this->showForm(_('Sync preferences saved.'), true); - } - } - - function showForm($msg = null, $success = false) { - - if ($msg) { - if ($success) { - $this->element('fb:success', array('message' => $msg)); - } else { - $this->element('fb:error', array('message' => $msg)); - } - } - - if ($this->facebook->api_client->users_hasAppPermission('publish_stream')) { - - $this->elementStart('form', array('method' => 'post', - 'id' => 'facebook_settings')); - - $this->elementStart('ul', 'form_data'); - - $this->elementStart('li'); - - $this->checkbox('noticesync', _('Automatically update my Facebook status with my notices.'), - ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND) : true); - - $this->elementEnd('li'); - - $this->elementStart('li'); - - $this->checkbox('replysync', _('Send "@" replies to Facebook.'), - ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true); - - $this->elementEnd('li'); - - $this->elementStart('li'); - - $prefix = trim($this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX)); - - $this->input('prefix', _('Prefix'), - ($prefix) ? $prefix : null, - _('A string to prefix notices with.')); - - $this->elementEnd('li'); - - $this->elementStart('li'); - - $this->submit('save', _('Save')); - - $this->elementEnd('li'); - - $this->elementEnd('ul'); - - $this->elementEnd('form'); - - } else { - - $instructions = sprintf(_('If you would like %s to automatically update ' . - 'your Facebook status with your latest notice, you need ' . - 'to give it permission.'), $this->app_name); - - $this->elementStart('p'); - $this->element('span', array('id' => 'permissions_notice'), $instructions); - $this->elementEnd('p'); - - $this->elementStart('ul', array('id' => 'fb-permissions-list')); - $this->elementStart('li', array('id' => 'fb-permissions-item')); - $this->elementStart('fb:prompt-permission', array('perms' => 'publish_stream', - 'next_fbjs' => 'document.setLocation(\'' . "$this->app_uri/settings.php" . '\')')); - $this->element('span', array('class' => 'facebook-button'), - sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name'))); - $this->elementEnd('fb:prompt-permission'); - $this->elementEnd('li'); - $this->elementEnd('ul'); - } - - } - - function title() - { - return _('Sync preferences'); - } - -} diff --git a/lib/facebookaction.php b/lib/facebookaction.php deleted file mode 100644 index 3f3a8d3b09..0000000000 --- a/lib/facebookaction.php +++ /dev/null @@ -1,672 +0,0 @@ -. - * - * @category Faceboook - * @package StatusNet - * @author Zach Copley - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) -{ - exit(1); -} - -require_once INSTALLDIR.'/lib/facebookutil.php'; -require_once INSTALLDIR.'/lib/noticeform.php'; - -class FacebookAction extends Action -{ - - var $facebook = null; - var $fbuid = null; - var $flink = null; - var $action = null; - var $app_uri = null; - var $app_name = null; - - /** - * Constructor - * - * Just wraps the HTMLOutputter constructor. - * - * @param string $output URI to output to, default = stdout - * @param boolean $indent Whether to indent output, default true - * - * @see XMLOutputter::__construct - * @see HTMLOutputter::__construct - */ - function __construct($output='php://output', $indent=true, $facebook=null, $flink=null) - { - parent::__construct($output, $indent); - - $this->facebook = $facebook; - $this->flink = $flink; - - if ($this->flink) { - $this->fbuid = $flink->foreign_id; - $this->user = $flink->getUser(); - } - - $this->args = array(); - } - - function prepare($argarray) - { - parent::prepare($argarray); - - $this->facebook = getFacebook(); - $this->fbuid = $this->facebook->require_login(); - - $this->action = $this->trimmed('action'); - - $app_props = $this->facebook->api_client->Admin_getAppProperties( - array('canvas_name', 'application_name')); - - $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; - $this->app_name = $app_props['application_name']; - - $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); - - return true; - - } - - function showStylesheets() - { - $this->cssLink('css/display.css', 'base'); - $this->cssLink('css/display.css',null,'screen, projection, tv'); - $this->cssLink('css/facebookapp.css', 'base'); - } - - function showScripts() - { - $this->script('js/facebookapp.js'); - } - - /** - * Start an Facebook ready HTML document - * - * For Facebook we don't want to actually output any headers, - * DTD info, etc. Just Stylesheet and JavaScript links. - * - * If $type isn't specified, will attempt to do content negotiation. - * - * @param string $type MIME type to use; default is to do negotation. - * - * @return void - */ - - function startHTML($type=null) - { - $this->showStylesheets(); - $this->showScripts(); - - $this->elementStart('div', array('class' => 'facebook-page')); - } - - /** - * Ends a Facebook ready HTML document - * - * @return void - */ - function endHTML() - { - $this->elementEnd('div'); - $this->endXML(); - } - - /** - * Show notice form. - * - * MAY overload if no notice form needed... or direct message box???? - * - * @return nothing - */ - function showNoticeForm() - { - // don't do it for most of the Facebook pages - } - - function showBody() - { - $this->elementStart('div', array('id' => 'wrap')); - $this->showHeader(); - $this->showCore(); - $this->showFooter(); - $this->elementEnd('div'); - } - - function showAside() - { - } - - function showHead($error, $success) - { - - if ($error) { - $this->element("h1", null, $error); - } - - if ($success) { - $this->element("h1", null, $success); - } - - $this->elementStart('fb:if-section-not-added', array('section' => 'profile')); - $this->elementStart('span', array('id' => 'add_to_profile')); - $this->element('fb:add-section-button', array('section' => 'profile')); - $this->elementEnd('span'); - $this->elementEnd('fb:if-section-not-added'); - - } - - // Make this into a widget later - function showLocalNav() - { - $this->elementStart('ul', array('class' => 'nav')); - - $this->elementStart('li', array('class' => - ($this->action == 'facebookhome') ? 'current' : 'facebook_home')); - $this->element('a', - array('href' => 'index.php', 'title' => _('Home')), _('Home')); - $this->elementEnd('li'); - - if (common_config('invite', 'enabled')) { - $this->elementStart('li', - array('class' => - ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite')); - $this->element('a', - array('href' => 'invite.php', 'title' => _('Invite')), _('Invite')); - $this->elementEnd('li'); - } - - $this->elementStart('li', - array('class' => - ($this->action == 'facebooksettings') ? 'current' : 'facebook_settings')); - $this->element('a', - array('href' => 'settings.php', - 'title' => _('Settings')), _('Settings')); - $this->elementEnd('li'); - - $this->elementEnd('ul'); - } - - /** - * Show header of the page. - * - * Calls template methods - * - * @return nothing - */ - function showHeader() - { - $this->elementStart('div', array('id' => 'header')); - $this->showLogo(); - $this->showNoticeForm(); - $this->elementEnd('div'); - } - - /** - * Show page, a template method. - * - * @return nothing - */ - function showPage($error = null, $success = null) - { - $this->startHTML(); - $this->showHead($error, $success); - $this->showBody(); - $this->endHTML(); - } - - function showInstructions() - { - - $this->elementStart('div', array('class' => 'facebook_guide')); - - $this->elementStart('dl', array('class' => 'system_notice')); - $this->element('dt', null, 'Page Notice'); - - $loginmsg_part1 = _('To use the %s Facebook Application you need to login ' . - 'with your username and password. Don\'t have a username yet? '); - $loginmsg_part2 = _(' a new account.'); - - $this->elementStart('dd'); - $this->elementStart('p'); - $this->text(sprintf($loginmsg_part1, common_config('site', 'name'))); - $this->element('a', - array('href' => common_local_url('register')), _('Register')); - $this->text($loginmsg_part2); - $this->elementEnd('p'); - $this->elementEnd('dd'); - - $this->elementEnd('dl'); - $this->elementEnd('div'); - } - - function showLoginForm($msg = null) - { - - $this->elementStart('div', array('id' => 'content')); - $this->element('h1', null, _('Login')); - - if ($msg) { - $this->element('fb:error', array('message' => $msg)); - } - - $this->showInstructions(); - - $this->elementStart('div', array('id' => 'content_inner')); - - $this->elementStart('form', array('method' => 'post', - 'class' => 'form_settings', - 'id' => 'login', - 'action' => 'index.php')); - - $this->elementStart('fieldset'); - - $this->elementStart('ul', array('class' => 'form_datas')); - $this->elementStart('li'); - $this->input('nickname', _('Nickname')); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->password('password', _('Password')); - $this->elementEnd('li'); - $this->elementEnd('ul'); - - $this->submit('submit', _('Login')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - - $this->elementStart('p'); - $this->element('a', array('href' => common_local_url('recoverpassword')), - _('Lost or forgotten password?')); - $this->elementEnd('p'); - - $this->elementEnd('div'); - $this->elementEnd('div'); - - } - - function updateProfileBox($notice) - { - - // Need to include inline CSS for styling the Profile box - - $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); - $icon_url = $app_props['icon_url']; - - $style = ''; - - $this->xw->openMemory(); - - $item = new FacebookProfileBoxNotice($notice, $this); - $item->show(); - - $fbml = "$style " . $this->xw->outputMemory(false) . ""; - $fbml .= "$style " . $this->xw->outputMemory(false) . ""; - - $fbml_main = "$style " . $this->xw->outputMemory(false) . ""; - - $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); - - $this->xw->openURI('php://output'); - } - - /** - * Generate pagination links - * - * @param boolean $have_before is there something before? - * @param boolean $have_after is there something after? - * @param integer $page current page - * @param string $action current action - * @param array $args rest of query arguments - * - * @return nothing - */ - function pagination($have_before, $have_after, $page, $action, $args=null) - { - // Does a little before-after block for next/prev page - if ($have_before || $have_after) { - $this->elementStart('div', array('class' => 'pagination')); - $this->elementStart('dl', null); - $this->element('dt', null, _('Pagination')); - $this->elementStart('dd', null); - $this->elementStart('ul', array('class' => 'nav')); - } - if ($have_before) { - $pargs = array('page' => $page-1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; - $this->elementStart('li', array('class' => 'nav_prev')); - $this->element('a', array('href' => "$this->app_uri/$action?page=$newargs[page]", 'rel' => 'prev'), - _('After')); - $this->elementEnd('li'); - } - if ($have_after) { - $pargs = array('page' => $page+1); - $newargs = $args ? array_merge($args, $pargs) : $pargs; - $this->elementStart('li', array('class' => 'nav_next')); - $this->element('a', array('href' => "$this->app_uri/$action?page=$newargs[page]", 'rel' => 'next'), - _('Before')); - $this->elementEnd('li'); - } - if ($have_before || $have_after) { - $this->elementEnd('ul'); - $this->elementEnd('dd'); - $this->elementEnd('dl'); - $this->elementEnd('div'); - } - } - - function saveNewNotice() - { - - $user = $this->flink->getUser(); - - $content = $this->trimmed('status_textarea'); - - if (!$content) { - $this->showPage(_('No notice content!')); - return; - } else { - $content_shortened = common_shorten_links($content); - - if (Notice::contentTooLong($content_shortened)) { - $this->showPage(sprintf(_('That\'s too long. Max notice size is %d chars.'), - Notice::maxContent())); - return; - } - } - - $inter = new CommandInterpreter(); - - $cmd = $inter->handle_command($user, $content_shortened); - - if ($cmd) { - - // XXX fix this - - $cmd->execute(new WebChannel()); - return; - } - - $replyto = $this->trimmed('inreplyto'); - - try { - $notice = Notice::saveNew($user->id, $content, - 'web', 1, ($replyto == 'false') ? null : $replyto); - } catch (Exception $e) { - $this->showPage($e->getMessage()); - return; - } - - common_broadcast_notice($notice); - - // Also update the user's Facebook status - facebookBroadcastNotice($notice); - - } - -} - -class FacebookNoticeForm extends NoticeForm -{ - - var $post_action = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param string $action action to return to, if any - * @param string $content content to pre-fill - */ - - function __construct($out=null, $action=null, $content=null, - $post_action=null, $user=null) - { - parent::__construct($out, $action, $content, $user); - $this->post_action = $post_action; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - - function action() - { - return $this->post_action; - } - -} - -class FacebookNoticeList extends NoticeList -{ - - /** - * constructor - * - * @param Notice $notice stream of notices from DB_DataObject - */ - - function __construct($notice, $out=null) - { - parent::__construct($notice, $out); - } - - /** - * show the list of notices - * - * "Uses up" the stream by looping through it. So, probably can't - * be called twice on the same list. - * - * @return int count of notices listed. - */ - - function show() - { - $this->out->elementStart('div', array('id' =>'notices_primary')); - $this->out->element('h2', null, _('Notices')); - $this->out->elementStart('ul', array('class' => 'notices')); - - $cnt = 0; - - while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) { - $cnt++; - - if ($cnt > NOTICES_PER_PAGE) { - break; - } - - $item = $this->newListItem($this->notice); - $item->show(); - } - - $this->out->elementEnd('ul'); - $this->out->elementEnd('div'); - - return $cnt; - } - - /** - * returns a new list item for the current notice - * - * Overridden to return a Facebook specific list item. - * - * @param Notice $notice the current notice - * - * @return FacebookNoticeListItem a list item for displaying the notice - * formatted for display in the Facebook App. - */ - - function newListItem($notice) - { - return new FacebookNoticeListItem($notice, $this); - } - -} - -class FacebookNoticeListItem extends NoticeListItem -{ - - /** - * constructor - * - * Also initializes the profile attribute. - * - * @param Notice $notice The notice we'll display - */ - - function __construct($notice, $out=null) - { - parent::__construct($notice, $out); - } - - /** - * recipe function for displaying a single notice in the Facebook App. - * - * Overridden to strip out some of the controls that we don't - * want to be available. - * - * @return void - */ - - function show() - { - $this->showStart(); - $this->showNotice(); - $this->showNoticeInfo(); - - // XXX: Need to update to show attachements and controls - - $this->showEnd(); - } - -} - -class FacebookProfileBoxNotice extends FacebookNoticeListItem -{ - - /** - * constructor - * - * Also initializes the profile attribute. - * - * @param Notice $notice The notice we'll display - */ - - function __construct($notice, $out=null) - { - parent::__construct($notice, $out); - } - - /** - * Recipe function for displaying a single notice in the - * Facebook App profile notice box - * - * @return void - */ - - function show() - { - $this->showNotice(); - $this->showNoticeInfo(); - $this->showAppLink(); - } - - function showAppLink() - { - - $this->facebook = getFacebook(); - - $app_props = $this->facebook->api_client->Admin_getAppProperties( - array('canvas_name', 'application_name')); - - $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; - $this->app_name = $app_props['application_name']; - - $this->out->elementStart('a', array('id' => 'facebook_statusnet_app', - 'href' => $this->app_uri)); - $this->out->text($this->app_name); - $this->out->elementEnd('a'); - } - -} diff --git a/lib/facebookutil.php b/lib/facebookutil.php deleted file mode 100644 index c991c5439c..0000000000 --- a/lib/facebookutil.php +++ /dev/null @@ -1,260 +0,0 @@ -. - */ - -require_once INSTALLDIR.'/extlib/facebook/facebook.php'; -require_once INSTALLDIR.'/lib/facebookaction.php'; -require_once INSTALLDIR.'/lib/noticelist.php'; - -define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2 -define("FACEBOOK_NOTICE_PREFIX", 1); -define("FACEBOOK_PROMPTED_UPDATE_PREF", 2); - -function getFacebook() -{ - static $facebook = null; - - $apikey = common_config('facebook', 'apikey'); - $secret = common_config('facebook', 'secret'); - - if ($facebook === null) { - $facebook = new Facebook($apikey, $secret); - } - - if (empty($facebook)) { - common_log(LOG_ERR, 'Could not make new Facebook client obj!', - __FILE__); - } - - return $facebook; -} - -function isFacebookBound($notice, $flink) { - - if (empty($flink)) { - return false; - } - - // Avoid a loop - - if ($notice->source == 'Facebook') { - common_log(LOG_INFO, "Skipping notice $notice->id because its " . - 'source is Facebook.'); - return false; - } - - // If the user does not want to broadcast to Facebook, move along - - if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) { - common_log(LOG_INFO, "Skipping notice $notice->id " . - 'because user has FOREIGN_NOTICE_SEND bit off.'); - return false; - } - - // If it's not a reply, or if the user WANTS to send @-replies, - // then, yeah, it can go to Facebook. - - if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || - ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) { - return true; - } - - return false; - -} - -function facebookBroadcastNotice($notice) -{ - $facebook = getFacebook(); - $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); - - if (isFacebookBound($notice, $flink)) { - - // Okay, we're good to go, update the FB status - - $status = null; - $fbuid = $flink->foreign_id; - $user = $flink->getUser(); - $attachments = $notice->attachments(); - - try { - - // Get the status 'verb' (prefix) the user has set - - // XXX: Does this call count against our per user FB request limit? - // If so we should consider storing verb elsewhere or not storing - - $prefix = trim($facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, - $fbuid)); - - $status = "$prefix $notice->content"; - - $can_publish = $facebook->api_client->users_hasAppPermission('publish_stream', - $fbuid); - - $can_update = $facebook->api_client->users_hasAppPermission('status_update', - $fbuid); - if (!empty($attachments) && $can_publish == 1) { - $fbattachment = format_attachments($attachments); - $facebook->api_client->stream_publish($status, $fbattachment, - null, null, $fbuid); - common_log(LOG_INFO, - "Posted notice $notice->id w/attachment " . - "to Facebook user's stream (fbuid = $fbuid)."); - } elseif ($can_update == 1 || $can_publish == 1) { - $facebook->api_client->users_setStatus($status, $fbuid, false, true); - common_log(LOG_INFO, - "Posted notice $notice->id to Facebook " . - "as a status update (fbuid = $fbuid)."); - } else { - $msg = "Not sending notice $notice->id to Facebook " . - "because user $user->nickname hasn't given the " . - 'Facebook app \'status_update\' or \'publish_stream\' permission.'; - common_log(LOG_WARNING, $msg); - } - - // Finally, attempt to update the user's profile box - - if ($can_publish == 1 || $can_update == 1) { - updateProfileBox($facebook, $flink, $notice); - } - - } catch (FacebookRestClientException $e) { - - $code = $e->getCode(); - - common_log(LOG_WARNING, 'Facebook returned error code ' . - $code . ': ' . $e->getMessage()); - common_log(LOG_WARNING, - 'Unable to update Facebook status for ' . - "$user->nickname (user id: $user->id)!"); - - if ($code == 200 || $code == 250) { - - // 200 The application does not have permission to operate on the passed in uid parameter. - // 250 Updating status requires the extended permission status_update or publish_stream. - // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML - - remove_facebook_app($flink); - - } else { - - // Try sending again later. - - return false; - } - - } - } - - return true; - -} - -function updateProfileBox($facebook, $flink, $notice) { - $fbaction = new FacebookAction($output = 'php://output', - $indent = true, $facebook, $flink); - $fbaction->updateProfileBox($notice); -} - -function format_attachments($attachments) -{ - $fbattachment = array(); - $fbattachment['media'] = array(); - - foreach($attachments as $attachment) - { - if($enclosure = $attachment->getEnclosure()){ - $fbmedia = get_fbmedia_for_attachment($enclosure); - }else{ - $fbmedia = get_fbmedia_for_attachment($attachment); - } - if($fbmedia){ - $fbattachment['media'][]=$fbmedia; - }else{ - $fbattachment['name'] = ($attachment->title ? - $attachment->title : $attachment->url); - $fbattachment['href'] = $attachment->url; - } - } - if(count($fbattachment['media'])>0){ - unset($fbattachment['name']); - unset($fbattachment['href']); - } - return $fbattachment; -} - -/** -* given an File objects, returns an associative array suitable for Facebook media -*/ -function get_fbmedia_for_attachment($attachment) -{ - $fbmedia = array(); - - if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) { - $fbmedia['type'] = 'image'; - $fbmedia['src'] = $attachment->url; - $fbmedia['href'] = $attachment->url; - } else if ($attachment->mimetype == 'audio/mpeg') { - $fbmedia['type'] = 'mp3'; - $fbmedia['src'] = $attachment->url; - }else if ($attachment->mimetype == 'application/x-shockwave-flash') { - $fbmedia['type'] = 'flash'; - - // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29 - // says that imgsrc is required... but we have no value to put in it - // $fbmedia['imgsrc']=''; - - $fbmedia['swfsrc'] = $attachment->url; - }else{ - return false; - } - return $fbmedia; -} - -function remove_facebook_app($flink) -{ - - $user = $flink->getUser(); - - common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' . - "user $user->nickname (user id: $user->id)."); - - $result = $flink->delete(); - - if (empty($result)) { - common_log(LOG_ERR, 'Could not remove Facebook App ' . - "Foreign_link for $user->nickname (user id: $user->id)!"); - common_log_db_error($flink, 'DELETE', __FILE__); - } - - // Notify the user that we are removing their FB app access - - $result = mail_facebook_app_removed($user); - - if (!$result) { - - $msg = 'Unable to send email to notify ' . - "$user->nickname (user id: $user->id) " . - 'that their Facebook app link was ' . - 'removed!'; - - common_log(LOG_WARNING, $msg); - } - -} diff --git a/lib/router.php b/lib/router.php index a5b6a9a304..4fb0834fd0 100644 --- a/lib/router.php +++ b/lib/router.php @@ -86,14 +86,6 @@ class Router $m->connect('doc/:title', array('action' => 'doc')); - // facebook - - $m->connect('facebook', array('action' => 'facebookhome')); - $m->connect('facebook/index.php', array('action' => 'facebookhome')); - $m->connect('facebook/settings.php', array('action' => 'facebooksettings')); - $m->connect('facebook/invite.php', array('action' => 'facebookinvite')); - $m->connect('facebook/remove', array('action' => 'facebookremove')); - // main stuff is repetitive $main = array('login', 'logout', 'register', 'subscribe', diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php new file mode 100644 index 0000000000..127cf96e64 --- /dev/null +++ b/plugins/Facebook/FacebookPlugin.php @@ -0,0 +1,151 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Facebook plugin to add a StatusNet Facebook application + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class FacebookPlugin extends Plugin +{ + + /** + * Add Facebook app actions to the router table + * + * Hook for RouterInitialized event. + * + * @param Net_URL_Mapper &$m path-to-action mapper + * + * @return boolean hook return + */ + + function onRouterInitialized(&$m) + { + $m->connect('facebook', array('action' => 'facebookhome')); + $m->connect('facebook/index.php', array('action' => 'facebookhome')); + $m->connect('facebook/settings.php', array('action' => 'facebooksettings')); + $m->connect('facebook/invite.php', array('action' => 'facebookinvite')); + $m->connect('facebook/remove', array('action' => 'facebookremove')); + + return true; + } + + /** + * Automatically load the actions and libraries used by the Facebook app + * + * @param Class $cls the class + * + * @return boolean hook return + * + */ + function onAutoload($cls) + { + switch ($cls) { + case 'FacebookAction': + case 'FacebookhomeAction': + case 'FacebookinviteAction': + case 'FacebookremoveAction': + case 'FacebooksettingsAction': + include_once INSTALLDIR . '/plugins/Facebook/' . + strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + default: + return true; + } + } + + /** + * Add a Facebook queue item for each notice + * + * @param Notice $notice the notice + * @param array &$transports the list of transports (queues) + * + * @return boolean hook return + */ + function onStartEnqueueNotice($notice, &$transports) + { + array_push($transports, 'facebook'); + return true; + } + + /** + * broadcast the message when not using queuehandler + * + * @param Notice &$notice the notice + * @param array $queue destination queue + * + * @return boolean hook return + */ + function onUnqueueHandleNotice(&$notice, $queue) + { + if (($queue == 'facebook') && ($this->_isLocal($notice))) { + facebookBroadcastNotice($notice); + return false; + } + return true; + } + + /** + * Determine whether the notice was locally created + * + * @param Notice $notice + * + * @return boolean locality + */ + function _isLocal($notice) + { + return ($notice->is_local == Notice::LOCAL_PUBLIC || + $notice->is_local == Notice::LOCAL_NONPUBLIC); + } + + /** + * Add Facebook queuehandler to the list of daemons to start + * + * @param array $daemons the list fo daemons to run + * + * @return boolean hook return + * + */ + function onGetValidDaemons($daemons) + { + array_push($daemons, INSTALLDIR . + '/plugins/Facebook/facebookqueuehandler.php'); + return true; + } + +} \ No newline at end of file diff --git a/plugins/Facebook/README b/plugins/Facebook/README new file mode 100644 index 0000000000..a350c5b5b7 --- /dev/null +++ b/plugins/Facebook/README @@ -0,0 +1,5 @@ + + +TODO: + +- Integrate this and the FB Connect plugin \ No newline at end of file diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php new file mode 100644 index 0000000000..f5ad3d06b4 --- /dev/null +++ b/plugins/Facebook/facebookaction.php @@ -0,0 +1,650 @@ +. + * + * @category Faceboook + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; +require_once INSTALLDIR . '/lib/noticeform.php'; + +class FacebookAction extends Action +{ + + var $facebook = null; + var $fbuid = null; + var $flink = null; + var $action = null; + var $app_uri = null; + var $app_name = null; + + function __construct($output='php://output', $indent=true, $facebook=null, $flink=null) + { + parent::__construct($output, $indent); + + $this->facebook = $facebook; + $this->flink = $flink; + + if ($this->flink) { + $this->fbuid = $flink->foreign_id; + $this->user = $flink->getUser(); + } + + $this->args = array(); + } + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->facebook = getFacebook(); + $this->fbuid = $this->facebook->require_login(); + + $this->action = $this->trimmed('action'); + + $app_props = $this->facebook->api_client->Admin_getAppProperties( + array('canvas_name', 'application_name')); + + $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; + $this->app_name = $app_props['application_name']; + + $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); + + return true; + + } + + function showStylesheets() + { + $this->cssLink('css/display.css', 'base'); + $this->cssLink('css/display.css',null,'screen, projection, tv'); + $this->cssLink('css/facebookapp.css', 'base'); + } + + function showScripts() + { + $this->script('js/facebookapp.js'); + } + + /** + * Start an Facebook ready HTML document + * + * For Facebook we don't want to actually output any headers, + * DTD info, etc. Just Stylesheet and JavaScript links. + * + * @param string $type MIME type to use; default is to do negotation. + * + * @return void + */ + + function startHTML($type=null) + { + $this->showStylesheets(); + $this->showScripts(); + + $this->elementStart('div', array('class' => 'facebook-page')); + } + + /** + * Ends a Facebook ready HTML document + * + * @return void + */ + function endHTML() + { + $this->elementEnd('div'); + $this->endXML(); + } + + /** + * Show notice form. + * + * @return nothing + */ + function showNoticeForm() + { + // don't do it for most of the Facebook pages + } + + function showBody() + { + $this->elementStart('div', array('id' => 'wrap')); + $this->showHeader(); + $this->showCore(); + $this->showFooter(); + $this->elementEnd('div'); + } + + function showHead($error, $success) + { + + if ($error) { + $this->element("h1", null, $error); + } + + if ($success) { + $this->element("h1", null, $success); + } + + $this->elementStart('fb:if-section-not-added', array('section' => 'profile')); + $this->elementStart('span', array('id' => 'add_to_profile')); + $this->element('fb:add-section-button', array('section' => 'profile')); + $this->elementEnd('span'); + $this->elementEnd('fb:if-section-not-added'); + + } + + // Make this into a widget later + function showLocalNav() + { + $this->elementStart('ul', array('class' => 'nav')); + + $this->elementStart('li', array('class' => + ($this->action == 'facebookhome') ? 'current' : 'facebook_home')); + $this->element('a', + array('href' => 'index.php', 'title' => _('Home')), _('Home')); + $this->elementEnd('li'); + + if (common_config('invite', 'enabled')) { + $this->elementStart('li', + array('class' => + ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite')); + $this->element('a', + array('href' => 'invite.php', 'title' => _('Invite')), _('Invite')); + $this->elementEnd('li'); + } + + $this->elementStart('li', + array('class' => + ($this->action == 'facebooksettings') ? 'current' : 'facebook_settings')); + $this->element('a', + array('href' => 'settings.php', + 'title' => _('Settings')), _('Settings')); + $this->elementEnd('li'); + + $this->elementEnd('ul'); + } + + /** + * Show header of the page. + * + * @return nothing + */ + function showHeader() + { + $this->elementStart('div', array('id' => 'header')); + $this->showLogo(); + $this->showNoticeForm(); + $this->elementEnd('div'); + } + + /** + * Show page, a template method. + * + * @return nothing + */ + function showPage($error = null, $success = null) + { + $this->startHTML(); + $this->showHead($error, $success); + $this->showBody(); + $this->endHTML(); + } + + function showInstructions() + { + + $this->elementStart('div', array('class' => 'facebook_guide')); + + $this->elementStart('dl', array('class' => 'system_notice')); + $this->element('dt', null, 'Page Notice'); + + $loginmsg_part1 = _('To use the %s Facebook Application you need to login ' . + 'with your username and password. Don\'t have a username yet? '); + $loginmsg_part2 = _(' a new account.'); + + $this->elementStart('dd'); + $this->elementStart('p'); + $this->text(sprintf($loginmsg_part1, common_config('site', 'name'))); + $this->element('a', + array('href' => common_local_url('register')), _('Register')); + $this->text($loginmsg_part2); + $this->elementEnd('p'); + $this->elementEnd('dd'); + + $this->elementEnd('dl'); + $this->elementEnd('div'); + } + + function showLoginForm($msg = null) + { + + $this->elementStart('div', array('id' => 'content')); + $this->element('h1', null, _('Login')); + + if ($msg) { + $this->element('fb:error', array('message' => $msg)); + } + + $this->showInstructions(); + + $this->elementStart('div', array('id' => 'content_inner')); + + $this->elementStart('form', array('method' => 'post', + 'class' => 'form_settings', + 'id' => 'login', + 'action' => 'index.php')); + + $this->elementStart('fieldset'); + + $this->elementStart('ul', array('class' => 'form_datas')); + $this->elementStart('li'); + $this->input('nickname', _('Nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->submit('submit', _('Login')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + + $this->elementStart('p'); + $this->element('a', array('href' => common_local_url('recoverpassword')), + _('Lost or forgotten password?')); + $this->elementEnd('p'); + + $this->elementEnd('div'); + $this->elementEnd('div'); + + } + + function updateProfileBox($notice) + { + + // Need to include inline CSS for styling the Profile box + + $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); + $icon_url = $app_props['icon_url']; + + $style = ''; + + $this->xw->openMemory(); + + $item = new FacebookProfileBoxNotice($notice, $this); + $item->show(); + + $fbml = "$style " . $this->xw->outputMemory(false) . ""; + $fbml .= "$style " . $this->xw->outputMemory(false) . ""; + + $fbml_main = "$style " . $this->xw->outputMemory(false) . ""; + + $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); + + $this->xw->openURI('php://output'); + } + + /** + * Generate pagination links + * + * @param boolean $have_before is there something before? + * @param boolean $have_after is there something after? + * @param integer $page current page + * @param string $action current action + * @param array $args rest of query arguments + * + * @return nothing + */ + function pagination($have_before, $have_after, $page, $action, $args=null) + { + // Does a little before-after block for next/prev page + if ($have_before || $have_after) { + $this->elementStart('div', array('class' => 'pagination')); + $this->elementStart('dl', null); + $this->element('dt', null, _('Pagination')); + $this->elementStart('dd', null); + $this->elementStart('ul', array('class' => 'nav')); + } + if ($have_before) { + $pargs = array('page' => $page-1); + $newargs = $args ? array_merge($args, $pargs) : $pargs; + $this->elementStart('li', array('class' => 'nav_prev')); + $this->element('a', array('href' => "$this->app_uri/$action?page=$newargs[page]", 'rel' => 'prev'), + _('After')); + $this->elementEnd('li'); + } + if ($have_after) { + $pargs = array('page' => $page+1); + $newargs = $args ? array_merge($args, $pargs) : $pargs; + $this->elementStart('li', array('class' => 'nav_next')); + $this->element('a', array('href' => "$this->app_uri/$action?page=$newargs[page]", 'rel' => 'next'), + _('Before')); + $this->elementEnd('li'); + } + if ($have_before || $have_after) { + $this->elementEnd('ul'); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); + } + } + + function saveNewNotice() + { + + $user = $this->flink->getUser(); + + $content = $this->trimmed('status_textarea'); + + if (!$content) { + $this->showPage(_('No notice content!')); + return; + } else { + $content_shortened = common_shorten_links($content); + + if (Notice::contentTooLong($content_shortened)) { + $this->showPage(sprintf(_('That\'s too long. Max notice size is %d chars.'), + Notice::maxContent())); + return; + } + } + + $inter = new CommandInterpreter(); + + $cmd = $inter->handle_command($user, $content_shortened); + + if ($cmd) { + + // XXX fix this + + $cmd->execute(new WebChannel()); + return; + } + + $replyto = $this->trimmed('inreplyto'); + + try { + $notice = Notice::saveNew($user->id, $content, + 'web', 1, ($replyto == 'false') ? null : $replyto); + } catch (Exception $e) { + $this->showPage($e->getMessage()); + return; + } + + common_broadcast_notice($notice); + + // Also update the user's Facebook status + facebookBroadcastNotice($notice); + + } + +} + +class FacebookNoticeForm extends NoticeForm +{ + + var $post_action = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param string $action action to return to, if any + * @param string $content content to pre-fill + */ + + function __construct($out=null, $action=null, $content=null, + $post_action=null, $user=null) + { + parent::__construct($out, $action, $content, $user); + $this->post_action = $post_action; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return $this->post_action; + } + +} + +class FacebookNoticeList extends NoticeList +{ + + /** + * constructor + * + * @param Notice $notice stream of notices from DB_DataObject + */ + + function __construct($notice, $out=null) + { + parent::__construct($notice, $out); + } + + /** + * show the list of notices + * + * "Uses up" the stream by looping through it. So, probably can't + * be called twice on the same list. + * + * @return int count of notices listed. + */ + + function show() + { + $this->out->elementStart('div', array('id' =>'notices_primary')); + $this->out->element('h2', null, _('Notices')); + $this->out->elementStart('ul', array('class' => 'notices')); + + $cnt = 0; + + while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) { + $cnt++; + + if ($cnt > NOTICES_PER_PAGE) { + break; + } + + $item = $this->newListItem($this->notice); + $item->show(); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('div'); + + return $cnt; + } + + /** + * returns a new list item for the current notice + * + * Overridden to return a Facebook specific list item. + * + * @param Notice $notice the current notice + * + * @return FacebookNoticeListItem a list item for displaying the notice + * formatted for display in the Facebook App. + */ + + function newListItem($notice) + { + return new FacebookNoticeListItem($notice, $this); + } + +} + +class FacebookNoticeListItem extends NoticeListItem +{ + + /** + * constructor + * + * Also initializes the profile attribute. + * + * @param Notice $notice The notice we'll display + */ + + function __construct($notice, $out=null) + { + parent::__construct($notice, $out); + } + + /** + * recipe function for displaying a single notice in the Facebook App. + * + * Overridden to strip out some of the controls that we don't + * want to be available. + * + * @return void + */ + + function show() + { + $this->showStart(); + $this->showNotice(); + $this->showNoticeInfo(); + + // XXX: Need to update to show attachements and controls + + $this->showEnd(); + } + +} + +class FacebookProfileBoxNotice extends FacebookNoticeListItem +{ + + /** + * constructor + * + * Also initializes the profile attribute. + * + * @param Notice $notice The notice we'll display + */ + + function __construct($notice, $out=null) + { + parent::__construct($notice, $out); + } + + /** + * Recipe function for displaying a single notice in the + * Facebook App profile notice box + * + * @return void + */ + + function show() + { + $this->showNotice(); + $this->showNoticeInfo(); + $this->showAppLink(); + } + + function showAppLink() + { + + $this->facebook = getFacebook(); + + $app_props = $this->facebook->api_client->Admin_getAppProperties( + array('canvas_name', 'application_name')); + + $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; + $this->app_name = $app_props['application_name']; + + $this->out->elementStart('a', array('id' => 'facebook_statusnet_app', + 'href' => $this->app_uri)); + $this->out->text($this->app_name); + $this->out->elementEnd('a'); + } + +} diff --git a/plugins/Facebook/facebookhome.php b/plugins/Facebook/facebookhome.php new file mode 100644 index 0000000000..91c0cc6b86 --- /dev/null +++ b/plugins/Facebook/facebookhome.php @@ -0,0 +1,277 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; + +class FacebookhomeAction extends FacebookAction +{ + + var $page = null; + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->page = $this->trimmed('page'); + + if (!$this->page) { + $this->page = 1; + } + + return true; + } + + function handle($args) + { + parent::handle($args); + + // If the user has opted not to initially allow the app to have + // Facebook status update permission, store that preference. Only + // promt the user the first time she uses the app + if ($this->arg('skip') || $args['fb_sig_request_method'] == 'GET') { + $this->facebook->api_client->data_setUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF, 'true'); + } + + if ($this->flink) { + + $this->user = $this->flink->getUser(); + + // If this is the first time the user has started the app + // prompt for Facebook status update permission + if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { + + if ($this->facebook->api_client->data_getUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { + $this->getUpdatePermission(); + return; + } + } + + // Make sure the user's profile box has the lastest notice + $notice = $this->user->getCurrentNotice(); + if ($notice) { + $this->updateProfileBox($notice); + } + + if ($this->arg('status_submit') == 'Send') { + $this->saveNewNotice(); + } + + // User is authenticated and has already been prompted once for + // Facebook status update permission? Then show the main page + // of the app + $this->showPage(); + + } else { + + // User hasn't authenticated yet, prompt for creds + $this->login(); + } + + } + + function login() + { + + $this->showStylesheets(); + + $nickname = common_canonical_nickname($this->trimmed('nickname')); + $password = $this->arg('password'); + + $msg = null; + + if ($nickname) { + + if (common_check_user($nickname, $password)) { + + $user = User::staticGet('nickname', $nickname); + + if (!$user) { + $this->showLoginForm(_("Server error - couldn't get user!")); + } + + $flink = DB_DataObject::factory('foreign_link'); + $flink->user_id = $user->id; + $flink->foreign_id = $this->fbuid; + $flink->service = FACEBOOK_SERVICE; + $flink->created = common_sql_now(); + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + // XXX: Do some error handling here + + $this->setDefaults(); + + $this->getUpdatePermission(); + return; + + } else { + $msg = _('Incorrect username or password.'); + } + } + + $this->showLoginForm($msg); + $this->showFooter(); + + } + + function setDefaults() + { + $this->facebook->api_client->data_setUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); + } + + function showNoticeForm() + { + $post_action = "$this->app_uri/index.php"; + + $notice_form = new FacebookNoticeForm($this, $post_action, null, + $post_action, $this->user); + $notice_form->show(); + } + + function title() + { + if ($this->page > 1) { + return sprintf(_("%s and friends, page %d"), $this->user->nickname, $this->page); + } else { + return sprintf(_("%s and friends"), $this->user->nickname); + } + } + + function showContent() + { + $notice = $this->user->noticeInbox(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); + + $nl = new NoticeList($notice, $this); + + $cnt = $nl->show(); + + $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'index.php', array('nickname' => $this->user->nickname)); + } + + function showNoticeList($notice) + { + + $nl = new NoticeList($notice, $this); + return $nl->show(); + } + + function getUpdatePermission() { + + $this->showStylesheets(); + + $this->elementStart('div', array('class' => 'facebook_guide')); + + $instructions = sprintf(_('If you would like the %s app to automatically update ' . + 'your Facebook status with your latest notice, you need ' . + 'to give it permission.'), $this->app_name); + + $this->elementStart('p'); + $this->element('span', array('id' => 'permissions_notice'), $instructions); + $this->elementEnd('p'); + + $this->elementStart('form', array('method' => 'post', + 'action' => "index.php", + 'id' => 'facebook-skip-permissions')); + + $this->elementStart('ul', array('id' => 'fb-permissions-list')); + $this->elementStart('li', array('id' => 'fb-permissions-item')); + + $next = urlencode("$this->app_uri/index.php"); + $api_key = common_config('facebook', 'apikey'); + + $auth_url = 'http://www.facebook.com/authorize.php?api_key=' . + $api_key . '&v=1.0&ext_perm=publish_stream&next=' . $next . + '&next_cancel=' . $next . '&submit=skip'; + + $this->elementStart('span', array('class' => 'facebook-button')); + $this->element('a', array('href' => $auth_url), + sprintf(_('Okay, do it!'), $this->app_name)); + $this->elementEnd('span'); + + $this->elementEnd('li'); + + $this->elementStart('li', array('id' => 'fb-permissions-item')); + $this->submit('skip', _('Skip')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementEnd('form'); + $this->elementEnd('div'); + + } + + /** + * Generate pagination links + * + * @param boolean $have_before is there something before? + * @param boolean $have_after is there something after? + * @param integer $page current page + * @param string $action current action + * @param array $args rest of query arguments + * + * @return nothing + */ + function pagination($have_before, $have_after, $page, $action, $args=null) + { + + // Does a little before-after block for next/prev page + + // XXX: Fix so this uses common_local_url() if possible. + + if ($have_before || $have_after) { + $this->elementStart('div', array('class' => 'pagination')); + $this->elementStart('dl', null); + $this->element('dt', null, _('Pagination')); + $this->elementStart('dd', null); + $this->elementStart('ul', array('class' => 'nav')); + } + if ($have_before) { + $pargs = array('page' => $page-1); + $newargs = $args ? array_merge($args, $pargs) : $pargs; + $this->elementStart('li', array('class' => 'nav_prev')); + $this->element('a', array('href' => "$action?page=$newargs[page]", 'rel' => 'prev'), + _('After')); + $this->elementEnd('li'); + } + if ($have_after) { + $pargs = array('page' => $page+1); + $newargs = $args ? array_merge($args, $pargs) : $pargs; + $this->elementStart('li', array('class' => 'nav_next')); + $this->element('a', array('href' => "$action?page=$newargs[page]", 'rel' => 'next'), + _('Before')); + $this->elementEnd('li'); + } + if ($have_before || $have_after) { + $this->elementEnd('ul'); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); + } + } + +} diff --git a/plugins/Facebook/facebookinvite.php b/plugins/Facebook/facebookinvite.php new file mode 100644 index 0000000000..ecda1717c3 --- /dev/null +++ b/plugins/Facebook/facebookinvite.php @@ -0,0 +1,145 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; + +class FacebookinviteAction extends FacebookAction +{ + + function handle($args) + { + parent::handle($args); + $this->showForm(); + } + + /** + * Wrapper for showing a page + * + * Stores an error and shows the page + * + * @param string $error Error, if any + * + * @return void + */ + + function showForm($error=null) + { + $this->error = $error; + $this->showPage(); + } + + /** + * Show the page content + * + * Either shows the registration form or, if registration was successful, + * instructions for using the site. + * + * @return void + */ + + function showContent() + { + if ($this->arg('ids')) { + $this->showSuccessContent(); + } else { + $this->showFormContent(); + } + } + + function showSuccessContent() + { + + $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), + common_config('site', 'name'))); + $this->element('p', null, _('Invitations have been sent to the following users:')); + + $friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to access the list? + + $this->elementStart('ul', array('id' => 'facebook-friends')); + + foreach ($friend_ids as $friend) { + $this->elementStart('li'); + $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); + $this->element('fb:name', array('uid' => $friend, + 'capitalize' => 'true')); + $this->elementEnd('li'); + } + + $this->elementEnd("ul"); + + } + + function showFormContent() + { + $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . + htmlentities(''); + + $this->elementStart('fb:request-form', array('action' => 'invite.php', + 'method' => 'post', + 'invite' => 'true', + 'type' => common_config('site', 'name'), + 'content' => $content)); + $this->hidden('invite', 'true'); + $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); + + $multi_params = array('showborder' => 'false'); + $multi_params['actiontext'] = $actiontext; + $multi_params['bypass'] = 'cancel'; + + // Get a list of users who are already using the app for exclusion + $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); + $exclude_ids_csv = null; + + // fbml needs these as a csv string, not an array + if ($exclude_ids) { + $exclude_ids_csv = implode(',', $exclude_ids); + $multi_params['exclude_ids'] = $exclude_ids_csv; + } + + $this->element('fb:multi-friend-selector', $multi_params); + $this->elementEnd('fb:request-form'); + + if ($exclude_ids) { + + $this->element('h2', null, sprintf(_('Friends already using %s:'), + common_config('site', 'name'))); + $this->elementStart('ul', array('id' => 'facebook-friends')); + + foreach ($exclude_ids as $friend) { + $this->elementStart('li'); + $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); + $this->element('fb:name', array('uid' => $friend, + 'capitalize' => 'true')); + $this->elementEnd('li'); + } + + $this->elementEnd("ul"); + } + } + + function title() + { + return sprintf(_('Send invitations')); + } + +} diff --git a/plugins/Facebook/facebooklogin.php b/plugins/Facebook/facebooklogin.php new file mode 100644 index 0000000000..f77aecca36 --- /dev/null +++ b/plugins/Facebook/facebooklogin.php @@ -0,0 +1,99 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; + +class FacebookinviteAction extends FacebookAction +{ + + function handle($args) + { + parent::handle($args); + + $this->error = $error; + + if ($this->flink) { + if (!$this->facebook->api_client->users_hasAppPermission('publish_stream') && + $this->facebook->api_client->data_getUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF) == 'true') { + + echo '

REDIRECT TO HOME

'; + } + } else { + $this->showPage(); + } + } + + function showContent() + { + + // If the user has opted not to initially allow the app to have + // Facebook status update permission, store that preference. Only + // promt the user the first time she uses the app + if ($this->arg('skip')) { + $this->facebook->api_client->data_setUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF, 'true'); + } + + if ($this->flink) { + + $this->user = $this->flink->getUser(); + + // If this is the first time the user has started the app + // prompt for Facebook status update permission + if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { + + if ($this->facebook->api_client->data_getUserPreference( + FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { + $this->getUpdatePermission(); + return; + } + } + + } else { + $this->showLoginForm(); + } + + } + + function showSuccessContent() + { + + } + + function showFormContent() + { + + } + + function title() + { + return sprintf(_('Login')); + } + + function redirectHome() + { + + } + +} diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php new file mode 100755 index 0000000000..30de59efb7 --- /dev/null +++ b/plugins/Facebook/facebookqueuehandler.php @@ -0,0 +1,74 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<log(LOG_INFO, "INITIALIZE"); + return true; + } + + function handle_notice($notice) + { + return facebookBroadcastNotice($notice); + } + + function finish() + { + } + +} + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +$handler = new FacebookQueueHandler($id); + +$handler->runOnce(); diff --git a/plugins/Facebook/facebookremove.php b/plugins/Facebook/facebookremove.php new file mode 100644 index 0000000000..8531a8e6ea --- /dev/null +++ b/plugins/Facebook/facebookremove.php @@ -0,0 +1,69 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; + +class FacebookremoveAction extends FacebookAction +{ + + function handle($args) + { + parent::handle($args); + + $secret = common_config('facebook', 'secret'); + + $sig = ''; + + ksort($_POST); + + foreach ($_POST as $key => $val) { + if (substr($key, 0, 7) == 'fb_sig_') { + $sig .= substr($key, 7) . '=' . $val; + } + } + + $sig .= $secret; + $verify = md5($sig); + + if ($verify == $this->arg('fb_sig')) { + + $flink = Foreign_link::getByForeignID($this->arg('fb_sig_user'), 2); + + common_debug("Removing foreign link to Facebook - local user ID: $flink->user_id, Facebook ID: $flink->foreign_id"); + + $result = $flink->delete(); + + if (!$result) { + common_log_db_error($flink, 'DELETE', __FILE__); + $this->serverError(_('Couldn\'t remove Facebook user.')); + return; + } + + } else { + # Someone bad tried to remove facebook link? + common_log(LOG_ERR, "Someone from $_SERVER[REMOTE_ADDR] " . + 'unsuccessfully tried to remove a foreign link to Facebook!'); + } + } + +} diff --git a/plugins/Facebook/facebooksettings.php b/plugins/Facebook/facebooksettings.php new file mode 100644 index 0000000000..4bfdfc0efc --- /dev/null +++ b/plugins/Facebook/facebooksettings.php @@ -0,0 +1,159 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/facebookaction.php'; + +class FacebooksettingsAction extends FacebookAction +{ + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + /** + * Show the page content + * + * Either shows the registration form or, if registration was successful, + * instructions for using the site. + * + * @return void + */ + + function showContent() + { + if ($this->arg('save')) { + $this->saveSettings(); + } else { + $this->showForm(); + } + } + + function saveSettings() { + + $noticesync = $this->arg('noticesync'); + $replysync = $this->arg('replysync'); + $prefix = $this->trimmed('prefix'); + + $original = clone($this->flink); + $this->flink->set_flags($noticesync, $replysync, false, false); + $result = $this->flink->update($original); + + if ($prefix == '' || $prefix == '0') { + // Facebook bug: saving empty strings to prefs now fails + // http://bugs.developers.facebook.com/show_bug.cgi?id=7110 + $trimmed = $prefix . ' '; + } else { + $trimmed = substr($prefix, 0, 128); + } + $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, + $trimmed); + + if ($result === false) { + $this->showForm(_('There was a problem saving your sync preferences!')); + } else { + $this->showForm(_('Sync preferences saved.'), true); + } + } + + function showForm($msg = null, $success = false) { + + if ($msg) { + if ($success) { + $this->element('fb:success', array('message' => $msg)); + } else { + $this->element('fb:error', array('message' => $msg)); + } + } + + if ($this->facebook->api_client->users_hasAppPermission('publish_stream')) { + + $this->elementStart('form', array('method' => 'post', + 'id' => 'facebook_settings')); + + $this->elementStart('ul', 'form_data'); + + $this->elementStart('li'); + + $this->checkbox('noticesync', _('Automatically update my Facebook status with my notices.'), + ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND) : true); + + $this->elementEnd('li'); + + $this->elementStart('li'); + + $this->checkbox('replysync', _('Send "@" replies to Facebook.'), + ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true); + + $this->elementEnd('li'); + + $this->elementStart('li'); + + $prefix = trim($this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX)); + + $this->input('prefix', _('Prefix'), + ($prefix) ? $prefix : null, + _('A string to prefix notices with.')); + + $this->elementEnd('li'); + + $this->elementStart('li'); + + $this->submit('save', _('Save')); + + $this->elementEnd('li'); + + $this->elementEnd('ul'); + + $this->elementEnd('form'); + + } else { + + $instructions = sprintf(_('If you would like %s to automatically update ' . + 'your Facebook status with your latest notice, you need ' . + 'to give it permission.'), $this->app_name); + + $this->elementStart('p'); + $this->element('span', array('id' => 'permissions_notice'), $instructions); + $this->elementEnd('p'); + + $this->elementStart('ul', array('id' => 'fb-permissions-list')); + $this->elementStart('li', array('id' => 'fb-permissions-item')); + $this->elementStart('fb:prompt-permission', array('perms' => 'publish_stream', + 'next_fbjs' => 'document.setLocation(\'' . "$this->app_uri/settings.php" . '\')')); + $this->element('span', array('class' => 'facebook-button'), + sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name'))); + $this->elementEnd('fb:prompt-permission'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + } + + } + + function title() + { + return _('Sync preferences'); + } + +} diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php new file mode 100644 index 0000000000..9817837f74 --- /dev/null +++ b/plugins/Facebook/facebookutil.php @@ -0,0 +1,260 @@ +. + */ + +require_once INSTALLDIR . '/extlib/facebook/facebook.php'; +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; +require_once INSTALLDIR . '/lib/noticelist.php'; + +define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2 +define("FACEBOOK_NOTICE_PREFIX", 1); +define("FACEBOOK_PROMPTED_UPDATE_PREF", 2); + +function getFacebook() +{ + static $facebook = null; + + $apikey = common_config('facebook', 'apikey'); + $secret = common_config('facebook', 'secret'); + + if ($facebook === null) { + $facebook = new Facebook($apikey, $secret); + } + + if (empty($facebook)) { + common_log(LOG_ERR, 'Could not make new Facebook client obj!', + __FILE__); + } + + return $facebook; +} + +function isFacebookBound($notice, $flink) { + + if (empty($flink)) { + return false; + } + + // Avoid a loop + + if ($notice->source == 'Facebook') { + common_log(LOG_INFO, "Skipping notice $notice->id because its " . + 'source is Facebook.'); + return false; + } + + // If the user does not want to broadcast to Facebook, move along + + if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) { + common_log(LOG_INFO, "Skipping notice $notice->id " . + 'because user has FOREIGN_NOTICE_SEND bit off.'); + return false; + } + + // If it's not a reply, or if the user WANTS to send @-replies, + // then, yeah, it can go to Facebook. + + if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) || + ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) { + return true; + } + + return false; + +} + +function facebookBroadcastNotice($notice) +{ + $facebook = getFacebook(); + $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); + + if (isFacebookBound($notice, $flink)) { + + // Okay, we're good to go, update the FB status + + $status = null; + $fbuid = $flink->foreign_id; + $user = $flink->getUser(); + $attachments = $notice->attachments(); + + try { + + // Get the status 'verb' (prefix) the user has set + + // XXX: Does this call count against our per user FB request limit? + // If so we should consider storing verb elsewhere or not storing + + $prefix = trim($facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, + $fbuid)); + + $status = "$prefix $notice->content"; + + $can_publish = $facebook->api_client->users_hasAppPermission('publish_stream', + $fbuid); + + $can_update = $facebook->api_client->users_hasAppPermission('status_update', + $fbuid); + if (!empty($attachments) && $can_publish == 1) { + $fbattachment = format_attachments($attachments); + $facebook->api_client->stream_publish($status, $fbattachment, + null, null, $fbuid); + common_log(LOG_INFO, + "Posted notice $notice->id w/attachment " . + "to Facebook user's stream (fbuid = $fbuid)."); + } elseif ($can_update == 1 || $can_publish == 1) { + $facebook->api_client->users_setStatus($status, $fbuid, false, true); + common_log(LOG_INFO, + "Posted notice $notice->id to Facebook " . + "as a status update (fbuid = $fbuid)."); + } else { + $msg = "Not sending notice $notice->id to Facebook " . + "because user $user->nickname hasn't given the " . + 'Facebook app \'status_update\' or \'publish_stream\' permission.'; + common_log(LOG_WARNING, $msg); + } + + // Finally, attempt to update the user's profile box + + if ($can_publish == 1 || $can_update == 1) { + updateProfileBox($facebook, $flink, $notice); + } + + } catch (FacebookRestClientException $e) { + + $code = $e->getCode(); + + common_log(LOG_WARNING, 'Facebook returned error code ' . + $code . ': ' . $e->getMessage()); + common_log(LOG_WARNING, + 'Unable to update Facebook status for ' . + "$user->nickname (user id: $user->id)!"); + + if ($code == 200 || $code == 250) { + + // 200 The application does not have permission to operate on the passed in uid parameter. + // 250 Updating status requires the extended permission status_update or publish_stream. + // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML + + remove_facebook_app($flink); + + } else { + + // Try sending again later. + + return false; + } + + } + } + + return true; + +} + +function updateProfileBox($facebook, $flink, $notice) { + $fbaction = new FacebookAction($output = 'php://output', + $indent = true, $facebook, $flink); + $fbaction->updateProfileBox($notice); +} + +function format_attachments($attachments) +{ + $fbattachment = array(); + $fbattachment['media'] = array(); + + foreach($attachments as $attachment) + { + if($enclosure = $attachment->getEnclosure()){ + $fbmedia = get_fbmedia_for_attachment($enclosure); + }else{ + $fbmedia = get_fbmedia_for_attachment($attachment); + } + if($fbmedia){ + $fbattachment['media'][]=$fbmedia; + }else{ + $fbattachment['name'] = ($attachment->title ? + $attachment->title : $attachment->url); + $fbattachment['href'] = $attachment->url; + } + } + if(count($fbattachment['media'])>0){ + unset($fbattachment['name']); + unset($fbattachment['href']); + } + return $fbattachment; +} + +/** +* given an File objects, returns an associative array suitable for Facebook media +*/ +function get_fbmedia_for_attachment($attachment) +{ + $fbmedia = array(); + + if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) { + $fbmedia['type'] = 'image'; + $fbmedia['src'] = $attachment->url; + $fbmedia['href'] = $attachment->url; + } else if ($attachment->mimetype == 'audio/mpeg') { + $fbmedia['type'] = 'mp3'; + $fbmedia['src'] = $attachment->url; + }else if ($attachment->mimetype == 'application/x-shockwave-flash') { + $fbmedia['type'] = 'flash'; + + // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29 + // says that imgsrc is required... but we have no value to put in it + // $fbmedia['imgsrc']=''; + + $fbmedia['swfsrc'] = $attachment->url; + }else{ + return false; + } + return $fbmedia; +} + +function remove_facebook_app($flink) +{ + + $user = $flink->getUser(); + + common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' . + "user $user->nickname (user id: $user->id)."); + + $result = $flink->delete(); + + if (empty($result)) { + common_log(LOG_ERR, 'Could not remove Facebook App ' . + "Foreign_link for $user->nickname (user id: $user->id)!"); + common_log_db_error($flink, 'DELETE', __FILE__); + } + + // Notify the user that we are removing their FB app access + + $result = mail_facebook_app_removed($user); + + if (!$result) { + + $msg = 'Unable to send email to notify ' . + "$user->nickname (user id: $user->id) " . + 'that their Facebook app link was ' . + 'removed!'; + + common_log(LOG_WARNING, $msg); + } + +} diff --git a/scripts/facebookqueuehandler.php b/scripts/facebookqueuehandler.php deleted file mode 100755 index e13ac4e850..0000000000 --- a/scripts/facebookqueuehandler.php +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - - function handle_notice($notice) - { - return facebookBroadcastNotice($notice); - } - - function finish() - { - } - -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new FacebookQueueHandler($id); - -$handler->runOnce(); diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 7caea1bb75..99ad41b374 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -39,7 +39,6 @@ $daemons = array(); $daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php'; $daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php'; -$daemons[] = INSTALLDIR.'/scripts/facebookqueuehandler.php'; $daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php'; if(common_config('xmpp','enabled')) {