From 2b79b4d21fa33844911f28b9e33ec958cbac3de9 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 06:19:56 -0400
Subject: Move OpenID-related files to OpenID plugin directory
As a first step to pluginizing our OpenID support, I've moved the
important OpenID-related files to a dedicated plugin directory. Many
of these classes are still referred to by libraries that are still in
core.
---
plugins/OpenID/User_openid.php | 25 ++
plugins/OpenID/finishaddopenid.php | 185 +++++++++++++
plugins/OpenID/finishopenidlogin.php | 495 +++++++++++++++++++++++++++++++++++
plugins/OpenID/openid.php | 280 ++++++++++++++++++++
plugins/OpenID/openidlogin.php | 131 +++++++++
plugins/OpenID/openidsettings.php | 234 +++++++++++++++++
plugins/OpenID/publicxrds.php | 122 +++++++++
plugins/OpenID/xrds.php | 173 ++++++++++++
8 files changed, 1645 insertions(+)
create mode 100644 plugins/OpenID/User_openid.php
create mode 100644 plugins/OpenID/finishaddopenid.php
create mode 100644 plugins/OpenID/finishopenidlogin.php
create mode 100644 plugins/OpenID/openid.php
create mode 100644 plugins/OpenID/openidlogin.php
create mode 100644 plugins/OpenID/openidsettings.php
create mode 100644 plugins/OpenID/publicxrds.php
create mode 100644 plugins/OpenID/xrds.php
(limited to 'plugins')
diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php
new file mode 100644
index 000000000..f4fda1c72
--- /dev/null
+++ b/plugins/OpenID/User_openid.php
@@ -0,0 +1,25 @@
+.
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/openid.php';
+
+/**
+ * Complete adding an OpenID
+ *
+ * Handle the return from an OpenID verification
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class FinishaddopenidAction extends Action
+{
+ var $msg = null;
+
+ /**
+ * Handle the redirect back from OpenID confirmation
+ *
+ * Check to see if the user's logged in, and then try
+ * to use the OpenID login system.
+ *
+ * @param array $args $_REQUEST arguments
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ $this->clientError(_('Not logged in.'));
+ } else {
+ $this->tryLogin();
+ }
+ }
+
+ /**
+ * Try to log in using OpenID
+ *
+ * Check the OpenID for validity; potentially store it.
+ *
+ * @return void
+ */
+
+ function tryLogin()
+ {
+ $consumer =& oid_consumer();
+
+ $response = $consumer->complete(common_local_url('finishaddopenid'));
+
+ if ($response->status == Auth_OpenID_CANCEL) {
+ $this->message(_('OpenID authentication cancelled.'));
+ return;
+ } else if ($response->status == Auth_OpenID_FAILURE) {
+ // Authentication failed; display the error message.
+ $this->message(sprintf(_('OpenID authentication failed: %s'),
+ $response->message));
+ } else if ($response->status == Auth_OpenID_SUCCESS) {
+
+ $display = $response->getDisplayIdentifier();
+ $canonical = ($response->endpoint && $response->endpoint->canonicalID) ?
+ $response->endpoint->canonicalID : $display;
+
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+ if ($sreg_resp) {
+ $sreg = $sreg_resp->contents();
+ }
+
+ $cur =& common_current_user();
+
+ $other = oid_get_user($canonical);
+
+ if ($other) {
+ if ($other->id == $cur->id) {
+ $this->message(_('You already have this OpenID!'));
+ } else {
+ $this->message(_('Someone else already has this OpenID.'));
+ }
+ return;
+ }
+
+ // start a transaction
+
+ $cur->query('BEGIN');
+
+ $result = oid_link_user($cur->id, $canonical, $display);
+
+ if (!$result) {
+ $this->message(_('Error connecting user.'));
+ return;
+ }
+ if ($sreg) {
+ if (!oid_update_user($cur, $sreg)) {
+ $this->message(_('Error updating profile'));
+ return;
+ }
+ }
+
+ // success!
+
+ $cur->query('COMMIT');
+
+ oid_set_last($display);
+
+ common_redirect(common_local_url('openidsettings'), 303);
+ }
+ }
+
+ /**
+ * Show a failure message
+ *
+ * Something went wrong. Save the message, and show the page.
+ *
+ * @param string $msg Error message to show
+ *
+ * @return void
+ */
+
+ function message($msg)
+ {
+ $this->message = $msg;
+ $this->showPage();
+ }
+
+ /**
+ * Title of the page
+ *
+ * @return string title
+ */
+
+ function title()
+ {
+ return _('OpenID Login');
+ }
+
+ /**
+ * Show error message
+ *
+ * @return void
+ */
+
+ function showPageNotice()
+ {
+ if ($this->message) {
+ $this->element('p', 'error', $this->message);
+ }
+ }
+}
diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php
new file mode 100644
index 000000000..ff0b35218
--- /dev/null
+++ b/plugins/OpenID/finishopenidlogin.php
@@ -0,0 +1,495 @@
+.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class FinishopenidloginAction extends Action
+{
+ var $error = null;
+ var $username = null;
+ var $message = null;
+
+ function handle($args)
+ {
+ parent::handle($args);
+ if (common_is_real_login()) {
+ $this->clientError(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+ if ($this->arg('create')) {
+ if (!$this->boolean('license')) {
+ $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
+ $this->trimmed('newname'));
+ return;
+ }
+ $this->createNewUser();
+ } else if ($this->arg('connect')) {
+ $this->connectUser();
+ } else {
+ common_debug(print_r($this->args, true), __FILE__);
+ $this->showForm(_('Something weird happened.'),
+ $this->trimmed('newname'));
+ }
+ } else {
+ $this->tryLogin();
+ }
+ }
+
+ function showPageNotice()
+ {
+ if ($this->error) {
+ $this->element('div', array('class' => 'error'), $this->error);
+ } else {
+ $this->element('div', 'instructions',
+ sprintf(_('This is the first time you\'ve logged into %s so we must connect your OpenID to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
+ }
+ }
+
+ function title()
+ {
+ return _('OpenID Account Setup');
+ }
+
+ function showForm($error=null, $username=null)
+ {
+ $this->error = $error;
+ $this->username = $username;
+
+ $this->showPage();
+ }
+
+ function showContent()
+ {
+ if (!empty($this->message_text)) {
+ $this->element('div', array('class' => 'error'), $this->message_text);
+ return;
+ }
+
+ $this->elementStart('form', array('method' => 'post',
+ 'id' => 'account_connect',
+ 'action' => common_local_url('finishopenidlogin')));
+ $this->hidden('token', common_session_token());
+ $this->element('h2', null,
+ _('Create new account'));
+ $this->element('p', null,
+ _('Create a new user with this nickname.'));
+ $this->input('newname', _('New nickname'),
+ ($this->username) ? $this->username : '',
+ _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+ $this->elementStart('p');
+ $this->element('input', array('type' => 'checkbox',
+ 'id' => 'license',
+ 'name' => 'license',
+ 'value' => 'true'));
+ $this->text(_('My text and files are available under '));
+ $this->element('a', array('href' => common_config('license', 'url')),
+ common_config('license', 'title'));
+ $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+ $this->elementEnd('p');
+ $this->submit('create', _('Create'));
+ $this->element('h2', null,
+ _('Connect existing account'));
+ $this->element('p', null,
+ _('If you already have an account, login with your username and password to connect it to your OpenID.'));
+ $this->input('nickname', _('Existing nickname'));
+ $this->password('password', _('Password'));
+ $this->submit('connect', _('Connect'));
+ $this->elementEnd('form');
+ }
+
+ function tryLogin()
+ {
+ $consumer = oid_consumer();
+
+ $response = $consumer->complete(common_local_url('finishopenidlogin'));
+
+ if ($response->status == Auth_OpenID_CANCEL) {
+ $this->message(_('OpenID authentication cancelled.'));
+ return;
+ } else if ($response->status == Auth_OpenID_FAILURE) {
+ // Authentication failed; display the error message.
+ $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
+ } else if ($response->status == Auth_OpenID_SUCCESS) {
+ // This means the authentication succeeded; extract the
+ // identity URL and Simple Registration data (if it was
+ // returned).
+ $display = $response->getDisplayIdentifier();
+ $canonical = ($response->endpoint->canonicalID) ?
+ $response->endpoint->canonicalID : $response->getDisplayIdentifier();
+
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+ if ($sreg_resp) {
+ $sreg = $sreg_resp->contents();
+ }
+
+ $user = oid_get_user($canonical);
+
+ if ($user) {
+ oid_set_last($display);
+ # XXX: commented out at @edd's request until better
+ # control over how data flows from OpenID provider.
+ # oid_update_user($user, $sreg);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ $this->goHome($user->nickname);
+ } else {
+ $this->saveValues($display, $canonical, $sreg);
+ $this->showForm(null, $this->bestNewNickname($display, $sreg));
+ }
+ }
+ }
+
+ function message($msg)
+ {
+ $this->message_text = $msg;
+ $this->showPage();
+ }
+
+ function saveValues($display, $canonical, $sreg)
+ {
+ common_ensure_session();
+ $_SESSION['openid_display'] = $display;
+ $_SESSION['openid_canonical'] = $canonical;
+ $_SESSION['openid_sreg'] = $sreg;
+ }
+
+ function getSavedValues()
+ {
+ return array($_SESSION['openid_display'],
+ $_SESSION['openid_canonical'],
+ $_SESSION['openid_sreg']);
+ }
+
+ function createNewUser()
+ {
+ # FIXME: save invite code before redirect, and check here
+
+ if (common_config('site', 'closed')) {
+ $this->clientError(_('Registration not allowed.'));
+ return;
+ }
+
+ $invite = null;
+
+ if (common_config('site', 'inviteonly')) {
+ $code = $_SESSION['invitecode'];
+ if (empty($code)) {
+ $this->clientError(_('Registration not allowed.'));
+ return;
+ }
+
+ $invite = Invitation::staticGet($code);
+
+ if (empty($invite)) {
+ $this->clientError(_('Not a valid invitation code.'));
+ return;
+ }
+ }
+
+ $nickname = $this->trimmed('newname');
+
+ if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+ return;
+ }
+
+ if (!User::allowed_nickname($nickname)) {
+ $this->showForm(_('Nickname not allowed.'));
+ return;
+ }
+
+ if (User::staticGet('nickname', $nickname)) {
+ $this->showForm(_('Nickname already in use. Try another one.'));
+ return;
+ }
+
+ list($display, $canonical, $sreg) = $this->getSavedValues();
+
+ if (!$display || !$canonical) {
+ $this->serverError(_('Stored OpenID not found.'));
+ return;
+ }
+
+ # Possible race condition... let's be paranoid
+
+ $other = oid_get_user($canonical);
+
+ if ($other) {
+ $this->serverError(_('Creating new account for OpenID that already has a user.'));
+ return;
+ }
+
+ $location = '';
+ if (!empty($sreg['country'])) {
+ if ($sreg['postcode']) {
+ # XXX: use postcode to get city and region
+ # XXX: also, store postcode somewhere -- it's valuable!
+ $location = $sreg['postcode'] . ', ' . $sreg['country'];
+ } else {
+ $location = $sreg['country'];
+ }
+ }
+
+ if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
+ $fullname = $sreg['fullname'];
+ } else {
+ $fullname = '';
+ }
+
+ if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
+ $email = $sreg['email'];
+ } else {
+ $email = '';
+ }
+
+ # XXX: add language
+ # XXX: add timezone
+
+ $args = array('nickname' => $nickname,
+ 'email' => $email,
+ 'fullname' => $fullname,
+ 'location' => $location);
+
+ if (!empty($invite)) {
+ $args['code'] = $invite->code;
+ }
+
+ $user = User::register($args);
+
+ $result = oid_link_user($user->id, $canonical, $display);
+
+ oid_set_last($display);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
+ 303);
+ }
+
+ function connectUser()
+ {
+ $nickname = $this->trimmed('nickname');
+ $password = $this->trimmed('password');
+
+ if (!common_check_user($nickname, $password)) {
+ $this->showForm(_('Invalid username or password.'));
+ return;
+ }
+
+ # They're legit!
+
+ $user = User::staticGet('nickname', $nickname);
+
+ list($display, $canonical, $sreg) = $this->getSavedValues();
+
+ if (!$display || !$canonical) {
+ $this->serverError(_('Stored OpenID not found.'));
+ return;
+ }
+
+ $result = oid_link_user($user->id, $canonical, $display);
+
+ if (!$result) {
+ $this->serverError(_('Error connecting user to OpenID.'));
+ return;
+ }
+
+ oid_update_user($user, $sreg);
+ oid_set_last($display);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ $this->goHome($user->nickname);
+ }
+
+ function goHome($nickname)
+ {
+ $url = common_get_returnto();
+ if ($url) {
+ # We don't have to return to it again
+ common_set_returnto(null);
+ } else {
+ $url = common_local_url('all',
+ array('nickname' =>
+ $nickname));
+ }
+ common_redirect($url, 303);
+ }
+
+ function bestNewNickname($display, $sreg)
+ {
+
+ # Try the passed-in nickname
+
+ if (!empty($sreg['nickname'])) {
+ $nickname = $this->nicknamize($sreg['nickname']);
+ if ($this->isNewNickname($nickname)) {
+ return $nickname;
+ }
+ }
+
+ # Try the full name
+
+ if (!empty($sreg['fullname'])) {
+ $fullname = $this->nicknamize($sreg['fullname']);
+ if ($this->isNewNickname($fullname)) {
+ return $fullname;
+ }
+ }
+
+ # Try the URL
+
+ $from_url = $this->openidToNickname($display);
+
+ if ($from_url && $this->isNewNickname($from_url)) {
+ return $from_url;
+ }
+
+ # XXX: others?
+
+ return null;
+ }
+
+ function isNewNickname($str)
+ {
+ if (!Validate::string($str, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ return false;
+ }
+ if (!User::allowed_nickname($str)) {
+ return false;
+ }
+ if (User::staticGet('nickname', $str)) {
+ return false;
+ }
+ return true;
+ }
+
+ function openidToNickname($openid)
+ {
+ if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
+ return $this->xriToNickname($openid);
+ } else {
+ return $this->urlToNickname($openid);
+ }
+ }
+
+ # We try to use an OpenID URL as a legal Laconica user name in this order
+ # 1. Plain hostname, like http://evanp.myopenid.com/
+ # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
+ # or http://getopenid.com/evanprodromou
+
+ function urlToNickname($openid)
+ {
+ static $bad = array('query', 'user', 'password', 'port', 'fragment');
+
+ $parts = parse_url($openid);
+
+ # If any of these parts exist, this won't work
+
+ foreach ($bad as $badpart) {
+ if (array_key_exists($badpart, $parts)) {
+ return null;
+ }
+ }
+
+ # We just have host and/or path
+
+ # If it's just a host...
+ if (array_key_exists('host', $parts) &&
+ (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
+ {
+ $hostparts = explode('.', $parts['host']);
+
+ # Try to catch common idiom of nickname.service.tld
+
+ if ((count($hostparts) > 2) &&
+ (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
+ (strcmp($hostparts[0], 'www') != 0))
+ {
+ return $this->nicknamize($hostparts[0]);
+ } else {
+ # Do the whole hostname
+ return $this->nicknamize($parts['host']);
+ }
+ } else {
+ if (array_key_exists('path', $parts)) {
+ # Strip starting, ending slashes
+ $path = preg_replace('@/$@', '', $parts['path']);
+ $path = preg_replace('@^/@', '', $path);
+ if (strpos($path, '/') === false) {
+ return $this->nicknamize($path);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ function xriToNickname($xri)
+ {
+ $base = $this->xriBase($xri);
+
+ if (!$base) {
+ return null;
+ } else {
+ # =evan.prodromou
+ # or @gratis*evan.prodromou
+ $parts = explode('*', substr($base, 1));
+ return $this->nicknamize(array_pop($parts));
+ }
+ }
+
+ function xriBase($xri)
+ {
+ if (substr($xri, 0, 6) == 'xri://') {
+ return substr($xri, 6);
+ } else {
+ return $xri;
+ }
+ }
+
+ # Given a string, try to make it work as a nickname
+
+ function nicknamize($str)
+ {
+ $str = preg_replace('/\W/', '', $str);
+ return strtolower($str);
+ }
+}
diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php
new file mode 100644
index 000000000..0b7633284
--- /dev/null
+++ b/plugins/OpenID/openid.php
@@ -0,0 +1,280 @@
+.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/classes/User_openid.php');
+
+require_once('Auth/OpenID.php');
+require_once('Auth/OpenID/Consumer.php');
+require_once('Auth/OpenID/SReg.php');
+require_once('Auth/OpenID/MySQLStore.php');
+
+# About one year cookie expiry
+
+define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60));
+define('OPENID_COOKIE_KEY', 'lastusedopenid');
+
+function oid_store()
+{
+ static $store = null;
+ if (!$store) {
+ # Can't be called statically
+ $user = new User();
+ $conn = $user->getDatabaseConnection();
+ $store = new Auth_OpenID_MySQLStore($conn);
+ }
+ return $store;
+}
+
+function oid_consumer()
+{
+ $store = oid_store();
+ $consumer = new Auth_OpenID_Consumer($store);
+ return $consumer;
+}
+
+function oid_clear_last()
+{
+ oid_set_last('');
+}
+
+function oid_set_last($openid_url)
+{
+ common_set_cookie(OPENID_COOKIE_KEY,
+ $openid_url,
+ time() + OPENID_COOKIE_EXPIRY);
+}
+
+function oid_get_last()
+{
+ if (empty($_COOKIE[OPENID_COOKIE_KEY])) {
+ return null;
+ }
+ $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
+ if ($openid_url && strlen($openid_url) > 0) {
+ return $openid_url;
+ } else {
+ return null;
+ }
+}
+
+function oid_link_user($id, $canonical, $display)
+{
+
+ $oid = new User_openid();
+ $oid->user_id = $id;
+ $oid->canonical = $canonical;
+ $oid->display = $display;
+ $oid->created = DB_DataObject_Cast::dateTime();
+
+ if (!$oid->insert()) {
+ $err = PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
+ return false;
+ }
+
+ return true;
+}
+
+function oid_get_user($openid_url)
+{
+ $user = null;
+ $oid = User_openid::staticGet('canonical', $openid_url);
+ if ($oid) {
+ $user = User::staticGet('id', $oid->user_id);
+ }
+ return $user;
+}
+
+function oid_check_immediate($openid_url, $backto=null)
+{
+ if (!$backto) {
+ $action = $_REQUEST['action'];
+ $args = common_copy_args($_GET);
+ unset($args['action']);
+ $backto = common_local_url($action, $args);
+ }
+ common_debug('going back to "' . $backto . '"', __FILE__);
+
+ common_ensure_session();
+
+ $_SESSION['openid_immediate_backto'] = $backto;
+ common_debug('passed-in variable is "' . $backto . '"', __FILE__);
+ common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
+
+ oid_authenticate($openid_url,
+ 'finishimmediate',
+ true);
+}
+
+function oid_authenticate($openid_url, $returnto, $immediate=false)
+{
+
+ $consumer = oid_consumer();
+
+ if (!$consumer) {
+ common_server_error(_('Cannot instantiate OpenID consumer object.'));
+ return false;
+ }
+
+ common_ensure_session();
+
+ $auth_request = $consumer->begin($openid_url);
+
+ // Handle failure status return values.
+ if (!$auth_request) {
+ return _('Not a valid OpenID.');
+ } else if (Auth_OpenID::isFailure($auth_request)) {
+ return sprintf(_('OpenID failure: %s'), $auth_request->message);
+ }
+
+ $sreg_request = Auth_OpenID_SRegRequest::build(// Required
+ array(),
+ // Optional
+ array('nickname',
+ 'email',
+ 'fullname',
+ 'language',
+ 'timezone',
+ 'postcode',
+ 'country'));
+
+ if ($sreg_request) {
+ $auth_request->addExtension($sreg_request);
+ }
+
+ $trust_root = common_root_url(true);
+ $process_url = common_local_url($returnto);
+
+ if ($auth_request->shouldSendRedirect()) {
+ $redirect_url = $auth_request->redirectURL($trust_root,
+ $process_url,
+ $immediate);
+ if (!$redirect_url) {
+ } else if (Auth_OpenID::isFailure($redirect_url)) {
+ return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
+ } else {
+ common_redirect($redirect_url, 303);
+ }
+ } else {
+ // Generate form markup and render it.
+ $form_id = 'openid_message';
+ $form_html = $auth_request->formMarkup($trust_root, $process_url,
+ $immediate, array('id' => $form_id));
+
+ # XXX: This is cheap, but things choke if we don't escape ampersands
+ # in the HTML attributes
+
+ $form_html = preg_replace('/&/', '&', $form_html);
+
+ // Display an error if the form markup couldn't be generated;
+ // otherwise, render the HTML.
+ if (Auth_OpenID::isFailure($form_html)) {
+ common_server_error(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
+ } else {
+ $action = new AutosubmitAction(); // see below
+ $action->form_html = $form_html;
+ $action->form_id = $form_id;
+ $action->prepare(array('action' => 'autosubmit'));
+ $action->handle(array('action' => 'autosubmit'));
+ }
+ }
+}
+
+# Half-assed attempt at a module-private function
+
+function _oid_print_instructions()
+{
+ common_element('div', 'instructions',
+ _('This form should automatically submit itself. '.
+ 'If not, click the submit button to go to your '.
+ 'OpenID provider.'));
+}
+
+# update a user from sreg parameters
+
+function oid_update_user(&$user, &$sreg)
+{
+
+ $profile = $user->getProfile();
+
+ $orig_profile = clone($profile);
+
+ if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+ $profile->fullname = $sreg['fullname'];
+ }
+
+ if ($sreg['country']) {
+ if ($sreg['postcode']) {
+ # XXX: use postcode to get city and region
+ # XXX: also, store postcode somewhere -- it's valuable!
+ $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
+ } else {
+ $profile->location = $sreg['country'];
+ }
+ }
+
+ # XXX save language if it's passed
+ # XXX save timezone if it's passed
+
+ if (!$profile->update($orig_profile)) {
+ common_server_error(_('Error saving the profile.'));
+ return false;
+ }
+
+ $orig_user = clone($user);
+
+ if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+ $user->email = $sreg['email'];
+ }
+
+ if (!$user->update($orig_user)) {
+ common_server_error(_('Error saving the user.'));
+ return false;
+ }
+
+ return true;
+}
+
+class AutosubmitAction extends Action
+{
+ var $form_html = null;
+ var $form_id = null;
+
+ function handle($args)
+ {
+ parent::handle($args);
+ $this->showPage();
+ }
+
+ function title()
+ {
+ return _('OpenID Auto-Submit');
+ }
+
+ function showContent()
+ {
+ $this->raw($this->form_html);
+ $this->element('script', null,
+ '$(document).ready(function() { ' .
+ ' $(\'#'. $this->form_id .'\').submit(); '.
+ '});');
+ }
+}
diff --git a/plugins/OpenID/openidlogin.php b/plugins/OpenID/openidlogin.php
new file mode 100644
index 000000000..a8d052096
--- /dev/null
+++ b/plugins/OpenID/openidlogin.php
@@ -0,0 +1,131 @@
+.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class OpenidloginAction extends Action
+{
+ function handle($args)
+ {
+ parent::handle($args);
+ if (common_is_real_login()) {
+ $this->clientError(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $openid_url = $this->trimmed('openid_url');
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(_('There was a problem with your session token. Try again, please.'), $openid_url);
+ return;
+ }
+
+ $rememberme = $this->boolean('rememberme');
+
+ common_ensure_session();
+
+ $_SESSION['openid_rememberme'] = $rememberme;
+
+ $result = oid_authenticate($openid_url,
+ 'finishopenidlogin');
+
+ if (is_string($result)) { # error message
+ unset($_SESSION['openid_rememberme']);
+ $this->showForm($result, $openid_url);
+ }
+ } else {
+ $openid_url = oid_get_last();
+ $this->showForm(null, $openid_url);
+ }
+ }
+
+ function getInstructions()
+ {
+ if (common_logged_in() && !common_is_real_login() &&
+ common_get_returnto()) {
+ // rememberme logins have to reauthenticate before
+ // changing any profile settings (cookie-stealing protection)
+ return _('For security reasons, please re-login with your ' .
+ '[OpenID](%%doc.openid%%) ' .
+ 'before changing your settings.');
+ } else {
+ return _('Login with an [OpenID](%%doc.openid%%) account.');
+ }
+ }
+
+ function showPageNotice()
+ {
+ if ($this->error) {
+ $this->element('div', array('class' => 'error'), $this->error);
+ } else {
+ $instr = $this->getInstructions();
+ $output = common_markup_to_html($instr);
+ $this->elementStart('div', 'instructions');
+ $this->raw($output);
+ $this->elementEnd('div');
+ }
+ }
+
+ function title()
+ {
+ return _('OpenID Login');
+ }
+
+ function showForm($error=null, $openid_url)
+ {
+ $this->error = $error;
+ $this->openid_url = $openid_url;
+ $this->showPage();
+ }
+
+ function showContent() {
+ $formaction = common_local_url('openidlogin');
+ $this->elementStart('form', array('method' => 'post',
+ 'id' => 'form_openid_login',
+ 'class' => 'form_settings',
+ 'action' => $formaction));
+ $this->elementStart('fieldset');
+ $this->element('legend', null, _('OpenID login'));
+ $this->hidden('token', common_session_token());
+
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ $this->input('openid_url', _('OpenID URL'),
+ $this->openid_url,
+ _('Your OpenID URL'));
+ $this->elementEnd('li');
+ $this->elementStart('li', array('id' => 'settings_rememberme'));
+ $this->checkbox('rememberme', _('Remember me'), false,
+ _('Automatically login in the future; ' .
+ 'not for shared computers!'));
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+ $this->submit('submit', _('Login'));
+ $this->elementEnd('fieldset');
+ $this->elementEnd('form');
+ }
+
+ function showLocalNav()
+ {
+ $nav = new LoginGroupNav($this);
+ $nav->show();
+ }
+}
diff --git a/plugins/OpenID/openidsettings.php b/plugins/OpenID/openidsettings.php
new file mode 100644
index 000000000..5f59ebc01
--- /dev/null
+++ b/plugins/OpenID/openidsettings.php
@@ -0,0 +1,234 @@
+.
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/accountsettingsaction.php';
+require_once INSTALLDIR.'/lib/openid.php';
+
+/**
+ * Settings for OpenID
+ *
+ * Lets users add, edit and delete OpenIDs from their account
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class OpenidsettingsAction extends AccountSettingsAction
+{
+ /**
+ * Title of the page
+ *
+ * @return string Page title
+ */
+
+ function title()
+ {
+ return _('OpenID settings');
+ }
+
+ /**
+ * Instructions for use
+ *
+ * @return string Instructions for use
+ */
+
+ function getInstructions()
+ {
+ return _('[OpenID](%%doc.openid%%) lets you log into many sites' .
+ ' with the same user account.'.
+ ' Manage your associated OpenIDs from here.');
+ }
+
+ /**
+ * Show the form for OpenID management
+ *
+ * We have one form with a few different submit buttons to do different things.
+ *
+ * @return void
+ */
+
+ function showContent()
+ {
+ $user = common_current_user();
+
+ $this->elementStart('form', array('method' => 'post',
+ 'id' => 'form_settings_openid_add',
+ 'class' => 'form_settings',
+ 'action' =>
+ common_local_url('openidsettings')));
+ $this->elementStart('fieldset', array('id' => 'settings_openid_add'));
+ $this->element('legend', null, _('Add OpenID'));
+ $this->hidden('token', common_session_token());
+ $this->element('p', 'form_guide',
+ _('If you want to add an OpenID to your account, ' .
+ 'enter it in the box below and click "Add".'));
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ $this->element('label', array('for' => 'openid_url'),
+ _('OpenID URL'));
+ $this->element('input', array('name' => 'openid_url',
+ 'type' => 'text',
+ 'id' => 'openid_url'));
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+ $this->element('input', array('type' => 'submit',
+ 'id' => 'settings_openid_add_action-submit',
+ 'name' => 'add',
+ 'class' => 'submit',
+ 'value' => _('Add')));
+ $this->elementEnd('fieldset');
+ $this->elementEnd('form');
+
+ $oid = new User_openid();
+
+ $oid->user_id = $user->id;
+
+ $cnt = $oid->find();
+
+ if ($cnt > 0) {
+
+ $this->element('h2', null, _('Remove OpenID'));
+
+ if ($cnt == 1 && !$user->password) {
+
+ $this->element('p', 'form_guide',
+ _('Removing your only OpenID '.
+ 'would make it impossible to log in! ' .
+ 'If you need to remove it, '.
+ 'add another OpenID first.'));
+
+ if ($oid->fetch()) {
+ $this->elementStart('p');
+ $this->element('a', array('href' => $oid->canonical),
+ $oid->display);
+ $this->elementEnd('p');
+ }
+
+ } else {
+
+ $this->element('p', 'form_guide',
+ _('You can remove an OpenID from your account '.
+ 'by clicking the button marked "Remove".'));
+ $idx = 0;
+
+ while ($oid->fetch()) {
+ $this->elementStart('form',
+ array('method' => 'POST',
+ 'id' => 'form_settings_openid_delete' . $idx,
+ 'class' => 'form_settings',
+ 'action' =>
+ common_local_url('openidsettings')));
+ $this->elementStart('fieldset');
+ $this->hidden('token', common_session_token());
+ $this->element('a', array('href' => $oid->canonical),
+ $oid->display);
+ $this->element('input', array('type' => 'hidden',
+ 'id' => 'openid_url'.$idx,
+ 'name' => 'openid_url',
+ 'value' => $oid->canonical));
+ $this->element('input', array('type' => 'submit',
+ 'id' => 'remove'.$idx,
+ 'name' => 'remove',
+ 'class' => 'submit remove',
+ 'value' => _('Remove')));
+ $this->elementEnd('fieldset');
+ $this->elementEnd('form');
+ $idx++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle a POST request
+ *
+ * Muxes to different sub-functions based on which button was pushed
+ *
+ * @return void
+ */
+
+ function handlePost()
+ {
+ // CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(_('There was a problem with your session token. '.
+ 'Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('add')) {
+ $result = oid_authenticate($this->trimmed('openid_url'),
+ 'finishaddopenid');
+ if (is_string($result)) { // error message
+ $this->showForm($result);
+ }
+ } else if ($this->arg('remove')) {
+ $this->removeOpenid();
+ } else {
+ $this->showForm(_('Something weird happened.'));
+ }
+ }
+
+ /**
+ * Handles a request to remove an OpenID from the user's account
+ *
+ * Validates input and, if everything is OK, deletes the OpenID.
+ * Reloads the form with a success or error notification.
+ *
+ * @return void
+ */
+
+ function removeOpenid()
+ {
+ $openid_url = $this->trimmed('openid_url');
+
+ $oid = User_openid::staticGet('canonical', $openid_url);
+
+ if (!$oid) {
+ $this->showForm(_('No such OpenID.'));
+ return;
+ }
+ $cur = common_current_user();
+ if (!$cur || $oid->user_id != $cur->id) {
+ $this->showForm(_('That OpenID does not belong to you.'));
+ return;
+ }
+ $oid->delete();
+ $this->showForm(_('OpenID removed.'), true);
+ return;
+ }
+}
diff --git a/plugins/OpenID/publicxrds.php b/plugins/OpenID/publicxrds.php
new file mode 100644
index 000000000..0a1421550
--- /dev/null
+++ b/plugins/OpenID/publicxrds.php
@@ -0,0 +1,122 @@
+
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/openid.php';
+
+/**
+ * Public XRDS for OpenID
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * @todo factor out similarities with XrdsAction
+ */
+class PublicxrdsAction extends Action
+{
+ /**
+ * Is read only?
+ *
+ * @return boolean true
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * Class handler.
+ *
+ * @param array $args array of arguments
+ *
+ * @return nothing
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+ header('Content-Type: application/xrds+xml');
+ $this->startXML();
+ $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ foreach (array('finishopenidlogin', 'finishaddopenid') as $finish) {
+ $this->showService(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
+ common_local_url($finish));
+ }
+ $this->elementEnd('XRD');
+ $this->elementEnd('XRDS');
+ $this->endXML();
+ }
+
+ /**
+ * Show service.
+ *
+ * @param string $type XRDS type
+ * @param string $uri URI
+ * @param array $params type parameters, null by default
+ * @param array $sigs type signatures, null by default
+ * @param string $localId local ID, null by default
+ *
+ * @return void
+ */
+ function showService($type, $uri, $params=null, $sigs=null, $localId=null)
+ {
+ $this->elementStart('Service');
+ if ($uri) {
+ $this->element('URI', null, $uri);
+ }
+ $this->element('Type', null, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ $this->element('Type', null, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ $this->element('Type', null, $sig);
+ }
+ }
+ if ($localId) {
+ $this->element('LocalID', null, $localId);
+ }
+ $this->elementEnd('Service');
+ }
+}
+
diff --git a/plugins/OpenID/xrds.php b/plugins/OpenID/xrds.php
new file mode 100644
index 000000000..9327a3c83
--- /dev/null
+++ b/plugins/OpenID/xrds.php
@@ -0,0 +1,173 @@
+
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/omb.php';
+
+/**
+ * XRDS for OpenID
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ */
+class XrdsAction extends Action
+{
+ /**
+ * Is read only?
+ *
+ * @return boolean true
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * Class handler.
+ *
+ * @param array $args query arguments
+ *
+ * @return void
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+ $nickname = $this->trimmed('nickname');
+ $user = User::staticGet('nickname', $nickname);
+ if (!$user) {
+ $this->clientError(_('No such user.'));
+ return;
+ }
+ $this->showXrds($user);
+ }
+
+ /**
+ * Show XRDS for a user.
+ *
+ * @param class $user XRDS for this user.
+ *
+ * @return void
+ */
+ function showXrds($user)
+ {
+ header('Content-Type: application/xrds+xml');
+ $this->startXML();
+ $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'oauth',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OAUTH_ENDPOINT_REQUEST,
+ common_local_url('requesttoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1),
+ $user->uri);
+ $this->showService(OAUTH_ENDPOINT_AUTHORIZE,
+ common_local_url('userauthorization'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->showService(OAUTH_ENDPOINT_ACCESS,
+ common_local_url('accesstoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->showService(OAUTH_ENDPOINT_RESOURCE,
+ null,
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->elementEnd('XRD');
+
+ // XXX: decide whether to include user's ID/nickname in postNotice URL
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'omb',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OMB_ENDPOINT_POSTNOTICE,
+ common_local_url('postnotice'));
+ $this->showService(OMB_ENDPOINT_UPDATEPROFILE,
+ common_local_url('updateprofile'));
+ $this->elementEnd('XRD');
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OAUTH_DISCOVERY,
+ '#oauth');
+ $this->showService(OMB_NAMESPACE,
+ '#omb');
+ $this->elementEnd('XRD');
+ $this->elementEnd('XRDS');
+ $this->endXML();
+ }
+
+ /**
+ * Show service.
+ *
+ * @param string $type XRDS type
+ * @param string $uri URI
+ * @param array $params type parameters, null by default
+ * @param array $sigs type signatures, null by default
+ * @param string $localId local ID, null by default
+ *
+ * @return void
+ */
+ function showService($type, $uri, $params=null, $sigs=null, $localId=null)
+ {
+ $this->elementStart('Service');
+ if ($uri) {
+ $this->element('URI', null, $uri);
+ }
+ $this->element('Type', null, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ $this->element('Type', null, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ $this->element('Type', null, $sig);
+ }
+ }
+ if ($localId) {
+ $this->element('LocalID', null, $localId);
+ }
+ $this->elementEnd('Service');
+ }
+}
+
--
cgit v1.2.3-54-g00ecf
From 6d64882270aa3dda3cb184d848692123e552ef1e Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 06:43:26 -0400
Subject: OpenIDPlugin sets up actions for router
---
lib/router.php | 9 ++---
plugins/OpenID/OpenIDPlugin.php | 78 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 81 insertions(+), 6 deletions(-)
create mode 100644 plugins/OpenID/OpenIDPlugin.php
(limited to 'plugins')
diff --git a/lib/router.php b/lib/router.php
index 19839b997..37a7e4a0f 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -50,8 +50,7 @@ class Router
var $m = null;
static $inst = null;
static $bare = array('requesttoken', 'accesstoken', 'userauthorization',
- 'postnotice', 'updateprofile', 'finishremotesubscribe',
- 'finishopenidlogin', 'finishaddopenid');
+ 'postnotice', 'updateprofile', 'finishremotesubscribe');
static function get()
{
@@ -76,7 +75,6 @@ class Router
$m->connect('', array('action' => 'public'));
$m->connect('rss', array('action' => 'publicrss'));
- $m->connect('xrds', array('action' => 'publicxrds'));
$m->connect('featuredrss', array('action' => 'featuredrss'));
$m->connect('favoritedrss', array('action' => 'favoritedrss'));
$m->connect('opensearch/people', array('action' => 'opensearch',
@@ -131,7 +129,6 @@ class Router
// exceptional
- $m->connect('main/openid', array('action' => 'openidlogin'));
$m->connect('main/remote', array('action' => 'remotesubscribe'));
$m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
@@ -141,7 +138,7 @@ class Router
// settings
- foreach (array('profile', 'avatar', 'password', 'openid', 'im',
+ foreach (array('profile', 'avatar', 'password', 'im',
'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
$m->connect('settings/'.$s, array('action' => $s.'settings'));
}
@@ -434,7 +431,7 @@ class Router
// user stuff
foreach (array('subscriptions', 'subscribers',
- 'nudge', 'xrds', 'all', 'foaf',
+ 'nudge', 'all', 'foaf',
'replies', 'inbox', 'outbox', 'microsummary') as $a) {
$m->connect(':nickname/'.$a,
array('action' => $a),
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
new file mode 100644
index 000000000..149a92356
--- /dev/null
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -0,0 +1,78 @@
+.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Plugin for OpenID authentication and identity
+ *
+ * This class enables consumer support for OpenID, the distributed authentication
+ * and identity system.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ * @link http://openid.net/
+ */
+
+class OpenIDPlugin extends Plugin
+{
+ /**
+ * Initializer for the plugin.
+ */
+
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Add OpenID-related paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @return boolean hook return
+ */
+
+ function onRouterInitialized(&$m)
+ {
+ $m->connect('main/openid', array('action' => 'openidlogin'));
+ $m->connect('settings/openid', array('action' => 'openidsettings'));
+ $m->connect(':nickname/xrds',
+ array('action' => 'xrds'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'));
+ $m->connect('xrds', array('action' => 'publicxrds'));
+ $m->connect('index.php?action=finishopenidlogin', array('action' => 'finishopenidlogin'));
+ $m->connect('index.php?action=finishaddopenid', array('action' => 'finishaddopenid'));
+
+ return true;
+ }
+}
\ No newline at end of file
--
cgit v1.2.3-54-g00ecf
From 7a742a0572a3f0aa529785f4f7eab320a2dd099d Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 07:20:17 -0400
Subject: show OpenID action in login nav
---
lib/logingroupnav.php | 5 -----
plugins/OpenID/OpenIDPlugin.php | 12 ++++++++++++
2 files changed, 12 insertions(+), 5 deletions(-)
(limited to 'plugins')
diff --git a/lib/logingroupnav.php b/lib/logingroupnav.php
index bae45b077..59f313302 100644
--- a/lib/logingroupnav.php
+++ b/lib/logingroupnav.php
@@ -87,11 +87,6 @@ class LoginGroupNav extends Widget
$action_name === 'register');
}
- $this->action->menuItem(common_local_url('openidlogin'),
- _('OpenID'),
- _('Login or register with OpenID'),
- $action_name === 'openidlogin');
-
Event::handle('EndLoginGroupNav', array(&$this->action));
}
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 149a92356..b70b1da19 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -75,4 +75,16 @@ class OpenIDPlugin extends Plugin
return true;
}
+
+ function onEndLoginGroupNav(&$action)
+ {
+ $action_name = $action->trimmed('action');
+
+ $action->menuItem(common_local_url('openidlogin'),
+ _('OpenID'),
+ _('Login or register with OpenID'),
+ $action_name === 'openidlogin');
+
+ return true;
+ }
}
\ No newline at end of file
--
cgit v1.2.3-54-g00ecf
From 3245357749d30f655ce10e8f71737680a61f59db Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 07:22:56 -0400
Subject: add account settings menu item for OpenID
---
lib/accountsettingsaction.php | 3 ---
plugins/OpenID/OpenIDPlugin.php | 12 ++++++++++++
2 files changed, 12 insertions(+), 3 deletions(-)
(limited to 'plugins')
diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php
index 9814c48ab..9a7c69124 100644
--- a/lib/accountsettingsaction.php
+++ b/lib/accountsettingsaction.php
@@ -116,9 +116,6 @@ class AccountSettingsNav extends Widget
'emailsettings' =>
array(_('Email'),
_('Change email handling')),
- 'openidsettings' =>
- array(_('OpenID'),
- _('Add or remove OpenIDs')),
'userdesignsettings' =>
array(_('Design'),
_('Design your profile')),
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index b70b1da19..f76fe1e3c 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -87,4 +87,16 @@ class OpenIDPlugin extends Plugin
return true;
}
+
+ function onEndAccountSettingsNav(&$action)
+ {
+ $action_name = $action->trimmed('action');
+
+ $action->menuItem(common_local_url('openidsettings'),
+ _('OpenID'),
+ _('Add or remove OpenIDs'),
+ $action_name === 'openidsettings');
+
+ return true;
+ }
}
\ No newline at end of file
--
cgit v1.2.3-54-g00ecf
From f704a7029ee3a329608b288e61bd0c91f3efaf4e Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 07:40:41 -0400
Subject: OpenIDPlugin autoloads class files
---
plugins/OpenID/OpenIDPlugin.php | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
(limited to 'plugins')
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index f76fe1e3c..5d600159b 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -99,4 +99,24 @@ class OpenIDPlugin extends Plugin
return true;
}
+
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'OpenidloginAction':
+ case 'FinishopenidloginAction':
+ case 'FinishaddopenidAction':
+ case 'XrdsAction':
+ case 'PublicxrdsAction':
+ case 'OpenidsettingsAction':
+ require_once(INSTALLDIR.'/plugins/OpenID/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+ return false;
+ case 'User_openid':
+ require_once(INSTALLDIR.'/plugins/OpenID/User_openid.php');
+ return false;
+ default:
+ return true;
+ }
+ }
}
\ No newline at end of file
--
cgit v1.2.3-54-g00ecf
From 797f296974e892ab0c6b46586fb5cfc857e0f2a0 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 07:45:16 -0400
Subject: fix require paths for OpenID plugin files
---
plugins/OpenID/finishaddopenid.php | 2 +-
plugins/OpenID/finishopenidlogin.php | 2 +-
plugins/OpenID/openid.php | 2 +-
plugins/OpenID/openidlogin.php | 2 +-
plugins/OpenID/openidsettings.php | 2 +-
plugins/OpenID/publicxrds.php | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
(limited to 'plugins')
diff --git a/plugins/OpenID/finishaddopenid.php b/plugins/OpenID/finishaddopenid.php
index 32bceecfd..7753158d5 100644
--- a/plugins/OpenID/finishaddopenid.php
+++ b/plugins/OpenID/finishaddopenid.php
@@ -31,7 +31,7 @@ if (!defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/lib/openid.php';
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
/**
* Complete adding an OpenID
diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php
index ff0b35218..516625e41 100644
--- a/plugins/OpenID/finishopenidlogin.php
+++ b/plugins/OpenID/finishopenidlogin.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-require_once(INSTALLDIR.'/lib/openid.php');
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
class FinishopenidloginAction extends Action
{
diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php
index 0b7633284..4787cd605 100644
--- a/plugins/OpenID/openid.php
+++ b/plugins/OpenID/openid.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-require_once(INSTALLDIR.'/classes/User_openid.php');
+require_once(INSTALLDIR.'/plugins/OpenID/User_openid.php');
require_once('Auth/OpenID.php');
require_once('Auth/OpenID/Consumer.php');
diff --git a/plugins/OpenID/openidlogin.php b/plugins/OpenID/openidlogin.php
index a8d052096..3d968c56e 100644
--- a/plugins/OpenID/openidlogin.php
+++ b/plugins/OpenID/openidlogin.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-require_once(INSTALLDIR.'/lib/openid.php');
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
class OpenidloginAction extends Action
{
diff --git a/plugins/OpenID/openidsettings.php b/plugins/OpenID/openidsettings.php
index 5f59ebc01..26bf64836 100644
--- a/plugins/OpenID/openidsettings.php
+++ b/plugins/OpenID/openidsettings.php
@@ -32,7 +32,7 @@ if (!defined('LACONICA')) {
}
require_once INSTALLDIR.'/lib/accountsettingsaction.php';
-require_once INSTALLDIR.'/lib/openid.php';
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
/**
* Settings for OpenID
diff --git a/plugins/OpenID/publicxrds.php b/plugins/OpenID/publicxrds.php
index 0a1421550..f088c25d1 100644
--- a/plugins/OpenID/publicxrds.php
+++ b/plugins/OpenID/publicxrds.php
@@ -33,7 +33,7 @@ if (!defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/lib/openid.php';
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
/**
* Public XRDS for OpenID
--
cgit v1.2.3-54-g00ecf
From 0616ea020550c2bf3d5ce5e9f6452f9359384146 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 09:00:58 -0400
Subject: move check for SSL from util.php to OpenIDPlugin
---
lib/util.php | 3 +--
plugins/OpenID/OpenIDPlugin.php | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 3 deletions(-)
(limited to 'plugins')
diff --git a/lib/util.php b/lib/util.php
index cd9bd9ed8..1f9dd429c 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -735,8 +735,7 @@ function common_local_url($action, $args=null, $params=null, $fragment=null)
function common_is_sensitive($action)
{
static $sensitive = array('login', 'register', 'passwordsettings',
- 'twittersettings', 'finishopenidlogin',
- 'finishaddopenid', 'api');
+ 'twittersettings', 'api');
$ssl = null;
if (Event::handle('SensitiveAction', array($action, &$ssl))) {
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 5d600159b..ccbad5ffb 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -119,4 +119,17 @@ class OpenIDPlugin extends Plugin
return true;
}
}
-}
\ No newline at end of file
+
+ function onSensitiveAction($action, &$ssl)
+ {
+ switch ($action)
+ {
+ case 'finishopenidlogin':
+ case 'finishaddopenid':
+ $ssl = true;
+ return false;
+ default:
+ return true;
+ }
+ }
+}
--
cgit v1.2.3-54-g00ecf
From 3997682d26d7ed48361610892b65ace9097f5fc0 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 09:03:58 -0400
Subject: incorrectly moved user xrds to OpenID plugin; fixing
---
actions/xrds.php | 173 ++++++++++++++++++++++++++++++++++++++++
lib/router.php | 2 +-
plugins/OpenID/OpenIDPlugin.php | 3 -
plugins/OpenID/xrds.php | 173 ----------------------------------------
4 files changed, 174 insertions(+), 177 deletions(-)
create mode 100644 actions/xrds.php
delete mode 100644 plugins/OpenID/xrds.php
(limited to 'plugins')
diff --git a/actions/xrds.php b/actions/xrds.php
new file mode 100644
index 000000000..9327a3c83
--- /dev/null
+++ b/actions/xrds.php
@@ -0,0 +1,173 @@
+
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/omb.php';
+
+/**
+ * XRDS for OpenID
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou
+ * @author Robin Millette
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ */
+class XrdsAction extends Action
+{
+ /**
+ * Is read only?
+ *
+ * @return boolean true
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * Class handler.
+ *
+ * @param array $args query arguments
+ *
+ * @return void
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+ $nickname = $this->trimmed('nickname');
+ $user = User::staticGet('nickname', $nickname);
+ if (!$user) {
+ $this->clientError(_('No such user.'));
+ return;
+ }
+ $this->showXrds($user);
+ }
+
+ /**
+ * Show XRDS for a user.
+ *
+ * @param class $user XRDS for this user.
+ *
+ * @return void
+ */
+ function showXrds($user)
+ {
+ header('Content-Type: application/xrds+xml');
+ $this->startXML();
+ $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'oauth',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OAUTH_ENDPOINT_REQUEST,
+ common_local_url('requesttoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1),
+ $user->uri);
+ $this->showService(OAUTH_ENDPOINT_AUTHORIZE,
+ common_local_url('userauthorization'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->showService(OAUTH_ENDPOINT_ACCESS,
+ common_local_url('accesstoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->showService(OAUTH_ENDPOINT_RESOURCE,
+ null,
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+ $this->elementEnd('XRD');
+
+ // XXX: decide whether to include user's ID/nickname in postNotice URL
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'omb',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OMB_ENDPOINT_POSTNOTICE,
+ common_local_url('postnotice'));
+ $this->showService(OMB_ENDPOINT_UPDATEPROFILE,
+ common_local_url('updateprofile'));
+ $this->elementEnd('XRD');
+ $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'version' => '2.0'));
+ $this->element('Type', null, 'xri://$xrds*simple');
+ $this->showService(OAUTH_DISCOVERY,
+ '#oauth');
+ $this->showService(OMB_NAMESPACE,
+ '#omb');
+ $this->elementEnd('XRD');
+ $this->elementEnd('XRDS');
+ $this->endXML();
+ }
+
+ /**
+ * Show service.
+ *
+ * @param string $type XRDS type
+ * @param string $uri URI
+ * @param array $params type parameters, null by default
+ * @param array $sigs type signatures, null by default
+ * @param string $localId local ID, null by default
+ *
+ * @return void
+ */
+ function showService($type, $uri, $params=null, $sigs=null, $localId=null)
+ {
+ $this->elementStart('Service');
+ if ($uri) {
+ $this->element('URI', null, $uri);
+ }
+ $this->element('Type', null, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ $this->element('Type', null, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ $this->element('Type', null, $sig);
+ }
+ }
+ if ($localId) {
+ $this->element('LocalID', null, $localId);
+ }
+ $this->elementEnd('Service');
+ }
+}
+
diff --git a/lib/router.php b/lib/router.php
index 37a7e4a0f..90ffaec16 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -431,7 +431,7 @@ class Router
// user stuff
foreach (array('subscriptions', 'subscribers',
- 'nudge', 'all', 'foaf',
+ 'nudge', 'all', 'foaf', 'xrds',
'replies', 'inbox', 'outbox', 'microsummary') as $a) {
$m->connect(':nickname/'.$a,
array('action' => $a),
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index ccbad5ffb..199b1b351 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -66,9 +66,6 @@ class OpenIDPlugin extends Plugin
{
$m->connect('main/openid', array('action' => 'openidlogin'));
$m->connect('settings/openid', array('action' => 'openidsettings'));
- $m->connect(':nickname/xrds',
- array('action' => 'xrds'),
- array('nickname' => '[a-zA-Z0-9]{1,64}'));
$m->connect('xrds', array('action' => 'publicxrds'));
$m->connect('index.php?action=finishopenidlogin', array('action' => 'finishopenidlogin'));
$m->connect('index.php?action=finishaddopenid', array('action' => 'finishaddopenid'));
diff --git a/plugins/OpenID/xrds.php b/plugins/OpenID/xrds.php
deleted file mode 100644
index 9327a3c83..000000000
--- a/plugins/OpenID/xrds.php
+++ /dev/null
@@ -1,173 +0,0 @@
-
- * @author Robin Millette
- * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link http://laconi.ca/
- *
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, Control Yourself, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/lib/omb.php';
-
-/**
- * XRDS for OpenID
- *
- * @category Action
- * @package Laconica
- * @author Evan Prodromou
- * @author Robin Millette
- * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link http://laconi.ca/
- */
-class XrdsAction extends Action
-{
- /**
- * Is read only?
- *
- * @return boolean true
- */
- function isReadOnly($args)
- {
- return true;
- }
-
- /**
- * Class handler.
- *
- * @param array $args query arguments
- *
- * @return void
- */
- function handle($args)
- {
- parent::handle($args);
- $nickname = $this->trimmed('nickname');
- $user = User::staticGet('nickname', $nickname);
- if (!$user) {
- $this->clientError(_('No such user.'));
- return;
- }
- $this->showXrds($user);
- }
-
- /**
- * Show XRDS for a user.
- *
- * @param class $user XRDS for this user.
- *
- * @return void
- */
- function showXrds($user)
- {
- header('Content-Type: application/xrds+xml');
- $this->startXML();
- $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
-
- $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
- 'xml:id' => 'oauth',
- 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
- 'version' => '2.0'));
- $this->element('Type', null, 'xri://$xrds*simple');
- $this->showService(OAUTH_ENDPOINT_REQUEST,
- common_local_url('requesttoken'),
- array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
- array(OAUTH_HMAC_SHA1),
- $user->uri);
- $this->showService(OAUTH_ENDPOINT_AUTHORIZE,
- common_local_url('userauthorization'),
- array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
- array(OAUTH_HMAC_SHA1));
- $this->showService(OAUTH_ENDPOINT_ACCESS,
- common_local_url('accesstoken'),
- array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
- array(OAUTH_HMAC_SHA1));
- $this->showService(OAUTH_ENDPOINT_RESOURCE,
- null,
- array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
- array(OAUTH_HMAC_SHA1));
- $this->elementEnd('XRD');
-
- // XXX: decide whether to include user's ID/nickname in postNotice URL
- $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
- 'xml:id' => 'omb',
- 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
- 'version' => '2.0'));
- $this->element('Type', null, 'xri://$xrds*simple');
- $this->showService(OMB_ENDPOINT_POSTNOTICE,
- common_local_url('postnotice'));
- $this->showService(OMB_ENDPOINT_UPDATEPROFILE,
- common_local_url('updateprofile'));
- $this->elementEnd('XRD');
- $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
- 'version' => '2.0'));
- $this->element('Type', null, 'xri://$xrds*simple');
- $this->showService(OAUTH_DISCOVERY,
- '#oauth');
- $this->showService(OMB_NAMESPACE,
- '#omb');
- $this->elementEnd('XRD');
- $this->elementEnd('XRDS');
- $this->endXML();
- }
-
- /**
- * Show service.
- *
- * @param string $type XRDS type
- * @param string $uri URI
- * @param array $params type parameters, null by default
- * @param array $sigs type signatures, null by default
- * @param string $localId local ID, null by default
- *
- * @return void
- */
- function showService($type, $uri, $params=null, $sigs=null, $localId=null)
- {
- $this->elementStart('Service');
- if ($uri) {
- $this->element('URI', null, $uri);
- }
- $this->element('Type', null, $type);
- if ($params) {
- foreach ($params as $param) {
- $this->element('Type', null, $param);
- }
- }
- if ($sigs) {
- foreach ($sigs as $sig) {
- $this->element('Type', null, $sig);
- }
- }
- if ($localId) {
- $this->element('LocalID', null, $localId);
- }
- $this->elementEnd('Service');
- }
-}
-
--
cgit v1.2.3-54-g00ecf
From 628a9371082531908a023618ad4216a2f254f481 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 11:16:43 -0400
Subject: say that it's OK to do OpenID login in private mode
---
index.php | 3 +--
plugins/OpenID/OpenIDPlugin.php | 13 +++++++++++++
2 files changed, 14 insertions(+), 2 deletions(-)
(limited to 'plugins')
diff --git a/index.php b/index.php
index 372a8536e..c1245136e 100644
--- a/index.php
+++ b/index.php
@@ -107,8 +107,7 @@ function checkMirror($action_obj)
function isLoginAction($action)
{
- static $loginActions = array('login', 'openidlogin', 'finishopenidlogin',
- 'recoverpassword', 'api', 'doc', 'register');
+ static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register');
$login = null;
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 199b1b351..fb6bc5cf8 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -129,4 +129,17 @@ class OpenIDPlugin extends Plugin
return true;
}
}
+
+ function onLoginAction($action, &$login)
+ {
+ switch ($action)
+ {
+ case 'openidlogin':
+ case 'finishopenidlogin':
+ $login = true;
+ return false;
+ default:
+ return true;
+ }
+ }
}
--
cgit v1.2.3-54-g00ecf
From 2ed46ce274c3b404fdf75aacafa5d5fa7a807240 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 13:04:31 -0400
Subject: move XRDS code from public action to OpenIDPlugin
---
actions/public.php | 18 ------------------
plugins/OpenID/OpenIDPlugin.php | 14 ++++++++++++++
2 files changed, 14 insertions(+), 18 deletions(-)
(limited to 'plugins')
diff --git a/actions/public.php b/actions/public.php
index d0317ac70..322a52963 100644
--- a/actions/public.php
+++ b/actions/public.php
@@ -101,8 +101,6 @@ class PublicAction extends Action
{
parent::handle($args);
- header('X-XRDS-Location: '. common_local_url('publicxrds'));
-
$this->showPage();
}
@@ -143,22 +141,6 @@ class PublicAction extends Action
_('Public Stream Feed (Atom)')));
}
- /**
- * Extra head elements
- *
- * We include a element linking to the publicxrds page, for OpenID
- * client-side authentication.
- *
- * @return void
- */
-
- function extraHead()
- {
- // for client side of OpenID authentication
- $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
- 'content' => common_local_url('publicxrds')));
- }
-
/**
* Show tabset for this page
*
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index fb6bc5cf8..a395f9107 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -142,4 +142,18 @@ class OpenIDPlugin extends Plugin
return true;
}
}
+
+ /**
+ * We include a element linking to the publicxrds page, for OpenID
+ * client-side authentication.
+ *
+ * @return void
+ */
+
+ function onEndHeadChildren($action)
+ {
+ // for client side of OpenID authentication
+ $action->element('meta', array('http-equiv' => 'X-XRDS-Location',
+ 'content' => common_local_url('publicxrds')));
+ }
}
--
cgit v1.2.3-54-g00ecf
From 622c0b24c3455557bdb1bee646dcd9c955a3b3ea Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 13:17:43 -0400
Subject: move redirect-to-openid code to OpenID plugin
---
classes/User.php | 11 -----------
lib/settingsaction.php | 6 +-----
plugins/OpenID/OpenIDPlugin.php | 15 +++++++++++++++
plugins/OpenID/User_openid.php | 13 ++++++++++++-
4 files changed, 28 insertions(+), 17 deletions(-)
(limited to 'plugins')
diff --git a/classes/User.php b/classes/User.php
index bea47a3b0..104dfbacb 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -670,17 +670,6 @@ class User extends Memcached_DataObject
return $profile;
}
- function hasOpenID()
- {
- $oid = new User_openid();
-
- $oid->user_id = $this->id;
-
- $cnt = $oid->find();
-
- return ($cnt > 0);
- }
-
function getDesign()
{
return Design::staticGet('id', $this->design_id);
diff --git a/lib/settingsaction.php b/lib/settingsaction.php
index 4cf9b80c4..a923a98b2 100644
--- a/lib/settingsaction.php
+++ b/lib/settingsaction.php
@@ -78,11 +78,7 @@ class SettingsAction extends CurrentUserDesignAction
common_set_returnto($this->selfUrl());
$user = common_current_user();
if (Event::handle('RedirectToLogin', array($this, $user))) {
- if ($user->hasOpenID()) {
- common_redirect(common_local_url('openidlogin'), 303);
- } else {
- common_redirect(common_local_url('login'), 303);
- }
+ common_redirect(common_local_url('login'), 303);
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index a395f9107..ec261d7f7 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -156,4 +156,19 @@ class OpenIDPlugin extends Plugin
$action->element('meta', array('http-equiv' => 'X-XRDS-Location',
'content' => common_local_url('publicxrds')));
}
+
+ /**
+ * Redirect to OpenID login if they have an OpenID
+ *
+ * @return boolean whether to continue
+ */
+
+ function onRedirectToLogin($action, $user)
+ {
+ if (!empty($user) && User_openid::hasOpenID($user->id)) {
+ common_redirect(common_local_url('openidlogin'), 303);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php
index f4fda1c72..338e0f6e9 100644
--- a/plugins/OpenID/User_openid.php
+++ b/plugins/OpenID/User_openid.php
@@ -4,7 +4,7 @@
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-class User_openid extends Memcached_DataObject
+class User_openid extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
@@ -22,4 +22,15 @@ class User_openid extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
+
+ static function hasOpenID($user_id)
+ {
+ $oid = new User_openid();
+
+ $oid->user_id = $user_id;
+
+ $cnt = $oid->find();
+
+ return ($cnt > 0);
+ }
}
--
cgit v1.2.3-54-g00ecf
From 5dc1291b59a1079cbe9bab05d12dae06b8e4c96d Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 4 Aug 2009 13:27:22 -0400
Subject: move openid instructions to OpenIDPlugin
---
actions/login.php | 3 +--
actions/register.php | 5 +----
plugins/OpenID/OpenIDPlugin.php | 25 +++++++++++++++++++++++++
3 files changed, 27 insertions(+), 6 deletions(-)
(limited to 'plugins')
diff --git a/actions/login.php b/actions/login.php
index 50de83f6f..f5a658bf5 100644
--- a/actions/login.php
+++ b/actions/login.php
@@ -250,8 +250,7 @@ class LoginAction extends Action
} else {
return _('Login with your username and password. ' .
'Don\'t have a username yet? ' .
- '[Register](%%action.register%%) a new account, or ' .
- 'try [OpenID](%%action.openidlogin%%). ');
+ '[Register](%%action.register%%) a new account.');
}
}
diff --git a/actions/register.php b/actions/register.php
index dcbbbdb6a..dd3edc4ed 100644
--- a/actions/register.php
+++ b/actions/register.php
@@ -329,10 +329,7 @@ class RegisterAction extends Action
common_markup_to_html(_('With this form you can create '.
' a new account. ' .
'You can then post notices and '.
- 'link up to friends and colleagues. '.
- '(Have an [OpenID](http://openid.net/)? ' .
- 'Try our [OpenID registration]'.
- '(%%action.openidlogin%%)!)'));
+ 'link up to friends and colleagues. '));
$this->elementStart('div', 'instructions');
$this->raw($instr);
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index ec261d7f7..87b25d42a 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -171,4 +171,29 @@ class OpenIDPlugin extends Plugin
}
return true;
}
+
+ function onEndShowPageNotice($action)
+ {
+ $name = $action->trimmed('action');
+
+ switch ($name)
+ {
+ case 'register':
+ $instr = '(Have an [OpenID](http://openid.net/)? ' .
+ 'Try our [OpenID registration]'.
+ '(%%action.openidlogin%%)!)';
+ break;
+ case 'login':
+ $instr = '(Have an [OpenID](http://openid.net/)? ' .
+ 'Try our [OpenID login]'.
+ '(%%action.openidlogin%%)!)';
+ break;
+ default:
+ return true;
+ }
+
+ $output = common_markup_to_html($instr);
+ $action->raw($output);
+ return true;
+ }
}
--
cgit v1.2.3-54-g00ecf
From bacef32aaca791ec5e25bd951094baec0b41df28 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Fri, 21 Aug 2009 16:38:39 -0400
Subject: Revert "Added a configuration option to disable OpenID."
This reverts commit 7dc3a90d1252137859a687e32313ea569dcf8796.
Conflicts:
actions/login.php
actions/register.php
lib/accountsettingsaction.php
lib/common.php
lib/logingroupnav.php
---
README | 8 --------
actions/login.php | 2 +-
config.php.sample | 3 ---
lib/common.php | 8 --------
plugins/OpenID/finishopenidlogin.php | 4 +---
plugins/OpenID/openidlogin.php | 4 +---
plugins/OpenID/openidsettings.php | 6 ------
7 files changed, 3 insertions(+), 32 deletions(-)
(limited to 'plugins')
diff --git a/README b/README
index ccdd9e674..53ed0a0fe 100644
--- a/README
+++ b/README
@@ -1200,14 +1200,6 @@ For configuring invites.
enabled: Whether to allow users to send invites. Default true.
-openid
-------
-
-For configuring OpenID.
-
-enabled: Whether to allow users to register and login using OpenID. Default
- true.
-
tag
---
diff --git a/actions/login.php b/actions/login.php
index e09fdc76b..f5a658bf5 100644
--- a/actions/login.php
+++ b/actions/login.php
@@ -247,7 +247,7 @@ class LoginAction extends Action
return _('For security reasons, please re-enter your ' .
'user name and password ' .
'before changing your settings.');
- } else if (common_config('openid', 'enabled')) {
+ } else {
return _('Login with your username and password. ' .
'Don\'t have a username yet? ' .
'[Register](%%action.register%%) a new account.');
diff --git a/config.php.sample b/config.php.sample
index 0fc5163b7..e8cbf76d5 100644
--- a/config.php.sample
+++ b/config.php.sample
@@ -99,9 +99,6 @@ $config['sphinx']['port'] = 3312;
// $config['xmpp']['public'][] = 'someindexer@example.net';
// $config['xmpp']['debug'] = false;
-// Disable OpenID
-// $config['openid']['enabled'] = false;
-
// Turn off invites
// $config['invite']['enabled'] = false;
diff --git a/lib/common.php b/lib/common.php
index 067a5a2a6..7b0afce51 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -175,8 +175,6 @@ $config =
'host' => null, # only set if != server
'debug' => false, # print extra debug info
'public' => array()), # JIDs of users who want to receive the public stream
- 'openid' =>
- array('enabled' => true),
'invite' =>
array('enabled' => true),
'sphinx' =>
@@ -383,12 +381,6 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
$config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini';
}
-// Ignore openidonly if OpenID is disabled
-
-if (!$config['openid']['enabled']) {
- $config['site']['openidonly'] = false;
-}
-
function __autoload($cls)
{
if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php
index 87fc3881e..bc0d2d66c 100644
--- a/plugins/OpenID/finishopenidlogin.php
+++ b/plugins/OpenID/finishopenidlogin.php
@@ -30,9 +30,7 @@ class FinishopenidloginAction extends Action
function handle($args)
{
parent::handle($args);
- if (!common_config('openid', 'enabled')) {
- common_redirect(common_local_url('login'));
- } else if (common_is_real_login()) {
+ if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
diff --git a/plugins/OpenID/openidlogin.php b/plugins/OpenID/openidlogin.php
index 76f573b9f..3d968c56e 100644
--- a/plugins/OpenID/openidlogin.php
+++ b/plugins/OpenID/openidlogin.php
@@ -26,9 +26,7 @@ class OpenidloginAction extends Action
function handle($args)
{
parent::handle($args);
- if (!common_config('openid', 'enabled')) {
- common_redirect(common_local_url('login'));
- } else if (common_is_real_login()) {
+ if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$openid_url = $this->trimmed('openid_url');
diff --git a/plugins/OpenID/openidsettings.php b/plugins/OpenID/openidsettings.php
index 57389903d..26bf64836 100644
--- a/plugins/OpenID/openidsettings.php
+++ b/plugins/OpenID/openidsettings.php
@@ -82,12 +82,6 @@ class OpenidsettingsAction extends AccountSettingsAction
function showContent()
{
- if (!common_config('openid', 'enabled')) {
- $this->element('div', array('class' => 'error'),
- _('OpenID is not available.'));
- return;
- }
-
$user = common_current_user();
$this->elementStart('form', array('method' => 'post',
--
cgit v1.2.3-54-g00ecf
From 8e21e37d707c61b673b0ba2154469d2fae91b90f Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Fri, 21 Aug 2009 16:46:47 -0400
Subject: move openid docs to OpenID plugin dir
---
doc-src/openid | 11 -----------
plugins/OpenID/doc-src/openid | 11 +++++++++++
2 files changed, 11 insertions(+), 11 deletions(-)
delete mode 100644 doc-src/openid
create mode 100644 plugins/OpenID/doc-src/openid
(limited to 'plugins')
diff --git a/doc-src/openid b/doc-src/openid
deleted file mode 100644
index c741e3674..000000000
--- a/doc-src/openid
+++ /dev/null
@@ -1,11 +0,0 @@
-%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
-
-If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
-To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
-
-There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
-
-* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
-* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
-* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
-* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/plugins/OpenID/doc-src/openid b/plugins/OpenID/doc-src/openid
new file mode 100644
index 000000000..c741e3674
--- /dev/null
+++ b/plugins/OpenID/doc-src/openid
@@ -0,0 +1,11 @@
+%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
+
+If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
+To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
+
+There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
+
+* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
+* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
+* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
+* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
--
cgit v1.2.3-54-g00ecf
From 209486d3be877d85b3e241797e6042283d0f66d6 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Fri, 21 Aug 2009 17:01:48 -0400
Subject: add OpenID doc menu item and doc page
---
plugins/OpenID/OpenIDPlugin.php | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
(limited to 'plugins')
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 87b25d42a..eb450fc5e 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -196,4 +196,30 @@ class OpenIDPlugin extends Plugin
$action->raw($output);
return true;
}
+
+ function onStartLoadDoc(&$title, &$output)
+ {
+ if ($title == 'openid')
+ {
+ $filename = INSTALLDIR.'/plugins/OpenID/doc-src/openid';
+
+ $c = file_get_contents($filename);
+ $output = common_markup_to_html($c);
+ return false; // success!
+ }
+
+ return true;
+ }
+
+ function onEndLoadDoc($title, &$output)
+ {
+ if ($title == 'help')
+ {
+ $menuitem = '* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service';
+
+ $output .= common_markup_to_html($menuitem);
+ }
+
+ return true;
+ }
}
--
cgit v1.2.3-54-g00ecf
From 9b9d80cd97704426e54434d8777c5c15154014ad Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Tue, 25 Aug 2009 14:52:25 -0700
Subject: Pluginized Twitter settings stuff
---
actions/twitterauthorization.php | 222 --------------------
actions/twittersettings.php | 277 -------------------------
lib/connectsettingsaction.php | 49 +++--
lib/router.php | 6 +-
lib/twitteroauthclient.php | 220 --------------------
plugins/TwitterBridge/TwitterBridgePlugin.php | 101 +++++++++
plugins/TwitterBridge/twitterauthorization.php | 222 ++++++++++++++++++++
plugins/TwitterBridge/twitteroauthclient.php | 220 ++++++++++++++++++++
plugins/TwitterBridge/twittersettings.php | 272 ++++++++++++++++++++++++
9 files changed, 842 insertions(+), 747 deletions(-)
delete mode 100644 actions/twitterauthorization.php
delete mode 100644 actions/twittersettings.php
delete mode 100644 lib/twitteroauthclient.php
create mode 100644 plugins/TwitterBridge/TwitterBridgePlugin.php
create mode 100644 plugins/TwitterBridge/twitterauthorization.php
create mode 100644 plugins/TwitterBridge/twitteroauthclient.php
create mode 100644 plugins/TwitterBridge/twittersettings.php
(limited to 'plugins')
diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php
deleted file mode 100644
index b04f35327..000000000
--- a/actions/twitterauthorization.php
+++ /dev/null
@@ -1,222 +0,0 @@
-.
- *
- * @category Twitter
- * @package Laconica
- * @author Zach Copely
- * @copyright 2009 Control Yourself, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Class for doing OAuth authentication against Twitter
- *
- * Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
- * authorizes it, and exchanges it for an access token. It also creates a link
- * (Foreign_link) between the Laconica user and Twitter user and stores the
- * access token and secret in the link.
- *
- * @category Twitter
- * @package Laconica
- * @author Zach Copley
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- *
- */
-class TwitterauthorizationAction extends Action
-{
- /**
- * Initialize class members. Looks for 'oauth_token' parameter.
- *
- * @param array $args misc. arguments
- *
- * @return boolean true
- */
- function prepare($args)
- {
- parent::prepare($args);
-
- $this->oauth_token = $this->arg('oauth_token');
-
- return true;
- }
-
- /**
- * Handler method
- *
- * @param array $args is ignored since it's now passed in in prepare()
- *
- * @return nothing
- */
- function handle($args)
- {
- parent::handle($args);
-
- if (!common_logged_in()) {
- $this->clientError(_('Not logged in.'), 403);
- }
-
- $user = common_current_user();
- $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
- // If there's already a foreign link record, it means we already
- // have an access token, and this is unecessary. So go back.
-
- if (isset($flink)) {
- common_redirect(common_local_url('twittersettings'));
- }
-
- // $this->oauth_token is only populated once Twitter authorizes our
- // request token. If it's empty we're at the beginning of the auth
- // process
-
- if (empty($this->oauth_token)) {
- $this->authorizeRequestToken();
- } else {
- $this->saveAccessToken();
- }
- }
-
- /**
- * Asks Twitter for a request token, and then redirects to Twitter
- * to authorize it.
- *
- * @return nothing
- */
- function authorizeRequestToken()
- {
- try {
-
- // Get a new request token and authorize it
-
- $client = new TwitterOAuthClient();
- $req_tok =
- $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
-
- // Sock the request token away in the session temporarily
-
- $_SESSION['twitter_request_token'] = $req_tok->key;
- $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
-
- $auth_link = $client->getAuthorizeLink($req_tok);
-
- } catch (TwitterOAuthClientException $e) {
- $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
- $e->getCode(), $e->getMessage());
- $this->serverError(_('Couldn\'t link your Twitter account.'));
- }
-
- common_redirect($auth_link);
- }
-
- /**
- * Called when Twitter returns an authorized request token. Exchanges
- * it for an access token and stores it.
- *
- * @return nothing
- */
- function saveAccessToken()
- {
-
- // Check to make sure Twitter returned the same request
- // token we sent them
-
- if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
- $this->serverError(_('Couldn\'t link your Twitter account.'));
- }
-
- try {
-
- $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
- $_SESSION['twitter_request_token_secret']);
-
- // Exchange the request token for an access token
-
- $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
-
- // Test the access token and get the user's Twitter info
-
- $client = new TwitterOAuthClient($atok->key, $atok->secret);
- $twitter_user = $client->verifyCredentials();
-
- } catch (OAuthClientException $e) {
- $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
- $e->getCode(), $e->getMessage());
- $this->serverError(_('Couldn\'t link your Twitter account.'));
- }
-
- // Save the access token and Twitter user info
-
- $this->saveForeignLink($atok, $twitter_user);
-
- // Clean up the the mess we made in the session
-
- unset($_SESSION['twitter_request_token']);
- unset($_SESSION['twitter_request_token_secret']);
-
- common_redirect(common_local_url('twittersettings'));
- }
-
- /**
- * Saves a Foreign_link between Twitter user and local user,
- * which includes the access token and secret.
- *
- * @param OAuthToken $access_token the access token to save
- * @param mixed $twitter_user twitter API user object
- *
- * @return nothing
- */
- function saveForeignLink($access_token, $twitter_user)
- {
- $user = common_current_user();
-
- $flink = new Foreign_link();
-
- $flink->user_id = $user->id;
- $flink->foreign_id = $twitter_user->id;
- $flink->service = TWITTER_SERVICE;
-
- $creds = TwitterOAuthClient::packToken($access_token);
-
- $flink->credentials = $creds;
- $flink->created = common_sql_now();
-
- // Defaults: noticesync on, everything else off
-
- $flink->set_flags(true, false, false, false);
-
- $flink_id = $flink->insert();
-
- if (empty($flink_id)) {
- common_log_db_error($flink, 'INSERT', __FILE__);
- $this->serverError(_('Couldn\'t link your Twitter account.'));
- }
-
- save_twitter_user($twitter_user->id, $twitter_user->screen_name);
- }
-
-}
-
diff --git a/actions/twittersettings.php b/actions/twittersettings.php
deleted file mode 100644
index 0859ab9d3..000000000
--- a/actions/twittersettings.php
+++ /dev/null
@@ -1,277 +0,0 @@
-.
- *
- * @category Settings
- * @package Laconica
- * @author Evan Prodromou
- * @copyright 2008-2009 Control Yourself, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-require_once INSTALLDIR.'/lib/twitter.php';
-
-/**
- * Settings for Twitter integration
- *
- * @category Settings
- * @package Laconica
- * @author Evan Prodromou
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- *
- * @see SettingsAction
- */
-
-class TwittersettingsAction extends ConnectSettingsAction
-{
- /**
- * Title of the page
- *
- * @return string Title of the page
- */
-
- function title()
- {
- return _('Twitter settings');
- }
-
- /**
- * Instructions for use
- *
- * @return instructions for use
- */
-
- function getInstructions()
- {
- return _('Connect your Twitter account to share your updates ' .
- 'with your Twitter friends and vice-versa.');
- }
-
- /**
- * Content area of the page
- *
- * Shows a form for associating a Twitter account with this
- * Laconica account. Also lets the user set preferences.
- *
- * @return void
- */
-
- function showContent()
- {
- if (!common_config('twitter', 'enabled')) {
- $this->element('div', array('class' => 'error'),
- _('Twitter is not available.'));
- return;
- }
-
- $user = common_current_user();
-
- $profile = $user->getProfile();
-
- $fuser = null;
-
- $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
- if (!empty($flink)) {
- $fuser = $flink->getForeignUser();
- }
-
- $this->elementStart('form', array('method' => 'post',
- 'id' => 'form_settings_twitter',
- 'class' => 'form_settings',
- 'action' =>
- common_local_url('twittersettings')));
-
- $this->hidden('token', common_session_token());
-
- $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
-
- if (empty($fuser)) {
- $this->elementStart('ul', 'form_data');
- $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
- $this->element('a', array('href' => common_local_url('twitterauthorization')),
- 'Connect my Twitter account');
- $this->elementEnd('li');
- $this->elementEnd('ul');
-
- $this->elementEnd('fieldset');
- } else {
- $this->element('legend', null, _('Twitter account'));
- $this->elementStart('p', array('id' => 'form_confirmed'));
- $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
- $this->elementEnd('p');
- $this->element('p', 'form_note',
- _('Connected Twitter account'));
-
- $this->submit('remove', _('Remove'));
-
- $this->elementEnd('fieldset');
-
- $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
-
- $this->element('legend', null, _('Preferences'));
- $this->elementStart('ul', 'form_data');
- $this->elementStart('li');
- $this->checkbox('noticesend',
- _('Automatically send my notices to Twitter.'),
- ($flink) ?
- ($flink->noticesync & FOREIGN_NOTICE_SEND) :
- true);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->checkbox('replysync',
- _('Send local "@" replies to Twitter.'),
- ($flink) ?
- ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
- true);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->checkbox('friendsync',
- _('Subscribe to my Twitter friends here.'),
- ($flink) ?
- ($flink->friendsync & FOREIGN_FRIEND_RECV) :
- false);
- $this->elementEnd('li');
-
- if (common_config('twitterbridge','enabled')) {
- $this->elementStart('li');
- $this->checkbox('noticerecv',
- _('Import my Friends Timeline.'),
- ($flink) ?
- ($flink->noticesync & FOREIGN_NOTICE_RECV) :
- false);
- $this->elementEnd('li');
-
- // preserve setting even if bidrection bridge toggled off
-
- if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
- $this->hidden('noticerecv', true, 'noticerecv');
- }
- }
-
- $this->elementEnd('ul');
-
- if ($flink) {
- $this->submit('save', _('Save'));
- } else {
- $this->submit('add', _('Add'));
- }
-
- $this->elementEnd('fieldset');
- }
-
- $this->elementEnd('form');
- }
-
- /**
- * Handle posts to this form
- *
- * Based on the button that was pressed, muxes out to other functions
- * to do the actual task requested.
- *
- * All sub-functions reload the form with a message -- success or failure.
- *
- * @return void
- */
-
- function handlePost()
- {
- // CSRF protection
- $token = $this->trimmed('token');
- if (!$token || $token != common_session_token()) {
- $this->showForm(_('There was a problem with your session token. '.
- 'Try again, please.'));
- return;
- }
-
- if ($this->arg('save')) {
- $this->savePreferences();
- } else if ($this->arg('remove')) {
- $this->removeTwitterAccount();
- } else {
- $this->showForm(_('Unexpected form submission.'));
- }
- }
-
- /**
- * Disassociate an existing Twitter account from this account
- *
- * @return void
- */
-
- function removeTwitterAccount()
- {
- $user = common_current_user();
- $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
- $result = $flink->delete();
-
- if (empty($result)) {
- common_log_db_error($flink, 'DELETE', __FILE__);
- $this->serverError(_('Couldn\'t remove Twitter user.'));
- return;
- }
-
- $this->showForm(_('Twitter account removed.'), true);
- }
-
- /**
- * Save user's Twitter-bridging preferences
- *
- * @return void
- */
-
- function savePreferences()
- {
- $noticesend = $this->boolean('noticesend');
- $noticerecv = $this->boolean('noticerecv');
- $friendsync = $this->boolean('friendsync');
- $replysync = $this->boolean('replysync');
-
- $user = common_current_user();
- $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
- if (empty($flink)) {
- common_log_db_error($flink, 'SELECT', __FILE__);
- $this->showForm(_('Couldn\'t save Twitter preferences.'));
- return;
- }
-
- $original = clone($flink);
- $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
- $result = $flink->update($original);
-
- if ($result === false) {
- common_log_db_error($flink, 'UPDATE', __FILE__);
- $this->showForm(_('Couldn\'t save Twitter preferences.'));
- return;
- }
-
- $this->showForm(_('Twitter preferences saved.'), true);
- }
-
-}
diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php
index 02c468a35..7902931e0 100644
--- a/lib/connectsettingsaction.php
+++ b/lib/connectsettingsaction.php
@@ -98,34 +98,37 @@ class ConnectSettingsNav extends Widget
function show()
{
- # action => array('prompt', 'title')
- $menu = array();
- if (common_config('xmpp', 'enabled')) {
- $menu['imsettings'] =
- array(_('IM'),
- _('Updates by instant messenger (IM)'));
- }
- if (common_config('sms', 'enabled')) {
- $menu['smssettings'] =
- array(_('SMS'),
- _('Updates by SMS'));
- }
- if (common_config('twitter', 'enabled')) {
- $menu['twittersettings'] =
- array(_('Twitter'),
- _('Twitter integration options'));
- }
-
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
- foreach ($menu as $menuaction => $menudesc) {
- $this->action->menuItem(common_local_url($menuaction),
- $menudesc[0],
- $menudesc[1],
- $action_name === $menuaction);
+ if (Event::handle('StartConnectSettingsNav', array(&$this->action))) {
+
+ # action => array('prompt', 'title')
+ $menu = array();
+ if (common_config('xmpp', 'enabled')) {
+ $menu['imsettings'] =
+ array(_('IM'),
+ _('Updates by instant messenger (IM)'));
+ }
+ if (common_config('sms', 'enabled')) {
+ $menu['smssettings'] =
+ array(_('SMS'),
+ _('Updates by SMS'));
+ }
+
+ foreach ($menu as $menuaction => $menudesc) {
+ $this->action->menuItem(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action_name === $menuaction);
+ }
+
+ Event::handle('EndConnectSettingsNav', array(&$this->action));
}
$this->action->elementEnd('ul');
}
+
}
+
+
diff --git a/lib/router.php b/lib/router.php
index 08bc0566d..5bffdb16b 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -86,10 +86,6 @@ class Router
$m->connect('doc/:title', array('action' => 'doc'));
- // Twitter
-
- $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
-
// facebook
$m->connect('facebook', array('action' => 'facebookhome'));
@@ -136,7 +132,7 @@ class Router
// settings
foreach (array('profile', 'avatar', 'password', 'im',
- 'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
+ 'email', 'sms', 'userdesign', 'other') as $s) {
$m->connect('settings/'.$s, array('action' => $s.'settings'));
}
diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php
deleted file mode 100644
index b7dc4a80c..000000000
--- a/lib/twitteroauthclient.php
+++ /dev/null
@@ -1,220 +0,0 @@
-.
- *
- * @category Integration
- * @package Laconica
- * @author Zach Copley
- * @copyright 2008 Control Yourself, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Class for talking to the Twitter API with OAuth.
- *
- * @category Integration
- * @package Laconica
- * @author Zach Copley
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- *
- */
-class TwitterOAuthClient extends OAuthClient
-{
- public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
- public static $authorizeURL = 'https://twitter.com/oauth/authorize';
- public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
-
- /**
- * Constructor
- *
- * @param string $oauth_token the user's token
- * @param string $oauth_token_secret the user's token secret
- *
- * @return nothing
- */
- function __construct($oauth_token = null, $oauth_token_secret = null)
- {
- $consumer_key = common_config('twitter', 'consumer_key');
- $consumer_secret = common_config('twitter', 'consumer_secret');
-
- parent::__construct($consumer_key, $consumer_secret,
- $oauth_token, $oauth_token_secret);
- }
-
- // XXX: the following two functions are to support the horrible hack
- // of using the credentils field in Foreign_link to store both
- // the access token and token secret. This hack should go away with
- // 0.9, in which we can make DB changes and add a new column for the
- // token itself.
-
- static function packToken($token)
- {
- return implode(chr(0), array($token->key, $token->secret));
- }
-
- static function unpackToken($str)
- {
- $vals = explode(chr(0), $str);
- return new OAuthToken($vals[0], $vals[1]);
- }
-
- /**
- * Builds a link to Twitter's endpoint for authorizing a request token
- *
- * @param OAuthToken $request_token token to authorize
- *
- * @return the link
- */
- function getAuthorizeLink($request_token)
- {
- return parent::getAuthorizeLink(self::$authorizeURL,
- $request_token,
- common_local_url('twitterauthorization'));
- }
-
- /**
- * Calls Twitter's /account/verify_credentials API method
- *
- * @return mixed the Twitter user
- */
- function verifyCredentials()
- {
- $url = 'https://twitter.com/account/verify_credentials.json';
- $response = $this->oAuthGet($url);
- $twitter_user = json_decode($response);
- return $twitter_user;
- }
-
- /**
- * Calls Twitter's /stutuses/update API method
- *
- * @param string $status text of the status
- * @param int $in_reply_to_status_id optional id of the status it's
- * a reply to
- *
- * @return mixed the status
- */
- function statusesUpdate($status, $in_reply_to_status_id = null)
- {
- $url = 'https://twitter.com/statuses/update.json';
- $params = array('status' => $status,
- 'in_reply_to_status_id' => $in_reply_to_status_id);
- $response = $this->oAuthPost($url, $params);
- $status = json_decode($response);
- return $status;
- }
-
- /**
- * Calls Twitter's /stutuses/friends_timeline API method
- *
- * @param int $since_id show statuses after this id
- * @param int $max_id show statuses before this id
- * @param int $cnt number of statuses to show
- * @param int $page page number
- *
- * @return mixed an array of statuses
- */
- function statusesFriendsTimeline($since_id = null, $max_id = null,
- $cnt = null, $page = null)
- {
-
- $url = 'https://twitter.com/statuses/friends_timeline.json';
- $params = array('since_id' => $since_id,
- 'max_id' => $max_id,
- 'count' => $cnt,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->oAuthGet($url);
- $statuses = json_decode($response);
- return $statuses;
- }
-
- /**
- * Calls Twitter's /stutuses/friends API method
- *
- * @param int $id id of the user whom you wish to see friends of
- * @param int $user_id numerical user id
- * @param int $screen_name screen name
- * @param int $page page number
- *
- * @return mixed an array of twitter users and their latest status
- */
- function statusesFriends($id = null, $user_id = null, $screen_name = null,
- $page = null)
- {
- $url = "https://twitter.com/statuses/friends.json";
-
- $params = array('id' => $id,
- 'user_id' => $user_id,
- 'screen_name' => $screen_name,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->oAuthGet($url);
- $friends = json_decode($response);
- return $friends;
- }
-
- /**
- * Calls Twitter's /stutuses/friends/ids API method
- *
- * @param int $id id of the user whom you wish to see friends of
- * @param int $user_id numerical user id
- * @param int $screen_name screen name
- * @param int $page page number
- *
- * @return mixed a list of ids, 100 per page
- */
- function friendsIds($id = null, $user_id = null, $screen_name = null,
- $page = null)
- {
- $url = "https://twitter.com/friends/ids.json";
-
- $params = array('id' => $id,
- 'user_id' => $user_id,
- 'screen_name' => $screen_name,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->oAuthGet($url);
- $ids = json_decode($response);
- return $ids;
- }
-
-}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
new file mode 100644
index 000000000..f7daa9a22
--- /dev/null
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -0,0 +1,101 @@
+.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Zach Copley
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Plugin for sending and importing Twitter statuses
+ *
+ * This class allows users to link their Twitter accounts
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Zach Copley
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ * @link http://twitter.com/
+ */
+
+class TwitterBridgePlugin extends Plugin
+{
+ /**
+ * Initializer for the plugin.
+ */
+
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Add Twitter-related paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @return boolean hook return
+ */
+
+ function onRouterInitialized(&$m)
+ {
+ $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
+ $m->connect('settings/twitter', array('action' => 'twittersettings'));
+
+ return true;
+ }
+
+ function onEndConnectSettingsNav(&$action)
+ {
+ $action_name = $action->trimmed('action');
+
+ $action->menuItem(common_local_url('twittersettings'),
+ _('Twitter'),
+ _('Twitter integration options'),
+ $action_name === 'twittersettings');
+
+ return true;
+ }
+
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'TwittersettingsAction':
+ case 'TwitterauthorizationAction':
+ require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+ return false;
+ case 'TwitterOAuthClient':
+ require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php');
+ return false;
+ default:
+ return true;
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
new file mode 100644
index 000000000..b04f35327
--- /dev/null
+++ b/plugins/TwitterBridge/twitterauthorization.php
@@ -0,0 +1,222 @@
+.
+ *
+ * @category Twitter
+ * @package Laconica
+ * @author Zach Copely
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Class for doing OAuth authentication against Twitter
+ *
+ * Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
+ * authorizes it, and exchanges it for an access token. It also creates a link
+ * (Foreign_link) between the Laconica user and Twitter user and stores the
+ * access token and secret in the link.
+ *
+ * @category Twitter
+ * @package Laconica
+ * @author Zach Copley
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+class TwitterauthorizationAction extends Action
+{
+ /**
+ * Initialize class members. Looks for 'oauth_token' parameter.
+ *
+ * @param array $args misc. arguments
+ *
+ * @return boolean true
+ */
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->oauth_token = $this->arg('oauth_token');
+
+ return true;
+ }
+
+ /**
+ * Handler method
+ *
+ * @param array $args is ignored since it's now passed in in prepare()
+ *
+ * @return nothing
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ $this->clientError(_('Not logged in.'), 403);
+ }
+
+ $user = common_current_user();
+ $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+ // If there's already a foreign link record, it means we already
+ // have an access token, and this is unecessary. So go back.
+
+ if (isset($flink)) {
+ common_redirect(common_local_url('twittersettings'));
+ }
+
+ // $this->oauth_token is only populated once Twitter authorizes our
+ // request token. If it's empty we're at the beginning of the auth
+ // process
+
+ if (empty($this->oauth_token)) {
+ $this->authorizeRequestToken();
+ } else {
+ $this->saveAccessToken();
+ }
+ }
+
+ /**
+ * Asks Twitter for a request token, and then redirects to Twitter
+ * to authorize it.
+ *
+ * @return nothing
+ */
+ function authorizeRequestToken()
+ {
+ try {
+
+ // Get a new request token and authorize it
+
+ $client = new TwitterOAuthClient();
+ $req_tok =
+ $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
+
+ // Sock the request token away in the session temporarily
+
+ $_SESSION['twitter_request_token'] = $req_tok->key;
+ $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
+
+ $auth_link = $client->getAuthorizeLink($req_tok);
+
+ } catch (TwitterOAuthClientException $e) {
+ $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
+ $e->getCode(), $e->getMessage());
+ $this->serverError(_('Couldn\'t link your Twitter account.'));
+ }
+
+ common_redirect($auth_link);
+ }
+
+ /**
+ * Called when Twitter returns an authorized request token. Exchanges
+ * it for an access token and stores it.
+ *
+ * @return nothing
+ */
+ function saveAccessToken()
+ {
+
+ // Check to make sure Twitter returned the same request
+ // token we sent them
+
+ if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
+ $this->serverError(_('Couldn\'t link your Twitter account.'));
+ }
+
+ try {
+
+ $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
+ $_SESSION['twitter_request_token_secret']);
+
+ // Exchange the request token for an access token
+
+ $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
+
+ // Test the access token and get the user's Twitter info
+
+ $client = new TwitterOAuthClient($atok->key, $atok->secret);
+ $twitter_user = $client->verifyCredentials();
+
+ } catch (OAuthClientException $e) {
+ $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
+ $e->getCode(), $e->getMessage());
+ $this->serverError(_('Couldn\'t link your Twitter account.'));
+ }
+
+ // Save the access token and Twitter user info
+
+ $this->saveForeignLink($atok, $twitter_user);
+
+ // Clean up the the mess we made in the session
+
+ unset($_SESSION['twitter_request_token']);
+ unset($_SESSION['twitter_request_token_secret']);
+
+ common_redirect(common_local_url('twittersettings'));
+ }
+
+ /**
+ * Saves a Foreign_link between Twitter user and local user,
+ * which includes the access token and secret.
+ *
+ * @param OAuthToken $access_token the access token to save
+ * @param mixed $twitter_user twitter API user object
+ *
+ * @return nothing
+ */
+ function saveForeignLink($access_token, $twitter_user)
+ {
+ $user = common_current_user();
+
+ $flink = new Foreign_link();
+
+ $flink->user_id = $user->id;
+ $flink->foreign_id = $twitter_user->id;
+ $flink->service = TWITTER_SERVICE;
+
+ $creds = TwitterOAuthClient::packToken($access_token);
+
+ $flink->credentials = $creds;
+ $flink->created = common_sql_now();
+
+ // Defaults: noticesync on, everything else off
+
+ $flink->set_flags(true, false, false, false);
+
+ $flink_id = $flink->insert();
+
+ if (empty($flink_id)) {
+ common_log_db_error($flink, 'INSERT', __FILE__);
+ $this->serverError(_('Couldn\'t link your Twitter account.'));
+ }
+
+ save_twitter_user($twitter_user->id, $twitter_user->screen_name);
+ }
+
+}
+
diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php
new file mode 100644
index 000000000..b7dc4a80c
--- /dev/null
+++ b/plugins/TwitterBridge/twitteroauthclient.php
@@ -0,0 +1,220 @@
+.
+ *
+ * @category Integration
+ * @package Laconica
+ * @author Zach Copley
+ * @copyright 2008 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Class for talking to the Twitter API with OAuth.
+ *
+ * @category Integration
+ * @package Laconica
+ * @author Zach Copley
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+class TwitterOAuthClient extends OAuthClient
+{
+ public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
+ public static $authorizeURL = 'https://twitter.com/oauth/authorize';
+ public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
+
+ /**
+ * Constructor
+ *
+ * @param string $oauth_token the user's token
+ * @param string $oauth_token_secret the user's token secret
+ *
+ * @return nothing
+ */
+ function __construct($oauth_token = null, $oauth_token_secret = null)
+ {
+ $consumer_key = common_config('twitter', 'consumer_key');
+ $consumer_secret = common_config('twitter', 'consumer_secret');
+
+ parent::__construct($consumer_key, $consumer_secret,
+ $oauth_token, $oauth_token_secret);
+ }
+
+ // XXX: the following two functions are to support the horrible hack
+ // of using the credentils field in Foreign_link to store both
+ // the access token and token secret. This hack should go away with
+ // 0.9, in which we can make DB changes and add a new column for the
+ // token itself.
+
+ static function packToken($token)
+ {
+ return implode(chr(0), array($token->key, $token->secret));
+ }
+
+ static function unpackToken($str)
+ {
+ $vals = explode(chr(0), $str);
+ return new OAuthToken($vals[0], $vals[1]);
+ }
+
+ /**
+ * Builds a link to Twitter's endpoint for authorizing a request token
+ *
+ * @param OAuthToken $request_token token to authorize
+ *
+ * @return the link
+ */
+ function getAuthorizeLink($request_token)
+ {
+ return parent::getAuthorizeLink(self::$authorizeURL,
+ $request_token,
+ common_local_url('twitterauthorization'));
+ }
+
+ /**
+ * Calls Twitter's /account/verify_credentials API method
+ *
+ * @return mixed the Twitter user
+ */
+ function verifyCredentials()
+ {
+ $url = 'https://twitter.com/account/verify_credentials.json';
+ $response = $this->oAuthGet($url);
+ $twitter_user = json_decode($response);
+ return $twitter_user;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/update API method
+ *
+ * @param string $status text of the status
+ * @param int $in_reply_to_status_id optional id of the status it's
+ * a reply to
+ *
+ * @return mixed the status
+ */
+ function statusesUpdate($status, $in_reply_to_status_id = null)
+ {
+ $url = 'https://twitter.com/statuses/update.json';
+ $params = array('status' => $status,
+ 'in_reply_to_status_id' => $in_reply_to_status_id);
+ $response = $this->oAuthPost($url, $params);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends_timeline API method
+ *
+ * @param int $since_id show statuses after this id
+ * @param int $max_id show statuses before this id
+ * @param int $cnt number of statuses to show
+ * @param int $page page number
+ *
+ * @return mixed an array of statuses
+ */
+ function statusesFriendsTimeline($since_id = null, $max_id = null,
+ $cnt = null, $page = null)
+ {
+
+ $url = 'https://twitter.com/statuses/friends_timeline.json';
+ $params = array('since_id' => $since_id,
+ 'max_id' => $max_id,
+ 'count' => $cnt,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $statuses = json_decode($response);
+ return $statuses;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed an array of twitter users and their latest status
+ */
+ function statusesFriends($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/statuses/friends.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $friends = json_decode($response);
+ return $friends;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends/ids API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed a list of ids, 100 per page
+ */
+ function friendsIds($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/friends/ids.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $ids = json_decode($response);
+ return $ids;
+ }
+
+}
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
new file mode 100644
index 000000000..a3e02e125
--- /dev/null
+++ b/plugins/TwitterBridge/twittersettings.php
@@ -0,0 +1,272 @@
+.
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/connectsettingsaction.php';
+require_once INSTALLDIR.'/lib/twitter.php';
+
+/**
+ * Settings for Twitter integration
+ *
+ * @category Settings
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ * @see SettingsAction
+ */
+
+class TwittersettingsAction extends ConnectSettingsAction
+{
+ /**
+ * Title of the page
+ *
+ * @return string Title of the page
+ */
+
+ function title()
+ {
+ return _('Twitter settings');
+ }
+
+ /**
+ * Instructions for use
+ *
+ * @return instructions for use
+ */
+
+ function getInstructions()
+ {
+ return _('Connect your Twitter account to share your updates ' .
+ 'with your Twitter friends and vice-versa.');
+ }
+
+ /**
+ * Content area of the page
+ *
+ * Shows a form for associating a Twitter account with this
+ * Laconica account. Also lets the user set preferences.
+ *
+ * @return void
+ */
+
+ function showContent()
+ {
+
+ $user = common_current_user();
+
+ $profile = $user->getProfile();
+
+ $fuser = null;
+
+ $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+ if (!empty($flink)) {
+ $fuser = $flink->getForeignUser();
+ }
+
+ $this->elementStart('form', array('method' => 'post',
+ 'id' => 'form_settings_twitter',
+ 'class' => 'form_settings',
+ 'action' =>
+ common_local_url('twittersettings')));
+
+ $this->hidden('token', common_session_token());
+
+ $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
+
+ if (empty($fuser)) {
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
+ $this->element('a', array('href' => common_local_url('twitterauthorization')),
+ 'Connect my Twitter account');
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+
+ $this->elementEnd('fieldset');
+ } else {
+ $this->element('legend', null, _('Twitter account'));
+ $this->elementStart('p', array('id' => 'form_confirmed'));
+ $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
+ $this->elementEnd('p');
+ $this->element('p', 'form_note',
+ _('Connected Twitter account'));
+
+ $this->submit('remove', _('Remove'));
+
+ $this->elementEnd('fieldset');
+
+ $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
+
+ $this->element('legend', null, _('Preferences'));
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ $this->checkbox('noticesend',
+ _('Automatically send my notices to Twitter.'),
+ ($flink) ?
+ ($flink->noticesync & FOREIGN_NOTICE_SEND) :
+ true);
+ $this->elementEnd('li');
+ $this->elementStart('li');
+ $this->checkbox('replysync',
+ _('Send local "@" replies to Twitter.'),
+ ($flink) ?
+ ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
+ true);
+ $this->elementEnd('li');
+ $this->elementStart('li');
+ $this->checkbox('friendsync',
+ _('Subscribe to my Twitter friends here.'),
+ ($flink) ?
+ ($flink->friendsync & FOREIGN_FRIEND_RECV) :
+ false);
+ $this->elementEnd('li');
+
+ if (common_config('twitterbridge','enabled')) {
+ $this->elementStart('li');
+ $this->checkbox('noticerecv',
+ _('Import my Friends Timeline.'),
+ ($flink) ?
+ ($flink->noticesync & FOREIGN_NOTICE_RECV) :
+ false);
+ $this->elementEnd('li');
+
+ // preserve setting even if bidrection bridge toggled off
+
+ if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
+ $this->hidden('noticerecv', true, 'noticerecv');
+ }
+ }
+
+ $this->elementEnd('ul');
+
+ if ($flink) {
+ $this->submit('save', _('Save'));
+ } else {
+ $this->submit('add', _('Add'));
+ }
+
+ $this->elementEnd('fieldset');
+ }
+
+ $this->elementEnd('form');
+ }
+
+ /**
+ * Handle posts to this form
+ *
+ * Based on the button that was pressed, muxes out to other functions
+ * to do the actual task requested.
+ *
+ * All sub-functions reload the form with a message -- success or failure.
+ *
+ * @return void
+ */
+
+ function handlePost()
+ {
+ // CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(_('There was a problem with your session token. '.
+ 'Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->savePreferences();
+ } else if ($this->arg('remove')) {
+ $this->removeTwitterAccount();
+ } else {
+ $this->showForm(_('Unexpected form submission.'));
+ }
+ }
+
+ /**
+ * Disassociate an existing Twitter account from this account
+ *
+ * @return void
+ */
+
+ function removeTwitterAccount()
+ {
+ $user = common_current_user();
+ $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ $this->serverError(_('Couldn\'t remove Twitter user.'));
+ return;
+ }
+
+ $this->showForm(_('Twitter account removed.'), true);
+ }
+
+ /**
+ * Save user's Twitter-bridging preferences
+ *
+ * @return void
+ */
+
+ function savePreferences()
+ {
+ $noticesend = $this->boolean('noticesend');
+ $noticerecv = $this->boolean('noticerecv');
+ $friendsync = $this->boolean('friendsync');
+ $replysync = $this->boolean('replysync');
+
+ $user = common_current_user();
+ $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+ if (empty($flink)) {
+ common_log_db_error($flink, 'SELECT', __FILE__);
+ $this->showForm(_('Couldn\'t save Twitter preferences.'));
+ return;
+ }
+
+ $original = clone($flink);
+ $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
+ $result = $flink->update($original);
+
+ if ($result === false) {
+ common_log_db_error($flink, 'UPDATE', __FILE__);
+ $this->showForm(_('Couldn\'t save Twitter preferences.'));
+ return;
+ }
+
+ $this->showForm(_('Twitter preferences saved.'), true);
+ }
+
+}
--
cgit v1.2.3-54-g00ecf
From 5efe588174c71979fc1970353c9a556ea441f138 Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Wed, 26 Aug 2009 00:59:06 +0000
Subject: Moved the rest of the Twitter stuff into the TwitterBridge plugin
---
lib/common.php | 1 -
lib/twitter.php | 258 ----------
plugins/TwitterBridge/TwitterBridgePlugin.php | 2 +-
.../TwitterBridge/daemons/synctwitterfriends.php | 280 +++++++++++
.../TwitterBridge/daemons/twitterqueuehandler.php | 73 +++
.../TwitterBridge/daemons/twitterstatusfetcher.php | 559 +++++++++++++++++++++
plugins/TwitterBridge/twitter.php | 258 ++++++++++
plugins/TwitterBridge/twitterauthorization.php | 2 +
plugins/TwitterBridge/twittersettings.php | 4 +-
scripts/synctwitterfriends.php | 279 ----------
scripts/twitterqueuehandler.php | 74 ---
scripts/twitterstatusfetcher.php | 558 --------------------
12 files changed, 1175 insertions(+), 1173 deletions(-)
delete mode 100644 lib/twitter.php
create mode 100755 plugins/TwitterBridge/daemons/synctwitterfriends.php
create mode 100755 plugins/TwitterBridge/daemons/twitterqueuehandler.php
create mode 100755 plugins/TwitterBridge/daemons/twitterstatusfetcher.php
create mode 100644 plugins/TwitterBridge/twitter.php
delete mode 100755 scripts/synctwitterfriends.php
delete mode 100755 scripts/twitterqueuehandler.php
delete mode 100755 scripts/twitterstatusfetcher.php
(limited to 'plugins')
diff --git a/lib/common.php b/lib/common.php
index 62cf5640d..d23d9da7d 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -409,7 +409,6 @@ require_once INSTALLDIR.'/lib/theme.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/Shorturl_api.php';
-require_once INSTALLDIR.'/lib/twitter.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
diff --git a/lib/twitter.php b/lib/twitter.php
deleted file mode 100644
index 280cdb0a3..000000000
--- a/lib/twitter.php
+++ /dev/null
@@ -1,258 +0,0 @@
-.
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
-
-function update_twitter_user($twitter_id, $screen_name)
-{
- $uri = 'http://twitter.com/' . $screen_name;
- $fuser = new Foreign_user();
-
- $fuser->query('BEGIN');
-
- // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
- // to work so good with tables that have multiple column primary keys
-
- // Any time we update the uri for a forein user we have to make sure there
- // are no dupe entries first -- unique constraint on the uri column
-
- $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
- $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
-
- $fuser->query($qry);
-
- // Update the user
-
- $qry = 'UPDATE foreign_user SET nickname = ';
- $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
- $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
-
- $fuser->query('COMMIT');
-
- $fuser->free();
- unset($fuser);
-
- return true;
-}
-
-function add_twitter_user($twitter_id, $screen_name)
-{
-
- $new_uri = 'http://twitter.com/' . $screen_name;
-
- // Clear out any bad old foreign_users with the new user's legit URL
- // This can happen when users move around or fakester accounts get
- // repoed, and things like that.
-
- $luser = new Foreign_user();
- $luser->uri = $new_uri;
- $luser->service = TWITTER_SERVICE;
- $result = $luser->delete();
-
- if (empty($result)) {
- common_log(LOG_WARNING,
- "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
- }
-
- $luser->free();
- unset($luser);
-
- // Otherwise, create a new Twitter user
-
- $fuser = new Foreign_user();
-
- $fuser->nickname = $screen_name;
- $fuser->uri = 'http://twitter.com/' . $screen_name;
- $fuser->id = $twitter_id;
- $fuser->service = TWITTER_SERVICE;
- $fuser->created = common_sql_now();
- $result = $fuser->insert();
-
- if (empty($result)) {
- common_log(LOG_WARNING,
- "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
- common_log_db_error($fuser, 'INSERT', __FILE__);
- } else {
- common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
- }
-
- return $result;
-}
-
-// Creates or Updates a Twitter user
-function save_twitter_user($twitter_id, $screen_name)
-{
-
- // Check to see whether the Twitter user is already in the system,
- // and update its screen name and uri if so.
-
- $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
-
- if (!empty($fuser)) {
-
- $result = true;
-
- // Only update if Twitter screen name has changed
-
- if ($fuser->nickname != $screen_name) {
- $result = update_twitter_user($twitter_id, $screen_name);
-
- common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
- "$fuser->id to $screen_name, was $fuser->nickname");
- }
-
- return $result;
-
- } else {
- return add_twitter_user($twitter_id, $screen_name);
- }
-
- $fuser->free();
- unset($fuser);
-
- return true;
-}
-
-function is_twitter_bound($notice, $flink) {
-
- // Check to see if notice should go to Twitter
- if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
-
- // If it's not a Twitter-style reply, or if the user WANTS to send replies.
- if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
- ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
- return true;
- }
- }
-
- return false;
-}
-
-function broadcast_twitter($notice)
-{
- $flink = Foreign_link::getByUserID($notice->profile_id,
- TWITTER_SERVICE);
-
- if (is_twitter_bound($notice, $flink)) {
-
- $user = $flink->getUser();
-
- // XXX: Hack to get around PHP cURL's use of @ being a a meta character
- $statustxt = preg_replace('/^@/', ' @', $notice->content);
-
- $token = TwitterOAuthClient::unpackToken($flink->credentials);
-
- $client = new TwitterOAuthClient($token->key, $token->secret);
-
- $status = null;
-
- try {
- $status = $client->statusesUpdate($statustxt);
- } catch (OAuthClientCurlException $e) {
-
- if ($e->getMessage() == 'The requested URL returned error: 401') {
-
- $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
- 'Twitter OAuth access token.',
- $user->nickname, $user->id);
- common_log(LOG_WARNING, $errmsg);
-
- // Bad auth token! We need to delete the foreign_link
- // to Twitter and inform the user.
-
- remove_twitter_link($flink);
- return true;
-
- } else {
-
- // Some other error happened, so we should probably
- // try to send again later.
-
- $errmsg = sprintf('cURL error trying to send notice to Twitter ' .
- 'for user %1$s (user id: %2$s) - ' .
- 'code: %3$s message: $4$s.',
- $user->nickname, $user->id,
- $e->getCode(), $e->getMessage());
- common_log(LOG_WARNING, $errmsg);
-
- return false;
- }
- }
-
- if (empty($status)) {
-
- // This could represent a failure posting,
- // or the Twitter API might just be behaving flakey.
-
- $errmsg = sprint('No data returned by Twitter API when ' .
- 'trying to send update for %1$s (user id %2$s).',
- $user->nickname, $user->id);
- common_log(LOG_WARNING, $errmsg);
-
- return false;
- }
-
- // Notice crossed the great divide
-
- $msg = sprintf('Twitter bridge posted notice %s to Twitter.',
- $notice->id);
- common_log(LOG_INFO, $msg);
- }
-
- return true;
-}
-
-function remove_twitter_link($flink)
-{
- $user = $flink->getUser();
-
- common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
- "user $user->nickname (user id: $user->id).");
-
- $result = $flink->delete();
-
- if (empty($result)) {
- common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
- "Foreign_link for $user->nickname (user id: $user->id)!");
- common_log_db_error($flink, 'DELETE', __FILE__);
- }
-
- // Notify the user that her Twitter bridge is down
-
- if (isset($user->email)) {
-
- $result = mail_twitter_bridge_removed($user);
-
- if (!$result) {
-
- $msg = 'Unable to send email to notify ' .
- "$user->nickname (user id: $user->id) " .
- 'that their Twitter bridge link was ' .
- 'removed!';
-
- common_log(LOG_WARNING, $msg);
- }
- }
-
-}
-
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index f7daa9a22..a8de1c664 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -90,7 +90,7 @@ class TwitterBridgePlugin extends Plugin
require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
return false;
case 'TwitterOAuthClient':
- require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php');
+ require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php');
return false;
default:
return true;
diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php
new file mode 100755
index 000000000..b7be5d043
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php
@@ -0,0 +1,280 @@
+#!/usr/bin/env php
+.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'di::';
+$longoptions = array('id::', 'debug');
+
+$helptext = <<
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+$helptext = <<_id);
+ }
+
+ /**
+ * Find all the Twitter foreign links for users who have requested
+ * automatically subscribing to their Twitter friends locally.
+ *
+ * @return array flinks an array of Foreign_link objects
+ */
+ function getObjects()
+ {
+ $flinks = array();
+ $flink = new Foreign_link();
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $flink->service = TWITTER_SERVICE;
+ $flink->orderBy('last_friendsync');
+ $flink->limit(25); // sync this many users during this run
+ $flink->find();
+
+ while ($flink->fetch()) {
+ if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
+ $flinks[] = clone($flink);
+ }
+ }
+
+ $conn->disconnect();
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+ return $flinks;
+ }
+
+ function childTask($flink) {
+
+ // Each child ps needs its own DB connection
+
+ // Note: DataObject::getDatabaseConnection() creates
+ // a new connection if there isn't one already
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $this->subscribeTwitterFriends($flink);
+
+ $flink->last_friendsync = common_sql_now();
+ $flink->update();
+
+ $conn->disconnect();
+
+ // XXX: Couldn't find a less brutal way to blow
+ // away a cached connection
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+ }
+
+ function fetchTwitterFriends($flink)
+ {
+ $friends = array();
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ try {
+ $friends_ids = $client->friendsIds();
+ } catch (OAuthCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - cURL error getting friend ids ' .
+ $e->getCode() . ' - ' . $e->getMessage());
+ return $friends;
+ }
+
+ if (empty($friends_ids)) {
+ common_debug($this->name() .
+ " - Twitter user $flink->foreign_id " .
+ 'doesn\'t have any friends!');
+ return $friends;
+ }
+
+ common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
+ "$flink->foreign_id has " .
+ count($friends_ids) . ' friends.');
+
+ // Calculate how many pages to get...
+ $pages = ceil(count($friends_ids) / 100);
+
+ if ($pages == 0) {
+ common_debug($this->name() . " - $user seems to have no friends.");
+ }
+
+ for ($i = 1; $i <= $pages; $i++) {
+
+ try {
+ $more_friends = $client->statusesFriends(null, null, null, $i);
+ } catch (OAuthCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - cURL error getting Twitter statuses/friends ' .
+ "page $i - " . $e->getCode() . ' - ' .
+ $e->getMessage());
+ }
+
+ if (empty($more_friends)) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Couldn't retrieve page $i " .
+ "of Twitter user $flink->foreign_id friends.");
+ continue;
+ } else {
+ $friends = array_merge($friends, $more_friends);
+ }
+ }
+
+ return $friends;
+ }
+
+ function subscribeTwitterFriends($flink)
+ {
+ $friends = $this->fetchTwitterFriends($flink);
+
+ if (empty($friends)) {
+ common_debug($this->name() .
+ ' - Couldn\'t get friends from Twitter for ' .
+ "Twitter user $flink->foreign_id.");
+ return false;
+ }
+
+ $user = $flink->getUser();
+
+ foreach ($friends as $friend) {
+
+ $friend_name = $friend->screen_name;
+ $friend_id = (int) $friend->id;
+
+ // Update or create the Foreign_user record for each
+ // Twitter friend
+
+ if (!save_twitter_user($friend_id, $friend_name)) {
+ common_log(LOG_WARNING, $this-name() .
+ " - Couldn't save $screen_name's friend, $friend_name.");
+ continue;
+ }
+
+ // Check to see if there's a related local user
+
+ $friend_flink = Foreign_link::getByForeignID($friend_id,
+ TWITTER_SERVICE);
+
+ if (!empty($friend_flink)) {
+
+ // Get associated user and subscribe her
+
+ $friend_user = User::staticGet('id', $friend_flink->user_id);
+
+ if (!empty($friend_user)) {
+ $result = subs_subscribe_to($user, $friend_user);
+
+ if ($result === true) {
+ common_log(LOG_INFO,
+ $this->name() . ' - Subscribed ' .
+ "$friend_user->nickname to $user->nickname.");
+ } else {
+ common_debug($this->name() .
+ ' - Tried subscribing ' .
+ "$friend_user->nickname to $user->nickname - " .
+ $result);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
+
+$id = null;
+$debug = null;
+
+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;
+}
+
+if (have_option('d') || have_option('debug')) {
+ $debug = true;
+}
+
+$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
+$syncer->runOnce();
+
diff --git a/plugins/TwitterBridge/daemons/twitterqueuehandler.php b/plugins/TwitterBridge/daemons/twitterqueuehandler.php
new file mode 100755
index 000000000..9aa9d1ed0
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/twitterqueuehandler.php
@@ -0,0 +1,73 @@
+#!/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 broadcast_twitter($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 TwitterQueueHandler($id);
+
+$handler->runOnce();
diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
new file mode 100755
index 000000000..3023bf423
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
@@ -0,0 +1,559 @@
+#!/usr/bin/env php
+.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+// Tune number of processes and how often to poll Twitter
+// XXX: Should these things be in config.php?
+define('MAXCHILDREN', 2);
+define('POLL_INTERVAL', 60); // in seconds
+
+$shortoptions = 'di::';
+$longoptions = array('id::', 'debug');
+
+$helptext = <<
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+// NOTE: an Avatar path MUST be set in config.php for this
+// script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar';
+
+class TwitterStatusFetcher extends ParallelizingDaemon
+{
+ /**
+ * Constructor
+ *
+ * @param string $id the name/id of this daemon
+ * @param int $interval sleep this long before doing everything again
+ * @param int $max_children maximum number of child processes at a time
+ * @param boolean $debug debug output flag
+ *
+ * @return void
+ *
+ **/
+ function __construct($id = null, $interval = 60,
+ $max_children = 2, $debug = null)
+ {
+ parent::__construct($id, $interval, $max_children, $debug);
+ }
+
+ /**
+ * Name of this daemon
+ *
+ * @return string Name of the daemon.
+ */
+
+ function name()
+ {
+ return ('twitterstatusfetcher.'.$this->_id);
+ }
+
+ /**
+ * Find all the Twitter foreign links for users who have requested
+ * importing of their friends' timelines
+ *
+ * @return array flinks an array of Foreign_link objects
+ */
+
+ function getObjects()
+ {
+ global $_DB_DATAOBJECT;
+
+ $flink = new Foreign_link();
+ $conn = &$flink->getDatabaseConnection();
+
+ $flink->service = TWITTER_SERVICE;
+ $flink->orderBy('last_noticesync');
+ $flink->find();
+
+ $flinks = array();
+
+ while ($flink->fetch()) {
+
+ if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+ FOREIGN_NOTICE_RECV) {
+ $flinks[] = clone($flink);
+ }
+ }
+
+ $flink->free();
+ unset($flink);
+
+ $conn->disconnect();
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+ return $flinks;
+ }
+
+ function childTask($flink) {
+
+ // Each child ps needs its own DB connection
+
+ // Note: DataObject::getDatabaseConnection() creates
+ // a new connection if there isn't one already
+
+ $conn = &$flink->getDatabaseConnection();
+
+ $this->getTimeline($flink);
+
+ $flink->last_friendsync = common_sql_now();
+ $flink->update();
+
+ $conn->disconnect();
+
+ // XXX: Couldn't find a less brutal way to blow
+ // away a cached connection
+
+ global $_DB_DATAOBJECT;
+ unset($_DB_DATAOBJECT['CONNECTIONS']);
+ }
+
+ function getTimeline($flink)
+ {
+ if (empty($flink)) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Can't retrieve Foreign_link for foreign ID $fid");
+ return;
+ }
+
+ common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
+ $flink->foreign_id);
+
+ // XXX: Biggest remaining issue - How do we know at which status
+ // to start importing? How many statuses? Right now I'm going
+ // with the default last 20.
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $timeline = null;
+
+ try {
+ $timeline = $client->statusesFriendsTimeline();
+ } catch (OAuthClientCurlException $e) {
+ common_log(LOG_WARNING, $this->name() .
+ ' - OAuth client unable to get friends timeline for user ' .
+ $flink->user_id . ' - code: ' .
+ $e->getCode() . 'msg: ' . $e->getMessage());
+ }
+
+ if (empty($timeline)) {
+ common_log(LOG_WARNING, $this->name() . " - Empty timeline.");
+ return;
+ }
+
+ // Reverse to preserve order
+
+ foreach (array_reverse($timeline) as $status) {
+
+ // Hacktastic: filter out stuff coming from this Laconica
+
+ $source = mb_strtolower(common_config('integration', 'source'));
+
+ if (preg_match("/$source/", mb_strtolower($status->source))) {
+ common_debug($this->name() . ' - Skipping import of status ' .
+ $status->id . ' with source ' . $source);
+ continue;
+ }
+
+ $this->saveStatus($status, $flink);
+ }
+
+ // Okay, record the time we synced with Twitter for posterity
+
+ $flink->last_noticesync = common_sql_now();
+ $flink->update();
+ }
+
+ function saveStatus($status, $flink)
+ {
+ $id = $this->ensureProfile($status->user);
+
+ $profile = Profile::staticGet($id);
+
+ if (empty($profile)) {
+ common_log(LOG_ERR, $this->name() .
+ ' - Problem saving notice. No associated Profile.');
+ return null;
+ }
+
+ // XXX: change of screen name?
+
+ $uri = 'http://twitter.com/' . $status->user->screen_name .
+ '/status/' . $status->id;
+
+ $notice = Notice::staticGet('uri', $uri);
+
+ // check to see if we've already imported the status
+
+ if (empty($notice)) {
+
+ $notice = new Notice();
+
+ $notice->profile_id = $id;
+ $notice->uri = $uri;
+ $notice->created = strftime('%Y-%m-%d %H:%M:%S',
+ strtotime($status->created_at));
+ $notice->content = common_shorten_links($status->text); // XXX
+ $notice->rendered = common_render_content($notice->content, $notice);
+ $notice->source = 'twitter';
+ $notice->reply_to = null; // XXX: lookup reply
+ $notice->is_local = Notice::GATEWAY;
+
+ if (Event::handle('StartNoticeSave', array(&$notice))) {
+ $id = $notice->insert();
+ Event::handle('EndNoticeSave', array($notice));
+ }
+ }
+
+ if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
+ 'user_id' => $flink->user_id))) {
+ // Add to inbox
+ $inbox = new Notice_inbox();
+
+ $inbox->user_id = $flink->user_id;
+ $inbox->notice_id = $notice->id;
+ $inbox->created = $notice->created;
+ $inbox->source = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
+
+ $inbox->insert();
+ }
+ }
+
+ function ensureProfile($user)
+ {
+ // check to see if there's already a profile for this user
+
+ $profileurl = 'http://twitter.com/' . $user->screen_name;
+ $profile = Profile::staticGet('profileurl', $profileurl);
+
+ if (!empty($profile)) {
+ common_debug($this->name() .
+ " - Profile for $profile->nickname found.");
+
+ // Check to see if the user's Avatar has changed
+
+ $this->checkAvatar($user, $profile);
+ return $profile->id;
+
+ } else {
+ common_debug($this->name() . ' - Adding profile and remote profile ' .
+ "for Twitter user: $profileurl.");
+
+ $profile = new Profile();
+ $profile->query("BEGIN");
+
+ $profile->nickname = $user->screen_name;
+ $profile->fullname = $user->name;
+ $profile->homepage = $user->url;
+ $profile->bio = $user->description;
+ $profile->location = $user->location;
+ $profile->profileurl = $profileurl;
+ $profile->created = common_sql_now();
+
+ $id = $profile->insert();
+
+ if (empty($id)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+
+ // check for remote profile
+
+ $remote_pro = Remote_profile::staticGet('uri', $profileurl);
+
+ if (empty($remote_pro)) {
+
+ $remote_pro = new Remote_profile();
+
+ $remote_pro->id = $id;
+ $remote_pro->uri = $profileurl;
+ $remote_pro->created = common_sql_now();
+
+ $rid = $remote_pro->insert();
+
+ if (empty($rid)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+ }
+
+ $profile->query("COMMIT");
+
+ $this->saveAvatars($user, $id);
+
+ return $id;
+ }
+ }
+
+ function checkAvatar($twitter_user, $profile)
+ {
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $newname = 'Twitter_' . $twitter_user->id . '_' .
+ $path_parts['basename'];
+
+ $oldname = $profile->getAvatar(48)->filename;
+
+ if ($newname != $oldname) {
+ common_debug($this->name() . ' - Avatar for Twitter user ' .
+ "$profile->nickname has changed.");
+ common_debug($this->name() . " - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+
+ if ($this->missingAvatarFile($profile)) {
+ common_debug($this->name() . ' - Twitter user ' .
+ $profile->nickname .
+ ' is missing one or more local avatars.');
+ common_debug($this->name() ." - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+
+ }
+
+ function updateAvatars($twitter_user, $profile) {
+
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $img_root = substr($path_parts['basename'], 0, -11);
+ $ext = $path_parts['extension'];
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $twitter_user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ $this->updateAvatar($profile->id, $size, $mediatype, $filename);
+ $this->fetchAvatar($url, $filename);
+ }
+ }
+
+ function missingAvatarFile($profile) {
+
+ foreach (array(24, 48, 73) as $size) {
+
+ $filename = $profile->getAvatar($size)->filename;
+ $avatarpath = Avatar::path($filename);
+
+ if (file_exists($avatarpath) == FALSE) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function getMediatype($ext)
+ {
+ $mediatype = null;
+
+ switch (strtolower($ext)) {
+ case 'jpg':
+ $mediatype = 'image/jpg';
+ break;
+ case 'gif':
+ $mediatype = 'image/gif';
+ break;
+ default:
+ $mediatype = 'image/png';
+ }
+
+ return $mediatype;
+ }
+
+ function saveAvatars($user, $id)
+ {
+ global $config;
+
+ $path_parts = pathinfo($user->profile_image_url);
+ $ext = $path_parts['extension'];
+ $end = strlen('_normal' . $ext);
+ $img_root = substr($path_parts['basename'], 0, -($end+1));
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ if ($this->fetchAvatar($url, $filename)) {
+ $this->newAvatar($id, $size, $mediatype, $filename);
+ } else {
+ common_log(LOG_WARNING, $this->id() .
+ " - Problem fetching Avatar: $url");
+ }
+ }
+ }
+
+ function updateAvatar($profile_id, $size, $mediatype, $filename) {
+
+ common_debug($this->name() . " - Updating avatar: $size");
+
+ $profile = Profile::staticGet($profile_id);
+
+ if (empty($profile)) {
+ common_debug($this->name() . " - Couldn't get profile: $profile_id!");
+ return;
+ }
+
+ $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
+ $avatar = $profile->getAvatar($sizes[$size]);
+
+ // Delete the avatar, if present
+
+ if ($avatar) {
+ $avatar->delete();
+ }
+
+ $this->newAvatar($profile->id, $size, $mediatype, $filename);
+ }
+
+ function newAvatar($profile_id, $size, $mediatype, $filename)
+ {
+ global $config;
+
+ $avatar = new Avatar();
+ $avatar->profile_id = $profile_id;
+
+ switch($size) {
+ case 'mini':
+ $avatar->width = 24;
+ $avatar->height = 24;
+ break;
+ case 'normal':
+ $avatar->width = 48;
+ $avatar->height = 48;
+ break;
+ default:
+
+ // Note: Twitter's big avatars are a different size than
+ // Laconica's (Laconica's = 96)
+
+ $avatar->width = 73;
+ $avatar->height = 73;
+ }
+
+ $avatar->original = 0; // we don't have the original
+ $avatar->mediatype = $mediatype;
+ $avatar->filename = $filename;
+ $avatar->url = Avatar::url($filename);
+
+ common_debug($this->name() . " - New filename: $avatar->url");
+
+ $avatar->created = common_sql_now();
+
+ $id = $avatar->insert();
+
+ if (empty($id)) {
+ common_log_db_error($avatar, 'INSERT', __FILE__);
+ return null;
+ }
+
+ common_debug($this->name() .
+ " - Saved new $size avatar for $profile_id.");
+
+ return $id;
+ }
+
+ function fetchAvatar($url, $filename)
+ {
+ $avatar_dir = INSTALLDIR . '/avatar/';
+
+ $avatarfile = $avatar_dir . $filename;
+
+ $out = fopen($avatarfile, 'wb');
+ if (!$out) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Couldn't open file $filename");
+ return false;
+ }
+
+ common_debug($this->name() . " - Fetching Twitter avatar: $url");
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_FILE, $out);
+ curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
+ $result = curl_exec($ch);
+ curl_close($ch);
+
+ fclose($out);
+
+ return $result;
+ }
+}
+
+$id = null;
+$debug = null;
+
+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;
+}
+
+if (have_option('d') || have_option('debug')) {
+ $debug = true;
+}
+
+$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug);
+$fetcher->runOnce();
+
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
new file mode 100644
index 000000000..280cdb0a3
--- /dev/null
+++ b/plugins/TwitterBridge/twitter.php
@@ -0,0 +1,258 @@
+.
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
+
+function update_twitter_user($twitter_id, $screen_name)
+{
+ $uri = 'http://twitter.com/' . $screen_name;
+ $fuser = new Foreign_user();
+
+ $fuser->query('BEGIN');
+
+ // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
+ // to work so good with tables that have multiple column primary keys
+
+ // Any time we update the uri for a forein user we have to make sure there
+ // are no dupe entries first -- unique constraint on the uri column
+
+ $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
+ $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
+
+ $fuser->query($qry);
+
+ // Update the user
+
+ $qry = 'UPDATE foreign_user SET nickname = ';
+ $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
+ $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
+
+ $fuser->query('COMMIT');
+
+ $fuser->free();
+ unset($fuser);
+
+ return true;
+}
+
+function add_twitter_user($twitter_id, $screen_name)
+{
+
+ $new_uri = 'http://twitter.com/' . $screen_name;
+
+ // Clear out any bad old foreign_users with the new user's legit URL
+ // This can happen when users move around or fakester accounts get
+ // repoed, and things like that.
+
+ $luser = new Foreign_user();
+ $luser->uri = $new_uri;
+ $luser->service = TWITTER_SERVICE;
+ $result = $luser->delete();
+
+ if (empty($result)) {
+ common_log(LOG_WARNING,
+ "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
+ }
+
+ $luser->free();
+ unset($luser);
+
+ // Otherwise, create a new Twitter user
+
+ $fuser = new Foreign_user();
+
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $fuser->id = $twitter_id;
+ $fuser->service = TWITTER_SERVICE;
+ $fuser->created = common_sql_now();
+ $result = $fuser->insert();
+
+ if (empty($result)) {
+ common_log(LOG_WARNING,
+ "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+ common_log_db_error($fuser, 'INSERT', __FILE__);
+ } else {
+ common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+ }
+
+ return $result;
+}
+
+// Creates or Updates a Twitter user
+function save_twitter_user($twitter_id, $screen_name)
+{
+
+ // Check to see whether the Twitter user is already in the system,
+ // and update its screen name and uri if so.
+
+ $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
+
+ if (!empty($fuser)) {
+
+ $result = true;
+
+ // Only update if Twitter screen name has changed
+
+ if ($fuser->nickname != $screen_name) {
+ $result = update_twitter_user($twitter_id, $screen_name);
+
+ common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
+ "$fuser->id to $screen_name, was $fuser->nickname");
+ }
+
+ return $result;
+
+ } else {
+ return add_twitter_user($twitter_id, $screen_name);
+ }
+
+ $fuser->free();
+ unset($fuser);
+
+ return true;
+}
+
+function is_twitter_bound($notice, $flink) {
+
+ // Check to see if notice should go to Twitter
+ if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
+
+ // If it's not a Twitter-style reply, or if the user WANTS to send replies.
+ if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
+ ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function broadcast_twitter($notice)
+{
+ $flink = Foreign_link::getByUserID($notice->profile_id,
+ TWITTER_SERVICE);
+
+ if (is_twitter_bound($notice, $flink)) {
+
+ $user = $flink->getUser();
+
+ // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+ $statustxt = preg_replace('/^@/', ' @', $notice->content);
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $status = null;
+
+ try {
+ $status = $client->statusesUpdate($statustxt);
+ } catch (OAuthClientCurlException $e) {
+
+ if ($e->getMessage() == 'The requested URL returned error: 401') {
+
+ $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
+ 'Twitter OAuth access token.',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
+
+ // Bad auth token! We need to delete the foreign_link
+ // to Twitter and inform the user.
+
+ remove_twitter_link($flink);
+ return true;
+
+ } else {
+
+ // Some other error happened, so we should probably
+ // try to send again later.
+
+ $errmsg = sprintf('cURL error trying to send notice to Twitter ' .
+ 'for user %1$s (user id: %2$s) - ' .
+ 'code: %3$s message: $4$s.',
+ $user->nickname, $user->id,
+ $e->getCode(), $e->getMessage());
+ common_log(LOG_WARNING, $errmsg);
+
+ return false;
+ }
+ }
+
+ if (empty($status)) {
+
+ // This could represent a failure posting,
+ // or the Twitter API might just be behaving flakey.
+
+ $errmsg = sprint('No data returned by Twitter API when ' .
+ 'trying to send update for %1$s (user id %2$s).',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
+
+ return false;
+ }
+
+ // Notice crossed the great divide
+
+ $msg = sprintf('Twitter bridge posted notice %s to Twitter.',
+ $notice->id);
+ common_log(LOG_INFO, $msg);
+ }
+
+ return true;
+}
+
+function remove_twitter_link($flink)
+{
+ $user = $flink->getUser();
+
+ common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
+ "user $user->nickname (user id: $user->id).");
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
+ "Foreign_link for $user->nickname (user id: $user->id)!");
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that her Twitter bridge is down
+
+ if (isset($user->email)) {
+
+ $result = mail_twitter_bridge_removed($user);
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify ' .
+ "$user->nickname (user id: $user->id) " .
+ 'that their Twitter bridge link was ' .
+ 'removed!';
+
+ common_log(LOG_WARNING, $msg);
+ }
+ }
+
+}
+
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
index b04f35327..a54528434 100644
--- a/plugins/TwitterBridge/twitterauthorization.php
+++ b/plugins/TwitterBridge/twitterauthorization.php
@@ -31,6 +31,8 @@ if (!defined('LACONICA')) {
exit(1);
}
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
/**
* Class for doing OAuth authentication against Twitter
*
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
index a3e02e125..b3d4a971f 100644
--- a/plugins/TwitterBridge/twittersettings.php
+++ b/plugins/TwitterBridge/twittersettings.php
@@ -31,8 +31,8 @@ if (!defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-require_once INSTALLDIR.'/lib/twitter.php';
+require_once INSTALLDIR . '/lib/connectsettingsaction.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
/**
* Settings for Twitter integration
diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php
deleted file mode 100755
index 2de464bcc..000000000
--- a/scripts/synctwitterfriends.php
+++ /dev/null
@@ -1,279 +0,0 @@
-#!/usr/bin/env php
-.
- */
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-
-$shortoptions = 'di::';
-$longoptions = array('id::', 'debug');
-
-$helptext = <<
- * @author Evan Prodromou
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-$helptext = <<_id);
- }
-
- /**
- * Find all the Twitter foreign links for users who have requested
- * automatically subscribing to their Twitter friends locally.
- *
- * @return array flinks an array of Foreign_link objects
- */
- function getObjects()
- {
- $flinks = array();
- $flink = new Foreign_link();
-
- $conn = &$flink->getDatabaseConnection();
-
- $flink->service = TWITTER_SERVICE;
- $flink->orderBy('last_friendsync');
- $flink->limit(25); // sync this many users during this run
- $flink->find();
-
- while ($flink->fetch()) {
- if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
- $flinks[] = clone($flink);
- }
- }
-
- $conn->disconnect();
-
- global $_DB_DATAOBJECT;
- unset($_DB_DATAOBJECT['CONNECTIONS']);
-
- return $flinks;
- }
-
- function childTask($flink) {
-
- // Each child ps needs its own DB connection
-
- // Note: DataObject::getDatabaseConnection() creates
- // a new connection if there isn't one already
-
- $conn = &$flink->getDatabaseConnection();
-
- $this->subscribeTwitterFriends($flink);
-
- $flink->last_friendsync = common_sql_now();
- $flink->update();
-
- $conn->disconnect();
-
- // XXX: Couldn't find a less brutal way to blow
- // away a cached connection
-
- global $_DB_DATAOBJECT;
- unset($_DB_DATAOBJECT['CONNECTIONS']);
- }
-
- function fetchTwitterFriends($flink)
- {
- $friends = array();
-
- $token = TwitterOAuthClient::unpackToken($flink->credentials);
-
- $client = new TwitterOAuthClient($token->key, $token->secret);
-
- try {
- $friends_ids = $client->friendsIds();
- } catch (OAuthCurlException $e) {
- common_log(LOG_WARNING, $this->name() .
- ' - cURL error getting friend ids ' .
- $e->getCode() . ' - ' . $e->getMessage());
- return $friends;
- }
-
- if (empty($friends_ids)) {
- common_debug($this->name() .
- " - Twitter user $flink->foreign_id " .
- 'doesn\'t have any friends!');
- return $friends;
- }
-
- common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
- "$flink->foreign_id has " .
- count($friends_ids) . ' friends.');
-
- // Calculate how many pages to get...
- $pages = ceil(count($friends_ids) / 100);
-
- if ($pages == 0) {
- common_debug($this->name() . " - $user seems to have no friends.");
- }
-
- for ($i = 1; $i <= $pages; $i++) {
-
- try {
- $more_friends = $client->statusesFriends(null, null, null, $i);
- } catch (OAuthCurlException $e) {
- common_log(LOG_WARNING, $this->name() .
- ' - cURL error getting Twitter statuses/friends ' .
- "page $i - " . $e->getCode() . ' - ' .
- $e->getMessage());
- }
-
- if (empty($more_friends)) {
- common_log(LOG_WARNING, $this->name() .
- " - Couldn't retrieve page $i " .
- "of Twitter user $flink->foreign_id friends.");
- continue;
- } else {
- $friends = array_merge($friends, $more_friends);
- }
- }
-
- return $friends;
- }
-
- function subscribeTwitterFriends($flink)
- {
- $friends = $this->fetchTwitterFriends($flink);
-
- if (empty($friends)) {
- common_debug($this->name() .
- ' - Couldn\'t get friends from Twitter for ' .
- "Twitter user $flink->foreign_id.");
- return false;
- }
-
- $user = $flink->getUser();
-
- foreach ($friends as $friend) {
-
- $friend_name = $friend->screen_name;
- $friend_id = (int) $friend->id;
-
- // Update or create the Foreign_user record for each
- // Twitter friend
-
- if (!save_twitter_user($friend_id, $friend_name)) {
- common_log(LOG_WARNING, $this-name() .
- " - Couldn't save $screen_name's friend, $friend_name.");
- continue;
- }
-
- // Check to see if there's a related local user
-
- $friend_flink = Foreign_link::getByForeignID($friend_id,
- TWITTER_SERVICE);
-
- if (!empty($friend_flink)) {
-
- // Get associated user and subscribe her
-
- $friend_user = User::staticGet('id', $friend_flink->user_id);
-
- if (!empty($friend_user)) {
- $result = subs_subscribe_to($user, $friend_user);
-
- if ($result === true) {
- common_log(LOG_INFO,
- $this->name() . ' - Subscribed ' .
- "$friend_user->nickname to $user->nickname.");
- } else {
- common_debug($this->name() .
- ' - Tried subscribing ' .
- "$friend_user->nickname to $user->nickname - " .
- $result);
- }
- }
- }
- }
-
- return true;
- }
-
-}
-
-$id = null;
-$debug = null;
-
-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;
-}
-
-if (have_option('d') || have_option('debug')) {
- $debug = true;
-}
-
-$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
-$syncer->runOnce();
-
diff --git a/scripts/twitterqueuehandler.php b/scripts/twitterqueuehandler.php
deleted file mode 100755
index 00e735d98..000000000
--- a/scripts/twitterqueuehandler.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 broadcast_twitter($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 TwitterQueueHandler($id);
-
-$handler->runOnce();
diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php
deleted file mode 100755
index f5289c5f4..000000000
--- a/scripts/twitterstatusfetcher.php
+++ /dev/null
@@ -1,558 +0,0 @@
-#!/usr/bin/env php
-.
- */
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-
-// Tune number of processes and how often to poll Twitter
-// XXX: Should these things be in config.php?
-define('MAXCHILDREN', 2);
-define('POLL_INTERVAL', 60); // in seconds
-
-$shortoptions = 'di::';
-$longoptions = array('id::', 'debug');
-
-$helptext = <<
- * @author Evan Prodromou
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-// NOTE: an Avatar path MUST be set in config.php for this
-// script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar';
-
-class TwitterStatusFetcher extends ParallelizingDaemon
-{
- /**
- * Constructor
- *
- * @param string $id the name/id of this daemon
- * @param int $interval sleep this long before doing everything again
- * @param int $max_children maximum number of child processes at a time
- * @param boolean $debug debug output flag
- *
- * @return void
- *
- **/
- function __construct($id = null, $interval = 60,
- $max_children = 2, $debug = null)
- {
- parent::__construct($id, $interval, $max_children, $debug);
- }
-
- /**
- * Name of this daemon
- *
- * @return string Name of the daemon.
- */
-
- function name()
- {
- return ('twitterstatusfetcher.'.$this->_id);
- }
-
- /**
- * Find all the Twitter foreign links for users who have requested
- * importing of their friends' timelines
- *
- * @return array flinks an array of Foreign_link objects
- */
-
- function getObjects()
- {
- global $_DB_DATAOBJECT;
-
- $flink = new Foreign_link();
- $conn = &$flink->getDatabaseConnection();
-
- $flink->service = TWITTER_SERVICE;
- $flink->orderBy('last_noticesync');
- $flink->find();
-
- $flinks = array();
-
- while ($flink->fetch()) {
-
- if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
- FOREIGN_NOTICE_RECV) {
- $flinks[] = clone($flink);
- }
- }
-
- $flink->free();
- unset($flink);
-
- $conn->disconnect();
- unset($_DB_DATAOBJECT['CONNECTIONS']);
-
- return $flinks;
- }
-
- function childTask($flink) {
-
- // Each child ps needs its own DB connection
-
- // Note: DataObject::getDatabaseConnection() creates
- // a new connection if there isn't one already
-
- $conn = &$flink->getDatabaseConnection();
-
- $this->getTimeline($flink);
-
- $flink->last_friendsync = common_sql_now();
- $flink->update();
-
- $conn->disconnect();
-
- // XXX: Couldn't find a less brutal way to blow
- // away a cached connection
-
- global $_DB_DATAOBJECT;
- unset($_DB_DATAOBJECT['CONNECTIONS']);
- }
-
- function getTimeline($flink)
- {
- if (empty($flink)) {
- common_log(LOG_WARNING, $this->name() .
- " - Can't retrieve Foreign_link for foreign ID $fid");
- return;
- }
-
- common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
- $flink->foreign_id);
-
- // XXX: Biggest remaining issue - How do we know at which status
- // to start importing? How many statuses? Right now I'm going
- // with the default last 20.
-
- $token = TwitterOAuthClient::unpackToken($flink->credentials);
-
- $client = new TwitterOAuthClient($token->key, $token->secret);
-
- $timeline = null;
-
- try {
- $timeline = $client->statusesFriendsTimeline();
- } catch (OAuthClientCurlException $e) {
- common_log(LOG_WARNING, $this->name() .
- ' - OAuth client unable to get friends timeline for user ' .
- $flink->user_id . ' - code: ' .
- $e->getCode() . 'msg: ' . $e->getMessage());
- }
-
- if (empty($timeline)) {
- common_log(LOG_WARNING, $this->name() . " - Empty timeline.");
- return;
- }
-
- // Reverse to preserve order
-
- foreach (array_reverse($timeline) as $status) {
-
- // Hacktastic: filter out stuff coming from this Laconica
-
- $source = mb_strtolower(common_config('integration', 'source'));
-
- if (preg_match("/$source/", mb_strtolower($status->source))) {
- common_debug($this->name() . ' - Skipping import of status ' .
- $status->id . ' with source ' . $source);
- continue;
- }
-
- $this->saveStatus($status, $flink);
- }
-
- // Okay, record the time we synced with Twitter for posterity
-
- $flink->last_noticesync = common_sql_now();
- $flink->update();
- }
-
- function saveStatus($status, $flink)
- {
- $id = $this->ensureProfile($status->user);
-
- $profile = Profile::staticGet($id);
-
- if (empty($profile)) {
- common_log(LOG_ERR, $this->name() .
- ' - Problem saving notice. No associated Profile.');
- return null;
- }
-
- // XXX: change of screen name?
-
- $uri = 'http://twitter.com/' . $status->user->screen_name .
- '/status/' . $status->id;
-
- $notice = Notice::staticGet('uri', $uri);
-
- // check to see if we've already imported the status
-
- if (empty($notice)) {
-
- $notice = new Notice();
-
- $notice->profile_id = $id;
- $notice->uri = $uri;
- $notice->created = strftime('%Y-%m-%d %H:%M:%S',
- strtotime($status->created_at));
- $notice->content = common_shorten_links($status->text); // XXX
- $notice->rendered = common_render_content($notice->content, $notice);
- $notice->source = 'twitter';
- $notice->reply_to = null; // XXX: lookup reply
- $notice->is_local = Notice::GATEWAY;
-
- if (Event::handle('StartNoticeSave', array(&$notice))) {
- $id = $notice->insert();
- Event::handle('EndNoticeSave', array($notice));
- }
- }
-
- if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
- 'user_id' => $flink->user_id))) {
- // Add to inbox
- $inbox = new Notice_inbox();
-
- $inbox->user_id = $flink->user_id;
- $inbox->notice_id = $notice->id;
- $inbox->created = $notice->created;
- $inbox->source = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
-
- $inbox->insert();
- }
- }
-
- function ensureProfile($user)
- {
- // check to see if there's already a profile for this user
-
- $profileurl = 'http://twitter.com/' . $user->screen_name;
- $profile = Profile::staticGet('profileurl', $profileurl);
-
- if (!empty($profile)) {
- common_debug($this->name() .
- " - Profile for $profile->nickname found.");
-
- // Check to see if the user's Avatar has changed
-
- $this->checkAvatar($user, $profile);
- return $profile->id;
-
- } else {
- common_debug($this->name() . ' - Adding profile and remote profile ' .
- "for Twitter user: $profileurl.");
-
- $profile = new Profile();
- $profile->query("BEGIN");
-
- $profile->nickname = $user->screen_name;
- $profile->fullname = $user->name;
- $profile->homepage = $user->url;
- $profile->bio = $user->description;
- $profile->location = $user->location;
- $profile->profileurl = $profileurl;
- $profile->created = common_sql_now();
-
- $id = $profile->insert();
-
- if (empty($id)) {
- common_log_db_error($profile, 'INSERT', __FILE__);
- $profile->query("ROLLBACK");
- return false;
- }
-
- // check for remote profile
-
- $remote_pro = Remote_profile::staticGet('uri', $profileurl);
-
- if (empty($remote_pro)) {
-
- $remote_pro = new Remote_profile();
-
- $remote_pro->id = $id;
- $remote_pro->uri = $profileurl;
- $remote_pro->created = common_sql_now();
-
- $rid = $remote_pro->insert();
-
- if (empty($rid)) {
- common_log_db_error($profile, 'INSERT', __FILE__);
- $profile->query("ROLLBACK");
- return false;
- }
- }
-
- $profile->query("COMMIT");
-
- $this->saveAvatars($user, $id);
-
- return $id;
- }
- }
-
- function checkAvatar($twitter_user, $profile)
- {
- global $config;
-
- $path_parts = pathinfo($twitter_user->profile_image_url);
-
- $newname = 'Twitter_' . $twitter_user->id . '_' .
- $path_parts['basename'];
-
- $oldname = $profile->getAvatar(48)->filename;
-
- if ($newname != $oldname) {
- common_debug($this->name() . ' - Avatar for Twitter user ' .
- "$profile->nickname has changed.");
- common_debug($this->name() . " - old: $oldname new: $newname");
-
- $this->updateAvatars($twitter_user, $profile);
- }
-
- if ($this->missingAvatarFile($profile)) {
- common_debug($this->name() . ' - Twitter user ' .
- $profile->nickname .
- ' is missing one or more local avatars.');
- common_debug($this->name() ." - old: $oldname new: $newname");
-
- $this->updateAvatars($twitter_user, $profile);
- }
-
- }
-
- function updateAvatars($twitter_user, $profile) {
-
- global $config;
-
- $path_parts = pathinfo($twitter_user->profile_image_url);
-
- $img_root = substr($path_parts['basename'], 0, -11);
- $ext = $path_parts['extension'];
- $mediatype = $this->getMediatype($ext);
-
- foreach (array('mini', 'normal', 'bigger') as $size) {
- $url = $path_parts['dirname'] . '/' .
- $img_root . '_' . $size . ".$ext";
- $filename = 'Twitter_' . $twitter_user->id . '_' .
- $img_root . "_$size.$ext";
-
- $this->updateAvatar($profile->id, $size, $mediatype, $filename);
- $this->fetchAvatar($url, $filename);
- }
- }
-
- function missingAvatarFile($profile) {
-
- foreach (array(24, 48, 73) as $size) {
-
- $filename = $profile->getAvatar($size)->filename;
- $avatarpath = Avatar::path($filename);
-
- if (file_exists($avatarpath) == FALSE) {
- return true;
- }
- }
-
- return false;
- }
-
- function getMediatype($ext)
- {
- $mediatype = null;
-
- switch (strtolower($ext)) {
- case 'jpg':
- $mediatype = 'image/jpg';
- break;
- case 'gif':
- $mediatype = 'image/gif';
- break;
- default:
- $mediatype = 'image/png';
- }
-
- return $mediatype;
- }
-
- function saveAvatars($user, $id)
- {
- global $config;
-
- $path_parts = pathinfo($user->profile_image_url);
- $ext = $path_parts['extension'];
- $end = strlen('_normal' . $ext);
- $img_root = substr($path_parts['basename'], 0, -($end+1));
- $mediatype = $this->getMediatype($ext);
-
- foreach (array('mini', 'normal', 'bigger') as $size) {
- $url = $path_parts['dirname'] . '/' .
- $img_root . '_' . $size . ".$ext";
- $filename = 'Twitter_' . $user->id . '_' .
- $img_root . "_$size.$ext";
-
- if ($this->fetchAvatar($url, $filename)) {
- $this->newAvatar($id, $size, $mediatype, $filename);
- } else {
- common_log(LOG_WARNING, $this->id() .
- " - Problem fetching Avatar: $url");
- }
- }
- }
-
- function updateAvatar($profile_id, $size, $mediatype, $filename) {
-
- common_debug($this->name() . " - Updating avatar: $size");
-
- $profile = Profile::staticGet($profile_id);
-
- if (empty($profile)) {
- common_debug($this->name() . " - Couldn't get profile: $profile_id!");
- return;
- }
-
- $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
- $avatar = $profile->getAvatar($sizes[$size]);
-
- // Delete the avatar, if present
-
- if ($avatar) {
- $avatar->delete();
- }
-
- $this->newAvatar($profile->id, $size, $mediatype, $filename);
- }
-
- function newAvatar($profile_id, $size, $mediatype, $filename)
- {
- global $config;
-
- $avatar = new Avatar();
- $avatar->profile_id = $profile_id;
-
- switch($size) {
- case 'mini':
- $avatar->width = 24;
- $avatar->height = 24;
- break;
- case 'normal':
- $avatar->width = 48;
- $avatar->height = 48;
- break;
- default:
-
- // Note: Twitter's big avatars are a different size than
- // Laconica's (Laconica's = 96)
-
- $avatar->width = 73;
- $avatar->height = 73;
- }
-
- $avatar->original = 0; // we don't have the original
- $avatar->mediatype = $mediatype;
- $avatar->filename = $filename;
- $avatar->url = Avatar::url($filename);
-
- common_debug($this->name() . " - New filename: $avatar->url");
-
- $avatar->created = common_sql_now();
-
- $id = $avatar->insert();
-
- if (empty($id)) {
- common_log_db_error($avatar, 'INSERT', __FILE__);
- return null;
- }
-
- common_debug($this->name() .
- " - Saved new $size avatar for $profile_id.");
-
- return $id;
- }
-
- function fetchAvatar($url, $filename)
- {
- $avatar_dir = INSTALLDIR . '/avatar/';
-
- $avatarfile = $avatar_dir . $filename;
-
- $out = fopen($avatarfile, 'wb');
- if (!$out) {
- common_log(LOG_WARNING, $this->name() .
- " - Couldn't open file $filename");
- return false;
- }
-
- common_debug($this->name() . " - Fetching Twitter avatar: $url");
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_FILE, $out);
- curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
- $result = curl_exec($ch);
- curl_close($ch);
-
- fclose($out);
-
- return $result;
- }
-}
-
-$id = null;
-$debug = null;
-
-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;
-}
-
-if (have_option('d') || have_option('debug')) {
- $debug = true;
-}
-
-$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug);
-$fetcher->runOnce();
-
--
cgit v1.2.3-54-g00ecf
From 72a60d63814188c7e282d678e281070070be5df2 Mon Sep 17 00:00:00 2001
From: Craig Andrews
Date: Fri, 28 Aug 2009 14:43:31 -0400
Subject: Added a PubSubHubBub plugin
---
plugins/PubSubHubBub/PubSubHubBubPlugin.php | 122 ++++++++++++++++++++++++++++
plugins/PubSubHubBub/publisher.php | 86 ++++++++++++++++++++
2 files changed, 208 insertions(+)
create mode 100644 plugins/PubSubHubBub/PubSubHubBubPlugin.php
create mode 100644 plugins/PubSubHubBub/publisher.php
(limited to 'plugins')
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
new file mode 100644
index 000000000..013a234d7
--- /dev/null
+++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
@@ -0,0 +1,122 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.com
+ * @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);
+}
+
+define('DEFAULT_HUB','http://2pubsubhubbub.appspot.com');
+
+require_once(INSTALLDIR.'/plugins/PubSubHubBub/publisher.php');
+
+class PubSubHubBubPlugin extends Plugin
+{
+ private $hub;
+
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onInitializePlugin(){
+ $this->hub = common_config('PubSubHubBub', 'hub');
+ if(empty($this->hub)){
+ $this->hub = DEFAULT_HUB;
+ }
+ }
+
+ function onStartApiAtom($action){
+ $action->element('link',array('rel'=>'hub','href'=>$this->hub),null);
+ }
+
+ function onStartApiRss($action){
+ $action->element('atom:link',array('rel'=>'hub','href'=>$this->hub),null);
+ }
+
+ function onEndNoticeSave($notice){
+ $publisher = new Publisher($this->hub);
+
+ $feeds = array();
+
+ //public timeline feeds
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'public_timeline.rss'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'public_timeline.atom'));
+
+ //author's own feeds
+ $user = User::staticGet('id',$notice->profile_id);
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
+
+ //tag feeds
+ $tag = new Notice_tag();
+ $tag->notice_id = $notice->id;
+ if ($tag->find()) {
+ while ($tag->fetch()) {
+ $feeds[]=common_local_url('api',array('apiaction' => 'tags','method' => 'timeline', 'argument'=>$tag->tag.'.atom'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'tags','method' => 'timeline', 'argument'=>$tag->tag.'.rss'));
+ }
+ }
+
+ //group feeds
+ $group_inbox = new Group_inbox();
+ $group_inbox->notice_id = $notice->id;
+ if ($group_inbox->find()) {
+ while ($group_inbox->fetch()) {
+ $group = User_group::staticGet('id',$group_inbox->group_id);
+ $feeds[]=common_local_url('api',array('apiaction' => 'groups','method' => 'timeline','argument' => $group->nickname.'.rss'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'groups','method' => 'timeline','argument' => $group->nickname.'.atom'));
+ }
+ }
+
+ //feed of each user that subscribes to the notice's author
+ $notice_inbox = new Notice_inbox();
+ $notice_inbox->notice_id = $notice->id;
+ if ($notice_inbox->find()) {
+ while ($notice_inbox->fetch()) {
+ $user = User::staticGet('id',$notice_inbox->user_id);
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
+ }
+ }
+
+ /* TODO: when the reply page gets RSS and ATOM feeds, implement this
+ //feed of user replied to
+ if($notice->reply_to){
+ $user = User::staticGet('id',$notice->reply_to);
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
+ $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
+ }*/
+
+ foreach(array_unique($feeds) as $feed){
+ if(! $publisher->publish_update($feed)){
+ common_log_line(LOG_WARNING,$feed.' was not published to hub at '.$this->hub.':'.$publisher->last_response());
+ }
+ }
+ }
+}
diff --git a/plugins/PubSubHubBub/publisher.php b/plugins/PubSubHubBub/publisher.php
new file mode 100644
index 000000000..f176a9b8a
--- /dev/null
+++ b/plugins/PubSubHubBub/publisher.php
@@ -0,0 +1,86 @@
+hub_url = $hub_url;
+ }
+
+ // accepts either a single url or an array of urls
+ public function publish_update($topic_urls, $http_function = false) {
+ if (!isset($topic_urls))
+ throw new Exception('Please specify a topic url');
+
+ // check that we're working with an array
+ if (!is_array($topic_urls)) {
+ $topic_urls = array($topic_urls);
+ }
+
+ // set the mode to publish
+ $post_string = "hub.mode=publish";
+ // loop through each topic url
+ foreach ($topic_urls as $topic_url) {
+
+ // lightweight check that we're actually working w/ a valid url
+ if (!preg_match("|^https?://|i",$topic_url))
+ throw new Exception('The specified topic url does not appear to be valid: '.$topic_url);
+
+ // append the topic url parameters
+ $post_string .= "&hub.url=".urlencode($topic_url);
+ }
+
+ // make the http post request and return true/false
+ // easy to over-write to use your own http function
+ if ($http_function)
+ return $http_function($this->hub_url,$post_string);
+ else
+ return $this->http_post($this->hub_url,$post_string);
+ }
+
+ // returns any error message from the latest request
+ public function last_response() {
+ return $this->last_response;
+ }
+
+ // default http function that uses curl to post to the hub endpoint
+ private function http_post($url, $post_string) {
+
+ // add any additional curl options here
+ $options = array(CURLOPT_URL => $url,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $post_string,
+ CURLOPT_USERAGENT => "PubSubHubbub-Publisher-PHP/1.0");
+
+ $ch = curl_init();
+ curl_setopt_array($ch, $options);
+
+ $response = curl_exec($ch);
+ $this->last_response = $response;
+ $info = curl_getinfo($ch);
+
+ curl_close($ch);
+
+ // all good
+ if ($info['http_code'] == 204)
+ return true;
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
--
cgit v1.2.3-54-g00ecf
From beae3db41375879e725af053edf8041bbd76ac8c Mon Sep 17 00:00:00 2001
From: Craig Andrews
Date: Thu, 3 Sep 2009 14:58:50 -0400
Subject: Pluginize the URL shorteners
---
actions/othersettings.php | 27 ++++++------
lib/Shorturl_api.php | 66 ++---------------------------
lib/plugin.php | 14 +++++++
lib/util.php | 56 ++++---------------------
plugins/LilUrl/LilUrlPlugin.php | 64 ++++++++++++++++++++++++++++
plugins/PtitUrl/PtitUrlPlugin.php | 62 +++++++++++++++++++++++++++
plugins/SimpleUrl/SimpleUrlPlugin.php | 79 +++++++++++++++++++++++++++++++++++
plugins/TightUrl/TightUrlPlugin.php | 62 +++++++++++++++++++++++++++
8 files changed, 305 insertions(+), 125 deletions(-)
create mode 100644 plugins/LilUrl/LilUrlPlugin.php
create mode 100644 plugins/PtitUrl/PtitUrlPlugin.php
create mode 100644 plugins/SimpleUrl/SimpleUrlPlugin.php
create mode 100644 plugins/TightUrl/TightUrlPlugin.php
(limited to 'plugins')
diff --git a/actions/othersettings.php b/actions/othersettings.php
index 8b674161a..4ccef8412 100644
--- a/actions/othersettings.php
+++ b/actions/othersettings.php
@@ -91,19 +91,20 @@ class OthersettingsAction extends AccountSettingsAction
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
- // I18N
-
- $services = array(
- '' => 'None',
- 'ur1.ca' => 'ur1.ca (free service)',
- '2tu.us' => '2tu.us (free service)',
- 'ptiturl.com' => 'ptiturl.com',
- 'bit.ly' => 'bit.ly',
- 'tinyurl.com' => 'tinyurl.com',
- 'is.gd' => 'is.gd',
- 'snipr.com' => 'snipr.com',
- 'metamark.net' => 'metamark.net'
- );
+ $services=array();
+ global $_shorteners;
+ if($_shorteners){
+ foreach($_shorteners as $name=>$value)
+ {
+ $services[$name]=$name;
+ if($value['info']['freeService']){
+ // I18N
+ $services[$name].=' (free service)';
+ }
+ }
+ }
+ asort($services);
+ $services['']='None';
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
index 6402dbc09..18ae7719b 100644
--- a/lib/Shorturl_api.php
+++ b/lib/Shorturl_api.php
@@ -19,7 +19,7 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-class ShortUrlApi
+abstract class ShortUrlApi
{
protected $service_url;
protected $long_limit = 27;
@@ -35,11 +35,9 @@ class ShortUrlApi
return $url;
}
- protected function shorten_imp($url) {
- return "To Override";
- }
+ protected abstract function shorten_imp($url);
- private function is_long($url) {
+ protected function is_long($url) {
return strlen($url) >= common_config('site', 'shorturllength');
}
@@ -71,61 +69,3 @@ class ShortUrlApi
}
}
-class LilUrl extends ShortUrlApi
-{
- function __construct()
- {
- parent::__construct('http://ur1.ca/');
- }
-
- protected function shorten_imp($url) {
- $data['longurl'] = $url;
- $response = $this->http_post($data);
- if (!$response) return $url;
- $y = @simplexml_load_string($response);
- if (!isset($y->body)) return $url;
- $x = $y->body->p[0]->a->attributes();
- if (isset($x['href'])) return $x['href'];
- return $url;
- }
-}
-
-
-class PtitUrl extends ShortUrlApi
-{
- function __construct()
- {
- parent::__construct('http://ptiturl.com/?creer=oui&action=Reduire&url=');
- }
-
- protected function shorten_imp($url) {
- $response = $this->http_get($url);
- if (!$response) return $url;
- $response = $this->tidy($response);
- $y = @simplexml_load_string($response);
- if (!isset($y->body)) return $url;
- $xml = $y->body->center->table->tr->td->pre->a->attributes();
- if (isset($xml['href'])) return $xml['href'];
- return $url;
- }
-}
-
-class TightUrl extends ShortUrlApi
-{
- function __construct()
- {
- parent::__construct('http://2tu.us/?save=y&url=');
- }
-
- protected function shorten_imp($url) {
- $response = $this->http_get($url);
- if (!$response) return $url;
- $response = $this->tidy($response);
- $y = @simplexml_load_string($response);
- if (!isset($y->body)) return $url;
- $xml = $y->body->p[0]->code[0]->a->attributes();
- if (isset($xml['href'])) return $xml['href'];
- return $url;
- }
-}
-
diff --git a/lib/plugin.php b/lib/plugin.php
index 87d7be5a7..59bf3ba9d 100644
--- a/lib/plugin.php
+++ b/lib/plugin.php
@@ -76,4 +76,18 @@ class Plugin
{
return true;
}
+
+ /*
+ * the name of the shortener
+ * shortenerInfo associative array with additional information. One possible element is 'freeService' which can be true or false
+ * shortener array, first element is the name of the class, second element is an array to be passed as constructor parameters to the class
+ */
+ function registerUrlShortener($name, $shortenerInfo, $shortener)
+ {
+ global $_shorteners;
+ if(!is_array($_shorteners)){
+ $_shorteners=array();
+ }
+ $_shorteners[$name]=array('info'=>$shortenerInfo, 'callInfo'=>$shortener);
+ }
}
diff --git a/lib/util.php b/lib/util.php
index 9cf462515..0a25907c4 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -1373,57 +1373,15 @@ function common_shorten_url($long_url)
} else {
$svc = $user->urlshorteningservice;
}
-
- $curlh = curl_init();
- curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
- curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet');
- curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
- switch($svc) {
- case 'ur1.ca':
- require_once INSTALLDIR.'/lib/Shorturl_api.php';
- $short_url_service = new LilUrl;
- $short_url = $short_url_service->shorten($long_url);
- break;
-
- case '2tu.us':
- $short_url_service = new TightUrl;
- require_once INSTALLDIR.'/lib/Shorturl_api.php';
- $short_url = $short_url_service->shorten($long_url);
- break;
-
- case 'ptiturl.com':
- require_once INSTALLDIR.'/lib/Shorturl_api.php';
- $short_url_service = new PtitUrl;
- $short_url = $short_url_service->shorten($long_url);
- break;
-
- case 'bit.ly':
- curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url));
- $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
- break;
-
- case 'is.gd':
- curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url));
- $short_url = curl_exec($curlh);
- break;
- case 'snipr.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
- $short_url = curl_exec($curlh);
- break;
- case 'metamark.net':
- curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
- $short_url = curl_exec($curlh);
- break;
- case 'tinyurl.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
- $short_url = curl_exec($curlh);
- break;
- default:
- $short_url = false;
+ global $_shorteners;
+ if(! $_shorteners[$svc]){
+ //the user selected service doesn't exist, so default to ur1.ca
+ $svc = 'ur1.ca';
}
- curl_close($curlh);
+ $reflectionObj = new ReflectionClass($_shorteners[$svc]['callInfo'][0]);
+ $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]);
+ $short_url = $short_url_service->shorten($long_url);
return $short_url;
}
diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php
new file mode 100644
index 000000000..7665b6c1e
--- /dev/null
+++ b/plugins/LilUrl/LilUrlPlugin.php
@@ -0,0 +1,64 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.com
+ * @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);
+}
+
+require_once(INSTALLDIR.'/lib/Shorturl_api.php');
+
+class LilUrlPlugin extends Plugin
+{
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onInitializePlugin(){
+ $this->registerUrlShortener(
+ 'ur1.ca',
+ array('freeService'=>true),
+ array('LilUrl',array('http://ur1.ca/'))
+ );
+ }
+}
+
+class LilUrl extends ShortUrlApi
+{
+ protected function shorten_imp($url) {
+ $data['longurl'] = $url;
+ $response = $this->http_post($data);
+ if (!$response) return $url;
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $x = $y->body->p[0]->a->attributes();
+ if (isset($x['href'])) return $x['href'];
+ return $url;
+ }
+}
diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php
new file mode 100644
index 000000000..f00d3e2f2
--- /dev/null
+++ b/plugins/PtitUrl/PtitUrlPlugin.php
@@ -0,0 +1,62 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.com
+ * @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);
+}
+
+class PtitUrlPlugin extends Plugin
+{
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onInitializePlugin(){
+ $this->registerUrlShortener(
+ 'ptiturl.com',
+ array(),
+ array('PtitUrl',array('http://ptiturl.com/?creer=oui&action=Reduire&url='))
+ );
+ }
+}
+
+class PtitUrl extends ShortUrlApi
+{
+ protected function shorten_imp($url) {
+ $response = $this->http_get($url);
+ if (!$response) return $url;
+ $response = $this->tidy($response);
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $xml = $y->body->center->table->tr->td->pre->a->attributes();
+ if (isset($xml['href'])) return $xml['href'];
+ return $url;
+ }
+}
diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php
new file mode 100644
index 000000000..82d772048
--- /dev/null
+++ b/plugins/SimpleUrl/SimpleUrlPlugin.php
@@ -0,0 +1,79 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.com
+ * @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);
+}
+
+class SimpleUrlPlugin extends Plugin
+{
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onInitializePlugin(){
+ $this->registerUrlShortener(
+ 'is.gd',
+ array(),
+ array('SimpleUrl',array('http://is.gd/api.php?longurl='))
+ );
+ $this->registerUrlShortener(
+ 'snipr.com',
+ array(),
+ array('SimpleUrl',array('http://snipr.com/site/snip?r=simple&link='))
+ );
+ $this->registerUrlShortener(
+ 'metamark.net',
+ array(),
+ array('SimpleUrl',array('http://metamark.net/api/rest/simple?long_url='))
+ );
+ $this->registerUrlShortener(
+ 'tinyurl.com',
+ array(),
+ array('SimpleUrl',array('http://tinyurl.com/api-create.php?url='))
+ );
+ }
+}
+
+class SimpleUrl extends ShortUrlApi
+{
+ protected function shorten_imp($url) {
+ $curlh = curl_init();
+ curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
+ curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet');
+ curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
+
+ curl_setopt($curlh, CURLOPT_URL, $this->service_url.urlencode($url));
+ $short_url = curl_exec($curlh);
+
+ curl_close($curlh);
+ return $short_url;
+ }
+}
diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php
new file mode 100644
index 000000000..48efb355f
--- /dev/null
+++ b/plugins/TightUrl/TightUrlPlugin.php
@@ -0,0 +1,62 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.com
+ * @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);
+}
+
+class TightUrlPlugin extends Plugin
+{
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onInitializePlugin(){
+ $this->registerUrlShortener(
+ '2tu.us',
+ array('freeService'=>true),
+ array('TightUrl',array('http://2tu.us/?save=y&url='))
+ );
+ }
+}
+
+class TightUrl extends ShortUrlApi
+{
+ protected function shorten_imp($url) {
+ $response = $this->http_get($url);
+ if (!$response) return $url;
+ $response = $this->tidy($response);
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $xml = $y->body->p[0]->code[0]->a->attributes();
+ if (isset($xml['href'])) return $xml['href'];
+ return $url;
+ }
+}
--
cgit v1.2.3-54-g00ecf
From 876f56254d3ccdcc7ba3e9c7693345cef8a6e22b Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Tue, 8 Sep 2009 16:07:01 -0700
Subject: Moved basic auth client into plugin dir
---
lib/twitterbasicauthclient.php | 236 -----------------------
plugins/TwitterBridge/twitterbasicauthclient.php | 236 +++++++++++++++++++++++
2 files changed, 236 insertions(+), 236 deletions(-)
delete mode 100644 lib/twitterbasicauthclient.php
create mode 100644 plugins/TwitterBridge/twitterbasicauthclient.php
(limited to 'plugins')
diff --git a/lib/twitterbasicauthclient.php b/lib/twitterbasicauthclient.php
deleted file mode 100644
index fd331fbdc..000000000
--- a/lib/twitterbasicauthclient.php
+++ /dev/null
@@ -1,236 +0,0 @@
-.
- *
- * @category Integration
- * @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') && !defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Exception wrapper for cURL errors
- *
- * @category Integration
- * @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 BasicAuthCurlException extends Exception
-{
-}
-
-/**
- * Class for talking to the Twitter API with HTTP Basic Auth.
- *
- * @category Integration
- * @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 TwitterBasicAuthClient
-{
- var $screen_name = null;
- var $password = null;
-
- /**
- * constructor
- *
- * @param Foreign_link $flink a Foreign_link storing the
- * Twitter user's password, etc.
- */
- function __construct($flink)
- {
- $fuser = $flink->getForeignUser();
- $this->screen_name = $fuser->nickname;
- $this->password = $flink->credentials;
- }
-
- /**
- * Calls Twitter's /statuses/update API method
- *
- * @param string $status text of the status
- * @param int $in_reply_to_status_id optional id of the status it's
- * a reply to
- *
- * @return mixed the status
- */
- function statusesUpdate($status, $in_reply_to_status_id = null)
- {
- $url = 'https://twitter.com/statuses/update.json';
- $params = array('status' => $status,
- 'source' => common_config('integration', 'source'),
- 'in_reply_to_status_id' => $in_reply_to_status_id);
- $response = $this->httpRequest($url, $params);
- $status = json_decode($response);
- return $status;
- }
-
- /**
- * Calls Twitter's /statuses/friends_timeline API method
- *
- * @param int $since_id show statuses after this id
- * @param int $max_id show statuses before this id
- * @param int $cnt number of statuses to show
- * @param int $page page number
- *
- * @return mixed an array of statuses
- */
- function statusesFriendsTimeline($since_id = null, $max_id = null,
- $cnt = null, $page = null)
- {
- $url = 'https://twitter.com/statuses/friends_timeline.json';
- $params = array('since_id' => $since_id,
- 'max_id' => $max_id,
- 'count' => $cnt,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->httpRequest($url);
- $statuses = json_decode($response);
- return $statuses;
- }
-
- /**
- * Calls Twitter's /statuses/friends API method
- *
- * @param int $id id of the user whom you wish to see friends of
- * @param int $user_id numerical user id
- * @param int $screen_name screen name
- * @param int $page page number
- *
- * @return mixed an array of twitter users and their latest status
- */
- function statusesFriends($id = null, $user_id = null, $screen_name = null,
- $page = null)
- {
- $url = "https://twitter.com/statuses/friends.json";
-
- $params = array('id' => $id,
- 'user_id' => $user_id,
- 'screen_name' => $screen_name,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->httpRequest($url);
- $friends = json_decode($response);
- return $friends;
- }
-
- /**
- * Calls Twitter's /statuses/friends/ids API method
- *
- * @param int $id id of the user whom you wish to see friends of
- * @param int $user_id numerical user id
- * @param int $screen_name screen name
- * @param int $page page number
- *
- * @return mixed a list of ids, 100 per page
- */
- function friendsIds($id = null, $user_id = null, $screen_name = null,
- $page = null)
- {
- $url = "https://twitter.com/friends/ids.json";
-
- $params = array('id' => $id,
- 'user_id' => $user_id,
- 'screen_name' => $screen_name,
- 'page' => $page);
- $qry = http_build_query($params);
-
- if (!empty($qry)) {
- $url .= "?$qry";
- }
-
- $response = $this->httpRequest($url);
- $ids = json_decode($response);
- return $ids;
- }
-
- /**
- * Make a HTTP request using cURL.
- *
- * @param string $url Where to make the request
- * @param array $params post parameters
- *
- * @return mixed the request
- */
- function httpRequest($url, $params = null, $auth = true)
- {
- $options = array(
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_FAILONERROR => true,
- CURLOPT_HEADER => false,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_USERAGENT => 'StatusNet',
- CURLOPT_CONNECTTIMEOUT => 120,
- CURLOPT_TIMEOUT => 120,
- CURLOPT_HTTPAUTH => CURLAUTH_ANY,
- CURLOPT_SSL_VERIFYPEER => false,
-
- // Twitter is strict about accepting invalid "Expect" headers
-
- CURLOPT_HTTPHEADER => array('Expect:')
- );
-
- if (isset($params)) {
- $options[CURLOPT_POST] = true;
- $options[CURLOPT_POSTFIELDS] = $params;
- }
-
- if ($auth) {
- $options[CURLOPT_USERPWD] = $this->screen_name .
- ':' . $this->password;
- }
-
- $ch = curl_init($url);
- curl_setopt_array($ch, $options);
- $response = curl_exec($ch);
-
- if ($response === false) {
- $msg = curl_error($ch);
- $code = curl_errno($ch);
- throw new BasicAuthCurlException($msg, $code);
- }
-
- curl_close($ch);
-
- return $response;
- }
-
-}
diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php
new file mode 100644
index 000000000..fd331fbdc
--- /dev/null
+++ b/plugins/TwitterBridge/twitterbasicauthclient.php
@@ -0,0 +1,236 @@
+.
+ *
+ * @category Integration
+ * @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') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Exception wrapper for cURL errors
+ *
+ * @category Integration
+ * @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 BasicAuthCurlException extends Exception
+{
+}
+
+/**
+ * Class for talking to the Twitter API with HTTP Basic Auth.
+ *
+ * @category Integration
+ * @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 TwitterBasicAuthClient
+{
+ var $screen_name = null;
+ var $password = null;
+
+ /**
+ * constructor
+ *
+ * @param Foreign_link $flink a Foreign_link storing the
+ * Twitter user's password, etc.
+ */
+ function __construct($flink)
+ {
+ $fuser = $flink->getForeignUser();
+ $this->screen_name = $fuser->nickname;
+ $this->password = $flink->credentials;
+ }
+
+ /**
+ * Calls Twitter's /statuses/update API method
+ *
+ * @param string $status text of the status
+ * @param int $in_reply_to_status_id optional id of the status it's
+ * a reply to
+ *
+ * @return mixed the status
+ */
+ function statusesUpdate($status, $in_reply_to_status_id = null)
+ {
+ $url = 'https://twitter.com/statuses/update.json';
+ $params = array('status' => $status,
+ 'source' => common_config('integration', 'source'),
+ 'in_reply_to_status_id' => $in_reply_to_status_id);
+ $response = $this->httpRequest($url, $params);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /statuses/friends_timeline API method
+ *
+ * @param int $since_id show statuses after this id
+ * @param int $max_id show statuses before this id
+ * @param int $cnt number of statuses to show
+ * @param int $page page number
+ *
+ * @return mixed an array of statuses
+ */
+ function statusesFriendsTimeline($since_id = null, $max_id = null,
+ $cnt = null, $page = null)
+ {
+ $url = 'https://twitter.com/statuses/friends_timeline.json';
+ $params = array('since_id' => $since_id,
+ 'max_id' => $max_id,
+ 'count' => $cnt,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->httpRequest($url);
+ $statuses = json_decode($response);
+ return $statuses;
+ }
+
+ /**
+ * Calls Twitter's /statuses/friends API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed an array of twitter users and their latest status
+ */
+ function statusesFriends($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/statuses/friends.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->httpRequest($url);
+ $friends = json_decode($response);
+ return $friends;
+ }
+
+ /**
+ * Calls Twitter's /statuses/friends/ids API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed a list of ids, 100 per page
+ */
+ function friendsIds($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/friends/ids.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->httpRequest($url);
+ $ids = json_decode($response);
+ return $ids;
+ }
+
+ /**
+ * Make a HTTP request using cURL.
+ *
+ * @param string $url Where to make the request
+ * @param array $params post parameters
+ *
+ * @return mixed the request
+ */
+ function httpRequest($url, $params = null, $auth = true)
+ {
+ $options = array(
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_USERAGENT => 'StatusNet',
+ CURLOPT_CONNECTTIMEOUT => 120,
+ CURLOPT_TIMEOUT => 120,
+ CURLOPT_HTTPAUTH => CURLAUTH_ANY,
+ CURLOPT_SSL_VERIFYPEER => false,
+
+ // Twitter is strict about accepting invalid "Expect" headers
+
+ CURLOPT_HTTPHEADER => array('Expect:')
+ );
+
+ if (isset($params)) {
+ $options[CURLOPT_POST] = true;
+ $options[CURLOPT_POSTFIELDS] = $params;
+ }
+
+ if ($auth) {
+ $options[CURLOPT_USERPWD] = $this->screen_name .
+ ':' . $this->password;
+ }
+
+ $ch = curl_init($url);
+ curl_setopt_array($ch, $options);
+ $response = curl_exec($ch);
+
+ if ($response === false) {
+ $msg = curl_error($ch);
+ $code = curl_errno($ch);
+ throw new BasicAuthCurlException($msg, $code);
+ }
+
+ curl_close($ch);
+
+ return $response;
+ }
+
+}
--
cgit v1.2.3-54-g00ecf
From 99c74f2cc4b2a2c62d4188c862fec96caec44c06 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 15 Sep 2009 21:22:15 -0400
Subject: statusize OpenID plugin
---
plugins/OpenID/OpenIDPlugin.php | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
(limited to 'plugins')
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index eb450fc5e..91bddf381 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -1,6 +1,6 @@
.
*
* @category Plugin
- * @package Laconica
- * @author Evan Prodromou
- * @copyright 2009 Control Yourself, Inc.
+ * @package StatusNet
+ * @author Evan Prodromou
+ * @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://laconi.ca/
+ * @link http://status.net/
*/
-if (!defined('LACONICA')) {
+if (!defined('STATUSNET')) {
exit(1);
}
@@ -36,10 +36,10 @@ if (!defined('LACONICA')) {
* and identity system.
*
* @category Plugin
- * @package Laconica
- * @author Evan Prodromou
+ * @package StatusNet
+ * @author Evan Prodromou
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
+ * @link http://status.net/
* @link http://openid.net/
*/
--
cgit v1.2.3-54-g00ecf
From 3bdf8423c408734ebb5ba614360e8b9048ff0039 Mon Sep 17 00:00:00 2001
From: Craig Andrews
Date: Mon, 21 Sep 2009 16:29:22 -0400
Subject: Fix typo is default hub url
---
plugins/PubSubHubBub/PubSubHubBubPlugin.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'plugins')
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
index 013a234d7..d7061ac0e 100644
--- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php
+++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
@@ -31,7 +31,7 @@ if (!defined('STATUSNET')) {
exit(1);
}
-define('DEFAULT_HUB','http://2pubsubhubbub.appspot.com');
+define('DEFAULT_HUB','http://pubsubhubbub.appspot.com');
require_once(INSTALLDIR.'/plugins/PubSubHubBub/publisher.php');
--
cgit v1.2.3-54-g00ecf
From 5cccbe987f9003ec4c40dbdcce254fe04069b107 Mon Sep 17 00:00:00 2001
From: Craig Andrews
Date: Mon, 21 Sep 2009 16:33:15 -0400
Subject: Use new queue handler event
---
plugins/PubSubHubBub/PubSubHubBubPlugin.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'plugins')
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
index d7061ac0e..e1e82e352 100644
--- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php
+++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
@@ -59,7 +59,7 @@ class PubSubHubBubPlugin extends Plugin
$action->element('atom:link',array('rel'=>'hub','href'=>$this->hub),null);
}
- function onEndNoticeSave($notice){
+ function onHandleQueuedNotice($notice){
$publisher = new Publisher($this->hub);
$feeds = array();
--
cgit v1.2.3-54-g00ecf
From 267ab67c8325243d176e1124f2024c7b08417f37 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Wed, 30 Sep 2009 12:22:55 +0000
Subject: Script no longer needed for Realtime plugin
---
plugins/Realtime/jquery.getUrlParam.js | 72 ----------------------------------
1 file changed, 72 deletions(-)
delete mode 100644 plugins/Realtime/jquery.getUrlParam.js
(limited to 'plugins')
diff --git a/plugins/Realtime/jquery.getUrlParam.js b/plugins/Realtime/jquery.getUrlParam.js
deleted file mode 100644
index e8f73eb47..000000000
--- a/plugins/Realtime/jquery.getUrlParam.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Copyright (c) 2006-2007 Mathias Bank (http://www.mathias-bank.de)
- * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
- * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
- *
- * Version 2.1
- *
- * Thanks to
- * Hinnerk Ruemenapf - http://hinnerk.ruemenapf.de/ for bug reporting and fixing.
- * Tom Leonard for some improvements
- *
- */
-jQuery.fn.extend({
-/**
-* Returns get parameters.
-*
-* If the desired param does not exist, null will be returned
-*
-* To get the document params:
-* @example value = $(document).getUrlParam("paramName");
-*
-* To get the params of a html-attribut (uses src attribute)
-* @example value = $('#imgLink').getUrlParam("paramName");
-*/
- getUrlParam: function(strParamName){
- strParamName = escape(unescape(strParamName));
-
- var returnVal = new Array();
- var qString = null;
-
- if ($(this).attr("nodeName")=="#document") {
- //document-handler
-
- if (window.location.search.search(strParamName) > -1 ){
-
- qString = window.location.search.substr(1,window.location.search.length).split("&");
- }
-
- } else if ($(this).attr("src")!="undefined") {
-
- var strHref = $(this).attr("src")
- if ( strHref.indexOf("?") > -1 ){
- var strQueryString = strHref.substr(strHref.indexOf("?")+1);
- qString = strQueryString.split("&");
- }
- } else if ($(this).attr("href")!="undefined") {
-
- var strHref = $(this).attr("href")
- if ( strHref.indexOf("?") > -1 ){
- var strQueryString = strHref.substr(strHref.indexOf("?")+1);
- qString = strQueryString.split("&");
- }
- } else {
- return null;
- }
-
-
- if (qString==null) return null;
-
-
- for (var i=0;i
Date: Wed, 30 Sep 2009 12:48:15 +0000
Subject: Relocated the button for pop up window for notice stream
---
plugins/Realtime/realtimeupdate.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
(limited to 'plugins')
diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js
index 4cd68a816..3303d3a44 100644
--- a/plugins/Realtime/realtimeupdate.js
+++ b/plugins/Realtime/realtimeupdate.js
@@ -125,14 +125,17 @@ RealtimeUpdate = {
addPopup: function(url, timeline, iconurl)
{
- $('#content').prepend('');
+ $('#notices_primary').css({'position':'relative'});
+ $('#notices_primary').prepend('');
$('#realtime_timeline').css({
- 'margin':'0 0 18px 0',
+ 'margin':'0 0 11px 0',
'background':'transparent url('+ iconurl + ') no-repeat 0% 30%',
'padding':'0 0 0 20px',
'display':'block',
- 'float':'right',
+ 'position':'absolute',
+ 'top':'-20px',
+ 'right':'0',
'border':'none',
'cursor':'pointer',
'color':$("a").css("color"),
--
cgit v1.2.3-54-g00ecf
From a57783de0214f061eca3ab65880f573e8668de03 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Wed, 30 Sep 2009 13:29:37 +0000
Subject: Timeout a little incase the notice item from XHR response is not
appended to the page.
---
plugins/Realtime/realtimeupdate.js | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
(limited to 'plugins')
diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js
index 3303d3a44..11e466325 100644
--- a/plugins/Realtime/realtimeupdate.js
+++ b/plugins/Realtime/realtimeupdate.js
@@ -30,19 +30,20 @@ RealtimeUpdate = {
receive: function(data)
{
- id = data.id;
-
- // Don't add it if it already exists
- //
- if ($("#notice-"+id).length > 0) {
- return;
- }
-
- var noticeItem = RealtimeUpdate.makeNoticeItem(data);
- $("#notices_primary .notices").prepend(noticeItem);
- $("#notices_primary .notice:first").css({display:"none"});
- $("#notices_primary .notice:first").fadeIn(1000);
- NoticeReply();
+ setTimeout(function() {
+ id = data.id;
+
+ // Don't add it if it already exists
+ if ($("#notice-"+id).length > 0) {
+ return;
+ }
+
+ var noticeItem = RealtimeUpdate.makeNoticeItem(data);
+ $("#notices_primary .notices").prepend(noticeItem);
+ $("#notices_primary .notice:first").css({display:"none"});
+ $("#notices_primary .notice:first").fadeIn(1000);
+ NoticeReply();
+ }, 500);
},
makeNoticeItem: function(data)
--
cgit v1.2.3-54-g00ecf
From f65baaaa4f48b84392200bb54161887669440cab Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Thu, 1 Oct 2009 15:43:57 -0400
Subject: change DB so OpenIDPlugin manages OpenID tables
---
db/statusnet.sql | 12 ------------
plugins/OpenID/OpenIDPlugin.php | 15 +++++++++++++++
2 files changed, 15 insertions(+), 12 deletions(-)
(limited to 'plugins')
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 221d60ce3..dfcddb643 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -195,18 +195,6 @@ create table nonce (
constraint primary key (consumer_key, ts, nonce)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-/* One-to-many relationship of user to openid_url */
-
-create table user_openid (
- canonical varchar(255) primary key comment 'Canonical true URL',
- display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical',
- user_id integer not null comment 'user owning this URL' references user (id),
- created datetime not null comment 'date this record was created',
- modified timestamp comment 'date this record was modified',
-
- index user_openid_user_id_idx (user_id)
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-
/* These are used by JanRain OpenID library */
create table oid_associations (
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 91bddf381..a933a1155 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -222,4 +222,19 @@ class OpenIDPlugin extends Plugin
return true;
}
+
+ function onCheckSchema() {
+ $schema = Schema::get();
+ $schema->ensureTable('user_openid',
+ array(new ColumnDef('canonical', 'varchar',
+ '255', false, 'PRI'),
+ new ColumnDef('display', 'varchar',
+ '255', false),
+ new ColumnDef('user_id', 'integer',
+ null, false, 'MUL'),
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'timestamp')));
+ return true;
+ }
}
--
cgit v1.2.3-54-g00ecf
From d09444309f80fe1f274ec2dda4975a96506fcca9 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Fri, 2 Oct 2009 11:46:14 +0000
Subject: Init for WAP 2.0 and XHTML Mobile Profile support. WAP20Plugin is a
superclass for various WAP 2.0 document types. MobileProfilePlugin extends
WAP20Plugin and it is intended for serving XHTML Mobile Profile 1.0.
Feature support for document types like WML 2.0 or WAP Push should be
created as seperate plugins and quite possibly extend WAP20Plugin.
---
plugins/Mobile/WAP20Plugin.php | 56 +++++++++++
plugins/MobileProfile/MobileProfilePlugin.php | 129 ++++++++++++++++++++++++++
2 files changed, 185 insertions(+)
create mode 100644 plugins/Mobile/WAP20Plugin.php
create mode 100644 plugins/MobileProfile/MobileProfilePlugin.php
(limited to 'plugins')
diff --git a/plugins/Mobile/WAP20Plugin.php b/plugins/Mobile/WAP20Plugin.php
new file mode 100644
index 000000000..aae48a520
--- /dev/null
+++ b/plugins/Mobile/WAP20Plugin.php
@@ -0,0 +1,56 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Sarven Capadisli
+ * @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') && !defined('LACONICA')) {
+ exit(1);
+}
+
+
+/**
+ * Superclass for plugin to output XHTML Mobile Profile
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Sarven Capadisli
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class WAP20Plugin extends Plugin
+{
+
+ function onStartShowHTML($action)
+ {
+
+ }
+
+}
+
+
+?>
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
new file mode 100644
index 000000000..5ee2d9097
--- /dev/null
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -0,0 +1,129 @@
+.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Sarven Capadisli
+ * @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') && !defined('LACONICA')) {
+ exit(1);
+}
+
+define('PAGE_TYPE_PREFS',
+ 'application/vnd.wap.xhtml+xml, application/xhtml+xml, text/xml;q=0.9'.
+ ', text/html;q=0.3'
+ );
+
+require_once INSTALLDIR.'/plugins/Mobile/WAP20Plugin.php';
+
+
+/**
+ * Superclass for plugin to output XHTML Mobile Profile
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Sarven Capadisli
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class MobileProfilePlugin extends WAP20Plugin
+{
+ public $DTDversion = null;
+ public $serveMobile = false;
+
+ function __construct($DTD='http://www.wapforum.org/DTD/xhtml-mobile10.dtd')
+ {
+ $this->DTD = $DTD;
+
+ parent::__construct();
+ }
+
+
+ function onStartShowHTML($action)
+ {
+ if (!$type) {
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
+ $_SERVER['HTTP_ACCEPT'] : null;
+
+ $cp = common_accept_to_prefs($httpaccept);
+ $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
+
+ $type = common_negotiate_type($cp, $sp);
+
+ if (!$type) {
+ throw new ClientException(_('This page is not available in a '.
+ 'media type you accept'), 406);
+ }
+ }
+
+ // XXX: If user is on the mobile site e.g., m.siteserver.com
+ // or they really want it, serve the mobile version
+
+ // FIXME: This is dirty and probably not accurate of doing it
+ if ((common_config('site', 'mobileserver').'/'.common_config('site', 'path').'/' ==
+ $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']) ||
+ preg_match("/.*\/.*wap.*xml/", $type)) {
+
+ $this->serveMobile = true;
+ }
+ else {
+ $this->serveMobile = false;
+ return true;
+ }
+
+ header('Content-Type: '.$type);
+
+ $action->extraHeaders();
+
+ $action->startXML('html',
+ '-//WAPFORUM//DTD XHTML Mobile 1.0//EN',
+ $this->DTD);
+
+ $language = $action->getLanguage();
+
+ $action->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+ 'xml:lang' => $language));
+
+ return false;
+ }
+
+
+
+ function onStartShowAside($action)
+ {
+
+ }
+
+
+ function onStartShowScripts($action)
+ {
+
+ }
+
+}
+
+
+?>
--
cgit v1.2.3-54-g00ecf
From 43cd0f87190e55c0973350265fffd2e6c3e5caa2 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Fri, 2 Oct 2009 12:46:26 +0000
Subject: Don't need text/xml until further evidence
---
plugins/MobileProfile/MobileProfilePlugin.php | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
(limited to 'plugins')
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index 5ee2d9097..dd67ffcc9 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -32,9 +32,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
}
define('PAGE_TYPE_PREFS',
- 'application/vnd.wap.xhtml+xml, application/xhtml+xml, text/xml;q=0.9'.
- ', text/html;q=0.3'
- );
+ 'application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html;q=0.9');
require_once INSTALLDIR.'/plugins/Mobile/WAP20Plugin.php';
--
cgit v1.2.3-54-g00ecf
From c2046a9ab6539669f8fdfe24798b124246e7807c Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Fri, 2 Oct 2009 15:38:20 +0000
Subject: Better logic to determine what to do with the visitor. Whether to
serve them the Mobile Profile or not, and possibly redirect.
---
plugins/MobileProfile/MobileProfilePlugin.php | 54 +++++++++++++++++++++++----
1 file changed, 46 insertions(+), 8 deletions(-)
(limited to 'plugins')
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index dd67ffcc9..cce8f8081 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -77,19 +77,57 @@ class MobileProfilePlugin extends WAP20Plugin
}
}
- // XXX: If user is on the mobile site e.g., m.siteserver.com
- // or they really want it, serve the mobile version
+ // XXX: This should probably graduate to WAP20Plugin
- // FIXME: This is dirty and probably not accurate of doing it
- if ((common_config('site', 'mobileserver').'/'.common_config('site', 'path').'/' ==
- $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']) ||
- preg_match("/.*\/.*wap.*xml/", $type)) {
+ // If they are on the mobile site, serve them MP
+ if ((common_config('site', 'mobileserver').'/'.
+ common_config('site', 'path').'/' ==
+ $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])) {
$this->serveMobile = true;
}
else {
- $this->serveMobile = false;
- return true;
+ // If they like the WAP 2.0 mimetype, serve them MP
+ if (strstr('application/vnd.wap.xhtml+xml', $type) !== false) {
+ $this->serveMobile = true;
+ }
+ else {
+ // If they are a mobile device that supports WAP 2.0,
+ // serve them MP
+
+ // XXX: Browser sniffing sucks
+ // I really don't like going through this every page,
+ // find a better way
+ $this->mobiledevices =
+ array('alcatel', 'android', 'audiovox', 'au-mic,',
+ 'avantgo', 'blackberry', 'blazer', 'cldc-', 'danger',
+ 'epoc', 'ericsson', 'ericy', 'ipone', 'ipaq', 'j2me',
+ 'lg', 'midp-', 'mobile', 'mot', 'netfront', 'nitro',
+ 'nokia', 'opera mini', 'palm', 'palmsource',
+ 'panasonic', 'philips', 'pocketpc', 'portalmmm',
+ 'rover', 'samsung', 'sanyo', 'series60', 'sharp',
+ 'sie-', 'smartphone', 'sony', 'symbian',
+ 'up.browser', 'up.link', 'up.link', 'vodafone',
+ 'wap1', 'wap2', 'windows ce');
+
+ $httpuseragent = strtolower($_SERVER['HTTP_USER_AGENT']);
+
+ foreach($this->mobiledevices as $mb) {
+ if (strstr($httpuseragent, $mb) !== false) {
+ $this->serveMobile = true;
+ break;
+ }
+ }
+ }
+
+ // If they are okay with MP, and the site has a mobile server,
+ // redirect there
+ if ($this->serveMobile &&
+ common_config('site', 'mobileserver') !== false) {
+
+ header("Location: ".common_config('site', 'mobileserver'));
+ exit();
+ }
}
header('Content-Type: '.$type);
--
cgit v1.2.3-54-g00ecf
From 604cfd8b116886cac04a8d062c66f11fb601318f Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Sat, 3 Oct 2009 20:17:26 +0000
Subject: Updated comment about browser sniffing
---
plugins/MobileProfile/MobileProfilePlugin.php | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
(limited to 'plugins')
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index cce8f8081..f594f3c0e 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -96,8 +96,16 @@ class MobileProfilePlugin extends WAP20Plugin
// serve them MP
// XXX: Browser sniffing sucks
+
// I really don't like going through this every page,
// find a better way
+
+ // May be better to categorize the devices in terms of
+ // low,mid,high-end
+
+ // Or, detect the mobile devices based on their support for
+ // MP 1.0, 1.1, or 1.2 may be ideal. Possible?
+
$this->mobiledevices =
array('alcatel', 'android', 'audiovox', 'au-mic,',
'avantgo', 'blackberry', 'blazer', 'cldc-', 'danger',
@@ -112,8 +120,8 @@ class MobileProfilePlugin extends WAP20Plugin
$httpuseragent = strtolower($_SERVER['HTTP_USER_AGENT']);
- foreach($this->mobiledevices as $mb) {
- if (strstr($httpuseragent, $mb) !== false) {
+ foreach($this->mobiledevices as $md) {
+ if (strstr($httpuseragent, $md) !== false) {
$this->serveMobile = true;
break;
}
--
cgit v1.2.3-54-g00ecf
From 63700f79588ded641645bc5eaf9265ea837bfff6 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Sat, 3 Oct 2009 20:22:40 +0000
Subject: Minor correction to public variable name
---
plugins/MobileProfile/MobileProfilePlugin.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'plugins')
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index f594f3c0e..cd88a6f7b 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -49,7 +49,7 @@ require_once INSTALLDIR.'/plugins/Mobile/WAP20Plugin.php';
class MobileProfilePlugin extends WAP20Plugin
{
- public $DTDversion = null;
+ public $DTD = null;
public $serveMobile = false;
function __construct($DTD='http://www.wapforum.org/DTD/xhtml-mobile10.dtd')
--
cgit v1.2.3-54-g00ecf
From f344a49b11607d4dbb64ab6935237cd5185a06b4 Mon Sep 17 00:00:00 2001
From: Sarven Capadisli
Date: Sat, 3 Oct 2009 21:29:14 +0000
Subject: Don't show .aside
---
plugins/MobileProfile/MobileProfilePlugin.php | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
(limited to 'plugins')
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index cd88a6f7b..932550189 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -49,7 +49,7 @@ require_once INSTALLDIR.'/plugins/Mobile/WAP20Plugin.php';
class MobileProfilePlugin extends WAP20Plugin
{
- public $DTD = null;
+ public $DTD = null;
public $serveMobile = false;
function __construct($DTD='http://www.wapforum.org/DTD/xhtml-mobile10.dtd')
@@ -158,7 +158,9 @@ class MobileProfilePlugin extends WAP20Plugin
function onStartShowAside($action)
{
-
+ if ($this->serveMobile) {
+ return false;
+ }
}
--
cgit v1.2.3-54-g00ecf
From 22b4a66de3441bcb54b748f98be5b53e3be4bece Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 30 Jun 2009 23:33:47 -0400
Subject: copy Comet plugin to Orbited
---
plugins/Orbited/CometPlugin.php | 205 +++++
plugins/Orbited/README | 26 +
plugins/Orbited/bayeux.class.inc.php | 134 ++++
plugins/Orbited/jquery.comet.js | 1451 ++++++++++++++++++++++++++++++++++
plugins/Orbited/json2.js | 478 +++++++++++
plugins/Orbited/updatetimeline.js | 154 ++++
6 files changed, 2448 insertions(+)
create mode 100644 plugins/Orbited/CometPlugin.php
create mode 100644 plugins/Orbited/README
create mode 100644 plugins/Orbited/bayeux.class.inc.php
create mode 100644 plugins/Orbited/jquery.comet.js
create mode 100644 plugins/Orbited/json2.js
create mode 100644 plugins/Orbited/updatetimeline.js
(limited to 'plugins')
diff --git a/plugins/Orbited/CometPlugin.php b/plugins/Orbited/CometPlugin.php
new file mode 100644
index 000000000..45251c66f
--- /dev/null
+++ b/plugins/Orbited/CometPlugin.php
@@ -0,0 +1,205 @@
+.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Plugin to do realtime updates using Comet
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class CometPlugin extends Plugin
+{
+ var $server = null;
+
+ function __construct($server=null, $username=null, $password=null)
+ {
+ $this->server = $server;
+ $this->username = $username;
+ $this->password = $password;
+
+ parent::__construct();
+ }
+
+ function onEndShowScripts($action)
+ {
+ $timeline = null;
+
+ $this->log(LOG_DEBUG, 'got action ' . $action->trimmed('action'));
+
+ switch ($action->trimmed('action')) {
+ case 'public':
+ $timeline = '/timelines/public';
+ break;
+ case 'tag':
+ $tag = $action->trimmed('tag');
+ if (!empty($tag)) {
+ $timeline = '/timelines/tag/'.$tag;
+ } else {
+ return true;
+ }
+ break;
+ default:
+ return true;
+ }
+
+ $scripts = array('jquery.comet.js', 'json2.js', 'updatetimeline.js');
+
+ foreach ($scripts as $script) {
+ $action->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('plugins/Comet/'.$script)),
+ ' ');
+ }
+
+ $user = common_current_user();
+
+ if (!empty($user->id)) {
+ $user_id = $user->id;
+ } else {
+ $user_id = 0;
+ }
+
+ $replyurl = common_local_url('newnotice');
+ $favorurl = common_local_url('favor');
+ // FIXME: need to find a better way to pass this pattern in
+ $deleteurl = common_local_url('deletenotice',
+ array('notice' => '0000000000'));
+
+ $action->elementStart('script', array('type' => 'text/javascript'));
+ $action->raw("$(document).ready(function() { updater.init(\"$this->server\", \"$timeline\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });");
+ $action->elementEnd('script');
+
+ return true;
+ }
+
+ function onEndNoticeSave($notice)
+ {
+ $this->log(LOG_INFO, "Called for save notice.");
+
+ $timelines = array();
+
+ // XXX: Add other timelines; this is just for the public one
+
+ if ($notice->is_local ||
+ ($notice->is_local == 0 && !common_config('public', 'localonly'))) {
+ $timelines[] = '/timelines/public';
+ }
+
+ $tags = $this->getNoticeTags($notice);
+
+ if (!empty($tags)) {
+ foreach ($tags as $tag) {
+ $timelines[] = '/timelines/tag/' . $tag;
+ }
+ }
+
+ if (count($timelines) > 0) {
+ // Require this, since we need it
+ require_once(INSTALLDIR.'/plugins/Comet/bayeux.class.inc.php');
+
+ $json = $this->noticeAsJson($notice);
+
+ // Bayeux? Comet? Huh? These terms confuse me
+ $bay = new Bayeux($this->server, $this->user, $this->password);
+
+ foreach ($timelines as $timeline) {
+ $this->log(LOG_INFO, "Posting notice $notice->id to '$timeline'.");
+ $bay->publish($timeline, $json);
+ }
+
+ $bay = NULL;
+ }
+
+ return true;
+ }
+
+ function noticeAsJson($notice)
+ {
+ // FIXME: this code should be abstracted to a neutral third
+ // party, like Notice::asJson(). I'm not sure of the ethics
+ // of refactoring from within a plugin, so I'm just abusing
+ // the TwitterApiAction method. Don't do this unless you're me!
+
+ require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+ $act = new TwitterApiAction('/dev/null');
+
+ $arr = $act->twitter_status_array($notice, true);
+ $arr['url'] = $notice->bestUrl();
+ $arr['html'] = htmlspecialchars($notice->rendered);
+ $arr['source'] = htmlspecialchars($arr['source']);
+
+ if (!empty($notice->reply_to)) {
+ $reply_to = Notice::staticGet('id', $notice->reply_to);
+ if (!empty($reply_to)) {
+ $arr['in_reply_to_status_url'] = $reply_to->bestUrl();
+ }
+ $reply_to = null;
+ }
+
+ $profile = $notice->getProfile();
+ $arr['user']['profile_url'] = $profile->profileurl;
+
+ return $arr;
+ }
+
+ function getNoticeTags($notice)
+ {
+ $tags = null;
+
+ $nt = new Notice_tag();
+ $nt->notice_id = $notice->id;
+
+ if ($nt->find()) {
+ $tags = array();
+ while ($nt->fetch()) {
+ $tags[] = $nt->tag;
+ }
+ }
+
+ $nt->free();
+ $nt = null;
+
+ return $tags;
+ }
+
+ // Push this up to Plugin
+
+ function log($level, $msg)
+ {
+ common_log($level, get_class($this) . ': '.$msg);
+ }
+}
diff --git a/plugins/Orbited/README b/plugins/Orbited/README
new file mode 100644
index 000000000..4abd40af7
--- /dev/null
+++ b/plugins/Orbited/README
@@ -0,0 +1,26 @@
+This is a plugin to automatically load notices in the browser no
+matter who creates them -- the kind of thing we see with
+search.twitter.com, rejaw.com, or FriendFeed's "real time" news.
+
+NOTE: this is an insecure version; don't roll it out on a production
+server.
+
+It requires a cometd server. I've only had the cometd-java server work
+correctly; something's wiggy with the Twisted-based server.
+
+After you have a cometd server installed, just add this code to your
+config.php:
+
+ require_once(INSTALLDIR.'/plugins/Comet/CometPlugin.php');
+ $cp = new CometPlugin('http://example.com:8080/cometd/');
+
+Change 'example.com:8080' to the name and port of the server you
+installed cometd on.
+
+TODO:
+
+* Needs to be tested with Ajax submission. Probably messes everything
+ up.
+* Add more timelines: personal inbox and tags would be great.
+* Add security. In particular, only let the PHP code publish notices
+ to the cometd server. Currently, it doesn't try to authenticate.
diff --git a/plugins/Orbited/bayeux.class.inc.php b/plugins/Orbited/bayeux.class.inc.php
new file mode 100644
index 000000000..39ad8a8fc
--- /dev/null
+++ b/plugins/Orbited/bayeux.class.inc.php
@@ -0,0 +1,134 @@
+ http://morglog.alleycatracing.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+class Bayeux
+{
+ private $oCurl = '';
+ private $nNextId = 0;
+
+ private $sUser = '';
+ private $sPassword = '';
+
+ public $sUrl = '';
+
+ function __construct($sUrl, $sUser='', $sPassword='')
+ {
+ $this->sUrl = $sUrl;
+
+ $this->oCurl = curl_init();
+
+ $aHeaders = array();
+ $aHeaders[] = 'Connection: Keep-Alive';
+
+ curl_setopt($this->oCurl, CURLOPT_URL, $sUrl);
+ curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders);
+ curl_setopt($this->oCurl, CURLOPT_HEADER, 0);
+ curl_setopt($this->oCurl, CURLOPT_POST, 1);
+ curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER,1);
+
+ if (!is_null($sUser) && mb_strlen($sUser) > 0) {
+ curl_setopt($this->oCurl, CURLOPT_USERPWD,"$sUser:$sPassword");
+ }
+
+ $this->handShake();
+ }
+
+ function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ function handShake()
+ {
+ $msgHandshake = array();
+ $msgHandshake['channel'] = '/meta/handshake';
+ $msgHandshake['version'] = "1.0";
+ $msgHandshake['minimumVersion'] = "0.9";
+ $msgHandshake['supportedConnectionTypes'] = array('long-polling');
+ $msgHandshake['id'] = $this->nNextId++;
+
+ curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));
+
+ $data = curl_exec($this->oCurl);
+
+ if(curl_errno($this->oCurl))
+ die("Error: " . curl_error($this->oCurl));
+
+ $oReturn = json_decode($data);
+
+ if (is_array($oReturn)) {
+ $oReturn = $oReturn[0];
+ }
+
+ $bSuccessful = ($oReturn->successful) ? true : false;
+
+ if($bSuccessful)
+ {
+ $this->clientId = $oReturn->clientId;
+
+ $this->connect();
+ }
+ }
+
+ public function connect()
+ {
+ $aMsg['channel'] = '/meta/connect';
+ $aMsg['id'] = $this->nNextId++;
+ $aMsg['clientId'] = $this->clientId;
+ $aMsg['connectionType'] = 'long-polling';
+
+ curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));
+
+ $data = curl_exec($this->oCurl);
+ }
+
+ function disconnect()
+ {
+ $msgHandshake = array();
+ $msgHandshake['channel'] = '/meta/disconnect';
+ $msgHandshake['id'] = $this->nNextId++;
+ $msgHandshake['clientId'] = $this->clientId;
+
+ curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));
+
+ curl_exec($this->oCurl);
+ }
+
+ public function publish($sChannel, $oData)
+ {
+ if(!$sChannel || !$oData)
+ return;
+
+ $aMsg = array();
+
+ $aMsg['channel'] = $sChannel;
+ $aMsg['id'] = $this->nNextId++;
+ $aMsg['data'] = $oData;
+ $aMsg['clientId'] = $this->clientId;
+
+ curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));
+
+ $data = curl_exec($this->oCurl);
+// var_dump($data);
+ }
+}
diff --git a/plugins/Orbited/jquery.comet.js b/plugins/Orbited/jquery.comet.js
new file mode 100644
index 000000000..6de437fa8
--- /dev/null
+++ b/plugins/Orbited/jquery.comet.js
@@ -0,0 +1,1451 @@
+/**
+ * Copyright 2008 Mort Bay Consulting Pty. Ltd.
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * ----------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http: *www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ----------------------------------------------------------------------------
+ * Licensed under the MIT license;
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * ----------------------------------------------------------------------------
+ * $Revision$ $Date$
+ */
+(function($)
+{
+ /**
+ * The constructor for a Comet object.
+ * There is a default Comet instance already created at the variable $.cometd,
+ * and hence that can be used to start a comet conversation with a server.
+ * In the rare case a page needs more than one comet conversation, a new instance can be
+ * created via:
+ *
+ * var url2 = ...;
+ * var cometd2 = new $.Cometd();
+ * cometd2.init(url2);
+ *
+ */
+ $.Cometd = function(name)
+ {
+ var _name = name || 'default';
+ var _logPriorities = { debug: 1, info: 2, warn: 3, error: 4 };
+ var _logLevel = 'info';
+ var _url;
+ var _xd = false;
+ var _transport;
+ var _status = 'disconnected';
+ var _messageId = 0;
+ var _clientId = null;
+ var _batch = 0;
+ var _messageQueue = [];
+ var _listeners = {};
+ var _backoff = 0;
+ var _backoffIncrement = 1000;
+ var _maxBackoff = 60000;
+ var _scheduledSend = null;
+ var _extensions = [];
+ var _advice = {};
+ var _handshakeProps;
+
+ /**
+ * Returns the name assigned to this Comet object, or the string 'default'
+ * if no name has been explicitely passed as parameter to the constructor.
+ */
+ this.getName = function()
+ {
+ return _name;
+ };
+
+ /**
+ * Configures the initial comet communication with the comet server.
+ * @param cometURL the URL of the comet server
+ */
+ this.configure = function(cometURL)
+ {
+ _configure(cometURL);
+ };
+
+ function _configure(cometURL)
+ {
+ _url = cometURL;
+ _debug('Initializing comet with url: {}', _url);
+
+ // Check immediately if we're cross domain
+ // If cross domain, the handshake must not send the long polling transport type
+ var urlParts = /(^https?:)?(\/\/(([^:\/\?#]+)(:(\d+))?))?([^\?#]*)/.exec(cometURL);
+ if (urlParts[3]) _xd = urlParts[3] != location.host;
+
+ // Temporary setup a transport to send the initial handshake
+ // The transport may be changed as a result of handshake
+ if (_xd)
+ _transport = newCallbackPollingTransport();
+ else
+ _transport = newLongPollingTransport();
+ _debug('Initial transport is {}', _transport.getType());
+ };
+
+ /**
+ * Configures and establishes the comet communication with the comet server
+ * via a handshake and a subsequent connect.
+ * @param cometURL the URL of the comet server
+ * @param handshakeProps an object to be merged with the handshake message
+ * @see #configure(cometURL)
+ * @see #handshake(handshakeProps)
+ */
+ this.init = function(cometURL, handshakeProps)
+ {
+ _configure(cometURL);
+ _handshake(handshakeProps);
+ };
+
+ /**
+ * Establishes the comet communication with the comet server
+ * via a handshake and a subsequent connect.
+ * @param handshakeProps an object to be merged with the handshake message
+ */
+ this.handshake = function(handshakeProps)
+ {
+ _handshake(handshakeProps);
+ };
+
+ /**
+ * Disconnects from the comet server.
+ * @param disconnectProps an object to be merged with the disconnect message
+ */
+ this.disconnect = function(disconnectProps)
+ {
+ var bayeuxMessage = {
+ channel: '/meta/disconnect'
+ };
+ var message = $.extend({}, disconnectProps, bayeuxMessage);
+ // Deliver immediately
+ // The handshake and connect mechanism make use of startBatch(), and in case
+ // of a failed handshake the disconnect would not be delivered if using _send().
+ _setStatus('disconnecting');
+ _deliver([message], false);
+ };
+
+ /**
+ * Marks the start of a batch of application messages to be sent to the server
+ * in a single request, obtaining a single response containing (possibly) many
+ * application reply messages.
+ * Messages are held in a queue and not sent until {@link #endBatch()} is called.
+ * If startBatch() is called multiple times, then an equal number of endBatch()
+ * calls must be made to close and send the batch of messages.
+ * @see #endBatch()
+ */
+ this.startBatch = function()
+ {
+ _startBatch();
+ };
+
+ /**
+ * Marks the end of a batch of application messages to be sent to the server
+ * in a single request.
+ * @see #startBatch()
+ */
+ this.endBatch = function()
+ {
+ _endBatch(true);
+ };
+
+ /**
+ * Subscribes to the given channel, performing the given callback in the given scope
+ * when a message for the channel arrives.
+ * @param channel the channel to subscribe to
+ * @param scope the scope of the callback
+ * @param callback the callback to call when a message is delivered to the channel
+ * @param subscribeProps an object to be merged with the subscribe message
+ * @return the subscription handle to be passed to {@link #unsubscribe(object)}
+ */
+ this.subscribe = function(channel, scope, callback, subscribeProps)
+ {
+ var subscription = this.addListener(channel, scope, callback);
+
+ // Send the subscription message after the subscription registration to avoid
+ // races where the server would deliver a message to the subscribers, but here
+ // on the client the subscription has not been added yet to the data structures
+ var bayeuxMessage = {
+ channel: '/meta/subscribe',
+ subscription: channel
+ };
+ var message = $.extend({}, subscribeProps, bayeuxMessage);
+ _send(message);
+
+ return subscription;
+ };
+
+ /**
+ * Unsubscribes the subscription obtained with a call to {@link #subscribe(string, object, function)}.
+ * @param subscription the subscription to unsubscribe.
+ */
+ this.unsubscribe = function(subscription, unsubscribeProps)
+ {
+ // Remove the local listener before sending the message
+ // This ensures that if the server fails, this client does not get notifications
+ this.removeListener(subscription);
+ var bayeuxMessage = {
+ channel: '/meta/unsubscribe',
+ subscription: subscription[0]
+ };
+ var message = $.extend({}, unsubscribeProps, bayeuxMessage);
+ _send(message);
+ };
+
+ /**
+ * Publishes a message on the given channel, containing the given content.
+ * @param channel the channel to publish the message to
+ * @param content the content of the message
+ * @param publishProps an object to be merged with the publish message
+ */
+ this.publish = function(channel, content, publishProps)
+ {
+ var bayeuxMessage = {
+ channel: channel,
+ data: content
+ };
+ var message = $.extend({}, publishProps, bayeuxMessage);
+ _send(message);
+ };
+
+ /**
+ * Adds a listener for bayeux messages, performing the given callback in the given scope
+ * when a message for the given channel arrives.
+ * @param channel the channel the listener is interested to
+ * @param scope the scope of the callback
+ * @param callback the callback to call when a message is delivered to the channel
+ * @returns the subscription handle to be passed to {@link #removeListener(object)}
+ * @see #removeListener(object)
+ */
+ this.addListener = function(channel, scope, callback)
+ {
+ // The data structure is a map, where each subscription
+ // holds the callback to be called and its scope.
+
+ // Normalize arguments
+ if (!callback)
+ {
+ callback = scope;
+ scope = undefined;
+ }
+
+ var subscription = {
+ scope: scope,
+ callback: callback
+ };
+
+ var subscriptions = _listeners[channel];
+ if (!subscriptions)
+ {
+ subscriptions = [];
+ _listeners[channel] = subscriptions;
+ }
+ // Pushing onto an array appends at the end and returns the id associated with the element increased by 1.
+ // Note that if:
+ // a.push('a'); var hb=a.push('b'); delete a[hb-1]; var hc=a.push('c');
+ // then:
+ // hc==3, a.join()=='a',,'c', a.length==3
+ var subscriptionIndex = subscriptions.push(subscription) - 1;
+ _debug('Added listener: channel \'{}\', callback \'{}\', index {}', channel, callback.name, subscriptionIndex);
+
+ // The subscription to allow removal of the listener is made of the channel and the index
+ return [channel, subscriptionIndex];
+ };
+
+ /**
+ * Removes the subscription obtained with a call to {@link #addListener(string, object, function)}.
+ * @param subscription the subscription to unsubscribe.
+ */
+ this.removeListener = function(subscription)
+ {
+ var subscriptions = _listeners[subscription[0]];
+ if (subscriptions)
+ {
+ delete subscriptions[subscription[1]];
+ _debug('Removed listener: channel \'{}\', index {}', subscription[0], subscription[1]);
+ }
+ };
+
+ /**
+ * Removes all listeners registered with {@link #addListener(channel, scope, callback)} or
+ * {@link #subscribe(channel, scope, callback)}.
+ */
+ this.clearListeners = function()
+ {
+ _listeners = {};
+ };
+
+ /**
+ * Returns a string representing the status of the bayeux communication with the comet server.
+ */
+ this.getStatus = function()
+ {
+ return _status;
+ };
+
+ /**
+ * Sets the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
+ * Default value is 1 second, which means if there is a persistent failure the retries will happen
+ * after 1 second, then after 2 seconds, then after 3 seconds, etc. So for example with 15 seconds of
+ * elapsed time, there will be 5 retries (at 1, 3, 6, 10 and 15 seconds elapsed).
+ * @param period the backoff period to set
+ * @see #getBackoffIncrement()
+ */
+ this.setBackoffIncrement = function(period)
+ {
+ _backoffIncrement = period;
+ };
+
+ /**
+ * Returns the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
+ * @see #setBackoffIncrement(period)
+ */
+ this.getBackoffIncrement = function()
+ {
+ return _backoffIncrement;
+ };
+
+ /**
+ * Returns the backoff period to wait before retrying an unsuccessful or failed message.
+ */
+ this.getBackoffPeriod = function()
+ {
+ return _backoff;
+ };
+
+ /**
+ * Sets the log level for console logging.
+ * Valid values are the strings 'error', 'warn', 'info' and 'debug', from
+ * less verbose to more verbose.
+ * @param level the log level string
+ */
+ this.setLogLevel = function(level)
+ {
+ _logLevel = level;
+ };
+
+ /**
+ * Registers an extension whose callbacks are called for every incoming message
+ * (that comes from the server to this client implementation) and for every
+ * outgoing message (that originates from this client implementation for the
+ * server).
+ * The format of the extension object is the following:
+ *
+ * {
+ * incoming: function(message) { ... },
+ * outgoing: function(message) { ... }
+ * }
+ * Both properties are optional, but if they are present they will be called
+ * respectively for each incoming message and for each outgoing message.
+ *
+ * @param name the name of the extension
+ * @param extension the extension to register
+ * @return true if the extension was registered, false otherwise
+ * @see #unregisterExtension(name)
+ */
+ this.registerExtension = function(name, extension)
+ {
+ var existing = false;
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var existingExtension = _extensions[i];
+ if (existingExtension.name == name)
+ {
+ existing = true;
+ return false;
+ }
+ }
+ if (!existing)
+ {
+ _extensions.push({
+ name: name,
+ extension: extension
+ });
+ _debug('Registered extension \'{}\'', name);
+ return true;
+ }
+ else
+ {
+ _info('Could not register extension with name \'{}\': another extension with the same name already exists');
+ return false;
+ }
+ };
+
+ /**
+ * Unregister an extension previously registered with
+ * {@link #registerExtension(name, extension)}.
+ * @param name the name of the extension to unregister.
+ * @return true if the extension was unregistered, false otherwise
+ */
+ this.unregisterExtension = function(name)
+ {
+ var unregistered = false;
+ $.each(_extensions, function(index, extension)
+ {
+ if (extension.name == name)
+ {
+ _extensions.splice(index, 1);
+ unregistered = true;
+ _debug('Unregistered extension \'{}\'', name);
+ return false;
+ }
+ });
+ return unregistered;
+ };
+
+ /**
+ * Starts a the batch of messages to be sent in a single request.
+ * @see _endBatch(deliverMessages)
+ */
+ function _startBatch()
+ {
+ ++_batch;
+ };
+
+ /**
+ * Ends the batch of messages to be sent in a single request,
+ * optionally delivering messages present in the message queue depending
+ * on the given argument.
+ * @param deliverMessages whether to deliver the messages in the queue or not
+ * @see _startBatch()
+ */
+ function _endBatch(deliverMessages)
+ {
+ --_batch;
+ if (_batch < 0) _batch = 0;
+ if (deliverMessages && _batch == 0 && !_isDisconnected())
+ {
+ var messages = _messageQueue;
+ _messageQueue = [];
+ if (messages.length > 0) _deliver(messages, false);
+ }
+ };
+
+ function _nextMessageId()
+ {
+ return ++_messageId;
+ };
+
+ /**
+ * Converts the given response into an array of bayeux messages
+ * @param response the response to convert
+ * @return an array of bayeux messages obtained by converting the response
+ */
+ function _convertToMessages(response)
+ {
+ if (response === undefined) return [];
+ if (response instanceof Array) return response;
+ if (response instanceof String || typeof response == 'string') return eval('(' + response + ')');
+ if (response instanceof Object) return [response];
+ throw 'Conversion Error ' + response + ', typeof ' + (typeof response);
+ };
+
+ function _setStatus(newStatus)
+ {
+ _debug('{} -> {}', _status, newStatus);
+ _status = newStatus;
+ };
+
+ function _isDisconnected()
+ {
+ return _status == 'disconnecting' || _status == 'disconnected';
+ };
+
+ /**
+ * Sends the initial handshake message
+ */
+ function _handshake(handshakeProps)
+ {
+ _debug('Starting handshake');
+ _clientId = null;
+
+ // Start a batch.
+ // This is needed because handshake and connect are async.
+ // It may happen that the application calls init() then subscribe()
+ // and the subscribe message is sent before the connect message, if
+ // the subscribe message is not held until the connect message is sent.
+ // So here we start a batch to hold temporarly any message until
+ // the connection is fully established.
+ _batch = 0;
+ _startBatch();
+
+ // Save the original properties provided by the user
+ // Deep copy to avoid the user to be able to change them later
+ _handshakeProps = $.extend(true, {}, handshakeProps);
+
+ var bayeuxMessage = {
+ version: '1.0',
+ minimumVersion: '0.9',
+ channel: '/meta/handshake',
+ supportedConnectionTypes: _xd ? ['callback-polling'] : ['long-polling', 'callback-polling']
+ };
+ // Do not allow the user to mess with the required properties,
+ // so merge first the user properties and *then* the bayeux message
+ var message = $.extend({}, handshakeProps, bayeuxMessage);
+
+ // We started a batch to hold the application messages,
+ // so here we must bypass it and deliver immediately.
+ _setStatus('handshaking');
+ _deliver([message], false);
+ };
+
+ function _findTransport(handshakeResponse)
+ {
+ var transportTypes = handshakeResponse.supportedConnectionTypes;
+ if (_xd)
+ {
+ // If we are cross domain, check if the server supports it, that's the only option
+ if ($.inArray('callback-polling', transportTypes) >= 0) return _transport;
+ }
+ else
+ {
+ // Check if we can keep long-polling
+ if ($.inArray('long-polling', transportTypes) >= 0) return _transport;
+
+ // The server does not support long-polling
+ if ($.inArray('callback-polling', transportTypes) >= 0) return newCallbackPollingTransport();
+ }
+ return null;
+ };
+
+ function _delayedHandshake()
+ {
+ _setStatus('handshaking');
+ _delayedSend(function()
+ {
+ _handshake(_handshakeProps);
+ });
+ };
+
+ function _delayedConnect()
+ {
+ _setStatus('connecting');
+ _delayedSend(function()
+ {
+ _connect();
+ });
+ };
+
+ function _delayedSend(operation)
+ {
+ _cancelDelayedSend();
+ var delay = _backoff;
+ _debug("Delayed send: backoff {}, interval {}", _backoff, _advice.interval);
+ if (_advice.interval && _advice.interval > 0)
+ delay += _advice.interval;
+ _scheduledSend = _setTimeout(operation, delay);
+ };
+
+ function _cancelDelayedSend()
+ {
+ if (_scheduledSend !== null) clearTimeout(_scheduledSend);
+ _scheduledSend = null;
+ };
+
+ function _setTimeout(funktion, delay)
+ {
+ return setTimeout(function()
+ {
+ try
+ {
+ funktion();
+ }
+ catch (x)
+ {
+ _debug('Exception during scheduled execution of function \'{}\': {}', funktion.name, x);
+ }
+ }, delay);
+ };
+
+ /**
+ * Sends the connect message
+ */
+ function _connect()
+ {
+ _debug('Starting connect');
+ var message = {
+ channel: '/meta/connect',
+ connectionType: _transport.getType()
+ };
+ _setStatus('connecting');
+ _deliver([message], true);
+ _setStatus('connected');
+ };
+
+ function _send(message)
+ {
+ if (_batch > 0)
+ _messageQueue.push(message);
+ else
+ _deliver([message], false);
+ };
+
+ /**
+ * Delivers the messages to the comet server
+ * @param messages the array of messages to send
+ */
+ function _deliver(messages, comet)
+ {
+ // We must be sure that the messages have a clientId.
+ // This is not guaranteed since the handshake may take time to return
+ // (and hence the clientId is not known yet) and the application
+ // may create other messages.
+ $.each(messages, function(index, message)
+ {
+ message['id'] = _nextMessageId();
+ if (_clientId) message['clientId'] = _clientId;
+ messages[index] = _applyOutgoingExtensions(message);
+ });
+
+ var self = this;
+ var envelope = {
+ url: _url,
+ messages: messages,
+ onSuccess: function(request, response)
+ {
+ try
+ {
+ _handleSuccess.call(self, request, response, comet);
+ }
+ catch (x)
+ {
+ _debug('Exception during execution of success callback: {}', x);
+ }
+ },
+ onFailure: function(request, reason, exception)
+ {
+ try
+ {
+ _handleFailure.call(self, request, messages, reason, exception, comet);
+ }
+ catch (x)
+ {
+ _debug('Exception during execution of failure callback: {}', x);
+ }
+ }
+ };
+ _debug('Sending request to {}, message(s): {}', envelope.url, JSON.stringify(envelope.messages));
+ _transport.send(envelope, comet);
+ };
+
+ function _applyIncomingExtensions(message)
+ {
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var extension = _extensions[i];
+ var callback = extension.extension.incoming;
+ if (callback && typeof callback === 'function')
+ {
+ _debug('Calling incoming extension \'{}\', callback \'{}\'', extension.name, callback.name);
+ message = _applyExtension(extension.name, callback, message) || message;
+ }
+ }
+ return message;
+ };
+
+ function _applyOutgoingExtensions(message)
+ {
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var extension = _extensions[i];
+ var callback = extension.extension.outgoing;
+ if (callback && typeof callback === 'function')
+ {
+ _debug('Calling outgoing extension \'{}\', callback \'{}\'', extension.name, callback.name);
+ message = _applyExtension(extension.name, callback, message) || message;
+ }
+ }
+ return message;
+ };
+
+ function _applyExtension(name, callback, message)
+ {
+ try
+ {
+ return callback(message);
+ }
+ catch (x)
+ {
+ _debug('Exception during execution of extension \'{}\': {}', name, x);
+ return message;
+ }
+ };
+
+ function _handleSuccess(request, response, comet)
+ {
+ var messages = _convertToMessages(response);
+ _debug('Received response {}', JSON.stringify(messages));
+
+ // Signal the transport it can deliver other queued requests
+ _transport.complete(request, true, comet);
+
+ for (var i = 0; i < messages.length; ++i)
+ {
+ var message = messages[i];
+ message = _applyIncomingExtensions(message);
+
+ if (message.advice) _advice = message.advice;
+
+ var channel = message.channel;
+ switch (channel)
+ {
+ case '/meta/handshake':
+ _handshakeSuccess(message);
+ break;
+ case '/meta/connect':
+ _connectSuccess(message);
+ break;
+ case '/meta/disconnect':
+ _disconnectSuccess(message);
+ break;
+ case '/meta/subscribe':
+ _subscribeSuccess(message);
+ break;
+ case '/meta/unsubscribe':
+ _unsubscribeSuccess(message);
+ break;
+ default:
+ _messageSuccess(message);
+ break;
+ }
+ }
+ };
+
+ function _handleFailure(request, messages, reason, exception, comet)
+ {
+ var xhr = request.xhr;
+ _debug('Request failed, status: {}, reason: {}, exception: {}', xhr && xhr.status, reason, exception);
+
+ // Signal the transport it can deliver other queued requests
+ _transport.complete(request, false, comet);
+
+ for (var i = 0; i < messages.length; ++i)
+ {
+ var message = messages[i];
+ var channel = message.channel;
+ switch (channel)
+ {
+ case '/meta/handshake':
+ _handshakeFailure(xhr, message);
+ break;
+ case '/meta/connect':
+ _connectFailure(xhr, message);
+ break;
+ case '/meta/disconnect':
+ _disconnectFailure(xhr, message);
+ break;
+ case '/meta/subscribe':
+ _subscribeFailure(xhr, message);
+ break;
+ case '/meta/unsubscribe':
+ _unsubscribeFailure(xhr, message);
+ break;
+ default:
+ _messageFailure(xhr, message);
+ break;
+ }
+ }
+ };
+
+ function _handshakeSuccess(message)
+ {
+ if (message.successful)
+ {
+ _debug('Handshake successful');
+ // Save clientId, figure out transport, then follow the advice to connect
+ _clientId = message.clientId;
+
+ var newTransport = _findTransport(message);
+ if (newTransport === null)
+ {
+ throw 'Could not agree on transport with server';
+ }
+ else
+ {
+ if (_transport.getType() != newTransport.getType())
+ {
+ _debug('Changing transport from {} to {}', _transport.getType(), newTransport.getType());
+ _transport = newTransport;
+ }
+ }
+
+ // Notify the listeners
+ // Here the new transport is in place, as well as the clientId, so
+ // the listener can perform a publish() if it wants, and the listeners
+ // are notified before the connect below.
+ _notifyListeners('/meta/handshake', message);
+
+ var action = _advice.reconnect ? _advice.reconnect : 'retry';
+ switch (action)
+ {
+ case 'retry':
+ _delayedConnect();
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ _debug('Handshake unsuccessful');
+
+ var retry = !_isDisconnected() && _advice.reconnect != 'none';
+ if (!retry) _setStatus('disconnected');
+
+ _notifyListeners('/meta/handshake', message);
+ _notifyListeners('/meta/unsuccessful', message);
+
+ // Only try again if we haven't been disconnected and
+ // the advice permits us to retry the handshake
+ if (retry)
+ {
+ _increaseBackoff();
+ _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
+ _delayedHandshake();
+ }
+ }
+ };
+
+ function _handshakeFailure(xhr, message)
+ {
+ _debug('Handshake failure');
+
+ // Notify listeners
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/handshake',
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'retry',
+ interval: _backoff
+ }
+ };
+
+ var retry = !_isDisconnected() && _advice.reconnect != 'none';
+ if (!retry) _setStatus('disconnected');
+
+ _notifyListeners('/meta/handshake', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+
+ // Only try again if we haven't been disconnected and the
+ // advice permits us to try again
+ if (retry)
+ {
+ _increaseBackoff();
+ _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
+ _delayedHandshake();
+ }
+ };
+
+ function _connectSuccess(message)
+ {
+ var action = _isDisconnected() ? 'none' : (_advice.reconnect ? _advice.reconnect : 'retry');
+ if (!_isDisconnected()) _setStatus(action == 'retry' ? 'connecting' : 'disconnecting');
+
+ if (message.successful)
+ {
+ _debug('Connect successful');
+
+ // End the batch and allow held messages from the application
+ // to go to the server (see _handshake() where we start the batch).
+ // The batch is ended before notifying the listeners, so that
+ // listeners can batch other cometd operations
+ _endBatch(true);
+
+ // Notify the listeners after the status change but before the next connect
+ _notifyListeners('/meta/connect', message);
+
+ // Connect was successful.
+ // Normally, the advice will say "reconnect: 'retry', interval: 0"
+ // and the server will hold the request, so when a response returns
+ // we immediately call the server again (long polling)
+ switch (action)
+ {
+ case 'retry':
+ _resetBackoff();
+ _delayedConnect();
+ break;
+ default:
+ _resetBackoff();
+ _setStatus('disconnected');
+ break;
+ }
+ }
+ else
+ {
+ _debug('Connect unsuccessful');
+
+ // Notify the listeners after the status change but before the next action
+ _notifyListeners('/meta/connect', message);
+ _notifyListeners('/meta/unsuccessful', message);
+
+ // Connect was not successful.
+ // This may happen when the server crashed, the current clientId
+ // will be invalid, and the server will ask to handshake again
+ switch (action)
+ {
+ case 'retry':
+ _increaseBackoff();
+ _delayedConnect();
+ break;
+ case 'handshake':
+ // End the batch but do not deliver the messages until we connect successfully
+ _endBatch(false);
+ _resetBackoff();
+ _delayedHandshake();
+ break;
+ case 'none':
+ _resetBackoff();
+ _setStatus('disconnected');
+ break;
+ }
+ }
+ };
+
+ function _connectFailure(xhr, message)
+ {
+ _debug('Connect failure');
+
+ // Notify listeners
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/connect',
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'retry',
+ interval: _backoff
+ }
+ };
+ _notifyListeners('/meta/connect', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+
+ if (!_isDisconnected())
+ {
+ var action = _advice.reconnect ? _advice.reconnect : 'retry';
+ switch (action)
+ {
+ case 'retry':
+ _increaseBackoff();
+ _debug('Connect failure, backing off and retrying in {} ms', _backoff);
+ _delayedConnect();
+ break;
+ case 'handshake':
+ _resetBackoff();
+ _delayedHandshake();
+ break;
+ case 'none':
+ _resetBackoff();
+ break;
+ default:
+ _debug('Unrecognized reconnect value: {}', action);
+ break;
+ }
+ }
+ };
+
+ function _disconnectSuccess(message)
+ {
+ if (message.successful)
+ {
+ _debug('Disconnect successful');
+ _disconnect(false);
+ _notifyListeners('/meta/disconnect', message);
+ }
+ else
+ {
+ _debug('Disconnect unsuccessful');
+ _disconnect(true);
+ _notifyListeners('/meta/disconnect', message);
+ _notifyListeners('/meta/usuccessful', message);
+ }
+ };
+
+ function _disconnect(abort)
+ {
+ _cancelDelayedSend();
+ if (abort) _transport.abort();
+ _clientId = null;
+ _setStatus('disconnected');
+ _batch = 0;
+ _messageQueue = [];
+ _resetBackoff();
+ };
+
+ function _disconnectFailure(xhr, message)
+ {
+ _debug('Disconnect failure');
+ _disconnect(true);
+
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/disconnect',
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/disconnect', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ };
+
+ function _subscribeSuccess(message)
+ {
+ if (message.successful)
+ {
+ _debug('Subscribe successful');
+ _notifyListeners('/meta/subscribe', message);
+ }
+ else
+ {
+ _debug('Subscribe unsuccessful');
+ _notifyListeners('/meta/subscribe', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ };
+
+ function _subscribeFailure(xhr, message)
+ {
+ _debug('Subscribe failure');
+
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/subscribe',
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/subscribe', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ };
+
+ function _unsubscribeSuccess(message)
+ {
+ if (message.successful)
+ {
+ _debug('Unsubscribe successful');
+ _notifyListeners('/meta/unsubscribe', message);
+ }
+ else
+ {
+ _debug('Unsubscribe unsuccessful');
+ _notifyListeners('/meta/unsubscribe', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ };
+
+ function _unsubscribeFailure(xhr, message)
+ {
+ _debug('Unsubscribe failure');
+
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/unsubscribe',
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/unsubscribe', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ };
+
+ function _messageSuccess(message)
+ {
+ if (message.successful === undefined)
+ {
+ if (message.data)
+ {
+ // It is a plain message, and not a bayeux meta message
+ _notifyListeners(message.channel, message);
+ }
+ else
+ {
+ _debug('Unknown message {}', JSON.stringify(message));
+ }
+ }
+ else
+ {
+ if (message.successful)
+ {
+ _debug('Publish successful');
+ _notifyListeners('/meta/publish', message);
+ }
+ else
+ {
+ _debug('Publish unsuccessful');
+ _notifyListeners('/meta/publish', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ }
+ };
+
+ function _messageFailure(xhr, message)
+ {
+ _debug('Publish failure');
+
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: message.channel,
+ request: message,
+ xhr: xhr,
+ advice: {
+ action: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/publish', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ };
+
+ function _notifyListeners(channel, message)
+ {
+ // Notify direct listeners
+ _notify(channel, message);
+
+ // Notify the globbing listeners
+ var channelParts = channel.split("/");
+ var last = channelParts.length - 1;
+ for (var i = last; i > 0; --i)
+ {
+ var channelPart = channelParts.slice(0, i).join('/') + '/*';
+ // We don't want to notify /foo/* if the channel is /foo/bar/baz,
+ // so we stop at the first non recursive globbing
+ if (i == last) _notify(channelPart, message);
+ // Add the recursive globber and notify
+ channelPart += '*';
+ _notify(channelPart, message);
+ }
+ };
+
+ function _notify(channel, message)
+ {
+ var subscriptions = _listeners[channel];
+ if (subscriptions && subscriptions.length > 0)
+ {
+ for (var i = 0; i < subscriptions.length; ++i)
+ {
+ var subscription = subscriptions[i];
+ // Subscriptions may come and go, so the array may have 'holes'
+ if (subscription)
+ {
+ try
+ {
+ _debug('Notifying subscription: channel \'{}\', callback \'{}\'', channel, subscription.callback.name);
+ subscription.callback.call(subscription.scope, message);
+ }
+ catch (x)
+ {
+ // Ignore exceptions from callbacks
+ _warn('Exception during execution of callback \'{}\' on channel \'{}\' for message {}, exception: {}', subscription.callback.name, channel, JSON.stringify(message), x);
+ }
+ }
+ }
+ }
+ };
+
+ function _resetBackoff()
+ {
+ _backoff = 0;
+ };
+
+ function _increaseBackoff()
+ {
+ if (_backoff < _maxBackoff) _backoff += _backoffIncrement;
+ };
+
+ var _error = this._error = function(text, args)
+ {
+ _log('error', _format.apply(this, arguments));
+ };
+
+ var _warn = this._warn = function(text, args)
+ {
+ _log('warn', _format.apply(this, arguments));
+ };
+
+ var _info = this._info = function(text, args)
+ {
+ _log('info', _format.apply(this, arguments));
+ };
+
+ var _debug = this._debug = function(text, args)
+ {
+ _log('debug', _format.apply(this, arguments));
+ };
+
+ function _log(level, text)
+ {
+ var priority = _logPriorities[level];
+ var configPriority = _logPriorities[_logLevel];
+ if (!configPriority) configPriority = _logPriorities['info'];
+ if (priority >= configPriority)
+ {
+ if (window.console) window.console.log(text);
+ }
+ };
+
+ function _format(text)
+ {
+ var braces = /\{\}/g;
+ var result = '';
+ var start = 0;
+ var count = 0;
+ while (braces.test(text))
+ {
+ result += text.substr(start, braces.lastIndex - start - 2);
+ var arg = arguments[++count];
+ result += arg !== undefined ? arg : '{}';
+ start = braces.lastIndex;
+ }
+ result += text.substr(start, text.length - start);
+ return result;
+ };
+
+ function newLongPollingTransport()
+ {
+ return $.extend({}, new Transport('long-polling'), new LongPollingTransport());
+ };
+
+ function newCallbackPollingTransport()
+ {
+ return $.extend({}, new Transport('callback-polling'), new CallbackPollingTransport());
+ };
+
+ /**
+ * Base object with the common functionality for transports.
+ * The key responsibility is to allow at most 2 outstanding requests to the server,
+ * to avoid that requests are sent behind a long poll.
+ * To achieve this, we have one reserved request for the long poll, and all other
+ * requests are serialized one after the other.
+ */
+ var Transport = function(type)
+ {
+ var _maxRequests = 2;
+ var _requestIds = 0;
+ var _cometRequest = null;
+ var _requests = [];
+ var _packets = [];
+
+ this.getType = function()
+ {
+ return type;
+ };
+
+ this.send = function(packet, comet)
+ {
+ if (comet)
+ _cometSend(this, packet);
+ else
+ _send(this, packet);
+ };
+
+ function _cometSend(self, packet)
+ {
+ if (_cometRequest !== null) throw 'Concurrent comet requests not allowed, request ' + _cometRequest.id + ' not yet completed';
+
+ var requestId = ++_requestIds;
+ _debug('Beginning comet request {}', requestId);
+
+ var request = {id: requestId};
+ _debug('Delivering comet request {}', requestId);
+ self.deliver(packet, request);
+ _cometRequest = request;
+ };
+
+ function _send(self, packet)
+ {
+ var requestId = ++_requestIds;
+ _debug('Beginning request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);
+
+ var request = {id: requestId};
+ // Consider the comet request which should always be present
+ if (_requests.length < _maxRequests - 1)
+ {
+ _debug('Delivering request {}', requestId);
+ self.deliver(packet, request);
+ _requests.push(request);
+ }
+ else
+ {
+ _packets.push([packet, request]);
+ _debug('Queued request {}, {} queued requests', requestId, _packets.length);
+ }
+ };
+
+ this.complete = function(request, success, comet)
+ {
+ if (comet)
+ _cometComplete(request);
+ else
+ _complete(this, request, success);
+ };
+
+ function _cometComplete(request)
+ {
+ var requestId = request.id;
+ if (_cometRequest !== request) throw 'Comet request mismatch, completing request ' + requestId;
+
+ // Reset comet request
+ _cometRequest = null;
+ _debug('Ended comet request {}', requestId);
+ };
+
+ function _complete(self, request, success)
+ {
+ var requestId = request.id;
+ var index = $.inArray(request, _requests);
+ // The index can be negative the request has been aborted
+ if (index >= 0) _requests.splice(index, 1);
+ _debug('Ended request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);
+
+ if (_packets.length > 0)
+ {
+ var packet = _packets.shift();
+ if (success)
+ {
+ _debug('Dequeueing and sending request {}, {} queued requests', packet[1].id, _packets.length);
+ _send(self, packet[0]);
+ }
+ else
+ {
+ _debug('Dequeueing and failing request {}, {} queued requests', packet[1].id, _packets.length);
+ // Keep the semantic of calling response callbacks asynchronously after the request
+ setTimeout(function() { packet[0].onFailure(packet[1], 'error'); }, 0);
+ }
+ }
+ };
+
+ this.abort = function()
+ {
+ for (var i = 0; i < _requests.length; ++i)
+ {
+ var request = _requests[i];
+ _debug('Aborting request {}', request.id);
+ if (request.xhr) request.xhr.abort();
+ }
+ if (_cometRequest)
+ {
+ _debug('Aborting comet request {}', _cometRequest.id);
+ if (_cometRequest.xhr) _cometRequest.xhr.abort();
+ }
+ _cometRequest = null;
+ _requests = [];
+ _packets = [];
+ };
+ };
+
+ var LongPollingTransport = function()
+ {
+ this.deliver = function(packet, request)
+ {
+ request.xhr = $.ajax({
+ url: packet.url,
+ type: 'POST',
+ contentType: 'text/json;charset=UTF-8',
+ beforeSend: function(xhr)
+ {
+ xhr.setRequestHeader('Connection', 'Keep-Alive');
+ return true;
+ },
+ data: JSON.stringify(packet.messages),
+ success: function(response) { packet.onSuccess(request, response); },
+ error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
+ });
+ };
+ };
+
+ var CallbackPollingTransport = function()
+ {
+ var _maxLength = 2000;
+ this.deliver = function(packet, request)
+ {
+ // Microsoft Internet Explorer has a 2083 URL max length
+ // We must ensure that we stay within that length
+ var messages = JSON.stringify(packet.messages);
+ // Encode the messages because all brackets, quotes, commas, colons, etc
+ // present in the JSON will be URL encoded, taking many more characters
+ var urlLength = packet.url.length + encodeURI(messages).length;
+ _debug('URL length: {}', urlLength);
+ // Let's stay on the safe side and use 2000 instead of 2083
+ // also because we did not count few characters among which
+ // the parameter name 'message' and the parameter 'jsonp',
+ // which sum up to about 50 chars
+ if (urlLength > _maxLength)
+ {
+ var x = packet.messages.length > 1 ?
+ 'Too many bayeux messages in the same batch resulting in message too big ' +
+ '(' + urlLength + ' bytes, max is ' + _maxLength + ') for transport ' + this.getType() :
+ 'Bayeux message too big (' + urlLength + ' bytes, max is ' + _maxLength + ') ' +
+ 'for transport ' + this.getType();
+ // Keep the semantic of calling response callbacks asynchronously after the request
+ _setTimeout(function() { packet.onFailure(request, 'error', x); }, 0);
+ }
+ else
+ {
+ $.ajax({
+ url: packet.url,
+ type: 'GET',
+ dataType: 'jsonp',
+ jsonp: 'jsonp',
+ beforeSend: function(xhr)
+ {
+ xhr.setRequestHeader('Connection', 'Keep-Alive');
+ return true;
+ },
+ data:
+ {
+ // In callback-polling, the content must be sent via the 'message' parameter
+ message: messages
+ },
+ success: function(response) { packet.onSuccess(request, response); },
+ error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
+ });
+ }
+ };
+ };
+ };
+
+ /**
+ * The JS object that exposes the comet API to applications
+ */
+ $.cometd = new $.Cometd(); // The default instance
+
+})(jQuery);
diff --git a/plugins/Orbited/json2.js b/plugins/Orbited/json2.js
new file mode 100644
index 000000000..7e27df518
--- /dev/null
+++ b/plugins/Orbited/json2.js
@@ -0,0 +1,478 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-04-16
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the object holding the key.
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*global JSON */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ JSON = {};
+}
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
diff --git a/plugins/Orbited/updatetimeline.js b/plugins/Orbited/updatetimeline.js
new file mode 100644
index 000000000..170949e9b
--- /dev/null
+++ b/plugins/Orbited/updatetimeline.js
@@ -0,0 +1,154 @@
+// update the local timeline from a Comet server
+//
+
+var updater = function()
+{
+ var _server;
+ var _timeline;
+ var _userid;
+ var _replyurl;
+ var _favorurl;
+ var _deleteurl;
+ var _cometd;
+
+ return {
+ init: function(server, timeline, userid, replyurl, favorurl, deleteurl)
+ {
+ _cometd = $.cometd; // Uses the default Comet object
+ _cometd.setLogLevel('debug');
+ _cometd.init(server);
+ _server = server;
+ _timeline = timeline;
+ _userid = userid;
+ _favorurl = favorurl;
+ _replyurl = replyurl;
+ _deleteurl = deleteurl;
+ _cometd.subscribe(timeline, receive);
+ $(window).unload(leave);
+ }
+ }
+
+ function leave()
+ {
+ _cometd.disconnect();
+ }
+
+ function receive(message)
+ {
+ id = message.data.id;
+
+ // Don't add it if it already exists
+
+ if ($("#notice-"+id).length > 0) {
+ return;
+ }
+
+ var noticeItem = makeNoticeItem(message.data);
+ $("#notices_primary .notices").prepend(noticeItem, true);
+ $("#notices_primary .notice:first").css({display:"none"});
+ $("#notices_primary .notice:first").fadeIn(1000);
+ NoticeHover();
+ NoticeReply();
+ }
+
+ function makeNoticeItem(data)
+ {
+ user = data['user'];
+ html = data['html'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
+ source = data['source'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
+
+ ni = "
";
+
+ return dl;
+ }
+}();
+
--
cgit v1.2.3-54-g00ecf
From 0d6314052fed12fd626bfa5519853f1d6ecb6814 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Wed, 1 Jul 2009 00:58:44 -0400
Subject: rename plugin
---
plugins/Orbited/CometPlugin.php | 205 --------------------------------------
plugins/Orbited/OrbitedPlugin.php | 205 ++++++++++++++++++++++++++++++++++++++
2 files changed, 205 insertions(+), 205 deletions(-)
delete mode 100644 plugins/Orbited/CometPlugin.php
create mode 100644 plugins/Orbited/OrbitedPlugin.php
(limited to 'plugins')
diff --git a/plugins/Orbited/CometPlugin.php b/plugins/Orbited/CometPlugin.php
deleted file mode 100644
index 45251c66f..000000000
--- a/plugins/Orbited/CometPlugin.php
+++ /dev/null
@@ -1,205 +0,0 @@
-.
- *
- * @category Plugin
- * @package Laconica
- * @author Evan Prodromou
- * @copyright 2009 Control Yourself, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Plugin to do realtime updates using Comet
- *
- * @category Plugin
- * @package Laconica
- * @author Evan Prodromou
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-class CometPlugin extends Plugin
-{
- var $server = null;
-
- function __construct($server=null, $username=null, $password=null)
- {
- $this->server = $server;
- $this->username = $username;
- $this->password = $password;
-
- parent::__construct();
- }
-
- function onEndShowScripts($action)
- {
- $timeline = null;
-
- $this->log(LOG_DEBUG, 'got action ' . $action->trimmed('action'));
-
- switch ($action->trimmed('action')) {
- case 'public':
- $timeline = '/timelines/public';
- break;
- case 'tag':
- $tag = $action->trimmed('tag');
- if (!empty($tag)) {
- $timeline = '/timelines/tag/'.$tag;
- } else {
- return true;
- }
- break;
- default:
- return true;
- }
-
- $scripts = array('jquery.comet.js', 'json2.js', 'updatetimeline.js');
-
- foreach ($scripts as $script) {
- $action->element('script', array('type' => 'text/javascript',
- 'src' => common_path('plugins/Comet/'.$script)),
- ' ');
- }
-
- $user = common_current_user();
-
- if (!empty($user->id)) {
- $user_id = $user->id;
- } else {
- $user_id = 0;
- }
-
- $replyurl = common_local_url('newnotice');
- $favorurl = common_local_url('favor');
- // FIXME: need to find a better way to pass this pattern in
- $deleteurl = common_local_url('deletenotice',
- array('notice' => '0000000000'));
-
- $action->elementStart('script', array('type' => 'text/javascript'));
- $action->raw("$(document).ready(function() { updater.init(\"$this->server\", \"$timeline\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });");
- $action->elementEnd('script');
-
- return true;
- }
-
- function onEndNoticeSave($notice)
- {
- $this->log(LOG_INFO, "Called for save notice.");
-
- $timelines = array();
-
- // XXX: Add other timelines; this is just for the public one
-
- if ($notice->is_local ||
- ($notice->is_local == 0 && !common_config('public', 'localonly'))) {
- $timelines[] = '/timelines/public';
- }
-
- $tags = $this->getNoticeTags($notice);
-
- if (!empty($tags)) {
- foreach ($tags as $tag) {
- $timelines[] = '/timelines/tag/' . $tag;
- }
- }
-
- if (count($timelines) > 0) {
- // Require this, since we need it
- require_once(INSTALLDIR.'/plugins/Comet/bayeux.class.inc.php');
-
- $json = $this->noticeAsJson($notice);
-
- // Bayeux? Comet? Huh? These terms confuse me
- $bay = new Bayeux($this->server, $this->user, $this->password);
-
- foreach ($timelines as $timeline) {
- $this->log(LOG_INFO, "Posting notice $notice->id to '$timeline'.");
- $bay->publish($timeline, $json);
- }
-
- $bay = NULL;
- }
-
- return true;
- }
-
- function noticeAsJson($notice)
- {
- // FIXME: this code should be abstracted to a neutral third
- // party, like Notice::asJson(). I'm not sure of the ethics
- // of refactoring from within a plugin, so I'm just abusing
- // the TwitterApiAction method. Don't do this unless you're me!
-
- require_once(INSTALLDIR.'/lib/twitterapi.php');
-
- $act = new TwitterApiAction('/dev/null');
-
- $arr = $act->twitter_status_array($notice, true);
- $arr['url'] = $notice->bestUrl();
- $arr['html'] = htmlspecialchars($notice->rendered);
- $arr['source'] = htmlspecialchars($arr['source']);
-
- if (!empty($notice->reply_to)) {
- $reply_to = Notice::staticGet('id', $notice->reply_to);
- if (!empty($reply_to)) {
- $arr['in_reply_to_status_url'] = $reply_to->bestUrl();
- }
- $reply_to = null;
- }
-
- $profile = $notice->getProfile();
- $arr['user']['profile_url'] = $profile->profileurl;
-
- return $arr;
- }
-
- function getNoticeTags($notice)
- {
- $tags = null;
-
- $nt = new Notice_tag();
- $nt->notice_id = $notice->id;
-
- if ($nt->find()) {
- $tags = array();
- while ($nt->fetch()) {
- $tags[] = $nt->tag;
- }
- }
-
- $nt->free();
- $nt = null;
-
- return $tags;
- }
-
- // Push this up to Plugin
-
- function log($level, $msg)
- {
- common_log($level, get_class($this) . ': '.$msg);
- }
-}
diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php
new file mode 100644
index 000000000..45251c66f
--- /dev/null
+++ b/plugins/Orbited/OrbitedPlugin.php
@@ -0,0 +1,205 @@
+.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Plugin to do realtime updates using Comet
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class CometPlugin extends Plugin
+{
+ var $server = null;
+
+ function __construct($server=null, $username=null, $password=null)
+ {
+ $this->server = $server;
+ $this->username = $username;
+ $this->password = $password;
+
+ parent::__construct();
+ }
+
+ function onEndShowScripts($action)
+ {
+ $timeline = null;
+
+ $this->log(LOG_DEBUG, 'got action ' . $action->trimmed('action'));
+
+ switch ($action->trimmed('action')) {
+ case 'public':
+ $timeline = '/timelines/public';
+ break;
+ case 'tag':
+ $tag = $action->trimmed('tag');
+ if (!empty($tag)) {
+ $timeline = '/timelines/tag/'.$tag;
+ } else {
+ return true;
+ }
+ break;
+ default:
+ return true;
+ }
+
+ $scripts = array('jquery.comet.js', 'json2.js', 'updatetimeline.js');
+
+ foreach ($scripts as $script) {
+ $action->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('plugins/Comet/'.$script)),
+ ' ');
+ }
+
+ $user = common_current_user();
+
+ if (!empty($user->id)) {
+ $user_id = $user->id;
+ } else {
+ $user_id = 0;
+ }
+
+ $replyurl = common_local_url('newnotice');
+ $favorurl = common_local_url('favor');
+ // FIXME: need to find a better way to pass this pattern in
+ $deleteurl = common_local_url('deletenotice',
+ array('notice' => '0000000000'));
+
+ $action->elementStart('script', array('type' => 'text/javascript'));
+ $action->raw("$(document).ready(function() { updater.init(\"$this->server\", \"$timeline\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });");
+ $action->elementEnd('script');
+
+ return true;
+ }
+
+ function onEndNoticeSave($notice)
+ {
+ $this->log(LOG_INFO, "Called for save notice.");
+
+ $timelines = array();
+
+ // XXX: Add other timelines; this is just for the public one
+
+ if ($notice->is_local ||
+ ($notice->is_local == 0 && !common_config('public', 'localonly'))) {
+ $timelines[] = '/timelines/public';
+ }
+
+ $tags = $this->getNoticeTags($notice);
+
+ if (!empty($tags)) {
+ foreach ($tags as $tag) {
+ $timelines[] = '/timelines/tag/' . $tag;
+ }
+ }
+
+ if (count($timelines) > 0) {
+ // Require this, since we need it
+ require_once(INSTALLDIR.'/plugins/Comet/bayeux.class.inc.php');
+
+ $json = $this->noticeAsJson($notice);
+
+ // Bayeux? Comet? Huh? These terms confuse me
+ $bay = new Bayeux($this->server, $this->user, $this->password);
+
+ foreach ($timelines as $timeline) {
+ $this->log(LOG_INFO, "Posting notice $notice->id to '$timeline'.");
+ $bay->publish($timeline, $json);
+ }
+
+ $bay = NULL;
+ }
+
+ return true;
+ }
+
+ function noticeAsJson($notice)
+ {
+ // FIXME: this code should be abstracted to a neutral third
+ // party, like Notice::asJson(). I'm not sure of the ethics
+ // of refactoring from within a plugin, so I'm just abusing
+ // the TwitterApiAction method. Don't do this unless you're me!
+
+ require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+ $act = new TwitterApiAction('/dev/null');
+
+ $arr = $act->twitter_status_array($notice, true);
+ $arr['url'] = $notice->bestUrl();
+ $arr['html'] = htmlspecialchars($notice->rendered);
+ $arr['source'] = htmlspecialchars($arr['source']);
+
+ if (!empty($notice->reply_to)) {
+ $reply_to = Notice::staticGet('id', $notice->reply_to);
+ if (!empty($reply_to)) {
+ $arr['in_reply_to_status_url'] = $reply_to->bestUrl();
+ }
+ $reply_to = null;
+ }
+
+ $profile = $notice->getProfile();
+ $arr['user']['profile_url'] = $profile->profileurl;
+
+ return $arr;
+ }
+
+ function getNoticeTags($notice)
+ {
+ $tags = null;
+
+ $nt = new Notice_tag();
+ $nt->notice_id = $notice->id;
+
+ if ($nt->find()) {
+ $tags = array();
+ while ($nt->fetch()) {
+ $tags[] = $nt->tag;
+ }
+ }
+
+ $nt->free();
+ $nt = null;
+
+ return $tags;
+ }
+
+ // Push this up to Plugin
+
+ function log($level, $msg)
+ {
+ common_log($level, get_class($this) . ': '.$msg);
+ }
+}
--
cgit v1.2.3-54-g00ecf
From 630dcda56fa056389bd715e3d1df704addf2a8ca Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Mon, 20 Jul 2009 19:17:39 -0400
Subject: remove unused files for OrbitedPlugin
---
plugins/Orbited/bayeux.class.inc.php | 134 ----
plugins/Orbited/jquery.comet.js | 1451 ----------------------------------
plugins/Orbited/json2.js | 478 -----------
3 files changed, 2063 deletions(-)
delete mode 100644 plugins/Orbited/bayeux.class.inc.php
delete mode 100644 plugins/Orbited/jquery.comet.js
delete mode 100644 plugins/Orbited/json2.js
(limited to 'plugins')
diff --git a/plugins/Orbited/bayeux.class.inc.php b/plugins/Orbited/bayeux.class.inc.php
deleted file mode 100644
index 39ad8a8fc..000000000
--- a/plugins/Orbited/bayeux.class.inc.php
+++ /dev/null
@@ -1,134 +0,0 @@
- http://morglog.alleycatracing.com
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-class Bayeux
-{
- private $oCurl = '';
- private $nNextId = 0;
-
- private $sUser = '';
- private $sPassword = '';
-
- public $sUrl = '';
-
- function __construct($sUrl, $sUser='', $sPassword='')
- {
- $this->sUrl = $sUrl;
-
- $this->oCurl = curl_init();
-
- $aHeaders = array();
- $aHeaders[] = 'Connection: Keep-Alive';
-
- curl_setopt($this->oCurl, CURLOPT_URL, $sUrl);
- curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders);
- curl_setopt($this->oCurl, CURLOPT_HEADER, 0);
- curl_setopt($this->oCurl, CURLOPT_POST, 1);
- curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER,1);
-
- if (!is_null($sUser) && mb_strlen($sUser) > 0) {
- curl_setopt($this->oCurl, CURLOPT_USERPWD,"$sUser:$sPassword");
- }
-
- $this->handShake();
- }
-
- function __destruct()
- {
- $this->disconnect();
- }
-
- function handShake()
- {
- $msgHandshake = array();
- $msgHandshake['channel'] = '/meta/handshake';
- $msgHandshake['version'] = "1.0";
- $msgHandshake['minimumVersion'] = "0.9";
- $msgHandshake['supportedConnectionTypes'] = array('long-polling');
- $msgHandshake['id'] = $this->nNextId++;
-
- curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));
-
- $data = curl_exec($this->oCurl);
-
- if(curl_errno($this->oCurl))
- die("Error: " . curl_error($this->oCurl));
-
- $oReturn = json_decode($data);
-
- if (is_array($oReturn)) {
- $oReturn = $oReturn[0];
- }
-
- $bSuccessful = ($oReturn->successful) ? true : false;
-
- if($bSuccessful)
- {
- $this->clientId = $oReturn->clientId;
-
- $this->connect();
- }
- }
-
- public function connect()
- {
- $aMsg['channel'] = '/meta/connect';
- $aMsg['id'] = $this->nNextId++;
- $aMsg['clientId'] = $this->clientId;
- $aMsg['connectionType'] = 'long-polling';
-
- curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));
-
- $data = curl_exec($this->oCurl);
- }
-
- function disconnect()
- {
- $msgHandshake = array();
- $msgHandshake['channel'] = '/meta/disconnect';
- $msgHandshake['id'] = $this->nNextId++;
- $msgHandshake['clientId'] = $this->clientId;
-
- curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake)))));
-
- curl_exec($this->oCurl);
- }
-
- public function publish($sChannel, $oData)
- {
- if(!$sChannel || !$oData)
- return;
-
- $aMsg = array();
-
- $aMsg['channel'] = $sChannel;
- $aMsg['id'] = $this->nNextId++;
- $aMsg['data'] = $oData;
- $aMsg['clientId'] = $this->clientId;
-
- curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg)))));
-
- $data = curl_exec($this->oCurl);
-// var_dump($data);
- }
-}
diff --git a/plugins/Orbited/jquery.comet.js b/plugins/Orbited/jquery.comet.js
deleted file mode 100644
index 6de437fa8..000000000
--- a/plugins/Orbited/jquery.comet.js
+++ /dev/null
@@ -1,1451 +0,0 @@
-/**
- * Copyright 2008 Mort Bay Consulting Pty. Ltd.
- * Dual licensed under the Apache License 2.0 and the MIT license.
- * ----------------------------------------------------------------------------
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http: *www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ----------------------------------------------------------------------------
- * Licensed under the MIT license;
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- * ----------------------------------------------------------------------------
- * $Revision$ $Date$
- */
-(function($)
-{
- /**
- * The constructor for a Comet object.
- * There is a default Comet instance already created at the variable $.cometd,
- * and hence that can be used to start a comet conversation with a server.
- * In the rare case a page needs more than one comet conversation, a new instance can be
- * created via:
- *
- * var url2 = ...;
- * var cometd2 = new $.Cometd();
- * cometd2.init(url2);
- *
- */
- $.Cometd = function(name)
- {
- var _name = name || 'default';
- var _logPriorities = { debug: 1, info: 2, warn: 3, error: 4 };
- var _logLevel = 'info';
- var _url;
- var _xd = false;
- var _transport;
- var _status = 'disconnected';
- var _messageId = 0;
- var _clientId = null;
- var _batch = 0;
- var _messageQueue = [];
- var _listeners = {};
- var _backoff = 0;
- var _backoffIncrement = 1000;
- var _maxBackoff = 60000;
- var _scheduledSend = null;
- var _extensions = [];
- var _advice = {};
- var _handshakeProps;
-
- /**
- * Returns the name assigned to this Comet object, or the string 'default'
- * if no name has been explicitely passed as parameter to the constructor.
- */
- this.getName = function()
- {
- return _name;
- };
-
- /**
- * Configures the initial comet communication with the comet server.
- * @param cometURL the URL of the comet server
- */
- this.configure = function(cometURL)
- {
- _configure(cometURL);
- };
-
- function _configure(cometURL)
- {
- _url = cometURL;
- _debug('Initializing comet with url: {}', _url);
-
- // Check immediately if we're cross domain
- // If cross domain, the handshake must not send the long polling transport type
- var urlParts = /(^https?:)?(\/\/(([^:\/\?#]+)(:(\d+))?))?([^\?#]*)/.exec(cometURL);
- if (urlParts[3]) _xd = urlParts[3] != location.host;
-
- // Temporary setup a transport to send the initial handshake
- // The transport may be changed as a result of handshake
- if (_xd)
- _transport = newCallbackPollingTransport();
- else
- _transport = newLongPollingTransport();
- _debug('Initial transport is {}', _transport.getType());
- };
-
- /**
- * Configures and establishes the comet communication with the comet server
- * via a handshake and a subsequent connect.
- * @param cometURL the URL of the comet server
- * @param handshakeProps an object to be merged with the handshake message
- * @see #configure(cometURL)
- * @see #handshake(handshakeProps)
- */
- this.init = function(cometURL, handshakeProps)
- {
- _configure(cometURL);
- _handshake(handshakeProps);
- };
-
- /**
- * Establishes the comet communication with the comet server
- * via a handshake and a subsequent connect.
- * @param handshakeProps an object to be merged with the handshake message
- */
- this.handshake = function(handshakeProps)
- {
- _handshake(handshakeProps);
- };
-
- /**
- * Disconnects from the comet server.
- * @param disconnectProps an object to be merged with the disconnect message
- */
- this.disconnect = function(disconnectProps)
- {
- var bayeuxMessage = {
- channel: '/meta/disconnect'
- };
- var message = $.extend({}, disconnectProps, bayeuxMessage);
- // Deliver immediately
- // The handshake and connect mechanism make use of startBatch(), and in case
- // of a failed handshake the disconnect would not be delivered if using _send().
- _setStatus('disconnecting');
- _deliver([message], false);
- };
-
- /**
- * Marks the start of a batch of application messages to be sent to the server
- * in a single request, obtaining a single response containing (possibly) many
- * application reply messages.
- * Messages are held in a queue and not sent until {@link #endBatch()} is called.
- * If startBatch() is called multiple times, then an equal number of endBatch()
- * calls must be made to close and send the batch of messages.
- * @see #endBatch()
- */
- this.startBatch = function()
- {
- _startBatch();
- };
-
- /**
- * Marks the end of a batch of application messages to be sent to the server
- * in a single request.
- * @see #startBatch()
- */
- this.endBatch = function()
- {
- _endBatch(true);
- };
-
- /**
- * Subscribes to the given channel, performing the given callback in the given scope
- * when a message for the channel arrives.
- * @param channel the channel to subscribe to
- * @param scope the scope of the callback
- * @param callback the callback to call when a message is delivered to the channel
- * @param subscribeProps an object to be merged with the subscribe message
- * @return the subscription handle to be passed to {@link #unsubscribe(object)}
- */
- this.subscribe = function(channel, scope, callback, subscribeProps)
- {
- var subscription = this.addListener(channel, scope, callback);
-
- // Send the subscription message after the subscription registration to avoid
- // races where the server would deliver a message to the subscribers, but here
- // on the client the subscription has not been added yet to the data structures
- var bayeuxMessage = {
- channel: '/meta/subscribe',
- subscription: channel
- };
- var message = $.extend({}, subscribeProps, bayeuxMessage);
- _send(message);
-
- return subscription;
- };
-
- /**
- * Unsubscribes the subscription obtained with a call to {@link #subscribe(string, object, function)}.
- * @param subscription the subscription to unsubscribe.
- */
- this.unsubscribe = function(subscription, unsubscribeProps)
- {
- // Remove the local listener before sending the message
- // This ensures that if the server fails, this client does not get notifications
- this.removeListener(subscription);
- var bayeuxMessage = {
- channel: '/meta/unsubscribe',
- subscription: subscription[0]
- };
- var message = $.extend({}, unsubscribeProps, bayeuxMessage);
- _send(message);
- };
-
- /**
- * Publishes a message on the given channel, containing the given content.
- * @param channel the channel to publish the message to
- * @param content the content of the message
- * @param publishProps an object to be merged with the publish message
- */
- this.publish = function(channel, content, publishProps)
- {
- var bayeuxMessage = {
- channel: channel,
- data: content
- };
- var message = $.extend({}, publishProps, bayeuxMessage);
- _send(message);
- };
-
- /**
- * Adds a listener for bayeux messages, performing the given callback in the given scope
- * when a message for the given channel arrives.
- * @param channel the channel the listener is interested to
- * @param scope the scope of the callback
- * @param callback the callback to call when a message is delivered to the channel
- * @returns the subscription handle to be passed to {@link #removeListener(object)}
- * @see #removeListener(object)
- */
- this.addListener = function(channel, scope, callback)
- {
- // The data structure is a map, where each subscription
- // holds the callback to be called and its scope.
-
- // Normalize arguments
- if (!callback)
- {
- callback = scope;
- scope = undefined;
- }
-
- var subscription = {
- scope: scope,
- callback: callback
- };
-
- var subscriptions = _listeners[channel];
- if (!subscriptions)
- {
- subscriptions = [];
- _listeners[channel] = subscriptions;
- }
- // Pushing onto an array appends at the end and returns the id associated with the element increased by 1.
- // Note that if:
- // a.push('a'); var hb=a.push('b'); delete a[hb-1]; var hc=a.push('c');
- // then:
- // hc==3, a.join()=='a',,'c', a.length==3
- var subscriptionIndex = subscriptions.push(subscription) - 1;
- _debug('Added listener: channel \'{}\', callback \'{}\', index {}', channel, callback.name, subscriptionIndex);
-
- // The subscription to allow removal of the listener is made of the channel and the index
- return [channel, subscriptionIndex];
- };
-
- /**
- * Removes the subscription obtained with a call to {@link #addListener(string, object, function)}.
- * @param subscription the subscription to unsubscribe.
- */
- this.removeListener = function(subscription)
- {
- var subscriptions = _listeners[subscription[0]];
- if (subscriptions)
- {
- delete subscriptions[subscription[1]];
- _debug('Removed listener: channel \'{}\', index {}', subscription[0], subscription[1]);
- }
- };
-
- /**
- * Removes all listeners registered with {@link #addListener(channel, scope, callback)} or
- * {@link #subscribe(channel, scope, callback)}.
- */
- this.clearListeners = function()
- {
- _listeners = {};
- };
-
- /**
- * Returns a string representing the status of the bayeux communication with the comet server.
- */
- this.getStatus = function()
- {
- return _status;
- };
-
- /**
- * Sets the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
- * Default value is 1 second, which means if there is a persistent failure the retries will happen
- * after 1 second, then after 2 seconds, then after 3 seconds, etc. So for example with 15 seconds of
- * elapsed time, there will be 5 retries (at 1, 3, 6, 10 and 15 seconds elapsed).
- * @param period the backoff period to set
- * @see #getBackoffIncrement()
- */
- this.setBackoffIncrement = function(period)
- {
- _backoffIncrement = period;
- };
-
- /**
- * Returns the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.
- * @see #setBackoffIncrement(period)
- */
- this.getBackoffIncrement = function()
- {
- return _backoffIncrement;
- };
-
- /**
- * Returns the backoff period to wait before retrying an unsuccessful or failed message.
- */
- this.getBackoffPeriod = function()
- {
- return _backoff;
- };
-
- /**
- * Sets the log level for console logging.
- * Valid values are the strings 'error', 'warn', 'info' and 'debug', from
- * less verbose to more verbose.
- * @param level the log level string
- */
- this.setLogLevel = function(level)
- {
- _logLevel = level;
- };
-
- /**
- * Registers an extension whose callbacks are called for every incoming message
- * (that comes from the server to this client implementation) and for every
- * outgoing message (that originates from this client implementation for the
- * server).
- * The format of the extension object is the following:
- *
- * {
- * incoming: function(message) { ... },
- * outgoing: function(message) { ... }
- * }
- * Both properties are optional, but if they are present they will be called
- * respectively for each incoming message and for each outgoing message.
- *
- * @param name the name of the extension
- * @param extension the extension to register
- * @return true if the extension was registered, false otherwise
- * @see #unregisterExtension(name)
- */
- this.registerExtension = function(name, extension)
- {
- var existing = false;
- for (var i = 0; i < _extensions.length; ++i)
- {
- var existingExtension = _extensions[i];
- if (existingExtension.name == name)
- {
- existing = true;
- return false;
- }
- }
- if (!existing)
- {
- _extensions.push({
- name: name,
- extension: extension
- });
- _debug('Registered extension \'{}\'', name);
- return true;
- }
- else
- {
- _info('Could not register extension with name \'{}\': another extension with the same name already exists');
- return false;
- }
- };
-
- /**
- * Unregister an extension previously registered with
- * {@link #registerExtension(name, extension)}.
- * @param name the name of the extension to unregister.
- * @return true if the extension was unregistered, false otherwise
- */
- this.unregisterExtension = function(name)
- {
- var unregistered = false;
- $.each(_extensions, function(index, extension)
- {
- if (extension.name == name)
- {
- _extensions.splice(index, 1);
- unregistered = true;
- _debug('Unregistered extension \'{}\'', name);
- return false;
- }
- });
- return unregistered;
- };
-
- /**
- * Starts a the batch of messages to be sent in a single request.
- * @see _endBatch(deliverMessages)
- */
- function _startBatch()
- {
- ++_batch;
- };
-
- /**
- * Ends the batch of messages to be sent in a single request,
- * optionally delivering messages present in the message queue depending
- * on the given argument.
- * @param deliverMessages whether to deliver the messages in the queue or not
- * @see _startBatch()
- */
- function _endBatch(deliverMessages)
- {
- --_batch;
- if (_batch < 0) _batch = 0;
- if (deliverMessages && _batch == 0 && !_isDisconnected())
- {
- var messages = _messageQueue;
- _messageQueue = [];
- if (messages.length > 0) _deliver(messages, false);
- }
- };
-
- function _nextMessageId()
- {
- return ++_messageId;
- };
-
- /**
- * Converts the given response into an array of bayeux messages
- * @param response the response to convert
- * @return an array of bayeux messages obtained by converting the response
- */
- function _convertToMessages(response)
- {
- if (response === undefined) return [];
- if (response instanceof Array) return response;
- if (response instanceof String || typeof response == 'string') return eval('(' + response + ')');
- if (response instanceof Object) return [response];
- throw 'Conversion Error ' + response + ', typeof ' + (typeof response);
- };
-
- function _setStatus(newStatus)
- {
- _debug('{} -> {}', _status, newStatus);
- _status = newStatus;
- };
-
- function _isDisconnected()
- {
- return _status == 'disconnecting' || _status == 'disconnected';
- };
-
- /**
- * Sends the initial handshake message
- */
- function _handshake(handshakeProps)
- {
- _debug('Starting handshake');
- _clientId = null;
-
- // Start a batch.
- // This is needed because handshake and connect are async.
- // It may happen that the application calls init() then subscribe()
- // and the subscribe message is sent before the connect message, if
- // the subscribe message is not held until the connect message is sent.
- // So here we start a batch to hold temporarly any message until
- // the connection is fully established.
- _batch = 0;
- _startBatch();
-
- // Save the original properties provided by the user
- // Deep copy to avoid the user to be able to change them later
- _handshakeProps = $.extend(true, {}, handshakeProps);
-
- var bayeuxMessage = {
- version: '1.0',
- minimumVersion: '0.9',
- channel: '/meta/handshake',
- supportedConnectionTypes: _xd ? ['callback-polling'] : ['long-polling', 'callback-polling']
- };
- // Do not allow the user to mess with the required properties,
- // so merge first the user properties and *then* the bayeux message
- var message = $.extend({}, handshakeProps, bayeuxMessage);
-
- // We started a batch to hold the application messages,
- // so here we must bypass it and deliver immediately.
- _setStatus('handshaking');
- _deliver([message], false);
- };
-
- function _findTransport(handshakeResponse)
- {
- var transportTypes = handshakeResponse.supportedConnectionTypes;
- if (_xd)
- {
- // If we are cross domain, check if the server supports it, that's the only option
- if ($.inArray('callback-polling', transportTypes) >= 0) return _transport;
- }
- else
- {
- // Check if we can keep long-polling
- if ($.inArray('long-polling', transportTypes) >= 0) return _transport;
-
- // The server does not support long-polling
- if ($.inArray('callback-polling', transportTypes) >= 0) return newCallbackPollingTransport();
- }
- return null;
- };
-
- function _delayedHandshake()
- {
- _setStatus('handshaking');
- _delayedSend(function()
- {
- _handshake(_handshakeProps);
- });
- };
-
- function _delayedConnect()
- {
- _setStatus('connecting');
- _delayedSend(function()
- {
- _connect();
- });
- };
-
- function _delayedSend(operation)
- {
- _cancelDelayedSend();
- var delay = _backoff;
- _debug("Delayed send: backoff {}, interval {}", _backoff, _advice.interval);
- if (_advice.interval && _advice.interval > 0)
- delay += _advice.interval;
- _scheduledSend = _setTimeout(operation, delay);
- };
-
- function _cancelDelayedSend()
- {
- if (_scheduledSend !== null) clearTimeout(_scheduledSend);
- _scheduledSend = null;
- };
-
- function _setTimeout(funktion, delay)
- {
- return setTimeout(function()
- {
- try
- {
- funktion();
- }
- catch (x)
- {
- _debug('Exception during scheduled execution of function \'{}\': {}', funktion.name, x);
- }
- }, delay);
- };
-
- /**
- * Sends the connect message
- */
- function _connect()
- {
- _debug('Starting connect');
- var message = {
- channel: '/meta/connect',
- connectionType: _transport.getType()
- };
- _setStatus('connecting');
- _deliver([message], true);
- _setStatus('connected');
- };
-
- function _send(message)
- {
- if (_batch > 0)
- _messageQueue.push(message);
- else
- _deliver([message], false);
- };
-
- /**
- * Delivers the messages to the comet server
- * @param messages the array of messages to send
- */
- function _deliver(messages, comet)
- {
- // We must be sure that the messages have a clientId.
- // This is not guaranteed since the handshake may take time to return
- // (and hence the clientId is not known yet) and the application
- // may create other messages.
- $.each(messages, function(index, message)
- {
- message['id'] = _nextMessageId();
- if (_clientId) message['clientId'] = _clientId;
- messages[index] = _applyOutgoingExtensions(message);
- });
-
- var self = this;
- var envelope = {
- url: _url,
- messages: messages,
- onSuccess: function(request, response)
- {
- try
- {
- _handleSuccess.call(self, request, response, comet);
- }
- catch (x)
- {
- _debug('Exception during execution of success callback: {}', x);
- }
- },
- onFailure: function(request, reason, exception)
- {
- try
- {
- _handleFailure.call(self, request, messages, reason, exception, comet);
- }
- catch (x)
- {
- _debug('Exception during execution of failure callback: {}', x);
- }
- }
- };
- _debug('Sending request to {}, message(s): {}', envelope.url, JSON.stringify(envelope.messages));
- _transport.send(envelope, comet);
- };
-
- function _applyIncomingExtensions(message)
- {
- for (var i = 0; i < _extensions.length; ++i)
- {
- var extension = _extensions[i];
- var callback = extension.extension.incoming;
- if (callback && typeof callback === 'function')
- {
- _debug('Calling incoming extension \'{}\', callback \'{}\'', extension.name, callback.name);
- message = _applyExtension(extension.name, callback, message) || message;
- }
- }
- return message;
- };
-
- function _applyOutgoingExtensions(message)
- {
- for (var i = 0; i < _extensions.length; ++i)
- {
- var extension = _extensions[i];
- var callback = extension.extension.outgoing;
- if (callback && typeof callback === 'function')
- {
- _debug('Calling outgoing extension \'{}\', callback \'{}\'', extension.name, callback.name);
- message = _applyExtension(extension.name, callback, message) || message;
- }
- }
- return message;
- };
-
- function _applyExtension(name, callback, message)
- {
- try
- {
- return callback(message);
- }
- catch (x)
- {
- _debug('Exception during execution of extension \'{}\': {}', name, x);
- return message;
- }
- };
-
- function _handleSuccess(request, response, comet)
- {
- var messages = _convertToMessages(response);
- _debug('Received response {}', JSON.stringify(messages));
-
- // Signal the transport it can deliver other queued requests
- _transport.complete(request, true, comet);
-
- for (var i = 0; i < messages.length; ++i)
- {
- var message = messages[i];
- message = _applyIncomingExtensions(message);
-
- if (message.advice) _advice = message.advice;
-
- var channel = message.channel;
- switch (channel)
- {
- case '/meta/handshake':
- _handshakeSuccess(message);
- break;
- case '/meta/connect':
- _connectSuccess(message);
- break;
- case '/meta/disconnect':
- _disconnectSuccess(message);
- break;
- case '/meta/subscribe':
- _subscribeSuccess(message);
- break;
- case '/meta/unsubscribe':
- _unsubscribeSuccess(message);
- break;
- default:
- _messageSuccess(message);
- break;
- }
- }
- };
-
- function _handleFailure(request, messages, reason, exception, comet)
- {
- var xhr = request.xhr;
- _debug('Request failed, status: {}, reason: {}, exception: {}', xhr && xhr.status, reason, exception);
-
- // Signal the transport it can deliver other queued requests
- _transport.complete(request, false, comet);
-
- for (var i = 0; i < messages.length; ++i)
- {
- var message = messages[i];
- var channel = message.channel;
- switch (channel)
- {
- case '/meta/handshake':
- _handshakeFailure(xhr, message);
- break;
- case '/meta/connect':
- _connectFailure(xhr, message);
- break;
- case '/meta/disconnect':
- _disconnectFailure(xhr, message);
- break;
- case '/meta/subscribe':
- _subscribeFailure(xhr, message);
- break;
- case '/meta/unsubscribe':
- _unsubscribeFailure(xhr, message);
- break;
- default:
- _messageFailure(xhr, message);
- break;
- }
- }
- };
-
- function _handshakeSuccess(message)
- {
- if (message.successful)
- {
- _debug('Handshake successful');
- // Save clientId, figure out transport, then follow the advice to connect
- _clientId = message.clientId;
-
- var newTransport = _findTransport(message);
- if (newTransport === null)
- {
- throw 'Could not agree on transport with server';
- }
- else
- {
- if (_transport.getType() != newTransport.getType())
- {
- _debug('Changing transport from {} to {}', _transport.getType(), newTransport.getType());
- _transport = newTransport;
- }
- }
-
- // Notify the listeners
- // Here the new transport is in place, as well as the clientId, so
- // the listener can perform a publish() if it wants, and the listeners
- // are notified before the connect below.
- _notifyListeners('/meta/handshake', message);
-
- var action = _advice.reconnect ? _advice.reconnect : 'retry';
- switch (action)
- {
- case 'retry':
- _delayedConnect();
- break;
- default:
- break;
- }
- }
- else
- {
- _debug('Handshake unsuccessful');
-
- var retry = !_isDisconnected() && _advice.reconnect != 'none';
- if (!retry) _setStatus('disconnected');
-
- _notifyListeners('/meta/handshake', message);
- _notifyListeners('/meta/unsuccessful', message);
-
- // Only try again if we haven't been disconnected and
- // the advice permits us to retry the handshake
- if (retry)
- {
- _increaseBackoff();
- _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
- _delayedHandshake();
- }
- }
- };
-
- function _handshakeFailure(xhr, message)
- {
- _debug('Handshake failure');
-
- // Notify listeners
- var failureMessage = {
- successful: false,
- failure: true,
- channel: '/meta/handshake',
- request: message,
- xhr: xhr,
- advice: {
- action: 'retry',
- interval: _backoff
- }
- };
-
- var retry = !_isDisconnected() && _advice.reconnect != 'none';
- if (!retry) _setStatus('disconnected');
-
- _notifyListeners('/meta/handshake', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
-
- // Only try again if we haven't been disconnected and the
- // advice permits us to try again
- if (retry)
- {
- _increaseBackoff();
- _debug('Handshake failure, backing off and retrying in {} ms', _backoff);
- _delayedHandshake();
- }
- };
-
- function _connectSuccess(message)
- {
- var action = _isDisconnected() ? 'none' : (_advice.reconnect ? _advice.reconnect : 'retry');
- if (!_isDisconnected()) _setStatus(action == 'retry' ? 'connecting' : 'disconnecting');
-
- if (message.successful)
- {
- _debug('Connect successful');
-
- // End the batch and allow held messages from the application
- // to go to the server (see _handshake() where we start the batch).
- // The batch is ended before notifying the listeners, so that
- // listeners can batch other cometd operations
- _endBatch(true);
-
- // Notify the listeners after the status change but before the next connect
- _notifyListeners('/meta/connect', message);
-
- // Connect was successful.
- // Normally, the advice will say "reconnect: 'retry', interval: 0"
- // and the server will hold the request, so when a response returns
- // we immediately call the server again (long polling)
- switch (action)
- {
- case 'retry':
- _resetBackoff();
- _delayedConnect();
- break;
- default:
- _resetBackoff();
- _setStatus('disconnected');
- break;
- }
- }
- else
- {
- _debug('Connect unsuccessful');
-
- // Notify the listeners after the status change but before the next action
- _notifyListeners('/meta/connect', message);
- _notifyListeners('/meta/unsuccessful', message);
-
- // Connect was not successful.
- // This may happen when the server crashed, the current clientId
- // will be invalid, and the server will ask to handshake again
- switch (action)
- {
- case 'retry':
- _increaseBackoff();
- _delayedConnect();
- break;
- case 'handshake':
- // End the batch but do not deliver the messages until we connect successfully
- _endBatch(false);
- _resetBackoff();
- _delayedHandshake();
- break;
- case 'none':
- _resetBackoff();
- _setStatus('disconnected');
- break;
- }
- }
- };
-
- function _connectFailure(xhr, message)
- {
- _debug('Connect failure');
-
- // Notify listeners
- var failureMessage = {
- successful: false,
- failure: true,
- channel: '/meta/connect',
- request: message,
- xhr: xhr,
- advice: {
- action: 'retry',
- interval: _backoff
- }
- };
- _notifyListeners('/meta/connect', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
-
- if (!_isDisconnected())
- {
- var action = _advice.reconnect ? _advice.reconnect : 'retry';
- switch (action)
- {
- case 'retry':
- _increaseBackoff();
- _debug('Connect failure, backing off and retrying in {} ms', _backoff);
- _delayedConnect();
- break;
- case 'handshake':
- _resetBackoff();
- _delayedHandshake();
- break;
- case 'none':
- _resetBackoff();
- break;
- default:
- _debug('Unrecognized reconnect value: {}', action);
- break;
- }
- }
- };
-
- function _disconnectSuccess(message)
- {
- if (message.successful)
- {
- _debug('Disconnect successful');
- _disconnect(false);
- _notifyListeners('/meta/disconnect', message);
- }
- else
- {
- _debug('Disconnect unsuccessful');
- _disconnect(true);
- _notifyListeners('/meta/disconnect', message);
- _notifyListeners('/meta/usuccessful', message);
- }
- };
-
- function _disconnect(abort)
- {
- _cancelDelayedSend();
- if (abort) _transport.abort();
- _clientId = null;
- _setStatus('disconnected');
- _batch = 0;
- _messageQueue = [];
- _resetBackoff();
- };
-
- function _disconnectFailure(xhr, message)
- {
- _debug('Disconnect failure');
- _disconnect(true);
-
- var failureMessage = {
- successful: false,
- failure: true,
- channel: '/meta/disconnect',
- request: message,
- xhr: xhr,
- advice: {
- action: 'none',
- interval: 0
- }
- };
- _notifyListeners('/meta/disconnect', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
- };
-
- function _subscribeSuccess(message)
- {
- if (message.successful)
- {
- _debug('Subscribe successful');
- _notifyListeners('/meta/subscribe', message);
- }
- else
- {
- _debug('Subscribe unsuccessful');
- _notifyListeners('/meta/subscribe', message);
- _notifyListeners('/meta/unsuccessful', message);
- }
- };
-
- function _subscribeFailure(xhr, message)
- {
- _debug('Subscribe failure');
-
- var failureMessage = {
- successful: false,
- failure: true,
- channel: '/meta/subscribe',
- request: message,
- xhr: xhr,
- advice: {
- action: 'none',
- interval: 0
- }
- };
- _notifyListeners('/meta/subscribe', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
- };
-
- function _unsubscribeSuccess(message)
- {
- if (message.successful)
- {
- _debug('Unsubscribe successful');
- _notifyListeners('/meta/unsubscribe', message);
- }
- else
- {
- _debug('Unsubscribe unsuccessful');
- _notifyListeners('/meta/unsubscribe', message);
- _notifyListeners('/meta/unsuccessful', message);
- }
- };
-
- function _unsubscribeFailure(xhr, message)
- {
- _debug('Unsubscribe failure');
-
- var failureMessage = {
- successful: false,
- failure: true,
- channel: '/meta/unsubscribe',
- request: message,
- xhr: xhr,
- advice: {
- action: 'none',
- interval: 0
- }
- };
- _notifyListeners('/meta/unsubscribe', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
- };
-
- function _messageSuccess(message)
- {
- if (message.successful === undefined)
- {
- if (message.data)
- {
- // It is a plain message, and not a bayeux meta message
- _notifyListeners(message.channel, message);
- }
- else
- {
- _debug('Unknown message {}', JSON.stringify(message));
- }
- }
- else
- {
- if (message.successful)
- {
- _debug('Publish successful');
- _notifyListeners('/meta/publish', message);
- }
- else
- {
- _debug('Publish unsuccessful');
- _notifyListeners('/meta/publish', message);
- _notifyListeners('/meta/unsuccessful', message);
- }
- }
- };
-
- function _messageFailure(xhr, message)
- {
- _debug('Publish failure');
-
- var failureMessage = {
- successful: false,
- failure: true,
- channel: message.channel,
- request: message,
- xhr: xhr,
- advice: {
- action: 'none',
- interval: 0
- }
- };
- _notifyListeners('/meta/publish', failureMessage);
- _notifyListeners('/meta/unsuccessful', failureMessage);
- };
-
- function _notifyListeners(channel, message)
- {
- // Notify direct listeners
- _notify(channel, message);
-
- // Notify the globbing listeners
- var channelParts = channel.split("/");
- var last = channelParts.length - 1;
- for (var i = last; i > 0; --i)
- {
- var channelPart = channelParts.slice(0, i).join('/') + '/*';
- // We don't want to notify /foo/* if the channel is /foo/bar/baz,
- // so we stop at the first non recursive globbing
- if (i == last) _notify(channelPart, message);
- // Add the recursive globber and notify
- channelPart += '*';
- _notify(channelPart, message);
- }
- };
-
- function _notify(channel, message)
- {
- var subscriptions = _listeners[channel];
- if (subscriptions && subscriptions.length > 0)
- {
- for (var i = 0; i < subscriptions.length; ++i)
- {
- var subscription = subscriptions[i];
- // Subscriptions may come and go, so the array may have 'holes'
- if (subscription)
- {
- try
- {
- _debug('Notifying subscription: channel \'{}\', callback \'{}\'', channel, subscription.callback.name);
- subscription.callback.call(subscription.scope, message);
- }
- catch (x)
- {
- // Ignore exceptions from callbacks
- _warn('Exception during execution of callback \'{}\' on channel \'{}\' for message {}, exception: {}', subscription.callback.name, channel, JSON.stringify(message), x);
- }
- }
- }
- }
- };
-
- function _resetBackoff()
- {
- _backoff = 0;
- };
-
- function _increaseBackoff()
- {
- if (_backoff < _maxBackoff) _backoff += _backoffIncrement;
- };
-
- var _error = this._error = function(text, args)
- {
- _log('error', _format.apply(this, arguments));
- };
-
- var _warn = this._warn = function(text, args)
- {
- _log('warn', _format.apply(this, arguments));
- };
-
- var _info = this._info = function(text, args)
- {
- _log('info', _format.apply(this, arguments));
- };
-
- var _debug = this._debug = function(text, args)
- {
- _log('debug', _format.apply(this, arguments));
- };
-
- function _log(level, text)
- {
- var priority = _logPriorities[level];
- var configPriority = _logPriorities[_logLevel];
- if (!configPriority) configPriority = _logPriorities['info'];
- if (priority >= configPriority)
- {
- if (window.console) window.console.log(text);
- }
- };
-
- function _format(text)
- {
- var braces = /\{\}/g;
- var result = '';
- var start = 0;
- var count = 0;
- while (braces.test(text))
- {
- result += text.substr(start, braces.lastIndex - start - 2);
- var arg = arguments[++count];
- result += arg !== undefined ? arg : '{}';
- start = braces.lastIndex;
- }
- result += text.substr(start, text.length - start);
- return result;
- };
-
- function newLongPollingTransport()
- {
- return $.extend({}, new Transport('long-polling'), new LongPollingTransport());
- };
-
- function newCallbackPollingTransport()
- {
- return $.extend({}, new Transport('callback-polling'), new CallbackPollingTransport());
- };
-
- /**
- * Base object with the common functionality for transports.
- * The key responsibility is to allow at most 2 outstanding requests to the server,
- * to avoid that requests are sent behind a long poll.
- * To achieve this, we have one reserved request for the long poll, and all other
- * requests are serialized one after the other.
- */
- var Transport = function(type)
- {
- var _maxRequests = 2;
- var _requestIds = 0;
- var _cometRequest = null;
- var _requests = [];
- var _packets = [];
-
- this.getType = function()
- {
- return type;
- };
-
- this.send = function(packet, comet)
- {
- if (comet)
- _cometSend(this, packet);
- else
- _send(this, packet);
- };
-
- function _cometSend(self, packet)
- {
- if (_cometRequest !== null) throw 'Concurrent comet requests not allowed, request ' + _cometRequest.id + ' not yet completed';
-
- var requestId = ++_requestIds;
- _debug('Beginning comet request {}', requestId);
-
- var request = {id: requestId};
- _debug('Delivering comet request {}', requestId);
- self.deliver(packet, request);
- _cometRequest = request;
- };
-
- function _send(self, packet)
- {
- var requestId = ++_requestIds;
- _debug('Beginning request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);
-
- var request = {id: requestId};
- // Consider the comet request which should always be present
- if (_requests.length < _maxRequests - 1)
- {
- _debug('Delivering request {}', requestId);
- self.deliver(packet, request);
- _requests.push(request);
- }
- else
- {
- _packets.push([packet, request]);
- _debug('Queued request {}, {} queued requests', requestId, _packets.length);
- }
- };
-
- this.complete = function(request, success, comet)
- {
- if (comet)
- _cometComplete(request);
- else
- _complete(this, request, success);
- };
-
- function _cometComplete(request)
- {
- var requestId = request.id;
- if (_cometRequest !== request) throw 'Comet request mismatch, completing request ' + requestId;
-
- // Reset comet request
- _cometRequest = null;
- _debug('Ended comet request {}', requestId);
- };
-
- function _complete(self, request, success)
- {
- var requestId = request.id;
- var index = $.inArray(request, _requests);
- // The index can be negative the request has been aborted
- if (index >= 0) _requests.splice(index, 1);
- _debug('Ended request {}, {} other requests, {} queued requests', requestId, _requests.length, _packets.length);
-
- if (_packets.length > 0)
- {
- var packet = _packets.shift();
- if (success)
- {
- _debug('Dequeueing and sending request {}, {} queued requests', packet[1].id, _packets.length);
- _send(self, packet[0]);
- }
- else
- {
- _debug('Dequeueing and failing request {}, {} queued requests', packet[1].id, _packets.length);
- // Keep the semantic of calling response callbacks asynchronously after the request
- setTimeout(function() { packet[0].onFailure(packet[1], 'error'); }, 0);
- }
- }
- };
-
- this.abort = function()
- {
- for (var i = 0; i < _requests.length; ++i)
- {
- var request = _requests[i];
- _debug('Aborting request {}', request.id);
- if (request.xhr) request.xhr.abort();
- }
- if (_cometRequest)
- {
- _debug('Aborting comet request {}', _cometRequest.id);
- if (_cometRequest.xhr) _cometRequest.xhr.abort();
- }
- _cometRequest = null;
- _requests = [];
- _packets = [];
- };
- };
-
- var LongPollingTransport = function()
- {
- this.deliver = function(packet, request)
- {
- request.xhr = $.ajax({
- url: packet.url,
- type: 'POST',
- contentType: 'text/json;charset=UTF-8',
- beforeSend: function(xhr)
- {
- xhr.setRequestHeader('Connection', 'Keep-Alive');
- return true;
- },
- data: JSON.stringify(packet.messages),
- success: function(response) { packet.onSuccess(request, response); },
- error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
- });
- };
- };
-
- var CallbackPollingTransport = function()
- {
- var _maxLength = 2000;
- this.deliver = function(packet, request)
- {
- // Microsoft Internet Explorer has a 2083 URL max length
- // We must ensure that we stay within that length
- var messages = JSON.stringify(packet.messages);
- // Encode the messages because all brackets, quotes, commas, colons, etc
- // present in the JSON will be URL encoded, taking many more characters
- var urlLength = packet.url.length + encodeURI(messages).length;
- _debug('URL length: {}', urlLength);
- // Let's stay on the safe side and use 2000 instead of 2083
- // also because we did not count few characters among which
- // the parameter name 'message' and the parameter 'jsonp',
- // which sum up to about 50 chars
- if (urlLength > _maxLength)
- {
- var x = packet.messages.length > 1 ?
- 'Too many bayeux messages in the same batch resulting in message too big ' +
- '(' + urlLength + ' bytes, max is ' + _maxLength + ') for transport ' + this.getType() :
- 'Bayeux message too big (' + urlLength + ' bytes, max is ' + _maxLength + ') ' +
- 'for transport ' + this.getType();
- // Keep the semantic of calling response callbacks asynchronously after the request
- _setTimeout(function() { packet.onFailure(request, 'error', x); }, 0);
- }
- else
- {
- $.ajax({
- url: packet.url,
- type: 'GET',
- dataType: 'jsonp',
- jsonp: 'jsonp',
- beforeSend: function(xhr)
- {
- xhr.setRequestHeader('Connection', 'Keep-Alive');
- return true;
- },
- data:
- {
- // In callback-polling, the content must be sent via the 'message' parameter
- message: messages
- },
- success: function(response) { packet.onSuccess(request, response); },
- error: function(xhr, reason, exception) { packet.onFailure(request, reason, exception); }
- });
- }
- };
- };
- };
-
- /**
- * The JS object that exposes the comet API to applications
- */
- $.cometd = new $.Cometd(); // The default instance
-
-})(jQuery);
diff --git a/plugins/Orbited/json2.js b/plugins/Orbited/json2.js
deleted file mode 100644
index 7e27df518..000000000
--- a/plugins/Orbited/json2.js
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- http://www.JSON.org/json2.js
- 2009-04-16
-
- Public Domain.
-
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
-
- See http://www.JSON.org/js.html
-
- This file creates a global JSON object containing two methods: stringify
- and parse.
-
- JSON.stringify(value, replacer, space)
- value any JavaScript value, usually an object or array.
-
- replacer an optional parameter that determines how object
- values are stringified for objects. It can be a
- function or an array of strings.
-
- space an optional parameter that specifies the indentation
- of nested structures. If it is omitted, the text will
- be packed without extra whitespace. If it is a number,
- it will specify the number of spaces to indent at each
- level. If it is a string (such as '\t' or ' '),
- it contains the characters used to indent at each level.
-
- This method produces a JSON text from a JavaScript value.
-
- When an object value is found, if the object contains a toJSON
- method, its toJSON method will be called and the result will be
- stringified. A toJSON method does not serialize: it returns the
- value represented by the name/value pair that should be serialized,
- or undefined if nothing should be serialized. The toJSON method
- will be passed the key associated with the value, and this will be
- bound to the object holding the key.
-
- For example, this would serialize Dates as ISO strings.
-
- Date.prototype.toJSON = function (key) {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- You can provide an optional replacer method. It will be passed the
- key and value of each member, with this bound to the containing
- object. The value that is returned from your method will be
- serialized. If your method returns undefined, then the member will
- be excluded from the serialization.
-
- If the replacer parameter is an array of strings, then it will be
- used to select the members to be serialized. It filters the results
- such that only members with keys listed in the replacer array are
- stringified.
-
- Values that do not have JSON representations, such as undefined or
- functions, will not be serialized. Such values in objects will be
- dropped; in arrays they will be replaced with null. You can use
- a replacer function to replace those with JSON values.
- JSON.stringify(undefined) returns undefined.
-
- The optional space parameter produces a stringification of the
- value that is filled with line breaks and indentation to make it
- easier to read.
-
- If the space parameter is a non-empty string, then that string will
- be used for indentation. If the space parameter is a number, then
- the indentation will be that many spaces.
-
- Example:
-
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
- // text is '["e",{"pluribus":"unum"}]'
-
-
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
-
- text = JSON.stringify([new Date()], function (key, value) {
- return this[key] instanceof Date ?
- 'Date(' + this[key] + ')' : value;
- });
- // text is '["Date(---current time---)"]'
-
-
- JSON.parse(text, reviver)
- This method parses a JSON text to produce an object or array.
- It can throw a SyntaxError exception.
-
- The optional reviver parameter is a function that can filter and
- transform the results. It receives each of the keys and values,
- and its return value is used instead of the original value.
- If it returns what it received, then the structure is not modified.
- If it returns undefined then the member is deleted.
-
- Example:
-
- // Parse the text. Values that look like ISO date strings will
- // be converted to Date objects.
-
- myData = JSON.parse(text, function (key, value) {
- var a;
- if (typeof value === 'string') {
- a =
-/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
- if (a) {
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
- +a[5], +a[6]));
- }
- }
- return value;
- });
-
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
- var d;
- if (typeof value === 'string' &&
- value.slice(0, 5) === 'Date(' &&
- value.slice(-1) === ')') {
- d = new Date(value.slice(5, -1));
- if (d) {
- return d;
- }
- }
- return value;
- });
-
-
- This is a reference implementation. You are free to copy, modify, or
- redistribute.
-
- This code should be minified before deployment.
- See http://javascript.crockford.com/jsmin.html
-
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
- NOT CONTROL.
-*/
-
-/*jslint evil: true */
-
-/*global JSON */
-
-/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
- call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
- getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
- lastIndex, length, parse, prototype, push, replace, slice, stringify,
- test, toJSON, toString, valueOf
-*/
-
-// Create a JSON object only if one does not already exist. We create the
-// methods in a closure to avoid creating global variables.
-
-if (!this.JSON) {
- JSON = {};
-}
-(function () {
-
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- if (typeof Date.prototype.toJSON !== 'function') {
-
- Date.prototype.toJSON = function (key) {
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- String.prototype.toJSON =
- Number.prototype.toJSON =
- Boolean.prototype.toJSON = function (key) {
- return this.valueOf();
- };
- }
-
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
-
-
- function quote(string) {
-
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can safely slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe escape
-// sequences.
-
- escapable.lastIndex = 0;
- return escapable.test(string) ?
- '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === 'string' ? c :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- }
-
-
- function str(key, holder) {
-
-// Produce a string from holder[key].
-
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
-
-// If the value has a toJSON method, call it to obtain a replacement value.
-
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
-// If we were called with a replacer function, then call the replacer to
-// obtain a replacement value.
-
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
-
-// What happens next depends on the value's type.
-
- switch (typeof value) {
- case 'string':
- return quote(value);
-
- case 'number':
-
-// JSON numbers must be finite. Encode non-finite numbers as null.
-
- return isFinite(value) ? String(value) : 'null';
-
- case 'boolean':
- case 'null':
-
-// If the value is a boolean or null, convert it to a string. Note:
-// typeof null does not produce 'null'. The case is included here in
-// the remote chance that this gets fixed someday.
-
- return String(value);
-
-// If the type is 'object', we might be dealing with an object or an array or
-// null.
-
- case 'object':
-
-// Due to a specification blunder in ECMAScript, typeof null is 'object',
-// so watch out for that case.
-
- if (!value) {
- return 'null';
- }
-
-// Make an array to hold the partial results of stringifying this object value.
-
- gap += indent;
- partial = [];
-
-// Is the value an array?
-
- if (Object.prototype.toString.apply(value) === '[object Array]') {
-
-// The value is an array. Stringify every element. Use null as a placeholder
-// for non-JSON values.
-
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
-
-// Join all of the elements together, separated with commas, and wrap them in
-// brackets.
-
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
-
-// If the replacer is an array, use it to select the members to be stringified.
-
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- k = rep[i];
- if (typeof k === 'string') {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
-
-// Otherwise, iterate through all of the keys in the object.
-
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
-
-// Join all of the member texts together, separated with commas,
-// and wrap them in braces.
-
- v = partial.length === 0 ? '{}' :
- gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- }
-
-// If the JSON object does not yet have a stringify method, give it one.
-
- if (typeof JSON.stringify !== 'function') {
- JSON.stringify = function (value, replacer, space) {
-
-// The stringify method takes a value and an optional replacer, and an optional
-// space parameter, and returns a JSON text. The replacer can be a function
-// that can replace values, or an array of strings that will select the keys.
-// A default replacer method can be provided. Use of the space parameter can
-// produce text that is more easily readable.
-
- var i;
- gap = '';
- indent = '';
-
-// If the space parameter is a number, make an indent string containing that
-// many spaces.
-
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
-
-// If the space parameter is a string, it will be used as the indent string.
-
- } else if (typeof space === 'string') {
- indent = space;
- }
-
-// If there is a replacer, it must be a function or an array.
-// Otherwise, throw an error.
-
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
-
-// Make a fake root object containing our value under the key of ''.
-// Return the result of stringifying the value.
-
- return str('', {'': value});
- };
- }
-
-
-// If the JSON object does not yet have a parse method, give it one.
-
- if (typeof JSON.parse !== 'function') {
- JSON.parse = function (text, reviver) {
-
-// The parse method takes a text and an optional reviver function, and returns
-// a JavaScript value if the text is a valid JSON text.
-
- var j;
-
- function walk(holder, key) {
-
-// The walk method is used to recursively walk the resulting structure so
-// that modifications can be made.
-
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
-
-
-// Parsing happens in four stages. In the first stage, we replace certain
-// Unicode characters with escape sequences. JavaScript handles many characters
-// incorrectly, either silently deleting them, or treating them as line endings.
-
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' +
- ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- });
- }
-
-// In the second stage, we run the text against regular expressions that look
-// for non-JSON patterns. We are especially concerned with '()' and 'new'
-// because they can cause invocation, and '=' because it can cause mutation.
-// But just to be safe, we want to reject all unexpected forms.
-
-// We split the second stage into 4 regexp operations in order to work around
-// crippling inefficiencies in IE's and Safari's regexp engines. First we
-// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
-// replace all simple value tokens with ']' characters. Third, we delete all
-// open brackets that follow a colon or comma or that begin the text. Finally,
-// we look to see that the remaining characters are only whitespace or ']' or
-// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
- if (/^[\],:{}\s]*$/.
-test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
-replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
-replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
-
-// In the third stage we use the eval function to compile the text into a
-// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
-// in JavaScript: it can begin a block or an object literal. We wrap the text
-// in parens to eliminate the ambiguity.
-
- j = eval('(' + text + ')');
-
-// In the optional fourth stage, we recursively walk the new structure, passing
-// each name/value pair to a reviver function for possible transformation.
-
- return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
- }
-
-// If the text is not JSON parseable, then a SyntaxError is thrown.
-
- throw new SyntaxError('JSON.parse');
- };
- }
-}());
--
cgit v1.2.3-54-g00ecf
From 9c2d0879e9064fe3b9cf9af2c7eabe869966bd92 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Tue, 21 Jul 2009 11:00:03 -0700
Subject: updated OrbitedPlugin to use RealtimePlugin
---
plugins/Orbited/OrbitedPlugin.php | 186 ++++++++++----------------------------
1 file changed, 47 insertions(+), 139 deletions(-)
(limited to 'plugins')
diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php
index 45251c66f..cf6e5de13 100644
--- a/plugins/Orbited/OrbitedPlugin.php
+++ b/plugins/Orbited/OrbitedPlugin.php
@@ -2,7 +2,7 @@
/**
* Laconica, the distributed open-source microblogging tool
*
- * Plugin to do "real time" updates using Comet/Bayeux
+ * Plugin to do "real time" updates using Orbited + STOMP
*
* PHP version 5
*
@@ -31,8 +31,13 @@ if (!defined('LACONICA')) {
exit(1);
}
+require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php';
+
/**
- * Plugin to do realtime updates using Comet
+ * Plugin to do realtime updates using Orbited + STOMP
+ *
+ * This plugin pushes data to a STOMP server which is then served to the
+ * browser by the Orbited server.
*
* @category Plugin
* @package Laconica
@@ -41,165 +46,68 @@ if (!defined('LACONICA')) {
* @link http://laconi.ca/
*/
-class CometPlugin extends Plugin
+class OrbitedPlugin extends RealtimePlugin
{
- var $server = null;
+ public $webserver = null;
+ public $webport = null;
+ public $channelbase = null;
+ public $stompserver = null;
+ public $username = null;
+ public $password = null;
- function __construct($server=null, $username=null, $password=null)
- {
- $this->server = $server;
- $this->username = $username;
- $this->password = $password;
+ protected $con = null;
- parent::__construct();
+ function _getScripts()
+ {
+ $scripts = parent::_getScripts();
+ $root = 'http://'.$this->webserver.(($this->webport == 80) ? '':':'.$this->webport);
+ $scripts[] = $root.'/static/Orbited.js';
+ $scripts[] = $root.'/static/protocols/stomp/stomp.js';
+ $scripts[] = common_path('plugins/Orbited/orbitedupdater.js');
+ return $scripts;
}
- function onEndShowScripts($action)
+ function _updateInitialize($timeline, $user_id)
{
- $timeline = null;
-
- $this->log(LOG_DEBUG, 'got action ' . $action->trimmed('action'));
-
- switch ($action->trimmed('action')) {
- case 'public':
- $timeline = '/timelines/public';
- break;
- case 'tag':
- $tag = $action->trimmed('tag');
- if (!empty($tag)) {
- $timeline = '/timelines/tag/'.$tag;
- } else {
- return true;
- }
- break;
- default:
- return true;
- }
-
- $scripts = array('jquery.comet.js', 'json2.js', 'updatetimeline.js');
-
- foreach ($scripts as $script) {
- $action->element('script', array('type' => 'text/javascript',
- 'src' => common_path('plugins/Comet/'.$script)),
- ' ');
- }
-
- $user = common_current_user();
-
- if (!empty($user->id)) {
- $user_id = $user->id;
- } else {
- $user_id = 0;
- }
-
- $replyurl = common_local_url('newnotice');
- $favorurl = common_local_url('favor');
- // FIXME: need to find a better way to pass this pattern in
- $deleteurl = common_local_url('deletenotice',
- array('notice' => '0000000000'));
-
- $action->elementStart('script', array('type' => 'text/javascript'));
- $action->raw("$(document).ready(function() { updater.init(\"$this->server\", \"$timeline\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });");
- $action->elementEnd('script');
-
- return true;
+ $script = parent::_updateInitialize($timeline, $user_id);
+ return $script." OrbitedUpdater.init(\"$this->stompserver\", $this->stompport, \"{$timeline}\");";
}
- function onEndNoticeSave($notice)
+ function _connect()
{
- $this->log(LOG_INFO, "Called for save notice.");
-
- $timelines = array();
+ require_once(INSTALLDIR.'/extlibs/Stomp.php');
- // XXX: Add other timelines; this is just for the public one
+ $stompserver = (empty($this->stompserver)) ? "tcp://{$this->webserver}:61613/" : $this->stompserver;
- if ($notice->is_local ||
- ($notice->is_local == 0 && !common_config('public', 'localonly'))) {
- $timelines[] = '/timelines/public';
- }
+ $this->con = new Stomp($stompserver);
- $tags = $this->getNoticeTags($notice);
-
- if (!empty($tags)) {
- foreach ($tags as $tag) {
- $timelines[] = '/timelines/tag/' . $tag;
- }
- }
-
- if (count($timelines) > 0) {
- // Require this, since we need it
- require_once(INSTALLDIR.'/plugins/Comet/bayeux.class.inc.php');
-
- $json = $this->noticeAsJson($notice);
-
- // Bayeux? Comet? Huh? These terms confuse me
- $bay = new Bayeux($this->server, $this->user, $this->password);
-
- foreach ($timelines as $timeline) {
- $this->log(LOG_INFO, "Posting notice $notice->id to '$timeline'.");
- $bay->publish($timeline, $json);
- }
-
- $bay = NULL;
+ if ($this->con->connect($this->username, $this->password)) {
+ $this->_log(LOG_INFO, "Connected.");
+ } else {
+ $this->_log(LOG_ERR, 'Failed to connect to queue server');
+ throw new ServerException('Failed to connect to queue server');
}
-
- return true;
}
- function noticeAsJson($notice)
+ function _publish($channel, $message)
{
- // FIXME: this code should be abstracted to a neutral third
- // party, like Notice::asJson(). I'm not sure of the ethics
- // of refactoring from within a plugin, so I'm just abusing
- // the TwitterApiAction method. Don't do this unless you're me!
-
- require_once(INSTALLDIR.'/lib/twitterapi.php');
-
- $act = new TwitterApiAction('/dev/null');
-
- $arr = $act->twitter_status_array($notice, true);
- $arr['url'] = $notice->bestUrl();
- $arr['html'] = htmlspecialchars($notice->rendered);
- $arr['source'] = htmlspecialchars($arr['source']);
+ $result = $this->con->send($channel,
+ json_encode($message));
- if (!empty($notice->reply_to)) {
- $reply_to = Notice::staticGet('id', $notice->reply_to);
- if (!empty($reply_to)) {
- $arr['in_reply_to_status_url'] = $reply_to->bestUrl();
- }
- $reply_to = null;
- }
-
- $profile = $notice->getProfile();
- $arr['user']['profile_url'] = $profile->profileurl;
-
- return $arr;
+ return $result;
+ // TODO: parse and deal with result
}
- function getNoticeTags($notice)
+ function _disconnect()
{
- $tags = null;
-
- $nt = new Notice_tag();
- $nt->notice_id = $notice->id;
-
- if ($nt->find()) {
- $tags = array();
- while ($nt->fetch()) {
- $tags[] = $nt->tag;
- }
- }
-
- $nt->free();
- $nt = null;
-
- return $tags;
+ $this->con->disconnect();
}
- // Push this up to Plugin
-
- function log($level, $msg)
+ function _pathToChannel($path)
{
- common_log($level, get_class($this) . ': '.$msg);
+ if (!empty($this->channelbase)) {
+ array_unshift($path, $this->channelbase);
+ }
+ return '/' . implode('/', $path);
}
}
--
cgit v1.2.3-54-g00ecf
From 035978270d609b650b8e32f252366e0e75b12507 Mon Sep 17 00:00:00 2001
From: Evan Prodromou
Date: Sun, 4 Oct 2009 03:02:04 -0400
Subject: Update OrbitedPlugin to work with RealtimePlugin framework
---
plugins/Orbited/OrbitedPlugin.php | 43 ++++++++++-
plugins/Orbited/orbitedupdater.js | 21 ++++++
plugins/Orbited/updatetimeline.js | 154 --------------------------------------
3 files changed, 60 insertions(+), 158 deletions(-)
create mode 100644 plugins/Orbited/orbitedupdater.js
delete mode 100644 plugins/Orbited/updatetimeline.js
(limited to 'plugins')
diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php
index cf6e5de13..ad7d1d276 100644
--- a/plugins/Orbited/OrbitedPlugin.php
+++ b/plugins/Orbited/OrbitedPlugin.php
@@ -52,34 +52,49 @@ class OrbitedPlugin extends RealtimePlugin
public $webport = null;
public $channelbase = null;
public $stompserver = null;
+ public $stompport = null;
public $username = null;
public $password = null;
+ public $webuser = null;
+ public $webpass = null;
protected $con = null;
function _getScripts()
{
$scripts = parent::_getScripts();
- $root = 'http://'.$this->webserver.(($this->webport == 80) ? '':':'.$this->webport);
+
+ $port = (is_null($this->webport)) ? 8000 : $this->webport;
+
+ $server = (is_null($this->webserver)) ? common_config('site', 'server') : $this->webserver;
+
+ $root = 'http://'.$server.(($port == 80) ? '':':'.$port);
+
$scripts[] = $root.'/static/Orbited.js';
$scripts[] = $root.'/static/protocols/stomp/stomp.js';
$scripts[] = common_path('plugins/Orbited/orbitedupdater.js');
+
return $scripts;
}
function _updateInitialize($timeline, $user_id)
{
$script = parent::_updateInitialize($timeline, $user_id);
- return $script." OrbitedUpdater.init(\"$this->stompserver\", $this->stompport, \"{$timeline}\");";
+
+ $server = $this->_getStompServer();
+ $port = $this->_getStompPort();
+
+ return $script." OrbitedUpdater.init(\"$server\", $port, ".
+ "\"{$timeline}\", \"{$this->webuser}\", \"{$this->webpass}\");";
}
function _connect()
{
require_once(INSTALLDIR.'/extlibs/Stomp.php');
- $stompserver = (empty($this->stompserver)) ? "tcp://{$this->webserver}:61613/" : $this->stompserver;
+ $url = $this->_getStompUrl();
- $this->con = new Stomp($stompserver);
+ $this->con = new Stomp($url);
if ($this->con->connect($this->username, $this->password)) {
$this->_log(LOG_INFO, "Connected.");
@@ -110,4 +125,24 @@ class OrbitedPlugin extends RealtimePlugin
}
return '/' . implode('/', $path);
}
+
+ function _getStompServer()
+ {
+ $server = (!is_null($this->stompserver)) ? $this->stompserver :
+ (!is_null($this->webserver)) ? $this->webserver :
+ common_config('site', 'server');
+ return $server;
+ }
+
+ function _getStompPort()
+ {
+ $port = (!is_null($this->stompport)) ? $this->stompport : 61613;
+ }
+
+ function _getStompUrl()
+ {
+ $server = $this->_getStompServer();
+ $port = $this->_getStompPort();
+ return "tcp://$server:$port/";
+ }
}
diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js
new file mode 100644
index 000000000..d70f4a4fd
--- /dev/null
+++ b/plugins/Orbited/orbitedupdater.js
@@ -0,0 +1,21 @@
+// Update the local timeline from a Orbited server
+
+var OrbitedUpdater = function()
+{
+ return {
+
+ init: function(server, port, timeline, username, password)
+ {
+ // set up stomp client.
+ stomp = new STOMPClient();
+
+ stomp.connect(server, port, username, password);
+ stomp.subscribe(timeline);
+
+ stomp.onmessageframe = function(frame) {
+ RealtimeUpdate.receive(JSON.parse(frame.body));
+ };
+ };
+ }
+}();
+
diff --git a/plugins/Orbited/updatetimeline.js b/plugins/Orbited/updatetimeline.js
deleted file mode 100644
index 170949e9b..000000000
--- a/plugins/Orbited/updatetimeline.js
+++ /dev/null
@@ -1,154 +0,0 @@
-// update the local timeline from a Comet server
-//
-
-var updater = function()
-{
- var _server;
- var _timeline;
- var _userid;
- var _replyurl;
- var _favorurl;
- var _deleteurl;
- var _cometd;
-
- return {
- init: function(server, timeline, userid, replyurl, favorurl, deleteurl)
- {
- _cometd = $.cometd; // Uses the default Comet object
- _cometd.setLogLevel('debug');
- _cometd.init(server);
- _server = server;
- _timeline = timeline;
- _userid = userid;
- _favorurl = favorurl;
- _replyurl = replyurl;
- _deleteurl = deleteurl;
- _cometd.subscribe(timeline, receive);
- $(window).unload(leave);
- }
- }
-
- function leave()
- {
- _cometd.disconnect();
- }
-
- function receive(message)
- {
- id = message.data.id;
-
- // Don't add it if it already exists
-
- if ($("#notice-"+id).length > 0) {
- return;
- }
-
- var noticeItem = makeNoticeItem(message.data);
- $("#notices_primary .notices").prepend(noticeItem, true);
- $("#notices_primary .notice:first").css({display:"none"});
- $("#notices_primary .notice:first").fadeIn(1000);
- NoticeHover();
- NoticeReply();
- }
-
- function makeNoticeItem(data)
- {
- user = data['user'];
- html = data['html'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
- source = data['source'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
-
- ni = "
',
- $title, $logout_url, $text);
-
- $action->raw($html);
-
- } else {
- $action->menuItem(common_local_url('logout'),
- _('Logout'), _('Logout from the site'), false, 'nav_logout');
- }
- }
- else {
- if (!common_config('site', 'openidonly')) {
- if (!common_config('site', 'closed')) {
- $action->menuItem(common_local_url('register'),
- _('Register'), _('Create an account'), false, 'nav_register');
- }
- $action->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
- } else {
- $this->menuItem(common_local_url('openidlogin'),
- _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
- }
- }
-
- $action->menuItem(common_local_url('doc', array('title' => 'help')),
- _('Help'), _('Help me!'), false, 'nav_help');
- if ($user || !common_config('site', 'private')) {
- $action->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
- }
-
- // We are replacing the primary nav entirely; give other
- // plugins a chance to handle it here.
-
- Event::handle('EndPrimaryNav', array($action));
-
- return false;
+ return true;
}
function onStartShowLocalNavBlock($action)
@@ -357,7 +321,7 @@ class FBConnectPlugin extends Plugin
}
function onStartLogout($action)
-{
+ {
$action->logout();
$fbuid = $this->loggedIn();
--
cgit v1.2.3-54-g00ecf
From 7539e26951ba9e1529d9a24a7a634861b7d080fe Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Tue, 20 Oct 2009 06:05:35 +0000
Subject: - Make Twitter bridge work with unqueuemanager - Add README
---
lib/unqueuemanager.php | 5 --
plugins/TwitterBridge/README | 86 +++++++++++++++++++++++++++
plugins/TwitterBridge/TwitterBridgePlugin.php | 42 +++++++++++--
plugins/TwitterBridge/twittersettings.php | 2 +-
4 files changed, 124 insertions(+), 11 deletions(-)
create mode 100644 plugins/TwitterBridge/README
(limited to 'plugins')
diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php
index 6cfe5bcbd..51261bcd7 100644
--- a/lib/unqueuemanager.php
+++ b/lib/unqueuemanager.php
@@ -48,11 +48,6 @@ class UnQueueManager
jabber_public_notice($notice);
}
break;
- case 'twitter':
- if ($this->_isLocal($notice)) {
- broadcast_twitter($notice);
- }
- break;
case 'facebook':
if ($this->_isLocal($notice)) {
require_once INSTALLDIR . '/lib/facebookutil.php';
diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README
new file mode 100644
index 000000000..09352188e
--- /dev/null
+++ b/plugins/TwitterBridge/README
@@ -0,0 +1,86 @@
+This Twitter "bridge" plugin allows you to integrate your StatusNet
+instance with Twitter. Installing it will allow your users to:
+
+ - automatically post notices to thier Twitter accounts
+ - automatically subscribe to other Twitter users who are also using
+ your StatusNet install, if possible (requires running a daemon)
+ - import their Twitter friends' tweets (requires running a daemon)
+
+Installation
+------------
+
+To enable the plugin, add the following to your config.php:
+
+ require_once(INSTALLDIR . '/plugins/TwitterBridge/TwitterBridgePlugin.php');
+ $tb = new TwitterBridgePlugin();
+
+OAuth is used to to access protected resources on Twitter (as opposed to
+HTTP Basic Auth)*. To use Twitter bridging you will need to register
+your instance of StatusNet as an application on Twitter
+(http://twitter.com/apps), and update the following variables in your
+config.php with the consumer key and secret Twitter generates for you:
+
+ $config['twitter']['consumer_key'] = 'YOURKEY';
+ $config['twitter']['consumer_secret'] = 'YOURSECRET';
+
+When registering your application with Twitter set the type to "Browser"
+and your Callback URL to:
+
+ http://example.org/mublog/twitter/authorization
+
+The default access type should be, "Read & Write".
+
+* Note: The plugin will still push notices to Twitter for users who
+ have previously setup the Twitter bridge using their Twitter name and
+ password under an older versions of StatusNet, but all new Twitter
+ bridge connections will use OAuth.
+
+Deamons
+-------
+
+For friend syncing and importing notices running two additional daemon
+scripts is necessary (synctwitterfriends.php and
+twitterstatusfetcher.php).
+
+In the daemons subidrectory of the plugin are three scripts:
+
+* Twitter Friends Syncing (daemons/synctwitterfriends.php)
+
+Users may set a flag in their settings ("Subscribe to my Twitter friends
+here" under the Twitter tab) to have StatusNet attempt to locate and
+subscribe to "friends" (people they "follow") on Twitter who also have
+accounts on your StatusNet system, and who have previously set up a link
+for automatically posting notices to Twitter.
+
+The plugin will try to start this daemon when you run
+scripts/startdaemons.sh.
+
+* Importing statuses from Twitter (daemons/twitterstatusfetcher.php)
+
+To allow your users to import their friends' Twitter statuses, you will
+need to enable the bidirectional Twitter bridge in your config.php:
+
+ $config['twitterimport']['enabled'] = true;
+
+The plugin will then start the TwitterStatusFetcher daemon along with the
+other daemons when you run scripts/startdaemons.sh.
+
+Additionally, you will want to set the integration source variable,
+which will keep notices posted to Twitter via StatusNet from looping
+back. The integration source should be set to the name of your
+application, exactly as you specified it on the settings page for your
+StatusNet application on Twitter, e.g.:
+
+ $config['integration']['source'] = 'YourApp';
+
+* TwitterQueueHandler (daemons/twitterqueuehandler.php)
+
+This script sends queued notices to Twitter for user who have opted to
+set up Twitter bridging.
+
+It's not strictly necessary to run this queue handler, and sites that
+haven't enabled queuing are still able to push notices to Twitter, but
+for larger sites and sites that wish to improve performance, this
+script allows notices to be sent "offline" via a separate process.
+
+The plugin will start this script when you run scripts/startdaemons.sh.
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index 1a27c30cd..e69567fc7 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -29,6 +29,8 @@ if (!defined('STATUSNET')) {
exit(1);
}
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
/**
* Plugin for sending and importing Twitter statuses
*
@@ -104,11 +106,11 @@ class TwitterBridgePlugin extends Plugin
switch ($cls) {
case 'TwittersettingsAction':
case 'TwitterauthorizationAction':
- include_once INSTALLDIR.'/plugins/TwitterBridge/' .
+ include_once INSTALLDIR . '/plugins/TwitterBridge/' .
strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'TwitterOAuthClient':
- include_once INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php';
+ include_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
return false;
default:
return true;
@@ -118,17 +120,47 @@ class TwitterBridgePlugin extends Plugin
/**
* Add a Twitter queue item for each notice
*
- * @param Notice $notice the notice
- * @param array $transports the list of transports (queues)
+ * @param Notice $notice the notice
+ * @param array &$transports the list of transports (queues)
*
* @return boolean hook return
*/
- function onStartEnqueueNotice($notice, $transports)
+ function onStartEnqueueNotice($notice, &$transports)
{
array_push($transports, 'twitter');
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 == 'twitter') && ($this->_isLocal($notice))) {
+ broadcast_twitter($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 Twitter bridge daemons to the list of daemons to start
*
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
index 2afa85ba4..ca22c9553 100644
--- a/plugins/TwitterBridge/twittersettings.php
+++ b/plugins/TwitterBridge/twittersettings.php
@@ -152,7 +152,7 @@ class TwittersettingsAction extends ConnectSettingsAction
false);
$this->elementEnd('li');
- if (common_config('twitterbridge','enabled')) {
+ if (common_config('twitterimport','enabled')) {
$this->elementStart('li');
$this->checkbox('noticerecv',
_('Import my Friends Timeline.'),
--
cgit v1.2.3-54-g00ecf
From 78e5a5980a21c04cfdacb993ca3c37bf65d21783 Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Tue, 20 Oct 2009 16:32:30 -0700
Subject: Extract out Facebook app stuff into a plugin
---
actions/facebookhome.php | 275 ------------
actions/facebookinvite.php | 145 -------
actions/facebooklogin.php | 101 -----
actions/facebookremove.php | 67 ---
actions/facebooksettings.php | 157 -------
lib/facebookaction.php | 672 ------------------------------
lib/facebookutil.php | 260 ------------
lib/router.php | 8 -
plugins/Facebook/FacebookPlugin.php | 151 +++++++
plugins/Facebook/README | 5 +
plugins/Facebook/facebookaction.php | 650 +++++++++++++++++++++++++++++
plugins/Facebook/facebookhome.php | 277 ++++++++++++
plugins/Facebook/facebookinvite.php | 145 +++++++
plugins/Facebook/facebooklogin.php | 99 +++++
plugins/Facebook/facebookqueuehandler.php | 74 ++++
plugins/Facebook/facebookremove.php | 69 +++
plugins/Facebook/facebooksettings.php | 159 +++++++
plugins/Facebook/facebookutil.php | 260 ++++++++++++
scripts/facebookqueuehandler.php | 74 ----
scripts/getvaliddaemons.php | 1 -
20 files changed, 1889 insertions(+), 1760 deletions(-)
delete mode 100644 actions/facebookhome.php
delete mode 100644 actions/facebookinvite.php
delete mode 100644 actions/facebooklogin.php
delete mode 100644 actions/facebookremove.php
delete mode 100644 actions/facebooksettings.php
delete mode 100644 lib/facebookaction.php
delete mode 100644 lib/facebookutil.php
create mode 100644 plugins/Facebook/FacebookPlugin.php
create mode 100644 plugins/Facebook/README
create mode 100644 plugins/Facebook/facebookaction.php
create mode 100644 plugins/Facebook/facebookhome.php
create mode 100644 plugins/Facebook/facebookinvite.php
create mode 100644 plugins/Facebook/facebooklogin.php
create mode 100755 plugins/Facebook/facebookqueuehandler.php
create mode 100644 plugins/Facebook/facebookremove.php
create mode 100644 plugins/Facebook/facebooksettings.php
create mode 100644 plugins/Facebook/facebookutil.php
delete mode 100755 scripts/facebookqueuehandler.php
(limited to 'plugins')
diff --git a/actions/facebookhome.php b/actions/facebookhome.php
deleted file mode 100644
index 70f205205..000000000
--- 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 6dfc9d688..000000000
--- 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 8ac2477ab..000000000
--- 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 ae231c0fb..000000000
--- 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 b2b1d6807..000000000
--- 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 3f3a8d3b0..000000000
--- 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 c991c5439..000000000
--- 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 a5b6a9a30..4fb0834fd 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 000000000..127cf96e6
--- /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 000000000..a350c5b5b
--- /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 000000000..f5ad3d06b
--- /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 000000000..91c0cc6b8
--- /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 000000000..ecda1717c
--- /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 000000000..f77aecca3
--- /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 000000000..30de59efb
--- /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 000000000..8531a8e6e
--- /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 000000000..4bfdfc0ef
--- /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 000000000..9817837f7
--- /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 e13ac4e85..000000000
--- 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 7caea1bb7..99ad41b37 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')) {
--
cgit v1.2.3-54-g00ecf
From d07c9d87504c3909466e2c99a1265af15aba3c3d Mon Sep 17 00:00:00 2001
From: Zach Copley
Date: Wed, 21 Oct 2009 01:07:03 +0000
Subject: Gather all the Facebook stuff together in one place
---
extlib/facebook/facebook.php | 598 ----
extlib/facebook/facebook_desktop.php | 104 -
extlib/facebook/facebookapi_php5_restlib.php | 3618 --------------------
extlib/facebook/jsonwrapper/JSON/JSON.php | 806 -----
extlib/facebook/jsonwrapper/JSON/LICENSE | 21 -
extlib/facebook/jsonwrapper/jsonwrapper.php | 6 -
extlib/facebook/jsonwrapper/jsonwrapper_inner.php | 23 -
plugins/FBConnect/FBCLoginGroupNav.php | 114 -
plugins/FBConnect/FBCSettingsNav.php | 115 -
plugins/FBConnect/FBC_XDReceiver.php | 68 -
plugins/FBConnect/FBConnectAuth.php | 461 ---
plugins/FBConnect/FBConnectLogin.php | 67 -
plugins/FBConnect/FBConnectPlugin.css | 36 -
plugins/FBConnect/FBConnectPlugin.php | 367 --
plugins/FBConnect/FBConnectSettings.php | 203 --
plugins/FBConnect/README | 76 -
plugins/FBConnect/fbfavicon.ico | Bin 1150 -> 0 bytes
plugins/Facebook/FBCLoginGroupNav.php | 114 +
plugins/Facebook/FBCSettingsNav.php | 115 +
plugins/Facebook/FBC_XDReceiver.php | 68 +
plugins/Facebook/FBConnectAuth.php | 461 +++
plugins/Facebook/FBConnectLogin.php | 67 +
plugins/Facebook/FBConnectPlugin.css | 36 +
plugins/Facebook/FBConnectPlugin.php | 367 ++
plugins/Facebook/FBConnectSettings.php | 203 ++
plugins/Facebook/README | 4 +
plugins/Facebook/facebook/facebook.php | 598 ++++
plugins/Facebook/facebook/facebook_desktop.php | 104 +
.../Facebook/facebook/facebookapi_php5_restlib.php | 3618 ++++++++++++++++++++
.../Facebook/facebook/jsonwrapper/JSON/JSON.php | 806 +++++
plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE | 21 +
.../Facebook/facebook/jsonwrapper/jsonwrapper.php | 6 +
.../facebook/jsonwrapper/jsonwrapper_inner.php | 23 +
plugins/Facebook/fbfavicon.ico | Bin 0 -> 1150 bytes
34 files changed, 6611 insertions(+), 6683 deletions(-)
delete mode 100644 extlib/facebook/facebook.php
delete mode 100644 extlib/facebook/facebook_desktop.php
delete mode 100755 extlib/facebook/facebookapi_php5_restlib.php
delete mode 100644 extlib/facebook/jsonwrapper/JSON/JSON.php
delete mode 100644 extlib/facebook/jsonwrapper/JSON/LICENSE
delete mode 100644 extlib/facebook/jsonwrapper/jsonwrapper.php
delete mode 100644 extlib/facebook/jsonwrapper/jsonwrapper_inner.php
delete mode 100644 plugins/FBConnect/FBCLoginGroupNav.php
delete mode 100644 plugins/FBConnect/FBCSettingsNav.php
delete mode 100644 plugins/FBConnect/FBC_XDReceiver.php
delete mode 100644 plugins/FBConnect/FBConnectAuth.php
delete mode 100644 plugins/FBConnect/FBConnectLogin.php
delete mode 100644 plugins/FBConnect/FBConnectPlugin.css
delete mode 100644 plugins/FBConnect/FBConnectPlugin.php
delete mode 100644 plugins/FBConnect/FBConnectSettings.php
delete mode 100644 plugins/FBConnect/README
delete mode 100644 plugins/FBConnect/fbfavicon.ico
create mode 100644 plugins/Facebook/FBCLoginGroupNav.php
create mode 100644 plugins/Facebook/FBCSettingsNav.php
create mode 100644 plugins/Facebook/FBC_XDReceiver.php
create mode 100644 plugins/Facebook/FBConnectAuth.php
create mode 100644 plugins/Facebook/FBConnectLogin.php
create mode 100644 plugins/Facebook/FBConnectPlugin.css
create mode 100644 plugins/Facebook/FBConnectPlugin.php
create mode 100644 plugins/Facebook/FBConnectSettings.php
create mode 100644 plugins/Facebook/facebook/facebook.php
create mode 100644 plugins/Facebook/facebook/facebook_desktop.php
create mode 100755 plugins/Facebook/facebook/facebookapi_php5_restlib.php
create mode 100644 plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php
create mode 100644 plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE
create mode 100644 plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php
create mode 100644 plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php
create mode 100644 plugins/Facebook/fbfavicon.ico
(limited to 'plugins')
diff --git a/extlib/facebook/facebook.php b/extlib/facebook/facebook.php
deleted file mode 100644
index 016e8e8e0..000000000
--- a/extlib/facebook/facebook.php
+++ /dev/null
@@ -1,598 +0,0 @@
-api_key = $api_key;
- $this->secret = $secret;
- $this->generate_session_secret = $generate_session_secret;
- $this->api_client = new FacebookRestClient($api_key, $secret, null);
- $this->validate_fb_params();
-
- // Set the default user id for methods that allow the caller to
- // pass an explicit uid instead of using a session key.
- $defaultUser = null;
- if ($this->user) {
- $defaultUser = $this->user;
- } else if ($this->profile_user) {
- $defaultUser = $this->profile_user;
- } else if ($this->canvas_user) {
- $defaultUser = $this->canvas_user;
- }
-
- $this->api_client->set_user($defaultUser);
-
-
- if (isset($this->fb_params['friends'])) {
- $this->api_client->friends_list = explode(',', $this->fb_params['friends']);
- }
- if (isset($this->fb_params['added'])) {
- $this->api_client->added = $this->fb_params['added'];
- }
- if (isset($this->fb_params['canvas_user'])) {
- $this->api_client->canvas_user = $this->fb_params['canvas_user'];
- }
- }
-
- /*
- * Validates that the parameters passed in were sent from Facebook. It does so
- * by validating that the signature matches one that could only be generated
- * by using your application's secret key.
- *
- * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
- * in that order. $_POST and $_GET are always more up-to-date than cookies,
- * so we prefer those if they are available.
- *
- * For nitty-gritty details of when each of these is used, check out
- * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
- *
- * @param bool resolve_auth_token convert an auth token into a session
- */
- public function validate_fb_params($resolve_auth_token=true) {
- $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
-
- // note that with preload FQL, it's possible to receive POST params in
- // addition to GET, so use a different prefix to differentiate them
- if (!$this->fb_params) {
- $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
- $fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
- $this->fb_params = array_merge($fb_params, $fb_post_params);
- }
-
- // Okay, something came in via POST or GET
- if ($this->fb_params) {
- $user = isset($this->fb_params['user']) ?
- $this->fb_params['user'] : null;
- $this->profile_user = isset($this->fb_params['profile_user']) ?
- $this->fb_params['profile_user'] : null;
- $this->canvas_user = isset($this->fb_params['canvas_user']) ?
- $this->fb_params['canvas_user'] : null;
- $this->base_domain = isset($this->fb_params['base_domain']) ?
- $this->fb_params['base_domain'] : null;
-
- if (isset($this->fb_params['session_key'])) {
- $session_key = $this->fb_params['session_key'];
- } else if (isset($this->fb_params['profile_session_key'])) {
- $session_key = $this->fb_params['profile_session_key'];
- } else {
- $session_key = null;
- }
- $expires = isset($this->fb_params['expires']) ?
- $this->fb_params['expires'] : null;
- $this->set_user($user,
- $session_key,
- $expires);
- }
- // if no Facebook parameters were found in the GET or POST variables,
- // then fall back to cookies, which may have cached user information
- // Cookies are also used to receive session data via the Javascript API
- else if ($cookies =
- $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
-
- $base_domain_cookie = 'base_domain_' . $this->api_key;
- if (isset($_COOKIE[$base_domain_cookie])) {
- $this->base_domain = $_COOKIE[$base_domain_cookie];
- }
-
- // use $api_key . '_' as a prefix for the cookies in case there are
- // multiple facebook clients on the same domain.
- $expires = isset($cookies['expires']) ? $cookies['expires'] : null;
- $this->set_user($cookies['user'],
- $cookies['session_key'],
- $expires);
- }
- // finally, if we received no parameters, but the 'auth_token' GET var
- // is present, then we are in the middle of auth handshake,
- // so go ahead and create the session
- else if ($resolve_auth_token && isset($_GET['auth_token']) &&
- $session = $this->do_get_session($_GET['auth_token'])) {
- if ($this->generate_session_secret &&
- !empty($session['secret'])) {
- $session_secret = $session['secret'];
- }
-
- if (isset($session['base_domain'])) {
- $this->base_domain = $session['base_domain'];
- }
-
- $this->set_user($session['uid'],
- $session['session_key'],
- $session['expires'],
- isset($session_secret) ? $session_secret : null);
- }
-
- return !empty($this->fb_params);
- }
-
- // Store a temporary session secret for the current session
- // for use with the JS client library
- public function promote_session() {
- try {
- $session_secret = $this->api_client->auth_promoteSession();
- if (!$this->in_fb_canvas()) {
- $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
- }
- return $session_secret;
- } catch (FacebookRestClientException $e) {
- // API_EC_PARAM means we don't have a logged in user, otherwise who
- // knows what it means, so just throw it.
- if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
- throw $e;
- }
- }
- }
-
- public function do_get_session($auth_token) {
- try {
- return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
- } catch (FacebookRestClientException $e) {
- // API_EC_PARAM means we don't have a logged in user, otherwise who
- // knows what it means, so just throw it.
- if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
- throw $e;
- }
- }
- }
-
- // Invalidate the session currently being used, and clear any state associated
- // with it. Note that the user will still remain logged into Facebook.
- public function expire_session() {
- if ($this->api_client->auth_expireSession()) {
- $this->clear_cookie_state();
- return true;
- } else {
- return false;
- }
- }
-
- /** Logs the user out of all temporary application sessions as well as their
- * Facebook session. Note this will only work if the user has a valid current
- * session with the application.
- *
- * @param string $next URL to redirect to upon logging out
- *
- */
- public function logout($next) {
- $logout_url = $this->get_logout_url($next);
-
- // Clear any stored state
- $this->clear_cookie_state();
-
- $this->redirect($logout_url);
- }
-
- /**
- * Clears any persistent state stored about the user, including
- * cookies and information related to the current session in the
- * client.
- *
- */
- public function clear_cookie_state() {
- if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
- $cookies = array('user', 'session_key', 'expires', 'ss');
- foreach ($cookies as $name) {
- setcookie($this->api_key . '_' . $name, false, time() - 3600);
- unset($_COOKIE[$this->api_key . '_' . $name]);
- }
- setcookie($this->api_key, false, time() - 3600);
- unset($_COOKIE[$this->api_key]);
- }
-
- // now, clear the rest of the stored state
- $this->user = 0;
- $this->api_client->session_key = 0;
- }
-
- public function redirect($url) {
- if ($this->in_fb_canvas()) {
- echo '';
- } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
- // make sure facebook.com url's load in the full frame so that we don't
- // get a frame within a frame.
- echo "";
- } else {
- header('Location: ' . $url);
- }
- exit;
- }
-
- public function in_frame() {
- return isset($this->fb_params['in_canvas'])
- || isset($this->fb_params['in_iframe']);
- }
- public function in_fb_canvas() {
- return isset($this->fb_params['in_canvas']);
- }
-
- public function get_loggedin_user() {
- return $this->user;
- }
-
- public function get_canvas_user() {
- return $this->canvas_user;
- }
-
- public function get_profile_user() {
- return $this->profile_user;
- }
-
- public static function current_url() {
- return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
- }
-
- // require_add and require_install have been removed.
- // see http://developer.facebook.com/news.php?blog=1&story=116 for more details
- public function require_login() {
- if ($user = $this->get_loggedin_user()) {
- return $user;
- }
- $this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
- }
-
- public function require_frame() {
- if (!$this->in_frame()) {
- $this->redirect($this->get_login_url(self::current_url(), true));
- }
- }
-
- public static function get_facebook_url($subdomain='www') {
- return 'http://' . $subdomain . '.facebook.com';
- }
-
- public function get_install_url($next=null) {
- // this was renamed, keeping for compatibility's sake
- return $this->get_add_url($next);
- }
-
- public function get_add_url($next=null) {
- $page = self::get_facebook_url().'/add.php';
- $params = array('api_key' => $this->api_key);
-
- if ($next) {
- $params['next'] = $next;
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function get_login_url($next, $canvas) {
- $page = self::get_facebook_url().'/login.php';
- $params = array('api_key' => $this->api_key,
- 'v' => '1.0');
-
- if ($next) {
- $params['next'] = $next;
- }
- if ($canvas) {
- $params['canvas'] = '1';
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function get_logout_url($next) {
- $page = self::get_facebook_url().'/logout.php';
- $params = array('app_key' => $this->api_key,
- 'session_key' => $this->api_client->session_key);
-
- if ($next) {
- $params['connect_next'] = 1;
- $params['next'] = $next;
- }
-
- return $page . '?' . http_build_query($params);
- }
-
- public function set_user($user, $session_key, $expires=null, $session_secret=null) {
- if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
- || $_COOKIE[$this->api_key . '_user'] != $user)) {
- $this->set_cookies($user, $session_key, $expires, $session_secret);
- }
- $this->user = $user;
- $this->api_client->session_key = $session_key;
- $this->session_expires = $expires;
- }
-
- public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
- $cookies = array();
- $cookies['user'] = $user;
- $cookies['session_key'] = $session_key;
- if ($expires != null) {
- $cookies['expires'] = $expires;
- }
- if ($session_secret != null) {
- $cookies['ss'] = $session_secret;
- }
-
- foreach ($cookies as $name => $val) {
- setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
- $_COOKIE[$this->api_key . '_' . $name] = $val;
- }
- $sig = self::generate_sig($cookies, $this->secret);
- setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
- $_COOKIE[$this->api_key] = $sig;
-
- if ($this->base_domain != null) {
- $base_domain_cookie = 'base_domain_' . $this->api_key;
- setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
- $_COOKIE[$base_domain_cookie] = $this->base_domain;
- }
- }
-
- /**
- * Tries to undo the badness of magic quotes as best we can
- * @param string $val Should come directly from $_GET, $_POST, etc.
- * @return string val without added slashes
- */
- public static function no_magic_quotes($val) {
- if (get_magic_quotes_gpc()) {
- return stripslashes($val);
- } else {
- return $val;
- }
- }
-
- /*
- * Get the signed parameters that were sent from Facebook. Validates the set
- * of parameters against the included signature.
- *
- * Since Facebook sends data to your callback URL via unsecured means, the
- * signature is the only way to make sure that the data actually came from
- * Facebook. So if an app receives a request at the callback URL, it should
- * always verify the signature that comes with against your own secret key.
- * Otherwise, it's possible for someone to spoof a request by
- * pretending to be someone else, i.e.:
- * www.your-callback-url.com/?fb_user=10101
- *
- * This is done automatically by verify_fb_params.
- *
- * @param assoc $params a full array of external parameters.
- * presumed $_GET, $_POST, or $_COOKIE
- * @param int $timeout number of seconds that the args are good for.
- * Specifically good for forcing cookies to expire.
- * @param string $namespace prefix string for the set of parameters we want
- * to verify. i.e., fb_sig or fb_post_sig
- *
- * @return assoc the subset of parameters containing the given prefix,
- * and also matching the signature associated with them.
- * OR an empty array if the params do not validate
- */
- public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
- $prefix = $namespace . '_';
- $prefix_len = strlen($prefix);
- $fb_params = array();
- if (empty($params)) {
- return array();
- }
-
- foreach ($params as $name => $val) {
- // pull out only those parameters that match the prefix
- // note that the signature itself ($params[$namespace]) is not in the list
- if (strpos($name, $prefix) === 0) {
- $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
- }
- }
-
- // validate that the request hasn't expired. this is most likely
- // for params that come from $_COOKIE
- if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
- return array();
- }
-
- // validate that the params match the signature
- $signature = isset($params[$namespace]) ? $params[$namespace] : null;
- if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
- return array();
- }
- return $fb_params;
- }
-
- /**
- * Validates the account that a user was trying to set up an
- * independent account through Facebook Connect.
- *
- * @param user The user attempting to set up an independent account.
- * @param hash The hash passed to the reclamation URL used.
- * @return bool True if the user is the one that selected the
- * reclamation link.
- */
- public function verify_account_reclamation($user, $hash) {
- return $hash == md5($user . $this->secret);
- }
-
- /**
- * Validates that a given set of parameters match their signature.
- * Parameters all match a given input prefix, such as "fb_sig".
- *
- * @param $fb_params an array of all Facebook-sent parameters,
- * not including the signature itself
- * @param $expected_sig the expected result to check against
- */
- public function verify_signature($fb_params, $expected_sig) {
- return self::generate_sig($fb_params, $this->secret) == $expected_sig;
- }
-
- /**
- * Validate the given signed public session data structure with
- * public key of the app that
- * the session proof belongs to.
- *
- * @param $signed_data the session info that is passed by another app
- * @param string $public_key Optional public key of the app. If this
- * is not passed, function will make an API call to get it.
- * return true if the session proof passed verification.
- */
- public function verify_signed_public_session_data($signed_data,
- $public_key = null) {
-
- // If public key is not already provided, we need to get it through API
- if (!$public_key) {
- $public_key = $this->api_client->auth_getAppPublicKey(
- $signed_data['api_key']);
- }
-
- // Create data to verify
- $data_to_serialize = $signed_data;
- unset($data_to_serialize['sig']);
- $serialized_data = implode('_', $data_to_serialize);
-
- // Decode signature
- $signature = base64_decode($signed_data['sig']);
- $result = openssl_verify($serialized_data, $signature, $public_key,
- OPENSSL_ALGO_SHA1);
- return $result == 1;
- }
-
- /*
- * Generate a signature using the application secret key.
- *
- * The only two entities that know your secret key are you and Facebook,
- * according to the Terms of Service. Since nobody else can generate
- * the signature, you can rely on it to verify that the information
- * came from Facebook.
- *
- * @param $params_array an array of all Facebook-sent parameters,
- * NOT INCLUDING the signature itself
- * @param $secret your app's secret key
- *
- * @return a hash to be checked against the signature provided by Facebook
- */
- public static function generate_sig($params_array, $secret) {
- $str = '';
-
- ksort($params_array);
- // Note: make sure that the signature parameter is not already included in
- // $params_array.
- foreach ($params_array as $k=>$v) {
- $str .= "$k=$v";
- }
- $str .= $secret;
-
- return md5($str);
- }
-
- public function encode_validationError($summary, $message) {
- return json_encode(
- array('errorCode' => FACEBOOK_API_VALIDATION_ERROR,
- 'errorTitle' => $summary,
- 'errorMessage' => $message));
- }
-
- public function encode_multiFeedStory($feed, $next) {
- return json_encode(
- array('method' => 'multiFeedStory',
- 'content' =>
- array('next' => $next,
- 'feed' => $feed)));
- }
-
- public function encode_feedStory($feed, $next) {
- return json_encode(
- array('method' => 'feedStory',
- 'content' =>
- array('next' => $next,
- 'feed' => $feed)));
- }
-
- public function create_templatizedFeedStory($title_template, $title_data=array(),
- $body_template='', $body_data = array(), $body_general=null,
- $image_1=null, $image_1_link=null,
- $image_2=null, $image_2_link=null,
- $image_3=null, $image_3_link=null,
- $image_4=null, $image_4_link=null) {
- return array('title_template'=> $title_template,
- 'title_data' => $title_data,
- 'body_template'=> $body_template,
- 'body_data' => $body_data,
- 'body_general' => $body_general,
- 'image_1' => $image_1,
- 'image_1_link' => $image_1_link,
- 'image_2' => $image_2,
- 'image_2_link' => $image_2_link,
- 'image_3' => $image_3,
- 'image_3_link' => $image_3_link,
- 'image_4' => $image_4,
- 'image_4_link' => $image_4_link);
- }
-
-
-}
-
diff --git a/extlib/facebook/facebook_desktop.php b/extlib/facebook/facebook_desktop.php
deleted file mode 100644
index e79a2ca34..000000000
--- a/extlib/facebook/facebook_desktop.php
+++ /dev/null
@@ -1,104 +0,0 @@
-app_secret = $secret;
- $this->verify_sig = false;
- parent::__construct($api_key, $secret);
- }
-
- public function do_get_session($auth_token) {
- $this->api_client->secret = $this->app_secret;
- $this->api_client->session_key = null;
- $session_info = parent::do_get_session($auth_token);
- if (!empty($session_info['secret'])) {
- // store the session secret
- $this->set_session_secret($session_info['secret']);
- }
- return $session_info;
- }
-
- public function set_session_secret($session_secret) {
- $this->secret = $session_secret;
- $this->api_client->secret = $session_secret;
- }
-
- public function require_login() {
- if ($this->get_loggedin_user()) {
- try {
- // try a session-based API call to ensure that we have the correct
- // session secret
- $user = $this->api_client->users_getLoggedInUser();
-
- // now that we have a valid session secret, verify the signature
- $this->verify_sig = true;
- if ($this->validate_fb_params(false)) {
- return $user;
- } else {
- // validation failed
- return null;
- }
- } catch (FacebookRestClientException $ex) {
- if (isset($_GET['auth_token'])) {
- // if we have an auth_token, use it to establish a session
- $session_info = $this->do_get_session($_GET['auth_token']);
- if ($session_info) {
- return $session_info['uid'];
- }
- }
- }
- }
- // if we get here, we need to redirect the user to log in
- $this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
- }
-
- public function verify_signature($fb_params, $expected_sig) {
- // we don't want to verify the signature until we have a valid
- // session secret
- if ($this->verify_sig) {
- return parent::verify_signature($fb_params, $expected_sig);
- } else {
- return true;
- }
- }
-}
diff --git a/extlib/facebook/facebookapi_php5_restlib.php b/extlib/facebook/facebookapi_php5_restlib.php
deleted file mode 100755
index 55cb7fb86..000000000
--- a/extlib/facebook/facebookapi_php5_restlib.php
+++ /dev/null
@@ -1,3618 +0,0 @@
-secret = $secret;
- $this->session_key = $session_key;
- $this->api_key = $api_key;
- $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
- $this->last_call_id = 0;
- $this->call_as_apikey = '';
- $this->use_curl_if_available = true;
- $this->server_addr = Facebook::get_facebook_url('api') . '/restserver.php';
-
- if (!empty($GLOBALS['facebook_config']['debug'])) {
- $this->cur_id = 0;
- ?>
-
-user = $uid;
- }
-
- /**
- * Normally, if the cURL library/PHP extension is available, it is used for
- * HTTP transactions. This allows that behavior to be overridden, falling
- * back to a vanilla-PHP implementation even if cURL is installed.
- *
- * @param $use_curl_if_available bool whether or not to use cURL if available
- */
- public function set_use_curl_if_available($use_curl_if_available) {
- $this->use_curl_if_available = $use_curl_if_available;
- }
-
- /**
- * Start a batch operation.
- */
- public function begin_batch() {
- if ($this->pending_batch()) {
- $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- $this->batch_queue = array();
- $this->pending_batch = true;
- }
-
- /*
- * End current batch operation
- */
- public function end_batch() {
- if (!$this->pending_batch()) {
- $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- $this->pending_batch = false;
-
- $this->execute_server_side_batch();
- $this->batch_queue = null;
- }
-
- /**
- * are we currently queueing up calls for a batch?
- */
- public function pending_batch() {
- return $this->pending_batch;
- }
-
- private function execute_server_side_batch() {
- $item_count = count($this->batch_queue);
- $method_feed = array();
- foreach ($this->batch_queue as $batch_item) {
- $method = $batch_item['m'];
- $params = $batch_item['p'];
- list($get, $post) = $this->finalize_params($method, $params);
- $method_feed[] = $this->create_url_string(array_merge($post, $get));
- }
-
- $serial_only =
- ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
-
- $params = array('method_feed' => json_encode($method_feed),
- 'serial_only' => $serial_only,
- 'format' => $this->format);
- $result = $this->call_method('facebook.batch.run', $params);
-
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
-
- for ($i = 0; $i < $item_count; $i++) {
- $batch_item = $this->batch_queue[$i];
- $batch_item['p']['format'] = $this->format;
- $batch_item_result = $this->convert_result($result[$i],
- $batch_item['m'],
- $batch_item['p']);
-
- if (is_array($batch_item_result) &&
- isset($batch_item_result['error_code'])) {
- throw new FacebookRestClientException($batch_item_result['error_msg'],
- $batch_item_result['error_code']);
- }
- $batch_item['r'] = $batch_item_result;
- }
- }
-
- public function begin_permissions_mode($permissions_apikey) {
- $this->call_as_apikey = $permissions_apikey;
- }
-
- public function end_permissions_mode() {
- $this->call_as_apikey = '';
- }
-
-
- /*
- * If a page is loaded via HTTPS, then all images and static
- * resources need to be printed with HTTPS urls to avoid
- * mixed content warnings. If your page loads with an HTTPS
- * url, then call set_use_ssl_resources to retrieve the correct
- * urls.
- */
- public function set_use_ssl_resources($is_ssl = true) {
- $this->use_ssl_resources = $is_ssl;
- }
-
- /**
- * Returns public information for an application (as shown in the application
- * directory) by either application ID, API key, or canvas page name.
- *
- * @param int $application_id (Optional) app id
- * @param string $application_api_key (Optional) api key
- * @param string $application_canvas_name (Optional) canvas name
- *
- * Exactly one argument must be specified, otherwise it is an error.
- *
- * @return array An array of public information about the application.
- */
- public function application_getPublicInfo($application_id=null,
- $application_api_key=null,
- $application_canvas_name=null) {
- return $this->call_method('facebook.application.getPublicInfo',
- array('application_id' => $application_id,
- 'application_api_key' => $application_api_key,
- 'application_canvas_name' => $application_canvas_name));
- }
-
- /**
- * Creates an authentication token to be used as part of the desktop login
- * flow. For more information, please see
- * http://wiki.developers.facebook.com/index.php/Auth.createToken.
- *
- * @return string An authentication token.
- */
- public function auth_createToken() {
- return $this->call_method('facebook.auth.createToken');
- }
-
- /**
- * Returns the session information available after current user logs in.
- *
- * @param string $auth_token the token returned by
- * auth_createToken or passed back to
- * your callback_url.
- * @param bool $generate_session_secret whether the session returned should
- * include a session secret
- *
- * @return array An assoc array containing session_key, uid
- */
- public function auth_getSession($auth_token, $generate_session_secret=false) {
- if (!$this->pending_batch()) {
- $result = $this->call_method('facebook.auth.getSession',
- array('auth_token' => $auth_token,
- 'generate_session_secret' => $generate_session_secret));
- $this->session_key = $result['session_key'];
-
- if (!empty($result['secret']) && !$generate_session_secret) {
- // desktop apps have a special secret
- $this->secret = $result['secret'];
- }
- return $result;
- }
- }
-
- /**
- * Generates a session-specific secret. This is for integration with
- * client-side API calls, such as the JS library.
- *
- * @return array A session secret for the current promoted session
- *
- * @error API_EC_PARAM_SESSION_KEY
- * API_EC_PARAM_UNKNOWN
- */
- public function auth_promoteSession() {
- return $this->call_method('facebook.auth.promoteSession');
- }
-
- /**
- * Expires the session that is currently being used. If this call is
- * successful, no further calls to the API (which require a session) can be
- * made until a valid session is created.
- *
- * @return bool true if session expiration was successful, false otherwise
- */
- public function auth_expireSession() {
- return $this->call_method('facebook.auth.expireSession');
- }
-
- /**
- * Revokes the given extended permission that the user granted at some
- * prior time (for instance, offline_access or email). If no user is
- * provided, it will be revoked for the user of the current session.
- *
- * @param string $perm The permission to revoke
- * @param int $uid The user for whom to revoke the permission.
- */
- public function auth_revokeExtendedPermission($perm, $uid=null) {
- return $this->call_method('facebook.auth.revokeExtendedPermission',
- array('perm' => $perm, 'uid' => $uid));
- }
-
- /**
- * Revokes the user's agreement to the Facebook Terms of Service for your
- * application. If you call this method for one of your users, you will no
- * longer be able to make API requests on their behalf until they again
- * authorize your application. Use with care. Note that if this method is
- * called without a user parameter, then it will revoke access for the
- * current session's user.
- *
- * @param int $uid (Optional) User to revoke
- *
- * @return bool true if revocation succeeds, false otherwise
- */
- public function auth_revokeAuthorization($uid=null) {
- return $this->call_method('facebook.auth.revokeAuthorization',
- array('uid' => $uid));
- }
-
- /**
- * Get public key that is needed to verify digital signature
- * an app may pass to other apps. The public key is only used by
- * other apps for verification purposes.
- * @param string API key of an app
- * @return string The public key for the app.
- */
- public function auth_getAppPublicKey($target_app_key) {
- return $this->call_method('facebook.auth.getAppPublicKey',
- array('target_app_key' => $target_app_key));
- }
-
- /**
- * Get a structure that can be passed to another app
- * as proof of session. The other app can verify it using public
- * key of this app.
- *
- * @return signed public session data structure.
- */
- public function auth_getSignedPublicSessionData() {
- return $this->call_method('facebook.auth.getSignedPublicSessionData',
- array());
- }
-
- /**
- * Returns the number of unconnected friends that exist in this application.
- * This number is determined based on the accounts registered through
- * connect.registerUsers() (see below).
- */
- public function connect_getUnconnectedFriendsCount() {
- return $this->call_method('facebook.connect.getUnconnectedFriendsCount',
- array());
- }
-
- /**
- * This method is used to create an association between an external user
- * account and a Facebook user account, as per Facebook Connect.
- *
- * This method takes an array of account data, including a required email_hash
- * and optional account data. For each connected account, if the user exists,
- * the information is added to the set of the user's connected accounts.
- * If the user has already authorized the site, the connected account is added
- * in the confirmed state. If the user has not yet authorized the site, the
- * connected account is added in the pending state.
- *
- * This is designed to help Facebook Connect recognize when two Facebook
- * friends are both members of a external site, but perhaps are not aware of
- * it. The Connect dialog (see fb:connect-form) is used when friends can be
- * identified through these email hashes. See the following url for details:
- *
- * http://wiki.developers.facebook.com/index.php/Connect.registerUsers
- *
- * @param mixed $accounts A (JSON-encoded) array of arrays, where each array
- * has three properties:
- * 'email_hash' (req) - public email hash of account
- * 'account_id' (opt) - remote account id;
- * 'account_url' (opt) - url to remote account;
- *
- * @return array The list of email hashes for the successfully registered
- * accounts.
- */
- public function connect_registerUsers($accounts) {
- return $this->call_method('facebook.connect.registerUsers',
- array('accounts' => $accounts));
- }
-
- /**
- * Unregisters a set of accounts registered using connect.registerUsers.
- *
- * @param array $email_hashes The (JSON-encoded) list of email hashes to be
- * unregistered.
- *
- * @return array The list of email hashes which have been successfully
- * unregistered.
- */
- public function connect_unregisterUsers($email_hashes) {
- return $this->call_method('facebook.connect.unregisterUsers',
- array('email_hashes' => $email_hashes));
- }
-
- /**
- * Returns events according to the filters specified.
- *
- * @param int $uid (Optional) User associated with events. A null
- * parameter will default to the session user.
- * @param array/string $eids (Optional) Filter by these event
- * ids. A null parameter will get all events for
- * the user. (A csv list will work but is deprecated)
- * @param int $start_time (Optional) Filter with this unix time as lower
- * bound. A null or zero parameter indicates no
- * lower bound.
- * @param int $end_time (Optional) Filter with this UTC as upper bound.
- * A null or zero parameter indicates no upper
- * bound.
- * @param string $rsvp_status (Optional) Only show events where the given uid
- * has this rsvp status. This only works if you
- * have specified a value for $uid. Values are as
- * in events.getMembers. Null indicates to ignore
- * rsvp status when filtering.
- *
- * @return array The events matching the query.
- */
- public function &events_get($uid=null,
- $eids=null,
- $start_time=null,
- $end_time=null,
- $rsvp_status=null) {
- return $this->call_method('facebook.events.get',
- array('uid' => $uid,
- 'eids' => $eids,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'rsvp_status' => $rsvp_status));
- }
-
- /**
- * Returns membership list data associated with an event.
- *
- * @param int $eid event id
- *
- * @return array An assoc array of four membership lists, with keys
- * 'attending', 'unsure', 'declined', and 'not_replied'
- */
- public function &events_getMembers($eid) {
- return $this->call_method('facebook.events.getMembers',
- array('eid' => $eid));
- }
-
- /**
- * RSVPs the current user to this event.
- *
- * @param int $eid event id
- * @param string $rsvp_status 'attending', 'unsure', or 'declined'
- *
- * @return bool true if successful
- */
- public function &events_rsvp($eid, $rsvp_status) {
- return $this->call_method('facebook.events.rsvp',
- array(
- 'eid' => $eid,
- 'rsvp_status' => $rsvp_status));
- }
-
- /**
- * Cancels an event. Only works for events where application is the admin.
- *
- * @param int $eid event id
- * @param string $cancel_message (Optional) message to send to members of
- * the event about why it is cancelled
- *
- * @return bool true if successful
- */
- public function &events_cancel($eid, $cancel_message='') {
- return $this->call_method('facebook.events.cancel',
- array('eid' => $eid,
- 'cancel_message' => $cancel_message));
- }
-
- /**
- * Creates an event on behalf of the user is there is a session, otherwise on
- * behalf of app. Successful creation guarantees app will be admin.
- *
- * @param assoc array $event_info json encoded event information
- * @param string $file (Optional) filename of picture to set
- *
- * @return int event id
- */
- public function events_create($event_info, $file = null) {
- if ($file) {
- return $this->call_upload_method('facebook.events.create',
- array('event_info' => $event_info),
- $file,
- Facebook::get_facebook_url('api-photo') . '/restserver.php');
- } else {
- return $this->call_method('facebook.events.create',
- array('event_info' => $event_info));
- }
- }
-
- /**
- * Edits an existing event. Only works for events where application is admin.
- *
- * @param int $eid event id
- * @param assoc array $event_info json encoded event information
- * @param string $file (Optional) filename of new picture to set
- *
- * @return bool true if successful
- */
- public function events_edit($eid, $event_info, $file = null) {
- if ($file) {
- return $this->call_upload_method('facebook.events.edit',
- array('eid' => $eid, 'event_info' => $event_info),
- $file,
- Facebook::get_facebook_url('api-photo') . '/restserver.php');
- } else {
- return $this->call_method('facebook.events.edit',
- array('eid' => $eid,
- 'event_info' => $event_info));
- }
- }
-
- /**
- * Fetches and re-caches the image stored at the given URL, for use in images
- * published to non-canvas pages via the API (for example, to user profiles
- * via profile.setFBML, or to News Feed via feed.publishUserAction).
- *
- * @param string $url The absolute URL from which to refresh the image.
- *
- * @return bool true on success
- */
- public function &fbml_refreshImgSrc($url) {
- return $this->call_method('facebook.fbml.refreshImgSrc',
- array('url' => $url));
- }
-
- /**
- * Fetches and re-caches the content stored at the given URL, for use in an
- * fb:ref FBML tag.
- *
- * @param string $url The absolute URL from which to fetch content. This URL
- * should be used in a fb:ref FBML tag.
- *
- * @return bool true on success
- */
- public function &fbml_refreshRefUrl($url) {
- return $this->call_method('facebook.fbml.refreshRefUrl',
- array('url' => $url));
- }
-
- /**
- * Lets you insert text strings in their native language into the Facebook
- * Translations database so they can be translated.
- *
- * @param array $native_strings An array of maps, where each map has a 'text'
- * field and a 'description' field.
- *
- * @return int Number of strings uploaded.
- */
- public function &fbml_uploadNativeStrings($native_strings) {
- return $this->call_method('facebook.fbml.uploadNativeStrings',
- array('native_strings' => json_encode($native_strings)));
- }
-
- /**
- * Associates a given "handle" with FBML markup so that the handle can be
- * used within the fb:ref FBML tag. A handle is unique within an application
- * and allows an application to publish identical FBML to many user profiles
- * and do subsequent updates without having to republish FBML on behalf of
- * each user.
- *
- * @param string $handle The handle to associate with the given FBML.
- * @param string $fbml The FBML to associate with the given handle.
- *
- * @return bool true on success
- */
- public function &fbml_setRefHandle($handle, $fbml) {
- return $this->call_method('facebook.fbml.setRefHandle',
- array('handle' => $handle, 'fbml' => $fbml));
- }
-
- /**
- * Register custom tags for the application. Custom tags can be used
- * to extend the set of tags available to applications in FBML
- * markup.
- *
- * Before you call this function,
- * make sure you read the full documentation at
- *
- * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags
- *
- * IMPORTANT: This function overwrites the values of
- * existing tags if the names match. Use this function with care because
- * it may break the FBML of any application that is using the
- * existing version of the tags.
- *
- * @param mixed $tags an array of tag objects (the full description is on the
- * wiki page)
- *
- * @return int the number of tags that were registered
- */
- public function &fbml_registerCustomTags($tags) {
- $tags = json_encode($tags);
- return $this->call_method('facebook.fbml.registerCustomTags',
- array('tags' => $tags));
- }
-
- /**
- * Get the custom tags for an application. If $app_id
- * is not specified, the calling app's tags are returned.
- * If $app_id is different from the id of the calling app,
- * only the app's public tags are returned.
- * The return value is an array of the same type as
- * the $tags parameter of fbml_registerCustomTags().
- *
- * @param int $app_id the application's id (optional)
- *
- * @return mixed an array containing the custom tag objects
- */
- public function &fbml_getCustomTags($app_id = null) {
- return $this->call_method('facebook.fbml.getCustomTags',
- array('app_id' => $app_id));
- }
-
-
- /**
- * Delete custom tags the application has registered. If
- * $tag_names is null, all the application's custom tags will be
- * deleted.
- *
- * IMPORTANT: If your application has registered public tags
- * that other applications may be using, don't delete those tags!
- * Doing so can break the FBML ofapplications that are using them.
- *
- * @param array $tag_names the names of the tags to delete (optinal)
- * @return bool true on success
- */
- public function &fbml_deleteCustomTags($tag_names = null) {
- return $this->call_method('facebook.fbml.deleteCustomTags',
- array('tag_names' => json_encode($tag_names)));
- }
-
-
-
- /**
- * This method is deprecated for calls made on behalf of users. This method
- * works only for publishing stories on a Facebook Page that has installed
- * your application. To publish stories to a user's profile, use
- * feed.publishUserAction instead.
- *
- * For more details on this call, please visit the wiki page:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction
- */
- public function &feed_publishTemplatizedAction($title_template,
- $title_data,
- $body_template,
- $body_data,
- $body_general,
- $image_1=null,
- $image_1_link=null,
- $image_2=null,
- $image_2_link=null,
- $image_3=null,
- $image_3_link=null,
- $image_4=null,
- $image_4_link=null,
- $target_ids='',
- $page_actor_id=null) {
- return $this->call_method('facebook.feed.publishTemplatizedAction',
- array('title_template' => $title_template,
- 'title_data' => $title_data,
- 'body_template' => $body_template,
- 'body_data' => $body_data,
- 'body_general' => $body_general,
- 'image_1' => $image_1,
- 'image_1_link' => $image_1_link,
- 'image_2' => $image_2,
- 'image_2_link' => $image_2_link,
- 'image_3' => $image_3,
- 'image_3_link' => $image_3_link,
- 'image_4' => $image_4,
- 'image_4_link' => $image_4_link,
- 'target_ids' => $target_ids,
- 'page_actor_id' => $page_actor_id));
- }
-
- /**
- * Registers a template bundle. Template bundles are somewhat involved, so
- * it's recommended you check out the wiki for more details:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
- *
- * @return string A template bundle id
- */
- public function &feed_registerTemplateBundle($one_line_story_templates,
- $short_story_templates = array(),
- $full_story_template = null,
- $action_links = array()) {
-
- $one_line_story_templates = json_encode($one_line_story_templates);
-
- if (!empty($short_story_templates)) {
- $short_story_templates = json_encode($short_story_templates);
- }
-
- if (isset($full_story_template)) {
- $full_story_template = json_encode($full_story_template);
- }
-
- if (isset($action_links)) {
- $action_links = json_encode($action_links);
- }
-
- return $this->call_method('facebook.feed.registerTemplateBundle',
- array('one_line_story_templates' => $one_line_story_templates,
- 'short_story_templates' => $short_story_templates,
- 'full_story_template' => $full_story_template,
- 'action_links' => $action_links));
- }
-
- /**
- * Retrieves the full list of active template bundles registered by the
- * requesting application.
- *
- * @return array An array of template bundles
- */
- public function &feed_getRegisteredTemplateBundles() {
- return $this->call_method('facebook.feed.getRegisteredTemplateBundles',
- array());
- }
-
- /**
- * Retrieves information about a specified template bundle previously
- * registered by the requesting application.
- *
- * @param string $template_bundle_id The template bundle id
- *
- * @return array Template bundle
- */
- public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) {
- return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID',
- array('template_bundle_id' => $template_bundle_id));
- }
-
- /**
- * Deactivates a previously registered template bundle.
- *
- * @param string $template_bundle_id The template bundle id
- *
- * @return bool true on success
- */
- public function &feed_deactivateTemplateBundleByID($template_bundle_id) {
- return $this->call_method('facebook.feed.deactivateTemplateBundleByID',
- array('template_bundle_id' => $template_bundle_id));
- }
-
- const STORY_SIZE_ONE_LINE = 1;
- const STORY_SIZE_SHORT = 2;
- const STORY_SIZE_FULL = 4;
-
- /**
- * Publishes a story on behalf of the user owning the session, using the
- * specified template bundle. This method requires an active session key in
- * order to be called.
- *
- * The parameters to this method ($templata_data in particular) are somewhat
- * involved. It's recommended you visit the wiki for details:
- *
- * http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
- *
- * @param int $template_bundle_id A template bundle id previously registered
- * @param array $template_data See wiki article for syntax
- * @param array $target_ids (Optional) An array of friend uids of the
- * user who shared in this action.
- * @param string $body_general (Optional) Additional markup that extends
- * the body of a short story.
- * @param int $story_size (Optional) A story size (see above)
- * @param string $user_message (Optional) A user message for a short
- * story.
- *
- * @return bool true on success
- */
- public function &feed_publishUserAction(
- $template_bundle_id, $template_data, $target_ids='', $body_general='',
- $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
- $user_message='') {
-
- if (is_array($template_data)) {
- $template_data = json_encode($template_data);
- } // allow client to either pass in JSON or an assoc that we JSON for them
-
- if (is_array($target_ids)) {
- $target_ids = json_encode($target_ids);
- $target_ids = trim($target_ids, "[]"); // we don't want square brackets
- }
-
- return $this->call_method('facebook.feed.publishUserAction',
- array('template_bundle_id' => $template_bundle_id,
- 'template_data' => $template_data,
- 'target_ids' => $target_ids,
- 'body_general' => $body_general,
- 'story_size' => $story_size,
- 'user_message' => $user_message));
- }
-
-
- /**
- * Publish a post to the user's stream.
- *
- * @param $message the user's message
- * @param $attachment the post's attachment (optional)
- * @param $action links the post's action links (optional)
- * @param $target_id the user on whose wall the post will be posted
- * (optional)
- * @param $uid the actor (defaults to session user)
- * @return string the post id
- */
- public function stream_publish(
- $message, $attachment = null, $action_links = null, $target_id = null,
- $uid = null) {
-
- return $this->call_method(
- 'facebook.stream.publish',
- array('message' => $message,
- 'attachment' => $attachment,
- 'action_links' => $action_links,
- 'target_id' => $target_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Remove a post from the user's stream.
- * Currently, you may only remove stories you application created.
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_remove($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.remove',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Add a comment to a stream post
- *
- * @param $post_id the post id
- * @param $comment the comment text
- * @param $uid the actor (defaults to session user)
- * @return string the id of the created comment
- */
- public function stream_addComment($post_id, $comment, $uid = null) {
- return $this->call_method(
- 'facebook.stream.addComment',
- array('post_id' => $post_id,
- 'comment' => $comment,
- 'uid' => $this->get_uid($uid)));
- }
-
-
- /**
- * Remove a comment from a stream post
- *
- * @param $comment_id the comment id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_removeComment($comment_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.removeComment',
- array('comment_id' => $comment_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Add a like to a stream post
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_addLike($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.addLike',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Remove a like from a stream post
- *
- * @param $post_id the post id
- * @param $uid the actor (defaults to session user)
- * @return bool
- */
- public function stream_removeLike($post_id, $uid = null) {
- return $this->call_method(
- 'facebook.stream.removeLike',
- array('post_id' => $post_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * For the current user, retrieves stories generated by the user's friends
- * while using this application. This can be used to easily create a
- * "News Feed" like experience.
- *
- * @return array An array of feed story objects.
- */
- public function &feed_getAppFriendStories() {
- return $this->call_method('facebook.feed.getAppFriendStories');
- }
-
- /**
- * Makes an FQL query. This is a generalized way of accessing all the data
- * in the API, as an alternative to most of the other method calls. More
- * info at http://wiki.developers.facebook.com/index.php/FQL
- *
- * @param string $query the query to evaluate
- *
- * @return array generalized array representing the results
- */
- public function &fql_query($query) {
- return $this->call_method('facebook.fql.query',
- array('query' => $query));
- }
-
- /**
- * Makes a set of FQL queries in parallel. This method takes a dictionary
- * of FQL queries where the keys are names for the queries. Results from
- * one query can be used within another query to fetch additional data. More
- * info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL
- *
- * @param string $queries JSON-encoded dictionary of queries to evaluate
- *
- * @return array generalized array representing the results
- */
- public function &fql_multiquery($queries) {
- return $this->call_method('facebook.fql.multiquery',
- array('queries' => $queries));
- }
-
- /**
- * Returns whether or not pairs of users are friends.
- * Note that the Facebook friend relationship is symmetric.
- *
- * @param array/string $uids1 list of ids (id_1, id_2,...)
- * of some length X (csv is deprecated)
- * @param array/string $uids2 list of ids (id_A, id_B,...)
- * of SAME length X (csv is deprecated)
- *
- * @return array An array with uid1, uid2, and bool if friends, e.g.:
- * array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
- * 1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
- * ...)
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function &friends_areFriends($uids1, $uids2) {
- return $this->call_method('facebook.friends.areFriends',
- array('uids1' => $uids1,
- 'uids2' => $uids2));
- }
-
- /**
- * Returns the friends of the current session user.
- *
- * @param int $flid (Optional) Only return friends on this friend list.
- * @param int $uid (Optional) Return friends for this user.
- *
- * @return array An array of friends
- */
- public function &friends_get($flid=null, $uid = null) {
- if (isset($this->friends_list)) {
- return $this->friends_list;
- }
- $params = array();
- if (!$uid && isset($this->canvas_user)) {
- $uid = $this->canvas_user;
- }
- if ($uid) {
- $params['uid'] = $uid;
- }
- if ($flid) {
- $params['flid'] = $flid;
- }
- return $this->call_method('facebook.friends.get', $params);
-
- }
-
- /**
- * Returns the mutual friends between the target uid and a source uid or
- * the current session user.
- *
- * @param int $target_uid Target uid for which mutual friends will be found.
- * @param int $source_uid (optional) Source uid for which mutual friends will
- * be found. If no source_uid is specified,
- * source_id will default to the session
- * user.
- * @return array An array of friend uids
- */
- public function &friends_getMutualFriends($target_uid, $source_uid = null) {
- return $this->call_method('facebook.friends.getMutualFriends',
- array("target_uid" => $target_uid,
- "source_uid" => $source_uid));
- }
-
- /**
- * Returns the set of friend lists for the current session user.
- *
- * @return array An array of friend list objects
- */
- public function &friends_getLists() {
- return $this->call_method('facebook.friends.getLists');
- }
-
- /**
- * Returns the friends of the session user, who are also users
- * of the calling application.
- *
- * @return array An array of friends also using the app
- */
- public function &friends_getAppUsers() {
- return $this->call_method('facebook.friends.getAppUsers');
- }
-
- /**
- * Returns groups according to the filters specified.
- *
- * @param int $uid (Optional) User associated with groups. A null
- * parameter will default to the session user.
- * @param array/string $gids (Optional) Array of group ids to query. A null
- * parameter will get all groups for the user.
- * (csv is deprecated)
- *
- * @return array An array of group objects
- */
- public function &groups_get($uid, $gids) {
- return $this->call_method('facebook.groups.get',
- array('uid' => $uid,
- 'gids' => $gids));
- }
-
- /**
- * Returns the membership list of a group.
- *
- * @param int $gid Group id
- *
- * @return array An array with four membership lists, with keys 'members',
- * 'admins', 'officers', and 'not_replied'
- */
- public function &groups_getMembers($gid) {
- return $this->call_method('facebook.groups.getMembers',
- array('gid' => $gid));
- }
-
- /**
- * Returns cookies according to the filters specified.
- *
- * @param int $uid User for which the cookies are needed.
- * @param string $name (Optional) A null parameter will get all cookies
- * for the user.
- *
- * @return array Cookies! Nom nom nom nom nom.
- */
- public function data_getCookies($uid, $name) {
- return $this->call_method('facebook.data.getCookies',
- array('uid' => $uid,
- 'name' => $name));
- }
-
- /**
- * Sets cookies according to the params specified.
- *
- * @param int $uid User for which the cookies are needed.
- * @param string $name Name of the cookie
- * @param string $value (Optional) if expires specified and is in the past
- * @param int $expires (Optional) Expiry time
- * @param string $path (Optional) Url path to associate with (default is /)
- *
- * @return bool true on success
- */
- public function data_setCookie($uid, $name, $value, $expires, $path) {
- return $this->call_method('facebook.data.setCookie',
- array('uid' => $uid,
- 'name' => $name,
- 'value' => $value,
- 'expires' => $expires,
- 'path' => $path));
- }
-
- /**
- * Retrieves links posted by the given user.
- *
- * @param int $uid The user whose links you wish to retrieve
- * @param int $limit The maximimum number of links to retrieve
- * @param array $link_ids (Optional) Array of specific link
- * IDs to retrieve by this user
- *
- * @return array An array of links.
- */
- public function &links_get($uid, $limit, $link_ids = null) {
- return $this->call_method('links.get',
- array('uid' => $uid,
- 'limit' => $limit,
- 'link_ids' => $link_ids));
- }
-
- /**
- * Posts a link on Facebook.
- *
- * @param string $url URL/link you wish to post
- * @param string $comment (Optional) A comment about this link
- * @param int $uid (Optional) User ID that is posting this link;
- * defaults to current session user
- *
- * @return bool
- */
- public function &links_post($url, $comment='', $uid = null) {
- return $this->call_method('links.post',
- array('uid' => $uid,
- 'url' => $url,
- 'comment' => $comment));
- }
-
- /**
- * Permissions API
- */
-
- /**
- * Checks API-access granted by self to the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_checkGrantedApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.checkGrantedApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Checks API-access granted to self by the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_checkAvailableApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.checkAvailableApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Grant API-access to the specified methods/namespaces to the specified
- * application.
- *
- * @param string $permissions_apikey Other application key
- * @param array(string) $method_arr (Optional) API methods/namespaces
- * allowed
- *
- * @return array API methods/namespaces which are allowed access
- */
- public function permissions_grantApiAccess($permissions_apikey, $method_arr) {
- return $this->call_method('facebook.permissions.grantApiAccess',
- array('permissions_apikey' => $permissions_apikey,
- 'method_arr' => $method_arr));
- }
-
- /**
- * Revoke API-access granted to the specified application.
- *
- * @param string $permissions_apikey Other application key
- *
- * @return bool true on success
- */
- public function permissions_revokeApiAccess($permissions_apikey) {
- return $this->call_method('facebook.permissions.revokeApiAccess',
- array('permissions_apikey' => $permissions_apikey));
- }
-
- /**
- * Payments Order API
- */
-
- /**
- * Set Payments properties for an app.
- *
- * @param properties a map from property names to values
- * @return true on success
- */
- public function payments_setProperties($properties) {
- return $this->call_method ('facebook.payments.setProperties',
- array('properties' => json_encode($properties)));
- }
-
- public function payments_getOrderDetails($order_id) {
- return json_decode($this->call_method(
- 'facebook.payments.getOrderDetails',
- array('order_id' => $order_id)), true);
- }
-
- public function payments_updateOrder($order_id, $status,
- $params) {
- return $this->call_method('facebook.payments.updateOrder',
- array('order_id' => $order_id,
- 'status' => $status,
- 'params' => json_encode($params)));
- }
-
- public function payments_getOrders($status, $start_time,
- $end_time, $test_mode=false) {
- return json_decode($this->call_method('facebook.payments.getOrders',
- array('status' => $status,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'test_mode' => $test_mode)), true);
- }
-
- /**
- * Creates a note with the specified title and content.
- *
- * @param string $title Title of the note.
- * @param string $content Content of the note.
- * @param int $uid (Optional) The user for whom you are creating a
- * note; defaults to current session user
- *
- * @return int The ID of the note that was just created.
- */
- public function ¬es_create($title, $content, $uid = null) {
- return $this->call_method('notes.create',
- array('uid' => $uid,
- 'title' => $title,
- 'content' => $content));
- }
-
- /**
- * Deletes the specified note.
- *
- * @param int $note_id ID of the note you wish to delete
- * @param int $uid (Optional) Owner of the note you wish to delete;
- * defaults to current session user
- *
- * @return bool
- */
- public function ¬es_delete($note_id, $uid = null) {
- return $this->call_method('notes.delete',
- array('uid' => $uid,
- 'note_id' => $note_id));
- }
-
- /**
- * Edits a note, replacing its title and contents with the title
- * and contents specified.
- *
- * @param int $note_id ID of the note you wish to edit
- * @param string $title Replacement title for the note
- * @param string $content Replacement content for the note
- * @param int $uid (Optional) Owner of the note you wish to edit;
- * defaults to current session user
- *
- * @return bool
- */
- public function ¬es_edit($note_id, $title, $content, $uid = null) {
- return $this->call_method('notes.edit',
- array('uid' => $uid,
- 'note_id' => $note_id,
- 'title' => $title,
- 'content' => $content));
- }
-
- /**
- * Retrieves all notes by a user. If note_ids are specified,
- * retrieves only those specific notes by that user.
- *
- * @param int $uid User whose notes you wish to retrieve
- * @param array $note_ids (Optional) List of specific note
- * IDs by this user to retrieve
- *
- * @return array A list of all of the given user's notes, or an empty list
- * if the viewer lacks permissions or if there are no visible
- * notes.
- */
- public function ¬es_get($uid, $note_ids = null) {
- return $this->call_method('notes.get',
- array('uid' => $uid,
- 'note_ids' => $note_ids));
- }
-
-
- /**
- * Returns the outstanding notifications for the session user.
- *
- * @return array An assoc array of notification count objects for
- * 'messages', 'pokes' and 'shares', a uid list of
- * 'friend_requests', a gid list of 'group_invites',
- * and an eid list of 'event_invites'
- */
- public function ¬ifications_get() {
- return $this->call_method('facebook.notifications.get');
- }
-
- /**
- * Sends a notification to the specified users.
- *
- * @return A comma separated list of successful recipients
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function ¬ifications_send($to_ids, $notification, $type) {
- return $this->call_method('facebook.notifications.send',
- array('to_ids' => $to_ids,
- 'notification' => $notification,
- 'type' => $type));
- }
-
- /**
- * Sends an email to the specified user of the application.
- *
- * @param array/string $recipients array of ids of the recipients (csv is deprecated)
- * @param string $subject subject of the email
- * @param string $text (plain text) body of the email
- * @param string $fbml fbml markup for an html version of the email
- *
- * @return string A comma separated list of successful recipients
- * @error
- * API_EC_PARAM_USER_ID_LIST
- */
- public function ¬ifications_sendEmail($recipients,
- $subject,
- $text,
- $fbml) {
- return $this->call_method('facebook.notifications.sendEmail',
- array('recipients' => $recipients,
- 'subject' => $subject,
- 'text' => $text,
- 'fbml' => $fbml));
- }
-
- /**
- * Returns the requested info fields for the requested set of pages.
- *
- * @param array/string $page_ids an array of page ids (csv is deprecated)
- * @param array/string $fields an array of strings describing the
- * info fields desired (csv is deprecated)
- * @param int $uid (Optional) limit results to pages of which this
- * user is a fan.
- * @param string type limits results to a particular type of page.
- *
- * @return array An array of pages
- */
- public function &pages_getInfo($page_ids, $fields, $uid, $type) {
- return $this->call_method('facebook.pages.getInfo',
- array('page_ids' => $page_ids,
- 'fields' => $fields,
- 'uid' => $uid,
- 'type' => $type));
- }
-
- /**
- * Returns true if the given user is an admin for the passed page.
- *
- * @param int $page_id target page id
- * @param int $uid (Optional) user id (defaults to the logged-in user)
- *
- * @return bool true on success
- */
- public function &pages_isAdmin($page_id, $uid = null) {
- return $this->call_method('facebook.pages.isAdmin',
- array('page_id' => $page_id,
- 'uid' => $uid));
- }
-
- /**
- * Returns whether or not the given page has added the application.
- *
- * @param int $page_id target page id
- *
- * @return bool true on success
- */
- public function &pages_isAppAdded($page_id) {
- return $this->call_method('facebook.pages.isAppAdded',
- array('page_id' => $page_id));
- }
-
- /**
- * Returns true if logged in user is a fan for the passed page.
- *
- * @param int $page_id target page id
- * @param int $uid user to compare. If empty, the logged in user.
- *
- * @return bool true on success
- */
- public function &pages_isFan($page_id, $uid = null) {
- return $this->call_method('facebook.pages.isFan',
- array('page_id' => $page_id,
- 'uid' => $uid));
- }
-
- /**
- * Adds a tag with the given information to a photo. See the wiki for details:
- *
- * http://wiki.developers.facebook.com/index.php/Photos.addTag
- *
- * @param int $pid The ID of the photo to be tagged
- * @param int $tag_uid The ID of the user being tagged. You must specify
- * either the $tag_uid or the $tag_text parameter
- * (unless $tags is specified).
- * @param string $tag_text Some text identifying the person being tagged.
- * You must specify either the $tag_uid or $tag_text
- * parameter (unless $tags is specified).
- * @param float $x The horizontal position of the tag, as a
- * percentage from 0 to 100, from the left of the
- * photo.
- * @param float $y The vertical position of the tag, as a percentage
- * from 0 to 100, from the top of the photo.
- * @param array $tags (Optional) An array of maps, where each map
- * can contain the tag_uid, tag_text, x, and y
- * parameters defined above. If specified, the
- * individual arguments are ignored.
- * @param int $owner_uid (Optional) The user ID of the user whose photo
- * you are tagging. If this parameter is not
- * specified, then it defaults to the session user.
- *
- * @return bool true on success
- */
- public function &photos_addTag($pid,
- $tag_uid,
- $tag_text,
- $x,
- $y,
- $tags,
- $owner_uid=0) {
- return $this->call_method('facebook.photos.addTag',
- array('pid' => $pid,
- 'tag_uid' => $tag_uid,
- 'tag_text' => $tag_text,
- 'x' => $x,
- 'y' => $y,
- 'tags' => (is_array($tags)) ? json_encode($tags) : null,
- 'owner_uid' => $this->get_uid($owner_uid)));
- }
-
- /**
- * Creates and returns a new album owned by the specified user or the current
- * session user.
- *
- * @param string $name The name of the album.
- * @param string $description (Optional) A description of the album.
- * @param string $location (Optional) A description of the location.
- * @param string $visible (Optional) A privacy setting for the album.
- * One of 'friends', 'friends-of-friends',
- * 'networks', or 'everyone'. Default 'everyone'.
- * @param int $uid (Optional) User id for creating the album; if
- * not specified, the session user is used.
- *
- * @return array An album object
- */
- public function &photos_createAlbum($name,
- $description='',
- $location='',
- $visible='',
- $uid=0) {
- return $this->call_method('facebook.photos.createAlbum',
- array('name' => $name,
- 'description' => $description,
- 'location' => $location,
- 'visible' => $visible,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Returns photos according to the filters specified.
- *
- * @param int $subj_id (Optional) Filter by uid of user tagged in the photos.
- * @param int $aid (Optional) Filter by an album, as returned by
- * photos_getAlbums.
- * @param array/string $pids (Optional) Restrict to an array of pids
- * (csv is deprecated)
- *
- * Note that at least one of these parameters needs to be specified, or an
- * error is returned.
- *
- * @return array An array of photo objects.
- */
- public function &photos_get($subj_id, $aid, $pids) {
- return $this->call_method('facebook.photos.get',
- array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids));
- }
-
- /**
- * Returns the albums created by the given user.
- *
- * @param int $uid (Optional) The uid of the user whose albums you want.
- * A null will return the albums of the session user.
- * @param string $aids (Optional) An array of aids to restrict
- * the query. (csv is deprecated)
- *
- * Note that at least one of the (uid, aids) parameters must be specified.
- *
- * @returns an array of album objects.
- */
- public function &photos_getAlbums($uid, $aids) {
- return $this->call_method('facebook.photos.getAlbums',
- array('uid' => $uid,
- 'aids' => $aids));
- }
-
- /**
- * Returns the tags on all photos specified.
- *
- * @param string $pids A list of pids to query
- *
- * @return array An array of photo tag objects, which include pid,
- * subject uid, and two floating-point numbers (xcoord, ycoord)
- * for tag pixel location.
- */
- public function &photos_getTags($pids) {
- return $this->call_method('facebook.photos.getTags',
- array('pids' => $pids));
- }
-
- /**
- * Uploads a photo.
- *
- * @param string $file The location of the photo on the local filesystem.
- * @param int $aid (Optional) The album into which to upload the
- * photo.
- * @param string $caption (Optional) A caption for the photo.
- * @param int uid (Optional) The user ID of the user whose photo you
- * are uploading
- *
- * @return array An array of user objects
- */
- public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
- return $this->call_upload_method('facebook.photos.upload',
- array('aid' => $aid,
- 'caption' => $caption,
- 'uid' => $uid),
- $file);
- }
-
-
- /**
- * Uploads a video.
- *
- * @param string $file The location of the video on the local filesystem.
- * @param string $title (Optional) A title for the video. Titles over 65 characters in length will be truncated.
- * @param string $description (Optional) A description for the video.
- *
- * @return array An array with the video's ID, title, description, and a link to view it on Facebook.
- */
- public function video_upload($file, $title=null, $description=null) {
- return $this->call_upload_method('facebook.video.upload',
- array('title' => $title,
- 'description' => $description),
- $file,
- Facebook::get_facebook_url('api-video') . '/restserver.php');
- }
-
- /**
- * Returns an array with the video limitations imposed on the current session's
- * associated user. Maximum length is measured in seconds; maximum size is
- * measured in bytes.
- *
- * @return array Array with "length" and "size" keys
- */
- public function &video_getUploadLimits() {
- return $this->call_method('facebook.video.getUploadLimits');
- }
-
- /**
- * Returns the requested info fields for the requested set of users.
- *
- * @param array/string $uids An array of user ids (csv is deprecated)
- * @param array/string $fields An array of info field names desired (csv is deprecated)
- *
- * @return array An array of user objects
- */
- public function &users_getInfo($uids, $fields) {
- return $this->call_method('facebook.users.getInfo',
- array('uids' => $uids,
- 'fields' => $fields));
- }
-
- /**
- * Returns the requested info fields for the requested set of users. A
- * session key must not be specified. Only data about users that have
- * authorized your application will be returned.
- *
- * Check the wiki for fields that can be queried through this API call.
- * Data returned from here should not be used for rendering to application
- * users, use users.getInfo instead, so that proper privacy rules will be
- * applied.
- *
- * @param array/string $uids An array of user ids (csv is deprecated)
- * @param array/string $fields An array of info field names desired (csv is deprecated)
- *
- * @return array An array of user objects
- */
- public function &users_getStandardInfo($uids, $fields) {
- return $this->call_method('facebook.users.getStandardInfo',
- array('uids' => $uids,
- 'fields' => $fields));
- }
-
- /**
- * Returns the user corresponding to the current session object.
- *
- * @return integer User id
- */
- public function &users_getLoggedInUser() {
- return $this->call_method('facebook.users.getLoggedInUser');
- }
-
- /**
- * Returns 1 if the user has the specified permission, 0 otherwise.
- * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission
- *
- * @return integer 1 or 0
- */
- public function &users_hasAppPermission($ext_perm, $uid=null) {
- return $this->call_method('facebook.users.hasAppPermission',
- array('ext_perm' => $ext_perm, 'uid' => $uid));
- }
-
- /**
- * Returns whether or not the user corresponding to the current
- * session object has the give the app basic authorization.
- *
- * @return boolean true if the user has authorized the app
- */
- public function &users_isAppUser($uid=null) {
- if ($uid === null && isset($this->is_user)) {
- return $this->is_user;
- }
-
- return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
- }
-
- /**
- * Returns whether or not the user corresponding to the current
- * session object is verified by Facebook. See the documentation
- * for Users.isVerified for details.
- *
- * @return boolean true if the user is verified
- */
- public function &users_isVerified() {
- return $this->call_method('facebook.users.isVerified');
- }
-
- /**
- * Sets the users' current status message. Message does NOT contain the
- * word "is" , so make sure to include a verb.
- *
- * Example: setStatus("is loving the API!")
- * will produce the status "Luke is loving the API!"
- *
- * @param string $status text-only message to set
- * @param int $uid user to set for (defaults to the
- * logged-in user)
- * @param bool $clear whether or not to clear the status,
- * instead of setting it
- * @param bool $status_includes_verb if true, the word "is" will *not* be
- * prepended to the status message
- *
- * @return boolean
- */
- public function &users_setStatus($status,
- $uid = null,
- $clear = false,
- $status_includes_verb = true) {
- $args = array(
- 'status' => $status,
- 'uid' => $uid,
- 'clear' => $clear,
- 'status_includes_verb' => $status_includes_verb,
- );
- return $this->call_method('facebook.users.setStatus', $args);
- }
-
- /**
- * Gets the comments for a particular xid. This is essentially a wrapper
- * around the comment FQL table.
- *
- * @param string $xid external id associated with the comments
- *
- * @return array of comment objects
- */
- public function &comments_get($xid) {
- $args = array('xid' => $xid);
- return $this->call_method('facebook.comments.get', $args);
- }
-
- /**
- * Add a comment to a particular xid on behalf of a user. If called
- * without an app_secret (with session secret), this will only work
- * for the session user.
- *
- * @param string $xid external id associated with the comments
- * @param string $text text of the comment
- * @param int $uid user adding the comment (def: session user)
- * @param string $title optional title for the stream story
- * @param string $url optional url for the stream story
- * @param bool $publish_to_stream publish a feed story about this comment?
- * a link will be generated to title/url in the story
- *
- * @return string comment_id associated with the comment
- */
- public function &comments_add($xid, $text, $uid=0, $title='', $url='',
- $publish_to_stream=false) {
- $args = array(
- 'xid' => $xid,
- 'uid' => $this->get_uid($uid),
- 'text' => $text,
- 'title' => $title,
- 'url' => $url,
- 'publish_to_stream' => $publish_to_stream);
-
- return $this->call_method('facebook.comments.add', $args);
- }
-
- /**
- * Remove a particular comment.
- *
- * @param string $xid the external id associated with the comments
- * @param string $comment_id id of the comment to remove (returned by
- * comments.add and comments.get)
- *
- * @return boolean
- */
- public function &comments_remove($xid, $comment_id) {
- $args = array(
- 'xid' => $xid,
- 'comment_id' => $comment_id);
- return $this->call_method('facebook.comments.remove', $args);
- }
-
- /**
- * Gets the stream on behalf of a user using a set of users. This
- * call will return the latest $limit queries between $start_time
- * and $end_time.
- *
- * @param int $viewer_id user making the call (def: session)
- * @param array $source_ids users/pages to look at (def: all connections)
- * @param int $start_time start time to look for stories (def: 1 day ago)
- * @param int $end_time end time to look for stories (def: now)
- * @param int $limit number of stories to attempt to fetch (def: 30)
- * @param string $filter_key key returned by stream.getFilters to fetch
- * @param array $metadata metadata to include with the return, allows
- * requested metadata to be returned, such as
- * profiles, albums, photo_tags
- *
- * @return array(
- * 'posts' => array of posts,
- * // if requested, the following data may be returned
- * 'profiles' => array of profile metadata of users/pages in posts
- * 'albums' => array of album metadata in posts
- * 'photo_tags' => array of photo_tags for photos in posts
- * )
- */
- public function &stream_get($viewer_id = null,
- $source_ids = null,
- $start_time = 0,
- $end_time = 0,
- $limit = 30,
- $filter_key = '') {
- $args = array(
- 'viewer_id' => $viewer_id,
- 'source_ids' => $source_ids,
- 'start_time' => $start_time,
- 'end_time' => $end_time,
- 'limit' => $limit,
- 'filter_key' => $filter_key);
- return $this->call_method('facebook.stream.get', $args);
- }
-
- /**
- * Gets the filters (with relevant filter keys for stream.get) for a
- * particular user. These filters are typical things like news feed,
- * friend lists, networks. They can be used to filter the stream
- * without complex queries to determine which ids belong in which groups.
- *
- * @param int $uid user to get filters for
- *
- * @return array of stream filter objects
- */
- public function &stream_getFilters($uid = null) {
- $args = array('uid' => $uid);
- return $this->call_method('facebook.stream.getFilters', $args);
- }
-
- /**
- * Gets the full comments given a post_id from stream.get or the
- * stream FQL table. Initially, only a set of preview comments are
- * returned because some posts can have many comments.
- *
- * @param string $post_id id of the post to get comments for
- *
- * @return array of comment objects
- */
- public function &stream_getComments($post_id) {
- $args = array('post_id' => $post_id);
- return $this->call_method('facebook.stream.getComments', $args);
- }
-
- /**
- * Sets the FBML for the profile of the user attached to this session.
- *
- * @param string $markup The FBML that describes the profile
- * presence of this app for the user
- * @param int $uid The user
- * @param string $profile Profile FBML
- * @param string $profile_action Profile action FBML (deprecated)
- * @param string $mobile_profile Mobile profile FBML
- * @param string $profile_main Main Tab profile FBML
- *
- * @return array A list of strings describing any compile errors for the
- * submitted FBML
- */
- function profile_setFBML($markup,
- $uid=null,
- $profile='',
- $profile_action='',
- $mobile_profile='',
- $profile_main='') {
- return $this->call_method('facebook.profile.setFBML',
- array('markup' => $markup,
- 'uid' => $uid,
- 'profile' => $profile,
- 'profile_action' => $profile_action,
- 'mobile_profile' => $mobile_profile,
- 'profile_main' => $profile_main));
- }
-
- /**
- * Gets the FBML for the profile box that is currently set for a user's
- * profile (your application set the FBML previously by calling the
- * profile.setFBML method).
- *
- * @param int $uid (Optional) User id to lookup; defaults to session.
- * @param int $type (Optional) 1 for original style, 2 for profile_main boxes
- *
- * @return string The FBML
- */
- public function &profile_getFBML($uid=null, $type=null) {
- return $this->call_method('facebook.profile.getFBML',
- array('uid' => $uid,
- 'type' => $type));
- }
-
- /**
- * Returns the specified user's application info section for the calling
- * application. These info sections have either been set via a previous
- * profile.setInfo call or by the user editing them directly.
- *
- * @param int $uid (Optional) User id to lookup; defaults to session.
- *
- * @return array Info fields for the current user. See wiki for structure:
- *
- * http://wiki.developers.facebook.com/index.php/Profile.getInfo
- *
- */
- public function &profile_getInfo($uid=null) {
- return $this->call_method('facebook.profile.getInfo',
- array('uid' => $uid));
- }
-
- /**
- * Returns the options associated with the specified info field for an
- * application info section.
- *
- * @param string $field The title of the field
- *
- * @return array An array of info options.
- */
- public function &profile_getInfoOptions($field) {
- return $this->call_method('facebook.profile.getInfoOptions',
- array('field' => $field));
- }
-
- /**
- * Configures an application info section that the specified user can install
- * on the Info tab of her profile. For details on the structure of an info
- * field, please see:
- *
- * http://wiki.developers.facebook.com/index.php/Profile.setInfo
- *
- * @param string $title Title / header of the info section
- * @param int $type 1 for text-only, 5 for thumbnail views
- * @param array $info_fields An array of info fields. See wiki for details.
- * @param int $uid (Optional)
- *
- * @return bool true on success
- */
- public function &profile_setInfo($title, $type, $info_fields, $uid=null) {
- return $this->call_method('facebook.profile.setInfo',
- array('uid' => $uid,
- 'type' => $type,
- 'title' => $title,
- 'info_fields' => json_encode($info_fields)));
- }
-
- /**
- * Specifies the objects for a field for an application info section. These
- * options populate the typeahead for a thumbnail.
- *
- * @param string $field The title of the field
- * @param array $options An array of items for a thumbnail, including
- * 'label', 'link', and optionally 'image',
- * 'description' and 'sublabel'
- *
- * @return bool true on success
- */
- public function profile_setInfoOptions($field, $options) {
- return $this->call_method('facebook.profile.setInfoOptions',
- array('field' => $field,
- 'options' => json_encode($options)));
- }
-
- /**
- * Get all the marketplace categories.
- *
- * @return array A list of category names
- */
- function marketplace_getCategories() {
- return $this->call_method('facebook.marketplace.getCategories',
- array());
- }
-
- /**
- * Get all the marketplace subcategories for a particular category.
- *
- * @param category The category for which we are pulling subcategories
- *
- * @return array A list of subcategory names
- */
- function marketplace_getSubCategories($category) {
- return $this->call_method('facebook.marketplace.getSubCategories',
- array('category' => $category));
- }
-
- /**
- * Get listings by either listing_id or user.
- *
- * @param listing_ids An array of listing_ids (optional)
- * @param uids An array of user ids (optional)
- *
- * @return array The data for matched listings
- */
- function marketplace_getListings($listing_ids, $uids) {
- return $this->call_method('facebook.marketplace.getListings',
- array('listing_ids' => $listing_ids, 'uids' => $uids));
- }
-
- /**
- * Search for Marketplace listings. All arguments are optional, though at
- * least one must be filled out to retrieve results.
- *
- * @param category The category in which to search (optional)
- * @param subcategory The subcategory in which to search (optional)
- * @param query A query string (optional)
- *
- * @return array The data for matched listings
- */
- function marketplace_search($category, $subcategory, $query) {
- return $this->call_method('facebook.marketplace.search',
- array('category' => $category,
- 'subcategory' => $subcategory,
- 'query' => $query));
- }
-
- /**
- * Remove a listing from Marketplace.
- *
- * @param listing_id The id of the listing to be removed
- * @param status 'SUCCESS', 'NOT_SUCCESS', or 'DEFAULT'
- *
- * @return bool True on success
- */
- function marketplace_removeListing($listing_id,
- $status='DEFAULT',
- $uid=null) {
- return $this->call_method('facebook.marketplace.removeListing',
- array('listing_id' => $listing_id,
- 'status' => $status,
- 'uid' => $uid));
- }
-
- /**
- * Create/modify a Marketplace listing for the loggedinuser.
- *
- * @param int listing_id The id of a listing to be modified, 0
- * for a new listing.
- * @param show_on_profile bool Should we show this listing on the
- * user's profile
- * @param listing_attrs array An array of the listing data
- *
- * @return int The listing_id (unchanged if modifying an existing listing).
- */
- function marketplace_createListing($listing_id,
- $show_on_profile,
- $attrs,
- $uid=null) {
- return $this->call_method('facebook.marketplace.createListing',
- array('listing_id' => $listing_id,
- 'show_on_profile' => $show_on_profile,
- 'listing_attrs' => json_encode($attrs),
- 'uid' => $uid));
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Data Store API
-
- /**
- * Set a user preference.
- *
- * @param pref_id preference identifier (0-200)
- * @param value preferece's value
- * @param uid the user id (defaults to current session user)
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_setUserPreference($pref_id, $value, $uid = null) {
- return $this->call_method('facebook.data.setUserPreference',
- array('pref_id' => $pref_id,
- 'value' => $value,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Set a user's all preferences for this application.
- *
- * @param values preferece values in an associative arrays
- * @param replace whether to replace all existing preferences or
- * merge into them.
- * @param uid the user id (defaults to current session user)
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_setUserPreferences($values,
- $replace = false,
- $uid = null) {
- return $this->call_method('facebook.data.setUserPreferences',
- array('values' => json_encode($values),
- 'replace' => $replace,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Get a user preference.
- *
- * @param pref_id preference identifier (0-200)
- * @param uid the user id (defaults to current session user)
- * @return preference's value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_getUserPreference($pref_id, $uid = null) {
- return $this->call_method('facebook.data.getUserPreference',
- array('pref_id' => $pref_id,
- 'uid' => $this->get_uid($uid)));
- }
-
- /**
- * Get a user preference.
- *
- * @param uid the user id (defaults to current session user)
- * @return preference values
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- * API_EC_PERMISSION_OTHER_USER
- */
- public function &data_getUserPreferences($uid = null) {
- return $this->call_method('facebook.data.getUserPreferences',
- array('uid' => $this->get_uid($uid)));
- }
-
- /**
- * Create a new object type.
- *
- * @param name object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_createObjectType($name) {
- return $this->call_method('facebook.data.createObjectType',
- array('name' => $name));
- }
-
- /**
- * Delete an object type.
- *
- * @param obj_type object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_dropObjectType($obj_type) {
- return $this->call_method('facebook.data.dropObjectType',
- array('obj_type' => $obj_type));
- }
-
- /**
- * Rename an object type.
- *
- * @param obj_type object type's name
- * @param new_name new object type's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameObjectType($obj_type, $new_name) {
- return $this->call_method('facebook.data.renameObjectType',
- array('obj_type' => $obj_type,
- 'new_name' => $new_name));
- }
-
- /**
- * Add a new property to an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to add
- * @param prop_type 1: integer; 2: string; 3: text blob
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_defineObjectProperty($obj_type,
- $prop_name,
- $prop_type) {
- return $this->call_method('facebook.data.defineObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name,
- 'prop_type' => $prop_type));
- }
-
- /**
- * Remove a previously defined property from an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to remove
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_undefineObjectProperty($obj_type, $prop_name) {
- return $this->call_method('facebook.data.undefineObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Rename a previously defined property of an object type.
- *
- * @param obj_type object type's name
- * @param prop_name name of the property to rename
- * @param new_name new name to use
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameObjectProperty($obj_type, $prop_name,
- $new_name) {
- return $this->call_method('facebook.data.renameObjectProperty',
- array('obj_type' => $obj_type,
- 'prop_name' => $prop_name,
- 'new_name' => $new_name));
- }
-
- /**
- * Retrieve a list of all object types that have defined for the application.
- *
- * @return a list of object type names
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectTypes() {
- return $this->call_method('facebook.data.getObjectTypes');
- }
-
- /**
- * Get definitions of all properties of an object type.
- *
- * @param obj_type object type's name
- * @return pairs of property name and property types
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectType($obj_type) {
- return $this->call_method('facebook.data.getObjectType',
- array('obj_type' => $obj_type));
- }
-
- /**
- * Create a new object.
- *
- * @param obj_type object type's name
- * @param properties (optional) properties to set initially
- * @return newly created object's id
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_createObject($obj_type, $properties = null) {
- return $this->call_method('facebook.data.createObject',
- array('obj_type' => $obj_type,
- 'properties' => json_encode($properties)));
- }
-
- /**
- * Update an existing object.
- *
- * @param obj_id object's id
- * @param properties new properties
- * @param replace true for replacing existing properties;
- * false for merging
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_updateObject($obj_id, $properties, $replace = false) {
- return $this->call_method('facebook.data.updateObject',
- array('obj_id' => $obj_id,
- 'properties' => json_encode($properties),
- 'replace' => $replace));
- }
-
- /**
- * Delete an existing object.
- *
- * @param obj_id object's id
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_deleteObject($obj_id) {
- return $this->call_method('facebook.data.deleteObject',
- array('obj_id' => $obj_id));
- }
-
- /**
- * Delete a list of objects.
- *
- * @param obj_ids objects to delete
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_deleteObjects($obj_ids) {
- return $this->call_method('facebook.data.deleteObjects',
- array('obj_ids' => json_encode($obj_ids)));
- }
-
- /**
- * Get a single property value of an object.
- *
- * @param obj_id object's id
- * @param prop_name individual property's name
- * @return individual property's value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjectProperty($obj_id, $prop_name) {
- return $this->call_method('facebook.data.getObjectProperty',
- array('obj_id' => $obj_id,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Get properties of an object.
- *
- * @param obj_id object's id
- * @param prop_names (optional) properties to return; null for all.
- * @return specified properties of an object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObject($obj_id, $prop_names = null) {
- return $this->call_method('facebook.data.getObject',
- array('obj_id' => $obj_id,
- 'prop_names' => json_encode($prop_names)));
- }
-
- /**
- * Get properties of a list of objects.
- *
- * @param obj_ids object ids
- * @param prop_names (optional) properties to return; null for all.
- * @return specified properties of an object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getObjects($obj_ids, $prop_names = null) {
- return $this->call_method('facebook.data.getObjects',
- array('obj_ids' => json_encode($obj_ids),
- 'prop_names' => json_encode($prop_names)));
- }
-
- /**
- * Set a single property value of an object.
- *
- * @param obj_id object's id
- * @param prop_name individual property's name
- * @param prop_value new value to set
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setObjectProperty($obj_id, $prop_name,
- $prop_value) {
- return $this->call_method('facebook.data.setObjectProperty',
- array('obj_id' => $obj_id,
- 'prop_name' => $prop_name,
- 'prop_value' => $prop_value));
- }
-
- /**
- * Read hash value by key.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param prop_name (optional) individual property's name
- * @return hash value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getHashValue($obj_type, $key, $prop_name = null) {
- return $this->call_method('facebook.data.getHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Write hash value by key.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param value hash value
- * @param prop_name (optional) individual property's name
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setHashValue($obj_type,
- $key,
- $value,
- $prop_name = null) {
- return $this->call_method('facebook.data.setHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'value' => $value,
- 'prop_name' => $prop_name));
- }
-
- /**
- * Increase a hash value by specified increment atomically.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @param prop_name individual property's name
- * @param increment (optional) default is 1
- * @return incremented hash value
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_incHashValue($obj_type,
- $key,
- $prop_name,
- $increment = 1) {
- return $this->call_method('facebook.data.incHashValue',
- array('obj_type' => $obj_type,
- 'key' => $key,
- 'prop_name' => $prop_name,
- 'increment' => $increment));
- }
-
- /**
- * Remove a hash key and its values.
- *
- * @param obj_type object type's name
- * @param key hash key
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeHashKey($obj_type, $key) {
- return $this->call_method('facebook.data.removeHashKey',
- array('obj_type' => $obj_type,
- 'key' => $key));
- }
-
- /**
- * Remove hash keys and their values.
- *
- * @param obj_type object type's name
- * @param keys hash keys
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeHashKeys($obj_type, $keys) {
- return $this->call_method('facebook.data.removeHashKeys',
- array('obj_type' => $obj_type,
- 'keys' => json_encode($keys)));
- }
-
- /**
- * Define an object association.
- *
- * @param name name of this association
- * @param assoc_type 1: one-way 2: two-way symmetric 3: two-way asymmetric
- * @param assoc_info1 needed info about first object type
- * @param assoc_info2 needed info about second object type
- * @param inverse (optional) name of reverse association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_defineAssociation($name, $assoc_type, $assoc_info1,
- $assoc_info2, $inverse = null) {
- return $this->call_method('facebook.data.defineAssociation',
- array('name' => $name,
- 'assoc_type' => $assoc_type,
- 'assoc_info1' => json_encode($assoc_info1),
- 'assoc_info2' => json_encode($assoc_info2),
- 'inverse' => $inverse));
- }
-
- /**
- * Undefine an object association.
- *
- * @param name name of this association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_undefineAssociation($name) {
- return $this->call_method('facebook.data.undefineAssociation',
- array('name' => $name));
- }
-
- /**
- * Rename an object association or aliases.
- *
- * @param name name of this association
- * @param new_name (optional) new name of this association
- * @param new_alias1 (optional) new alias for object type 1
- * @param new_alias2 (optional) new alias for object type 2
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_ALREADY_EXISTS
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_renameAssociation($name, $new_name, $new_alias1 = null,
- $new_alias2 = null) {
- return $this->call_method('facebook.data.renameAssociation',
- array('name' => $name,
- 'new_name' => $new_name,
- 'new_alias1' => $new_alias1,
- 'new_alias2' => $new_alias2));
- }
-
- /**
- * Get definition of an object association.
- *
- * @param name name of this association
- * @return specified association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociationDefinition($name) {
- return $this->call_method('facebook.data.getAssociationDefinition',
- array('name' => $name));
- }
-
- /**
- * Get definition of all associations.
- *
- * @return all defined associations
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociationDefinitions() {
- return $this->call_method('facebook.data.getAssociationDefinitions',
- array());
- }
-
- /**
- * Create or modify an association between two objects.
- *
- * @param name name of association
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @param data (optional) extra string data to store
- * @param assoc_time (optional) extra time data; default to creation time
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null,
- $assoc_time = null) {
- return $this->call_method('facebook.data.setAssociation',
- array('name' => $name,
- 'obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2,
- 'data' => $data,
- 'assoc_time' => $assoc_time));
- }
-
- /**
- * Create or modify associations between objects.
- *
- * @param assocs associations to set
- * @param name (optional) name of association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_setAssociations($assocs, $name = null) {
- return $this->call_method('facebook.data.setAssociations',
- array('assocs' => json_encode($assocs),
- 'name' => $name));
- }
-
- /**
- * Remove an association between two objects.
- *
- * @param name name of association
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociation($name, $obj_id1, $obj_id2) {
- return $this->call_method('facebook.data.removeAssociation',
- array('name' => $name,
- 'obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2));
- }
-
- /**
- * Remove associations between objects by specifying pairs of object ids.
- *
- * @param assocs associations to remove
- * @param name (optional) name of association
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociations($assocs, $name = null) {
- return $this->call_method('facebook.data.removeAssociations',
- array('assocs' => json_encode($assocs),
- 'name' => $name));
- }
-
- /**
- * Remove associations between objects by specifying one object id.
- *
- * @param name name of association
- * @param obj_id who's association to remove
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_removeAssociatedObjects($name, $obj_id) {
- return $this->call_method('facebook.data.removeAssociatedObjects',
- array('name' => $name,
- 'obj_id' => $obj_id));
- }
-
- /**
- * Retrieve a list of associated objects.
- *
- * @param name name of association
- * @param obj_id who's association to retrieve
- * @param no_data only return object ids
- * @return associated objects
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) {
- return $this->call_method('facebook.data.getAssociatedObjects',
- array('name' => $name,
- 'obj_id' => $obj_id,
- 'no_data' => $no_data));
- }
-
- /**
- * Count associated objects.
- *
- * @param name name of association
- * @param obj_id who's association to retrieve
- * @return associated object's count
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjectCount($name, $obj_id) {
- return $this->call_method('facebook.data.getAssociatedObjectCount',
- array('name' => $name,
- 'obj_id' => $obj_id));
- }
-
- /**
- * Get a list of associated object counts.
- *
- * @param name name of association
- * @param obj_ids whose association to retrieve
- * @return associated object counts
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_DATA_OBJECT_NOT_FOUND
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_INVALID_OPERATION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociatedObjectCounts($name, $obj_ids) {
- return $this->call_method('facebook.data.getAssociatedObjectCounts',
- array('name' => $name,
- 'obj_ids' => json_encode($obj_ids)));
- }
-
- /**
- * Find all associations between two objects.
- *
- * @param obj_id1 id of first object
- * @param obj_id2 id of second object
- * @param no_data only return association names without data
- * @return all associations between objects
- * @error
- * API_EC_DATA_DATABASE_ERROR
- * API_EC_PARAM
- * API_EC_PERMISSION
- * API_EC_DATA_QUOTA_EXCEEDED
- * API_EC_DATA_UNKNOWN_ERROR
- */
- public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) {
- return $this->call_method('facebook.data.getAssociations',
- array('obj_id1' => $obj_id1,
- 'obj_id2' => $obj_id2,
- 'no_data' => $no_data));
- }
-
- /**
- * Get the properties that you have set for an app.
- *
- * @param properties List of properties names to fetch
- *
- * @return array A map from property name to value
- */
- public function admin_getAppProperties($properties) {
- return json_decode(
- $this->call_method('facebook.admin.getAppProperties',
- array('properties' => json_encode($properties))), true);
- }
-
- /**
- * Set properties for an app.
- *
- * @param properties A map from property names to values
- *
- * @return bool true on success
- */
- public function admin_setAppProperties($properties) {
- return $this->call_method('facebook.admin.setAppProperties',
- array('properties' => json_encode($properties)));
- }
-
- /**
- * Returns the allocation limit value for a specified integration point name
- * Integration point names are defined in lib/api/karma/constants.php in the
- * limit_map.
- *
- * @param string $integration_point_name Name of an integration point
- * (see developer wiki for list).
- * @param int $uid Specific user to check the limit.
- *
- * @return int Integration point allocation value
- */
- public function &admin_getAllocation($integration_point_name, $uid=null) {
- return $this->call_method('facebook.admin.getAllocation',
- array('integration_point_name' => $integration_point_name,
- 'uid' => $uid));
- }
-
- /**
- * Returns values for the specified metrics for the current application, in
- * the given time range. The metrics are collected for fixed-length periods,
- * and the times represent midnight at the end of each period.
- *
- * @param start_time unix time for the start of the range
- * @param end_time unix time for the end of the range
- * @param period number of seconds in the desired period
- * @param metrics list of metrics to look up
- *
- * @return array A map of the names and values for those metrics
- */
- public function &admin_getMetrics($start_time, $end_time, $period, $metrics) {
- return $this->call_method('admin.getMetrics',
- array('start_time' => $start_time,
- 'end_time' => $end_time,
- 'period' => $period,
- 'metrics' => json_encode($metrics)));
- }
-
- /**
- * Sets application restriction info.
- *
- * Applications can restrict themselves to only a limited user demographic
- * based on users' age and/or location or based on static predefined types
- * specified by facebook for specifying diff age restriction for diff
- * locations.
- *
- * @param array $restriction_info The age restriction settings to set.
- *
- * @return bool true on success
- */
- public function admin_setRestrictionInfo($restriction_info = null) {
- $restriction_str = null;
- if (!empty($restriction_info)) {
- $restriction_str = json_encode($restriction_info);
- }
- return $this->call_method('admin.setRestrictionInfo',
- array('restriction_str' => $restriction_str));
- }
-
- /**
- * Gets application restriction info.
- *
- * Applications can restrict themselves to only a limited user demographic
- * based on users' age and/or location or based on static predefined types
- * specified by facebook for specifying diff age restriction for diff
- * locations.
- *
- * @return array The age restriction settings for this application.
- */
- public function admin_getRestrictionInfo() {
- return json_decode(
- $this->call_method('admin.getRestrictionInfo'),
- true);
- }
-
-
- /**
- * Bans a list of users from the app. Banned users can't
- * access the app's canvas page and forums.
- *
- * @param array $uids an array of user ids
- * @return bool true on success
- */
- public function admin_banUsers($uids) {
- return $this->call_method(
- 'admin.banUsers', array('uids' => json_encode($uids)));
- }
-
- /**
- * Unban users that have been previously banned with
- * admin_banUsers().
- *
- * @param array $uids an array of user ids
- * @return bool true on success
- */
- public function admin_unbanUsers($uids) {
- return $this->call_method(
- 'admin.unbanUsers', array('uids' => json_encode($uids)));
- }
-
- /**
- * Gets the list of users that have been banned from the application.
- * $uids is an optional parameter that filters the result with the list
- * of provided user ids. If $uids is provided,
- * only banned user ids that are contained in $uids are returned.
- *
- * @param array $uids an array of user ids to filter by
- * @return bool true on success
- */
-
- public function admin_getBannedUsers($uids = null) {
- return $this->call_method(
- 'admin.getBannedUsers',
- array('uids' => $uids ? json_encode($uids) : null));
- }
-
-
- /* UTILITY FUNCTIONS */
-
- /**
- * Calls the specified normal POST method with the specified parameters.
- *
- * @param string $method Name of the Facebook method to invoke
- * @param array $params A map of param names => param values
- *
- * @return mixed Result of method call; this returns a reference to support
- * 'delayed returns' when in a batch context.
- * See: http://wiki.developers.facebook.com/index.php/Using_batching_API
- */
- public function &call_method($method, $params = array()) {
- if ($this->format) {
- $params['format'] = $this->format;
- }
- if (!$this->pending_batch()) {
- if ($this->call_as_apikey) {
- $params['call_as_apikey'] = $this->call_as_apikey;
- }
- $data = $this->post_request($method, $params);
- $result = $this->convert_result($data, $method, $params);
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
- }
- else {
- $result = null;
- $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result);
- $this->batch_queue[] = $batch_item;
- }
-
- return $result;
- }
-
- protected function convert_result($data, $method, $params) {
- $is_xml = (empty($params['format']) ||
- strtolower($params['format']) != 'json');
- return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params)
- : json_decode($data, true);
- }
-
- /**
- * Change the response format
- *
- * @param string $format The response format (json, xml)
- */
- public function setFormat($format) {
- $this->format = $format;
- return $this;
- }
-
- /**
- * get the current response serialization format
- *
- * @return string 'xml', 'json', or null (which means 'xml')
- */
- public function getFormat() {
- return $this->format;
- }
-
- /**
- * Calls the specified file-upload POST method with the specified parameters
- *
- * @param string $method Name of the Facebook method to invoke
- * @param array $params A map of param names => param values
- * @param string $file A path to the file to upload (required)
- *
- * @return array A dictionary representing the response.
- */
- public function call_upload_method($method, $params, $file, $server_addr = null) {
- if (!$this->pending_batch()) {
- if (!file_exists($file)) {
- $code =
- FacebookAPIErrorCodes::API_EC_PARAM;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- if ($this->format) {
- $params['format'] = $this->format;
- }
- $data = $this->post_upload_request($method,
- $params,
- $file,
- $server_addr);
- $result = $this->convert_result($data, $method, $params);
-
- if (is_array($result) && isset($result['error_code'])) {
- throw new FacebookRestClientException($result['error_msg'],
- $result['error_code']);
- }
- }
- else {
- $code =
- FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
- $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
- throw new FacebookRestClientException($description, $code);
- }
-
- return $result;
- }
-
- protected function convert_xml_to_result($xml, $method, $params) {
- $sxml = simplexml_load_string($xml);
- $result = self::convert_simplexml_to_array($sxml);
-
- if (!empty($GLOBALS['facebook_config']['debug'])) {
- // output the raw xml and its corresponding php object, for debugging:
- print '