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 = "
  • "+ + "
    "+ + ""+ + ""+ + "\""+user['screen_name']+"\"/"+ + ""+user['screen_name']+""+ + ""+ + ""+ + "

    "+html+"

    "+ + "
    "+ + "
    "+ + "
    "+ + "
    Published
    "+ + "
    "+ + ""+ + "a few seconds ago"+ + " "+ + "
    "+ + "
    "+ + "
    "+ + "
    From
    "+ + "
    "+source+"
    "+ // may have a link, I think + "
    "; + + if (data['in_reply_to_status_id']) { + ni = ni+"
    "+ + "
    To
    "+ + "
    "+ + "in reply to"+ + "
    "+ + "
    "; + } + + ni = ni+"
    "+ + "
    "; + + if (_userid != 0) { + var input = $("form#form_notice fieldset input#token"); + var session_key = input.val(); + ni = ni+makeFavoriteForm(data['id'], session_key); + ni = ni+makeReplyLink(data['id'], data['user']['screen_name']); + if (_userid == data['user']['id']) { + ni = ni+makeDeleteLink(data['id']); + } + } + + ni = ni+"
    "+ + "
  • "; + return ni; + } + + function makeFavoriteForm(id, session_key) + { + var ff; + + ff = "
    "+ + "
    "+ + "Favor this notice"+ // XXX: i18n + ""+ + ""+ + ""+ + "
    "+ + "
    "; + return ff; + } + + function makeReplyLink(id, nickname) + { + var rl; + rl = "
    "+ + "
    Reply to this notice
    "+ + "
    "+ + "Reply "+id+""+ + ""+ + "
    "+ + "
    "; + return rl; + } + + function makeDeleteLink(id) + { + var dl, delurl; + delurl = _deleteurl.replace("0000000000", id); + + dl = "
    "+ + "
    Delete this notice
    "+ + "
    "+ + "Delete"+ + "
    "+ + "
    "; + + 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 = "
  • "+ - "
    "+ - ""+ - ""+ - "\""+user['screen_name']+"\"/"+ - ""+user['screen_name']+""+ - ""+ - ""+ - "

    "+html+"

    "+ - "
    "+ - "
    "+ - "
    "+ - "
    Published
    "+ - "
    "+ - ""+ - "a few seconds ago"+ - " "+ - "
    "+ - "
    "+ - "
    "+ - "
    From
    "+ - "
    "+source+"
    "+ // may have a link, I think - "
    "; - - if (data['in_reply_to_status_id']) { - ni = ni+"
    "+ - "
    To
    "+ - "
    "+ - "in reply to"+ - "
    "+ - "
    "; - } - - ni = ni+"
    "+ - "
    "; - - if (_userid != 0) { - var input = $("form#form_notice fieldset input#token"); - var session_key = input.val(); - ni = ni+makeFavoriteForm(data['id'], session_key); - ni = ni+makeReplyLink(data['id'], data['user']['screen_name']); - if (_userid == data['user']['id']) { - ni = ni+makeDeleteLink(data['id']); - } - } - - ni = ni+"
    "+ - "
  • "; - return ni; - } - - function makeFavoriteForm(id, session_key) - { - var ff; - - ff = "
    "+ - "
    "+ - "Favor this notice"+ // XXX: i18n - ""+ - ""+ - ""+ - "
    "+ - "
    "; - return ff; - } - - function makeReplyLink(id, nickname) - { - var rl; - rl = "
    "+ - "
    Reply to this notice
    "+ - "
    "+ - "Reply "+id+""+ - ""+ - "
    "+ - "
    "; - return rl; - } - - function makeDeleteLink(id) - { - var dl, delurl; - delurl = _deleteurl.replace("0000000000", id); - - dl = "
    "+ - "
    Delete this notice
    "+ - "
    "+ - "Delete"+ - "
    "+ - "
    "; - - return dl; - } -}(); - -- cgit v1.2.3-54-g00ecf From 27ff66c9de64a60cb3219f6d823cb290d4f18c01 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 4 Oct 2009 04:05:40 -0400 Subject: Some changes required from Orbited debugging --- plugins/Orbited/OrbitedPlugin.php | 18 ++++++++++++------ plugins/Orbited/orbitedextra.js | 2 ++ plugins/Orbited/orbitedupdater.js | 11 +++++++---- 3 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 plugins/Orbited/orbitedextra.js (limited to 'plugins') diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php index ad7d1d276..ba87b266a 100644 --- a/plugins/Orbited/OrbitedPlugin.php +++ b/plugins/Orbited/OrbitedPlugin.php @@ -60,6 +60,12 @@ class OrbitedPlugin extends RealtimePlugin protected $con = null; + function onStartShowHeadElements($action) + { + // See http://orbited.org/wiki/Deployment#Cross-SubdomainDeployment + $action->element('script', null, ' document.domain = document.domain; '); + } + function _getScripts() { $scripts = parent::_getScripts(); @@ -71,6 +77,7 @@ class OrbitedPlugin extends RealtimePlugin $root = 'http://'.$server.(($port == 80) ? '':':'.$port); $scripts[] = $root.'/static/Orbited.js'; + $scripts[] = common_path('plugins/Orbited/orbitedextra.js'); $scripts[] = $root.'/static/protocols/stomp/stomp.js'; $scripts[] = common_path('plugins/Orbited/orbitedupdater.js'); @@ -90,16 +97,16 @@ class OrbitedPlugin extends RealtimePlugin function _connect() { - require_once(INSTALLDIR.'/extlibs/Stomp.php'); + require_once(INSTALLDIR.'/extlib/Stomp.php'); $url = $this->_getStompUrl(); $this->con = new Stomp($url); if ($this->con->connect($this->username, $this->password)) { - $this->_log(LOG_INFO, "Connected."); + $this->log(LOG_INFO, "Connected."); } else { - $this->_log(LOG_ERR, 'Failed to connect to queue server'); + $this->log(LOG_ERR, 'Failed to connect to queue server'); throw new ServerException('Failed to connect to queue server'); } } @@ -128,15 +135,14 @@ class OrbitedPlugin extends RealtimePlugin function _getStompServer() { - $server = (!is_null($this->stompserver)) ? $this->stompserver : + return (!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; + return (!is_null($this->stompport)) ? $this->stompport : 61613; } function _getStompUrl() diff --git a/plugins/Orbited/orbitedextra.js b/plugins/Orbited/orbitedextra.js new file mode 100644 index 000000000..47e5c0c80 --- /dev/null +++ b/plugins/Orbited/orbitedextra.js @@ -0,0 +1,2 @@ +TCPSocket = Orbited.TCPSocket; + diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js index d70f4a4fd..8c5ab3b73 100644 --- a/plugins/Orbited/orbitedupdater.js +++ b/plugins/Orbited/orbitedupdater.js @@ -9,13 +9,16 @@ var OrbitedUpdater = function() // 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)); }; - }; + + stomp.onconnectedframe = function() { + stomp.subscribe(timeline); + } + + stomp.connect(server, port, username, password); + } } }(); -- cgit v1.2.3-54-g00ecf From 091e7b908befb7d24404ea653f59b38063e04f69 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 4 Oct 2009 04:10:15 -0400 Subject: need to show scripts at end of body in RealtimePlugin --- plugins/Realtime/RealtimePlugin.php | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 0f0d0f9f4..181927968 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -230,6 +230,7 @@ class RealtimePlugin extends Plugin } $action->showContentBlock(); + $action->showScripts(); $action->elementEnd('body'); return false; // No default processing } -- cgit v1.2.3-54-g00ecf From affe00276a008038e8a8b37b3812215e382fe98e Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 4 Oct 2009 04:11:10 -0400 Subject: remove spurious readme from Orbited --- plugins/Orbited/README | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 plugins/Orbited/README (limited to 'plugins') diff --git a/plugins/Orbited/README b/plugins/Orbited/README deleted file mode 100644 index 4abd40af7..000000000 --- a/plugins/Orbited/README +++ /dev/null @@ -1,26 +0,0 @@ -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. -- cgit v1.2.3-54-g00ecf From 960207c81eb9f420f916c6889f2f4b4c09a5a480 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 13:27:46 +0000 Subject: Don't redirect if mobile server is same as site server --- plugins/MobileProfile/MobileProfilePlugin.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 932550189..d854b6e5e 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -131,7 +131,9 @@ class MobileProfilePlugin extends WAP20Plugin // If they are okay with MP, and the site has a mobile server, // redirect there if ($this->serveMobile && - common_config('site', 'mobileserver') !== false) { + common_config('site', 'mobileserver') !== false && + common_config('site', 'mobileserver') != + common_config('site', 'server')) { header("Location: ".common_config('site', 'mobileserver')); exit(); -- cgit v1.2.3-54-g00ecf From c64e1792bf108961c288ffa91667be34d9e6ca6a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 13:58:29 +0000 Subject: Fixed redirect. Added common_path for mobileserver --- plugins/MobileProfile/MobileProfilePlugin.php | 28 ++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index d854b6e5e..02a732493 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -132,10 +132,11 @@ class MobileProfilePlugin extends WAP20Plugin // redirect there if ($this->serveMobile && common_config('site', 'mobileserver') !== false && - common_config('site', 'mobileserver') != - common_config('site', 'server')) { + (common_config('site', 'mobileserver') != + common_config('site', 'server'))) { - header("Location: ".common_config('site', 'mobileserver')); + // FIXME: Redirect to equivalent page on mobile site instead + header("Location: ".$this->_common_path('')); exit(); } } @@ -171,6 +172,27 @@ class MobileProfilePlugin extends WAP20Plugin } + + function _common_path($relative, $ssl=false) + { + $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : ''; + + if (($ssl && (common_config('site', 'ssl') === 'sometimes')) + || common_config('site', 'ssl') === 'always') { + $proto = 'https'; + if (is_string(common_config('site', 'sslserver')) && + mb_strlen(common_config('site', 'sslserver')) > 0) { + $serverpart = common_config('site', 'sslserver'); + } else { + $serverpart = common_config('site', 'mobileserver'); + } + } else { + $proto = 'http'; + $serverpart = common_config('site', 'mobileserver'); + } + + return $proto.'://'.$serverpart.'/'.$pathpart.$relative; + } } -- cgit v1.2.3-54-g00ecf From 7addccacaab5bf7510fbe537b512bc8d5c3f558f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 16:26:04 +0000 Subject: Added ability to define mobile stylesheets (handheld, screen) at the theme level. If there are no mobile stylesheets in the theme directory, it will use the ones that come with the plugin. --- plugins/MobileProfile/MobileProfilePlugin.php | 24 ++++++++++++++++++++++++ plugins/MobileProfile/mp-handheld.css | 0 plugins/MobileProfile/mp-screen.css | 1 + 3 files changed, 25 insertions(+) create mode 100644 plugins/MobileProfile/mp-handheld.css create mode 100644 plugins/MobileProfile/mp-screen.css (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 02a732493..35756cfe7 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -158,6 +158,30 @@ class MobileProfilePlugin extends WAP20Plugin } + function onStartShowStatusNetStyles($action) { + if (file_exists(theme_file('css/mp-screen.css'))) { + $action->cssLink('css/mp-screen.css', null, 'screen'); + } + else { + $action->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => common_path('plugins/MobileProfile/mp-screen.css') . '?version=' . STATUSNET_VERSION, + 'media' => 'screen')); + } + + if (file_exists(theme_file('css/mp-handheld.css'))) { + $action->cssLink('css/mp-handheld.css', null, 'handheld'); + } + else { + $action->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => common_path('plugins/MobileProfile/mp-handheld.css') . '?version=' . STATUSNET_VERSION, + 'media' => 'handheld')); + } + + return false; + } + function onStartShowAside($action) { diff --git a/plugins/MobileProfile/mp-handheld.css b/plugins/MobileProfile/mp-handheld.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css new file mode 100644 index 000000000..31986eef0 --- /dev/null +++ b/plugins/MobileProfile/mp-screen.css @@ -0,0 +1 @@ +@import url(../../theme/base/css/display.css); -- cgit v1.2.3-54-g00ecf From de2c4e36bc5e36714db32b4987f1fde1903dc059 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 16:33:52 +0000 Subject: Mobile Profile plugin will use the identica theme for now --- plugins/MobileProfile/mp-screen.css | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 31986eef0..5aec3c372 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -1 +1,2 @@ @import url(../../theme/base/css/display.css); +@import url(../../theme/identica/css/display.css); -- cgit v1.2.3-54-g00ecf From a7bed6f7d90098a16ec6da743ac34f0983e6afe3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 16:50:53 +0000 Subject: Added custom showHead --- plugins/MobileProfile/MobileProfilePlugin.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 35756cfe7..e7db09f15 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -158,6 +158,20 @@ class MobileProfilePlugin extends WAP20Plugin } + function onStartShowHeadElements($action) { + if (!$action->serveMobile) { + return true; + } + + $action->showTitle(); + $action->showShortcutIcon(); + $action->showStylesheets(); + $action->showFeeds(); + $action->showDescription(); + $action->extraHead(); + } + + function onStartShowStatusNetStyles($action) { if (file_exists(theme_file('css/mp-screen.css'))) { $action->cssLink('css/mp-screen.css', null, 'screen'); -- cgit v1.2.3-54-g00ecf From ef7e4cb72d12b714215317635e1de4badf340529 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 20:42:03 +0000 Subject: If not meant to serveMobile, show the default styles --- plugins/MobileProfile/MobileProfilePlugin.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index e7db09f15..117e0aae6 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -173,6 +173,10 @@ class MobileProfilePlugin extends WAP20Plugin function onStartShowStatusNetStyles($action) { + if (!$action->serveMobile) { + return true; + } + if (file_exists(theme_file('css/mp-screen.css'))) { $action->cssLink('css/mp-screen.css', null, 'screen'); } -- cgit v1.2.3-54-g00ecf From 3ee1af9aaa3a7e69a97f36498e6fe9617f708581 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 20:43:52 +0000 Subject: Minor adjustment to indenting --- 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 117e0aae6..3f3d10261 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -158,7 +158,8 @@ class MobileProfilePlugin extends WAP20Plugin } - function onStartShowHeadElements($action) { + function onStartShowHeadElements($action) + { if (!$action->serveMobile) { return true; } @@ -172,7 +173,8 @@ class MobileProfilePlugin extends WAP20Plugin } - function onStartShowStatusNetStyles($action) { + function onStartShowStatusNetStyles($action) + { if (!$action->serveMobile) { return true; } -- cgit v1.2.3-54-g00ecf From fe4a49d4a5cc728d6ee0484ceecba796af29fc42 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 20:53:30 +0000 Subject: If they are not on the mobile site or not interested in getting the mobile profile, then give them the regular output. --- plugins/MobileProfile/MobileProfilePlugin.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 3f3d10261..28a2eb0c1 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -141,6 +141,10 @@ class MobileProfilePlugin extends WAP20Plugin } } + if (!$this->serveMobile) { + return true; + } + header('Content-Type: '.$type); $action->extraHeaders(); -- cgit v1.2.3-54-g00ecf From 5087a24c487891f4c5bc9fe2ff65be12fa74b930 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 21:09:16 +0000 Subject: Init showHeader --- plugins/MobileProfile/MobileProfilePlugin.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 28a2eb0c1..90ec8129d 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -207,6 +207,25 @@ class MobileProfilePlugin extends WAP20Plugin } + function onStartShowHeader($action) + { + if (!$this->serveMobile) { + return true; + } + + $action->elementStart('div', array('id' => 'header')); + $action->showLogo(); + $action->showPrimaryNav(); + $action->showSiteNotice(); + if (common_logged_in()) { + $action->showNoticeForm(); + } else { + $action->showAnonymousMessage(); + } + $action->elementEnd('div'); + } + + function onStartShowAside($action) { if ($this->serveMobile) { -- cgit v1.2.3-54-g00ecf From d001c5f4b1ebfd2a28baba3138eae4902598618c Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 21:37:05 +0000 Subject: Init showLogo --- plugins/MobileProfile/MobileProfilePlugin.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 90ec8129d..4e9bfd6c8 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -214,7 +214,7 @@ class MobileProfilePlugin extends WAP20Plugin } $action->elementStart('div', array('id' => 'header')); - $action->showLogo(); + $this->_showLogo($action); $action->showPrimaryNav(); $action->showSiteNotice(); if (common_logged_in()) { @@ -226,6 +226,26 @@ class MobileProfilePlugin extends WAP20Plugin } + function _showLogo($action) + { + $action->elementStart('address', 'vcard'); + $action->elementStart('a', array('class' => 'url home bookmark', + 'href' => common_local_url('public'))); + if (common_config('site', 'mobilelogo') || + file_exists(theme_file('logo.png')) || + file_exists(theme_file('mobilelogo.gif'))) { + + $action->element('img', array('class' => 'photo', + 'src' => (common_config('site', 'mobilelogo')) ? common_config('site', 'mobilelogo') : + ((file_exists(theme_file('mobilelogo.gif'))) ? (theme_path('mobilelogo.gif')): theme_path('logo.png')), + 'alt' => common_config('site', 'name'))); + } + $action->element('span', array('class' => 'fn org'), common_config('site', 'name')); + $action->elementEnd('a'); + $action->elementEnd('address'); + } + + function onStartShowAside($action) { if ($this->serveMobile) { -- cgit v1.2.3-54-g00ecf From 3c9f177c03f631cdbc8f0a440fd2dc84d6252419 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 21:37:24 +0000 Subject: Stop output after showing header --- plugins/MobileProfile/MobileProfilePlugin.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 4e9bfd6c8..c28d61cbe 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -223,6 +223,8 @@ class MobileProfilePlugin extends WAP20Plugin $action->showAnonymousMessage(); } $action->elementEnd('div'); + + return false; } -- cgit v1.2.3-54-g00ecf From 479a5e74a3cdc70c1e69c96d04c505c8efa00eb9 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 21:42:38 +0000 Subject: Removed site_notice and anon_notice from output because this information is not particularly crucial for the mobile user. --- plugins/MobileProfile/MobileProfilePlugin.php | 3 --- 1 file changed, 3 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index c28d61cbe..37aeeee87 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -216,11 +216,8 @@ class MobileProfilePlugin extends WAP20Plugin $action->elementStart('div', array('id' => 'header')); $this->_showLogo($action); $action->showPrimaryNav(); - $action->showSiteNotice(); if (common_logged_in()) { $action->showNoticeForm(); - } else { - $action->showAnonymousMessage(); } $action->elementEnd('div'); -- cgit v1.2.3-54-g00ecf From acda8d4c7914b0bcea974c34d72c8b93ba731c17 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 4 Oct 2009 22:16:59 +0000 Subject: Init showPrimaryNav --- plugins/MobileProfile/MobileProfilePlugin.php | 48 ++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 37aeeee87..7a36522f1 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -215,7 +215,7 @@ class MobileProfilePlugin extends WAP20Plugin $action->elementStart('div', array('id' => 'header')); $this->_showLogo($action); - $action->showPrimaryNav(); + $this->_showPrimaryNav($action); if (common_logged_in()) { $action->showNoticeForm(); } @@ -245,6 +245,52 @@ class MobileProfilePlugin extends WAP20Plugin } + function _showPrimaryNav($action) { + $user = common_current_user(); + $connect = ''; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } else if (common_config('twitter', 'enabled')) { + $connect = 'twittersettings'; + } + + $action->elementStart('ul', array('id' => 'site_nav_global_primary')); + if ($user) { + $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), + _('Home')); + $action->menuItem(common_local_url('profilesettings'), + _('Account')); + if ($connect) { + $action->menuItem(common_local_url($connect), + _('Connect')); + } + if (common_config('invite', 'enabled')) { + $action->menuItem(common_local_url('invite'), + _('Invite')); + } + $action->menuItem(common_local_url('logout'), + _('Logout')); + } + else { + if (!common_config('site', 'closed')) { + $action->menuItem(common_local_url('register'), + _('Register')); + } + $action->menuItem(common_local_url('login'), + _('Login')); + } + $action->menuItem(common_local_url('doc', array('title' => 'help')), + _('Help')); + if ($user || !common_config('site', 'private')) { + $action->menuItem(common_local_url('peoplesearch'), + _('Search')); + } + $action->elementEnd('ul'); + } + + function onStartShowAside($action) { if ($this->serveMobile) { -- cgit v1.2.3-54-g00ecf From 5c01501f5534fed7d0ec32ac4a33396bd99f508d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 5 Oct 2009 10:29:34 +0000 Subject: Minor correction --- 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 7a36522f1..79b080b03 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -179,7 +179,7 @@ class MobileProfilePlugin extends WAP20Plugin function onStartShowStatusNetStyles($action) { - if (!$action->serveMobile) { + if (!$this->serveMobile) { return true; } -- cgit v1.2.3-54-g00ecf From 676f681d22bcb1d4b3b3b4216622d2cbf7f637c3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 5 Oct 2009 11:04:32 +0000 Subject: Init mobile profile screen stylesheet. It reuses base and identica themes. The handheld stylesheet could import this stylesheet as well. --- plugins/MobileProfile/mp-screen.css | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 5aec3c372..9ed3a1900 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -1,2 +1,63 @@ @import url(../../theme/base/css/display.css); @import url(../../theme/identica/css/display.css); + +#wrap { +min-width:0; +max-width:100%; +} + +#header { +margin:2%; +} + +address { +margin:0 1% 0; +} +address .vcard .photo { +margin-right:0; +} + +#site_nav_global_primary { +margin-right:0; +width:35%; +list-style-type:none; +} +#site_nav_global_primary li { +margin-left:3%; +} + +/*Remove this from HTML*/ +#form_notice label[for=notice_data-text] { +display:none; +} + +#form_notice { +width:100%; +} + +#site_nav_local_views li { +margin-left:1px; +margin-right:0; +} +#site_nav_local_views li:first-child { +margin-left:0; +} +#site_nav_local_views a { +padding:0 3%; +display:block; +} +#site_nav_local_views .current a { +text-shadow:none; +} +#site_nav_local_views li { +-moz-box-shadow:none; +-webkit-box-shadow:none; +box-shadow:none; +} + + +#content { +width:96.41%; +} + + -- cgit v1.2.3-54-g00ecf From 7b4d138946d76200ce1c7c20e85e67538be5bf10 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 5 Oct 2009 14:42:11 +0000 Subject: Init showNoticeFormData --- plugins/MobileProfile/MobileProfilePlugin.php | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 79b080b03..21eaa124e 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -291,6 +291,45 @@ class MobileProfilePlugin extends WAP20Plugin } + function onStartShowNoticeFormData($form) + { + if (!$this->serveMobile) { + return true; + } + + $form->out->element('textarea', array('id' => 'notice_data-text', + 'cols' => 35, + 'rows' => 4, + 'name' => 'status_textarea'), + ($form->content) ? $form->content : ''); + + $contentLimit = Notice::maxContent(); + + $form->out->element('script', array('type' => 'text/javascript'), + 'maxLength = ' . $contentLimit . ';'); + + if ($contentLimit > 0) { + $form->out->element('div', array('id' => 'notice_text-count'), + $contentLimit); + } + + if (common_config('attachments', 'uploads')) { + $form->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); + $form->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); + $form->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + } + if ($form->action) { + $form->out->hidden('notice_return-to', $form->action, 'returnto'); + } + $form->out->hidden('notice_in-reply-to', $form->inreplyto, 'inreplyto'); + + return false; + } + + function onStartShowAside($action) { if ($this->serveMobile) { -- cgit v1.2.3-54-g00ecf From 9898a38b34d9ae1131beb6278e3bc353c4123ccd Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 5 Oct 2009 15:46:32 +0000 Subject: No longer need to style --- plugins/MobileProfile/mp-screen.css | 4 ---- 1 file changed, 4 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 9ed3a1900..09c8b4127 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -26,10 +26,6 @@ list-style-type:none; margin-left:3%; } -/*Remove this from HTML*/ -#form_notice label[for=notice_data-text] { -display:none; -} #form_notice { width:100%; -- cgit v1.2.3-54-g00ecf From e224da0bf655678322b388164a4c4f9d249eaecb Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 5 Oct 2009 16:59:41 +0000 Subject: Init styles for form_notice --- plugins/MobileProfile/mp-screen.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 09c8b4127..641921f14 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -31,6 +31,22 @@ margin-left:3%; width:100%; } +#form_notice textarea { +width:75%; +} + +#notice_text-count { +position:absolute; +bottom:2px; +right:32.5%; +z-index:9; +} + +#notice_action-submit { +width:17%; +} + + #site_nav_local_views li { margin-left:1px; margin-right:0; -- cgit v1.2.3-54-g00ecf From f7d86f1cc62248c0de742bd44af5bf30e70add9d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 6 Oct 2009 16:04:36 +0000 Subject: Returning false seems to fix IE from reclaiming window focus. I think 'WTF' best describes this behaviour. Further investigation required. --- plugins/Realtime/realtimeupdate.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 11e466325..a75f17d8c 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -25,6 +25,8 @@ RealtimeUpdate = { 'border-top-color':'#AAAAAA', 'border-top-style':'solid' }); + + return false; }); }, -- cgit v1.2.3-54-g00ecf From 921b25bf6281eb9d9d500eb8f5d98b8b473794cb Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 11:31:52 +0000 Subject: Some UI adjustments for form notice --- plugins/MobileProfile/mp-screen.css | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 641921f14..a8135e0b4 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -7,11 +7,11 @@ max-width:100%; } #header { -margin:2%; +margin:2% 4% 4% 2%; } address { -margin:0 1% 0; +margin:0 2% 0; } address .vcard .photo { margin-right:0; @@ -32,18 +32,27 @@ width:100%; } #form_notice textarea { -width:75%; +width:60%; +height:20px; } #notice_text-count { position:absolute; bottom:2px; -right:32.5%; +right:40%; z-index:9; } -#notice_action-submit { -width:17%; +#form_notice label[for="notice_data-attach"], +#form_notice #notice_data-attach { +top:0; +right:28%; +} + +#form_notice #notice_action-submit { +width:20%; +right:4%; +text-align:center; } -- cgit v1.2.3-54-g00ecf From e36c9523383769313b11a67aa3859cb09b20ec52 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 12:28:38 +0000 Subject: Fixed notice-options --- plugins/MobileProfile/mp-screen.css | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index a8135e0b4..1fff2273d 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -82,3 +82,8 @@ width:96.41%; } +.notice-options { +width:90px; +margin-right:2%; +} + -- cgit v1.2.3-54-g00ecf From 0799f48197c67dbbbd1b613f1fc9c66d9250f2ae Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 14:48:14 +0000 Subject: Updated mobilelogo extension --- 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 21eaa124e..1164974b9 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -236,7 +236,7 @@ class MobileProfilePlugin extends WAP20Plugin $action->element('img', array('class' => 'photo', 'src' => (common_config('site', 'mobilelogo')) ? common_config('site', 'mobilelogo') : - ((file_exists(theme_file('mobilelogo.gif'))) ? (theme_path('mobilelogo.gif')): theme_path('logo.png')), + ((file_exists(theme_file('mobilelogo.png'))) ? (theme_path('mobilelogo.png')) : theme_path('logo.png')), 'alt' => common_config('site', 'name'))); } $action->element('span', array('class' => 'fn org'), common_config('site', 'name')); -- cgit v1.2.3-54-g00ecf From 0b741f15c2519103240fcdc9e74651a5c584724f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 15:32:56 +0000 Subject: Removed the Help item from global primary navigation in the header since it is also used in the footer area. Frees up 'precious'space. --- plugins/MobileProfile/MobileProfilePlugin.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 1164974b9..ecaea29c0 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -281,8 +281,6 @@ class MobileProfilePlugin extends WAP20Plugin $action->menuItem(common_local_url('login'), _('Login')); } - $action->menuItem(common_local_url('doc', array('title' => 'help')), - _('Help')); if ($user || !common_config('site', 'private')) { $action->menuItem(common_local_url('peoplesearch'), _('Search')); -- cgit v1.2.3-54-g00ecf From 76d7fa475e3bdfdf0f70da55d3a51d1123aa3e14 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 15:34:57 +0000 Subject: Updated header UI --- plugins/MobileProfile/mp-screen.css | 40 +++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 1fff2273d..afc07422b 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -7,23 +7,42 @@ max-width:100%; } #header { -margin:2% 4% 4% 2%; +margin:0; +padding:2%; +width:96%; } address { -margin:0 2% 0; +margin:4% 0 0 0; } address .vcard .photo { margin-right:0; } +address img + .fn { +display:block; +margin-top:6%; +width:100%; +} + +.vcard .photo { +margin-right:7px; +} + + #site_nav_global_primary { -margin-right:0; -width:35%; +margin:0; +width:100%; list-style-type:none; +position:absolute; +top:0; +left:0; } #site_nav_global_primary li { -margin-left:3%; +margin-left:0; +margin-right:1%; +float:left; +font-size:0.9em; } @@ -43,10 +62,15 @@ right:40%; z-index:9; } +/*input type=file no good in +iPhone/iPod Touch, Android, Opera Mini Simulator +*/ +#form_notice #notice_text-count + label, #form_notice label[for="notice_data-attach"], #form_notice #notice_data-attach { top:0; -right:28%; +/*right:28%; Looks good but no good in iDevice, Android*/ +right:40%; } #form_notice #notice_action-submit { @@ -57,14 +81,14 @@ text-align:center; #site_nav_local_views li { -margin-left:1px; +margin-left:0; margin-right:0; } #site_nav_local_views li:first-child { margin-left:0; } #site_nav_local_views a { -padding:0 3%; +padding:0; display:block; } #site_nav_local_views .current a { -- cgit v1.2.3-54-g00ecf From 5dc728a756b37f552480b72237f227a659f57928 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 9 Oct 2009 16:18:47 +0000 Subject: Reduced whitespace --- plugins/MobileProfile/mp-screen.css | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index afc07422b..9b42149c0 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -21,7 +21,7 @@ margin-right:0; address img + .fn { display:block; -margin-top:6%; +margin-top:8%; width:100%; } @@ -40,7 +40,7 @@ left:0; } #site_nav_global_primary li { margin-left:0; -margin-right:1%; +margin-right:2%; float:left; font-size:0.9em; } @@ -105,9 +105,30 @@ box-shadow:none; width:96.41%; } +h1 { +margin-bottom:0; +} + +.notice, +.profile { +padding-top:4px; +padding-bottom:4px; +} +.notice .entry-title { +} +.notice div.entry-content { +margin-left:0; +width:75%; +} .notice-options { -width:90px; +width:70px; margin-right:2%; } + +#footer { +width:96%; +padding:2%; +} + -- cgit v1.2.3-54-g00ecf From bb08611def2309711f91c1ab6cdab92fb7c069b2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 9 Oct 2009 14:22:18 -0700 Subject: Delete action/api.php and rename lib/twitterapi.php to lib/api.php --- actions/api.php | 306 --------- actions/apifriendshipsexists.php | 2 +- actions/apigrouplistall.php | 2 +- actions/apigroupmembership.php | 2 +- actions/apigroupshow.php | 2 +- actions/apihelptest.php | 2 +- actions/apistatusesshow.php | 4 +- actions/apistatusnetconfig.php | 2 +- actions/apistatusnetversion.php | 2 +- actions/apitimelinegroup.php | 4 +- actions/apitimelinepublic.php | 4 +- actions/apitimelinetag.php | 4 +- actions/apiusershow.php | 2 +- actions/twitapinotifications.php | 40 -- actions/twitapisearchatom.php | 6 +- actions/twitapisearchjson.php | 6 +- actions/twitapitrends.php | 6 +- lib/api.php | 1271 +++++++++++++++++++++++++++++++++++ lib/apiauth.php | 4 +- lib/twitterapi.php | 1251 ---------------------------------- plugins/Realtime/RealtimePlugin.php | 2 +- 21 files changed, 1299 insertions(+), 1625 deletions(-) delete mode 100644 actions/api.php delete mode 100644 actions/twitapinotifications.php create mode 100644 lib/api.php delete mode 100644 lib/twitterapi.php (limited to 'plugins') diff --git a/actions/api.php b/actions/api.php deleted file mode 100644 index 1bc90de11..000000000 --- a/actions/api.php +++ /dev/null @@ -1,306 +0,0 @@ -. - * - * @category Actions - * @package Actions - * @author Evan Prodromou - * @author Brenda Wallace - * @author Jeffery To - * @author Robin Millette - * @author Tom Adams - * @author Christopher Vollick - * @author CiaranG - * @author Craig Andrews - * @author Gina Haeussge - * @author Mike Cochrane - * @author Sarven Capadisli - * @license GNU Affero General Public License http://www.gnu.org/licenses/ - * @link http://status.net - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -class ApiAction extends Action -{ - - var $user; - var $content_type; - var $api_arg; - var $api_method; - var $api_action; - var $auth_user; - var $auth_pw; - - function handle($args) - { - parent::handle($args); - - $this->api_action = $this->arg('apiaction'); - $method = $this->arg('method'); - $argument = $this->arg('argument'); - $this->basic_auth_process_header(); - - if (isset($argument)) { - $cmdext = explode('.', $argument); - $this->api_arg = $cmdext[0]; - $this->api_method = $method; - $this->content_type = strtolower($cmdext[1]); - } else { - - //Requested format / content-type will be an extension on the method - $cmdext = explode('.', $method); - $this->api_method = $cmdext[0]; - $this->content_type = strtolower($cmdext[1]); - } - - if ($this->requires_auth()) { - if (!isset($this->auth_user)) { - - //This header makes basic auth go - header('WWW-Authenticate: Basic realm="StatusNet API"'); - - //If the user hits cancel -- bam! - $this->show_basic_auth_error(); - } else { - $nickname = $this->auth_user; - $password = $this->auth_pw; - $user = common_check_user($nickname, $password); - - if ($user) { - $this->user = $user; - $this->process_command(); - } else { - //basic authentication failed - list($proxy, $ip) = common_client_ip(); - - common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip."); - $this->show_basic_auth_error(); - } - } - } else { - - // Caller might give us a username even if not required - if (isset($this->auth_user)) { - $user = User::staticGet('nickname', $this->auth_user); - if ($user) { - $this->user = $user; - } - //Twitter doesn't throw an error if the user isn't found - } - - $this->process_command(); - } - } - - function process_command() - { - $action = "twitapi$this->api_action"; - $actionfile = INSTALLDIR."/actions/$action.php"; - - if (file_exists($actionfile)) { - include_once $actionfile; - $action_class = ucfirst($action)."Action"; - $action_obj = new $action_class(); - - if (!$action_obj->prepare($this->args)) { - return; - } - - if (method_exists($action_obj, $this->api_method)) { - $apidata = array( 'content-type' => $this->content_type, - 'api_method' => $this->api_method, - 'api_arg' => $this->api_arg, - 'user' => $this->user); - - call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata); - } else { - $this->clientError("API method not found!", $code = 404); - } - } else { - $this->clientError("API method not found!", $code = 404); - } - } - - // Whitelist of API methods that don't need authentication - function requires_auth() - { - static $noauth = array( 'statuses/public_timeline', - 'statuses/show', - 'users/show', - 'help/test', - 'help/downtime_schedule', - 'statusnet/version', - 'statusnet/config', - 'statusnet/wadl', - 'tags/timeline', - 'oembed/oembed', - 'groups/show', - 'groups/timeline', - 'groups/list_all', - 'groups/membership', - 'groups/is_member', - 'groups/timeline'); - - static $bareauth = array('statuses/user_timeline', - 'statuses/friends_timeline', - 'statuses/home_timeline', - 'statuses/friends', - 'statuses/replies', - 'statuses/mentions', - 'statuses/followers', - 'favorites/favorites', - 'friendships/show', - 'groups/list_groups'); - - $fullname = "$this->api_action/$this->api_method"; - - // If the site is "private", all API methods except statusnet/config - // need authentication - - if (common_config('site', 'private')) { - return $fullname != 'statusnet/config' || false; - } - - // bareauth: only needs auth if without an argument or query param specifying user - - if (in_array($fullname, $bareauth)) { - - // Special case: friendships/show only needs auth if source_id or - // source_screen_name is not specified as a param - - if ($fullname == 'friendships/show') { - - $source_id = $this->arg('source_id'); - $source_screen_name = $this->arg('source_screen_name'); - - if (empty($source_id) && empty($source_screen_name)) { - return true; - } - - return false; - } - - // if all of these are empty, auth is required - - $id = $this->arg('id'); - $user_id = $this->arg('user_id'); - $screen_name = $this->arg('screen_name'); - - if (empty($this->api_arg) - && empty($id) - && empty($user_id) - && empty($screen_name) - ) { - return true; - } else { - return false; - } - - } else if (in_array($fullname, $noauth)) { - - // noauth: never needs auth - - return false; - } else { - - // everybody else needs auth - - return true; - } - } - - function basic_auth_process_header() - { - if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) { - $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; - } - - if (isset($_SERVER['PHP_AUTH_USER'])) { - $this->auth_user = $_SERVER['PHP_AUTH_USER']; - $this->auth_pw = $_SERVER['PHP_AUTH_PW']; - } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) { - // decode the HTTP_AUTHORIZATION header on php-cgi server self - // on fcgid server the header name is AUTHORIZATION - - $auth_hash = base64_decode(substr($authorization_header, 6)); - list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); - - // set all to null on a empty basic auth request - if ($this->auth_user == "") { - $this->auth_user = null; - $this->auth_pw = null; - } - } else { - $this->auth_user = null; - $this->auth_pw = null; - } - } - - function show_basic_auth_error() - { - header('HTTP/1.1 401 Unauthorized'); - $msg = 'Could not authenticate you.'; - - if ($this->content_type == 'xml') { - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->endXML(); - } else if ($this->content_type == 'json') { - header('Content-Type: application/json; charset=utf-8'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - } else { - header('Content-type: text/plain'); - print "$msg\n"; - } - } - - function isReadOnly($args) - { - $apiaction = $args['apiaction']; - $method = $args['method']; - - list($cmdtext, $fmt) = explode('.', $method); - - static $write_methods = array( - 'account' => array('update_location', 'update_delivery_device', 'end_session'), - 'blocks' => array('create', 'destroy'), - 'direct_messages' => array('create', 'destroy'), - 'favorites' => array('create', 'destroy'), - 'friendships' => array('create', 'destroy'), - 'help' => array(), - 'notifications' => array('follow', 'leave'), - 'statuses' => array('update', 'destroy'), - 'users' => array() - ); - - if (array_key_exists($apiaction, $write_methods)) { - if (!in_array($cmdtext, $write_methods[$apiaction])) { - return true; - } - } - - return false; - } -} diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php index 3d6e7448d..d1d5d520f 100644 --- a/actions/apifriendshipsexists.php +++ b/actions/apifriendshipsexists.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Tests for the existence of friendship between two users. Will return true if diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php index b1964d800..80dcad9dc 100644 --- a/actions/apigrouplistall.php +++ b/actions/apigrouplistall.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * Returns of the lastest 20 groups for the site diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php index 0cd3ed290..872ee45ee 100644 --- a/actions/apigroupmembership.php +++ b/actions/apigroupmembership.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * List 20 newest members of the group specified by name or ID. diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php index 733c9ccfe..a38d50afe 100644 --- a/actions/apigroupshow.php +++ b/actions/apigroupshow.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Outputs detailed information about the group specified by ID diff --git a/actions/apihelptest.php b/actions/apihelptest.php index 5f32165cf..2cec46462 100644 --- a/actions/apihelptest.php +++ b/actions/apihelptest.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * Returns the string "ok" in the requested format with a 200 OK HTTP status code. diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php index 55eea2356..9e28fe2ab 100644 --- a/actions/apistatusesshow.php +++ b/actions/apistatusesshow.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Returns the notice specified by id as a Twitter-style status and inline user @@ -43,7 +43,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @link http://status.net/ */ -class ApiStatusesShowAction extends TwitterapiAction +class ApiStatusesShowAction extends ApiAction { var $notice_id = null; diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index 94bd5b4b3..6847a48fe 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * Gives a full dump of configuration variables for this instance diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php index 471297ad5..e6f35e7d2 100644 --- a/actions/apistatusnetversion.php +++ b/actions/apistatusnetversion.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * Returns a version number for this version of StatusNet, which diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index 11f73eeed..9d6ac6ad1 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/twitterapi.php'; +require_once INSTALLDIR . '/lib/api.php'; /** * Returns the most recent notices (default 20) posted to the group specified by ID @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/twitterapi.php'; * @link http://status.net/ */ -class ApiTimelineGroupAction extends TwitterapiAction +class ApiTimelineGroupAction extends ApiAction { var $group = null; diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 10bde6f37..2638dd292 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Returns the most recent notices (default 20) posted by everybody @@ -43,7 +43,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @link http://status.net/ */ -class ApiTimelinePublicAction extends TwitterapiAction +class ApiTimelinePublicAction extends ApiAction { var $notices = null; diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 2a23bb72a..0efe8d244 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Returns the 20 most recent notices tagged by a given tag @@ -43,7 +43,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @link http://status.net/ */ -class ApiTimelineTagAction extends TwitterapiAction +class ApiTimelineTagAction extends ApiAction { var $notices = null; diff --git a/actions/apiusershow.php b/actions/apiusershow.php index 2e2ceab41..afcbd3618 100644 --- a/actions/apiusershow.php +++ b/actions/apiusershow.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Ouputs information for a user, specified by ID or screen name. diff --git a/actions/twitapinotifications.php b/actions/twitapinotifications.php deleted file mode 100644 index 0653e69ab..000000000 --- a/actions/twitapinotifications.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -# This naming convention looks real sick -class TwitapinotificationsAction extends TwitterapiAction -{ - - function follow($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function leave($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - -} \ No newline at end of file diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 2f587d604..0ef9d2826 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Action for outputting search results in Twitter compatible Atom @@ -46,10 +46,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * - * @see TwitterapiAction + * @see ApiAction */ -class TwitapisearchatomAction extends TwitterapiAction +class TwitapisearchatomAction extends ApiAction { var $cnt; diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php index c628ee624..5abff6496 100644 --- a/actions/twitapisearchjson.php +++ b/actions/twitapisearchjson.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; /** @@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; * @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/ - * @see TwitterapiAction + * @see ApiAction */ -class TwitapisearchjsonAction extends TwitterapiAction +class TwitapisearchjsonAction extends ApiAction { var $query; var $lang; diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php index 83ab28f35..779405e6d 100644 --- a/actions/twitapitrends.php +++ b/actions/twitapitrends.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Returns the top ten queries that are currently trending @@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * - * @see TwitterapiAction + * @see ApiAction */ -class TwitapitrendsAction extends TwitterapiAction +class TwitapitrendsAction extends ApiAction { var $callback; diff --git a/lib/api.php b/lib/api.php new file mode 100644 index 000000000..93b4a7513 --- /dev/null +++ b/lib/api.php @@ -0,0 +1,1271 @@ +. + * + * @category API + * @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); +} + +/** + * Contains most of the Twitter-compatible API output functions. + * + * @category API + * @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 ApiAction extends Action +{ + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + } + + /** + * Overrides XMLOutputter::element to write booleans as strings (true|false). + * See that method's documentation for more info. + * + * @param string $tag Element type or tagname + * @param array $attrs Array of element attributes, as + * key-value pairs + * @param string $content string content of the element + * + * @return void + */ + function element($tag, $attrs=null, $content=null) + { + if (is_bool($content)) { + $content = ($content ? 'true' : 'false'); + } + + return parent::element($tag, $attrs, $content); + } + + function twitter_user_array($profile, $get_notice=false) + { + $twitter_user = array(); + + $twitter_user['id'] = intval($profile->id); + $twitter_user['name'] = $profile->getBestName(); + $twitter_user['screen_name'] = $profile->nickname; + $twitter_user['location'] = ($profile->location) ? $profile->location : null; + $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; + + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : + Avatar::defaultImage(AVATAR_STREAM_SIZE); + + $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; + $twitter_user['protected'] = false; # not supported by StatusNet yet + $twitter_user['followers_count'] = $profile->subscriberCount(); + + // To be supported soon... + $twitter_user['profile_background_color'] = ''; + $twitter_user['profile_text_color'] = ''; + $twitter_user['profile_link_color'] = ''; + $twitter_user['profile_sidebar_fill_color'] = ''; + $twitter_user['profile_sidebar_border_color'] = ''; + + $twitter_user['friends_count'] = $profile->subscriptionCount(); + + $twitter_user['created_at'] = $this->date_twitter($profile->created); + + $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! + + // Need to pull up the user for some of this + $user = User::staticGet($profile->id); + + $timezone = 'UTC'; + + if ($user->timezone) { + $timezone = $user->timezone; + } + + $t = new DateTime; + $t->setTimezone(new DateTimeZone($timezone)); + + $twitter_user['utc_offset'] = $t->format('Z'); + $twitter_user['time_zone'] = $timezone; + + // To be supported some day, perhaps + $twitter_user['profile_background_image_url'] = ''; + $twitter_user['profile_background_tile'] = false; + + $twitter_user['statuses_count'] = $profile->noticeCount(); + + // Is the requesting user following this user? + $twitter_user['following'] = false; + $twitter_user['notifications'] = false; + + if (isset($apidata['user'])) { + + $twitter_user['following'] = $apidata['user']->isSubscribed($profile); + + // Notifications on? + $sub = Subscription::pkeyGet(array('subscriber' => + $apidata['user']->id, 'subscribed' => $profile->id)); + + if ($sub) { + $twitter_user['notifications'] = ($sub->jabber || $sub->sms); + } + } + + if ($get_notice) { + $notice = $profile->getCurrentNotice(); + if ($notice) { + # don't get user! + $twitter_user['status'] = $this->twitter_status_array($notice, false); + } + } + + return $twitter_user; + } + + function twitter_status_array($notice, $include_user=true) + { + $profile = $notice->getProfile(); + + $twitter_status = array(); + $twitter_status['text'] = $notice->content; + $twitter_status['truncated'] = false; # Not possible on StatusNet + $twitter_status['created_at'] = $this->date_twitter($notice->created); + $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? + intval($notice->reply_to) : null; + $twitter_status['source'] = $this->source_link($notice->source); + $twitter_status['id'] = intval($notice->id); + + $replier_profile = null; + + if ($notice->reply_to) { + $reply = Notice::staticGet(intval($notice->reply_to)); + if ($reply) { + $replier_profile = $reply->getProfile(); + } + } + + $twitter_status['in_reply_to_user_id'] = + ($replier_profile) ? intval($replier_profile->id) : null; + $twitter_status['in_reply_to_screen_name'] = + ($replier_profile) ? $replier_profile->nickname : null; + + if (isset($this->auth_user)) { + $twitter_status['favorited'] = $this->auth_user->hasFave($notice); + } else { + $twitter_status['favorited'] = false; + } + + // Enclosures + $attachments = $notice->attachments(); + + if (!empty($attachments)) { + + $twitter_status['attachments'] = array(); + + foreach ($attachments as $attachment) { + if ($attachment->isEnclosure()) { + $enclosure = array(); + $enclosure['url'] = $attachment->url; + $enclosure['mimetype'] = $attachment->mimetype; + $enclosure['size'] = $attachment->size; + $twitter_status['attachments'][] = $enclosure; + } + } + } + + if ($include_user) { + # Don't get notice (recursive!) + $twitter_user = $this->twitter_user_array($profile, false); + $twitter_status['user'] = $twitter_user; + } + + return $twitter_status; + } + + function twitter_group_array($group) + { + $twitter_group=array(); + $twitter_group['id']=$group->id; + $twitter_group['url']=$group->permalink(); + $twitter_group['nickname']=$group->nickname; + $twitter_group['fullname']=$group->fullname; + $twitter_group['homepage_url']=$group->homepage_url; + $twitter_group['original_logo']=$group->original_logo; + $twitter_group['homepage_logo']=$group->homepage_logo; + $twitter_group['stream_logo']=$group->stream_logo; + $twitter_group['mini_logo']=$group->mini_logo; + $twitter_group['homepage']=$group->homepage; + $twitter_group['description']=$group->description; + $twitter_group['location']=$group->location; + $twitter_group['created']=$this->date_twitter($group->created); + $twitter_group['modified']=$this->date_twitter($group->modified); + return $twitter_group; + } + + function twitter_rss_group_array($group) + { + $entry = array(); + $entry['content']=$group->description; + $entry['title']=$group->nickname; + $entry['link']=$group->permalink(); + $entry['published']=common_date_iso8601($group->created); + $entry['updated']==common_date_iso8601($group->modified); + $taguribase = common_config('integration', 'groupuri'); + $entry['id'] = "group:$groupuribase:$entry[link]"; + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($group->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function twitter_rss_entry_array($notice) + { + $profile = $notice->getProfile(); + $entry = array(); + + // We trim() to avoid extraneous whitespace in the output + + $entry['content'] = common_xml_safe_str(trim($notice->rendered)); + $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); + $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); + $entry['published'] = common_date_iso8601($notice->created); + + $taguribase = common_config('integration', 'taguri'); + $entry['id'] = "tag:$taguribase:$entry[link]"; + + $entry['updated'] = $entry['published']; + $entry['author'] = $profile->getBestName(); + + // Enclosures + $attachments = $notice->attachments(); + $enclosures = array(); + + foreach ($attachments as $attachment) { + $enclosure_o=$attachment->getEnclosure(); + if ($enclosure_o) { + $enclosure = array(); + $enclosure['url'] = $enclosure_o->url; + $enclosure['mimetype'] = $enclosure_o->mimetype; + $enclosure['size'] = $enclosure_o->size; + $enclosures[] = $enclosure; + } + } + + if (!empty($enclosures)) { + $entry['enclosures'] = $enclosures; + } + +/* + // Enclosure + $attachments = $notice->attachments(); + if($attachments){ + $entry['enclosures']=array(); + foreach($attachments as $attachment){ + if ($attachment->isEnclosure()) { + $enclosure=array(); + $enclosure['url']=$attachment->url; + $enclosure['mimetype']=$attachment->mimetype; + $enclosure['size']=$attachment->size; + $entry['enclosures'][]=$enclosure; + } + } + } +*/ + + // Tags/Categories + $tag = new Notice_tag(); + $tag->notice_id = $notice->id; + if ($tag->find()) { + $entry['tags']=array(); + while ($tag->fetch()) { + $entry['tags'][]=$tag->tag; + } + } + $tag->free(); + + // RSS Item specific + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($notice->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + + function twitter_relationship_array($source, $target) + { + $relationship = array(); + + $relationship['source'] = + $this->relationship_details_array($source, $target); + $relationship['target'] = + $this->relationship_details_array($target, $source); + + return array('relationship' => $relationship); + } + + function relationship_details_array($source, $target) + { + $details = array(); + + $details['screen_name'] = $source->nickname; + $details['followed_by'] = $target->isSubscribed($source); + $details['following'] = $source->isSubscribed($target); + + $notifications = false; + + if ($source->isSubscribed($target)) { + + $sub = Subscription::pkeyGet(array('subscriber' => + $source->id, 'subscribed' => $target->id)); + + if (!empty($sub)) { + $notifications = ($sub->jabber || $sub->sms); + } + } + + $details['notifications_enabled'] = $notifications; + $details['blocking'] = $source->hasBlocked($target); + $details['id'] = $source->id; + + return $details; + } + + function show_twitter_xml_relationship($relationship) + { + $this->elementStart('relationship'); + + foreach($relationship as $element => $value) { + if ($element == 'source' || $element == 'target') { + $this->elementStart($element); + $this->show_xml_relationship_details($value); + $this->elementEnd($element); + } + } + + $this->elementEnd('relationship'); + } + + function show_xml_relationship_details($details) + { + foreach($details as $element => $value) { + $this->element($element, null, $value); + } + } + + function show_twitter_xml_status($twitter_status) + { + $this->elementStart('status'); + foreach($twitter_status as $element => $value) { + switch ($element) { + case 'user': + $this->show_twitter_xml_user($twitter_status['user']); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + case 'attachments': + $this->show_xml_attachments($twitter_status['attachments']); + break; + default: + $this->element($element, null, $value); + } + } + $this->elementEnd('status'); + } + + function show_twitter_xml_group($twitter_group) + { + $this->elementStart('group'); + foreach($twitter_group as $element => $value) { + $this->element($element, null, $value); + } + $this->elementEnd('group'); + } + + function show_twitter_xml_user($twitter_user, $role='user') + { + $this->elementStart($role); + foreach($twitter_user as $element => $value) { + if ($element == 'status') { + $this->show_twitter_xml_status($twitter_user['status']); + } else { + $this->element($element, null, $value); + } + } + $this->elementEnd($role); + } + + function show_xml_attachments($attachments) { + if (!empty($attachments)) { + $this->elementStart('attachments', array('type' => 'array')); + foreach ($attachments as $attachment) { + $attrs = array(); + $attrs['url'] = $attachment['url']; + $attrs['mimetype'] = $attachment['mimetype']; + $attrs['size'] = $attachment['size']; + $this->element('enclosure', $attrs, ''); + } + $this->elementEnd('attachments'); + } + } + + function show_twitter_rss_item($entry) + { + $this->elementStart('item'); + $this->element('title', null, $entry['title']); + $this->element('description', null, $entry['description']); + $this->element('pubDate', null, $entry['pubDate']); + $this->element('guid', null, $entry['guid']); + $this->element('link', null, $entry['link']); + + # RSS only supports 1 enclosure per item + if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ + $enclosure = $entry['enclosures'][0]; + $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); + } + + if(array_key_exists('tags', $entry)){ + foreach($entry['tags'] as $tag){ + $this->element('category', null,$tag); + } + } + + $this->elementEnd('item'); + } + + function show_json_objects($objects) + { + print(json_encode($objects)); + } + + function show_single_xml_status($notice) + { + $this->init_document('xml'); + $twitter_status = $this->twitter_status_array($notice); + $this->show_twitter_xml_status($twitter_status); + $this->end_document('xml'); + } + + function show_single_json_status($notice) + { + $this->init_document('json'); + $status = $this->twitter_status_array($notice); + $this->show_json_objects($status); + $this->end_document('json'); + } + + + function show_xml_timeline($notice) + { + + $this->init_document('xml'); + $this->elementStart('statuses', array('type' => 'array')); + + if (is_array($notice)) { + foreach ($notice as $n) { + $twitter_status = $this->twitter_status_array($n); + $this->show_twitter_xml_status($twitter_status); + } + } else { + while ($notice->fetch()) { + $twitter_status = $this->twitter_status_array($notice); + $this->show_twitter_xml_status($twitter_status); + } + } + + $this->elementEnd('statuses'); + $this->end_document('xml'); + } + + function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null) + { + + $this->init_document('rss'); + + $this->element('title', null, $title); + $this->element('link', null, $link); + if (!is_null($suplink)) { + # For FriendFeed's SUP protocol + $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'rel' => 'http://api.friendfeed.com/2008/03#sup', + 'href' => $suplink, + 'type' => 'application/json')); + } + $this->element('description', null, $subtitle); + $this->element('language', null, 'en-us'); + $this->element('ttl', null, '40'); + + if (is_array($notice)) { + foreach ($notice as $n) { + $entry = $this->twitter_rss_entry_array($n); + $this->show_twitter_rss_item($entry); + } + } else { + while ($notice->fetch()) { + $entry = $this->twitter_rss_entry_array($notice); + $this->show_twitter_rss_item($entry); + } + } + + $this->end_twitter_rss(); + } + + function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) + { + + $this->init_document('atom'); + + $this->element('title', null, $title); + $this->element('id', null, $id); + $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); + + if (!is_null($suplink)) { + # For FriendFeed's SUP protocol + $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', + 'href' => $suplink, + 'type' => 'application/json')); + } + + if (!is_null($selfuri)) { + $this->element('link', array('href' => $selfuri, + 'rel' => 'self', 'type' => 'application/atom+xml'), null); + } + + $this->element('updated', null, common_date_iso8601('now')); + $this->element('subtitle', null, $subtitle); + + if (is_array($notice)) { + foreach ($notice as $n) { + $this->raw($n->asAtomEntry()); + } + } else { + while ($notice->fetch()) { + $this->raw($notice->asAtomEntry()); + } + } + + $this->end_document('atom'); + + } + + function show_rss_groups($group, $title, $link, $subtitle) + { + + $this->init_document('rss'); + + $this->element('title', null, $title); + $this->element('link', null, $link); + $this->element('description', null, $subtitle); + $this->element('language', null, 'en-us'); + $this->element('ttl', null, '40'); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitter_rss_group_array($g); + $this->show_twitter_rss_item($twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitter_rss_group_array($group); + $this->show_twitter_rss_item($twitter_group); + } + } + + $this->end_twitter_rss(); + } + + + function showTwitterAtomEntry($entry) + { + $this->elementStart('entry'); + $this->element('title', null, $entry['title']); + $this->element('content', array('type' => 'html'), $entry['content']); + $this->element('id', null, $entry['id']); + $this->element('published', null, $entry['published']); + $this->element('updated', null, $entry['updated']); + $this->element('link', array('type' => 'text/html', + 'href' => $entry['link'], + 'rel' => 'alternate')); + $this->element('link', array('type' => $entry['avatar-type'], + 'href' => $entry['avatar'], + 'rel' => 'image')); + $this->elementStart('author'); + + $this->element('name', null, $entry['author-name']); + $this->element('uri', null, $entry['author-uri']); + + $this->elementEnd('author'); + $this->elementEnd('entry'); + } + + function showXmlDirectMessage($dm) + { + $this->elementStart('direct_message'); + foreach($dm as $element => $value) { + switch ($element) { + case 'sender': + case 'recipient': + $this->show_twitter_xml_user($value, $element); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + default: + $this->element($element, null, $value); + break; + } + } + $this->elementEnd('direct_message'); + } + + function directMessageArray($message) + { + $dmsg = array(); + + $from_profile = $message->getFrom(); + $to_profile = $message->getTo(); + + $dmsg['id'] = $message->id; + $dmsg['sender_id'] = $message->from_profile; + $dmsg['text'] = trim($message->content); + $dmsg['recipient_id'] = $message->to_profile; + $dmsg['created_at'] = $this->date_twitter($message->created); + $dmsg['sender_screen_name'] = $from_profile->nickname; + $dmsg['recipient_screen_name'] = $to_profile->nickname; + $dmsg['sender'] = $this->twitter_user_array($from_profile, false); + $dmsg['recipient'] = $this->twitter_user_array($to_profile, false); + + return $dmsg; + } + + function rssDirectMessageArray($message) + { + $entry = array(); + + $from = $message->getFrom(); + + $entry['title'] = sprintf('Message from %s to %s', + $from->nickname, $message->getTo()->nickname); + + $entry['content'] = common_xml_safe_str($message->rendered); + $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); + $entry['published'] = common_date_iso8601($message->created); + + $taguribase = common_config('integration', 'taguri'); + + $entry['id'] = "tag:$taguribase:$entry[link]"; + $entry['updated'] = $entry['published']; + + $entry['author-name'] = $from->getBestName(); + $entry['author-uri'] = $from->homepage; + + $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); + + $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); + $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; + + // RSS item specific + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($message->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function showSingleXmlDirectMessage($message) + { + $this->init_document('xml'); + $dmsg = $this->directMessageArray($message); + $this->showXmlDirectMessage($dmsg); + $this->end_document('xml'); + } + + function showSingleJsonDirectMessage($message) + { + $this->init_document('json'); + $dmsg = $this->directMessageArray($message); + $this->show_json_objects($dmsg); + $this->end_document('json'); + } + + function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + { + + $this->init_document('atom'); + + $this->element('title', null, $title); + $this->element('id', null, $id); + $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); + + if (!is_null($selfuri)) { + $this->element('link', array('href' => $selfuri, + 'rel' => 'self', 'type' => 'application/atom+xml'), null); + } + + $this->element('updated', null, common_date_iso8601('now')); + $this->element('subtitle', null, $subtitle); + + if (is_array($group)) { + foreach ($group as $g) { + $this->raw($g->asAtomEntry()); + } + } else { + while ($group->fetch()) { + $this->raw($group->asAtomEntry()); + } + } + + $this->end_document('atom'); + + } + + function show_json_timeline($notice) + { + + $this->init_document('json'); + + $statuses = array(); + + if (is_array($notice)) { + foreach ($notice as $n) { + $twitter_status = $this->twitter_status_array($n); + array_push($statuses, $twitter_status); + } + } else { + while ($notice->fetch()) { + $twitter_status = $this->twitter_status_array($notice); + array_push($statuses, $twitter_status); + } + } + + $this->show_json_objects($statuses); + + $this->end_document('json'); + } + + function show_json_groups($group) + { + + $this->init_document('json'); + + $groups = array(); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitter_group_array($g); + array_push($groups, $twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitter_group_array($group); + array_push($groups, $twitter_group); + } + } + + $this->show_json_objects($groups); + + $this->end_document('json'); + } + + function show_xml_groups($group) + { + + $this->init_document('xml'); + $this->elementStart('groups', array('type' => 'array')); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitter_group_array($g); + $this->show_twitter_xml_group($twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitter_group_array($group); + $this->show_twitter_xml_group($twitter_group); + } + } + + $this->elementEnd('groups'); + $this->end_document('xml'); + } + + function show_twitter_xml_users($user) + { + + $this->init_document('xml'); + $this->elementStart('users', array('type' => 'array')); + + if (is_array($user)) { + foreach ($user as $u) { + $twitter_user = $this->twitter_user_array($u); + $this->show_twitter_xml_user($twitter_user); + } + } else { + while ($user->fetch()) { + $twitter_user = $this->twitter_user_array($user); + $this->show_twitter_xml_user($twitter_user); + } + } + + $this->elementEnd('users'); + $this->end_document('xml'); + } + + function show_json_users($user) + { + + $this->init_document('json'); + + $users = array(); + + if (is_array($user)) { + foreach ($user as $u) { + $twitter_user = $this->twitter_user_array($u); + array_push($users, $twitter_user); + } + } else { + while ($user->fetch()) { + $twitter_user = $this->twitter_user_array($user); + array_push($users, $twitter_user); + } + } + + $this->show_json_objects($users); + + $this->end_document('json'); + } + + function show_single_json_group($group) + { + $this->init_document('json'); + $twitter_group = $this->twitter_group_array($group); + $this->show_json_objects($twitter_group); + $this->end_document('json'); + } + + function show_single_xml_group($group) + { + $this->init_document('xml'); + $twitter_group = $this->twitter_group_array($group); + $this->show_twitter_xml_group($twitter_group); + $this->end_document('xml'); + } + + function date_twitter($dt) + { + $dateStr = date('d F Y H:i:s', strtotime($dt)); + $d = new DateTime($dateStr, new DateTimeZone('UTC')); + $d->setTimezone(new DateTimeZone(common_timezone())); + return $d->format('D M d H:i:s O Y'); + } + + // XXX: Candidate for a general utility method somewhere? + function count_subscriptions($profile) + { + + $count = 0; + $sub = new Subscription(); + $sub->subscribed = $profile->id; + + $count = $sub->find(); + + if ($count > 0) { + return $count - 1; + } else { + return 0; + } + } + + function init_document($type='xml') + { + switch ($type) { + case 'xml': + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + break; + case 'json': + header('Content-Type: application/json; charset=utf-8'); + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print $callback . '('; + } + break; + case 'rss': + header("Content-Type: application/rss+xml; charset=utf-8"); + $this->init_twitter_rss(); + break; + case 'atom': + header('Content-Type: application/atom+xml; charset=utf-8'); + $this->init_twitter_atom(); + break; + default: + $this->clientError(_('Not a supported data format.')); + break; + } + + return; + } + + function end_document($type='xml') + { + switch ($type) { + case 'xml': + $this->endXML(); + break; + case 'json': + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print ')'; + } + break; + case 'rss': + $this->end_twitter_rss(); + break; + case 'atom': + $this->end_twitter_rss(); + break; + default: + $this->clientError(_('Not a supported data format.')); + break; + } + return; + } + + function clientError($msg, $code = 400, $format = 'xml') + { + $action = $this->trimmed('action'); + + common_debug("User error '$code' on '$action': $msg", __FILE__); + + if (!array_key_exists($code, ClientErrorAction::$status)) { + $code = 400; + } + + $status_string = ClientErrorAction::$status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); + + if ($format == 'xml') { + $this->init_document('xml'); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->end_document('xml'); + } elseif ($format == 'json'){ + $this->init_document('json'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + $this->end_document('json'); + } else { + + // If user didn't request a useful format, throw a regular client error + throw new ClientException($msg, $code); + } + } + + function serverError($msg, $code = 500, $content_type = 'json') + { + $action = $this->trimmed('action'); + + common_debug("Server error '$code' on '$action': $msg", __FILE__); + + if (!array_key_exists($code, ServerErrorAction::$status)) { + $code = 400; + } + + $status_string = ServerErrorAction::$status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); + + if ($content_type == 'xml') { + $this->init_document('xml'); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->end_document('xml'); + } else { + $this->init_document('json'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + $this->end_document('json'); + } + } + + function init_twitter_rss() + { + $this->startXML(); + $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); + $this->elementStart('channel'); + Event::handle('StartApiRss', array($this)); + } + + function end_twitter_rss() + { + $this->elementEnd('channel'); + $this->elementEnd('rss'); + $this->endXML(); + } + + function init_twitter_atom() + { + $this->startXML(); + // FIXME: don't hardcode the language here! + $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'xml:lang' => 'en-US', + 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); + Event::handle('StartApiAtom', array($this)); + } + + function end_twitter_atom() + { + $this->elementEnd('feed'); + $this->endXML(); + } + + function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true) + { + $profile_array = $this->twitter_user_array($profile, $includeStatuses); + switch ($content_type) { + case 'xml': + $this->show_twitter_xml_user($profile_array); + break; + case 'json': + $this->show_json_objects($profile_array); + break; + default: + $this->clientError(_('Not a supported data format.')); + return; + } + return; + } + + function get_user($id, $apidata=null) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $apidata['user']; + } + + } else if (is_numeric($id)) { + return User::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User::staticGet('nickname', $nickname); + } + } + + function getTargetUser($id) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $this->auth_user; + } + + } else if (is_numeric($id)) { + return User::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User::staticGet('nickname', $nickname); + } + } + + function getTargetGroup($id) + { + if (empty($id)) { + if (is_numeric($this->arg('id'))) { + return User_group::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User_group::staticGet('nickname', $nickname); + } else if ($this->arg('group_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('group_id'))) { + return User_group::staticGet('id', $this->arg('group_id')); + } + } else if ($this->arg('group_name')) { + $nickname = common_canonical_nickname($this->arg('group_name')); + return User_group::staticGet('nickname', $nickname); + } + + } else if (is_numeric($id)) { + return User_group::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User_group::staticGet('nickname', $nickname); + } + } + + function get_profile($id) + { + if (is_numeric($id)) { + return Profile::staticGet($id); + } else { + $user = User::staticGet('nickname', $id); + if ($user) { + return $user->getProfile(); + } else { + return null; + } + } + } + + function source_link($source) + { + $source_name = _($source); + switch ($source) { + case 'web': + case 'xmpp': + case 'mail': + case 'omb': + case 'api': + break; + default: + $ns = Notice_source::staticGet($source); + if ($ns) { + $source_name = '' . $ns->name . ''; + } + break; + } + return $source_name; + } + + /** + * Returns query argument or default value if not found. Certain + * parameters used throughout the API are lightly scrubbed and + * bounds checked. This overrides Action::arg(). + * + * @param string $key requested argument + * @param string $def default value to return if $key is not provided + * + * @return var $var + */ + function arg($key, $def=null) + { + + // XXX: Do even more input validation/scrubbing? + + if (array_key_exists($key, $this->args)) { + switch($key) { + case 'page': + $page = (int)$this->args['page']; + return ($page < 1) ? 1 : $page; + case 'count': + $count = (int)$this->args['count']; + if ($count < 1) { + return 20; + } elseif ($count > 200) { + return 200; + } else { + return $count; + } + case 'since_id': + $since_id = (int)$this->args['since_id']; + return ($since_id < 1) ? 0 : $since_id; + case 'max_id': + $max_id = (int)$this->args['max_id']; + return ($max_id < 1) ? 0 : $max_id; + case 'since': + return strtotime($this->args['since']); + default: + return parent::arg($key, $def); + } + } else { + return $def; + } + } + +} diff --git a/lib/apiauth.php b/lib/apiauth.php index f0b4b6bf7..d7f8017eb 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Actions extending this class will require auth @@ -43,7 +43,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @link http://status.net/ */ -class ApiAuthAction extends TwitterapiAction +class ApiAuthAction extends ApiAction { var $auth_user = null; diff --git a/lib/twitterapi.php b/lib/twitterapi.php deleted file mode 100644 index e5904cc85..000000000 --- a/lib/twitterapi.php +++ /dev/null @@ -1,1251 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -class TwitterapiAction extends Action -{ - - /** - * Initialization. - * - * @param array $args Web and URL arguments - * - * @return boolean false if user doesn't exist - */ - - function prepare($args) - { - parent::prepare($args); - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - function handle($args) - { - parent::handle($args); - } - - /** - * Overrides XMLOutputter::element to write booleans as strings (true|false). - * See that method's documentation for more info. - * - * @param string $tag Element type or tagname - * @param array $attrs Array of element attributes, as - * key-value pairs - * @param string $content string content of the element - * - * @return void - */ - function element($tag, $attrs=null, $content=null) - { - if (is_bool($content)) { - $content = ($content ? 'true' : 'false'); - } - - return parent::element($tag, $attrs, $content); - } - - function twitter_user_array($profile, $get_notice=false) - { - $twitter_user = array(); - - $twitter_user['id'] = intval($profile->id); - $twitter_user['name'] = $profile->getBestName(); - $twitter_user['screen_name'] = $profile->nickname; - $twitter_user['location'] = ($profile->location) ? $profile->location : null; - $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; - - $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); - $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : - Avatar::defaultImage(AVATAR_STREAM_SIZE); - - $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; - $twitter_user['protected'] = false; # not supported by StatusNet yet - $twitter_user['followers_count'] = $profile->subscriberCount(); - - // To be supported soon... - $twitter_user['profile_background_color'] = ''; - $twitter_user['profile_text_color'] = ''; - $twitter_user['profile_link_color'] = ''; - $twitter_user['profile_sidebar_fill_color'] = ''; - $twitter_user['profile_sidebar_border_color'] = ''; - - $twitter_user['friends_count'] = $profile->subscriptionCount(); - - $twitter_user['created_at'] = $this->date_twitter($profile->created); - - $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! - - // Need to pull up the user for some of this - $user = User::staticGet($profile->id); - - $timezone = 'UTC'; - - if ($user->timezone) { - $timezone = $user->timezone; - } - - $t = new DateTime; - $t->setTimezone(new DateTimeZone($timezone)); - - $twitter_user['utc_offset'] = $t->format('Z'); - $twitter_user['time_zone'] = $timezone; - - // To be supported some day, perhaps - $twitter_user['profile_background_image_url'] = ''; - $twitter_user['profile_background_tile'] = false; - - $twitter_user['statuses_count'] = $profile->noticeCount(); - - // Is the requesting user following this user? - $twitter_user['following'] = false; - $twitter_user['notifications'] = false; - - if (isset($apidata['user'])) { - - $twitter_user['following'] = $apidata['user']->isSubscribed($profile); - - // Notifications on? - $sub = Subscription::pkeyGet(array('subscriber' => - $apidata['user']->id, 'subscribed' => $profile->id)); - - if ($sub) { - $twitter_user['notifications'] = ($sub->jabber || $sub->sms); - } - } - - if ($get_notice) { - $notice = $profile->getCurrentNotice(); - if ($notice) { - # don't get user! - $twitter_user['status'] = $this->twitter_status_array($notice, false); - } - } - - return $twitter_user; - } - - function twitter_status_array($notice, $include_user=true) - { - $profile = $notice->getProfile(); - - $twitter_status = array(); - $twitter_status['text'] = $notice->content; - $twitter_status['truncated'] = false; # Not possible on StatusNet - $twitter_status['created_at'] = $this->date_twitter($notice->created); - $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? - intval($notice->reply_to) : null; - $twitter_status['source'] = $this->source_link($notice->source); - $twitter_status['id'] = intval($notice->id); - - $replier_profile = null; - - if ($notice->reply_to) { - $reply = Notice::staticGet(intval($notice->reply_to)); - if ($reply) { - $replier_profile = $reply->getProfile(); - } - } - - $twitter_status['in_reply_to_user_id'] = - ($replier_profile) ? intval($replier_profile->id) : null; - $twitter_status['in_reply_to_screen_name'] = - ($replier_profile) ? $replier_profile->nickname : null; - - if (isset($this->auth_user)) { - $twitter_status['favorited'] = $this->auth_user->hasFave($notice); - } else { - $twitter_status['favorited'] = false; - } - - // Enclosures - $attachments = $notice->attachments(); - - if (!empty($attachments)) { - - $twitter_status['attachments'] = array(); - - foreach ($attachments as $attachment) { - if ($attachment->isEnclosure()) { - $enclosure = array(); - $enclosure['url'] = $attachment->url; - $enclosure['mimetype'] = $attachment->mimetype; - $enclosure['size'] = $attachment->size; - $twitter_status['attachments'][] = $enclosure; - } - } - } - - if ($include_user) { - # Don't get notice (recursive!) - $twitter_user = $this->twitter_user_array($profile, false); - $twitter_status['user'] = $twitter_user; - } - - return $twitter_status; - } - - function twitter_group_array($group) - { - $twitter_group=array(); - $twitter_group['id']=$group->id; - $twitter_group['url']=$group->permalink(); - $twitter_group['nickname']=$group->nickname; - $twitter_group['fullname']=$group->fullname; - $twitter_group['homepage_url']=$group->homepage_url; - $twitter_group['original_logo']=$group->original_logo; - $twitter_group['homepage_logo']=$group->homepage_logo; - $twitter_group['stream_logo']=$group->stream_logo; - $twitter_group['mini_logo']=$group->mini_logo; - $twitter_group['homepage']=$group->homepage; - $twitter_group['description']=$group->description; - $twitter_group['location']=$group->location; - $twitter_group['created']=$this->date_twitter($group->created); - $twitter_group['modified']=$this->date_twitter($group->modified); - return $twitter_group; - } - - function twitter_rss_group_array($group) - { - $entry = array(); - $entry['content']=$group->description; - $entry['title']=$group->nickname; - $entry['link']=$group->permalink(); - $entry['published']=common_date_iso8601($group->created); - $entry['updated']==common_date_iso8601($group->modified); - $taguribase = common_config('integration', 'groupuri'); - $entry['id'] = "group:$groupuribase:$entry[link]"; - - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($group->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function twitter_rss_entry_array($notice) - { - $profile = $notice->getProfile(); - $entry = array(); - - // We trim() to avoid extraneous whitespace in the output - - $entry['content'] = common_xml_safe_str(trim($notice->rendered)); - $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); - $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); - $entry['published'] = common_date_iso8601($notice->created); - - $taguribase = common_config('integration', 'taguri'); - $entry['id'] = "tag:$taguribase:$entry[link]"; - - $entry['updated'] = $entry['published']; - $entry['author'] = $profile->getBestName(); - - // Enclosures - $attachments = $notice->attachments(); - $enclosures = array(); - - foreach ($attachments as $attachment) { - $enclosure_o=$attachment->getEnclosure(); - if ($enclosure_o) { - $enclosure = array(); - $enclosure['url'] = $enclosure_o->url; - $enclosure['mimetype'] = $enclosure_o->mimetype; - $enclosure['size'] = $enclosure_o->size; - $enclosures[] = $enclosure; - } - } - - if (!empty($enclosures)) { - $entry['enclosures'] = $enclosures; - } - -/* - // Enclosure - $attachments = $notice->attachments(); - if($attachments){ - $entry['enclosures']=array(); - foreach($attachments as $attachment){ - if ($attachment->isEnclosure()) { - $enclosure=array(); - $enclosure['url']=$attachment->url; - $enclosure['mimetype']=$attachment->mimetype; - $enclosure['size']=$attachment->size; - $entry['enclosures'][]=$enclosure; - } - } - } -*/ - - // Tags/Categories - $tag = new Notice_tag(); - $tag->notice_id = $notice->id; - if ($tag->find()) { - $entry['tags']=array(); - while ($tag->fetch()) { - $entry['tags'][]=$tag->tag; - } - } - $tag->free(); - - // RSS Item specific - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($notice->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - - function twitter_relationship_array($source, $target) - { - $relationship = array(); - - $relationship['source'] = - $this->relationship_details_array($source, $target); - $relationship['target'] = - $this->relationship_details_array($target, $source); - - return array('relationship' => $relationship); - } - - function relationship_details_array($source, $target) - { - $details = array(); - - $details['screen_name'] = $source->nickname; - $details['followed_by'] = $target->isSubscribed($source); - $details['following'] = $source->isSubscribed($target); - - $notifications = false; - - if ($source->isSubscribed($target)) { - - $sub = Subscription::pkeyGet(array('subscriber' => - $source->id, 'subscribed' => $target->id)); - - if (!empty($sub)) { - $notifications = ($sub->jabber || $sub->sms); - } - } - - $details['notifications_enabled'] = $notifications; - $details['blocking'] = $source->hasBlocked($target); - $details['id'] = $source->id; - - return $details; - } - - function show_twitter_xml_relationship($relationship) - { - $this->elementStart('relationship'); - - foreach($relationship as $element => $value) { - if ($element == 'source' || $element == 'target') { - $this->elementStart($element); - $this->show_xml_relationship_details($value); - $this->elementEnd($element); - } - } - - $this->elementEnd('relationship'); - } - - function show_xml_relationship_details($details) - { - foreach($details as $element => $value) { - $this->element($element, null, $value); - } - } - - function show_twitter_xml_status($twitter_status) - { - $this->elementStart('status'); - foreach($twitter_status as $element => $value) { - switch ($element) { - case 'user': - $this->show_twitter_xml_user($twitter_status['user']); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - case 'attachments': - $this->show_xml_attachments($twitter_status['attachments']); - break; - default: - $this->element($element, null, $value); - } - } - $this->elementEnd('status'); - } - - function show_twitter_xml_group($twitter_group) - { - $this->elementStart('group'); - foreach($twitter_group as $element => $value) { - $this->element($element, null, $value); - } - $this->elementEnd('group'); - } - - function show_twitter_xml_user($twitter_user, $role='user') - { - $this->elementStart($role); - foreach($twitter_user as $element => $value) { - if ($element == 'status') { - $this->show_twitter_xml_status($twitter_user['status']); - } else { - $this->element($element, null, $value); - } - } - $this->elementEnd($role); - } - - function show_xml_attachments($attachments) { - if (!empty($attachments)) { - $this->elementStart('attachments', array('type' => 'array')); - foreach ($attachments as $attachment) { - $attrs = array(); - $attrs['url'] = $attachment['url']; - $attrs['mimetype'] = $attachment['mimetype']; - $attrs['size'] = $attachment['size']; - $this->element('enclosure', $attrs, ''); - } - $this->elementEnd('attachments'); - } - } - - function show_twitter_rss_item($entry) - { - $this->elementStart('item'); - $this->element('title', null, $entry['title']); - $this->element('description', null, $entry['description']); - $this->element('pubDate', null, $entry['pubDate']); - $this->element('guid', null, $entry['guid']); - $this->element('link', null, $entry['link']); - - # RSS only supports 1 enclosure per item - if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ - $enclosure = $entry['enclosures'][0]; - $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); - } - - if(array_key_exists('tags', $entry)){ - foreach($entry['tags'] as $tag){ - $this->element('category', null,$tag); - } - } - - $this->elementEnd('item'); - } - - function show_json_objects($objects) - { - print(json_encode($objects)); - } - - function show_single_xml_status($notice) - { - $this->init_document('xml'); - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); - $this->end_document('xml'); - } - - function show_single_json_status($notice) - { - $this->init_document('json'); - $status = $this->twitter_status_array($notice); - $this->show_json_objects($status); - $this->end_document('json'); - } - - - function show_xml_timeline($notice) - { - - $this->init_document('xml'); - $this->elementStart('statuses', array('type' => 'array')); - - if (is_array($notice)) { - foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); - $this->show_twitter_xml_status($twitter_status); - } - } else { - while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); - } - } - - $this->elementEnd('statuses'); - $this->end_document('xml'); - } - - function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null) - { - - $this->init_document('rss'); - - $this->element('title', null, $title); - $this->element('link', null, $link); - if (!is_null($suplink)) { - # For FriendFeed's SUP protocol - $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', - 'rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); - } - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($notice)) { - foreach ($notice as $n) { - $entry = $this->twitter_rss_entry_array($n); - $this->show_twitter_rss_item($entry); - } - } else { - while ($notice->fetch()) { - $entry = $this->twitter_rss_entry_array($notice); - $this->show_twitter_rss_item($entry); - } - } - - $this->end_twitter_rss(); - } - - function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) - { - - $this->init_document('atom'); - - $this->element('title', null, $title); - $this->element('id', null, $id); - $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); - - if (!is_null($suplink)) { - # For FriendFeed's SUP protocol - $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); - } - - if (!is_null($selfuri)) { - $this->element('link', array('href' => $selfuri, - 'rel' => 'self', 'type' => 'application/atom+xml'), null); - } - - $this->element('updated', null, common_date_iso8601('now')); - $this->element('subtitle', null, $subtitle); - - if (is_array($notice)) { - foreach ($notice as $n) { - $this->raw($n->asAtomEntry()); - } - } else { - while ($notice->fetch()) { - $this->raw($notice->asAtomEntry()); - } - } - - $this->end_document('atom'); - - } - - function show_rss_groups($group, $title, $link, $subtitle) - { - - $this->init_document('rss'); - - $this->element('title', null, $title); - $this->element('link', null, $link); - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitter_rss_group_array($g); - $this->show_twitter_rss_item($twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitter_rss_group_array($group); - $this->show_twitter_rss_item($twitter_group); - } - } - - $this->end_twitter_rss(); - } - - - function showTwitterAtomEntry($entry) - { - $this->elementStart('entry'); - $this->element('title', null, $entry['title']); - $this->element('content', array('type' => 'html'), $entry['content']); - $this->element('id', null, $entry['id']); - $this->element('published', null, $entry['published']); - $this->element('updated', null, $entry['updated']); - $this->element('link', array('type' => 'text/html', - 'href' => $entry['link'], - 'rel' => 'alternate')); - $this->element('link', array('type' => $entry['avatar-type'], - 'href' => $entry['avatar'], - 'rel' => 'image')); - $this->elementStart('author'); - - $this->element('name', null, $entry['author-name']); - $this->element('uri', null, $entry['author-uri']); - - $this->elementEnd('author'); - $this->elementEnd('entry'); - } - - function showXmlDirectMessage($dm) - { - $this->elementStart('direct_message'); - foreach($dm as $element => $value) { - switch ($element) { - case 'sender': - case 'recipient': - $this->show_twitter_xml_user($value, $element); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - default: - $this->element($element, null, $value); - break; - } - } - $this->elementEnd('direct_message'); - } - - function directMessageArray($message) - { - $dmsg = array(); - - $from_profile = $message->getFrom(); - $to_profile = $message->getTo(); - - $dmsg['id'] = $message->id; - $dmsg['sender_id'] = $message->from_profile; - $dmsg['text'] = trim($message->content); - $dmsg['recipient_id'] = $message->to_profile; - $dmsg['created_at'] = $this->date_twitter($message->created); - $dmsg['sender_screen_name'] = $from_profile->nickname; - $dmsg['recipient_screen_name'] = $to_profile->nickname; - $dmsg['sender'] = $this->twitter_user_array($from_profile, false); - $dmsg['recipient'] = $this->twitter_user_array($to_profile, false); - - return $dmsg; - } - - function rssDirectMessageArray($message) - { - $entry = array(); - - $from = $message->getFrom(); - - $entry['title'] = sprintf('Message from %s to %s', - $from->nickname, $message->getTo()->nickname); - - $entry['content'] = common_xml_safe_str($message->rendered); - $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); - $entry['published'] = common_date_iso8601($message->created); - - $taguribase = common_config('integration', 'taguri'); - - $entry['id'] = "tag:$taguribase:$entry[link]"; - $entry['updated'] = $entry['published']; - - $entry['author-name'] = $from->getBestName(); - $entry['author-uri'] = $from->homepage; - - $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); - - $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); - $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; - - // RSS item specific - - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($message->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function showSingleXmlDirectMessage($message) - { - $this->init_document('xml'); - $dmsg = $this->directMessageArray($message); - $this->showXmlDirectMessage($dmsg); - $this->end_document('xml'); - } - - function showSingleJsonDirectMessage($message) - { - $this->init_document('json'); - $dmsg = $this->directMessageArray($message); - $this->show_json_objects($dmsg); - $this->end_document('json'); - } - - function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null) - { - - $this->init_document('atom'); - - $this->element('title', null, $title); - $this->element('id', null, $id); - $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); - - if (!is_null($selfuri)) { - $this->element('link', array('href' => $selfuri, - 'rel' => 'self', 'type' => 'application/atom+xml'), null); - } - - $this->element('updated', null, common_date_iso8601('now')); - $this->element('subtitle', null, $subtitle); - - if (is_array($group)) { - foreach ($group as $g) { - $this->raw($g->asAtomEntry()); - } - } else { - while ($group->fetch()) { - $this->raw($group->asAtomEntry()); - } - } - - $this->end_document('atom'); - - } - - function show_json_timeline($notice) - { - - $this->init_document('json'); - - $statuses = array(); - - if (is_array($notice)) { - foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); - array_push($statuses, $twitter_status); - } - } else { - while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); - array_push($statuses, $twitter_status); - } - } - - $this->show_json_objects($statuses); - - $this->end_document('json'); - } - - function show_json_groups($group) - { - - $this->init_document('json'); - - $groups = array(); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); - array_push($groups, $twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); - array_push($groups, $twitter_group); - } - } - - $this->show_json_objects($groups); - - $this->end_document('json'); - } - - function show_xml_groups($group) - { - - $this->init_document('xml'); - $this->elementStart('groups', array('type' => 'array')); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); - $this->show_twitter_xml_group($twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); - } - } - - $this->elementEnd('groups'); - $this->end_document('xml'); - } - - function show_twitter_xml_users($user) - { - - $this->init_document('xml'); - $this->elementStart('users', array('type' => 'array')); - - if (is_array($user)) { - foreach ($user as $u) { - $twitter_user = $this->twitter_user_array($u); - $this->show_twitter_xml_user($twitter_user); - } - } else { - while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); - $this->show_twitter_xml_user($twitter_user); - } - } - - $this->elementEnd('users'); - $this->end_document('xml'); - } - - function show_json_users($user) - { - - $this->init_document('json'); - - $users = array(); - - if (is_array($user)) { - foreach ($user as $u) { - $twitter_user = $this->twitter_user_array($u); - array_push($users, $twitter_user); - } - } else { - while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); - array_push($users, $twitter_user); - } - } - - $this->show_json_objects($users); - - $this->end_document('json'); - } - - function show_single_json_group($group) - { - $this->init_document('json'); - $twitter_group = $this->twitter_group_array($group); - $this->show_json_objects($twitter_group); - $this->end_document('json'); - } - - function show_single_xml_group($group) - { - $this->init_document('xml'); - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); - $this->end_document('xml'); - } - - function date_twitter($dt) - { - $dateStr = date('d F Y H:i:s', strtotime($dt)); - $d = new DateTime($dateStr, new DateTimeZone('UTC')); - $d->setTimezone(new DateTimeZone(common_timezone())); - return $d->format('D M d H:i:s O Y'); - } - - // XXX: Candidate for a general utility method somewhere? - function count_subscriptions($profile) - { - - $count = 0; - $sub = new Subscription(); - $sub->subscribed = $profile->id; - - $count = $sub->find(); - - if ($count > 0) { - return $count - 1; - } else { - return 0; - } - } - - function init_document($type='xml') - { - switch ($type) { - case 'xml': - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - break; - case 'json': - header('Content-Type: application/json; charset=utf-8'); - - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print $callback . '('; - } - break; - case 'rss': - header("Content-Type: application/rss+xml; charset=utf-8"); - $this->init_twitter_rss(); - break; - case 'atom': - header('Content-Type: application/atom+xml; charset=utf-8'); - $this->init_twitter_atom(); - break; - default: - $this->clientError(_('Not a supported data format.')); - break; - } - - return; - } - - function end_document($type='xml') - { - switch ($type) { - case 'xml': - $this->endXML(); - break; - case 'json': - - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print ')'; - } - break; - case 'rss': - $this->end_twitter_rss(); - break; - case 'atom': - $this->end_twitter_rss(); - break; - default: - $this->clientError(_('Not a supported data format.')); - break; - } - return; - } - - function clientError($msg, $code = 400, $format = 'xml') - { - $action = $this->trimmed('action'); - - common_debug("User error '$code' on '$action': $msg", __FILE__); - - if (!array_key_exists($code, ClientErrorAction::$status)) { - $code = 400; - } - - $status_string = ClientErrorAction::$status[$code]; - - header('HTTP/1.1 '.$code.' '.$status_string); - - if ($format == 'xml') { - $this->init_document('xml'); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->end_document('xml'); - } elseif ($format == 'json'){ - $this->init_document('json'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - $this->end_document('json'); - } else { - - // If user didn't request a useful format, throw a regular client error - throw new ClientException($msg, $code); - } - } - - function serverError($msg, $code = 500, $content_type = 'json') - { - $action = $this->trimmed('action'); - - common_debug("Server error '$code' on '$action': $msg", __FILE__); - - if (!array_key_exists($code, ServerErrorAction::$status)) { - $code = 400; - } - - $status_string = ServerErrorAction::$status[$code]; - - header('HTTP/1.1 '.$code.' '.$status_string); - - if ($content_type == 'xml') { - $this->init_document('xml'); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->end_document('xml'); - } else { - $this->init_document('json'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - $this->end_document('json'); - } - } - - function init_twitter_rss() - { - $this->startXML(); - $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); - $this->elementStart('channel'); - Event::handle('StartApiRss', array($this)); - } - - function end_twitter_rss() - { - $this->elementEnd('channel'); - $this->elementEnd('rss'); - $this->endXML(); - } - - function init_twitter_atom() - { - $this->startXML(); - // FIXME: don't hardcode the language here! - $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', - 'xml:lang' => 'en-US', - 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); - Event::handle('StartApiAtom', array($this)); - } - - function end_twitter_atom() - { - $this->elementEnd('feed'); - $this->endXML(); - } - - function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true) - { - $profile_array = $this->twitter_user_array($profile, $includeStatuses); - switch ($content_type) { - case 'xml': - $this->show_twitter_xml_user($profile_array); - break; - case 'json': - $this->show_json_objects($profile_array); - break; - default: - $this->clientError(_('Not a supported data format.')); - return; - } - return; - } - - function get_user($id, $apidata=null) - { - if (empty($id)) { - - // Twitter supports these other ways of passing the user ID - if (is_numeric($this->arg('id'))) { - return User::staticGet($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - return User::staticGet('nickname', $nickname); - } else if ($this->arg('user_id')) { - // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it doesn't get used - if (is_numeric($this->arg('user_id'))) { - return User::staticGet('id', $this->arg('user_id')); - } - } else if ($this->arg('screen_name')) { - $nickname = common_canonical_nickname($this->arg('screen_name')); - return User::staticGet('nickname', $nickname); - } else { - // Fall back to trying the currently authenticated user - return $apidata['user']; - } - - } else if (is_numeric($id)) { - return User::staticGet($id); - } else { - $nickname = common_canonical_nickname($id); - return User::staticGet('nickname', $nickname); - } - } - - function getTargetUser($id) - { - if (empty($id)) { - - // Twitter supports these other ways of passing the user ID - if (is_numeric($this->arg('id'))) { - return User::staticGet($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - return User::staticGet('nickname', $nickname); - } else if ($this->arg('user_id')) { - // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it doesn't get used - if (is_numeric($this->arg('user_id'))) { - return User::staticGet('id', $this->arg('user_id')); - } - } else if ($this->arg('screen_name')) { - $nickname = common_canonical_nickname($this->arg('screen_name')); - return User::staticGet('nickname', $nickname); - } else { - // Fall back to trying the currently authenticated user - return $this->auth_user; - } - - } else if (is_numeric($id)) { - return User::staticGet($id); - } else { - $nickname = common_canonical_nickname($id); - return User::staticGet('nickname', $nickname); - } - } - - function getTargetGroup($id) - { - if (empty($id)) { - if (is_numeric($this->arg('id'))) { - return User_group::staticGet($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - return User_group::staticGet('nickname', $nickname); - } else if ($this->arg('group_id')) { - // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it doesn't get used - if (is_numeric($this->arg('group_id'))) { - return User_group::staticGet('id', $this->arg('group_id')); - } - } else if ($this->arg('group_name')) { - $nickname = common_canonical_nickname($this->arg('group_name')); - return User_group::staticGet('nickname', $nickname); - } - - } else if (is_numeric($id)) { - return User_group::staticGet($id); - } else { - $nickname = common_canonical_nickname($id); - return User_group::staticGet('nickname', $nickname); - } - } - - function get_profile($id) - { - if (is_numeric($id)) { - return Profile::staticGet($id); - } else { - $user = User::staticGet('nickname', $id); - if ($user) { - return $user->getProfile(); - } else { - return null; - } - } - } - - function source_link($source) - { - $source_name = _($source); - switch ($source) { - case 'web': - case 'xmpp': - case 'mail': - case 'omb': - case 'api': - break; - default: - $ns = Notice_source::staticGet($source); - if ($ns) { - $source_name = '' . $ns->name . ''; - } - break; - } - return $source_name; - } - - /** - * Returns query argument or default value if not found. Certain - * parameters used throughout the API are lightly scrubbed and - * bounds checked. This overrides Action::arg(). - * - * @param string $key requested argument - * @param string $def default value to return if $key is not provided - * - * @return var $var - */ - function arg($key, $def=null) - { - - // XXX: Do even more input validation/scrubbing? - - if (array_key_exists($key, $this->args)) { - switch($key) { - case 'page': - $page = (int)$this->args['page']; - return ($page < 1) ? 1 : $page; - case 'count': - $count = (int)$this->args['count']; - if ($count < 1) { - return 20; - } elseif ($count > 200) { - return 200; - } else { - return $count; - } - case 'since_id': - $since_id = (int)$this->args['since_id']; - return ($since_id < 1) ? 0 : $since_id; - case 'max_id': - $max_id = (int)$this->args['max_id']; - return ($max_id < 1) ? 0 : $max_id; - case 'since': - return strtotime($this->args['since']); - default: - return parent::arg($key, $def); - } - } else { - return $def; - } - } - -} diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 181927968..31e75221b 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -242,7 +242,7 @@ class RealtimePlugin extends Plugin // 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'); + require_once(INSTALLDIR.'/lib/api.php'); $act = new TwitterApiAction('/dev/null'); -- cgit v1.2.3-54-g00ecf From 57dfad64beae100187dcaf3c205645e89611e115 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 9 Oct 2009 17:21:22 -0700 Subject: Missed some of the references to the old TwitterApiAction - removed --- actions/apiaccountverifycredentials.php | 5 +++-- actions/apifriendshipsexists.php | 2 +- actions/apigrouplistall.php | 2 +- actions/apigroupmembership.php | 2 +- actions/apigroupshow.php | 2 +- actions/apihelptest.php | 2 +- actions/apistatusnetconfig.php | 2 +- actions/apistatusnetversion.php | 2 +- actions/apiusershow.php | 2 +- plugins/Realtime/RealtimePlugin.php | 4 ++-- 10 files changed, 13 insertions(+), 12 deletions(-) (limited to 'plugins') diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php index 104b9867f..8b976bbf3 100644 --- a/actions/apiaccountverifycredentials.php +++ b/actions/apiaccountverifycredentials.php @@ -67,8 +67,9 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction case 'json': $args['id'] = $this->auth_user->id; $action_obj = new ApiUserShowAction(); - $action_obj->prepare($args); - $action_obj->handle($args); + if ($action_obj->prepare($args)) { + $action_obj->handle($args); + } break; default: header('Content-Type: text/html; charset=utf-8'); diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php index 1ebfc9e8f..df9f0c949 100644 --- a/actions/apifriendshipsexists.php +++ b/actions/apifriendshipsexists.php @@ -44,7 +44,7 @@ require_once INSTALLDIR.'/lib/api.php'; * @link http://status.net/ */ -class ApiFriendshipsExistsAction extends TwitterApiAction +class ApiFriendshipsExistsAction extends ApiAction { var $user_a = null; var $user_b = null; diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php index a0f04ed76..070927b1e 100644 --- a/actions/apigrouplistall.php +++ b/actions/apigrouplistall.php @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiGroupListAllAction extends TwitterApiAction +class ApiGroupListAllAction extends ApiAction { var $page = null; var $count = null; diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php index da510ff26..0e9510d71 100644 --- a/actions/apigroupmembership.php +++ b/actions/apigroupmembership.php @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiGroupMembershipAction extends TwitterApiAction +class ApiGroupMembershipAction extends ApiAction { var $page = null; var $count = null; diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php index cdaf707aa..262fe6718 100644 --- a/actions/apigroupshow.php +++ b/actions/apigroupshow.php @@ -43,7 +43,7 @@ require_once INSTALLDIR.'/lib/api.php'; * @link http://status.net/ */ -class ApiGroupShowAction extends TwitterApiAction +class ApiGroupShowAction extends ApiAction { var $group = null; diff --git a/actions/apihelptest.php b/actions/apihelptest.php index ac1bb1a5b..cd5b86cf9 100644 --- a/actions/apihelptest.php +++ b/actions/apihelptest.php @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiHelpTestAction extends TwitterApiAction +class ApiHelpTestAction extends ApiAction { /** diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index 6fb65331f..356942a85 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -47,7 +47,7 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiStatusnetConfigAction extends TwitterApiAction +class ApiStatusnetConfigAction extends ApiAction { var $keys = array( 'site' => array('name', 'server', 'theme', 'path', 'fancy', 'language', diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php index 2cd99edb7..fb632fd93 100644 --- a/actions/apistatusnetversion.php +++ b/actions/apistatusnetversion.php @@ -46,7 +46,7 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiStatusnetVersionAction extends TwitterApiAction +class ApiStatusnetVersionAction extends ApiAction { /** * Take arguments for running diff --git a/actions/apiusershow.php b/actions/apiusershow.php index 1cd0cdfd5..442efc194 100644 --- a/actions/apiusershow.php +++ b/actions/apiusershow.php @@ -44,7 +44,7 @@ require_once INSTALLDIR.'/lib/api.php'; * @link http://status.net/ */ -class ApiUserShowAction extends TwitterApiAction +class ApiUserShowAction extends ApiAction { var $user = null; diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 31e75221b..9a750bbc8 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -240,11 +240,11 @@ class RealtimePlugin extends Plugin // 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! + // the ApiAction method. Don't do this unless you're me! require_once(INSTALLDIR.'/lib/api.php'); - $act = new TwitterApiAction('/dev/null'); + $act = new ApiAction('/dev/null'); $arr = $act->twitter_status_array($notice, true); $arr['url'] = $notice->bestUrl(); -- cgit v1.2.3-54-g00ecf From 061af8fa06ccb98f667d1ee670da2dbb179d8b0e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 9 Oct 2009 17:53:35 -0700 Subject: CamelCase all function names in the API code --- actions/apiaccountratelimitstatus.php | 4 +- actions/apiblockcreate.php | 6 +- actions/apiblockdestroy.php | 6 +- actions/apidirectmessage.php | 20 +-- actions/apifavoritecreate.php | 2 +- actions/apifavoritedestroy.php | 2 +- actions/apifriendshipscreate.php | 6 +- actions/apifriendshipsdestroy.php | 6 +- actions/apifriendshipsexists.php | 8 +- actions/apifriendshipsshow.php | 12 +- actions/apigroupismember.php | 10 +- actions/apigroupjoin.php | 2 +- actions/apigroupleave.php | 2 +- actions/apigrouplist.php | 8 +- actions/apigrouplistall.php | 8 +- actions/apigroupmembership.php | 4 +- actions/apigroupshow.php | 2 +- actions/apihelptest.php | 8 +- actions/apistatusesdestroy.php | 4 +- actions/apistatusesshow.php | 2 +- actions/apistatusesupdate.php | 2 +- actions/apistatusnetconfig.php | 10 +- actions/apistatusnetversion.php | 8 +- actions/apisubscriptions.php | 8 +- actions/apitimelinefavorites.php | 8 +- actions/apitimelinefriends.php | 8 +- actions/apitimelinegroup.php | 8 +- actions/apitimelinementions.php | 8 +- actions/apitimelinepublic.php | 8 +- actions/apitimelinetag.php | 8 +- actions/apitimelineuser.php | 8 +- actions/apiusershow.php | 14 +- actions/twitapisearchatom.php | 2 +- actions/twitapisearchjson.php | 4 +- lib/api.php | 295 ++++++++++++++++------------------ lib/twitter.php | 4 +- plugins/Realtime/RealtimePlugin.php | 2 +- 37 files changed, 255 insertions(+), 272 deletions(-) (limited to 'plugins') diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php index b823e1cd2..af86dae6a 100644 --- a/actions/apiaccountratelimitstatus.php +++ b/actions/apiaccountratelimitstatus.php @@ -73,7 +73,7 @@ class ApiAccountRateLimitStatusAction extends ApiBareAuthAction $reset = new DateTime(); $reset->modify('+1 hour'); - $this->init_document($this->format); + $this->initDocument($this->format); if ($this->format == 'xml') { $this->elementStart('hash'); @@ -101,7 +101,7 @@ class ApiAccountRateLimitStatusAction extends ApiBareAuthAction print json_encode($out); } - $this->end_document($this->format); + $this->endDocument($this->format); } } diff --git a/actions/apiblockcreate.php b/actions/apiblockcreate.php index 6dd28dd5e..e003e5ee9 100644 --- a/actions/apiblockcreate.php +++ b/actions/apiblockcreate.php @@ -100,9 +100,9 @@ class ApiBlockCreateAction extends ApiAuthAction if ($this->user->hasBlocked($this->other) || $this->user->block($this->other) ) { - $this->init_document($this->format); - $this->show_profile($this->other, $this->format); - $this->end_document($this->format); + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); } else { $this->serverError(_('Block user failed.'), 500, $this->format); } diff --git a/actions/apiblockdestroy.php b/actions/apiblockdestroy.php index a869dfe46..470b60ea5 100644 --- a/actions/apiblockdestroy.php +++ b/actions/apiblockdestroy.php @@ -99,9 +99,9 @@ class ApiBlockDestroyAction extends ApiAuthAction if (!$this->user->hasBlocked($this->other) || $this->user->unblock($this->other) ) { - $this->init_document($this->format); - $this->show_profile($this->other, $this->format); - $this->end_document($this->format); + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); } else { $this->serverError(_('Unblock user failed.')); } diff --git a/actions/apidirectmessage.php b/actions/apidirectmessage.php index f0013c54c..4e55886d9 100644 --- a/actions/apidirectmessage.php +++ b/actions/apidirectmessage.php @@ -230,7 +230,7 @@ class ApiDirectMessageAction extends ApiAuthAction function showXmlDirectMessages() { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('direct-messages', array('type' => 'array')); foreach ($this->messages as $m) { @@ -239,7 +239,7 @@ class ApiDirectMessageAction extends ApiAuthAction } $this->elementEnd('direct-messages'); - $this->end_document('xml'); + $this->endDocument('xml'); } /** @@ -250,7 +250,7 @@ class ApiDirectMessageAction extends ApiAuthAction function showJsonDirectMessages() { - $this->init_document('json'); + $this->initDocument('json'); $dmsgs = array(); @@ -259,8 +259,8 @@ class ApiDirectMessageAction extends ApiAuthAction array_push($dmsgs, $dm_array); } - $this->show_json_objects($dmsgs); - $this->end_document('json'); + $this->showJsonObjects($dmsgs); + $this->endDocument('json'); } /** @@ -271,7 +271,7 @@ class ApiDirectMessageAction extends ApiAuthAction function showRssDirectMessages() { - $this->init_document('rss'); + $this->initDocument('rss'); $this->element('title', null, $this->title); @@ -292,10 +292,10 @@ class ApiDirectMessageAction extends ApiAuthAction foreach ($this->messages as $m) { $entry = $this->rssDirectMessageArray($m); - $this->show_twitter_rss_item($entry); + $this->showTwitterRssItem($entry); } - $this->end_twitter_rss(); + $this->endTwitterRss(); } /** @@ -306,7 +306,7 @@ class ApiDirectMessageAction extends ApiAuthAction function showAtomDirectMessages() { - $this->init_document('atom'); + $this->initDocument('atom'); $this->element('title', null, $this->title); $this->element('id', null, $this->id); @@ -334,7 +334,7 @@ class ApiDirectMessageAction extends ApiAuthAction $this->showTwitterAtomEntry($entry); } - $this->end_document('atom'); + $this->endDocument('atom'); } /** diff --git a/actions/apifavoritecreate.php b/actions/apifavoritecreate.php index db001561e..1b05eb95c 100644 --- a/actions/apifavoritecreate.php +++ b/actions/apifavoritecreate.php @@ -135,7 +135,7 @@ class ApiFavoriteCreateAction extends ApiAuthAction $this->user->blowFavesCache(); if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } diff --git a/actions/apifavoritedestroy.php b/actions/apifavoritedestroy.php index 3640459f9..aac866d7e 100644 --- a/actions/apifavoritedestroy.php +++ b/actions/apifavoritedestroy.php @@ -138,7 +138,7 @@ class ApiFavoriteDestroyAction extends ApiAuthAction $this->user->blowFavesCache(); if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } diff --git a/actions/apifriendshipscreate.php b/actions/apifriendshipscreate.php index 85eaf3a29..6c44d7961 100644 --- a/actions/apifriendshipscreate.php +++ b/actions/apifriendshipscreate.php @@ -126,9 +126,9 @@ class ApiFriendshipsCreateAction extends ApiAuthAction return; } - $this->init_document($this->format); - $this->show_profile($this->other, $this->format); - $this->end_document($this->format); + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); } } diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php index 274378051..a15f202aa 100644 --- a/actions/apifriendshipsdestroy.php +++ b/actions/apifriendshipsdestroy.php @@ -128,9 +128,9 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction return; } - $this->init_document($this->format); - $this->show_profile($this->other, $this->format); - $this->end_document($this->format); + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); } } diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php index df9f0c949..93be5f84e 100644 --- a/actions/apifriendshipsexists.php +++ b/actions/apifriendshipsexists.php @@ -107,14 +107,14 @@ class ApiFriendshipsExistsAction extends ApiAction switch ($this->format) { case 'xml': - $this->init_document('xml'); + $this->initDocument('xml'); $this->element('friends', null, $result); - $this->end_document('xml'); + $this->endDocument('xml'); break; case 'json': - $this->init_document('json'); + $this->initDocument('json'); print json_encode($result); - $this->end_document('json'); + $this->endDocument('json'); break; default: break; diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php index 0ae6a7b82..8850496c7 100644 --- a/actions/apifriendshipsshow.php +++ b/actions/apifriendshipsshow.php @@ -143,18 +143,18 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction return; } - $result = $this->twitter_relationship_array($this->source, $this->target); + $result = $this->twitterRelationshipArray($this->source, $this->target); switch ($this->format) { case 'xml': - $this->init_document('xml'); - $this->show_twitter_xml_relationship($result[relationship]); - $this->end_document('xml'); + $this->initDocument('xml'); + $this->showTwitterXmlRelationship($result[relationship]); + $this->endDocument('xml'); break; case 'json': - $this->init_document('json'); + $this->initDocument('json'); print json_encode($result); - $this->end_document('json'); + $this->endDocument('json'); break; default: break; diff --git a/actions/apigroupismember.php b/actions/apigroupismember.php index 359b7ca4f..6cf327012 100644 --- a/actions/apigroupismember.php +++ b/actions/apigroupismember.php @@ -95,14 +95,14 @@ class ApiGroupIsMemberAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->init_document('xml'); + $this->initDocument('xml'); $this->element('is_member', null, $is_member); - $this->end_document('xml'); + $this->endDocument('xml'); break; case 'json': - $this->init_document('json'); - $this->show_json_objects(array('is_member' => $is_member)); - $this->end_document('json'); + $this->initDocument('json'); + $this->showJsonObjects(array('is_member' => $is_member)); + $this->endDocument('json'); break; default: $this->clientError( diff --git a/actions/apigroupjoin.php b/actions/apigroupjoin.php index e51842bcb..f930aa519 100644 --- a/actions/apigroupjoin.php +++ b/actions/apigroupjoin.php @@ -143,7 +143,7 @@ class ApiGroupJoinAction extends ApiAuthAction $this->show_single_xml_group($this->group); break; case 'json': - $this->show_single_json_group($this->group); + $this->showSingleJsonGroup($this->group); break; default: $this->clientError( diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php index 332bd7b7b..4e3192ac0 100644 --- a/actions/apigroupleave.php +++ b/actions/apigroupleave.php @@ -129,7 +129,7 @@ class ApiGroupLeaveAction extends ApiAuthAction $this->show_single_xml_group($this->group); break; case 'json': - $this->show_single_json_group($this->group); + $this->showSingleJsonGroup($this->group); break; default: $this->clientError( diff --git a/actions/apigrouplist.php b/actions/apigrouplist.php index 7a5aab72e..1fc31831a 100644 --- a/actions/apigrouplist.php +++ b/actions/apigrouplist.php @@ -101,15 +101,15 @@ class ApiGroupListAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->show_xml_groups($this->groups); + $this->showXmlGroups($this->groups); break; case 'rss': - $this->show_rss_groups($this->groups, $title, $link, $subtitle); + $this->showRssGroups($this->groups, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . 'api/statusnet/groups/list/' . $this->user->id . '.atom'; - $this->show_atom_groups( + $this->showAtomGroups( $this->groups, $title, $id, @@ -119,7 +119,7 @@ class ApiGroupListAction extends ApiBareAuthAction ); break; case 'json': - $this->show_json_groups($this->groups); + $this->showJsonGroups($this->groups); break; default: $this->clientError( diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php index 3e236816a..ef96a08bd 100644 --- a/actions/apigrouplistall.php +++ b/actions/apigrouplistall.php @@ -89,15 +89,15 @@ class ApiGroupListAllAction extends ApiAction switch($this->format) { case 'xml': - $this->show_xml_groups($this->groups); + $this->showXmlGroups($this->groups); break; case 'rss': - $this->show_rss_groups($this->groups, $title, $link, $subtitle); + $this->showRssGroups($this->groups, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . 'api/statusnet/groups/list_all.atom'; - $this->show_atom_groups( + $this->showAtomGroups( $this->groups, $title, $id, @@ -107,7 +107,7 @@ class ApiGroupListAllAction extends ApiAction ); break; case 'json': - $this->show_json_groups($this->groups); + $this->showJsonGroups($this->groups); break; default: $this->clientError( diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php index 0bc846d60..27f77029e 100644 --- a/actions/apigroupmembership.php +++ b/actions/apigroupmembership.php @@ -85,10 +85,10 @@ class ApiGroupMembershipAction extends ApiAction switch($this->format) { case 'xml': - $this->show_twitter_xml_users($this->profiles); + $this->showTwitterXmlUsers($this->profiles); break; case 'json': - $this->show_json_users($this->profiles); + $this->showJsonUsers($this->profiles); break; default: $this->clientError( diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php index 262fe6718..8969ae194 100644 --- a/actions/apigroupshow.php +++ b/actions/apigroupshow.php @@ -93,7 +93,7 @@ class ApiGroupShowAction extends ApiAction $this->show_single_xml_group($this->group); break; case 'json': - $this->show_single_json_group($this->group); + $this->showSingleJsonGroup($this->group); break; default: $this->clientError(_('API method not found!'), 404, $this->format); diff --git a/actions/apihelptest.php b/actions/apihelptest.php index cd5b86cf9..4691cbf99 100644 --- a/actions/apihelptest.php +++ b/actions/apihelptest.php @@ -74,13 +74,13 @@ class ApiHelpTestAction extends ApiAction parent::handle($args); if ($this->format == 'xml') { - $this->init_document('xml'); + $this->initDocument('xml'); $this->element('ok', null, 'true'); - $this->end_document('xml'); + $this->endDocument('xml'); } elseif ($this->format == 'json') { - $this->init_document('json'); + $this->initDocument('json'); print '"ok"'; - $this->end_document('json'); + $this->endDocument('json'); } else { $this->clientError( _('API method not found!'), diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php index 7680f96dc..74a1310a2 100644 --- a/actions/apistatusesdestroy.php +++ b/actions/apistatusesdestroy.php @@ -112,7 +112,7 @@ class ApiStatusesDestroyAction extends ApiAuthAction $this->notice->delete(); if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } @@ -134,7 +134,7 @@ class ApiStatusesDestroyAction extends ApiAuthAction { if (!empty($this->notice)) { if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php index 0096cfe6b..5d32a0bfc 100644 --- a/actions/apistatusesshow.php +++ b/actions/apistatusesshow.php @@ -110,7 +110,7 @@ class ApiStatusesShowAction extends ApiAction { if (!empty($this->notice)) { if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 7d6a574ef..479654be8 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -201,7 +201,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction { if (!empty($this->notice)) { if ($this->format == 'xml') { - $this->show_single_xml_status($this->notice); + $this->showSingleXmlStatus($this->notice); } elseif ($this->format == 'json') { $this->show_single_json_status($this->notice); } diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index 356942a85..a93570698 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -88,7 +88,7 @@ class ApiStatusnetConfigAction extends ApiAction switch ($this->format) { case 'xml': - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('config'); // XXX: check that all sections and settings are legal XML elements @@ -111,7 +111,7 @@ class ApiStatusnetConfigAction extends ApiAction $this->elementEnd($section); } $this->elementEnd('config'); - $this->end_document('xml'); + $this->endDocument('xml'); break; case 'json': $result = array(); @@ -122,9 +122,9 @@ class ApiStatusnetConfigAction extends ApiAction = common_config($section, $setting); } } - $this->init_document('json'); - $this->show_json_objects($result); - $this->end_document('json'); + $this->initDocument('json'); + $this->showJsonObjects($result); + $this->endDocument('json'); break; default: $this->clientError( diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php index fb632fd93..6af9bdd1a 100644 --- a/actions/apistatusnetversion.php +++ b/actions/apistatusnetversion.php @@ -77,14 +77,14 @@ class ApiStatusnetVersionAction extends ApiAction switch ($this->format) { case 'xml': - $this->init_document('xml'); + $this->initDocument('xml'); $this->element('version', null, STATUSNET_VERSION); - $this->end_document('xml'); + $this->endDocument('xml'); break; case 'json': - $this->init_document('json'); + $this->initDocument('json'); print '"'.STATUSNET_VERSION.'"'; - $this->end_document('json'); + $this->endDocument('json'); break; default: $this->clientError( diff --git a/actions/apisubscriptions.php b/actions/apisubscriptions.php index 1332fd71c..a952e20b8 100644 --- a/actions/apisubscriptions.php +++ b/actions/apisubscriptions.php @@ -108,7 +108,7 @@ class ApiSubscriptionsAction extends ApiBareAuthAction return; } - $this->init_document($this->format); + $this->initDocument($this->format); if (isset($this->ids_only)) { $this->showIds(); @@ -116,7 +116,7 @@ class ApiSubscriptionsAction extends ApiBareAuthAction $this->showProfiles(isset($this->lite) ? false : true); } - $this->end_document($this->format); + $this->endDocument($this->format); } /** @@ -204,7 +204,7 @@ class ApiSubscriptionsAction extends ApiBareAuthAction case 'xml': $this->elementStart('users', array('type' => 'array')); foreach ($this->profiles as $profile) { - $this->show_profile( + $this->showProfile( $profile, $this->format, null, @@ -216,7 +216,7 @@ class ApiSubscriptionsAction extends ApiBareAuthAction case 'json': $arrays = array(); foreach ($this->profiles as $profile) { - $arrays[] = $this->twitter_user_array( + $arrays[] = $this->twitterUserArray( $profile, $include_statuses ); diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index d35e05183..c85e56264 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -121,21 +121,21 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . ltrim($_SERVER['QUERY_STRING'], 'p='); - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, null, $selfuri ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index 544824078..90f3b3c06 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -112,10 +112,10 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': @@ -130,13 +130,13 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction 'api/statuses/friends_timeline.atom'; } - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, null, $selfuri ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index 6f2043fb6..2a6f35d72 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -108,16 +108,16 @@ class ApiTimelineGroupAction extends ApiAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . 'api/statusnet/groups/timeline/' . $this->group->nickname . '.atom'; - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, @@ -128,7 +128,7 @@ class ApiTimelineGroupAction extends ApiAction ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError( diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php index 60669d9ba..c25fb0a0e 100644 --- a/actions/apitimelinementions.php +++ b/actions/apitimelinementions.php @@ -118,21 +118,21 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . ltrim($_SERVER['QUERY_STRING'], 'p='); - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, null, $selfuri ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index b992136d1..4bff4adb6 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -99,20 +99,20 @@ class ApiTimelinePublicAction extends ApiAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, null, $selfuri ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 53e79e4b9..cf211b173 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -107,16 +107,16 @@ class ApiTimelineTagAction extends ApiAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + $this->showRssTimeline($this->notices, $title, $link, $subtitle); break; case 'atom': $selfuri = common_root_url() . 'api/statusnet/tags/timeline/' . $this->tag . '.atom'; - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, @@ -127,7 +127,7 @@ class ApiTimelineTagAction extends ApiAction ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 652a1b0d9..e7fac3e7a 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -123,10 +123,10 @@ class ApiTimelineUserAction extends ApiBareAuthAction switch($this->format) { case 'xml': - $this->show_xml_timeline($this->notices); + $this->showXmlTimeline($this->notices); break; case 'rss': - $this->show_rss_timeline( + $this->showRssTimeline( $this->notices, $title, $link, $subtitle, $suplink ); @@ -140,13 +140,13 @@ class ApiTimelineUserAction extends ApiBareAuthAction $selfuri = common_root_url() . 'api/statuses/user_timeline.atom'; } - $this->show_atom_timeline( + $this->showAtomTimeline( $this->notices, $title, $id, $link, $subtitle, $suplink, $selfuri ); break; case 'json': - $this->show_json_timeline($this->notices); + $this->showJsonTimeline($this->notices); break; default: $this->clientError(_('API method not found!'), $code = 404); diff --git a/actions/apiusershow.php b/actions/apiusershow.php index 442efc194..d07040a43 100644 --- a/actions/apiusershow.php +++ b/actions/apiusershow.php @@ -105,16 +105,16 @@ class ApiUserShowAction extends ApiAction return; } - $twitter_user = $this->twitter_user_array($this->user->getProfile(), true); + $twitter_user = $this->twitterUserArray($this->user->getProfile(), true); if ($this->format == 'xml') { - $this->init_document('xml'); - $this->show_twitter_xml_user($twitter_user); - $this->end_document('xml'); + $this->initDocument('xml'); + $this->showTwitterXmlUser($twitter_user); + $this->endDocument('xml'); } elseif ($this->format == 'json') { - $this->init_document('json'); - $this->show_json_objects($twitter_user); - $this->end_document('json'); + $this->initDocument('json'); + $this->showJsonObjects($twitter_user); + $this->endDocument('json'); } } diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 0ef9d2826..7d618c471 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -340,7 +340,7 @@ class TwitapisearchatomAction extends ApiAction // TODO: Here is where we'd put in a link to an atom feed for threads $this->element("twitter:source", null, - htmlentities($this->source_link($notice->source))); + htmlentities($this->sourceLink($notice->source))); $this->elementStart('author'); diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php index 5abff6496..c7fa741a0 100644 --- a/actions/twitapisearchjson.php +++ b/actions/twitapisearchjson.php @@ -134,9 +134,9 @@ class TwitapisearchjsonAction extends ApiAction $results = new JSONSearchResultsList($notice, $q, $this->rpp, $this->page); - $this->init_document('json'); + $this->initDocument('json'); $results->show(); - $this->end_document('json'); + $this->endDocument('json'); } /** diff --git a/lib/api.php b/lib/api.php index 95a0779ad..0801d2823 100644 --- a/lib/api.php +++ b/lib/api.php @@ -106,7 +106,7 @@ class ApiAction extends Action return parent::element($tag, $attrs, $content); } - function twitter_user_array($profile, $get_notice=false) + function twitterUserArray($profile, $get_notice=false) { $twitter_user = array(); @@ -133,7 +133,7 @@ class ApiAction extends Action $twitter_user['friends_count'] = $profile->subscriptionCount(); - $twitter_user['created_at'] = $this->date_twitter($profile->created); + $twitter_user['created_at'] = $this->dateTwitter($profile->created); $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! @@ -179,24 +179,24 @@ class ApiAction extends Action $notice = $profile->getCurrentNotice(); if ($notice) { # don't get user! - $twitter_user['status'] = $this->twitter_status_array($notice, false); + $twitter_user['status'] = $this->twitterStatusArray($notice, false); } } return $twitter_user; } - function twitter_status_array($notice, $include_user=true) + function twitterStatusArray($notice, $include_user=true) { $profile = $notice->getProfile(); $twitter_status = array(); $twitter_status['text'] = $notice->content; $twitter_status['truncated'] = false; # Not possible on StatusNet - $twitter_status['created_at'] = $this->date_twitter($notice->created); + $twitter_status['created_at'] = $this->dateTwitter($notice->created); $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : null; - $twitter_status['source'] = $this->source_link($notice->source); + $twitter_status['source'] = $this->sourceLink($notice->source); $twitter_status['id'] = intval($notice->id); $replier_profile = null; @@ -239,14 +239,14 @@ class ApiAction extends Action if ($include_user) { # Don't get notice (recursive!) - $twitter_user = $this->twitter_user_array($profile, false); + $twitter_user = $this->twitterUserArray($profile, false); $twitter_status['user'] = $twitter_user; } return $twitter_status; } - function twitter_group_array($group) + function twitterGroupArray($group) { $twitter_group=array(); $twitter_group['id']=$group->id; @@ -261,12 +261,12 @@ class ApiAction extends Action $twitter_group['homepage']=$group->homepage; $twitter_group['description']=$group->description; $twitter_group['location']=$group->location; - $twitter_group['created']=$this->date_twitter($group->created); - $twitter_group['modified']=$this->date_twitter($group->modified); + $twitter_group['created']=$this->dateTwitter($group->created); + $twitter_group['modified']=$this->dateTwitter($group->modified); return $twitter_group; } - function twitter_rss_group_array($group) + function twitterRssGroupArray($group) { $entry = array(); $entry['content']=$group->description; @@ -284,7 +284,7 @@ class ApiAction extends Action return $entry; } - function twitter_rss_entry_array($notice) + function twitterRssEntryArray($notice) { $profile = $notice->getProfile(); $entry = array(); @@ -341,19 +341,19 @@ class ApiAction extends Action } - function twitter_relationship_array($source, $target) + function twitterRelationshipArray($source, $target) { $relationship = array(); $relationship['source'] = - $this->relationship_details_array($source, $target); + $this->relationshipDetailsArray($source, $target); $relationship['target'] = - $this->relationship_details_array($target, $source); + $this->relationshipDetailsArray($target, $source); return array('relationship' => $relationship); } - function relationship_details_array($source, $target) + function relationshipDetailsArray($source, $target) { $details = array(); @@ -380,14 +380,14 @@ class ApiAction extends Action return $details; } - function show_twitter_xml_relationship($relationship) + function showTwitterXmlRelationship($relationship) { $this->elementStart('relationship'); foreach($relationship as $element => $value) { if ($element == 'source' || $element == 'target') { $this->elementStart($element); - $this->show_xml_relationship_details($value); + $this->showXmlRelationshipDetails($value); $this->elementEnd($element); } } @@ -395,26 +395,26 @@ class ApiAction extends Action $this->elementEnd('relationship'); } - function show_xml_relationship_details($details) + function showXmlRelationshipDetails($details) { foreach($details as $element => $value) { $this->element($element, null, $value); } } - function show_twitter_xml_status($twitter_status) + function showTwitterXmlStatus($twitter_status) { $this->elementStart('status'); foreach($twitter_status as $element => $value) { switch ($element) { case 'user': - $this->show_twitter_xml_user($twitter_status['user']); + $this->showTwitterXmlUser($twitter_status['user']); break; case 'text': $this->element($element, null, common_xml_safe_str($value)); break; case 'attachments': - $this->show_xml_attachments($twitter_status['attachments']); + $this->showXmlAttachments($twitter_status['attachments']); break; default: $this->element($element, null, $value); @@ -423,7 +423,7 @@ class ApiAction extends Action $this->elementEnd('status'); } - function show_twitter_xml_group($twitter_group) + function showTwitterXmlGroup($twitter_group) { $this->elementStart('group'); foreach($twitter_group as $element => $value) { @@ -432,12 +432,12 @@ class ApiAction extends Action $this->elementEnd('group'); } - function show_twitter_xml_user($twitter_user, $role='user') + function showTwitterXmlUser($twitter_user, $role='user') { $this->elementStart($role); foreach($twitter_user as $element => $value) { if ($element == 'status') { - $this->show_twitter_xml_status($twitter_user['status']); + $this->showTwitterXmlStatus($twitter_user['status']); } else { $this->element($element, null, $value); } @@ -445,7 +445,7 @@ class ApiAction extends Action $this->elementEnd($role); } - function show_xml_attachments($attachments) { + function showXmlAttachments($attachments) { if (!empty($attachments)) { $this->elementStart('attachments', array('type' => 'array')); foreach ($attachments as $attachment) { @@ -459,7 +459,7 @@ class ApiAction extends Action } } - function show_twitter_rss_item($entry) + function showTwitterRssItem($entry) { $this->elementStart('item'); $this->element('title', null, $entry['title']); @@ -483,59 +483,59 @@ class ApiAction extends Action $this->elementEnd('item'); } - function show_json_objects($objects) + function showJsonObjects($objects) { print(json_encode($objects)); } - function show_single_xml_status($notice) + function showSingleXmlStatus($notice) { - $this->init_document('xml'); - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); - $this->end_document('xml'); + $this->initDocument('xml'); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); + $this->endDocument('xml'); } function show_single_json_status($notice) { - $this->init_document('json'); - $status = $this->twitter_status_array($notice); - $this->show_json_objects($status); - $this->end_document('json'); + $this->initDocument('json'); + $status = $this->twitterStatusArray($notice); + $this->showJsonObjects($status); + $this->endDocument('json'); } - function show_xml_timeline($notice) + function showXmlTimeline($notice) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('statuses', array('type' => 'array')); if (is_array($notice)) { foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); - $this->show_twitter_xml_status($twitter_status); + $twitter_status = $this->twitterStatusArray($n); + $this->showTwitterXmlStatus($twitter_status); } } else { while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); } } $this->elementEnd('statuses'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null) + function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) { - $this->init_document('rss'); + $this->initDocument('rss'); $this->element('title', null, $title); $this->element('link', null, $link); if (!is_null($suplink)) { - # For FriendFeed's SUP protocol + // For FriendFeed's SUP protocol $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', 'rel' => 'http://api.friendfeed.com/2008/03#sup', 'href' => $suplink, @@ -547,23 +547,23 @@ class ApiAction extends Action if (is_array($notice)) { foreach ($notice as $n) { - $entry = $this->twitter_rss_entry_array($n); - $this->show_twitter_rss_item($entry); + $entry = $this->twitterRssEntryArray($n); + $this->showTwitterRssItem($entry); } } else { while ($notice->fetch()) { - $entry = $this->twitter_rss_entry_array($notice); - $this->show_twitter_rss_item($entry); + $entry = $this->twitterRssEntryArray($notice); + $this->showTwitterRssItem($entry); } } - $this->end_twitter_rss(); + $this->endTwitterRss(); } - function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) + function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) { - $this->init_document('atom'); + $this->initDocument('atom'); $this->element('title', null, $title); $this->element('id', null, $id); @@ -594,14 +594,14 @@ class ApiAction extends Action } } - $this->end_document('atom'); + $this->endDocument('atom'); } - function show_rss_groups($group, $title, $link, $subtitle) + function showRssGroups($group, $title, $link, $subtitle) { - $this->init_document('rss'); + $this->initDocument('rss'); $this->element('title', null, $title); $this->element('link', null, $link); @@ -611,17 +611,17 @@ class ApiAction extends Action if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_rss_group_array($g); - $this->show_twitter_rss_item($twitter_group); + $twitter_group = $this->twitterRssGroupArray($g); + $this->showTwitterRssItem($twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_rss_group_array($group); - $this->show_twitter_rss_item($twitter_group); + $twitter_group = $this->twitterRssGroupArray($group); + $this->showTwitterRssItem($twitter_group); } } - $this->end_twitter_rss(); + $this->endTwitterRss(); } @@ -655,7 +655,7 @@ class ApiAction extends Action switch ($element) { case 'sender': case 'recipient': - $this->show_twitter_xml_user($value, $element); + $this->showTwitterXmlUser($value, $element); break; case 'text': $this->element($element, null, common_xml_safe_str($value)); @@ -679,11 +679,11 @@ class ApiAction extends Action $dmsg['sender_id'] = $message->from_profile; $dmsg['text'] = trim($message->content); $dmsg['recipient_id'] = $message->to_profile; - $dmsg['created_at'] = $this->date_twitter($message->created); + $dmsg['created_at'] = $this->dateTwitter($message->created); $dmsg['sender_screen_name'] = $from_profile->nickname; $dmsg['recipient_screen_name'] = $to_profile->nickname; - $dmsg['sender'] = $this->twitter_user_array($from_profile, false); - $dmsg['recipient'] = $this->twitter_user_array($to_profile, false); + $dmsg['sender'] = $this->twitterUserArray($from_profile, false); + $dmsg['recipient'] = $this->twitterUserArray($to_profile, false); return $dmsg; } @@ -725,24 +725,24 @@ class ApiAction extends Action function showSingleXmlDirectMessage($message) { - $this->init_document('xml'); + $this->initDocument('xml'); $dmsg = $this->directMessageArray($message); $this->showXmlDirectMessage($dmsg); - $this->end_document('xml'); + $this->endDocument('xml'); } function showSingleJsonDirectMessage($message) { - $this->init_document('json'); + $this->initDocument('json'); $dmsg = $this->directMessageArray($message); - $this->show_json_objects($dmsg); - $this->end_document('json'); + $this->showJsonObjects($dmsg); + $this->endDocument('json'); } - function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) { - $this->init_document('atom'); + $this->initDocument('atom'); $this->element('title', null, $title); $this->element('id', null, $id); @@ -766,143 +766,143 @@ class ApiAction extends Action } } - $this->end_document('atom'); + $this->endDocument('atom'); } - function show_json_timeline($notice) + function showJsonTimeline($notice) { - $this->init_document('json'); + $this->initDocument('json'); $statuses = array(); if (is_array($notice)) { foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); + $twitter_status = $this->twitterStatusArray($n); array_push($statuses, $twitter_status); } } else { while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); + $twitter_status = $this->twitterStatusArray($notice); array_push($statuses, $twitter_status); } } - $this->show_json_objects($statuses); + $this->showJsonObjects($statuses); - $this->end_document('json'); + $this->endDocument('json'); } - function show_json_groups($group) + function showJsonGroups($group) { - $this->init_document('json'); + $this->initDocument('json'); $groups = array(); if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); + $twitter_group = $this->twitterGroupArray($g); array_push($groups, $twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); + $twitter_group = $this->twitterGroupArray($group); array_push($groups, $twitter_group); } } - $this->show_json_objects($groups); + $this->showJsonObjects($groups); - $this->end_document('json'); + $this->endDocument('json'); } - function show_xml_groups($group) + function showXmlGroups($group) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('groups', array('type' => 'array')); if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); - $this->show_twitter_xml_group($twitter_group); + $twitter_group = $this->twitterGroupArray($g); + $this->showTwitterXmlGroup($twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); } } $this->elementEnd('groups'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_twitter_xml_users($user) + function showTwitterXmlUsers($user) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('users', array('type' => 'array')); if (is_array($user)) { foreach ($user as $u) { - $twitter_user = $this->twitter_user_array($u); - $this->show_twitter_xml_user($twitter_user); + $twitter_user = $this->twitterUserArray($u); + $this->showTwitterXmlUser($twitter_user); } } else { while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); - $this->show_twitter_xml_user($twitter_user); + $twitter_user = $this->twitterUserArray($user); + $this->showTwitterXmlUser($twitter_user); } } $this->elementEnd('users'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_json_users($user) + function showJsonUsers($user) { - $this->init_document('json'); + $this->initDocument('json'); $users = array(); if (is_array($user)) { foreach ($user as $u) { - $twitter_user = $this->twitter_user_array($u); + $twitter_user = $this->twitterUserArray($u); array_push($users, $twitter_user); } } else { while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); + $twitter_user = $this->twitterUserArray($user); array_push($users, $twitter_user); } } - $this->show_json_objects($users); + $this->showJsonObjects($users); - $this->end_document('json'); + $this->endDocument('json'); } - function show_single_json_group($group) + function showSingleJsonGroup($group) { - $this->init_document('json'); - $twitter_group = $this->twitter_group_array($group); - $this->show_json_objects($twitter_group); - $this->end_document('json'); + $this->initDocument('json'); + $twitter_group = $this->twitterGroupArray($group); + $this->showJsonObjects($twitter_group); + $this->endDocument('json'); } function show_single_xml_group($group) { - $this->init_document('xml'); - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); - $this->end_document('xml'); + $this->initDocument('xml'); + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); + $this->endDocument('xml'); } - function date_twitter($dt) + function dateTwitter($dt) { $dateStr = date('d F Y H:i:s', strtotime($dt)); $d = new DateTime($dateStr, new DateTimeZone('UTC')); @@ -910,24 +910,7 @@ class ApiAction extends Action return $d->format('D M d H:i:s O Y'); } - // XXX: Candidate for a general utility method somewhere? - function count_subscriptions($profile) - { - - $count = 0; - $sub = new Subscription(); - $sub->subscribed = $profile->id; - - $count = $sub->find(); - - if ($count > 0) { - return $count - 1; - } else { - return 0; - } - } - - function init_document($type='xml') + function initDocument($type='xml') { switch ($type) { case 'xml': @@ -945,11 +928,11 @@ class ApiAction extends Action break; case 'rss': header("Content-Type: application/rss+xml; charset=utf-8"); - $this->init_twitter_rss(); + $this->initTwitterRss(); break; case 'atom': header('Content-Type: application/atom+xml; charset=utf-8'); - $this->init_twitter_atom(); + $this->initTwitterAtom(); break; default: $this->clientError(_('Not a supported data format.')); @@ -959,7 +942,7 @@ class ApiAction extends Action return; } - function end_document($type='xml') + function endDocument($type='xml') { switch ($type) { case 'xml': @@ -974,10 +957,10 @@ class ApiAction extends Action } break; case 'rss': - $this->end_twitter_rss(); + $this->endTwitterRss(); break; case 'atom': - $this->end_twitter_rss(); + $this->endTwitterRss(); break; default: $this->clientError(_('Not a supported data format.')); @@ -1001,17 +984,17 @@ class ApiAction extends Action header('HTTP/1.1 '.$code.' '.$status_string); if ($format == 'xml') { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('hash'); $this->element('error', null, $msg); $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); - $this->end_document('xml'); + $this->endDocument('xml'); } elseif ($format == 'json'){ - $this->init_document('json'); + $this->initDocument('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); - $this->end_document('json'); + $this->endDocument('json'); } else { // If user didn't request a useful format, throw a regular client error @@ -1034,21 +1017,21 @@ class ApiAction extends Action header('HTTP/1.1 '.$code.' '.$status_string); if ($content_type == 'xml') { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('hash'); $this->element('error', null, $msg); $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); - $this->end_document('xml'); + $this->endDocument('xml'); } else { - $this->init_document('json'); + $this->initDocument('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); - $this->end_document('json'); + $this->endDocument('json'); } } - function init_twitter_rss() + function initTwitterRss() { $this->startXML(); $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); @@ -1056,14 +1039,14 @@ class ApiAction extends Action Event::handle('StartApiRss', array($this)); } - function end_twitter_rss() + function endTwitterRss() { $this->elementEnd('channel'); $this->elementEnd('rss'); $this->endXML(); } - function init_twitter_atom() + function initTwitterAtom() { $this->startXML(); // FIXME: don't hardcode the language here! @@ -1073,21 +1056,21 @@ class ApiAction extends Action Event::handle('StartApiAtom', array($this)); } - function end_twitter_atom() + function endTwitterAtom() { $this->elementEnd('feed'); $this->endXML(); } - function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true) + function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true) { - $profile_array = $this->twitter_user_array($profile, $includeStatuses); + $profile_array = $this->twitterUserArray($profile, $includeStatuses); switch ($content_type) { case 'xml': - $this->show_twitter_xml_user($profile_array); + $this->showTwitterXmlUser($profile_array); break; case 'json': - $this->show_json_objects($profile_array); + $this->showJsonObjects($profile_array); break; default: $this->clientError(_('Not a supported data format.')); @@ -1155,7 +1138,7 @@ class ApiAction extends Action } } - function source_link($source) + function sourceLink($source) { $source_name = _($source); switch ($source) { diff --git a/lib/twitter.php b/lib/twitter.php index b49e2e119..afc3f55ba 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 -function update_twitter_user($twitter_id, $screen_name) +function updateTwitter_user($twitter_id, $screen_name) { $uri = 'http://twitter.com/' . $screen_name; $fuser = new Foreign_user(); @@ -115,7 +115,7 @@ function save_twitter_user($twitter_id, $screen_name) // Only update if Twitter screen name has changed if ($fuser->nickname != $screen_name) { - $result = update_twitter_user($twitter_id, $screen_name); + $result = updateTwitter_user($twitter_id, $screen_name); common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' . "$fuser->id to $screen_name, was $fuser->nickname"); diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 9a750bbc8..0c7c1240c 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -246,7 +246,7 @@ class RealtimePlugin extends Plugin $act = new ApiAction('/dev/null'); - $arr = $act->twitter_status_array($notice, true); + $arr = $act->twitterStatusArray($notice, true); $arr['url'] = $notice->bestUrl(); $arr['html'] = htmlspecialchars($notice->rendered); $arr['source'] = htmlspecialchars($arr['source']); -- cgit v1.2.3-54-g00ecf From 0947ca8b9331b03a172d86b37a511e50100fd0bb Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 12:29:19 +0000 Subject: Init mp-handheld stylesheet. For now, it is reusing mp-screen. Future updates will handle media queries --- plugins/MobileProfile/mp-handheld.css | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-handheld.css b/plugins/MobileProfile/mp-handheld.css index e69de29bb..e0ea823d5 100644 --- a/plugins/MobileProfile/mp-handheld.css +++ b/plugins/MobileProfile/mp-handheld.css @@ -0,0 +1 @@ +@import url(mp-screen.css); -- cgit v1.2.3-54-g00ecf From 7af1c83f4e8d66e2f70006bf6568c5b8410a2311 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 12:30:00 +0000 Subject: Fixed typo, added ipod --- plugins/MobileProfile/MobileProfilePlugin.php | 58 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index ecaea29c0..3785feeec 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -107,16 +107,54 @@ class MobileProfilePlugin extends WAP20Plugin // 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', - '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'); + array( + 'alcatel', + 'android', + 'audiovox', + 'au-mic,', + 'avantgo', + 'blackberry', + 'blazer', + 'cldc-', + 'danger', + 'epoc', + 'ericsson', + 'ericy', + 'iphone', + 'ipaq', + 'ipod', + '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']); -- cgit v1.2.3-54-g00ecf From 44a5cd28050fa732c32943a8193664f44a4cfad1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 12:46:53 +0000 Subject: Added function to set a list of features the mobile device supports and output accordingly e.g., if device is not known to have an open file system or unable to make use of input type="file" don't output that feature --- plugins/MobileProfile/MobileProfilePlugin.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 3785feeec..f36d97ad9 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -51,6 +51,7 @@ class MobileProfilePlugin extends WAP20Plugin { public $DTD = null; public $serveMobile = false; + public $mobileFeatures = array(); function __construct($DTD='http://www.wapforum.org/DTD/xhtml-mobile10.dtd') { @@ -106,8 +107,7 @@ class MobileProfilePlugin extends WAP20Plugin // 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( + $this->mobiledevices = array( 'alcatel', 'android', 'audiovox', @@ -160,6 +160,8 @@ class MobileProfilePlugin extends WAP20Plugin foreach($this->mobiledevices as $md) { if (strstr($httpuseragent, $md) !== false) { + setMobileFeatures($httpuseragent); + $this->serveMobile = true; break; } @@ -200,6 +202,24 @@ class MobileProfilePlugin extends WAP20Plugin } + function setMobileFeatures($useragent) + { + /* List of devices that support input type="file" */ + $mobiledeviceInputFileType = array( + 'nokia' + ); + + $this->mobileFeatures['inputfiletype'] = false; + + foreach($mobiledeviceInputFileType as $md) { + if (strstr($useragent, $md) !== false) { + $this->mobileFeatures['inputfiletype'] = true; + break; + } + } + } + + function onStartShowHeadElements($action) { if (!$action->serveMobile) { -- cgit v1.2.3-54-g00ecf From 410883d146282a0a416181f8ca0dae817597593b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 12:58:51 +0000 Subject: Ran phpcs and fixed a few errors and warnings --- plugins/MobileProfile/MobileProfilePlugin.php | 37 ++++++++++++--------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index f36d97ad9..dada1948f 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -49,13 +49,13 @@ require_once INSTALLDIR.'/plugins/Mobile/WAP20Plugin.php'; class MobileProfilePlugin extends WAP20Plugin { - public $DTD = null; - public $serveMobile = false; - public $mobileFeatures = array(); + public $DTD = null; + public $serveMobile = false; + public $mobileFeatures = array(); function __construct($DTD='http://www.wapforum.org/DTD/xhtml-mobile10.dtd') { - $this->DTD = $DTD; + $this->DTD = $DTD; parent::__construct(); } @@ -86,13 +86,11 @@ class MobileProfilePlugin extends WAP20Plugin $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])) { $this->serveMobile = true; - } - else { + } else { // If they like the WAP 2.0 mimetype, serve them MP if (strstr('application/vnd.wap.xhtml+xml', $type) !== false) { $this->serveMobile = true; - } - else { + } else { // If they are a mobile device that supports WAP 2.0, // serve them MP @@ -158,7 +156,7 @@ class MobileProfilePlugin extends WAP20Plugin $httpuseragent = strtolower($_SERVER['HTTP_USER_AGENT']); - foreach($this->mobiledevices as $md) { + foreach ($this->mobiledevices as $md) { if (strstr($httpuseragent, $md) !== false) { setMobileFeatures($httpuseragent); @@ -204,14 +202,13 @@ class MobileProfilePlugin extends WAP20Plugin function setMobileFeatures($useragent) { - /* List of devices that support input type="file" */ $mobiledeviceInputFileType = array( 'nokia' ); $this->mobileFeatures['inputfiletype'] = false; - foreach($mobiledeviceInputFileType as $md) { + foreach ($mobiledeviceInputFileType as $md) { if (strstr($useragent, $md) !== false) { $this->mobileFeatures['inputfiletype'] = true; break; @@ -243,8 +240,7 @@ class MobileProfilePlugin extends WAP20Plugin if (file_exists(theme_file('css/mp-screen.css'))) { $action->cssLink('css/mp-screen.css', null, 'screen'); - } - else { + } else { $action->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => common_path('plugins/MobileProfile/mp-screen.css') . '?version=' . STATUSNET_VERSION, @@ -253,8 +249,7 @@ class MobileProfilePlugin extends WAP20Plugin if (file_exists(theme_file('css/mp-handheld.css'))) { $action->cssLink('css/mp-handheld.css', null, 'handheld'); - } - else { + } else { $action->element('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => common_path('plugins/MobileProfile/mp-handheld.css') . '?version=' . STATUSNET_VERSION, @@ -303,8 +298,9 @@ class MobileProfilePlugin extends WAP20Plugin } - function _showPrimaryNav($action) { - $user = common_current_user(); + function _showPrimaryNav($action) + { + $user = common_current_user(); $connect = ''; if (common_config('xmpp', 'enabled')) { $connect = 'imsettings'; @@ -330,8 +326,7 @@ class MobileProfilePlugin extends WAP20Plugin } $action->menuItem(common_local_url('logout'), _('Logout')); - } - else { + } else { if (!common_config('site', 'closed')) { $action->menuItem(common_local_url('register'), _('Register')); @@ -370,7 +365,7 @@ class MobileProfilePlugin extends WAP20Plugin } if (common_config('attachments', 'uploads')) { - $form->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); + $form->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); $form->out->element('input', array('id' => 'notice_data-attach', 'type' => 'file', 'name' => 'attach', @@ -414,7 +409,7 @@ class MobileProfilePlugin extends WAP20Plugin $serverpart = common_config('site', 'mobileserver'); } } else { - $proto = 'http'; + $proto = 'http'; $serverpart = common_config('site', 'mobileserver'); } -- cgit v1.2.3-54-g00ecf From d65702b301a15b9255e9cdb89d3a31c5c6bc355f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 13:05:19 +0000 Subject: Only output attachment if the mobile device is interested --- plugins/MobileProfile/MobileProfilePlugin.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index dada1948f..2b1ccf520 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -365,12 +365,14 @@ class MobileProfilePlugin extends WAP20Plugin } if (common_config('attachments', 'uploads')) { - $form->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); - $form->out->element('input', array('id' => 'notice_data-attach', - 'type' => 'file', - 'name' => 'attach', - 'title' => _('Attach a file'))); - $form->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + if ($this->mobileFeatures['inputfiletype']) { + $form->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); + $form->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); + $form->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + } } if ($form->action) { $form->out->hidden('notice_return-to', $form->action, 'returnto'); -- cgit v1.2.3-54-g00ecf From 0e333200a45402c7e040cf1e8c36808c93052b9f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 12 Oct 2009 13:06:16 +0000 Subject: Added missing $this --- 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 2b1ccf520..518ceb758 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -158,7 +158,7 @@ class MobileProfilePlugin extends WAP20Plugin foreach ($this->mobiledevices as $md) { if (strstr($httpuseragent, $md) !== false) { - setMobileFeatures($httpuseragent); + $this->setMobileFeatures($httpuseragent); $this->serveMobile = true; break; -- cgit v1.2.3-54-g00ecf From 5eaf9f7d2eebaf6f656bfbd3d04f8a17c31898d2 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 12 Oct 2009 21:21:00 -0400 Subject: Added a new plugin that requires a user to have a validated email address before being allowed to post notices --- .../RequireValidatedEmailPlugin.php | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php (limited to 'plugins') diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php new file mode 100644 index 000000000..4806538a0 --- /dev/null +++ b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php @@ -0,0 +1,52 @@ +. + * + * @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') && !defined('LACONICA')) { + exit(1); +} + +class RequireValidatedEmailPlugin extends Plugin +{ + function __construct() + { + parent::__construct(); + } + + function onStartNoticeSave($notice) + { + $user = User::staticGet('id', $notice->profile_id); + if (!empty($user)) { // it's a remote notice + if (empty($user->email)) { + throw new ClientException(_("You must validate your email address before posting.")); + } + } + return true; + } +} + -- cgit v1.2.3-54-g00ecf From a74bb63adda8bf2658f43a850e2474f23b126abc Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 13 Oct 2009 13:42:58 +0000 Subject: Added styles for the Profile page --- plugins/MobileProfile/mp-screen.css | 39 ++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 9b42149c0..7bab15141 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -66,16 +66,22 @@ z-index:9; iPhone/iPod Touch, Android, Opera Mini Simulator */ #form_notice #notice_text-count + label, -#form_notice label[for="notice_data-attach"], +#form_notice label[for="notice_data-attach"] { +display:none; +} #form_notice #notice_data-attach { -top:0; -/*right:28%; Looks good but no good in iDevice, Android*/ -right:40%; +top:auto; +bottom:0; +left:0; +right:auto; +opacity:1; +z-index:9; +width:65%; } #form_notice #notice_action-submit { width:20%; -right:4%; +right:2%; text-align:center; } @@ -127,6 +133,29 @@ margin-right:2%; } + +.entity_profile { +width:auto; +} +.entity_actions { +margin-right:0; +margin-left:0; +clear:both; +float:left; +width:auto; +max-width:9999px; +} + +.entity_profile { +margin-bottom:7px; +} + +.entity_actions li { +float:left; +margin-right:2%; +} + + #footer { width:96%; padding:2%; -- cgit v1.2.3-54-g00ecf From dff412a3b15ac7136884eec5280f4552f8dda759 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 13 Oct 2009 14:56:58 +0000 Subject: Styles for entity actions --- plugins/MobileProfile/mp-screen.css | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 7bab15141..89e1c4d48 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -137,24 +137,53 @@ margin-right:2%; .entity_profile { width:auto; } + + + .entity_actions { margin-right:0; margin-left:0; clear:both; -float:left; +float:none; width:auto; max-width:9999px; } .entity_profile { margin-bottom:7px; +min-height:0; } .entity_actions li { float:left; -margin-right:2%; +margin-right:1.5%; +margin-bottom:0; } +.entity_profile .entity_fn, +.entity_profile .entity_nickname, +.entity_profile .entity_location, +.entity_profile .entity_url, +.entity_profile .entity_note, +.entity_profile .entity_tags, +.entity_profile .entity_aliases { +line-height:1.4; +} +.form_user_block input.submit, +.form_user_unblock input.submit, +.form_group_block input.submit, +.form_group_unblock input.submit, +.entity_send-a-message a, +.entity_edit a, +.form_user_nudge input.submit, +.entity_nudge p, +.form_make_admin input.submit { + +} + +.entity_profile .entity_depiction { +margin-bottom:2%; +} #footer { width:96%; -- cgit v1.2.3-54-g00ecf From e6c4dceb5a39b9f834b37a0d68ea9aa018e15b74 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 13 Oct 2009 15:22:05 +0000 Subject: Updated Profile view. Works better in Opera Mini, Webkits --- plugins/MobileProfile/mp-screen.css | 44 ++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 18 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 89e1c4d48..28b7f49ab 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -145,7 +145,7 @@ margin-right:0; margin-left:0; clear:both; float:none; -width:auto; +width:100%; max-width:9999px; } @@ -154,12 +154,6 @@ margin-bottom:7px; min-height:0; } -.entity_actions li { -float:left; -margin-right:1.5%; -margin-bottom:0; -} - .entity_profile .entity_fn, .entity_profile .entity_nickname, .entity_profile .entity_location, @@ -168,23 +162,37 @@ margin-bottom:0; .entity_profile .entity_tags, .entity_profile .entity_aliases { line-height:1.4; +margin-left:0; } -.form_user_block input.submit, -.form_user_unblock input.submit, -.form_group_block input.submit, -.form_group_unblock input.submit, -.entity_send-a-message a, -.entity_edit a, -.form_user_nudge input.submit, -.entity_nudge p, -.form_make_admin input.submit { +.entity_profile .entity_depiction { +margin-bottom:1%; +margin-right:7px; } -.entity_profile .entity_depiction { -margin-bottom:2%; +.entity_actions { +margin-bottom:1%; +float:left; +width:100%; } +.entity_actions li { +float:left; +margin-right:1.5%; +margin-bottom:0; +height:29px; +width:40%; +} + +.user_in .entity_actions .entity_subscribe { +margin-bottom:47px; +width:auto; +height:auto; +margin-right:5%; +} + + + #footer { width:96%; padding:2%; -- cgit v1.2.3-54-g00ecf From 637293a52fb51c67097f763626b2e5456de32e98 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 13 Oct 2009 18:12:30 -0400 Subject: outline for plugin --- plugins/UserFlag/UserFlagPlugin.php | 124 ++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 plugins/UserFlag/UserFlagPlugin.php (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php new file mode 100644 index 000000000..600ed4271 --- /dev/null +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -0,0 +1,124 @@ +. + * + * @category Plugin + * @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://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Allows users to flag content and accounts as offensive/spam/whatever + * + * @category Plugin + * @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://status.net/ + */ + +class UserFlagPlugin extends Plugin +{ + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing user-submitted flags on notices + + $schema->ensureTable('user_flag_notice', + array(new ColumnDef('notice_id', 'integer', null, + null, 'PRI'), + new ColumnDef('user_id', 'integer', null, + null, 'PRI'), + new ColumnDef('flag', 'varchar', '8'), + new ColumnDef('created', 'datetime', null, + null, 'MUL'))); + + // Allowable values for user_flag_notice + + $schema->ensureTable('notice_flag', + array(new ColumnDef('flag', 'varchar', '8', null, null, 'PRI'), + new ColumnDef('display', 'varchar', '255'), + new ColumnDef('created', 'datetime', null, + null, 'MUL'))); + + // For storing user-submitted flags on profiles + + $schema->ensureTable('user_flag_profile', + array(new ColumnDef('profile_id', 'integer', null, + null, 'PRI'), + new ColumnDef('user_id', 'integer', null, + null, 'PRI'), + new ColumnDef('flag', 'varchar', '8'), + new ColumnDef('created', 'datetime', null, + null, 'MUL'))); + + // Allowable values for user_flag_notice + + $schema->ensureTable('profile_flag', + array(new ColumnDef('flag', 'varchar', '8', null, null, 'PRI'), + new ColumnDef('display', 'varchar', '255'), + new ColumnDef('created', 'datetime', null, + null, 'MUL'))); + return true; + } + + function onInitializePlugin() + { + // XXX: do something here? + return true; + } + + function onRouterInitialized(&$m) { + $m->connect('main/flag/notice', array('action' => 'flagnotice')); + $m->connect('main/flag/profile', array('action' => 'flagprofile')); + $m->connect('admin/notice/flag', array('action' => 'adminnoticeflag')); + $m->connect('admin/profile/flag', array('action' => 'adminprofileflag')); + return true; + } + + function onAutoload($cls) + { + switch ($cls) + { + case 'FlagnoticeAction': + case 'FlagprofileAction': + case 'AdminnoticeflagAction': + case 'AdminprofileflagAction': + require_once(INSTALLDIR.'/plugins/UserFlag/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); + return false; + case 'User_flag_notice': + case 'Notice_flag': + case 'User_flag_profile': + case 'Profile_flag': + require_once(INSTALLDIR.'/plugins/UserFlag/'.$cls.'.php'); + return false; + default: + return true; + } + } +} -- cgit v1.2.3-54-g00ecf From 0fd8e758ade32204b452cc9fd40e071f0ec8c0f6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 14 Oct 2009 04:50:16 +0000 Subject: Make queuing and daemons work via events --- classes/Avatar.php | 2 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 17 +++++++++++++++++ plugins/TwitterBridge/daemons/synctwitterfriends.php | 10 ++-------- plugins/TwitterBridge/daemons/twitterstatusfetcher.php | 9 ++++----- plugins/TwitterBridge/twitter.php | 3 +++ scripts/getvaliddaemons.php | 9 --------- 6 files changed, 27 insertions(+), 23 deletions(-) (limited to 'plugins') diff --git a/classes/Avatar.php b/classes/Avatar.php index 5e8b315fe..64f105179 100644 --- a/classes/Avatar.php +++ b/classes/Avatar.php @@ -81,7 +81,7 @@ class Avatar extends Memcached_DataObject if (empty($server)) { $server = common_config('site', 'server'); } - + common_debug('path = ' . $path); // XXX: protocol return 'http://'.$server.$path.$filename; diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index a8de1c664..69bec0651 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -97,5 +97,22 @@ class TwitterBridgePlugin extends Plugin } } + function onStartEnqueueNotice($notice, $transports) + { + array_push($transports, 'twitter'); + return true; + } + + function onGetValidDaemons($daemons) + { + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterqueuehandler.php'); + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); + + if (common_config('twitterbridge', 'enabled')) { + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); + } + + return true; + } } \ No newline at end of file diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index 0668c6222..ed2bf48a2 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -33,6 +33,8 @@ END_OF_TRIM_HELP; require_once INSTALLDIR . '/scripts/commandline.inc'; require_once INSTALLDIR . '/lib/parallelizingdaemon.php'; require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; +require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php'; +require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php'; /** * Daemon to sync local friends with Twitter friends @@ -45,14 +47,6 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * @link http://status.net/ */ -$helptext = <<filename = $filename; $avatar->url = Avatar::url($filename); - common_debug($this->name() . " - New filename: $avatar->url"); - $avatar->created = common_sql_now(); $id = $avatar->insert(); @@ -516,9 +517,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon function fetchAvatar($url, $filename) { - $avatar_dir = INSTALLDIR . '/avatar/'; - - $avatarfile = $avatar_dir . $filename; + $avatarfile = Avatar::path($filename); $out = fopen($avatarfile, 'wb'); if (!$out) { diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index afc3f55ba..ac1f49c36 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -23,6 +23,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 +require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php'; +require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php'; + function updateTwitter_user($twitter_id, $screen_name) { $uri = 'http://twitter.com/' . $screen_name; diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 6dd019712..7caea1bb7 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -49,15 +49,6 @@ if(common_config('xmpp','enabled')) { $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php'; } -if(common_config('twitterbridge','enabled')) { - $daemons[] = INSTALLDIR.'/scripts/twitterstatusfetcher.php'; -} - -if (common_config('twitter', 'enabled')) { - $daemons[] = INSTALLDIR.'/scripts/twitterqueuehandler.php'; - $daemons[] = INSTALLDIR.'/scripts/synctwitterfriends.php'; -} - if (common_config('sms', 'enabled')) { $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php'; } -- cgit v1.2.3-54-g00ecf From 6b5810f5d562260a0143446982bf22ef50298d80 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 14 Oct 2009 09:53:07 +0000 Subject: Moved to location of the FBConnect JavaScript to the end of . --- plugins/FBConnect/FBConnectPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index ff74aade4..b31f69d81 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -102,7 +102,7 @@ class FBConnectPlugin extends Plugin // Note: this script needs to appear in the - function onStartShowHeader($action) + function onEndShowScripts($action) { if ($this->reqFbScripts($action)) { -- cgit v1.2.3-54-g00ecf From 658683e240ef6e44c6450a16448fe7cb82f792f2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 15 Oct 2009 04:47:11 -0400 Subject: Stub actions for UserFlag plugin Stub files for the actions in UserFlag plugin --- plugins/UserFlag/adminnoticeflagaction.php | 88 ++++++++++++++++++++++++++ plugins/UserFlag/adminprofileflagaction.php | 88 ++++++++++++++++++++++++++ plugins/UserFlag/flagnoticeaction.php | 97 +++++++++++++++++++++++++++++ plugins/UserFlag/flagprofileaction.php | 95 ++++++++++++++++++++++++++++ 4 files changed, 368 insertions(+) create mode 100644 plugins/UserFlag/adminnoticeflagaction.php create mode 100644 plugins/UserFlag/adminprofileflagaction.php create mode 100644 plugins/UserFlag/flagnoticeaction.php create mode 100644 plugins/UserFlag/flagprofileaction.php (limited to 'plugins') diff --git a/plugins/UserFlag/adminnoticeflagaction.php b/plugins/UserFlag/adminnoticeflagaction.php new file mode 100644 index 000000000..002368999 --- /dev/null +++ b/plugins/UserFlag/adminnoticeflagaction.php @@ -0,0 +1,88 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, 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('STATUSNET')) { + exit(1); +} + +/** + * Show the latest and greatest notice flags + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class AdminnoticeflagAction extends Action +{ + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + + function prepare($args) + { + return true; + } + + /** + * Handle request + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $this->showPage(); + } + + function title() { + return _('Flagged notices'); + } + + /** + * save the notice flag + * + * @return void + */ + + function showContent() + { + } +} + diff --git a/plugins/UserFlag/adminprofileflagaction.php b/plugins/UserFlag/adminprofileflagaction.php new file mode 100644 index 000000000..b264beecb --- /dev/null +++ b/plugins/UserFlag/adminprofileflagaction.php @@ -0,0 +1,88 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, 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('STATUSNET')) { + exit(1); +} + +/** + * Show the latest and greatest profile flags + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class AdminprofileflagAction extends Action +{ + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + + function prepare($args) + { + return true; + } + + /** + * Handle request + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $this->showPage(); + } + + function title() { + return _('Flagged profiles'); + } + + /** + * save the profile flag + * + * @return void + */ + + function showContent() + { + } +} + diff --git a/plugins/UserFlag/flagnoticeaction.php b/plugins/UserFlag/flagnoticeaction.php new file mode 100644 index 000000000..b33cb2036 --- /dev/null +++ b/plugins/UserFlag/flagnoticeaction.php @@ -0,0 +1,97 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, 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('STATUSNET')) { + exit(1); +} + +/** + * Action to flag a notice. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class FlagnoticeAction extends Action +{ + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + + function prepare($args) + { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + throw new ClientException(_('Action only accepts POST')); + } + + return true; + } + + /** + * Handle request + * + * Shows a page with list of favorite notices + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $this->flagNotice(); + $this->returnTo(); + } + + function title() { + return _('Flag notice'); + } + + /** + * save the notice flag + * + * @return void + */ + + function flagNotice() + { + } +} + diff --git a/plugins/UserFlag/flagprofileaction.php b/plugins/UserFlag/flagprofileaction.php new file mode 100644 index 000000000..794151099 --- /dev/null +++ b/plugins/UserFlag/flagprofileaction.php @@ -0,0 +1,95 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, 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('STATUSNET')) { + exit(1); +} + +/** + * Action to flag a profile. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class FlagprofileAction extends Action +{ + var $profile = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + + function prepare($args) + { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + throw new ClientException(_('Action only accepts POST')); + } + + return true; + } + + /** + * Handle request + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $this->flagProfile(); + $this->returnTo(); + } + + function title() { + return _('Flag profile'); + } + + /** + * save the profile flag + * + * @return void + */ + + function flagProfile() + { + } +} + -- cgit v1.2.3-54-g00ecf From ed85dc2700eb664c98ca74fef2db2e548cf1a104 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 11:25:38 +0000 Subject: Updated comments --- plugins/MobileProfile/MobileProfilePlugin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 518ceb758..eba460ed6 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -63,6 +63,7 @@ class MobileProfilePlugin extends WAP20Plugin function onStartShowHTML($action) { + if (!$type) { $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null; @@ -97,9 +98,9 @@ class MobileProfilePlugin extends WAP20Plugin // XXX: Browser sniffing sucks // I really don't like going through this every page, - // find a better way + // perhaps use $_SESSION or cookies - // May be better to categorize the devices in terms of + // May be better to group the devices in terms of // low,mid,high-end // Or, detect the mobile devices based on their support for -- cgit v1.2.3-54-g00ecf From 94c7b2b43ab04c94e85bc276e6ee9bd59d424984 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 12:47:59 +0000 Subject: Changed textarea @cols value for smaller screens --- 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 eba460ed6..4bbdb3541 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -350,7 +350,7 @@ class MobileProfilePlugin extends WAP20Plugin } $form->out->element('textarea', array('id' => 'notice_data-text', - 'cols' => 35, + 'cols' => 15, 'rows' => 4, 'name' => 'status_textarea'), ($form->content) ? $form->content : ''); -- cgit v1.2.3-54-g00ecf From d7efe87e770e83339a86dce83b7f75a99a52567d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 13:08:57 +0000 Subject: Better widths for notice options (improves Opera Mini's rendering) --- plugins/MobileProfile/mp-screen.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 28b7f49ab..d72b316cf 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -125,10 +125,10 @@ padding-bottom:4px; } .notice div.entry-content { margin-left:0; -width:75%; +width:65%; } .notice-options { -width:70px; +width:30%; margin-right:2%; } -- cgit v1.2.3-54-g00ecf From 70a39c5b7b99f6b9d8805761c23a585dc1016eb6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:05:43 +0000 Subject: Better layout for form_settings --- plugins/MobileProfile/mp-screen.css | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index d72b316cf..0f97de17c 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -30,6 +30,29 @@ margin-right:7px; } +.form_settings label { +width:auto; +display:block; +float:none; +} +.form_settings .form_data textarea, +.form_settings .form_data select, +.form_settings .form_data input { +margin-left:0; +display:block; +width:96.41%; +} +.form_settings .form_data label { +float:none; +} + +.form_settings .form_data p.form_guide { +width:auto; +margin-left:0; +} + + + #site_nav_global_primary { margin:0; width:100%; -- cgit v1.2.3-54-g00ecf From 9c90fd05f7377151c614aab76c69a346c3dd114a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:20:42 +0000 Subject: Set width only to textarea --- plugins/MobileProfile/mp-screen.css | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 0f97de17c..6adf7b1aa 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -40,8 +40,11 @@ float:none; .form_settings .form_data input { margin-left:0; display:block; +} +.form_settings .form_data textarea { width:96.41%; } + .form_settings .form_data label { float:none; } -- cgit v1.2.3-54-g00ecf From 63d2476d742a7d892b8fa7db0a9bccb7eb21bc55 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:22:46 +0000 Subject: More margin for logo --- plugins/MobileProfile/mp-screen.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 6adf7b1aa..16d212c79 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -13,7 +13,7 @@ width:96%; } address { -margin:4% 0 0 0; +margin:5% 0 0 0; } address .vcard .photo { margin-right:0; -- cgit v1.2.3-54-g00ecf From 414ca4e83f85fc329b1c8469ded612e94272c9ff Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:46:49 +0000 Subject: Adjusted global and local navigation spacing and size. Fixed logo's view in Opera Mini --- plugins/MobileProfile/mp-screen.css | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 16d212c79..9b7d53120 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -8,12 +8,14 @@ max-width:100%; #header { margin:0; -padding:2%; +padding:0.7em 2%; width:96%; } address { -margin:5% 0 0 0; +margin:1em 0 0 0; +float:left; +width:100%; } address .vcard .photo { margin-right:0; @@ -21,8 +23,8 @@ margin-right:0; address img + .fn { display:block; -margin-top:8%; -width:100%; +margin-top:1.25em; +float:left; } .vcard .photo { @@ -66,7 +68,7 @@ left:0; } #site_nav_global_primary li { margin-left:0; -margin-right:2%; +margin-right:4%; float:left; font-size:0.9em; } @@ -120,8 +122,9 @@ margin-right:0; margin-left:0; } #site_nav_local_views a { -padding:0; +padding:1px 4px; display:block; +font-size:0.9em; } #site_nav_local_views .current a { text-shadow:none; @@ -136,6 +139,11 @@ box-shadow:none; #content { width:96.41%; } +#content, +#site_nav_local_views a, +#aside_primary { +border:0; +} h1 { margin-bottom:0; -- cgit v1.2.3-54-g00ecf From 09bafa13b13a3bee581f7ace1e9f97ade24828c1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:52:49 +0000 Subject: Minor adjustment to site name spacing --- plugins/MobileProfile/mp-screen.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 9b7d53120..468d6b32c 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -23,7 +23,7 @@ margin-right:0; address img + .fn { display:block; -margin-top:1.25em; +margin-top:1em; float:left; } @@ -138,6 +138,7 @@ box-shadow:none; #content { width:96.41%; +min-height:auto; } #content, #site_nav_local_views a, -- cgit v1.2.3-54-g00ecf From afa00b558f981d1ae01715f5aa52ce992708bdd8 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 14:59:21 +0000 Subject: Reduced some margin-bottoms --- plugins/MobileProfile/mp-screen.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 468d6b32c..15ec76339 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -32,11 +32,19 @@ margin-right:7px; } +.form_settings fieldset { +margin-bottom:7px; +} + .form_settings label { width:auto; display:block; float:none; } +.form_settings .form_data li { +margin-bottom:7px; +} + .form_settings .form_data textarea, .form_settings .form_data select, .form_settings .form_data input { @@ -146,6 +154,11 @@ min-height:auto; border:0; } +.instructions p, +.instructions ul { +margin-bottom:4px; +} + h1 { margin-bottom:0; } -- cgit v1.2.3-54-g00ecf From 42f3aff9eb412080579c349460835f7cae918775 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 15 Oct 2009 15:02:53 +0000 Subject: Slight adjustment to local navigation padding --- plugins/MobileProfile/mp-screen.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 15ec76339..1bb0248ec 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -130,7 +130,7 @@ margin-right:0; margin-left:0; } #site_nav_local_views a { -padding:1px 4px; +padding:1px 3px; display:block; font-size:0.9em; } -- cgit v1.2.3-54-g00ecf From 109a54c4f08177cce82ef0f4a4ec8cd41ed2ecec Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sat, 17 Oct 2009 00:32:02 +0000 Subject: Changed config flag for importing friends' timeline and added some comments --- lib/default.php | 2 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 79 ++++++++++++++++++++------- 2 files changed, 59 insertions(+), 22 deletions(-) (limited to 'plugins') diff --git a/lib/default.php b/lib/default.php index 9f3d4b1f9..68029c977 100644 --- a/lib/default.php +++ b/lib/default.php @@ -140,7 +140,7 @@ $default = array('enabled' => true), 'sms' => array('enabled' => true), - 'twitterbridge' => + 'twitterimport' => array('enabled' => false), 'integration' => array('source' => 'StatusNet', # source attribute for Twitter diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 69bec0651..1a27c30cd 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -1,6 +1,6 @@ . * * @category Plugin - * @package Laconica - * @author Zach Copley + * @package StatusNet + * @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')) { +if (!defined('STATUSNET')) { exit(1); } @@ -35,8 +35,8 @@ if (!defined('LACONICA')) { * This class allows users to link their Twitter accounts * * @category Plugin - * @package Laconica - * @author Zach Copley + * @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://laconi.ca/ * @link http://twitter.com/ @@ -58,17 +58,27 @@ class TwitterBridgePlugin extends Plugin * * Hook for RouterInitialized event. * + * @param Net_URL_Mapper &$m path-to-action mapper + * * @return boolean hook return */ function onRouterInitialized(&$m) { - $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); + $m->connect('twitter/authorization', + array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); return true; } + /** + * Add the Twitter Settings page to the Connect Settings menu + * + * @param Action &$action The calling page + * + * @return boolean hook return + */ function onEndConnectSettingsNav(&$action) { $action_name = $action->trimmed('action'); @@ -81,38 +91,65 @@ class TwitterBridgePlugin extends Plugin return true; } + /** + * Automatically load the actions and libraries used by the Twitter bridge + * + * @param Class $cls the class + * + * @return boolean hook return + * + */ function onAutoload($cls) { - switch ($cls) - { - case 'TwittersettingsAction': - case 'TwitterauthorizationAction': - require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); + switch ($cls) { + case 'TwittersettingsAction': + case 'TwitterauthorizationAction': + include_once INSTALLDIR.'/plugins/TwitterBridge/' . + strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; - case 'TwitterOAuthClient': - require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'); + case 'TwitterOAuthClient': + include_once INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'; return false; - default: + default: return true; } } + /** + * Add a Twitter 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, 'twitter'); return true; } + /** + * Add Twitter bridge daemons 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/TwitterBridge/daemons/twitterqueuehandler.php'); - array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); - - if (common_config('twitterbridge', 'enabled')) { - array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); + array_push($daemons, INSTALLDIR . + '/plugins/TwitterBridge/daemons/twitterqueuehandler.php'); + array_push($daemons, INSTALLDIR . + '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); + + if (common_config('twitterimport', 'enabled')) { + array_push($daemons, INSTALLDIR + . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); } return true; } -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf From eaaa01a1713b14f48b9889bfbe7f91a9ddd60fd5 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 18 Oct 2009 15:10:49 +0000 Subject: Added an update counter in the document title. When window is in blur, it will update the document title when new notices are received. The counter will reset when window is blurred (after a focus) again. --- plugins/Realtime/realtimeupdate.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index a75f17d8c..9371326fe 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -7,6 +7,7 @@ RealtimeUpdate = { _replyurl: '', _favorurl: '', _deleteurl: '', + _updatecounter: 0, init: function(userid, replyurl, favorurl, deleteurl) { @@ -15,6 +16,8 @@ RealtimeUpdate = { RealtimeUpdate._favorurl = favorurl; RealtimeUpdate._deleteurl = deleteurl; + DT = document.title; + $(window).blur(function() { $('#notices_primary .notice').css({ 'border-top-color':$('#notices_primary .notice:last').css('border-top-color'), @@ -25,7 +28,10 @@ RealtimeUpdate = { 'border-top-color':'#AAAAAA', 'border-top-style':'solid' }); - + + RealtimeUpdate._updatecounter = 0; + document.title = DT; + return false; }); }, @@ -39,12 +45,15 @@ RealtimeUpdate = { 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(); + + RealtimeUpdate._updatecounter += 1; + document.title = '('+RealtimeUpdate._updatecounter+') ' + DT; }, 500); }, -- cgit v1.2.3-54-g00ecf From 9cdd77625e25e7c506121d42b28c1ec870c2d4e4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 20 Oct 2009 00:49:11 +0000 Subject: Less intrusive, more reliable way for FB Connect plugin to modify the primary nav in order to show FB mini-avatar and have the logout link logout of FB as well as StatusNet. --- plugins/FBConnect/FBConnectPlugin.php | 126 ++++++++++++---------------------- 1 file changed, 45 insertions(+), 81 deletions(-) (limited to 'plugins') diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index b31f69d81..0dacf9012 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -115,26 +115,48 @@ class FBConnectPlugin extends Plugin // XXX: Facebook says we don't need this FB_RequireFeatures(), // but we actually do, for IE and Safari. Gar. - $html = sprintf('', $apikey, - $login_url, $logout_url); - - $action->raw($html); + $js = ''; + + $js = sprintf($js, $apikey, $login_url, $logout_url); + + // Compress the bugger down a bit + $js = str_replace(' ', '', $js); + + $action->raw(" $js"); // leading two spaces to make it line up } } @@ -150,7 +172,6 @@ class FBConnectPlugin extends Plugin function onEndShowStatusNetStyles($action) { - if ($this->reqFbScripts($action)) { $action->cssLink('plugins/FBConnect/FBConnectPlugin.css'); } @@ -269,66 +290,9 @@ class FBConnectPlugin extends Plugin $action->elementEnd('li'); } + } - $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), - _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); - $action->menuItem(common_local_url('profilesettings'), - _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); - $action->menuItem(common_local_url($connect), - _('Connect'), _('Connect to services'), false, 'nav_connect'); - if (common_config('invite', 'enabled')) { - $action->menuItem(common_local_url('invite'), - _('Invite'), - sprintf(_('Invite friends and colleagues to join you on %s'), - common_config('site', 'name')), - false, 'nav_invitecontact'); - } - - // Need to override the Logout link to make it do FB stuff - if (!empty($fbuid)) { - - $logout_url = common_local_url('logout'); - $title = _('Logout from the site'); - $text = _('Logout'); - - $html = sprintf('', - $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 '
    '; - $this->cur_id++; - print $this->cur_id . ': Called ' . $method . ', show ' . - 'Params | '. - 'XML | '. - 'SXML | '. - 'PHP'; - print ''; - print ''; - print ''; - print ''; - print '
    '; - } - return $result; - } - - protected function finalize_params($method, $params) { - list($get, $post) = $this->add_standard_params($method, $params); - // we need to do this before signing the params - $this->convert_array_values_to_json($post); - $post['sig'] = Facebook::generate_sig(array_merge($get, $post), - $this->secret); - return array($get, $post); - } - - private function convert_array_values_to_json(&$params) { - foreach ($params as $key => &$val) { - if (is_array($val)) { - $val = json_encode($val); - } - } - } - - /** - * Add the generally required params to our request. - * Params method, api_key, and v should be sent over as get. - */ - private function add_standard_params($method, $params) { - $post = $params; - $get = array(); - if ($this->call_as_apikey) { - $get['call_as_apikey'] = $this->call_as_apikey; - } - $get['method'] = $method; - $get['session_key'] = $this->session_key; - $get['api_key'] = $this->api_key; - $post['call_id'] = microtime(true); - if ($post['call_id'] <= $this->last_call_id) { - $post['call_id'] = $this->last_call_id + 0.001; - } - $this->last_call_id = $post['call_id']; - if (isset($post['v'])) { - $get['v'] = $post['v']; - unset($post['v']); - } else { - $get['v'] = '1.0'; - } - if (isset($this->use_ssl_resources) && - $this->use_ssl_resources) { - $post['return_ssl_resources'] = true; - } - return array($get, $post); - } - - private function create_url_string($params) { - $post_params = array(); - foreach ($params as $key => &$val) { - $post_params[] = $key.'='.urlencode($val); - } - return implode('&', $post_params); - } - - private function run_multipart_http_transaction($method, $params, $file, $server_addr) { - - // the format of this message is specified in RFC1867/RFC1341. - // we add twenty pseudo-random digits to the end of the boundary string. - $boundary = '--------------------------FbMuLtIpArT' . - sprintf("%010d", mt_rand()) . - sprintf("%010d", mt_rand()); - $content_type = 'multipart/form-data; boundary=' . $boundary; - // within the message, we prepend two extra hyphens. - $delimiter = '--' . $boundary; - $close_delimiter = $delimiter . '--'; - $content_lines = array(); - foreach ($params as $key => &$val) { - $content_lines[] = $delimiter; - $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"'; - $content_lines[] = ''; - $content_lines[] = $val; - } - // now add the file data - $content_lines[] = $delimiter; - $content_lines[] = - 'Content-Disposition: form-data; filename="' . $file . '"'; - $content_lines[] = 'Content-Type: application/octet-stream'; - $content_lines[] = ''; - $content_lines[] = file_get_contents($file); - $content_lines[] = $close_delimiter; - $content_lines[] = ''; - $content = implode("\r\n", $content_lines); - return $this->run_http_post_transaction($content_type, $content, $server_addr); - } - - public function post_request($method, $params) { - list($get, $post) = $this->finalize_params($method, $params); - $post_string = $this->create_url_string($post); - $get_string = $this->create_url_string($get); - $url_with_get = $this->server_addr . '?' . $get_string; - if ($this->use_curl_if_available && function_exists('curl_init')) { - $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url_with_get); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $useragent); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - $result = $this->curl_exec($ch); - curl_close($ch); - } else { - $content_type = 'application/x-www-form-urlencoded'; - $content = $post_string; - $result = $this->run_http_post_transaction($content_type, - $content, - $url_with_get); - } - return $result; - } - - /** - * execute a curl transaction -- this exists mostly so subclasses can add - * extra options and/or process the response, if they wish. - * - * @param resource $ch a curl handle - */ - protected function curl_exec($ch) { - $result = curl_exec($ch); - return $result; - } - - private function post_upload_request($method, $params, $file, $server_addr = null) { - $server_addr = $server_addr ? $server_addr : $this->server_addr; - list($get, $post) = $this->finalize_params($method, $params); - $get_string = $this->create_url_string($get); - $url_with_get = $server_addr . '?' . $get_string; - if ($this->use_curl_if_available && function_exists('curl_init')) { - // prepending '@' causes cURL to upload the file; the key is ignored. - $post['_file'] = '@' . $file; - $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url_with_get); - // this has to come before the POSTFIELDS set! - curl_setopt($ch, CURLOPT_POST, 1); - // passing an array gets curl to use the multipart/form-data content type - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $useragent); - $result = $this->curl_exec($ch); - curl_close($ch); - } else { - $result = $this->run_multipart_http_transaction($method, $post, - $file, $url_with_get); - } - return $result; - } - - private function run_http_post_transaction($content_type, $content, $server_addr) { - - $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion(); - $content_length = strlen($content); - $context = - array('http' => - array('method' => 'POST', - 'user_agent' => $user_agent, - 'header' => 'Content-Type: ' . $content_type . "\r\n" . - 'Content-Length: ' . $content_length, - 'content' => $content)); - $context_id = stream_context_create($context); - $sock = fopen($server_addr, 'r', false, $context_id); - - $result = ''; - if ($sock) { - while (!feof($sock)) { - $result .= fgets($sock, 4096); - } - fclose($sock); - } - return $result; - } - - public static function convert_simplexml_to_array($sxml) { - $arr = array(); - if ($sxml) { - foreach ($sxml as $k => $v) { - if ($sxml['list']) { - $arr[] = self::convert_simplexml_to_array($v); - } else { - $arr[$k] = self::convert_simplexml_to_array($v); - } - } - } - if (sizeof($arr) > 0) { - return $arr; - } else { - return (string)$sxml; - } - } - - protected function get_uid($uid) { - return $uid ? $uid : $this->user; - } -} - - -class FacebookRestClientException extends Exception { -} - -// Supporting methods and values------ - -/** - * Error codes and descriptions for the Facebook API. - */ - -class FacebookAPIErrorCodes { - - const API_EC_SUCCESS = 0; - - /* - * GENERAL ERRORS - */ - const API_EC_UNKNOWN = 1; - const API_EC_SERVICE = 2; - const API_EC_METHOD = 3; - const API_EC_TOO_MANY_CALLS = 4; - const API_EC_BAD_IP = 5; - const API_EC_HOST_API = 6; - const API_EC_HOST_UP = 7; - const API_EC_SECURE = 8; - const API_EC_RATE = 9; - const API_EC_PERMISSION_DENIED = 10; - const API_EC_DEPRECATED = 11; - const API_EC_VERSION = 12; - const API_EC_INTERNAL_FQL_ERROR = 13; - const API_EC_HOST_PUP = 14; - - /* - * PARAMETER ERRORS - */ - const API_EC_PARAM = 100; - const API_EC_PARAM_API_KEY = 101; - const API_EC_PARAM_SESSION_KEY = 102; - const API_EC_PARAM_CALL_ID = 103; - const API_EC_PARAM_SIGNATURE = 104; - const API_EC_PARAM_TOO_MANY = 105; - const API_EC_PARAM_USER_ID = 110; - const API_EC_PARAM_USER_FIELD = 111; - const API_EC_PARAM_SOCIAL_FIELD = 112; - const API_EC_PARAM_EMAIL = 113; - const API_EC_PARAM_USER_ID_LIST = 114; - const API_EC_PARAM_FIELD_LIST = 115; - const API_EC_PARAM_ALBUM_ID = 120; - const API_EC_PARAM_PHOTO_ID = 121; - const API_EC_PARAM_FEED_PRIORITY = 130; - const API_EC_PARAM_CATEGORY = 140; - const API_EC_PARAM_SUBCATEGORY = 141; - const API_EC_PARAM_TITLE = 142; - const API_EC_PARAM_DESCRIPTION = 143; - const API_EC_PARAM_BAD_JSON = 144; - const API_EC_PARAM_BAD_EID = 150; - const API_EC_PARAM_UNKNOWN_CITY = 151; - const API_EC_PARAM_BAD_PAGE_TYPE = 152; - - /* - * USER PERMISSIONS ERRORS - */ - const API_EC_PERMISSION = 200; - const API_EC_PERMISSION_USER = 210; - const API_EC_PERMISSION_NO_DEVELOPERS = 211; - const API_EC_PERMISSION_OFFLINE_ACCESS = 212; - const API_EC_PERMISSION_ALBUM = 220; - const API_EC_PERMISSION_PHOTO = 221; - const API_EC_PERMISSION_MESSAGE = 230; - const API_EC_PERMISSION_OTHER_USER = 240; - const API_EC_PERMISSION_STATUS_UPDATE = 250; - const API_EC_PERMISSION_PHOTO_UPLOAD = 260; - const API_EC_PERMISSION_VIDEO_UPLOAD = 261; - const API_EC_PERMISSION_SMS = 270; - const API_EC_PERMISSION_CREATE_LISTING = 280; - const API_EC_PERMISSION_CREATE_NOTE = 281; - const API_EC_PERMISSION_SHARE_ITEM = 282; - const API_EC_PERMISSION_EVENT = 290; - const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291; - const API_EC_PERMISSION_LIVEMESSAGE = 292; - const API_EC_PERMISSION_RSVP_EVENT = 299; - - /* - * DATA EDIT ERRORS - */ - const API_EC_EDIT = 300; - const API_EC_EDIT_USER_DATA = 310; - const API_EC_EDIT_PHOTO = 320; - const API_EC_EDIT_ALBUM_SIZE = 321; - const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322; - const API_EC_EDIT_PHOTO_TAG_PHOTO = 323; - const API_EC_EDIT_PHOTO_FILE = 324; - const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325; - const API_EC_EDIT_PHOTO_TAG_LIMIT = 326; - const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327; - const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328; - - const API_EC_MALFORMED_MARKUP = 329; - const API_EC_EDIT_MARKUP = 330; - - const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340; - const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341; - const API_EC_EDIT_FEED_TITLE_LINK = 342; - const API_EC_EDIT_FEED_TITLE_LENGTH = 343; - const API_EC_EDIT_FEED_TITLE_NAME = 344; - const API_EC_EDIT_FEED_TITLE_BLANK = 345; - const API_EC_EDIT_FEED_BODY_LENGTH = 346; - const API_EC_EDIT_FEED_PHOTO_SRC = 347; - const API_EC_EDIT_FEED_PHOTO_LINK = 348; - - const API_EC_EDIT_VIDEO_SIZE = 350; - const API_EC_EDIT_VIDEO_INVALID_FILE = 351; - const API_EC_EDIT_VIDEO_INVALID_TYPE = 352; - const API_EC_EDIT_VIDEO_FILE = 353; - - const API_EC_EDIT_FEED_TITLE_ARRAY = 360; - const API_EC_EDIT_FEED_TITLE_PARAMS = 361; - const API_EC_EDIT_FEED_BODY_ARRAY = 362; - const API_EC_EDIT_FEED_BODY_PARAMS = 363; - const API_EC_EDIT_FEED_PHOTO = 364; - const API_EC_EDIT_FEED_TEMPLATE = 365; - const API_EC_EDIT_FEED_TARGET = 366; - const API_EC_EDIT_FEED_MARKUP = 367; - - /** - * SESSION ERRORS - */ - const API_EC_SESSION_TIMED_OUT = 450; - const API_EC_SESSION_METHOD = 451; - const API_EC_SESSION_INVALID = 452; - const API_EC_SESSION_REQUIRED = 453; - const API_EC_SESSION_REQUIRED_FOR_SECRET = 454; - const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455; - - - /** - * FQL ERRORS - */ - const FQL_EC_UNKNOWN_ERROR = 600; - const FQL_EC_PARSER = 601; // backwards compatibility - const FQL_EC_PARSER_ERROR = 601; - const FQL_EC_UNKNOWN_FIELD = 602; - const FQL_EC_UNKNOWN_TABLE = 603; - const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility - const FQL_EC_NO_INDEX = 604; - const FQL_EC_UNKNOWN_FUNCTION = 605; - const FQL_EC_INVALID_PARAM = 606; - const FQL_EC_INVALID_FIELD = 607; - const FQL_EC_INVALID_SESSION = 608; - const FQL_EC_UNSUPPORTED_APP_TYPE = 609; - const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610; - const FQL_EC_DEPRECATED_TABLE = 611; - const FQL_EC_EXTENDED_PERMISSION = 612; - const FQL_EC_RATE_LIMIT_EXCEEDED = 613; - const FQL_EC_UNRESOLVED_DEPENDENCY = 614; - - const API_EC_REF_SET_FAILED = 700; - - /** - * DATA STORE API ERRORS - */ - const API_EC_DATA_UNKNOWN_ERROR = 800; - const API_EC_DATA_INVALID_OPERATION = 801; - const API_EC_DATA_QUOTA_EXCEEDED = 802; - const API_EC_DATA_OBJECT_NOT_FOUND = 803; - const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804; - const API_EC_DATA_DATABASE_ERROR = 805; - const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806; - const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807; - const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808; - const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809; - const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810; - const API_EC_DATA_MALFORMED_ACTION_LINK = 811; - const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812; - - /* - * APPLICATION INFO ERRORS - */ - const API_EC_NO_SUCH_APP = 900; - - /* - * BATCH ERRORS - */ - const API_EC_BATCH_TOO_MANY_ITEMS = 950; - const API_EC_BATCH_ALREADY_STARTED = 951; - const API_EC_BATCH_NOT_STARTED = 952; - const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953; - - /* - * EVENT API ERRORS - */ - const API_EC_EVENT_INVALID_TIME = 1000; - - /* - * INFO BOX ERRORS - */ - const API_EC_INFO_NO_INFORMATION = 1050; - const API_EC_INFO_SET_FAILED = 1051; - - /* - * LIVEMESSAGE API ERRORS - */ - const API_EC_LIVEMESSAGE_SEND_FAILED = 1100; - const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101; - const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102; - - /* - * PAYMENTS API ERRORS - */ - const API_EC_PAYMENTS_UNKNOWN = 1150; - const API_EC_PAYMENTS_APP_INVALID = 1151; - const API_EC_PAYMENTS_DATABASE = 1152; - const API_EC_PAYMENTS_PERMISSION_DENIED = 1153; - const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154; - const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155; - const API_EC_PAYMENTS_INVALID_ORDER = 1156; - const API_EC_PAYMENTS_INVALID_PARAM = 1157; - const API_EC_PAYMENTS_INVALID_OPERATION = 1158; - const API_EC_PAYMENTS_PAYMENT_FAILED = 1159; - const API_EC_PAYMENTS_DISABLED = 1160; - - /* - * CONNECT SESSION ERRORS - */ - const API_EC_CONNECT_FEED_DISABLED = 1300; - - /* - * Platform tag bundles errors - */ - const API_EC_TAG_BUNDLE_QUOTA = 1400; - - /* - * SHARE - */ - const API_EC_SHARE_BAD_URL = 1500; - - /* - * NOTES - */ - const API_EC_NOTE_CANNOT_MODIFY = 1600; - - /* - * COMMENTS - */ - const API_EC_COMMENTS_UNKNOWN = 1700; - const API_EC_COMMENTS_POST_TOO_LONG = 1701; - const API_EC_COMMENTS_DB_DOWN = 1702; - const API_EC_COMMENTS_INVALID_XID = 1703; - const API_EC_COMMENTS_INVALID_UID = 1704; - const API_EC_COMMENTS_INVALID_POST = 1705; - const API_EC_COMMENTS_INVALID_REMOVE = 1706; - - /** - * This array is no longer maintained; to view the description of an error - * code, please look at the message element of the API response or visit - * the developer wiki at http://wiki.developers.facebook.com/. - */ - public static $api_error_descriptions = array( - self::API_EC_SUCCESS => 'Success', - self::API_EC_UNKNOWN => 'An unknown error occurred', - self::API_EC_SERVICE => 'Service temporarily unavailable', - self::API_EC_METHOD => 'Unknown method', - self::API_EC_TOO_MANY_CALLS => 'Application request limit reached', - self::API_EC_BAD_IP => 'Unauthorized source IP address', - self::API_EC_PARAM => 'Invalid parameter', - self::API_EC_PARAM_API_KEY => 'Invalid API key', - self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid', - self::API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous', - self::API_EC_PARAM_SIGNATURE => 'Incorrect signature', - self::API_EC_PARAM_USER_ID => 'Invalid user id', - self::API_EC_PARAM_USER_FIELD => 'Invalid user info field', - self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field', - self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list', - self::API_EC_PARAM_FIELD_LIST => 'Invalid field list', - self::API_EC_PARAM_ALBUM_ID => 'Invalid album id', - self::API_EC_PARAM_BAD_EID => 'Invalid eid', - self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city', - self::API_EC_PERMISSION => 'Permissions error', - self::API_EC_PERMISSION_USER => 'User not visible', - self::API_EC_PERMISSION_NO_DEVELOPERS => 'Application has no developers', - self::API_EC_PERMISSION_ALBUM => 'Album not visible', - self::API_EC_PERMISSION_PHOTO => 'Photo not visible', - self::API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event', - self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event', - self::API_EC_EDIT_ALBUM_SIZE => 'Album is full', - self::FQL_EC_PARSER => 'FQL: Parser Error', - self::FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field', - self::FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table', - self::FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable', - self::FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function', - self::FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in', - self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error', - self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation', - self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded', - self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found', - self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists', - self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again', - self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first', - self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch', - self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode' - ); -} diff --git a/extlib/facebook/jsonwrapper/JSON/JSON.php b/extlib/facebook/jsonwrapper/JSON/JSON.php deleted file mode 100644 index 0cddbddb4..000000000 --- a/extlib/facebook/jsonwrapper/JSON/JSON.php +++ /dev/null @@ -1,806 +0,0 @@ - - * @author Matt Knapp - * @author Brett Stimmerman - * @copyright 2005 Michal Migurski - * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 - */ - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_SLICE', 1); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_STR', 2); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_ARR', 3); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_OBJ', 4); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_CMT', 5); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_LOOSE_TYPE', 16); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_SUPPRESS_ERRORS', 32); - -/** - * Converts to and from JSON format. - * - * Brief example of use: - * - * - * // create a new instance of Services_JSON - * $json = new Services_JSON(); - * - * // convert a complexe value to JSON notation, and send it to the browser - * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); - * $output = $json->encode($value); - * - * print($output); - * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] - * - * // accept incoming POST data, assumed to be in JSON notation - * $input = file_get_contents('php://input', 1000000); - * $value = $json->decode($input); - * - */ -class Services_JSON -{ - /** - * constructs a new JSON instance - * - * @param int $use object behavior flags; combine with boolean-OR - * - * possible values: - * - SERVICES_JSON_LOOSE_TYPE: loose typing. - * "{...}" syntax creates associative arrays - * instead of objects in decode(). - * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. - * Values which can't be encoded (e.g. resources) - * appear as NULL instead of throwing errors. - * By default, a deeply-nested resource will - * bubble up with an error, so all return values - * from encode() should be checked with isError() - */ - function Services_JSON($use = 0) - { - $this->use = $use; - } - - /** - * convert a string from one UTF-16 char to one UTF-8 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf16 UTF-16 character - * @return string UTF-8 character - * @access private - */ - function utf162utf8($utf16) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - } - - $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); - - switch(true) { - case ((0x7F & $bytes) == $bytes): - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x7F & $bytes); - - case (0x07FF & $bytes) == $bytes: - // return a 2-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xC0 | (($bytes >> 6) & 0x1F)) - . chr(0x80 | ($bytes & 0x3F)); - - case (0xFFFF & $bytes) == $bytes: - // return a 3-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xE0 | (($bytes >> 12) & 0x0F)) - . chr(0x80 | (($bytes >> 6) & 0x3F)) - . chr(0x80 | ($bytes & 0x3F)); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * convert a string from one UTF-8 char to one UTF-16 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf8 UTF-8 character - * @return string UTF-16 character - * @access private - */ - function utf82utf16($utf8) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); - } - - switch(strlen($utf8)) { - case 1: - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return $utf8; - - case 2: - // return a UTF-16 character from a 2-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x07 & (ord($utf8{0}) >> 2)) - . chr((0xC0 & (ord($utf8{0}) << 6)) - | (0x3F & ord($utf8{1}))); - - case 3: - // return a UTF-16 character from a 3-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr((0xF0 & (ord($utf8{0}) << 4)) - | (0x0F & (ord($utf8{1}) >> 2))) - . chr((0xC0 & (ord($utf8{1}) << 6)) - | (0x7F & ord($utf8{2}))); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encode($var) - { - switch (gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; - - case 'NULL': - return 'null'; - - case 'integer': - return (int) $var; - - case 'double': - case 'float': - return (float) $var; - - case 'string': - // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - /* - * Iterate over every character in the string, - * escaping with a slash or encoding to UTF-8 where necessary - */ - for ($c = 0; $c < $strlen_var; ++$c) { - - $ord_var_c = ord($var{$c}); - - switch (true) { - case $ord_var_c == 0x08: - $ascii .= '\b'; - break; - case $ord_var_c == 0x09: - $ascii .= '\t'; - break; - case $ord_var_c == 0x0A: - $ascii .= '\n'; - break; - case $ord_var_c == 0x0C: - $ascii .= '\f'; - break; - case $ord_var_c == 0x0D: - $ascii .= '\r'; - break; - - case $ord_var_c == 0x22: - case $ord_var_c == 0x2F: - case $ord_var_c == 0x5C: - // double quote, slash, slosh - $ascii .= '\\'.$var{$c}; - break; - - case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): - // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; - break; - - case (($ord_var_c & 0xE0) == 0xC0): - // characters U-00000080 - U-000007FF, mask 110XXXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c + 1})); - $c += 1; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF0) == 0xE0): - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2})); - $c += 2; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF8) == 0xF0): - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3})); - $c += 3; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFC) == 0xF8): - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4})); - $c += 4; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFE) == 0xFC): - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4}), - ord($var{$c + 5})); - $c += 5; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - } - } - - return '"'.$ascii.'"'; - - case 'array': - /* - * As per JSON spec if any array key is not an integer - * we must treat the the whole array as an object. We - * also try to catch a sparsely populated associative - * array with numeric keys here because some JS engines - * will create an array with empty indexes up to - * max_index which can cause memory issues and because - * the keys, which may be relevant, will be remapped - * otherwise. - * - * As per the ECMA and JSON specification an object may - * have any string as a property. Unfortunately due to - * a hole in the ECMA specification if the key is a - * ECMA reserved word or starts with a digit the - * parameter is only accessible using ECMAScript's - * bracket notation. - */ - - // treat as a JSON object - if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { - $properties = array_map(array($this, 'name_value'), - array_keys($var), - array_values($var)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - } - - // treat it like a regular array - $elements = array_map(array($this, 'encode'), $var); - - foreach($elements as $element) { - if(Services_JSON::isError($element)) { - return $element; - } - } - - return '[' . join(',', $elements) . ']'; - - case 'object': - $vars = get_object_vars($var); - - $properties = array_map(array($this, 'name_value'), - array_keys($vars), - array_values($vars)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - - default: - return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) - ? 'null' - : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); - } - } - - /** - * array-walking function for use in generating JSON-formatted name-value pairs - * - * @param string $name name of key to use - * @param mixed $value reference to an array element to be encoded - * - * @return string JSON-formatted name-value pair, like '"name":value' - * @access private - */ - function name_value($name, $value) - { - $encoded_value = $this->encode($value); - - if(Services_JSON::isError($encoded_value)) { - return $encoded_value; - } - - return $this->encode(strval($name)) . ':' . $encoded_value; - } - - /** - * reduce a string by removing leading and trailing comments and whitespace - * - * @param $str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - * @access private - */ - function reduce_string($str) - { - $str = preg_replace(array( - - // eliminate single line comments in '// ...' form - '#^\s*//(.+)$#m', - - // eliminate multi-line comments in '/* ... */' form, at start of string - '#^\s*/\*(.+)\*/#Us', - - // eliminate multi-line comments in '/* ... */' form, at end of string - '#/\*(.+)\*/\s*$#Us' - - ), '', $str); - - // eliminate extraneous space - return trim($str); - } - - /** - * decodes a JSON string into appropriate variable - * - * @param string $str JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given JSON input string. - * See argument 1 to Services_JSON() above for object-output behavior. - * Note that decode() always returns strings - * in ASCII or UTF-8 format! - * @access public - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch (strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - $m = array(); - - if (is_numeric($str)) { - // Lookie-loo, it's a number - - // This would work on its own, but I'm trying to be - // good about returning integers where appropriate: - // return (float)$str; - - // Return float or int, as appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { - // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c < $strlen_chrs; ++$c) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); - - switch (true) { - case $substr_chrs_c_2 == '\b': - $utf8 .= chr(0x08); - ++$c; - break; - case $substr_chrs_c_2 == '\t': - $utf8 .= chr(0x09); - ++$c; - break; - case $substr_chrs_c_2 == '\n': - $utf8 .= chr(0x0A); - ++$c; - break; - case $substr_chrs_c_2 == '\f': - $utf8 .= chr(0x0C); - ++$c; - break; - case $substr_chrs_c_2 == '\r': - $utf8 .= chr(0x0D); - ++$c; - break; - - case $substr_chrs_c_2 == '\\"': - case $substr_chrs_c_2 == '\\\'': - case $substr_chrs_c_2 == '\\\\': - case $substr_chrs_c_2 == '\\/': - if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || - ($delim == "'" && $substr_chrs_c_2 != '\\"')) { - $utf8 .= $chrs{++$c}; - } - break; - - case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): - // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) - . chr(hexdec(substr($chrs, ($c + 4), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 5; - break; - - case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): - $utf8 .= $chrs{$c}; - break; - - case ($ord_chrs_c & 0xE0) == 0xC0: - // characters U-00000080 - U-000007FF, mask 110XXXXX - //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); - ++$c; - break; - - case ($ord_chrs_c & 0xF0) == 0xE0: - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); - $c += 2; - break; - - case ($ord_chrs_c & 0xF8) == 0xF0: - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); - $c += 3; - break; - - case ($ord_chrs_c & 0xFC) == 0xF8: - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); - $c += 4; - break; - - case ($ord_chrs_c & 0xFE) == 0xFC: - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); - $c += 5; - break; - - } - - } - - return $utf8; - - } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { - // array, or object notation - - if ($str{0} == '[') { - $stk = array(SERVICES_JSON_IN_ARR); - $arr = array(); - } else { - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = new stdClass(); - } - } - - array_push($stk, array('what' => SERVICES_JSON_SLICE, - 'where' => 0, - 'delim' => false)); - - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if ($chrs == '') { - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } else { - return $obj; - - } - } - - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c <= $strlen_chrs; ++$c) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { - // found a comma that is not inside a string, array, etc., - // OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - // we are in an object, so figure - // out the property name and set an - // element in an associative array, - // for now - $parts = array(); - - if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } - - } - - } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { - // found a quote, and we are not inside a string - array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif (($chrs{$c} == $top['delim']) && - ($top['what'] == SERVICES_JSON_IN_STR) && - ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { - // found a quote, we're in a string, and it's not escaped - // we know that it's not escaped becase there is _not_ an - // odd number of backslashes at the end of the string so far - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '[') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { - // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '{') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { - // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($substr_chrs_c_2 == '/*') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { - // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for ($i = $top['where']; $i <= $c; ++$i) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } - - } - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - return $obj; - - } - - } - } - } - - /** - * @todo Ultimately, this should just call PEAR::isError() - */ - function isError($data, $code = null) - { - if (class_exists('pear')) { - return PEAR::isError($data, $code); - } elseif (is_object($data) && (get_class($data) == 'services_json_error' || - is_subclass_of($data, 'services_json_error'))) { - return true; - } - - return false; - } -} - -if (class_exists('PEAR_Error')) { - - class Services_JSON_Error extends PEAR_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - parent::PEAR_Error($message, $code, $mode, $options, $userinfo); - } - } - -} else { - - /** - * @todo Ultimately, this class shall be descended from PEAR_Error - */ - class Services_JSON_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - - } - } - -} - -?> diff --git a/extlib/facebook/jsonwrapper/JSON/LICENSE b/extlib/facebook/jsonwrapper/JSON/LICENSE deleted file mode 100644 index 4ae6bef55..000000000 --- a/extlib/facebook/jsonwrapper/JSON/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/extlib/facebook/jsonwrapper/jsonwrapper.php b/extlib/facebook/jsonwrapper/jsonwrapper.php deleted file mode 100644 index 29509deba..000000000 --- a/extlib/facebook/jsonwrapper/jsonwrapper.php +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php b/extlib/facebook/jsonwrapper/jsonwrapper_inner.php deleted file mode 100644 index 36a3f2863..000000000 --- a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php +++ /dev/null @@ -1,23 +0,0 @@ -encode($arg); -} - -function json_decode($arg) -{ - global $services_json; - if (!isset($services_json)) { - $services_json = new Services_JSON(); - } - return $services_json->decode($arg); -} - -?> diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php deleted file mode 100644 index 81b2520a4..000000000 --- a/plugins/FBConnect/FBCLoginGroupNav.php +++ /dev/null @@ -1,114 +0,0 @@ -. - * - * @category Menu - * @package StatusNet - * @author Evan Prodromou - * @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); -} - -require_once INSTALLDIR . '/lib/widget.php'; - -/** - * Menu for login group of actions - * - * @category Output - * @package StatusNet - * @author Evan Prodromou - * @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/ - * - * @see Widget - */ - -class FBCLoginGroupNav extends Widget -{ - var $action = null; - - /** - * Construction - * - * @param Action $action current action, used for output - */ - - function __construct($action=null) - { - parent::__construct($action); - $this->action = $action; - } - - /** - * Show the menu - * - * @return void - */ - - function show() - { - $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); - $this->action->element('dt', null, _('Local views')); - $this->action->elementStart('dd'); - - // action => array('prompt', 'title') - $menu = array(); - - if (!common_config('site','openidonly')) { - $menu['login'] = array(_('Login'), - _('Login with a username and password')); - - if (!(common_config('site','closed') || common_config('site','inviteonly'))) { - $menu['register'] = array(_('Register'), - _('Sign up for a new account')); - } - } - - if (common_config('openid', 'enabled')) { - $menu['openidlogin'] = array(_('OpenID'), - _('Login or register with OpenID')); - } - - $menu['FBConnectLogin'] = array(_('Facebook'), - _('Login or register using Facebook')); - - $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); - } - - $this->action->elementEnd('ul'); - - $this->action->elementEnd('dd'); - $this->action->elementEnd('dl'); - } -} diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php deleted file mode 100644 index ed02371e2..000000000 --- a/plugins/FBConnect/FBCSettingsNav.php +++ /dev/null @@ -1,115 +0,0 @@ -. - * - * @category Menu - * @package StatusNet - * @author Evan Prodromou - * @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); -} - -require_once INSTALLDIR . '/lib/widget.php'; - -/** - * A widget for showing the connect group local nav menu - * - * @category Output - * @package StatusNet - * @author Evan Prodromou - * @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/ - * - * @see Widget - */ - -class FBCSettingsNav extends Widget -{ - var $action = null; - - /** - * Construction - * - * @param Action $action current action, used for output - */ - - function __construct($action=null) - { - parent::__construct($action); - $this->action = $action; - } - - /** - * Show the menu - * - * @return void - */ - - function show() - { - - $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); - $this->action->element('dt', null, _('Local views')); - $this->action->elementStart('dd'); - - # 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')); - } - $menu['FBConnectSettings'] = - array(_('Facebook'), - _('Facebook Connect settings')); - - $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); - } - - $this->action->elementEnd('ul'); - - $this->action->elementEnd('dd'); - $this->action->elementEnd('dl'); - } -} diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php deleted file mode 100644 index 2bc790d5a..000000000 --- a/plugins/FBConnect/FBC_XDReceiver.php +++ /dev/null @@ -1,68 +0,0 @@ -showPage(); - } - - function showPage() - { - // cache the xd_receiver - header('Cache-Control: max-age=225065900'); - header('Expires:'); - header('Pragma:'); - - $this->startXML('html'); - - $language = $this->getLanguage(); - - $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', - 'xml:lang' => $language, - 'lang' => $language)); - $this->elementStart('head'); - $this->element('title', null, 'cross domain receiver page'); - $this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js'); - $this->elementEnd('head'); - $this->elementStart('body'); - $this->elementEnd('body'); - - $this->elementEnd('html'); - } - -} - diff --git a/plugins/FBConnect/FBConnectAuth.php b/plugins/FBConnect/FBConnectAuth.php deleted file mode 100644 index 647d5def8..000000000 --- a/plugins/FBConnect/FBConnectAuth.php +++ /dev/null @@ -1,461 +0,0 @@ -. - * - * @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/ - */ - -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; - -class FBConnectauthAction extends Action -{ - var $fbuid = null; - var $fb_fields = null; - - function prepare($args) { - parent::prepare($args); - - $this->fbuid = getFacebook()->get_loggedin_user(); - - if ($this->fbuid > 0) { - $this->fb_fields = $this->getFacebookFields($this->fbuid, - array('first_name', 'last_name', 'name')); - } else { - list($proxy, $ip) = common_client_ip(); - - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - "Failed auth attempt, proxy = $proxy, ip = $ip."); - - $this->clientError(_('You must be logged into Facebook to ' . - 'use Facebook Connect.')); - } - - return true; - } - - function handle($args) - { - parent::handle($args); - - if (common_is_real_login()) { - - // User is already logged in. Does she already have a linked Facebook acct? - $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); - - if (!empty($flink)) { - - // User already has a linked Facebook account and shouldn't be here - common_debug('Facebook Connect Plugin - ' . - 'There is already a local user (' . $flink->user_id . - ') linked with this Facebook (' . $this->fbuid . ').'); - - // We don't want these cookies - getFacebook()->clear_cookie_state(); - - $this->clientError(_('There is already a local user linked with this Facebook.')); - - } else { - - // User came from the Facebook connect settings tab, and - // probably just wants to link/relink their Facebook account - $this->connectUser(); - } - - } 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->connectNewUser(); - } else { - common_debug('Facebook Connect Plugin - ' . - print_r($this->args, true)); - $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 Facebook 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 _('Facebook Account Setup'); - } - - function showForm($error=null, $username=null) - { - $this->error = $error; - $this->username = $username; - - $this->showPage(); - } - - function showPage() - { - parent::showPage(); - } - - function showContent() - { - if (!empty($this->message_text)) { - $this->element('p', null, $this->message); - return; - } - - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_settings_facebook_connect', - 'class' => 'form_settings', - 'action' => common_local_url('FBConnectAuth'))); - $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options')); - $this->element('legend', null, _('Connection options')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->element('input', array('type' => 'checkbox', - 'id' => 'license', - 'class' => 'checkbox', - 'name' => 'license', - 'value' => 'true')); - $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); - $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('label'); - $this->elementEnd('li'); - $this->elementEnd('ul'); - - $this->elementStart('fieldset'); - $this->hidden('token', common_session_token()); - $this->element('legend', null, - _('Create new account')); - $this->element('p', null, - _('Create a new user with this nickname.')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->input('newname', _('New nickname'), - ($this->username) ? $this->username : '', - _('1-64 lowercase letters or numbers, no punctuation or spaces')); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('create', _('Create')); - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset'); - $this->element('legend', 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 Facebook.')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->input('nickname', _('Existing nickname')); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->password('password', _('Password')); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('connect', _('Connect')); - $this->elementEnd('fieldset'); - - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - } - - function message($msg) - { - $this->message_text = $msg; - $this->showPage(); - } - - function createNewUser() - { - 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' => NICKNAME_FMT))) { - $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; - } - - $fullname = trim($this->fb_fields['firstname'] . - ' ' . $this->fb_fields['lastname']); - - $args = array('nickname' => $nickname, 'fullname' => $fullname); - - if (!empty($invite)) { - $args['code'] = $invite->code; - } - - $user = User::register($args); - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (!$result) { - $this->serverError(_('Error connecting user to Facebook.')); - return; - } - - common_set_user($user); - common_real_login(true); - - common_debug('Facebook Connect Plugin - ' . - "Registered new user $user->id from Facebook user $this->fbuid"); - - common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), - 303); - } - - function connectNewUser() - { - $nickname = $this->trimmed('nickname'); - $password = $this->trimmed('password'); - - if (!common_check_user($nickname, $password)) { - $this->showForm(_('Invalid username or password.')); - return; - } - - $user = User::staticGet('nickname', $nickname); - - if (!empty($user)) { - common_debug('Facebook Connect Plugin - ' . - "Legit user to connect to Facebook: $nickname"); - } - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (!$result) { - $this->serverError(_('Error connecting user to Facebook.')); - return; - } - - common_debug('Facebook Connnect Plugin - ' . - "Connected Facebook user $this->fbuid to local user $user->id"); - - common_set_user($user); - common_real_login(true); - - $this->goHome($user->nickname); - } - - function connectUser() - { - $user = common_current_user(); - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (empty($result)) { - $this->serverError(_('Error connecting user to Facebook.')); - return; - } - - common_debug('Facebook Connect Plugin - ' . - "Connected Facebook user $this->fbuid to local user $user->id"); - - // Return to Facebook connection settings tab - common_redirect(common_local_url('FBConnectSettings'), 303); - } - - function tryLogin() - { - common_debug('Facebook Connect Plugin - ' . - "Trying login for Facebook user $this->fbuid."); - - $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); - - if (!empty($flink)) { - $user = $flink->getUser(); - - if (!empty($user)) { - - common_debug('Facebook Connect Plugin - ' . - "Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)"); - - common_set_user($user); - common_real_login(true); - $this->goHome($user->nickname); - } - - } else { - - common_debug('Facebook Connect Plugin - ' . - "No flink found for fbuid: $this->fbuid - new user"); - - $this->showForm(null, $this->bestNewNickname()); - } - } - - 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 flinkUser($user_id, $fbuid) - { - $flink = new Foreign_link(); - $flink->user_id = $user_id; - $flink->foreign_id = $fbuid; - $flink->service = FACEBOOK_CONNECT_SERVICE; - $flink->created = common_sql_now(); - - $flink_id = $flink->insert(); - - return $flink_id; - } - - function bestNewNickname() - { - if (!empty($this->fb_fields['name'])) { - $nickname = $this->nicknamize($this->fb_fields['name']); - if ($this->isNewNickname($nickname)) { - return $nickname; - } - } - - // Try the full name - - $fullname = trim($this->fb_fields['firstname'] . - ' ' . $this->fb_fields['lastname']); - - if (!empty($fullname)) { - $fullname = $this->nicknamize($fullname); - if ($this->isNewNickname($fullname)) { - return $fullname; - } - } - - return null; - } - - // Given a string, try to make it work as a nickname - - function nicknamize($str) - { - $str = preg_replace('/\W/', '', $str); - return strtolower($str); - } - - function isNewNickname($str) - { - if (!Validate::string($str, array('min_length' => 1, - 'max_length' => 64, - 'format' => NICKNAME_FMT))) { - return false; - } - if (!User::allowed_nickname($str)) { - return false; - } - if (User::staticGet('nickname', $str)) { - return false; - } - return true; - } - - // XXX: Consider moving this to lib/facebookutil.php - function getFacebookFields($fb_uid, $fields) { - try { - - $facebook = getFacebook(); - - $infos = $facebook->api_client->users_getInfo($fb_uid, $fields); - - if (empty($infos)) { - return null; - } - return reset($infos); - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - "Facebook client failure when requesting " . - join(",", $fields) . " on uid " . $fb_uid . - " : ". $e->getMessage()); - return null; - } - } - -} diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php deleted file mode 100644 index 5696d8848..000000000 --- a/plugins/FBConnect/FBConnectLogin.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; - -class FBConnectLoginAction extends Action -{ - function handle($args) - { - parent::handle($args); - - if (common_is_real_login()) { - $this->clientError(_('Already logged in.')); - } - - $this->showPage(); - } - - function getInstructions() - { - return _('Login with your Facebook Account'); - } - - function showPageNotice() - { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - $this->elementStart('div', 'instructions'); - $this->raw($output); - $this->elementEnd('div'); - } - - function title() - { - return _('Facebook Login'); - } - - function showContent() { - - $this->elementStart('fieldset'); - $this->element('fb:login-button', array('onlogin' => 'goto_login()', - 'length' => 'long')); - - $this->elementEnd('fieldset'); - } - -} diff --git a/plugins/FBConnect/FBConnectPlugin.css b/plugins/FBConnect/FBConnectPlugin.css deleted file mode 100644 index 49217bf13..000000000 --- a/plugins/FBConnect/FBConnectPlugin.css +++ /dev/null @@ -1,36 +0,0 @@ -/** Styles for Facebook logo and Facebook user profile avatar. - * - * @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/ - */ - -#site_nav_global_primary #nav_fb { -position:relative; -margin-left:18px; -} - -#nav_fb #fbc_profile-pic { -position:absolute; -top:-3px; -left:-18px; -display:inline; -border:1px solid #3B5998; -padding:1px; -} - -#nav_fb #fb_favicon { -position:absolute; -top:-13px; -left:-25px; -display:inline; -} - -#settings_facebook_connect_options legend { -display:none; -} -#form_settings_facebook_connect fieldset fieldset legend { -display:block; -} diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php deleted file mode 100644 index 0dacf9012..000000000 --- a/plugins/FBConnect/FBConnectPlugin.php +++ /dev/null @@ -1,367 +0,0 @@ -. - * - * @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') && !defined('LACONICA')) { - exit(1); -} - -define("FACEBOOK_CONNECT_SERVICE", 3); - -require_once INSTALLDIR . '/lib/facebookutil.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php'; - -/** - * Plugin to enable Facebook Connect - * - * @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 FBConnectPlugin extends Plugin -{ - function __construct() - { - parent::__construct(); - } - - // Hook in new actions - function onRouterInitialized(&$m) { - $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); - $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); - $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); - $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); - } - - // Add in xmlns:fb - function onStartShowHTML($action) - { - - if ($this->reqFbScripts($action)) { - - // XXX: Horrible hack to make Safari, FF2, and Chrome work with - // Facebook Connect. These browser cannot use Facebook's - // DOM parsing routines unless the mime type of the page is - // text/html even though Facebook Connect uses XHTML. This is - // A bug in Facebook Connect, and this is a temporary solution - // until they fix their JavaScript libs. - header('Content-Type: text/html'); - - $action->extraHeaders(); - - $action->startXML('html'); - - $language = $action->getLanguage(); - - $action->elementStart('html', - array('xmlns' => 'http://www.w3.org/1999/xhtml', - 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', - 'xml:lang' => $language, - 'lang' => $language)); - - return false; - - } else { - - return true; - } - } - - // Note: this script needs to appear in the - - function onEndShowScripts($action) - { - if ($this->reqFbScripts($action)) { - - $apikey = common_config('facebook', 'apikey'); - $plugin_path = common_path('plugins/FBConnect'); - - $login_url = common_local_url('FBConnectAuth'); - $logout_url = common_local_url('logout'); - - // XXX: Facebook says we don't need this FB_RequireFeatures(), - // but we actually do, for IE and Safari. Gar. - - $js = ''; - - $js = sprintf($js, $apikey, $login_url, $logout_url); - - // Compress the bugger down a bit - $js = str_replace(' ', '', $js); - - $action->raw(" $js"); // leading two spaces to make it line up - } - - } - - // Note: this script needs to appear as close as possible to - - function onEndShowFooter($action) - { - if ($this->reqFbScripts($action)) { - $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'); - } - } - - function onEndShowStatusNetStyles($action) - { - if ($this->reqFbScripts($action)) { - $action->cssLink('plugins/FBConnect/FBConnectPlugin.css'); - } - } - - /** - * Does the Action we're plugged into require the FB Scripts? We only - * want to output FB namespace, scripts, CSS, etc. on the pages that - * really need them. - * - * @param Action the action in question - * - * @return boolean true - */ - - function reqFbScripts($action) { - - // If you're logged in w/FB Connect, you always need the FB stuff - - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - return true; - } - - // List of actions that require FB stuff - - $needy = array('FBConnectLoginAction', - 'FBConnectauthAction', - 'FBConnectSettingsAction'); - - if (in_array(get_class($action), $needy)) { - return true; - } - - return false; - - } - - /** - * Is the user currently logged in with FB Connect? - * - * @return mixed $fbuid the Facebook ID of the logged in user, or null - */ - - function loggedIn() - { - $user = common_current_user(); - - if (!empty($user)) { - - $flink = Foreign_link::getByUserId($user->id, - FACEBOOK_CONNECT_SERVICE); - $fbuid = 0; - - if (!empty($flink)) { - - try { - - $facebook = getFacebook(); - $fbuid = $facebook->get_loggedin_user(); - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Problem getting Facebook user: ' . - $e->getMessage()); - } - - if ($fbuid > 0) { - return $fbuid; - } - } - } - - return null; - } - - function onStartPrimaryNav($action) - { - - $user = common_current_user(); - $connect = 'FBConnectSettings'; - if (common_config('xmpp', 'enabled')) { - $connect = 'imsettings'; - } else if (common_config('sms', 'enabled')) { - $connect = 'smssettings'; - } else if (common_config('twitter', 'enabled')) { - $connect = 'twittersettings'; - } - - if (!empty($user)) { - - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - - /* Default FB silhouette pic for FB users who haven't - uploaded a profile pic yet. */ - - $silhouetteUrl = - 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; - - $url = $this->getProfilePicURL($fbuid); - - $action->elementStart('li', array('id' => 'nav_fb')); - - $action->element('img', array('id' => 'fbc_profile-pic', - 'src' => (!empty($url)) ? $url : $silhouetteUrl, - 'alt' => 'Facebook Connect User', - 'width' => '16'), ''); - - $iconurl = common_path('plugins/FBConnect/fbfavicon.ico'); - $action->element('img', array('id' => 'fb_favicon', - 'src' => $iconurl)); - - $action->elementEnd('li'); - - } - } - - return true; - } - - function onStartShowLocalNavBlock($action) - { - $action_name = get_class($action); - - $login_actions = array('LoginAction', 'RegisterAction', - 'OpenidloginAction', 'FBConnectLoginAction'); - - if (in_array($action_name, $login_actions)) { - $nav = new FBCLoginGroupNav($action); - $nav->show(); - return false; - } - - $connect_actions = array('SmssettingsAction', 'ImsettingsAction', - 'TwittersettingsAction', 'FBConnectSettingsAction'); - - if (in_array($action_name, $connect_actions)) { - $nav = new FBCSettingsNav($action); - $nav->show(); - return false; - } - - return true; - } - - function onStartLogout($action) - { - $action->logout(); - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - try { - $facebook = getFacebook(); - $facebook->expire_session(); - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Could\'t logout of Facebook: ' . - $e->getMessage()); - } - } - - return true; - } - - function getProfilePicURL($fbuid) - { - - $facebook = getFacebook(); - $url = null; - - try { - - $fqry = 'SELECT pic_square FROM user WHERE uid = %s'; - - $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid)); - - if (!empty($result)) { - $url = $result[0]['pic_square']; - } - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - "Facebook client failure requesting profile pic!"); - } - - return $url; - - } - -} diff --git a/plugins/FBConnect/FBConnectSettings.php b/plugins/FBConnect/FBConnectSettings.php deleted file mode 100644 index 911c56787..000000000 --- a/plugins/FBConnect/FBConnectSettings.php +++ /dev/null @@ -1,203 +0,0 @@ -. - * - * @category Settings - * @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); -} - -require_once INSTALLDIR.'/lib/connectsettingsaction.php'; - -/** - * Facebook Connect settings action - * - * @category Settings - * @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 FBConnectSettingsAction extends ConnectSettingsAction -{ - /** - * Title of the page - * - * @return string Title of the page - */ - - function title() - { - return _('Facebook Connect Settings'); - } - - /** - * Instructions for use - * - * @return instructions for use - */ - - function getInstructions() - { - return _('Manage how your account connects to Facebook'); - } - - /** - * Content area of the page - * - * Shows a form for uploading an avatar. - * - * @return void - */ - - function showContent() - { - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); - - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_settings_facebook', - 'class' => 'form_settings', - 'action' => - common_local_url('FBConnectSettings'))); - - if (!$flink) { - - $this->element('p', 'instructions', - _('There is no Facebook user connected to this account.')); - - $this->element('fb:login-button', array('onlogin' => 'goto_login()', - 'length' => 'long')); - - } else { - - $this->element('p', 'form_note', - _('Connected Facebook user')); - - $this->elementStart('p', array('class' => 'facebook-user-display')); - $this->elementStart('fb:profile-pic', - array('uid' => $flink->foreign_id, - 'size' => 'small', - 'linked' => 'true', - 'facebook-logo' => 'true')); - $this->elementEnd('fb:profile-pic'); - - $this->elementStart('fb:name', array('uid' => $flink->foreign_id, - 'useyou' => 'false')); - $this->elementEnd('fb:name'); - $this->elementEnd('p'); - - $this->hidden('token', common_session_token()); - - $this->elementStart('fieldset'); - - $this->element('legend', null, _('Disconnect my account from Facebook')); - - if (!$user->password) { - - $this->elementStart('p', array('class' => 'form_guide')); - $this->text(_('Disconnecting your Faceboook ' . - 'would make it impossible to log in! Please ')); - $this->element('a', - array('href' => common_local_url('passwordsettings')), - _('set a password')); - - $this->text(_(' first.')); - $this->elementEnd('p'); - } else { - - $note = 'Keep your %s account but disconnect from Facebook. ' . - 'You\'ll use your %s password to log in.'; - - $site = common_config('site', 'name'); - - $this->element('p', 'instructions', - sprintf($note, $site, $site)); - - $this->submit('disconnect', _('Disconnect')); - } - - $this->elementEnd('fieldset'); - } - - $this->elementEnd('form'); - } - - /** - * Handle post - * - * Disconnects the current Facebook user from the current user's account - * - * @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('disconnect')) { - - $user = common_current_user(); - - $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); - $result = $flink->delete(); - - if ($result === false) { - common_log_db_error($user, 'DELETE', __FILE__); - $this->serverError(_('Couldn\'t delete link to Facebook.')); - return; - } - - try { - - // Clear FB Connect cookies out - $facebook = getFacebook(); - $facebook->clear_cookie_state(); - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Couldn\'t clear Facebook cookies: ' . - $e->getMessage()); - } - - $this->showForm(_('You have disconnected from Facebook.'), true); - - } else { - $this->showForm(_('Not sure what you\'re trying to do.')); - return; - } - - } - -} diff --git a/plugins/FBConnect/README b/plugins/FBConnect/README deleted file mode 100644 index 77d57eff9..000000000 --- a/plugins/FBConnect/README +++ /dev/null @@ -1,76 +0,0 @@ -This plugin allows you to utilize Facebook Connect with StatusNet. -Supported Facebook Connect features: - -- Authenticate (register/login/logout -- works similar to OpenID) -- Associate an existing StatusNet account with a Facebook account -- Disconnect a Facebook account from a StatusNet account - -Future planned functionality: - -- Invite Facebook friends to use your StatusNet installation -- Auto-subscribe Facebook friends already using StatusNet -- Share StatusNet favorite notices to your Facebook stream - -To use the plugin you will need to configure a Facebook application -to point to your StatusNet installation (see the Installation section -below). - -Installation -============ - -If you don't already have the built-in Facebook application configured, -you'll need to log into Facebook and create/configure a new application. -Please follow the instructions in the section titled, "Setting Up Your -Application and Getting an API Key," on the following page of the -Facebook developer wiki: - - http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site - -If you already are using the build-in StatusNet Facebook application, -you can modify your existing application's configuration using the -Facebook Developer Application on Facebook. Use it to edit your -application settings, and under the 'Connect' tab, change the 'Connect -URL' to be the main URL for your StatusNet site. E.g.: - - http://SITE/PATH_TO_STATUSNET/ - -After you application is created and configured, you'll need to add its -API key and secret to your StatusNet config.php file: - - $config['facebook']['apikey'] = 'APIKEY'; - $config['facebook']['secret'] = 'SECRET'; - -Finally, to enable the plugin, add the following stanza to your -config.php: - - addPlugin('FBConnect'); - -To try out the plugin, fire up your browser and connect to: - - http://SITE/PATH_TO_STATUSNET/main/facebooklogin - -or, if you do not have fancy URLs turned on: - - http://SITE/PATH_TO_STATUSNET/index.php/main/facebooklogin - -You should see a page with a blue button that says: "Connect with -Facebook". - -Connect/Disconnect existing account -=================================== - -If the Facebook Connect plugin is enabled, there will be a new Facebook -Connect Settings tab under each user's Connect menu. Users can connect -and disconnect to their Facebook accounts from it. Note: Before a user -can disconnect from Facebook, she must set a normal StatusNet password. -Otherwise, she might not be able to login in to her account in the -future. This is usually only required for users who have used Facebook -Connect to register their StatusNet account, and therefore haven't -already set a local password. - -Helpful links -============= - -Facebook Connect Homepage: -http://developers.facebook.com/connect.php - diff --git a/plugins/FBConnect/fbfavicon.ico b/plugins/FBConnect/fbfavicon.ico deleted file mode 100644 index c57c0342f..000000000 Binary files a/plugins/FBConnect/fbfavicon.ico and /dev/null differ diff --git a/plugins/Facebook/FBCLoginGroupNav.php b/plugins/Facebook/FBCLoginGroupNav.php new file mode 100644 index 000000000..81b2520a4 --- /dev/null +++ b/plugins/Facebook/FBCLoginGroupNav.php @@ -0,0 +1,114 @@ +. + * + * @category Menu + * @package StatusNet + * @author Evan Prodromou + * @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); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +/** + * Menu for login group of actions + * + * @category Output + * @package StatusNet + * @author Evan Prodromou + * @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/ + * + * @see Widget + */ + +class FBCLoginGroupNav extends Widget +{ + var $action = null; + + /** + * Construction + * + * @param Action $action current action, used for output + */ + + function __construct($action=null) + { + parent::__construct($action); + $this->action = $action; + } + + /** + * Show the menu + * + * @return void + */ + + function show() + { + $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); + $this->action->element('dt', null, _('Local views')); + $this->action->elementStart('dd'); + + // action => array('prompt', 'title') + $menu = array(); + + if (!common_config('site','openidonly')) { + $menu['login'] = array(_('Login'), + _('Login with a username and password')); + + if (!(common_config('site','closed') || common_config('site','inviteonly'))) { + $menu['register'] = array(_('Register'), + _('Sign up for a new account')); + } + } + + if (common_config('openid', 'enabled')) { + $menu['openidlogin'] = array(_('OpenID'), + _('Login or register with OpenID')); + } + + $menu['FBConnectLogin'] = array(_('Facebook'), + _('Login or register using Facebook')); + + $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); + } + + $this->action->elementEnd('ul'); + + $this->action->elementEnd('dd'); + $this->action->elementEnd('dl'); + } +} diff --git a/plugins/Facebook/FBCSettingsNav.php b/plugins/Facebook/FBCSettingsNav.php new file mode 100644 index 000000000..ed02371e2 --- /dev/null +++ b/plugins/Facebook/FBCSettingsNav.php @@ -0,0 +1,115 @@ +. + * + * @category Menu + * @package StatusNet + * @author Evan Prodromou + * @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); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +/** + * A widget for showing the connect group local nav menu + * + * @category Output + * @package StatusNet + * @author Evan Prodromou + * @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/ + * + * @see Widget + */ + +class FBCSettingsNav extends Widget +{ + var $action = null; + + /** + * Construction + * + * @param Action $action current action, used for output + */ + + function __construct($action=null) + { + parent::__construct($action); + $this->action = $action; + } + + /** + * Show the menu + * + * @return void + */ + + function show() + { + + $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); + $this->action->element('dt', null, _('Local views')); + $this->action->elementStart('dd'); + + # 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')); + } + $menu['FBConnectSettings'] = + array(_('Facebook'), + _('Facebook Connect settings')); + + $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); + } + + $this->action->elementEnd('ul'); + + $this->action->elementEnd('dd'); + $this->action->elementEnd('dl'); + } +} diff --git a/plugins/Facebook/FBC_XDReceiver.php b/plugins/Facebook/FBC_XDReceiver.php new file mode 100644 index 000000000..2bc790d5a --- /dev/null +++ b/plugins/Facebook/FBC_XDReceiver.php @@ -0,0 +1,68 @@ +showPage(); + } + + function showPage() + { + // cache the xd_receiver + header('Cache-Control: max-age=225065900'); + header('Expires:'); + header('Pragma:'); + + $this->startXML('html'); + + $language = $this->getLanguage(); + + $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xml:lang' => $language, + 'lang' => $language)); + $this->elementStart('head'); + $this->element('title', null, 'cross domain receiver page'); + $this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js'); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->elementEnd('body'); + + $this->elementEnd('html'); + } + +} + diff --git a/plugins/Facebook/FBConnectAuth.php b/plugins/Facebook/FBConnectAuth.php new file mode 100644 index 000000000..647d5def8 --- /dev/null +++ b/plugins/Facebook/FBConnectAuth.php @@ -0,0 +1,461 @@ +. + * + * @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/ + */ + +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; + +class FBConnectauthAction extends Action +{ + var $fbuid = null; + var $fb_fields = null; + + function prepare($args) { + parent::prepare($args); + + $this->fbuid = getFacebook()->get_loggedin_user(); + + if ($this->fbuid > 0) { + $this->fb_fields = $this->getFacebookFields($this->fbuid, + array('first_name', 'last_name', 'name')); + } else { + list($proxy, $ip) = common_client_ip(); + + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + "Failed auth attempt, proxy = $proxy, ip = $ip."); + + $this->clientError(_('You must be logged into Facebook to ' . + 'use Facebook Connect.')); + } + + return true; + } + + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + + // User is already logged in. Does she already have a linked Facebook acct? + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); + + if (!empty($flink)) { + + // User already has a linked Facebook account and shouldn't be here + common_debug('Facebook Connect Plugin - ' . + 'There is already a local user (' . $flink->user_id . + ') linked with this Facebook (' . $this->fbuid . ').'); + + // We don't want these cookies + getFacebook()->clear_cookie_state(); + + $this->clientError(_('There is already a local user linked with this Facebook.')); + + } else { + + // User came from the Facebook connect settings tab, and + // probably just wants to link/relink their Facebook account + $this->connectUser(); + } + + } 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->connectNewUser(); + } else { + common_debug('Facebook Connect Plugin - ' . + print_r($this->args, true)); + $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 Facebook 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 _('Facebook Account Setup'); + } + + function showForm($error=null, $username=null) + { + $this->error = $error; + $this->username = $username; + + $this->showPage(); + } + + function showPage() + { + parent::showPage(); + } + + function showContent() + { + if (!empty($this->message_text)) { + $this->element('p', null, $this->message); + return; + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_facebook_connect', + 'class' => 'form_settings', + 'action' => common_local_url('FBConnectAuth'))); + $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options')); + $this->element('legend', null, _('Connection options')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->element('input', array('type' => 'checkbox', + 'id' => 'license', + 'class' => 'checkbox', + 'name' => 'license', + 'value' => 'true')); + $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); + $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('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, + _('Create new account')); + $this->element('p', null, + _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('newname', _('New nickname'), + ($this->username) ? $this->username : '', + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('create', _('Create')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + $this->element('legend', 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 Facebook.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); + + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function message($msg) + { + $this->message_text = $msg; + $this->showPage(); + } + + function createNewUser() + { + 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' => NICKNAME_FMT))) { + $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; + } + + $fullname = trim($this->fb_fields['firstname'] . + ' ' . $this->fb_fields['lastname']); + + $args = array('nickname' => $nickname, 'fullname' => $fullname); + + if (!empty($invite)) { + $args['code'] = $invite->code; + } + + $user = User::register($args); + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_set_user($user); + common_real_login(true); + + common_debug('Facebook Connect Plugin - ' . + "Registered new user $user->id from Facebook user $this->fbuid"); + + common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), + 303); + } + + function connectNewUser() + { + $nickname = $this->trimmed('nickname'); + $password = $this->trimmed('password'); + + if (!common_check_user($nickname, $password)) { + $this->showForm(_('Invalid username or password.')); + return; + } + + $user = User::staticGet('nickname', $nickname); + + if (!empty($user)) { + common_debug('Facebook Connect Plugin - ' . + "Legit user to connect to Facebook: $nickname"); + } + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_debug('Facebook Connnect Plugin - ' . + "Connected Facebook user $this->fbuid to local user $user->id"); + + common_set_user($user); + common_real_login(true); + + $this->goHome($user->nickname); + } + + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (empty($result)) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_debug('Facebook Connect Plugin - ' . + "Connected Facebook user $this->fbuid to local user $user->id"); + + // Return to Facebook connection settings tab + common_redirect(common_local_url('FBConnectSettings'), 303); + } + + function tryLogin() + { + common_debug('Facebook Connect Plugin - ' . + "Trying login for Facebook user $this->fbuid."); + + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); + + if (!empty($flink)) { + $user = $flink->getUser(); + + if (!empty($user)) { + + common_debug('Facebook Connect Plugin - ' . + "Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)"); + + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); + } + + } else { + + common_debug('Facebook Connect Plugin - ' . + "No flink found for fbuid: $this->fbuid - new user"); + + $this->showForm(null, $this->bestNewNickname()); + } + } + + 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 flinkUser($user_id, $fbuid) + { + $flink = new Foreign_link(); + $flink->user_id = $user_id; + $flink->foreign_id = $fbuid; + $flink->service = FACEBOOK_CONNECT_SERVICE; + $flink->created = common_sql_now(); + + $flink_id = $flink->insert(); + + return $flink_id; + } + + function bestNewNickname() + { + if (!empty($this->fb_fields['name'])) { + $nickname = $this->nicknamize($this->fb_fields['name']); + if ($this->isNewNickname($nickname)) { + return $nickname; + } + } + + // Try the full name + + $fullname = trim($this->fb_fields['firstname'] . + ' ' . $this->fb_fields['lastname']); + + if (!empty($fullname)) { + $fullname = $this->nicknamize($fullname); + if ($this->isNewNickname($fullname)) { + return $fullname; + } + } + + return null; + } + + // Given a string, try to make it work as a nickname + + function nicknamize($str) + { + $str = preg_replace('/\W/', '', $str); + return strtolower($str); + } + + function isNewNickname($str) + { + if (!Validate::string($str, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + return false; + } + if (!User::allowed_nickname($str)) { + return false; + } + if (User::staticGet('nickname', $str)) { + return false; + } + return true; + } + + // XXX: Consider moving this to lib/facebookutil.php + function getFacebookFields($fb_uid, $fields) { + try { + + $facebook = getFacebook(); + + $infos = $facebook->api_client->users_getInfo($fb_uid, $fields); + + if (empty($infos)) { + return null; + } + return reset($infos); + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + "Facebook client failure when requesting " . + join(",", $fields) . " on uid " . $fb_uid . + " : ". $e->getMessage()); + return null; + } + } + +} diff --git a/plugins/Facebook/FBConnectLogin.php b/plugins/Facebook/FBConnectLogin.php new file mode 100644 index 000000000..5696d8848 --- /dev/null +++ b/plugins/Facebook/FBConnectLogin.php @@ -0,0 +1,67 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; + +class FBConnectLoginAction extends Action +{ + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + $this->clientError(_('Already logged in.')); + } + + $this->showPage(); + } + + function getInstructions() + { + return _('Login with your Facebook Account'); + } + + function showPageNotice() + { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); + } + + function title() + { + return _('Facebook Login'); + } + + function showContent() { + + $this->elementStart('fieldset'); + $this->element('fb:login-button', array('onlogin' => 'goto_login()', + 'length' => 'long')); + + $this->elementEnd('fieldset'); + } + +} diff --git a/plugins/Facebook/FBConnectPlugin.css b/plugins/Facebook/FBConnectPlugin.css new file mode 100644 index 000000000..49217bf13 --- /dev/null +++ b/plugins/Facebook/FBConnectPlugin.css @@ -0,0 +1,36 @@ +/** Styles for Facebook logo and Facebook user profile avatar. + * + * @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/ + */ + +#site_nav_global_primary #nav_fb { +position:relative; +margin-left:18px; +} + +#nav_fb #fbc_profile-pic { +position:absolute; +top:-3px; +left:-18px; +display:inline; +border:1px solid #3B5998; +padding:1px; +} + +#nav_fb #fb_favicon { +position:absolute; +top:-13px; +left:-25px; +display:inline; +} + +#settings_facebook_connect_options legend { +display:none; +} +#form_settings_facebook_connect fieldset fieldset legend { +display:block; +} diff --git a/plugins/Facebook/FBConnectPlugin.php b/plugins/Facebook/FBConnectPlugin.php new file mode 100644 index 000000000..0dacf9012 --- /dev/null +++ b/plugins/Facebook/FBConnectPlugin.php @@ -0,0 +1,367 @@ +. + * + * @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') && !defined('LACONICA')) { + exit(1); +} + +define("FACEBOOK_CONNECT_SERVICE", 3); + +require_once INSTALLDIR . '/lib/facebookutil.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php'; + +/** + * Plugin to enable Facebook Connect + * + * @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 FBConnectPlugin extends Plugin +{ + function __construct() + { + parent::__construct(); + } + + // Hook in new actions + function onRouterInitialized(&$m) { + $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); + $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); + $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); + $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); + } + + // Add in xmlns:fb + function onStartShowHTML($action) + { + + if ($this->reqFbScripts($action)) { + + // XXX: Horrible hack to make Safari, FF2, and Chrome work with + // Facebook Connect. These browser cannot use Facebook's + // DOM parsing routines unless the mime type of the page is + // text/html even though Facebook Connect uses XHTML. This is + // A bug in Facebook Connect, and this is a temporary solution + // until they fix their JavaScript libs. + header('Content-Type: text/html'); + + $action->extraHeaders(); + + $action->startXML('html'); + + $language = $action->getLanguage(); + + $action->elementStart('html', + array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', + 'xml:lang' => $language, + 'lang' => $language)); + + return false; + + } else { + + return true; + } + } + + // Note: this script needs to appear in the + + function onEndShowScripts($action) + { + if ($this->reqFbScripts($action)) { + + $apikey = common_config('facebook', 'apikey'); + $plugin_path = common_path('plugins/FBConnect'); + + $login_url = common_local_url('FBConnectAuth'); + $logout_url = common_local_url('logout'); + + // XXX: Facebook says we don't need this FB_RequireFeatures(), + // but we actually do, for IE and Safari. Gar. + + $js = ''; + + $js = sprintf($js, $apikey, $login_url, $logout_url); + + // Compress the bugger down a bit + $js = str_replace(' ', '', $js); + + $action->raw(" $js"); // leading two spaces to make it line up + } + + } + + // Note: this script needs to appear as close as possible to + + function onEndShowFooter($action) + { + if ($this->reqFbScripts($action)) { + $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'); + } + } + + function onEndShowStatusNetStyles($action) + { + if ($this->reqFbScripts($action)) { + $action->cssLink('plugins/FBConnect/FBConnectPlugin.css'); + } + } + + /** + * Does the Action we're plugged into require the FB Scripts? We only + * want to output FB namespace, scripts, CSS, etc. on the pages that + * really need them. + * + * @param Action the action in question + * + * @return boolean true + */ + + function reqFbScripts($action) { + + // If you're logged in w/FB Connect, you always need the FB stuff + + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + return true; + } + + // List of actions that require FB stuff + + $needy = array('FBConnectLoginAction', + 'FBConnectauthAction', + 'FBConnectSettingsAction'); + + if (in_array(get_class($action), $needy)) { + return true; + } + + return false; + + } + + /** + * Is the user currently logged in with FB Connect? + * + * @return mixed $fbuid the Facebook ID of the logged in user, or null + */ + + function loggedIn() + { + $user = common_current_user(); + + if (!empty($user)) { + + $flink = Foreign_link::getByUserId($user->id, + FACEBOOK_CONNECT_SERVICE); + $fbuid = 0; + + if (!empty($flink)) { + + try { + + $facebook = getFacebook(); + $fbuid = $facebook->get_loggedin_user(); + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Problem getting Facebook user: ' . + $e->getMessage()); + } + + if ($fbuid > 0) { + return $fbuid; + } + } + } + + return null; + } + + function onStartPrimaryNav($action) + { + + $user = common_current_user(); + $connect = 'FBConnectSettings'; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } else if (common_config('twitter', 'enabled')) { + $connect = 'twittersettings'; + } + + if (!empty($user)) { + + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + + /* Default FB silhouette pic for FB users who haven't + uploaded a profile pic yet. */ + + $silhouetteUrl = + 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; + + $url = $this->getProfilePicURL($fbuid); + + $action->elementStart('li', array('id' => 'nav_fb')); + + $action->element('img', array('id' => 'fbc_profile-pic', + 'src' => (!empty($url)) ? $url : $silhouetteUrl, + 'alt' => 'Facebook Connect User', + 'width' => '16'), ''); + + $iconurl = common_path('plugins/FBConnect/fbfavicon.ico'); + $action->element('img', array('id' => 'fb_favicon', + 'src' => $iconurl)); + + $action->elementEnd('li'); + + } + } + + return true; + } + + function onStartShowLocalNavBlock($action) + { + $action_name = get_class($action); + + $login_actions = array('LoginAction', 'RegisterAction', + 'OpenidloginAction', 'FBConnectLoginAction'); + + if (in_array($action_name, $login_actions)) { + $nav = new FBCLoginGroupNav($action); + $nav->show(); + return false; + } + + $connect_actions = array('SmssettingsAction', 'ImsettingsAction', + 'TwittersettingsAction', 'FBConnectSettingsAction'); + + if (in_array($action_name, $connect_actions)) { + $nav = new FBCSettingsNav($action); + $nav->show(); + return false; + } + + return true; + } + + function onStartLogout($action) + { + $action->logout(); + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + try { + $facebook = getFacebook(); + $facebook->expire_session(); + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Could\'t logout of Facebook: ' . + $e->getMessage()); + } + } + + return true; + } + + function getProfilePicURL($fbuid) + { + + $facebook = getFacebook(); + $url = null; + + try { + + $fqry = 'SELECT pic_square FROM user WHERE uid = %s'; + + $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid)); + + if (!empty($result)) { + $url = $result[0]['pic_square']; + } + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + "Facebook client failure requesting profile pic!"); + } + + return $url; + + } + +} diff --git a/plugins/Facebook/FBConnectSettings.php b/plugins/Facebook/FBConnectSettings.php new file mode 100644 index 000000000..911c56787 --- /dev/null +++ b/plugins/Facebook/FBConnectSettings.php @@ -0,0 +1,203 @@ +. + * + * @category Settings + * @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); +} + +require_once INSTALLDIR.'/lib/connectsettingsaction.php'; + +/** + * Facebook Connect settings action + * + * @category Settings + * @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 FBConnectSettingsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Facebook Connect Settings'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Manage how your account connects to Facebook'); + } + + /** + * Content area of the page + * + * Shows a form for uploading an avatar. + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_facebook', + 'class' => 'form_settings', + 'action' => + common_local_url('FBConnectSettings'))); + + if (!$flink) { + + $this->element('p', 'instructions', + _('There is no Facebook user connected to this account.')); + + $this->element('fb:login-button', array('onlogin' => 'goto_login()', + 'length' => 'long')); + + } else { + + $this->element('p', 'form_note', + _('Connected Facebook user')); + + $this->elementStart('p', array('class' => 'facebook-user-display')); + $this->elementStart('fb:profile-pic', + array('uid' => $flink->foreign_id, + 'size' => 'small', + 'linked' => 'true', + 'facebook-logo' => 'true')); + $this->elementEnd('fb:profile-pic'); + + $this->elementStart('fb:name', array('uid' => $flink->foreign_id, + 'useyou' => 'false')); + $this->elementEnd('fb:name'); + $this->elementEnd('p'); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset'); + + $this->element('legend', null, _('Disconnect my account from Facebook')); + + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_('Disconnecting your Faceboook ' . + 'would make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _('set a password')); + + $this->text(_(' first.')); + $this->elementEnd('p'); + } else { + + $note = 'Keep your %s account but disconnect from Facebook. ' . + 'You\'ll use your %s password to log in.'; + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site, $site)); + + $this->submit('disconnect', _('Disconnect')); + } + + $this->elementEnd('fieldset'); + } + + $this->elementEnd('form'); + } + + /** + * Handle post + * + * Disconnects the current Facebook user from the current user's account + * + * @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('disconnect')) { + + $user = common_current_user(); + + $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); + $result = $flink->delete(); + + if ($result === false) { + common_log_db_error($user, 'DELETE', __FILE__); + $this->serverError(_('Couldn\'t delete link to Facebook.')); + return; + } + + try { + + // Clear FB Connect cookies out + $facebook = getFacebook(); + $facebook->clear_cookie_state(); + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Couldn\'t clear Facebook cookies: ' . + $e->getMessage()); + } + + $this->showForm(_('You have disconnected from Facebook.'), true); + + } else { + $this->showForm(_('Not sure what you\'re trying to do.')); + return; + } + + } + +} diff --git a/plugins/Facebook/README b/plugins/Facebook/README index a350c5b5b..a8aaa1066 100644 --- a/plugins/Facebook/README +++ b/plugins/Facebook/README @@ -1,4 +1,8 @@ +// Facebook plugin +require_once(INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php'); +$fb = new FacebookPlugin(); + TODO: diff --git a/plugins/Facebook/facebook/facebook.php b/plugins/Facebook/facebook/facebook.php new file mode 100644 index 000000000..016e8e8e0 --- /dev/null +++ b/plugins/Facebook/facebook/facebook.php @@ -0,0 +1,598 @@ +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/plugins/Facebook/facebook/facebook_desktop.php b/plugins/Facebook/facebook/facebook_desktop.php new file mode 100644 index 000000000..e79a2ca34 --- /dev/null +++ b/plugins/Facebook/facebook/facebook_desktop.php @@ -0,0 +1,104 @@ +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/plugins/Facebook/facebook/facebookapi_php5_restlib.php b/plugins/Facebook/facebook/facebookapi_php5_restlib.php new file mode 100755 index 000000000..55cb7fb86 --- /dev/null +++ b/plugins/Facebook/facebook/facebookapi_php5_restlib.php @@ -0,0 +1,3618 @@ +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 '
    '; + $this->cur_id++; + print $this->cur_id . ': Called ' . $method . ', show ' . + 'Params | '. + 'XML | '. + 'SXML | '. + 'PHP'; + print ''; + print ''; + print ''; + print ''; + print '
    '; + } + return $result; + } + + protected function finalize_params($method, $params) { + list($get, $post) = $this->add_standard_params($method, $params); + // we need to do this before signing the params + $this->convert_array_values_to_json($post); + $post['sig'] = Facebook::generate_sig(array_merge($get, $post), + $this->secret); + return array($get, $post); + } + + private function convert_array_values_to_json(&$params) { + foreach ($params as $key => &$val) { + if (is_array($val)) { + $val = json_encode($val); + } + } + } + + /** + * Add the generally required params to our request. + * Params method, api_key, and v should be sent over as get. + */ + private function add_standard_params($method, $params) { + $post = $params; + $get = array(); + if ($this->call_as_apikey) { + $get['call_as_apikey'] = $this->call_as_apikey; + } + $get['method'] = $method; + $get['session_key'] = $this->session_key; + $get['api_key'] = $this->api_key; + $post['call_id'] = microtime(true); + if ($post['call_id'] <= $this->last_call_id) { + $post['call_id'] = $this->last_call_id + 0.001; + } + $this->last_call_id = $post['call_id']; + if (isset($post['v'])) { + $get['v'] = $post['v']; + unset($post['v']); + } else { + $get['v'] = '1.0'; + } + if (isset($this->use_ssl_resources) && + $this->use_ssl_resources) { + $post['return_ssl_resources'] = true; + } + return array($get, $post); + } + + private function create_url_string($params) { + $post_params = array(); + foreach ($params as $key => &$val) { + $post_params[] = $key.'='.urlencode($val); + } + return implode('&', $post_params); + } + + private function run_multipart_http_transaction($method, $params, $file, $server_addr) { + + // the format of this message is specified in RFC1867/RFC1341. + // we add twenty pseudo-random digits to the end of the boundary string. + $boundary = '--------------------------FbMuLtIpArT' . + sprintf("%010d", mt_rand()) . + sprintf("%010d", mt_rand()); + $content_type = 'multipart/form-data; boundary=' . $boundary; + // within the message, we prepend two extra hyphens. + $delimiter = '--' . $boundary; + $close_delimiter = $delimiter . '--'; + $content_lines = array(); + foreach ($params as $key => &$val) { + $content_lines[] = $delimiter; + $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"'; + $content_lines[] = ''; + $content_lines[] = $val; + } + // now add the file data + $content_lines[] = $delimiter; + $content_lines[] = + 'Content-Disposition: form-data; filename="' . $file . '"'; + $content_lines[] = 'Content-Type: application/octet-stream'; + $content_lines[] = ''; + $content_lines[] = file_get_contents($file); + $content_lines[] = $close_delimiter; + $content_lines[] = ''; + $content = implode("\r\n", $content_lines); + return $this->run_http_post_transaction($content_type, $content, $server_addr); + } + + public function post_request($method, $params) { + list($get, $post) = $this->finalize_params($method, $params); + $post_string = $this->create_url_string($post); + $get_string = $this->create_url_string($get); + $url_with_get = $this->server_addr . '?' . $get_string; + if ($this->use_curl_if_available && function_exists('curl_init')) { + $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url_with_get); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, $useragent); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + $result = $this->curl_exec($ch); + curl_close($ch); + } else { + $content_type = 'application/x-www-form-urlencoded'; + $content = $post_string; + $result = $this->run_http_post_transaction($content_type, + $content, + $url_with_get); + } + return $result; + } + + /** + * execute a curl transaction -- this exists mostly so subclasses can add + * extra options and/or process the response, if they wish. + * + * @param resource $ch a curl handle + */ + protected function curl_exec($ch) { + $result = curl_exec($ch); + return $result; + } + + private function post_upload_request($method, $params, $file, $server_addr = null) { + $server_addr = $server_addr ? $server_addr : $this->server_addr; + list($get, $post) = $this->finalize_params($method, $params); + $get_string = $this->create_url_string($get); + $url_with_get = $server_addr . '?' . $get_string; + if ($this->use_curl_if_available && function_exists('curl_init')) { + // prepending '@' causes cURL to upload the file; the key is ignored. + $post['_file'] = '@' . $file; + $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url_with_get); + // this has to come before the POSTFIELDS set! + curl_setopt($ch, CURLOPT_POST, 1); + // passing an array gets curl to use the multipart/form-data content type + curl_setopt($ch, CURLOPT_POSTFIELDS, $post); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, $useragent); + $result = $this->curl_exec($ch); + curl_close($ch); + } else { + $result = $this->run_multipart_http_transaction($method, $post, + $file, $url_with_get); + } + return $result; + } + + private function run_http_post_transaction($content_type, $content, $server_addr) { + + $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion(); + $content_length = strlen($content); + $context = + array('http' => + array('method' => 'POST', + 'user_agent' => $user_agent, + 'header' => 'Content-Type: ' . $content_type . "\r\n" . + 'Content-Length: ' . $content_length, + 'content' => $content)); + $context_id = stream_context_create($context); + $sock = fopen($server_addr, 'r', false, $context_id); + + $result = ''; + if ($sock) { + while (!feof($sock)) { + $result .= fgets($sock, 4096); + } + fclose($sock); + } + return $result; + } + + public static function convert_simplexml_to_array($sxml) { + $arr = array(); + if ($sxml) { + foreach ($sxml as $k => $v) { + if ($sxml['list']) { + $arr[] = self::convert_simplexml_to_array($v); + } else { + $arr[$k] = self::convert_simplexml_to_array($v); + } + } + } + if (sizeof($arr) > 0) { + return $arr; + } else { + return (string)$sxml; + } + } + + protected function get_uid($uid) { + return $uid ? $uid : $this->user; + } +} + + +class FacebookRestClientException extends Exception { +} + +// Supporting methods and values------ + +/** + * Error codes and descriptions for the Facebook API. + */ + +class FacebookAPIErrorCodes { + + const API_EC_SUCCESS = 0; + + /* + * GENERAL ERRORS + */ + const API_EC_UNKNOWN = 1; + const API_EC_SERVICE = 2; + const API_EC_METHOD = 3; + const API_EC_TOO_MANY_CALLS = 4; + const API_EC_BAD_IP = 5; + const API_EC_HOST_API = 6; + const API_EC_HOST_UP = 7; + const API_EC_SECURE = 8; + const API_EC_RATE = 9; + const API_EC_PERMISSION_DENIED = 10; + const API_EC_DEPRECATED = 11; + const API_EC_VERSION = 12; + const API_EC_INTERNAL_FQL_ERROR = 13; + const API_EC_HOST_PUP = 14; + + /* + * PARAMETER ERRORS + */ + const API_EC_PARAM = 100; + const API_EC_PARAM_API_KEY = 101; + const API_EC_PARAM_SESSION_KEY = 102; + const API_EC_PARAM_CALL_ID = 103; + const API_EC_PARAM_SIGNATURE = 104; + const API_EC_PARAM_TOO_MANY = 105; + const API_EC_PARAM_USER_ID = 110; + const API_EC_PARAM_USER_FIELD = 111; + const API_EC_PARAM_SOCIAL_FIELD = 112; + const API_EC_PARAM_EMAIL = 113; + const API_EC_PARAM_USER_ID_LIST = 114; + const API_EC_PARAM_FIELD_LIST = 115; + const API_EC_PARAM_ALBUM_ID = 120; + const API_EC_PARAM_PHOTO_ID = 121; + const API_EC_PARAM_FEED_PRIORITY = 130; + const API_EC_PARAM_CATEGORY = 140; + const API_EC_PARAM_SUBCATEGORY = 141; + const API_EC_PARAM_TITLE = 142; + const API_EC_PARAM_DESCRIPTION = 143; + const API_EC_PARAM_BAD_JSON = 144; + const API_EC_PARAM_BAD_EID = 150; + const API_EC_PARAM_UNKNOWN_CITY = 151; + const API_EC_PARAM_BAD_PAGE_TYPE = 152; + + /* + * USER PERMISSIONS ERRORS + */ + const API_EC_PERMISSION = 200; + const API_EC_PERMISSION_USER = 210; + const API_EC_PERMISSION_NO_DEVELOPERS = 211; + const API_EC_PERMISSION_OFFLINE_ACCESS = 212; + const API_EC_PERMISSION_ALBUM = 220; + const API_EC_PERMISSION_PHOTO = 221; + const API_EC_PERMISSION_MESSAGE = 230; + const API_EC_PERMISSION_OTHER_USER = 240; + const API_EC_PERMISSION_STATUS_UPDATE = 250; + const API_EC_PERMISSION_PHOTO_UPLOAD = 260; + const API_EC_PERMISSION_VIDEO_UPLOAD = 261; + const API_EC_PERMISSION_SMS = 270; + const API_EC_PERMISSION_CREATE_LISTING = 280; + const API_EC_PERMISSION_CREATE_NOTE = 281; + const API_EC_PERMISSION_SHARE_ITEM = 282; + const API_EC_PERMISSION_EVENT = 290; + const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291; + const API_EC_PERMISSION_LIVEMESSAGE = 292; + const API_EC_PERMISSION_RSVP_EVENT = 299; + + /* + * DATA EDIT ERRORS + */ + const API_EC_EDIT = 300; + const API_EC_EDIT_USER_DATA = 310; + const API_EC_EDIT_PHOTO = 320; + const API_EC_EDIT_ALBUM_SIZE = 321; + const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322; + const API_EC_EDIT_PHOTO_TAG_PHOTO = 323; + const API_EC_EDIT_PHOTO_FILE = 324; + const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325; + const API_EC_EDIT_PHOTO_TAG_LIMIT = 326; + const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327; + const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328; + + const API_EC_MALFORMED_MARKUP = 329; + const API_EC_EDIT_MARKUP = 330; + + const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340; + const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341; + const API_EC_EDIT_FEED_TITLE_LINK = 342; + const API_EC_EDIT_FEED_TITLE_LENGTH = 343; + const API_EC_EDIT_FEED_TITLE_NAME = 344; + const API_EC_EDIT_FEED_TITLE_BLANK = 345; + const API_EC_EDIT_FEED_BODY_LENGTH = 346; + const API_EC_EDIT_FEED_PHOTO_SRC = 347; + const API_EC_EDIT_FEED_PHOTO_LINK = 348; + + const API_EC_EDIT_VIDEO_SIZE = 350; + const API_EC_EDIT_VIDEO_INVALID_FILE = 351; + const API_EC_EDIT_VIDEO_INVALID_TYPE = 352; + const API_EC_EDIT_VIDEO_FILE = 353; + + const API_EC_EDIT_FEED_TITLE_ARRAY = 360; + const API_EC_EDIT_FEED_TITLE_PARAMS = 361; + const API_EC_EDIT_FEED_BODY_ARRAY = 362; + const API_EC_EDIT_FEED_BODY_PARAMS = 363; + const API_EC_EDIT_FEED_PHOTO = 364; + const API_EC_EDIT_FEED_TEMPLATE = 365; + const API_EC_EDIT_FEED_TARGET = 366; + const API_EC_EDIT_FEED_MARKUP = 367; + + /** + * SESSION ERRORS + */ + const API_EC_SESSION_TIMED_OUT = 450; + const API_EC_SESSION_METHOD = 451; + const API_EC_SESSION_INVALID = 452; + const API_EC_SESSION_REQUIRED = 453; + const API_EC_SESSION_REQUIRED_FOR_SECRET = 454; + const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455; + + + /** + * FQL ERRORS + */ + const FQL_EC_UNKNOWN_ERROR = 600; + const FQL_EC_PARSER = 601; // backwards compatibility + const FQL_EC_PARSER_ERROR = 601; + const FQL_EC_UNKNOWN_FIELD = 602; + const FQL_EC_UNKNOWN_TABLE = 603; + const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility + const FQL_EC_NO_INDEX = 604; + const FQL_EC_UNKNOWN_FUNCTION = 605; + const FQL_EC_INVALID_PARAM = 606; + const FQL_EC_INVALID_FIELD = 607; + const FQL_EC_INVALID_SESSION = 608; + const FQL_EC_UNSUPPORTED_APP_TYPE = 609; + const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610; + const FQL_EC_DEPRECATED_TABLE = 611; + const FQL_EC_EXTENDED_PERMISSION = 612; + const FQL_EC_RATE_LIMIT_EXCEEDED = 613; + const FQL_EC_UNRESOLVED_DEPENDENCY = 614; + + const API_EC_REF_SET_FAILED = 700; + + /** + * DATA STORE API ERRORS + */ + const API_EC_DATA_UNKNOWN_ERROR = 800; + const API_EC_DATA_INVALID_OPERATION = 801; + const API_EC_DATA_QUOTA_EXCEEDED = 802; + const API_EC_DATA_OBJECT_NOT_FOUND = 803; + const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804; + const API_EC_DATA_DATABASE_ERROR = 805; + const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806; + const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807; + const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808; + const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809; + const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810; + const API_EC_DATA_MALFORMED_ACTION_LINK = 811; + const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812; + + /* + * APPLICATION INFO ERRORS + */ + const API_EC_NO_SUCH_APP = 900; + + /* + * BATCH ERRORS + */ + const API_EC_BATCH_TOO_MANY_ITEMS = 950; + const API_EC_BATCH_ALREADY_STARTED = 951; + const API_EC_BATCH_NOT_STARTED = 952; + const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953; + + /* + * EVENT API ERRORS + */ + const API_EC_EVENT_INVALID_TIME = 1000; + + /* + * INFO BOX ERRORS + */ + const API_EC_INFO_NO_INFORMATION = 1050; + const API_EC_INFO_SET_FAILED = 1051; + + /* + * LIVEMESSAGE API ERRORS + */ + const API_EC_LIVEMESSAGE_SEND_FAILED = 1100; + const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101; + const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102; + + /* + * PAYMENTS API ERRORS + */ + const API_EC_PAYMENTS_UNKNOWN = 1150; + const API_EC_PAYMENTS_APP_INVALID = 1151; + const API_EC_PAYMENTS_DATABASE = 1152; + const API_EC_PAYMENTS_PERMISSION_DENIED = 1153; + const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154; + const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155; + const API_EC_PAYMENTS_INVALID_ORDER = 1156; + const API_EC_PAYMENTS_INVALID_PARAM = 1157; + const API_EC_PAYMENTS_INVALID_OPERATION = 1158; + const API_EC_PAYMENTS_PAYMENT_FAILED = 1159; + const API_EC_PAYMENTS_DISABLED = 1160; + + /* + * CONNECT SESSION ERRORS + */ + const API_EC_CONNECT_FEED_DISABLED = 1300; + + /* + * Platform tag bundles errors + */ + const API_EC_TAG_BUNDLE_QUOTA = 1400; + + /* + * SHARE + */ + const API_EC_SHARE_BAD_URL = 1500; + + /* + * NOTES + */ + const API_EC_NOTE_CANNOT_MODIFY = 1600; + + /* + * COMMENTS + */ + const API_EC_COMMENTS_UNKNOWN = 1700; + const API_EC_COMMENTS_POST_TOO_LONG = 1701; + const API_EC_COMMENTS_DB_DOWN = 1702; + const API_EC_COMMENTS_INVALID_XID = 1703; + const API_EC_COMMENTS_INVALID_UID = 1704; + const API_EC_COMMENTS_INVALID_POST = 1705; + const API_EC_COMMENTS_INVALID_REMOVE = 1706; + + /** + * This array is no longer maintained; to view the description of an error + * code, please look at the message element of the API response or visit + * the developer wiki at http://wiki.developers.facebook.com/. + */ + public static $api_error_descriptions = array( + self::API_EC_SUCCESS => 'Success', + self::API_EC_UNKNOWN => 'An unknown error occurred', + self::API_EC_SERVICE => 'Service temporarily unavailable', + self::API_EC_METHOD => 'Unknown method', + self::API_EC_TOO_MANY_CALLS => 'Application request limit reached', + self::API_EC_BAD_IP => 'Unauthorized source IP address', + self::API_EC_PARAM => 'Invalid parameter', + self::API_EC_PARAM_API_KEY => 'Invalid API key', + self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid', + self::API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous', + self::API_EC_PARAM_SIGNATURE => 'Incorrect signature', + self::API_EC_PARAM_USER_ID => 'Invalid user id', + self::API_EC_PARAM_USER_FIELD => 'Invalid user info field', + self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field', + self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list', + self::API_EC_PARAM_FIELD_LIST => 'Invalid field list', + self::API_EC_PARAM_ALBUM_ID => 'Invalid album id', + self::API_EC_PARAM_BAD_EID => 'Invalid eid', + self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city', + self::API_EC_PERMISSION => 'Permissions error', + self::API_EC_PERMISSION_USER => 'User not visible', + self::API_EC_PERMISSION_NO_DEVELOPERS => 'Application has no developers', + self::API_EC_PERMISSION_ALBUM => 'Album not visible', + self::API_EC_PERMISSION_PHOTO => 'Photo not visible', + self::API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event', + self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event', + self::API_EC_EDIT_ALBUM_SIZE => 'Album is full', + self::FQL_EC_PARSER => 'FQL: Parser Error', + self::FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field', + self::FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table', + self::FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable', + self::FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function', + self::FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in', + self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error', + self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation', + self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded', + self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found', + self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists', + self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again', + self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first', + self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch', + self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode' + ); +} diff --git a/plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php b/plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php new file mode 100644 index 000000000..0cddbddb4 --- /dev/null +++ b/plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE b/plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE new file mode 100644 index 000000000..4ae6bef55 --- /dev/null +++ b/plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE @@ -0,0 +1,21 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php new file mode 100644 index 000000000..29509deba --- /dev/null +++ b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php @@ -0,0 +1,6 @@ + diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php new file mode 100644 index 000000000..36a3f2863 --- /dev/null +++ b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php @@ -0,0 +1,23 @@ +encode($arg); +} + +function json_decode($arg) +{ + global $services_json; + if (!isset($services_json)) { + $services_json = new Services_JSON(); + } + return $services_json->decode($arg); +} + +?> diff --git a/plugins/Facebook/fbfavicon.ico b/plugins/Facebook/fbfavicon.ico new file mode 100644 index 000000000..c57c0342f Binary files /dev/null and b/plugins/Facebook/fbfavicon.ico differ -- cgit v1.2.3-54-g00ecf From aa58e8a73800d21d299b8c28d1b466b4d2b9cc70 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 21 Oct 2009 01:17:31 +0000 Subject: Combine Facebook and Facebook Connect plugins into one big plugin --- plugins/Facebook/FBConnect.css | 36 ++++ plugins/Facebook/FBConnectPlugin.css | 36 ---- plugins/Facebook/FBConnectPlugin.php | 367 ----------------------------------- plugins/Facebook/FacebookPlugin.php | 318 ++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+), 403 deletions(-) create mode 100644 plugins/Facebook/FBConnect.css delete mode 100644 plugins/Facebook/FBConnectPlugin.css delete mode 100644 plugins/Facebook/FBConnectPlugin.php (limited to 'plugins') diff --git a/plugins/Facebook/FBConnect.css b/plugins/Facebook/FBConnect.css new file mode 100644 index 000000000..49217bf13 --- /dev/null +++ b/plugins/Facebook/FBConnect.css @@ -0,0 +1,36 @@ +/** Styles for Facebook logo and Facebook user profile avatar. + * + * @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/ + */ + +#site_nav_global_primary #nav_fb { +position:relative; +margin-left:18px; +} + +#nav_fb #fbc_profile-pic { +position:absolute; +top:-3px; +left:-18px; +display:inline; +border:1px solid #3B5998; +padding:1px; +} + +#nav_fb #fb_favicon { +position:absolute; +top:-13px; +left:-25px; +display:inline; +} + +#settings_facebook_connect_options legend { +display:none; +} +#form_settings_facebook_connect fieldset fieldset legend { +display:block; +} diff --git a/plugins/Facebook/FBConnectPlugin.css b/plugins/Facebook/FBConnectPlugin.css deleted file mode 100644 index 49217bf13..000000000 --- a/plugins/Facebook/FBConnectPlugin.css +++ /dev/null @@ -1,36 +0,0 @@ -/** Styles for Facebook logo and Facebook user profile avatar. - * - * @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/ - */ - -#site_nav_global_primary #nav_fb { -position:relative; -margin-left:18px; -} - -#nav_fb #fbc_profile-pic { -position:absolute; -top:-3px; -left:-18px; -display:inline; -border:1px solid #3B5998; -padding:1px; -} - -#nav_fb #fb_favicon { -position:absolute; -top:-13px; -left:-25px; -display:inline; -} - -#settings_facebook_connect_options legend { -display:none; -} -#form_settings_facebook_connect fieldset fieldset legend { -display:block; -} diff --git a/plugins/Facebook/FBConnectPlugin.php b/plugins/Facebook/FBConnectPlugin.php deleted file mode 100644 index 0dacf9012..000000000 --- a/plugins/Facebook/FBConnectPlugin.php +++ /dev/null @@ -1,367 +0,0 @@ -. - * - * @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') && !defined('LACONICA')) { - exit(1); -} - -define("FACEBOOK_CONNECT_SERVICE", 3); - -require_once INSTALLDIR . '/lib/facebookutil.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; -require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php'; - -/** - * Plugin to enable Facebook Connect - * - * @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 FBConnectPlugin extends Plugin -{ - function __construct() - { - parent::__construct(); - } - - // Hook in new actions - function onRouterInitialized(&$m) { - $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); - $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); - $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); - $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); - } - - // Add in xmlns:fb - function onStartShowHTML($action) - { - - if ($this->reqFbScripts($action)) { - - // XXX: Horrible hack to make Safari, FF2, and Chrome work with - // Facebook Connect. These browser cannot use Facebook's - // DOM parsing routines unless the mime type of the page is - // text/html even though Facebook Connect uses XHTML. This is - // A bug in Facebook Connect, and this is a temporary solution - // until they fix their JavaScript libs. - header('Content-Type: text/html'); - - $action->extraHeaders(); - - $action->startXML('html'); - - $language = $action->getLanguage(); - - $action->elementStart('html', - array('xmlns' => 'http://www.w3.org/1999/xhtml', - 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', - 'xml:lang' => $language, - 'lang' => $language)); - - return false; - - } else { - - return true; - } - } - - // Note: this script needs to appear in the - - function onEndShowScripts($action) - { - if ($this->reqFbScripts($action)) { - - $apikey = common_config('facebook', 'apikey'); - $plugin_path = common_path('plugins/FBConnect'); - - $login_url = common_local_url('FBConnectAuth'); - $logout_url = common_local_url('logout'); - - // XXX: Facebook says we don't need this FB_RequireFeatures(), - // but we actually do, for IE and Safari. Gar. - - $js = ''; - - $js = sprintf($js, $apikey, $login_url, $logout_url); - - // Compress the bugger down a bit - $js = str_replace(' ', '', $js); - - $action->raw(" $js"); // leading two spaces to make it line up - } - - } - - // Note: this script needs to appear as close as possible to - - function onEndShowFooter($action) - { - if ($this->reqFbScripts($action)) { - $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'); - } - } - - function onEndShowStatusNetStyles($action) - { - if ($this->reqFbScripts($action)) { - $action->cssLink('plugins/FBConnect/FBConnectPlugin.css'); - } - } - - /** - * Does the Action we're plugged into require the FB Scripts? We only - * want to output FB namespace, scripts, CSS, etc. on the pages that - * really need them. - * - * @param Action the action in question - * - * @return boolean true - */ - - function reqFbScripts($action) { - - // If you're logged in w/FB Connect, you always need the FB stuff - - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - return true; - } - - // List of actions that require FB stuff - - $needy = array('FBConnectLoginAction', - 'FBConnectauthAction', - 'FBConnectSettingsAction'); - - if (in_array(get_class($action), $needy)) { - return true; - } - - return false; - - } - - /** - * Is the user currently logged in with FB Connect? - * - * @return mixed $fbuid the Facebook ID of the logged in user, or null - */ - - function loggedIn() - { - $user = common_current_user(); - - if (!empty($user)) { - - $flink = Foreign_link::getByUserId($user->id, - FACEBOOK_CONNECT_SERVICE); - $fbuid = 0; - - if (!empty($flink)) { - - try { - - $facebook = getFacebook(); - $fbuid = $facebook->get_loggedin_user(); - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Problem getting Facebook user: ' . - $e->getMessage()); - } - - if ($fbuid > 0) { - return $fbuid; - } - } - } - - return null; - } - - function onStartPrimaryNav($action) - { - - $user = common_current_user(); - $connect = 'FBConnectSettings'; - if (common_config('xmpp', 'enabled')) { - $connect = 'imsettings'; - } else if (common_config('sms', 'enabled')) { - $connect = 'smssettings'; - } else if (common_config('twitter', 'enabled')) { - $connect = 'twittersettings'; - } - - if (!empty($user)) { - - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - - /* Default FB silhouette pic for FB users who haven't - uploaded a profile pic yet. */ - - $silhouetteUrl = - 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; - - $url = $this->getProfilePicURL($fbuid); - - $action->elementStart('li', array('id' => 'nav_fb')); - - $action->element('img', array('id' => 'fbc_profile-pic', - 'src' => (!empty($url)) ? $url : $silhouetteUrl, - 'alt' => 'Facebook Connect User', - 'width' => '16'), ''); - - $iconurl = common_path('plugins/FBConnect/fbfavicon.ico'); - $action->element('img', array('id' => 'fb_favicon', - 'src' => $iconurl)); - - $action->elementEnd('li'); - - } - } - - return true; - } - - function onStartShowLocalNavBlock($action) - { - $action_name = get_class($action); - - $login_actions = array('LoginAction', 'RegisterAction', - 'OpenidloginAction', 'FBConnectLoginAction'); - - if (in_array($action_name, $login_actions)) { - $nav = new FBCLoginGroupNav($action); - $nav->show(); - return false; - } - - $connect_actions = array('SmssettingsAction', 'ImsettingsAction', - 'TwittersettingsAction', 'FBConnectSettingsAction'); - - if (in_array($action_name, $connect_actions)) { - $nav = new FBCSettingsNav($action); - $nav->show(); - return false; - } - - return true; - } - - function onStartLogout($action) - { - $action->logout(); - $fbuid = $this->loggedIn(); - - if (!empty($fbuid)) { - try { - $facebook = getFacebook(); - $facebook->expire_session(); - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Could\'t logout of Facebook: ' . - $e->getMessage()); - } - } - - return true; - } - - function getProfilePicURL($fbuid) - { - - $facebook = getFacebook(); - $url = null; - - try { - - $fqry = 'SELECT pic_square FROM user WHERE uid = %s'; - - $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid)); - - if (!empty($result)) { - $url = $result[0]['pic_square']; - } - - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - "Facebook client failure requesting profile pic!"); - } - - return $url; - - } - -} diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index 127cf96e6..cf6781cfa 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -31,6 +31,16 @@ if (!defined('STATUSNET')) { exit(1); } +define("FACEBOOK_CONNECT_SERVICE", 3); + +require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBConnectAuth.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBConnectLogin.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBConnectSettings.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBCLoginGroupNav.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php'; +require_once INSTALLDIR . '/plugins/Facebook/FBC_XDReceiver.php'; + /** * Facebook plugin to add a StatusNet Facebook application * @@ -56,12 +66,22 @@ class FacebookPlugin extends Plugin function onRouterInitialized(&$m) { + + // Facebook App stuff + $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')); + // Facebook Connect stuff + + $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); + $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); + $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); + $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); + return true; } @@ -89,6 +109,304 @@ class FacebookPlugin extends Plugin } } + // Add in xmlns:fb + function onStartShowHTML($action) + { + + if ($this->reqFbScripts($action)) { + + // XXX: Horrible hack to make Safari, FF2, and Chrome work with + // Facebook Connect. These browser cannot use Facebook's + // DOM parsing routines unless the mime type of the page is + // text/html even though Facebook Connect uses XHTML. This is + // A bug in Facebook Connect, and this is a temporary solution + // until they fix their JavaScript libs. + header('Content-Type: text/html'); + + $action->extraHeaders(); + + $action->startXML('html'); + + $language = $action->getLanguage(); + + $action->elementStart('html', + array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', + 'xml:lang' => $language, + 'lang' => $language)); + + return false; + + } else { + + return true; + } + } + + // Note: this script needs to appear in the + + function onEndShowScripts($action) + { + if ($this->reqFbScripts($action)) { + + $apikey = common_config('facebook', 'apikey'); + $plugin_path = common_path('plugins/Facebook'); + + $login_url = common_local_url('FBConnectAuth'); + $logout_url = common_local_url('logout'); + + // XXX: Facebook says we don't need this FB_RequireFeatures(), + // but we actually do, for IE and Safari. Gar. + + $js = ''; + + $js = sprintf($js, $apikey, $login_url, $logout_url); + + // Compress the bugger down a bit + $js = str_replace(' ', '', $js); + + $action->raw(" $js"); // leading two spaces to make it line up + } + + } + + // Note: this script needs to appear as close as possible to + + function onEndShowFooter($action) + { + if ($this->reqFbScripts($action)) { + $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'); + } + } + + function onEndShowStatusNetStyles($action) + { + if ($this->reqFbScripts($action)) { + $action->cssLink('plugins/Facebook/FBConnect.css'); + } + } + + /** + * Does the Action we're plugged into require the FB Scripts? We only + * want to output FB namespace, scripts, CSS, etc. on the pages that + * really need them. + * + * @param Action the action in question + * + * @return boolean true + */ + + function reqFbScripts($action) { + + // If you're logged in w/FB Connect, you always need the FB stuff + + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + return true; + } + + // List of actions that require FB stuff + + $needy = array('FBConnectLoginAction', + 'FBConnectauthAction', + 'FBConnectSettingsAction'); + + if (in_array(get_class($action), $needy)) { + return true; + } + + return false; + + } + + /** + * Is the user currently logged in with FB Connect? + * + * @return mixed $fbuid the Facebook ID of the logged in user, or null + */ + + function loggedIn() + { + $user = common_current_user(); + + if (!empty($user)) { + + $flink = Foreign_link::getByUserId($user->id, + FACEBOOK_CONNECT_SERVICE); + $fbuid = 0; + + if (!empty($flink)) { + + try { + + $facebook = getFacebook(); + $fbuid = $facebook->get_loggedin_user(); + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Problem getting Facebook user: ' . + $e->getMessage()); + } + + if ($fbuid > 0) { + return $fbuid; + } + } + } + + return null; + } + + function onStartPrimaryNav($action) + { + + $user = common_current_user(); + $connect = 'FBConnectSettings'; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } else if (common_config('twitter', 'enabled')) { + $connect = 'twittersettings'; + } + + if (!empty($user)) { + + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + + /* Default FB silhouette pic for FB users who haven't + uploaded a profile pic yet. */ + + $silhouetteUrl = + 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; + + $url = $this->getProfilePicURL($fbuid); + + $action->elementStart('li', array('id' => 'nav_fb')); + + $action->element('img', array('id' => 'fbc_profile-pic', + 'src' => (!empty($url)) ? $url : $silhouetteUrl, + 'alt' => 'Facebook Connect User', + 'width' => '16'), ''); + + $iconurl = common_path('plugins/Facebook/fbfavicon.ico'); + $action->element('img', array('id' => 'fb_favicon', + 'src' => $iconurl)); + + $action->elementEnd('li'); + + } + } + + return true; + } + + function onStartShowLocalNavBlock($action) + { + $action_name = get_class($action); + + $login_actions = array('LoginAction', 'RegisterAction', + 'OpenidloginAction', 'FBConnectLoginAction'); + + if (in_array($action_name, $login_actions)) { + $nav = new FBCLoginGroupNav($action); + $nav->show(); + return false; + } + + $connect_actions = array('SmssettingsAction', 'ImsettingsAction', + 'TwittersettingsAction', 'FBConnectSettingsAction'); + + if (in_array($action_name, $connect_actions)) { + $nav = new FBCSettingsNav($action); + $nav->show(); + return false; + } + + return true; + } + + function onStartLogout($action) + { + $action->logout(); + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + try { + $facebook = getFacebook(); + $facebook->expire_session(); + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Could\'t logout of Facebook: ' . + $e->getMessage()); + } + } + + return true; + } + + function getProfilePicURL($fbuid) + { + + $facebook = getFacebook(); + $url = null; + + try { + + $fqry = 'SELECT pic_square FROM user WHERE uid = %s'; + + $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid)); + + if (!empty($result)) { + $url = $result[0]['pic_square']; + } + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + "Facebook client failure requesting profile pic!"); + } + + return $url; + + } + /** * Add a Facebook queue item for each notice * -- cgit v1.2.3-54-g00ecf From 3e5c8f28c362e945ca7d89e7aaff1e1304a9192a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 21 Oct 2009 17:29:54 +0000 Subject: Fix references --- plugins/Facebook/FBConnectAuth.php | 2 +- plugins/Facebook/FBConnectLogin.php | 3 ++- plugins/Facebook/facebookutil.php | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/Facebook/FBConnectAuth.php b/plugins/Facebook/FBConnectAuth.php index 647d5def8..b909a4977 100644 --- a/plugins/Facebook/FBConnectAuth.php +++ b/plugins/Facebook/FBConnectAuth.php @@ -27,7 +27,7 @@ * @link http://status.net/ */ -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; +require_once INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php'; class FBConnectauthAction extends Action { diff --git a/plugins/Facebook/FBConnectLogin.php b/plugins/Facebook/FBConnectLogin.php index 5696d8848..f146bef7d 100644 --- a/plugins/Facebook/FBConnectLogin.php +++ b/plugins/Facebook/FBConnectLogin.php @@ -21,7 +21,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; + +require_once INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php'; class FBConnectLoginAction extends Action { diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index 9817837f7..da53f35e2 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -require_once INSTALLDIR . '/extlib/facebook/facebook.php'; +require_once INSTALLDIR . '/plugins/Facebook/facebook/facebook.php'; require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; require_once INSTALLDIR . '/lib/noticelist.php'; -- cgit v1.2.3-54-g00ecf From 6a2185a26b6d9e7b41fb0da1043000d677714141 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 21 Oct 2009 22:02:10 +0000 Subject: Make paths and class loading work right --- plugins/Facebook/FacebookPlugin.php | 39 ++++++++++++++++++------------- plugins/Facebook/facebookqueuehandler.php | 5 ++-- plugins/Facebook/facebooksettings.php | 2 +- 3 files changed, 26 insertions(+), 20 deletions(-) (limited to 'plugins') diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index cf6781cfa..bcd1a7c74 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -34,12 +34,6 @@ if (!defined('STATUSNET')) { define("FACEBOOK_CONNECT_SERVICE", 3); require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBConnectAuth.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBConnectLogin.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBConnectSettings.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBCLoginGroupNav.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php'; -require_once INSTALLDIR . '/plugins/Facebook/FBC_XDReceiver.php'; /** * Facebook plugin to add a StatusNet Facebook application @@ -69,11 +63,11 @@ class FacebookPlugin extends Plugin // Facebook App stuff - $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')); + $m->connect('facebook/app', array('action' => 'facebookhome')); + $m->connect('facebook/app/index.php', array('action' => 'facebookhome')); + $m->connect('facebook/app/settings.php', array('action' => 'facebooksettings')); + $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); + $m->connect('facebook/app/remove', array('action' => 'facebookremove')); // Facebook Connect stuff @@ -104,12 +98,25 @@ class FacebookPlugin extends Plugin include_once INSTALLDIR . '/plugins/Facebook/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; + case 'FBConnectAuthAction': + case 'FBConnectLoginAction': + case 'FBConnectSettingsAction': + case 'FBC_XDReceiverAction': + include_once INSTALLDIR . '/plugins/Facebook/' . + mb_substr($cls, 0, -6) . '.php'; + return false; + case 'FBCLoginGroupNav': + include_once INSTALLDIR . '/plugins/Facebook/FBCLoginGroupNav.php'; + return false; + case 'FBCSettingsNav': + include_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php'; + return false; default: return true; } } - // Add in xmlns:fb + // Add in xmlns:fb function onStartShowHTML($action) { @@ -143,7 +150,7 @@ class FacebookPlugin extends Plugin } } - // Note: this script needs to appear in the + // Note: this script needs to appear in the function onEndShowScripts($action) { @@ -204,7 +211,7 @@ class FacebookPlugin extends Plugin } - // Note: this script needs to appear as close as possible to + // Note: this script needs to appear as close as possible to function onEndShowFooter($action) { @@ -220,7 +227,7 @@ class FacebookPlugin extends Plugin } } - /** + /** * Does the Action we're plugged into require the FB Scripts? We only * want to output FB namespace, scripts, CSS, etc. on the pages that * really need them. @@ -466,4 +473,4 @@ class FacebookPlugin extends Plugin return true; } -} \ No newline at end of file +} diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 30de59efb..e4ae7d4ee 100755 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); $shortoptions = 'i::'; $longoptions = array('id::'); @@ -30,8 +30,7 @@ Daemon script for pushing new notices to Facebook. END_OF_FACEBOOK_HELP; -require_once INSTALLDIR.'/scripts/commandline.inc'; - +require_once INSTALLDIR . '/scripts/commandline.inc'; require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; require_once INSTALLDIR . '/lib/queuehandler.php'; diff --git a/plugins/Facebook/facebooksettings.php b/plugins/Facebook/facebooksettings.php index 4bfdfc0ef..2f182e368 100644 --- a/plugins/Facebook/facebooksettings.php +++ b/plugins/Facebook/facebooksettings.php @@ -21,7 +21,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/lib/facebookaction.php'; +require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php'; class FacebooksettingsAction extends FacebookAction { -- cgit v1.2.3-54-g00ecf From ec92cab6ff0005da4d4bbf2a9548f831dc215fdf Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 21 Oct 2009 17:53:23 -0700 Subject: Removed Facebook stuff from the StatusNet README and wrote a new README for the Facebook plugin. --- README | 49 ------------------ plugins/Facebook/README | 130 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 54 deletions(-) (limited to 'plugins') diff --git a/README b/README index ee75b802e..03064ba18 100644 --- a/README +++ b/README @@ -526,8 +526,6 @@ This will run eight (for now) queue handlers: of registered users. * xmppconfirmhandler.php - sends confirmation messages to registered users. -* facebookqueuehandler.php - sends queued notices to Facebook for users - of the built-in Facebook application. Note that these queue daemons are pretty raw, and need your care. In particular, they leak memory, and you may want to restart them on a @@ -545,53 +543,6 @@ our kind of hacky home-grown DB-based queue solution. See the "queues" config section below for how to configure to use STOMP. As of this writing, the software has been tested with ActiveMQ ( -Built-in Facebook Application ------------------------------ - -StatusNet's Facebook application allows your users to automatically -update their Facebook statuses with their latest notices, invite -their friends to use the app (and thus your site), view their notice -timelines, and post notices -- all from within Facebook. The application -is built into StatusNet and runs on your host. For automatic Facebook -status updating to work you will need to enable queuing and run the -facebookqueuehandler.php daemon (see the "Queues and daemons" section -above). - -Quick setup instructions*: - -Install the Facebook Developer application on Facebook: - - http://www.facebook.com/developers/ - -Use it to create a new application and generate an API key and secret. -Uncomment the Facebook app section of your config.php and copy in the -key and secret, e.g.: - - # Config section for the built-in Facebook application - $config['facebook']['apikey'] = 'APIKEY'; - $config['facebook']['secret'] = 'SECRET'; - -In Facebook's application editor, specify the following URLs for your app: - -- Canvas Callback URL: http://example.net/mublog/facebook/ -- Post-Remove Callback URL: http://example.net/mublog/facebook/remove -- Post-Add Redirect URL: http://apps.facebook.com/yourapp/ -- Canvas Page URL: http://apps.facebook.com/yourapp/ - -(Replace 'example.net' with your host's URL, 'mublog' with the path -to your StatusNet installation, and 'yourapp' with the name of the -Facebook application you created.) - -Additionally, Choose "Web" for Application type in the Advanced tab. -In the "Canvas setting" section, choose the "FBML" for Render Method, -"Smart Size" for IFrame size, and "Full width (760px)" for Canvas Width. -Everything else can be left with default values. - -*For more detailed instructions please see the installation guide on the -StatusNet wiki: - - http://status.net/trac/wiki/FacebookApplication - Sitemaps -------- diff --git a/plugins/Facebook/README b/plugins/Facebook/README index a8aaa1066..bf2f4a180 100644 --- a/plugins/Facebook/README +++ b/plugins/Facebook/README @@ -1,9 +1,129 @@ +This plugin allows you to use Facebook Connect with StatusNet, provides a +Facebook application for your users, and allows them to update their +Facebook statuses from StatusNet. -// Facebook plugin -require_once(INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php'); -$fb = new FacebookPlugin(); +Facebook Connect +---------------- +Facebook connect allows users to register and login using nothing but their +Facebook credentials. With Facebook Connect, your users can: -TODO: +- Authenticate (register/login/logout -- works similar to OpenID) +- Associate an existing StatusNet account with a Facebook account +- Disconnect a Facebook account from a StatusNet account -- Integrate this and the FB Connect plugin \ No newline at end of file +Built-in Facebook Application +----------------------------- + +The plugin also installs a StatusNet Facebook application that allows your +users to automatically update their Facebook statuses with their latest +notices, invite their friends to use the app (and thus your site), view +their notice timelines, and post notices -- all from within Facebook. The +application is built into the StatusNet Facebook plugin and runs on your +host. + +Quick setup instructions* +------------------------- + +Install the Facebook Developer application on Facebook: + + http://www.facebook.com/developers/ + +Use it to create a new application and generate an API key and secret. Add a +Facebook app section of your config.php and copy in the key and secret, +e.g.: + + // Config section for the built-in Facebook application + $config['facebook']['apikey'] = 'APIKEY'; + $config['facebook']['secret'] = 'SECRET'; + +In Facebook's application editor, specify the following URLs for your app: + +- Canvas Callback URL : http://example.net/mublog/facebook/app/ +- Post-Remove Callback URL: http://example.net/mublog/facebook/app/remove +- Post-Add Redirect URL : http://apps.facebook.com/yourapp/ +- Canvas Page URL : http://apps.facebook.com/yourapp/ +- Connect URL : http://example.net/mublog/ + + *** ATTENTION *** + These URLs have changed slightly since StatusNet version 0.8.1, + so if you have been using the Facebook app previously, you will + need to update your configuration! + +Replace "example.net" with your host's URL, "mublog" with the path to your +StatusNet installation, and 'yourapp' with the name of the Facebook +application you created. (If you don't have "Fancy URLs" on, you'll need to +change http://example.net/mublog/ to http://example.net/mublog/index.php/). + +Additionally, Choose "Web" for Application type in the Advanced tab. In the +"Canvas setting" section, choose the "FBML" for Render Method, "Smart Size" +for IFrame size, and "Full width (760px)" for Canvas Width. Everything else +can be left with default values. + +* NOTE: For more under-the-hood detailed instructions about setting up a + Facebook application and getting an API key, check out the + following pages on the Facebook wiki: + + http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site + http://wiki.developers.facebook.com/index.php/Creating_your_first_application + +Finally you must activate the plugin by adding the following line to your +config.php: + + addPlugin('Facebook'); + +Testing It Out +-------------- + +If the Facebook plugin is enabled and working, there will be a new Facebook +Connect Settings tab under each user's Connect menu. Users can connect and +disconnect* to their Facebook accounts from it. + +To try out the plugin, fire up your browser and connect to: + + http://SITE/PATH_TO_STATUSNET/main/facebooklogin + +or, if you do not have fancy URLs turned on: + + http://SITE/PATH_TO_STATUSNET/index.php/main/facebooklogin + +You should see a page with a blue button that says: "Connect with Facebook" +and you should be able to login or register. + +From within Facebook, you should also be able to get to the Facebook +application, and run it by hitting the link you specified above when +configuring it: + + http://apps.facebook.com/yourapp/ + +That link should be present you with a login screen. After logging in to +the app, you are given the option to update their Facebook status via +StatusNet. + +* Note: Before a user can disconnect from Facebook, she must set a normal + StatusNet password. Otherwise, she might not be able to login in to her + account in the future. This is usually only required for users who have + used Facebook Connect to register their StatusNet account, and therefore + haven't already set a local password. + +Offline Queue Handling +---------------------- + +For larger sites needing better performance it's possible to enable queuing +and have users' notices posted to Facebook via a separate "offline" +FacebookQueueHandler (facebookqueuhandler.php in the Facebook plugin +directory), which will be started by the plugin along with their other +daemons when you run scripts/startdaemons.sh. See the StatusNet README for +more about queuing and daemons. + +TODO +---- + +- Invite Facebook friends to use your StatusNet installation via Facebook + Connect +- Auto-subscribe Facebook friends already using StatusNet +- Share StatusNet favorite notices to your Facebook stream +- Allow users to update their Facebook statuses once they have authenticated + with Facebook Connect (no need for them to use the Facebook app if they + don't want to). +- Re-design the whole thing to support multiple instances of StatusNet -- cgit v1.2.3-54-g00ecf From 9c983c383029c9aaa7d0cf04a5fc9973d514dff7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 22 Oct 2009 15:44:36 -0400 Subject: extract Geonames stuff to a plugin --- lib/location.php | 119 ++++++++++++------- plugins/GeonamesPlugin.php | 281 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 44 deletions(-) create mode 100644 plugins/GeonamesPlugin.php (limited to 'plugins') diff --git a/lib/location.php b/lib/location.php index f4ce7f67a..5b7f47102 100644 --- a/lib/location.php +++ b/lib/location.php @@ -34,6 +34,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * class for locations * + * These are stored in the DB as part of notice and profile records, + * but since they're about the same in both, we have a separate class + * for them. + * * @category Location * @package StatusNet * @author Evan Prodromou @@ -48,74 +52,94 @@ class Location public $location_id; public $location_ns; - var $names; + var $names = array(); - const geonames = 1; - const whereOnEarth = 2; + /** + * Constructor that makes a Location from a string name + * + * @param string $name Human-readable name (any kind) + * @param string $language Language, default = common_language() + * + * @return Location Location with that name (or null if not found) + */ - static function fromName($name, $language=null, $location_ns=null) + static function fromName($name, $language=null) { if (is_null($language)) { $language = common_language(); } - if (is_null($location_ns)) { - $location_ns = common_config('location', 'namespace'); - } $location = null; - if (Event::handle('LocationFromName', array($name, $language, $location_ns, &$location))) { + // Let a third-party handle it - switch ($location_ns) { - case Location::geonames: - return Location::fromGeonamesName($name, $language); - break; - case Location::whereOnEarth: - return Location::fromWhereOnEarthName($name, $language); - break; - } - } + Event::handle('LocationFromName', array($name, $language, &$location)); return $location; } - static function fromGeonamesName($name, $language) + /** + * Constructor that makes a Location from an ID + * + * @param integer $id Identifier ID + * @param integer $ns Namespace of the identifier + * @param string $language Language to return name in (default is common) + * + * @return Location The location with this ID (or null if none) + */ + + static function fromId($id, $ns, $language=null) { $location = null; - $client = HTTPClient::start(); - - // XXX: break down a name by commas, narrow by each - - $str = http_build_query(array('maxRows' => 1, - 'q' => $name, - 'lang' => $language, - 'type' => 'json')); - - $result = $client->get('http://ws.geonames.org/search?'.$str); - - if ($result->code == "200") { - $rj = json_decode($result->body); - if (count($rj['geonames']) > 0) { - $n = $rj['geonames'][0]; - $location = new Location(); - $location->lat = $n->lat; - $location->lon = $n->lon; - $location->name = $n->name; - $location->location_id = $n->geonameId; - $location->location_ns = Location:geonames; - } - } + + // Let a third-party handle it + + Event::handle('LocationFromId', array($id, $ns, $language, &$location)); return $location; } - static function fromId($location_id, $location_ns = null) + /** + * Constructor that finds the nearest location to a lat/lon pair + * + * @param float $lat Latitude + * @param float $lon Longitude + * @param string $language Language for results, default = current + * + * @return Location the location found, or null if none found + */ + + static function fromLatLon($lat, $lon, $language=null) { - if (is_null($location_ns)) { - $location_ns = common_config('location', 'namespace'); + if (is_null($language)) { + $language = common_language(); } + + $location = null; + + // Let a third-party handle it + + if (Event::handle('LocationFromLatLon', + array($lat, $lon, $language, &$location))) { + // Default is just the lat/lon pair + + $location = new Location(); + + $location->lat = $lat; + $location->lon = $lon; + } + + return $location; } + /** + * Get the name for this location in the given language + * + * @param string $language language to use, default = current + * + * @return string location name or null if not found + */ + function getName($language=null) { if (is_null($language)) { @@ -124,6 +148,13 @@ class Location if (array_key_exists($this->names, $language)) { return $this->names[$language]; + } else { + $name = null; + Event::handle('LocationNameLanguage', array($this, $language, &$name)); + if (!empty($name)) { + $this->names[$language] = $name; + return $name; + } } } } diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php new file mode 100644 index 000000000..934e998c7 --- /dev/null +++ b/plugins/GeonamesPlugin.php @@ -0,0 +1,281 @@ +. + * + * @category Action + * @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://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin to convert string locations to Geonames IDs and vice versa + * + * This handles most of the events that Location class emits. It uses + * the geonames.org Web service to convert names like 'Montreal, Quebec, Canada' + * into IDs and lat/lon pairs. + * + * @category Plugin + * @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://status.net/ + * + * @seeAlso Location + */ + +class GeonamesPlugin extends Plugin +{ + const NAMESPACE = 1; + + /** + * convert a name into a Location object + * + * @param string $name Name to convert + * @param string $language ISO code for anguage the name is in + * @param Location &$location Location object (may be null) + * + * @return boolean whether to continue (results in $location) + */ + + function onLocationFromName($name, $language, &$location) + { + $client = HTTPClient::start(); + + // XXX: break down a name by commas, narrow by each + + $str = http_build_query(array('maxRows' => 1, + 'q' => $name, + 'lang' => $language, + 'type' => 'json')); + + $result = $client->get('http://ws.geonames.org/search?'.$str); + + if ($result->code == "200") { + $rj = json_decode($result->body); + if (count($rj['geonames']) > 0) { + $n = $rj['geonames'][0]; + + $location = new Location(); + + $location->lat = $n['lat']; + $location->lon = $n['lng']; + $location->names[$language] = $n['name']; + $location->location_id = $n['geonameId']; + $location->location_ns = self::NAMESPACE; + + // handled, don't continue processing! + return false; + } + } + + // Continue processing; we don't have the answer + return true; + } + + /** + * convert an id into a Location object + * + * @param string $id Name to convert + * @param string $ns Name to convert + * @param string $language ISO code for language for results + * @param Location &$location Location object (may be null) + * + * @return boolean whether to continue (results in $location) + */ + + function onLocationFromId($id, $ns, $language, &$location) + { + if ($ns != self::NAMESPACE) { + // It's not one of our IDs... keep processing + return true; + } + + $client = HTTPClient::start(); + + $str = http_build_query(array('geonameId' => $id, + 'lang' => $language)); + + $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); + + if ($result->code == "200") { + + $rj = json_decode($result->body); + + if (count($rj['geonames']) > 0) { + + $parts = array(); + + foreach ($rj['geonames'] as $level) { + if (in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $level['name']; + } + } + + $last = $rj['geonames'][count($rj['geonames'])-1]; + + if (!in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $last['name']; + } + + $location = new Location(); + + $location->location_id = $last['geonameId']; + $location->location_ns = self::NAMESPACE; + $location->lat = $last['lat']; + $location->lon = $last['lng']; + $location->names[$language] = implode(', ', array_reverse($parts)); + } + } + + // We're responsible for this NAMESPACE; nobody else + // can resolve it + + return false; + } + + /** + * convert a lat/lon pair into a Location object + * + * Given a lat/lon, we try to find a Location that's around + * it or nearby. We prefer populated places (cities, towns, villages). + * + * @param string $lat Latitude + * @param string $lon Longitude + * @param string $language ISO code for language for results + * @param Location &$location Location object (may be null) + * + * @return boolean whether to continue (results in $location) + */ + + function onLocationFromLatLon($lat, $lon, $language, &$location) + { + $client = HTTPClient::start(); + + $str = http_build_query(array('lat' => $lat, + 'lng' => $lon, + 'lang' => $language)); + + $result = + $client->get('http://ws.geonames.org/findNearbyPlaceNameJSON?'.$str); + + if ($result->code == "200") { + + $rj = json_decode($result->body); + + if (count($rj['geonames']) > 0) { + + $n = $rj['geonames'][0]; + + $parts = array(); + + $location = new Location(); + + $parts[] = $n['name']; + + if (!empty($n['adminName1'])) { + $parts[] = $n['adminName1']; + } + + if (!empty($n['countryName'])) { + $parts[] = $n['countryName']; + } + + $location->location_id = $n['geonameId']; + $location->location_ns = self::NAMESPACE; + $location->lat = $lat; + $location->lon = $lon; + + $location->names[$language] = implode(', ', $parts); + + // Success! We handled it, so no further processing + + return false; + } + } + + // For some reason we don't know, so pass. + + return true; + } + + /** + * Human-readable name for a location + * + * Given a location, we try to retrieve a human-readable name + * in the target language. + * + * @param Location $location Location to get the name for + * @param string $language ISO code for language to find name in + * @param string &$name Place to put the name + * + * @return boolean whether to continue + */ + + function onLocationNameLanguage($location, $language, &$name) + { + if ($location->location_ns != self::NAMESPACE) { + // It's not one of our IDs... keep processing + return true; + } + + $client = HTTPClient::start(); + + $str = http_build_query(array('geonameId' => $id, + 'lang' => $language)); + + $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); + + if ($result->code == "200") { + + $rj = json_decode($result->body); + + if (count($rj['geonames']) > 0) { + + $parts = array(); + + foreach ($rj['geonames'] as $level) { + if (in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $level['name']; + } + } + + $last = $rj['geonames'][count($rj['geonames'])-1]; + + if (!in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $last['name']; + } + + if (count($parts)) { + $name = implode(', ', array_reverse($parts)); + return false; + } + } + } + + return true; + } +} -- cgit v1.2.3-54-g00ecf From de4b2098d1a73734f94dd4a2c5395857fb6afa89 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 22 Oct 2009 16:20:31 -0400 Subject: results of json parsing are objects not arrays --- plugins/GeonamesPlugin.php | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) (limited to 'plugins') diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index 934e998c7..745cd4126 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -76,15 +76,15 @@ class GeonamesPlugin extends Plugin if ($result->code == "200") { $rj = json_decode($result->body); - if (count($rj['geonames']) > 0) { - $n = $rj['geonames'][0]; + if (count($rj->geonames) > 0) { + $n = $rj->geonames[0]; $location = new Location(); - $location->lat = $n['lat']; - $location->lon = $n['lng']; - $location->names[$language] = $n['name']; - $location->location_id = $n['geonameId']; + $location->lat = $n->lat; + $location->lon = $n->lng; + $location->names[$language] = $n->name; + $location->location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; // handled, don't continue processing! @@ -125,28 +125,28 @@ class GeonamesPlugin extends Plugin $rj = json_decode($result->body); - if (count($rj['geonames']) > 0) { + if (count($rj->geonames) > 0) { $parts = array(); - foreach ($rj['geonames'] as $level) { - if (in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { - $parts[] = $level['name']; + foreach ($rj->geonames as $level) { + if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $level->name; } } - $last = $rj['geonames'][count($rj['geonames'])-1]; + $last = $rj->geonames[count($rj->geonames)-1]; - if (!in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { - $parts[] = $last['name']; + if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $last->name; } $location = new Location(); - $location->location_id = $last['geonameId']; + $location->location_id = $last->geonameId; $location->location_ns = self::NAMESPACE; - $location->lat = $last['lat']; - $location->lon = $last['lng']; + $location->lat = $last->lat; + $location->lon = $last->lng; $location->names[$language] = implode(', ', array_reverse($parts)); } } @@ -186,25 +186,25 @@ class GeonamesPlugin extends Plugin $rj = json_decode($result->body); - if (count($rj['geonames']) > 0) { + if (count($rj->geonames) > 0) { - $n = $rj['geonames'][0]; + $n = $rj->geonames[0]; $parts = array(); $location = new Location(); - $parts[] = $n['name']; + $parts[] = $n->name; - if (!empty($n['adminName1'])) { - $parts[] = $n['adminName1']; + if (!empty($n->adminName1)) { + $parts[] = $n->adminName1; } - if (!empty($n['countryName'])) { - $parts[] = $n['countryName']; + if (!empty($n->countryName)) { + $parts[] = $n->countryName; } - $location->location_id = $n['geonameId']; + $location->location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; $location->lat = $lat; $location->lon = $lon; @@ -253,20 +253,20 @@ class GeonamesPlugin extends Plugin $rj = json_decode($result->body); - if (count($rj['geonames']) > 0) { + if (count($rj->geonames) > 0) { $parts = array(); - foreach ($rj['geonames'] as $level) { - if (in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { - $parts[] = $level['name']; + foreach ($rj->geonames as $level) { + if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $level->name; } } - $last = $rj['geonames'][count($rj['geonames'])-1]; + $last = $rj->geonames[count($rj->geonames)-1]; - if (!in_array($level['fcode'], array('PCLI', 'ADM1', 'PPL'))) { - $parts[] = $last['name']; + if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { + $parts[] = $last->name; } if (count($parts)) { -- cgit v1.2.3-54-g00ecf From 62ba25f53aaff0cbeb248b10bb2ed45dfbd2b671 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 23 Oct 2009 23:05:23 +0000 Subject: Some phpcs cleanup --- plugins/Facebook/FacebookPlugin.php | 109 ++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 16 deletions(-) (limited to 'plugins') diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index bcd1a7c74..fc3adcfad 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -65,7 +65,8 @@ class FacebookPlugin extends Plugin $m->connect('facebook/app', array('action' => 'facebookhome')); $m->connect('facebook/app/index.php', array('action' => 'facebookhome')); - $m->connect('facebook/app/settings.php', array('action' => 'facebooksettings')); + $m->connect('facebook/app/settings.php', + array('action' => 'facebooksettings')); $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); $m->connect('facebook/app/remove', array('action' => 'facebookremove')); @@ -87,6 +88,7 @@ class FacebookPlugin extends Plugin * @return boolean hook return * */ + function onAutoload($cls) { switch ($cls) { @@ -116,7 +118,15 @@ class FacebookPlugin extends Plugin } } - // Add in xmlns:fb + /** + * Override normal HTML output to force the content type to + * text/html and add in xmlns:fb + * + * @param Action $action the current action + * + * @return void + */ + function onStartShowHTML($action) { @@ -128,6 +138,7 @@ class FacebookPlugin extends Plugin // text/html even though Facebook Connect uses XHTML. This is // A bug in Facebook Connect, and this is a temporary solution // until they fix their JavaScript libs. + header('Content-Type: text/html'); $action->extraHeaders(); @@ -150,22 +161,31 @@ class FacebookPlugin extends Plugin } } - // Note: this script needs to appear in the + /** + * Add in the Facebook Connect JavaScript stuff + * + * Note: this script needs to appear in the + * + * @param Action $action the current action + * + * @return void + * + */ function onEndShowScripts($action) { if ($this->reqFbScripts($action)) { - $apikey = common_config('facebook', 'apikey'); + $apikey = common_config('facebook', 'apikey'); $plugin_path = common_path('plugins/Facebook'); - $login_url = common_local_url('FBConnectAuth'); + $login_url = common_local_url('FBConnectAuth'); $logout_url = common_local_url('logout'); // XXX: Facebook says we don't need this FB_RequireFeatures(), // but we actually do, for IE and Safari. Gar. - $js = '"; } else { diff --git a/plugins/Facebook/facebook/facebook_desktop.php b/plugins/Facebook/facebook/facebook_desktop.php index e79a2ca34..425bb5c7b 100644 --- a/plugins/Facebook/facebook/facebook_desktop.php +++ b/plugins/Facebook/facebook/facebook_desktop.php @@ -93,7 +93,7 @@ class FacebookDesktop extends Facebook { } public function verify_signature($fb_params, $expected_sig) { - // we don't want to verify the signature until we have a valid + // we do not want to verify the signature until we have a valid // session secret if ($this->verify_sig) { return parent::verify_signature($fb_params, $expected_sig); diff --git a/plugins/Facebook/facebook/facebookapi_php5_restlib.php b/plugins/Facebook/facebook/facebookapi_php5_restlib.php index e2a6fe88b..781390002 100755 --- a/plugins/Facebook/facebook/facebookapi_php5_restlib.php +++ b/plugins/Facebook/facebook/facebookapi_php5_restlib.php @@ -46,7 +46,7 @@ class FacebookRestClient { // on canvas pages public $added; public $is_user; - // we don't pass friends list to iframes, but we want to make + // we do not pass friends list to iframes, but we want to make // friends_get really simple in the canvas_user (non-logged in) case. // So we use the canvas_user as default arg to friends_get public $canvas_user; @@ -657,7 +657,7 @@ function toggleDisplay(id, type) { * deleted. * * IMPORTANT: If your application has registered public tags - * that other applications may be using, don't delete those tags! + * that other applications may be using, do not 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) @@ -820,7 +820,7 @@ function toggleDisplay(id, type) { if (is_array($target_ids)) { $target_ids = json_encode($target_ids); - $target_ids = trim($target_ids, "[]"); // we don't want square brackets + $target_ids = trim($target_ids, "[]"); // we do not want square brackets } return $this->call_method('facebook.feed.publishUserAction', diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php index 29509deba..9c6c62663 100644 --- a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php +++ b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php @@ -1,5 +1,5 @@ location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; - // handled, don't continue processing! + // handled, do not continue processing! return false; } } - // Continue processing; we don't have the answer + // Continue processing; we do not have the answer return true; } @@ -217,7 +217,7 @@ class GeonamesPlugin extends Plugin } } - // For some reason we don't know, so pass. + // For some reason we do not know, so pass. return true; } @@ -299,7 +299,7 @@ class GeonamesPlugin extends Plugin $url = 'http://www.geonames.org/' . $location->location_id; - // it's been filled, so don't process further. + // it's been filled, so do not process further. return false; } } diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php index ff0b451d3..b5d978294 100644 --- a/plugins/OpenID/finishopenidlogin.php +++ b/plugins/OpenID/finishopenidlogin.php @@ -341,7 +341,7 @@ class FinishopenidloginAction extends Action { $url = common_get_returnto(); if ($url) { - # We don't have to return to it again + # We do not have to return to it again common_set_returnto(null); } else { $url = common_local_url('all', @@ -421,7 +421,7 @@ class FinishopenidloginAction extends Action $parts = parse_url($openid); - # If any of these parts exist, this won't work + # If any of these parts exist, this will not work foreach ($bad as $badpart) { if (array_key_exists($badpart, $parts)) { diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index cd042226b..4a76a8791 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -187,7 +187,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) $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 + # XXX: This is cheap, but things choke if we do not escape ampersands # in the HTML attributes $form_html = preg_replace('/&/', '&', $form_html); diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php index 54faa0bdb..81ef7c683 100644 --- a/plugins/PiwikAnalyticsPlugin.php +++ b/plugins/PiwikAnalyticsPlugin.php @@ -44,7 +44,7 @@ if (!defined('STATUSNET')) { * 'piwikId' => 'id')); * * Replace 'example.com/piwik/' with the URL to your Piwik installation and - * make sure you don't forget the final /. + * make sure you do not forget the final /. * Replace 'id' with the ID your statusnet installation has in your Piwik * analytics setup - for example '8'. * diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 0c7c1240c..88a87dcf9 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -240,7 +240,7 @@ class RealtimePlugin extends Plugin // 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 ApiAction method. Don't do this unless you're me! + // the ApiAction method. Do not do this unless you're me! require_once(INSTALLDIR.'/lib/api.php'); diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index 6a155b301..76410c7cb 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -115,7 +115,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon // Each child ps needs its own DB connection // Note: DataObject::getDatabaseConnection() creates - // a new connection if there isn't one already + // a new connection if there is not one already $conn = &$flink->getDatabaseConnection(); diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index ab610e553..5d0d83be3 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -136,7 +136,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon // Each child ps needs its own DB connection // Note: DataObject::getDatabaseConnection() creates - // a new connection if there isn't one already + // a new connection if there is not one already $conn = &$flink->getDatabaseConnection(); @@ -499,7 +499,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon $avatar->height = 73; } - $avatar->original = 0; // we don't have the original + $avatar->original = 0; // we do not have the original $avatar->mediatype = $mediatype; $avatar->filename = $filename; $avatar->url = Avatar::url($filename); diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 3c6803e49..d48089caa 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -33,7 +33,7 @@ function updateTwitter_user($twitter_id, $screen_name) $fuser->query('BEGIN'); - // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem + // Dropping down to SQL because regular DB_DataObject udpate stuff does not 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 diff --git a/scripts/console.php b/scripts/console.php index 2413f5079..fecb75b4a 100755 --- a/scripts/console.php +++ b/scripts/console.php @@ -60,9 +60,9 @@ function read_input_line($prompt) } /** - * On Unix-like systems where PHP readline extension isn't present, + * On Unix-like systems where PHP readline extension is not present, * -cough- Mac OS X -cough- we can shell out to bash to do it for us. - * This lets us at least handle things like arrow keys, but we don't + * This lets us at least handle things like arrow keys, but we do not * get any entry history. :( * * Shamelessly ripped from when I wrote the same code for MediaWiki. :) diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php index b4e4d9f08..4ec4526ef 100755 --- a/scripts/maildaemon.php +++ b/scripts/maildaemon.php @@ -231,7 +231,7 @@ class MailerDaemon foreach ($parsed->parts as $part) { $this->extract_part($part,$msg,$attachments); } - //we don't want any attachments that are a result of this parsing + //we do not want any attachments that are a result of this parsing return $msg; } diff --git a/scripts/xmppconfirmhandler.php b/scripts/xmppconfirmhandler.php index c7ed15e49..f5f824dee 100755 --- a/scripts/xmppconfirmhandler.php +++ b/scripts/xmppconfirmhandler.php @@ -69,7 +69,7 @@ class XmppConfirmHandler extends XmppQueueHandler continue; } else { $this->log(LOG_INFO, 'Confirmation sent for ' . $confirm->address); - # Mark confirmation sent; need a dupe so we don't have the WHERE clause + # Mark confirmation sent; need a dupe so we do not have the WHERE clause $dupe = Confirm_address::staticGet('code', $confirm->code); if (!$dupe) { common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__); -- cgit v1.2.3-54-g00ecf From 2a0a363e1d846334bc37d46b5f6f42e64a5a96eb Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 8 Nov 2009 23:06:25 +0000 Subject: Updated Realtime plugin to use the util's NoticeReply object --- plugins/Realtime/realtimeupdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index e82b4dbfb..ca6ea891a 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -76,7 +76,7 @@ RealtimeUpdate = { $("#notices_primary .notices").prepend(noticeItem); $("#notices_primary .notice:first").css({display:"none"}); $("#notices_primary .notice:first").fadeIn(1000); - NoticeReply(); + SN.U.NoticeReply(); RealtimeUpdate._updatecounter += 1; document.title = '('+RealtimeUpdate._updatecounter+') ' + DT; -- cgit v1.2.3-54-g00ecf From 2577c85a38f94612f29183b6a0a36f14d1c1f07d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 9 Nov 2009 11:11:06 +0000 Subject: Added flag icon for UserFlag plugin --- plugins/UserFlag/flag.gif | Bin 0 -> 80 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 plugins/UserFlag/flag.gif (limited to 'plugins') diff --git a/plugins/UserFlag/flag.gif b/plugins/UserFlag/flag.gif new file mode 100644 index 000000000..68c8aee25 Binary files /dev/null and b/plugins/UserFlag/flag.gif differ -- cgit v1.2.3-54-g00ecf From 22310d17a4886d5382832caee43da0bcf7914419 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 9 Nov 2009 13:45:10 -0500 Subject: shorten flag notification and include a class --- plugins/UserFlag/UserFlagPlugin.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 2376bb859..fe4a74869 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -102,8 +102,7 @@ class UserFlagPlugin extends Plugin $action->elementStart('li', 'entity_flag'); if (User_flag_profile::exists($profile->id, $user->id)) { - $action->element('p', array(), - _('Flagged for review')); + $action->element('p', 'flagged', _('Flagged')); } else { $form = new FlagProfileForm($action, $profile, array('action' => 'showstream', -- cgit v1.2.3-54-g00ecf From 088081675fb7d5250a9b9dfe5015de0822cb5ac2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 9 Nov 2009 20:01:46 +0100 Subject: Revert "Remove more contractions" This reverts commit 5ab709b73977131813884558bf56d97172a7aa26. Missed this one yesterday... --- actions/allrss.php | 2 +- actions/apiaccountratelimitstatus.php | 2 +- actions/apifriendshipsdestroy.php | 2 +- actions/attachment.php | 4 ++-- actions/avatarbynickname.php | 2 +- actions/groupblock.php | 2 +- actions/login.php | 2 +- actions/logout.php | 2 +- actions/newmessage.php | 2 +- actions/newnotice.php | 2 +- actions/opensearch.php | 2 +- actions/passwordsettings.php | 2 +- actions/register.php | 2 +- actions/showgroup.php | 2 +- actions/showmessage.php | 8 ++++---- actions/shownotice.php | 6 +++--- actions/showstream.php | 2 +- actions/sup.php | 2 +- actions/twitapisearchatom.php | 2 +- actions/twitapitrends.php | 2 +- classes/File_redirection.php | 8 ++++---- classes/Notice.php | 6 +++--- classes/Profile.php | 4 ++-- classes/User.php | 6 +++--- lib/api.php | 14 +++++++------- lib/apiauth.php | 2 +- lib/dberroraction.php | 6 +++--- lib/error.php | 4 ++-- lib/htmloutputter.php | 2 +- lib/imagefile.php | 2 +- lib/jabber.php | 2 +- lib/mail.php | 6 +++--- lib/noticelist.php | 4 ++-- lib/queuehandler.php | 12 ++++++------ lib/rssaction.php | 2 +- lib/search_engines.php | 2 +- lib/util.php | 12 ++++++------ lib/xmloutputter.php | 2 +- lib/xmppqueuehandler.php | 2 +- plugins/Autocomplete/autocomplete.php | 2 +- plugins/BlogspamNetPlugin.php | 2 +- plugins/Facebook/FBConnectAuth.php | 4 ++-- plugins/Facebook/FacebookPlugin.php | 4 ++-- plugins/Facebook/facebook/facebook.php | 8 ++++---- plugins/Facebook/facebook/facebook_desktop.php | 2 +- plugins/Facebook/facebook/facebookapi_php5_restlib.php | 6 +++--- plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php | 2 +- plugins/Facebook/facebookaction.php | 6 +++--- plugins/GeonamesPlugin.php | 8 ++++---- plugins/OpenID/finishopenidlogin.php | 4 ++-- plugins/OpenID/openid.php | 2 +- plugins/PiwikAnalyticsPlugin.php | 2 +- plugins/Realtime/RealtimePlugin.php | 2 +- plugins/TwitterBridge/daemons/synctwitterfriends.php | 2 +- plugins/TwitterBridge/daemons/twitterstatusfetcher.php | 4 ++-- plugins/TwitterBridge/twitter.php | 2 +- scripts/console.php | 4 ++-- scripts/maildaemon.php | 2 +- scripts/xmppconfirmhandler.php | 2 +- 59 files changed, 110 insertions(+), 110 deletions(-) (limited to 'plugins') diff --git a/actions/allrss.php b/actions/allrss.php index 4a5d15c7b..28b1be27d 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -56,7 +56,7 @@ class AllrssAction extends Rss10Action * * @param array $args Web and URL arguments * - * @return boolean false if user does not exist + * @return boolean false if user doesn't exist */ function prepare($args) { diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php index c7c0e7c00..96179f175 100644 --- a/actions/apiaccountratelimitstatus.php +++ b/actions/apiaccountratelimitstatus.php @@ -36,7 +36,7 @@ if (!defined('STATUSNET')) { require_once INSTALLDIR . '/lib/apibareauth.php'; /** - * We do not have a rate limit, but some clients check this method. + * We don't have a rate limit, but some clients check this method. * It always returns the same thing: 150 hits left. * * @category API diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php index fb73624c9..3d9b7e001 100644 --- a/actions/apifriendshipsdestroy.php +++ b/actions/apifriendshipsdestroy.php @@ -113,7 +113,7 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction return; } - // Do not allow unsubscribing from yourself! + // Don't allow unsubscribing from yourself! if ($this->user->id == $this->other->id) { $this->clientError( diff --git a/actions/attachment.php b/actions/attachment.php index ca9e57845..6981354d1 100644 --- a/actions/attachment.php +++ b/actions/attachment.php @@ -146,7 +146,7 @@ class AttachmentAction extends Action } /** - * Do not show local navigation + * Don't show local navigation * * @return void */ @@ -170,7 +170,7 @@ class AttachmentAction extends Action } /** - * Do not show page notice + * Don't show page notice * * @return void */ diff --git a/actions/avatarbynickname.php b/actions/avatarbynickname.php index 1a6925e11..537950792 100644 --- a/actions/avatarbynickname.php +++ b/actions/avatarbynickname.php @@ -49,7 +49,7 @@ class AvatarbynicknameAction extends Action * * @param array $args query arguments * - * @return boolean false if nickname or user is not found + * @return boolean false if nickname or user isn't found */ function handle($args) { diff --git a/actions/groupblock.php b/actions/groupblock.php index 133101eb7..979a56a81 100644 --- a/actions/groupblock.php +++ b/actions/groupblock.php @@ -95,7 +95,7 @@ class GroupblockAction extends Action $this->clientError(_('User is already blocked from group.')); return false; } - // XXX: could have proactive blocks, but we do not have UI for it. + // XXX: could have proactive blocks, but we don't have UI for it. if (!$this->profile->isMember($this->group)) { $this->clientError(_('User is not a member of group.')); return false; diff --git a/actions/login.php b/actions/login.php index 679817520..ad57dd667 100644 --- a/actions/login.php +++ b/actions/login.php @@ -159,7 +159,7 @@ class LoginAction extends Action $url = common_get_returnto(); if ($url) { - // We do not have to return to it again + // We don't have to return to it again common_set_returnto(null); } else { $url = common_local_url('all', diff --git a/actions/logout.php b/actions/logout.php index 7e768fca6..1e0adae57 100644 --- a/actions/logout.php +++ b/actions/logout.php @@ -81,7 +81,7 @@ class LogoutAction extends Action { common_set_user(null); common_real_login(false); // not logged in - common_forgetme(); // do not log back in! + common_forgetme(); // don't log back in! } } diff --git a/actions/newmessage.php b/actions/newmessage.php index 73307fdfc..0db2e7181 100644 --- a/actions/newmessage.php +++ b/actions/newmessage.php @@ -61,7 +61,7 @@ class NewmessageAction extends Action /** * Title of the page * - * Note that this usually does not get called unless something went wrong + * Note that this usually doesn't get called unless something went wrong * * @return string page title */ diff --git a/actions/newnotice.php b/actions/newnotice.php index fc06e5c98..fbd7ab6bc 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -59,7 +59,7 @@ class NewnoticeAction extends Action /** * Title of the page * - * Note that this usually does not get called unless something went wrong + * Note that this usually doesn't get called unless something went wrong * * @return string page title */ diff --git a/actions/opensearch.php b/actions/opensearch.php index b205d2fe2..861b53d7d 100644 --- a/actions/opensearch.php +++ b/actions/opensearch.php @@ -52,7 +52,7 @@ class OpensearchAction extends Action * * @param array $args query arguments * - * @return boolean false if user does not exist + * @return boolean false if user doesn't exist */ function handle($args) { diff --git a/actions/passwordsettings.php b/actions/passwordsettings.php index 6658d279f..87eb45a7d 100644 --- a/actions/passwordsettings.php +++ b/actions/passwordsettings.php @@ -97,7 +97,7 @@ class PasswordsettingsAction extends AccountSettingsAction $this->elementStart('ul', 'form_data'); - // Users who logged in with OpenID will not have a pwd + // Users who logged in with OpenID won't have a pwd if ($user->password) { $this->elementStart('li'); $this->password('oldpassword', _('Old password')); diff --git a/actions/register.php b/actions/register.php index c4f6760aa..57f8e7bdf 100644 --- a/actions/register.php +++ b/actions/register.php @@ -174,7 +174,7 @@ class RegisterAction extends Action $bio = $this->trimmed('bio'); $location = $this->trimmed('location'); - // We do not trim these... whitespace is OK in a password! + // We don't trim these... whitespace is OK in a password! $password = $this->arg('password'); $confirm = $this->arg('confirm'); diff --git a/actions/showgroup.php b/actions/showgroup.php index ae956befa..a4af29391 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -418,7 +418,7 @@ class ShowgroupAction extends GroupDesignAction // XXX: WORM cache this $members = $this->group->getMembers(); $members_count = 0; - /** $member->count() does not work. */ + /** $member->count() doesn't work. */ while ($members->fetch()) { $members_count++; } diff --git a/actions/showmessage.php b/actions/showmessage.php index cf3a819c1..db757948b 100644 --- a/actions/showmessage.php +++ b/actions/showmessage.php @@ -137,7 +137,7 @@ class ShowmessageAction extends MailboxAction } /** - * Do not show local navigation + * Don't show local navigation * * @return void */ @@ -147,7 +147,7 @@ class ShowmessageAction extends MailboxAction } /** - * Do not show page notice + * Don't show page notice * * @return void */ @@ -157,7 +157,7 @@ class ShowmessageAction extends MailboxAction } /** - * Do not show aside + * Don't show aside * * @return void */ @@ -167,7 +167,7 @@ class ShowmessageAction extends MailboxAction } /** - * Do not show any instructions + * Don't show any instructions * * @return string */ diff --git a/actions/shownotice.php b/actions/shownotice.php index 688089f02..5d16fdad9 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -208,7 +208,7 @@ class ShownoticeAction extends OwnerDesignAction } /** - * Do not show local navigation + * Don't show local navigation * * @return void */ @@ -234,7 +234,7 @@ class ShownoticeAction extends OwnerDesignAction } /** - * Do not show page notice + * Don't show page notice * * @return void */ @@ -244,7 +244,7 @@ class ShownoticeAction extends OwnerDesignAction } /** - * Do not show aside + * Don't show aside * * @return void */ diff --git a/actions/showstream.php b/actions/showstream.php index 4952ebdb7..663638c18 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -253,7 +253,7 @@ class ShowstreamAction extends ProfileAction } } -// We do not show the author for a profile, since we already know who it is! +// We don't show the author for a profile, since we already know who it is! class ProfileNoticeList extends NoticeList { diff --git a/actions/sup.php b/actions/sup.php index a199f247e..5daf0a1c1 100644 --- a/actions/sup.php +++ b/actions/sup.php @@ -61,7 +61,7 @@ class SupAction extends Action $notice = new Notice(); # XXX: cache this. Depends on how big this protocol becomes; - # Re-doing this query every 15 seconds is not the end of the world. + # Re-doing this query every 15 seconds isn't the end of the world. $divider = common_sql_date(time() - $seconds); diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 511d7cdc6..7d618c471 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -250,7 +250,7 @@ class TwitapisearchatomAction extends ApiAction } // FIXME: this alternate link is not quite right because our - // web-based notice search does not support a rpp (responses per + // web-based notice search doesn't support a rpp (responses per // page) param yet $this->element('link', array('type' => 'text/html', diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php index 2d17e77cc..779405e6d 100644 --- a/actions/twitapitrends.php +++ b/actions/twitapitrends.php @@ -55,7 +55,7 @@ class TwitapitrendsAction extends ApiAction * * @param array $args Web and URL arguments * - * @return boolean false if user does not exist + * @return boolean false if user doesn't exist */ function prepare($args) { diff --git a/classes/File_redirection.php b/classes/File_redirection.php index c951c1ee7..08a6e8d8b 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -53,7 +53,7 @@ class File_redirection extends Memcached_DataObject 'connect_timeout' => 10, // # seconds to wait 'max_redirs' => $redirs, // # max number of http redirections to follow 'follow_redirects' => true, // Follow redirects - 'store_body' => false, // We will not need body content here. + 'store_body' => false, // We won't need body content here. )); return $request; } @@ -81,12 +81,12 @@ class File_redirection extends Memcached_DataObject } try { $request = self::_commonHttp($short_url, $redirs); - // Do not include body in output + // Don't include body in output $request->setMethod(HTTP_Request2::METHOD_HEAD); $response = $request->send(); if (405 == $response->getStatus()) { - // Server does not support HEAD method? Can this really happen? + // Server doesn't support HEAD method? Can this really happen? // We'll try again as a GET and ignore the response data. $request = self::_commonHttp($short_url, $redirs); $response = $request->send(); @@ -178,7 +178,7 @@ class File_redirection extends Memcached_DataObject case 'aim': case 'jabber': case 'xmpp': - // do not touch anything + // don't touch anything break; default: diff --git a/classes/Notice.php b/classes/Notice.php index 32a8b693c..9886875cb 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -146,7 +146,7 @@ class Notice extends Memcached_DataObject /* Add them to the database */ foreach(array_unique($hashtags) as $hashtag) { - /* elide characters we do not want in the tag */ + /* elide characters we don't want in the tag */ $this->saveTag($hashtag); } return true; @@ -1105,7 +1105,7 @@ class Notice extends Memcached_DataObject if (empty($recipient)) { continue; } - // Do not save replies from blocked profile to local user + // Don't save replies from blocked profile to local user $recipient_user = User::staticGet('id', $recipient->id); if (!empty($recipient_user) && $recipient_user->hasBlocked($sender)) { continue; @@ -1131,7 +1131,7 @@ class Notice extends Memcached_DataObject $tagged = Profile_tag::getTagged($sender->id, $tag); foreach ($tagged as $t) { if (!$replied[$t->id]) { - // Do not save replies from blocked profile to local user + // Don't save replies from blocked profile to local user $t_user = User::staticGet('id', $t->id); if ($t_user && $t_user->hasBlocked($sender)) { continue; diff --git a/classes/Profile.php b/classes/Profile.php index a50f4951d..7c1e9db33 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -101,7 +101,7 @@ class Profile extends Memcached_DataObject } foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { - # We do not do a scaled one if original is our scaled size + # We don't do a scaled one if original is our scaled size if (!($avatar->width == $size && $avatar->height == $size)) { $scaled_filename = $imagefile->resize($size); @@ -174,7 +174,7 @@ class Profile extends Memcached_DataObject function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) { - // XXX: I'm not sure this is going to be any faster. It probably is not. + // XXX: I'm not sure this is going to be any faster. It probably isn't. $ids = Notice::stream(array($this, '_streamDirect'), array(), 'profile:notice_ids:' . $this->id, diff --git a/classes/User.php b/classes/User.php index c529b82e0..9b90ce61b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -87,7 +87,7 @@ class User extends Memcached_DataObject return (is_null($sub)) ? false : true; } - // 'update' will not write key columns, so we have to do it ourselves. + // 'update' won't write key columns, so we have to do it ourselves. function updateKeys(&$orig) { @@ -384,7 +384,7 @@ class User extends Memcached_DataObject return false; } - // Otherwise, cache does not have all faves; + // Otherwise, cache doesn't have all faves; // fall through to the default } @@ -463,7 +463,7 @@ class User extends Memcached_DataObject { $cache = common_memcache(); if ($cache) { - // Faves do not happen chronologically, so we need to blow + // Faves don't happen chronologically, so we need to blow // ;last cache, too $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id)); $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last')); diff --git a/lib/api.php b/lib/api.php index fb4c4289b..a1236ab7e 100644 --- a/lib/api.php +++ b/lib/api.php @@ -66,7 +66,7 @@ class ApiAction extends Action * * @param array $args Web and URL arguments * - * @return boolean false if user does not exist + * @return boolean false if user doesn't exist */ function prepare($args) @@ -138,7 +138,7 @@ class ApiAction extends Action $design = null; $user = $profile->getUser(); - // Note: some profiles do not have an associated user + // Note: some profiles don't have an associated user if (!empty($user)) { $design = $user->getDesign(); @@ -203,7 +203,7 @@ class ApiAction extends Action if ($get_notice) { $notice = $profile->getCurrentNotice(); if ($notice) { - # do not get user! + # don't get user! $twitter_user['status'] = $this->twitterStatusArray($notice, false); } } @@ -263,7 +263,7 @@ class ApiAction extends Action } if ($include_user) { - # Do not get notice (recursive!) + # Don't get notice (recursive!) $twitter_user = $this->twitterUserArray($profile, false); $twitter_status['user'] = $twitter_user; } @@ -1074,7 +1074,7 @@ class ApiAction extends Action function initTwitterAtom() { $this->startXML(); - // FIXME: do not hardcode the language here! + // FIXME: don't hardcode the language here! $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); @@ -1116,7 +1116,7 @@ class ApiAction extends Action return User::staticGet('nickname', $nickname); } else if ($this->arg('user_id')) { // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it does not get used + // overrides screen_name even if it doesn't get used if (is_numeric($this->arg('user_id'))) { return User::staticGet('id', $this->arg('user_id')); } @@ -1146,7 +1146,7 @@ class ApiAction extends Action return User_group::staticGet('nickname', $nickname); } else if ($this->arg('group_id')) { // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it does not get used + // overrides screen_name even if it doesn't get used if (is_numeric($this->arg('group_id'))) { return User_group::staticGet('id', $this->arg('group_id')); } diff --git a/lib/apiauth.php b/lib/apiauth.php index b8189f15d..2f2e44a26 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -87,7 +87,7 @@ class ApiAuthAction extends ApiAction } /** - * Check for a user specified via HTTP basic auth. If there is not + * Check for a user specified via HTTP basic auth. If there isn't * one, try to get one by outputting the basic auth header. * * @return boolean true or false diff --git a/lib/dberroraction.php b/lib/dberroraction.php index 893797b70..2cb66a022 100644 --- a/lib/dberroraction.php +++ b/lib/dberroraction.php @@ -39,7 +39,7 @@ require_once INSTALLDIR.'/lib/servererroraction.php'; * * This only occurs if there's been a DB_DataObject_Error that's * reported through PEAR, so we try to avoid doing anything that connects - * to the DB, so we do not trigger it again. + * to the DB, so we don't trigger it again. * * @category Action * @package StatusNet @@ -62,12 +62,12 @@ class DBErrorAction extends ServerErrorAction function getLanguage() { - // Do not try to figure out user's language; just show the page + // Don't try to figure out user's language; just show the page return common_config('site', 'language'); } function showPrimaryNav() { - // do not show primary nav + // don't show primary nav } } diff --git a/lib/error.php b/lib/error.php index 5ed5dec1b..3162cfe65 100644 --- a/lib/error.php +++ b/lib/error.php @@ -104,11 +104,11 @@ class ErrorAction extends Action { parent::showPage(); - // We do not want to have any more output after this + // We don't want to have any more output after this exit(); } - // Overload a bunch of stuff so the page is not too bloated + // Overload a bunch of stuff so the page isn't too bloated function showBody() { diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 73bd9ce81..c2ec83c28 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -76,7 +76,7 @@ class HTMLOutputter extends XMLOutputter /** * Start an HTML document * - * If $type is not specified, will attempt to do content negotiation. + * If $type isn't specified, will attempt to do content negotiation. * * Attempts to do content negotiation for language, also. * diff --git a/lib/imagefile.php b/lib/imagefile.php index edc7218d0..cf1668f20 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -119,7 +119,7 @@ class ImageFile return; } - // Do not crop/scale if it is not necessary + // Don't crop/scale if it isn't necessary if ($size === $this->width && $size === $this->height && $x === 0 diff --git a/lib/jabber.php b/lib/jabber.php index d666fcbb3..73f2ec660 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -437,7 +437,7 @@ function jabber_public_notice($notice) $public = common_config('xmpp', 'public'); - // FIXME PRIV do not send out private messages here + // FIXME PRIV don't send out private messages here // XXX: should we send out non-local messages if public,localonly // = false? I think not diff --git a/lib/mail.php b/lib/mail.php index 79630b721..5218059e9 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -467,7 +467,7 @@ function mail_notify_nudge($from, $to) "these days and is inviting you to post some news.\n\n". "So let's hear from you :)\n\n". "%3\$s\n\n". - "Do not reply to this email. It will not get to them.\n\n". + "Don't reply to this email; it won't get to them.\n\n". "With kind regards,\n". "%4\$s\n"), $from_profile->getBestName(), @@ -516,7 +516,7 @@ function mail_notify_message($message, $from=null, $to=null) "------------------------------------------------------\n\n". "You can reply to their message here:\n\n". "%4\$s\n\n". - "Do not reply to this email. It will not get to them.\n\n". + "Don't reply to this email; it won't get to them.\n\n". "With kind regards,\n". "%5\$s\n"), $from_profile->getBestName(), @@ -532,7 +532,7 @@ function mail_notify_message($message, $from=null, $to=null) /** * notify a user that one of their notices has been chosen as a 'fave' * - * Does not check that the user has an email address nor if they + * Doesn't check that the user has an email address nor if they * want to receive notification of faves. Maybe this happens higher * up the stack...? * diff --git a/lib/noticelist.php b/lib/noticelist.php index 4e5623ded..bf12bb73c 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -347,7 +347,7 @@ class NoticeListItem extends Widget * show the link to the main page for the notice * * Displays a link to the page for a notice, with "relative" time. Tries to - * get remote notice URLs correct, but does not always succeed. + * get remote notice URLs correct, but doesn't always succeed. * * @return void */ @@ -483,7 +483,7 @@ class NoticeListItem extends Widget * show a link to reply to the current notice * * Should either do the reply in the current notice form (if available), or - * link out to the notice-posting form. A little flakey, does not always work. + * link out to the notice-posting form. A little flakey, doesn't always work. * * @return void */ diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 7c07ca4f9..cd43b1e09 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -96,8 +96,8 @@ class QueueHandler extends Daemon * Initialization, run when the queue handler starts. * If this function indicates failure, the handler run will be aborted. * - * @fixme run() will abort if this does not return true, - * but some subclasses do not bother. + * @fixme run() will abort if this doesn't return true, + * but some subclasses don't bother. * @return boolean true on success, false on failure */ function start() @@ -108,8 +108,8 @@ class QueueHandler extends Daemon * Cleanup, run when the queue handler ends. * If this function indicates failure, a warning will be logged. * - * @fixme run() will throw warnings if this does not return true, - * but many subclasses do not bother. + * @fixme run() will throw warnings if this doesn't return true, + * but many subclasses don't bother. * @return boolean true on success, false on failure */ function finish() @@ -137,7 +137,7 @@ class QueueHandler extends Daemon * method, which passes control back to our handle_notice() method for * each notice that comes in on the queue. * - * Most of the time this will not need to be overridden in a subclass. + * Most of the time this won't need to be overridden in a subclass. * * @return boolean true on success, false on failure */ @@ -173,7 +173,7 @@ class QueueHandler extends Daemon * Called by QueueHandler after each handled item or empty polling cycle. * This is a good time to e.g. service your XMPP connection. * - * Does not need to be overridden if there's no maintenance to do. + * Doesn't need to be overridden if there's no maintenance to do. * * @param int $timeout seconds to sleep if there's nothing to do */ diff --git a/lib/rssaction.php b/lib/rssaction.php index 0e84a65e9..faf6bec7d 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -386,7 +386,7 @@ class Rss10Action extends Action return null; } - // FIXME: does not handle modified profiles, avatars, deleted notices + // FIXME: doesn't handle modified profiles, avatars, deleted notices return strtotime($this->notices[0]->created); } diff --git a/lib/search_engines.php b/lib/search_engines.php index 82713235c..69f6ff468 100644 --- a/lib/search_engines.php +++ b/lib/search_engines.php @@ -119,7 +119,7 @@ class MySQLSearch extends SearchEngine return true; } else if ('identica_notices' === $this->table) { - // Do not show imported notices + // Don't show imported notices $this->target->whereAdd('notice.is_local != ' . Notice::GATEWAY); if (strtolower($q) != $q) { diff --git a/lib/util.php b/lib/util.php index b4f5af1af..a4865c46c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -62,7 +62,7 @@ function common_init_language() $locale_set = common_init_locale($language); setlocale(LC_CTYPE, 'C'); - // So we do not have to make people install the gettext locales + // So we don't have to make people install the gettext locales $path = common_config('site','locale_path'); bindtextdomain("statusnet", $path); bind_textdomain_codeset("statusnet", "UTF-8"); @@ -139,7 +139,7 @@ function common_check_user($nickname, $password) } } }else{ - //no handler indicated the credentials were valid, and we know their not valid because the user is not in the database + //no handler indicated the credentials were valid, and we know their not valid because the user isn't in the database return false; } } else { @@ -396,7 +396,7 @@ function common_current_user() } // Logins that are 'remembered' aren't 'real' -- they're subject to -// cookie-stealing. So, we do not let them do certain things. New reg, +// cookie-stealing. So, we don't let them do certain things. New reg, // OpenID, and password logins _are_ real. function common_real_login($real=true) @@ -1147,7 +1147,7 @@ function common_accept_to_prefs($accept, $def = '*/*') $parts = explode(',', $accept); foreach($parts as $part) { - // FIXME: does not deal with params like 'text/html; level=1' + // FIXME: doesn't deal with params like 'text/html; level=1' @list($value, $qpart) = explode(';', trim($part)); $match = array(); if(!isset($qpart)) { @@ -1346,7 +1346,7 @@ function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext) } // FIXME: show error page if we're on the Web - /* Do not execute PHP internal error handler */ + /* Don't execute PHP internal error handler */ return true; } @@ -1448,7 +1448,7 @@ function common_shorten_url($long_url) } global $_shorteners; if (!isset($_shorteners[$svc])) { - //the user selected service does not exist, so default to ur1.ca + //the user selected service doesn't exist, so default to ur1.ca $svc = 'ur1.ca'; } if (!isset($_shorteners[$svc])) { diff --git a/lib/xmloutputter.php b/lib/xmloutputter.php index 9d862b2d0..5f06e491d 100644 --- a/lib/xmloutputter.php +++ b/lib/xmloutputter.php @@ -112,7 +112,7 @@ class XMLOutputter * * Utility for outputting an XML element. A convenient wrapper * for a bunch of longer XMLWriter calls. This is best for - * when an element does not have any sub-elements; if that's the + * when an element doesn't have any sub-elements; if that's the * case, use elementStart() and elementEnd() instead. * * The $content element will be escaped for XML. If you need diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php index 7caa078ae..f28fc9088 100644 --- a/lib/xmppqueuehandler.php +++ b/lib/xmppqueuehandler.php @@ -37,7 +37,7 @@ class XmppQueueHandler extends QueueHandler function start() { - # Low priority; we do not want to receive messages + # Low priority; we don't want to receive messages $this->log(LOG_INFO, "INITIALIZE"); $this->conn = jabber_connect($this->_id.$this->transport()); diff --git a/plugins/Autocomplete/autocomplete.php b/plugins/Autocomplete/autocomplete.php index aeb100cfa..379390ffd 100644 --- a/plugins/Autocomplete/autocomplete.php +++ b/plugins/Autocomplete/autocomplete.php @@ -79,7 +79,7 @@ class AutocompleteAction extends Action function etag() { return '"' . implode(':', array($this->arg('action'), - crc32($this->arg('q')), //the actual string can have funny characters in we do not want showing up in the etag + crc32($this->arg('q')), //the actual string can have funny characters in we don't want showing up in the etag $this->arg('limit'), $this->lastModified())) . '"'; } diff --git a/plugins/BlogspamNetPlugin.php b/plugins/BlogspamNetPlugin.php index bf60fdcaf..51236001a 100644 --- a/plugins/BlogspamNetPlugin.php +++ b/plugins/BlogspamNetPlugin.php @@ -85,7 +85,7 @@ class BlogspamNetPlugin extends Plugin } else if (preg_match('/^SPAM(:(.*))?$/', $response, $match)) { throw new ClientException(sprintf(_("Spam checker results: %s"), $match[2]), 400); } else if (preg_match('/^OK$/', $response)) { - // do not do anything + // don't do anything } else { throw new ServerException(sprintf(_("Unexpected response from %s: %s"), $this->baseUrl, $response), 500); } diff --git a/plugins/Facebook/FBConnectAuth.php b/plugins/Facebook/FBConnectAuth.php index 165477419..b909a4977 100644 --- a/plugins/Facebook/FBConnectAuth.php +++ b/plugins/Facebook/FBConnectAuth.php @@ -71,7 +71,7 @@ class FBConnectauthAction extends Action 'There is already a local user (' . $flink->user_id . ') linked with this Facebook (' . $this->fbuid . ').'); - // We do not want these cookies + // We don't want these cookies getFacebook()->clear_cookie_state(); $this->clientError(_('There is already a local user linked with this Facebook.')); @@ -364,7 +364,7 @@ class FBConnectauthAction extends Action { $url = common_get_returnto(); if ($url) { - // We do not have to return to it again + // We don't have to return to it again common_set_returnto(null); } else { $url = common_local_url('all', diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index cd1ad7b45..b68534b24 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -182,7 +182,7 @@ class FacebookPlugin extends Plugin $login_url = common_local_url('FBConnectAuth'); $logout_url = common_local_url('logout'); - // XXX: Facebook says we do not need this FB_RequireFeatures(), + // XXX: Facebook says we don't need this FB_RequireFeatures(), // but we actually do, for IE and Safari. Gar. $js = '"; } else { diff --git a/plugins/Facebook/facebook/facebook_desktop.php b/plugins/Facebook/facebook/facebook_desktop.php index 425bb5c7b..e79a2ca34 100644 --- a/plugins/Facebook/facebook/facebook_desktop.php +++ b/plugins/Facebook/facebook/facebook_desktop.php @@ -93,7 +93,7 @@ class FacebookDesktop extends Facebook { } public function verify_signature($fb_params, $expected_sig) { - // we do not want to verify the signature until we have a valid + // 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); diff --git a/plugins/Facebook/facebook/facebookapi_php5_restlib.php b/plugins/Facebook/facebook/facebookapi_php5_restlib.php index c742df748..55cb7fb86 100755 --- a/plugins/Facebook/facebook/facebookapi_php5_restlib.php +++ b/plugins/Facebook/facebook/facebookapi_php5_restlib.php @@ -46,7 +46,7 @@ class FacebookRestClient { // on canvas pages public $added; public $is_user; - // we do not pass friends list to iframes, but we want to make + // we don't pass friends list to iframes, but we want to make // friends_get really simple in the canvas_user (non-logged in) case. // So we use the canvas_user as default arg to friends_get public $canvas_user; @@ -657,7 +657,7 @@ function toggleDisplay(id, type) { * deleted. * * IMPORTANT: If your application has registered public tags - * that other applications may be using, do not delete those 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) @@ -820,7 +820,7 @@ function toggleDisplay(id, type) { if (is_array($target_ids)) { $target_ids = json_encode($target_ids); - $target_ids = trim($target_ids, "[]"); // we do not want square brackets + $target_ids = trim($target_ids, "[]"); // we don't want square brackets } return $this->call_method('facebook.feed.publishUserAction', diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php index 9c6c62663..29509deba 100644 --- a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php +++ b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php @@ -1,5 +1,5 @@ location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; - // handled, do not continue processing! + // handled, don't continue processing! return false; } } - // Continue processing; we do not have the answer + // Continue processing; we don't have the answer return true; } @@ -217,7 +217,7 @@ class GeonamesPlugin extends Plugin } } - // For some reason we do not know, so pass. + // For some reason we don't know, so pass. return true; } @@ -299,7 +299,7 @@ class GeonamesPlugin extends Plugin $url = 'http://www.geonames.org/' . $location->location_id; - // it's been filled, so do not process further. + // it's been filled, so don't process further. return false; } } diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php index b5d978294..ff0b451d3 100644 --- a/plugins/OpenID/finishopenidlogin.php +++ b/plugins/OpenID/finishopenidlogin.php @@ -341,7 +341,7 @@ class FinishopenidloginAction extends Action { $url = common_get_returnto(); if ($url) { - # We do not have to return to it again + # We don't have to return to it again common_set_returnto(null); } else { $url = common_local_url('all', @@ -421,7 +421,7 @@ class FinishopenidloginAction extends Action $parts = parse_url($openid); - # If any of these parts exist, this will not work + # If any of these parts exist, this won't work foreach ($bad as $badpart) { if (array_key_exists($badpart, $parts)) { diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index c5f6d1713..ff7a93899 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -187,7 +187,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) $form_html = $auth_request->formMarkup($trust_root, $process_url, $immediate, array('id' => $form_id)); - # XXX: This is cheap, but things choke if we do not escape ampersands + # XXX: This is cheap, but things choke if we don't escape ampersands # in the HTML attributes $form_html = preg_replace('/&/', '&', $form_html); diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php index 81ef7c683..54faa0bdb 100644 --- a/plugins/PiwikAnalyticsPlugin.php +++ b/plugins/PiwikAnalyticsPlugin.php @@ -44,7 +44,7 @@ if (!defined('STATUSNET')) { * 'piwikId' => 'id')); * * Replace 'example.com/piwik/' with the URL to your Piwik installation and - * make sure you do not forget the final /. + * make sure you don't forget the final /. * Replace 'id' with the ID your statusnet installation has in your Piwik * analytics setup - for example '8'. * diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 88a87dcf9..0c7c1240c 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -240,7 +240,7 @@ class RealtimePlugin extends Plugin // 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 ApiAction method. Do not do this unless you're me! + // the ApiAction method. Don't do this unless you're me! require_once(INSTALLDIR.'/lib/api.php'); diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index c89c02eed..671e3c7af 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -115,7 +115,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon // Each child ps needs its own DB connection // Note: DataObject::getDatabaseConnection() creates - // a new connection if there is not one already + // a new connection if there isn't one already $conn = &$flink->getDatabaseConnection(); diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 25df0d839..b5428316b 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -136,7 +136,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon // Each child ps needs its own DB connection // Note: DataObject::getDatabaseConnection() creates - // a new connection if there is not one already + // a new connection if there isn't one already $conn = &$flink->getDatabaseConnection(); @@ -499,7 +499,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon $avatar->height = 73; } - $avatar->original = 0; // we do not have the original + $avatar->original = 0; // we don't have the original $avatar->mediatype = $mediatype; $avatar->filename = $filename; $avatar->url = Avatar::url($filename); diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index d48089caa..3c6803e49 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -33,7 +33,7 @@ function updateTwitter_user($twitter_id, $screen_name) $fuser->query('BEGIN'); - // Dropping down to SQL because regular DB_DataObject udpate stuff does not seem + // 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 diff --git a/scripts/console.php b/scripts/console.php index 2a000d39b..41dd43f28 100755 --- a/scripts/console.php +++ b/scripts/console.php @@ -60,9 +60,9 @@ function read_input_line($prompt) } /** - * On Unix-like systems where PHP readline extension is not present, + * On Unix-like systems where PHP readline extension isn't present, * -cough- Mac OS X -cough- we can shell out to bash to do it for us. - * This lets us at least handle things like arrow keys, but we do not + * This lets us at least handle things like arrow keys, but we don't * get any entry history. :( * * Shamelessly ripped from when I wrote the same code for MediaWiki. :) diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php index 4ec4526ef..b4e4d9f08 100755 --- a/scripts/maildaemon.php +++ b/scripts/maildaemon.php @@ -231,7 +231,7 @@ class MailerDaemon foreach ($parsed->parts as $part) { $this->extract_part($part,$msg,$attachments); } - //we do not want any attachments that are a result of this parsing + //we don't want any attachments that are a result of this parsing return $msg; } diff --git a/scripts/xmppconfirmhandler.php b/scripts/xmppconfirmhandler.php index f5f824dee..c7ed15e49 100755 --- a/scripts/xmppconfirmhandler.php +++ b/scripts/xmppconfirmhandler.php @@ -69,7 +69,7 @@ class XmppConfirmHandler extends XmppQueueHandler continue; } else { $this->log(LOG_INFO, 'Confirmation sent for ' . $confirm->address); - # Mark confirmation sent; need a dupe so we do not have the WHERE clause + # Mark confirmation sent; need a dupe so we don't have the WHERE clause $dupe = Confirm_address::staticGet('code', $confirm->code); if (!$dupe) { common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__); -- cgit v1.2.3-54-g00ecf From 3be120571446880cb71a57845204b3213e6df67e Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 9 Nov 2009 17:43:37 -0500 Subject: Add a new event: CanUserChangeField --- EVENTS.txt | 4 ++++ actions/passwordsettings.php | 14 ++++++++++++++ lib/accountsettingsaction.php | 35 ++++++++++++++++++++--------------- plugins/Ldap/LdapPlugin.php | 11 +++++++++++ 4 files changed, 49 insertions(+), 15 deletions(-) (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index 25a51516b..c3fe73134 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -489,6 +489,10 @@ ChangePassword: Handle a password change request - $newpassword: the desired new password - &$errormsg: set this to an error message if the password could not be changed. If the password was changed, leave this as false +CanUserChangeField: Determines if a user is allowed to change a specific profile field +- $nickname: nickname of the user who would like to know which of their profile fields are mutable +- $field: name of the field the user wants to change (nickname, fullname, password, avatar, etc) + UserDeleteRelated: Specify additional tables to delete entries from when deleting users - $user: User object - &$related: array of DB_DataObject class names to delete entries on matching user_id. diff --git a/actions/passwordsettings.php b/actions/passwordsettings.php index 6658d279f..15539d4a0 100644 --- a/actions/passwordsettings.php +++ b/actions/passwordsettings.php @@ -58,6 +58,19 @@ class PasswordsettingsAction extends AccountSettingsAction return _('Change password'); } + function prepare($args){ + parent::prepare($args); + + $user = common_current_user(); + + Event::handle('CanUserChangeField', array($user->nickname, 'password')); + + if(! $fields['password']){ + //user is not allowed to change his password + $this->clientError(_('You are not allowed to change your password')); + } + } + /** * Instructions for use * @@ -86,6 +99,7 @@ class PasswordsettingsAction extends AccountSettingsAction function showContent() { $user = common_current_user(); + $this->elementStart('form', array('method' => 'POST', 'id' => 'form_password', 'class' => 'form_settings', diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php index a004a3ed9..9865e1748 100644 --- a/lib/accountsettingsaction.php +++ b/lib/accountsettingsaction.php @@ -102,26 +102,31 @@ class AccountSettingsNav extends Widget $this->action->elementStart('ul', array('class' => 'nav')); if (Event::handle('StartAccountSettingsNav', array(&$this->action))) { + $user = common_current_user(); - $menu = - array('profilesettings' => + $menu = array(); + $menu['profilesettings'] = array(_('Profile'), - _('Change your profile settings')), - 'avatarsettings' => - array(_('Avatar'), - _('Upload an avatar')), - 'passwordsettings' => - array(_('Password'), - _('Change your password')), - 'emailsettings' => + _('Change your profile settings')); + if(Event::handle('CanUserChangeField', array($user->nickname, 'avatar'))){ + $menu['avatarsettings'] = + array(_('Avatar'), + _('Upload an avatar')); + } + if(Event::handle('CanUserChangeField', array($user->nickname, 'password'))){ + $menu['passwordsettings'] = + array(_('Password'), + _('Change your password')); + } + $menu['emailsettings'] = array(_('Email'), - _('Change email handling')), - 'userdesignsettings' => + _('Change email handling')); + $menu['userdesignsettings'] = array(_('Design'), - _('Design your profile')), - 'othersettings' => + _('Design your profile')); + $menu['othersettings'] = array(_('Other'), - _('Other options'))); + _('Other options')); foreach ($menu as $menuaction => $menudesc) { $this->action->menuItem(common_local_url($menuaction), diff --git a/plugins/Ldap/LdapPlugin.php b/plugins/Ldap/LdapPlugin.php index 755562f54..3795ffd7f 100644 --- a/plugins/Ldap/LdapPlugin.php +++ b/plugins/Ldap/LdapPlugin.php @@ -102,4 +102,15 @@ class LdapPlugin extends Plugin //return false, indicating that the event has been handled return false; } + + function onCanUserChangeField($nickname, $field) + { + switch($field) + { + case 'password': + case 'nickname': + case 'email': + return false; + } + } } -- cgit v1.2.3-54-g00ecf From 1cd6650ae43d548f209d68e9feaaa7185d5ffecb Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Tue, 10 Nov 2009 16:27:20 -0500 Subject: Changed to Evan's event style and added an AuthPlugin superclass --- EVENTS.txt | 6 +- actions/passwordsettings.php | 23 ++----- plugins/Auth/AuthPlugin.php | 145 +++++++++++++++++++++++++++++++++++++++++++ plugins/Ldap/LdapPlugin.php | 114 +++++++++++++++++++++++++++------- plugins/Ldap/README | 52 +++++++++++----- plugins/Ldap/ldap.php | 108 -------------------------------- 6 files changed, 282 insertions(+), 166 deletions(-) create mode 100644 plugins/Auth/AuthPlugin.php delete mode 100644 plugins/Ldap/ldap.php (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index ced130f5f..97b7de299 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -491,11 +491,13 @@ EndCheckPassword: After checking a username/password pair - $password: The password that was checked - $authenticatedUser: User object if credentials match a user, else null. -ChangePassword: Handle a password change request +StartChangePassword: Before changing a password - $nickname: user's nickname - $oldpassword: the user's old password - $newpassword: the desired new password -- &$errormsg: set this to an error message if the password could not be changed. If the password was changed, leave this as false + +EndChangePassword: After changing a password +- $nickname: user's nickname CanUserChangeField: Determines if a user is allowed to change a specific profile field - $nickname: nickname of the user who would like to know which of their profile fields are mutable diff --git a/actions/passwordsettings.php b/actions/passwordsettings.php index 024f1287f..9e79501e2 100644 --- a/actions/passwordsettings.php +++ b/actions/passwordsettings.php @@ -58,19 +58,6 @@ class PasswordsettingsAction extends AccountSettingsAction return _('Change password'); } - function prepare($args){ - parent::prepare($args); - - $user = common_current_user(); - - Event::handle('CanUserChangeField', array($user->nickname, 'password')); - - if(! $fields['password']){ - //user is not allowed to change his password - $this->clientError(_('You are not allowed to change your password')); - } - } - /** * Instructions for use * @@ -182,8 +169,8 @@ class PasswordsettingsAction extends AccountSettingsAction $oldpassword = null; } - $errormsg = false; - if(! Event::handle('ChangePassword', array($user->nickname, $oldpassword, $newpassword, &$errormsg))){ + $success = false; + if(! Event::handle('StartChangePassword', array($user->nickname, $oldpassword, $newpassword))){ //no handler changed the password, so change the password internally $original = clone($user); @@ -199,11 +186,9 @@ class PasswordsettingsAction extends AccountSettingsAction $this->serverError(_('Can\'t save new password.')); return; } + Event::handle('EndChangePassword', array($nickname)); } - if($errormsg === false) - $this->showForm(_('Password saved.'), true); - else - $this->showForm($errormsg); + $this->showForm(_('Password saved.'), true); } } diff --git a/plugins/Auth/AuthPlugin.php b/plugins/Auth/AuthPlugin.php new file mode 100644 index 000000000..71e7ae4fb --- /dev/null +++ b/plugins/Auth/AuthPlugin.php @@ -0,0 +1,145 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 plugins that do authentication + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class AuthPlugin extends Plugin +{ + //is this plugin authoritative for authentication? + protected $authn_authoritative = false; + + //should accounts be automatically created after a successful login attempt? + protected $autoregistration = false; + + //------------Auth plugin should implement some (or all) of these methods------------\\ + /** + * Check if a nickname/password combination is valid + * @param nickname + * @param password + * @return boolean true if the credentials are valid, false if they are invalid. + */ + function checkPassword($nickname, $password) + { + return false; + } + + /** + * Automatically register a user when they attempt to login with valid credentials. + * User::register($data) is a very useful method for this implementation + * @param nickname + * @return boolean true if the user was created, false if autoregistration is not allowed, null if this plugin is not responsible for this nickname + */ + function autoRegister($nickname) + { + return null; + } + + /** + * Change a user's password + * The old password has been verified to be valid by this plugin before this call is made + * @param nickname + * @param oldpassword + * @param newpassword + * @return boolean true if the password was changed, false if password changing failed for some reason, null if this plugin is not responsible for this nickname + */ + function changePassword($nickname,$oldpassword,$newpassword) + { + return null; + } + + /** + * Can a user change this field in his own profile? + * @param nickname + * @param field + * @return boolean true if the field can be changed, false if not allowed to change it, null if this plugin is not responsible for this nickname + */ + function canUserChangeField($nickname, $field) + { + return null; + } + + //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ + function __construct() + { + parent::__construct(); + } + + function StartCheckPassword($nickname, $password, &$authenticatedUser){ + $authenticated = $this->checkPassword($nickname, $password); + if($authenticated){ + $authenticatedUser = User::staticGet('nickname', $nickname); + if(!$authenticatedUser && $this->autoregistration){ + if($this->autoregister($nickname)){ + $authenticatedUser = User::staticGet('nickname', $nickname); + } + } + return false; + }else{ + if($this->authn_authoritative){ + return false; + } + } + //we're not authoritative, so let other handlers try + } + + function onStartChangePassword($nickname,$oldpassword,$newpassword) + { + $authenticated = $this->checkPassword($nickname, $oldpassword); + if($authenticated){ + $result = $this->changePassword($nickname,$oldpassword,$newpassword); + if($result){ + //stop handling of other handlers, because what was requested was done + return false; + }else{ + throw new Exception(_('Password changing failed')); + } + }else{ + if($this->authn_authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing failed')); + }else{ + //let another handler try + return null; + } + } + + } +} + diff --git a/plugins/Ldap/LdapPlugin.php b/plugins/Ldap/LdapPlugin.php index 3795ffd7f..8a416bccc 100644 --- a/plugins/Ldap/LdapPlugin.php +++ b/plugins/Ldap/LdapPlugin.php @@ -31,38 +31,42 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/plugins/Ldap/ldap.php'; +require_once INSTALLDIR.'/plugins/Auth/AuthPlugin.php'; +require_once 'Net/LDAP2.php'; -class LdapPlugin extends Plugin +class LdapPlugin extends AuthPlugin { - private $config = array(); function __construct() { parent::__construct(); } + + //---interface implementation---// - function onCheckPassword($nickname, $password, &$authenticated) + function checkPassword($nickname, $password) { - if(ldap_check_password($nickname, $password)){ - $authenticated = true; - //stop handling of other events, because we have an answer + $ldap = $this->ldap_get_connection(); + if(!$ldap){ return false; } - if(common_config('ldap','authoritative')){ - //a false return stops handler processing + $entry = $this->ldap_get_user($nickname); + if(!$entry){ return false; + }else{ + $config = $this->ldap_get_config(); + $config['binddn']=$entry->dn(); + $config['bindpw']=$password; + if($this->ldap_get_connection($config)){ + return true; + }else{ + return false; + } } } - function onAutoRegister($nickname) + function autoRegister($nickname) { - $user = User::staticGet('nickname', $nickname); - if (! is_null($user) && $user !== false) { - common_log(LOG_WARNING, "An attempt was made to autoregister an existing user with nickname: $nickname"); - return; - } - $attributes=array(); $config_attributes = array('nickname','email','fullname','homepage','location'); foreach($config_attributes as $config_attribute){ @@ -71,7 +75,7 @@ class LdapPlugin extends Plugin array_push($attributes,$value); } } - $entry = ldap_get_user($nickname,$attributes); + $entry = $this->ldap_get_user($nickname,$attributes); if($entry){ $registration_data = array(); foreach($config_attributes as $config_attribute){ @@ -89,21 +93,22 @@ class LdapPlugin extends Plugin //set the database saved password to a random string. $registration_data['password']=common_good_rand(16); $user = User::register($registration_data); - //prevent other handlers from running, as we have registered the user - return false; + return true; + }else{ + //user isn't in ldap, so we cannot register him + return null; } } - function onChangePassword($nickname,$oldpassword,$newpassword,&$errormsg) + function changePassword($nickname,$oldpassword,$newpassword) { //TODO implement this - $errormsg = _('Sorry, changing LDAP passwords is not supported at this time'); + throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); - //return false, indicating that the event has been handled return false; } - function onCanUserChangeField($nickname, $field) + function canUserChangeField($nickname, $field) { switch($field) { @@ -113,4 +118,67 @@ class LdapPlugin extends Plugin return false; } } + + //---utility functions---// + function ldap_get_config(){ + $config = array(); + $keys = array('host','port','version','starttls','binddn','bindpw','basedn','options','filter','scope'); + foreach($keys as $key){ + $value = $this->$key; + if($value!==false){ + $config[$key]=$value; + } + } + return $config; + } + + function ldap_get_connection($config = null){ + if($config == null){ + $config = $this->ldap_get_config(); + } + + //cannot use Net_LDAP2::connect() as StatusNet uses + //PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); + //PEAR handling can be overridden on instance objects, so we do that. + $ldap = new Net_LDAP2($config); + $ldap->setErrorHandling(PEAR_ERROR_RETURN); + $err=$ldap->bind(); + if (Net_LDAP2::isError($err)) { + common_log(LOG_WARNING, 'Could not connect to LDAP server: '.$err->getMessage()); + return false; + } + return $ldap; + } + + /** + * get an LDAP entry for a user with a given username + * + * @param string $username + * $param array $attributes LDAP attributes to retrieve + * @return string DN + */ + function ldap_get_user($username,$attributes=array()){ + $ldap = $this->ldap_get_connection(); + $filter = Net_LDAP2_Filter::create(common_config('ldap','nickname_attribute'), 'equals', $username); + $options = array( + 'scope' => 'sub', + 'attributes' => $attributes + ); + $search = $ldap->search(null,$filter,$options); + + if (PEAR::isError($search)) { + common_log(LOG_WARNING, 'Error while getting DN for user: '.$search->getMessage()); + return false; + } + + if($search->count()==0){ + return false; + }else if($search->count()==1){ + $entry = $search->shiftEntry(); + return $entry; + }else{ + common_log(LOG_WARNING, 'Found ' . $search->count() . ' ldap user with the username: ' . $username); + return false; + } + } } diff --git a/plugins/Ldap/README b/plugins/Ldap/README index 617738e0b..1b6e3e75a 100644 --- a/plugins/Ldap/README +++ b/plugins/Ldap/README @@ -2,22 +2,46 @@ The LDAP plugin allows for StatusNet to handle authentication, authorization, an Installation ============ -Add configuration entries to config.php. These entries are: +add "addPlugin('ldap', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php -The following are documented at http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -$config['ldap']['binddn'] -$config['ldap']['bindpw'] -$config['ldap']['basedn'] -$config['ldap']['host'] -$config['ldap']['nickname_attribute'] Set this to the name of the ldap attribute that holds the username. For example, on Microsoft's Active Directory, this should be set to 'sAMAccountName' -$config['ldap']['nickname_email'] Set this to the name of the ldap attribute that holds the user's email address. For example, on Microsoft's Active Directory, this should be set to 'mail' -$config['ldap']['nickname_fullname'] Set this to the name of the ldap attribute that holds the user's full name. For example, on Microsoft's Active Directory, this should be set to 'displayName' -$config['ldap']['nickname_homepage'] Set this to the name of the ldap attribute that holds the the url of the user's home page. -$config['ldap']['nickname_location'] Set this to the name of the ldap attribute that holds the user's location. -$config['ldap']['authoritative'] Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database) -$config['ldap']['autoregister'] Set to true if users should be automatically created when they attempt to login +Settings +======== +authn_authoritative: Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). +autoregistration: Set to true if users should be automatically created when they attempt to login. -Finally, add "addPlugin('ldap');" to the bottom of your config.php +host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right.. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +port: Port on the server. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +version: LDAP version. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +starttls: TLS is started after connecting. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +binddn: The distinguished name to bind as (username). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +bindpw: Password for the binddn. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +basedn*: LDAP base name (root directory). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +options: See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +filter: Default search filter. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name + nickname* + email + fullname + homepage + location + +* required + +Example +======= +Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. + +addPlugin('ldap', array( + 'binddn'=>'username', + 'bindpw'=>'password', + 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', + 'host'=>array('server1', 'server2'), + 'attributes'=>array( + 'nickname'=>'sAMAccountName', + 'email'=>'mail', + 'fullname'=>'displayName') +)); diff --git a/plugins/Ldap/ldap.php b/plugins/Ldap/ldap.php deleted file mode 100644 index d92a058fb..000000000 --- a/plugins/Ldap/ldap.php +++ /dev/null @@ -1,108 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once 'Net/LDAP2.php'; - -function ldap_get_config(){ - static $config = null; - if($config == null){ - $config = array(); - $keys = array('host','port','version','starttls','binddn','bindpw','basedn','options','scope'); - foreach($keys as $key){ - $value = common_config('ldap', $key); - if($value!==false){ - $config[$key]=$value; - } - } - } - return $config; -} - -function ldap_get_connection($config = null){ - if($config == null){ - $config = ldap_get_config(); - } - - //cannot use Net_LDAP2::connect() as StatusNet uses - //PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); - //PEAR handling can be overridden on instance objects, so we do that. - $ldap = new Net_LDAP2($config); - $ldap->setErrorHandling(PEAR_ERROR_RETURN); - $err=$ldap->bind(); - if (Net_LDAP2::isError($err)) { - common_log(LOG_WARNING, 'Could not connect to LDAP server: '.$err->getMessage()); - return false; - } - return $ldap; -} - -function ldap_check_password($username, $password){ - $ldap = ldap_get_connection(); - if(!$ldap){ - return false; - } - $entry = ldap_get_user($username); - if(!$entry){ - return false; - }else{ - $config = ldap_get_config(); - $config['binddn']=$entry->dn(); - $config['bindpw']=$password; - if(ldap_get_connection($config)){ - return true; - }else{ - return false; - } - } -} - -/** - * get an LDAP entry for a user with a given username - * - * @param string $username - * $param array $attributes LDAP attributes to retrieve - * @return string DN - */ -function ldap_get_user($username,$attributes=array()){ - $ldap = ldap_get_connection(); - $filter = Net_LDAP2_Filter::create(common_config('ldap','nickname_attribute'), 'equals', $username); - $options = array( - 'scope' => 'sub', - 'attributes' => $attributes - ); - $search = $ldap->search(null,$filter,$options); - - if (PEAR::isError($search)) { - common_log(LOG_WARNING, 'Error while getting DN for user: '.$search->getMessage()); - return false; - } - - if($search->count()==0){ - return false; - }else if($search->count()==1){ - $entry = $search->shiftEntry(); - return $entry; - }else{ - common_log(LOG_WARNING, 'Found ' . $search->count() . ' ldap user with the username: ' . $username); - return false; - } -} - -- cgit v1.2.3-54-g00ecf From 53c86c43c4b8cba313335f5d70f7f77d4ab640d2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 3 Nov 2009 16:57:39 -0800 Subject: Bringing Sphinx search support up to code: broken out to a plugin, now supports multiple sites on a single server. Upgrade notes: * Index names have changed from hardcoded 'Identica_people' and 'Identica_notices' to use the database name and actual table names. Must reindex. New events: * GetSearchEngine to override default search engine class selection from plugins New scripts: * gen_config.php generates a sphinx.conf from database configuration (with theoretical support for status_network table, but it doesn't seem to be cleanly queriable right now without knowing the db setup info for that. Needs generalized support.) * Replaced old sphinx-indexer.sh and sphinx-cron.sh with index_update.php Other fixes: * sphinx.conf.sample better matches our live config, skipping unused stopword list and using a more realistic indexer memory limit Further notes: * Probably doesn't work right with PostgreSQL yet; Sphinx can pull from PG but the extraction queries currently look like they use some MySQL-specific functions. --- README | 31 ++----- actions/noticesearch.php | 2 +- actions/noticesearchrss.php | 2 +- actions/peoplesearch.php | 2 +- actions/twitapisearchatom.php | 2 +- actions/twitapisearchjson.php | 2 +- classes/Memcached_DataObject.php | 29 +++--- classes/Status_network.php | 28 ++++-- lib/default.php | 4 - lib/search_engines.php | 71 ++------------- plugins/SphinxSearch/README | 45 +++++++++ plugins/SphinxSearch/SphinxSearchPlugin.php | 100 ++++++++++++++++++++ plugins/SphinxSearch/scripts/gen_config.php | 126 ++++++++++++++++++++++++++ plugins/SphinxSearch/scripts/index_update.php | 61 +++++++++++++ plugins/SphinxSearch/scripts/sphinx-utils.php | 63 +++++++++++++ plugins/SphinxSearch/scripts/sphinx.sh | 15 +++ plugins/SphinxSearch/sphinx.conf.sample | 71 +++++++++++++++ plugins/SphinxSearch/sphinxsearch.php | 96 ++++++++++++++++++++ scripts/sphinx-cron.sh | 24 ----- scripts/sphinx-indexer.sh | 24 ----- scripts/sphinx.sh | 15 --- sphinx.conf.sample | 71 --------------- 22 files changed, 625 insertions(+), 259 deletions(-) create mode 100644 plugins/SphinxSearch/README create mode 100644 plugins/SphinxSearch/SphinxSearchPlugin.php create mode 100755 plugins/SphinxSearch/scripts/gen_config.php create mode 100755 plugins/SphinxSearch/scripts/index_update.php create mode 100644 plugins/SphinxSearch/scripts/sphinx-utils.php create mode 100755 plugins/SphinxSearch/scripts/sphinx.sh create mode 100644 plugins/SphinxSearch/sphinx.conf.sample create mode 100644 plugins/SphinxSearch/sphinxsearch.php delete mode 100755 scripts/sphinx-cron.sh delete mode 100755 scripts/sphinx-indexer.sh delete mode 100755 scripts/sphinx.sh delete mode 100644 sphinx.conf.sample (limited to 'plugins') diff --git a/README b/README index 7ecd025ac..fb78ab01d 100644 --- a/README +++ b/README @@ -389,20 +389,16 @@ the server first. Sphinx ------ -To use a Sphinx server to search users and notices, you also need -to install, compile and enable the sphinx pecl extension for php on the -client side, which itself depends on the sphinx development files. -"pecl install sphinx" should take care of that. Add "extension=sphinx.so" -to your php.ini and reload apache to enable it. +To use a Sphinx server to search users and notices, you'll need to +enable the SphinxSearch plugin. Add to your config.php: -You can update your MySQL or Postgresql databases to drop their fulltext -search indexes, since they're now provided by sphinx. + addPlugin('SphinxSearch'); + $config['sphinx']['server'] = 'searchhost.local'; -On the sphinx server side, a script reads the main database and build -the keyword index. A cron job reads the database and keeps the sphinx -indexes up to date. scripts/sphinx-cron.sh should be called by cron -every 5 minutes, for example. scripts/sphinx.sh is an init.d script -to start and stop the sphinx search daemon. +You also need to install, compile and enable the sphinx pecl extension for +php on the client side, which itself depends on the sphinx development files. + +See plugins/SphinxSearch/README for more details and server setup. SMS --- @@ -1168,17 +1164,6 @@ base: memcached uses key-value pairs to store data. We build long, StatusNet site using your memcached server. port: Port to connect to; defaults to 11211. -sphinx ------- - -You can get a significant boost in performance using Sphinx Search -instead of your database server to search for users and notices. -. - -enabled: Set to true to enable. Default false. -server: a string with the hostname of the sphinx server. -port: an integer with the port number of the sphinx server. - emailpost --------- diff --git a/actions/noticesearch.php b/actions/noticesearch.php index 79cf572cc..1e5a69180 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -104,7 +104,7 @@ class NoticesearchAction extends SearchAction { $notice = new Notice(); - $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine = $notice->getSearchEngine('notice'); $search_engine->set_sort_mode('chron'); // Ask for an extra to see if there's more. $search_engine->limit((($page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1); diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php index f59ad7962..18f07f855 100644 --- a/actions/noticesearchrss.php +++ b/actions/noticesearchrss.php @@ -62,7 +62,7 @@ class NoticesearchrssAction extends Rss10Action $notice = new Notice(); - $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine = $notice->getSearchEngine('notice'); $search_engine->set_sort_mode('chron'); if (!$limit) $limit = 20; diff --git a/actions/peoplesearch.php b/actions/peoplesearch.php index 38135ecbd..69de44859 100644 --- a/actions/peoplesearch.php +++ b/actions/peoplesearch.php @@ -61,7 +61,7 @@ class PeoplesearchAction extends SearchAction function showResults($q, $page) { $profile = new Profile(); - $search_engine = $profile->getSearchEngine('identica_people'); + $search_engine = $profile->getSearchEngine('profile'); $search_engine->set_sort_mode('chron'); // Ask for an extra to see if there's more. $search_engine->limit((($page-1)*PROFILES_PER_PAGE), PROFILES_PER_PAGE + 1); diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 7d618c471..526ca2ae8 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -161,7 +161,7 @@ class TwitapisearchatomAction extends ApiAction // lcase it for comparison $q = strtolower($this->query); - $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine = $notice->getSearchEngine('notice'); $search_engine->set_sort_mode('chron'); $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true); diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php index c7fa741a0..741ed78d6 100644 --- a/actions/twitapisearchjson.php +++ b/actions/twitapisearchjson.php @@ -121,7 +121,7 @@ class TwitapisearchjsonAction extends ApiAction // lcase it for comparison $q = strtolower($this->query); - $search_engine = $notice->getSearchEngine('identica_notices'); + $search_engine = $notice->getSearchEngine('notice'); $search_engine->set_sort_mode('chron'); $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true); if (false === $search_engine->query($q)) { diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 9c2ac3e01..753fe954e 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -184,27 +184,20 @@ class Memcached_DataObject extends DB_DataObject require_once INSTALLDIR.'/lib/search_engines.php'; static $search_engine; if (!isset($search_engine)) { - $connected = false; - if (common_config('sphinx', 'enabled')) { - $search_engine = new SphinxSearch($this, $table); - $connected = $search_engine->is_connected(); - } - - // unable to connect to sphinx' search daemon - if (!$connected) { - if ('mysql' === common_config('db', 'type')) { - $type = common_config('search', 'type'); - if ($type == 'like') { - $search_engine = new MySQLLikeSearch($this, $table); - } else if ($type == 'fulltext') { - $search_engine = new MySQLSearch($this, $table); - } else { - throw new ServerException('Unknown search type: ' . $type); - } + if (Event::handle('GetSearchEngine', array($this, $table, &$search_engine))) { + if ('mysql' === common_config('db', 'type')) { + $type = common_config('search', 'type'); + if ($type == 'like') { + $search_engine = new MySQLLikeSearch($this, $table); + } else if ($type == 'fulltext') { + $search_engine = new MySQLSearch($this, $table); } else { - $search_engine = new PGSearch($this, $table); + throw new ServerException('Unknown search type: ' . $type); } + } else { + $search_engine = new PGSearch($this, $table); } + } } return $search_engine; } diff --git a/classes/Status_network.php b/classes/Status_network.php index fe4f0b0c5..b3117640d 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -57,14 +57,16 @@ class Status_network extends DB_DataObject $config['db']['ini_'.$dbname] = INSTALLDIR.'/classes/status_network.ini'; $config['db']['table_status_network'] = $dbname; - self::$cache = new Memcache(); + if (class_exists('Memcache')) { + self::$cache = new Memcache(); - if (is_array($servers)) { - foreach($servers as $server) { - self::$cache->addServer($server); + if (is_array($servers)) { + foreach($servers as $server) { + self::$cache->addServer($server); + } + } else { + self::$cache->addServer($servers); } - } else { - self::$cache->addServer($servers); } self::$base = $dbname; @@ -76,6 +78,10 @@ class Status_network extends DB_DataObject static function memGet($k, $v) { + if (!self::$cache) { + return self::staticGet($k, $v); + } + $ck = self::cacheKey($k, $v); $sn = self::$cache->get($ck); @@ -92,10 +98,12 @@ class Status_network extends DB_DataObject function decache() { - $keys = array('nickname', 'hostname', 'pathname'); - foreach ($keys as $k) { - $ck = self::cacheKey($k, $this->$k); - self::$cache->delete($ck); + if (self::$cache) { + $keys = array('nickname', 'hostname', 'pathname'); + foreach ($keys as $k) { + $ck = self::cacheKey($k, $this->$k); + self::$cache->delete($ck); + } } } diff --git a/lib/default.php b/lib/default.php index f6cc4b725..95366e0b3 100644 --- a/lib/default.php +++ b/lib/default.php @@ -125,10 +125,6 @@ $default = 'public' => array()), # JIDs of users who want to receive the public stream 'invite' => array('enabled' => true), - 'sphinx' => - array('enabled' => false, - 'server' => 'localhost', - 'port' => 3312), 'tag' => array('dropoff' => 864000.0), 'popular' => diff --git a/lib/search_engines.php b/lib/search_engines.php index 69f6ff468..332db3f89 100644 --- a/lib/search_engines.php +++ b/lib/search_engines.php @@ -46,70 +46,11 @@ class SearchEngine } } -class SphinxSearch extends SearchEngine -{ - private $sphinx; - private $connected; - - function __construct($target, $table) - { - $fp = @fsockopen(common_config('sphinx', 'server'), common_config('sphinx', 'port')); - if (!$fp) { - $this->connected = false; - return; - } - fclose($fp); - parent::__construct($target, $table); - $this->sphinx = new SphinxClient; - $this->sphinx->setServer(common_config('sphinx', 'server'), common_config('sphinx', 'port')); - $this->connected = true; - } - - function is_connected() - { - return $this->connected; - } - - function limit($offset, $count, $rss = false) - { - //FIXME without LARGEST_POSSIBLE, the most recent results aren't returned - // this probably has a large impact on performance - $LARGEST_POSSIBLE = 1e6; - - if ($rss) { - $this->sphinx->setLimits($offset, $count, $count, $LARGEST_POSSIBLE); - } - else { - // return at most 50 pages of results - $this->sphinx->setLimits($offset, $count, 50 * ($count - 1), $LARGEST_POSSIBLE); - } - - return $this->target->limit(0, $count); - } - - function query($q) - { - $result = $this->sphinx->query($q, $this->table); - if (!isset($result['matches'])) return false; - $id_set = join(', ', array_keys($result['matches'])); - $this->target->whereAdd("id in ($id_set)"); - return true; - } - - function set_sort_mode($mode) - { - if ('chron' === $mode) { - $this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts'); - return $this->target->orderBy('created desc'); - } - } -} - class MySQLSearch extends SearchEngine { function query($q) { - if ('identica_people' === $this->table) { + if ('profile' === $this->table) { $this->target->whereAdd('MATCH(nickname, fullname, location, bio, homepage) ' . 'AGAINST (\''.addslashes($q).'\' IN BOOLEAN MODE)'); if (strtolower($q) != $q) { @@ -117,7 +58,7 @@ class MySQLSearch extends SearchEngine 'AGAINST (\''.addslashes(strtolower($q)).'\' IN BOOLEAN MODE)', 'OR'); } return true; - } else if ('identica_notices' === $this->table) { + } else if ('notice' === $this->table) { // Don't show imported notices $this->target->whereAdd('notice.is_local != ' . Notice::GATEWAY); @@ -143,13 +84,13 @@ class MySQLLikeSearch extends SearchEngine { function query($q) { - if ('identica_people' === $this->table) { + if ('profile' === $this->table) { $qry = sprintf('(nickname LIKE "%%%1$s%%" OR '. ' fullname LIKE "%%%1$s%%" OR '. ' location LIKE "%%%1$s%%" OR '. ' bio LIKE "%%%1$s%%" OR '. ' homepage LIKE "%%%1$s%%")', addslashes($q)); - } else if ('identica_notices' === $this->table) { + } else if ('notice' === $this->table) { $qry = sprintf('content LIKE "%%%1$s%%"', addslashes($q)); } else { throw new ServerException('Unknown table: ' . $this->table); @@ -165,9 +106,9 @@ class PGSearch extends SearchEngine { function query($q) { - if ('identica_people' === $this->table) { + if ('profile' === $this->table) { return $this->target->whereAdd('textsearch @@ plainto_tsquery(\''.addslashes($q).'\')'); - } else if ('identica_notices' === $this->table) { + } else if ('notice' === $this->table) { // XXX: We need to filter out gateway notices (notice.is_local = -2) --Zach diff --git a/plugins/SphinxSearch/README b/plugins/SphinxSearch/README new file mode 100644 index 000000000..5a2c063bd --- /dev/null +++ b/plugins/SphinxSearch/README @@ -0,0 +1,45 @@ +You can get a significant boost in performance using Sphinx Search +instead of your database server to search for users and notices. +. + +Configuration +------------- + +In StatusNet's configuration, you can adjust the following settings +under 'sphinx': + +enabled: Set to true to enable. Default false. +server: a string with the hostname of the sphinx server. +port: an integer with the port number of the sphinx server. + + +Requirements +------------ + +To use a Sphinx server to search users and notices, you also need +to install, compile and enable the sphinx pecl extension for php on the +client side, which itself depends on the sphinx development files. +"pecl install sphinx" should take care of that. Add "extension=sphinx.so" +to your php.ini and reload apache to enable it. + +You can update your MySQL or Postgresql databases to drop their fulltext +search indexes, since they're now provided by sphinx. + + +You will also need a Sphinx server to serve the search queries. + +On the sphinx server side, a script reads the main database and build +the keyword index. A cron job reads the database and keeps the sphinx +indexes up to date. scripts/sphinx-cron.sh should be called by cron +every 5 minutes, for example. scripts/sphinx.sh is an init.d script +to start and stop the sphinx search daemon. + + +Server configuration +-------------------- +scripts/gen_config.php can generate a sphinx.conf file listing MySQL +data sources for your databases. You may need to tweak paths afterwards. + + $ plugins/SphinxSearch/scripts/gen_config.php > sphinx.conf + +If you wish, you can build a full config yourself based on sphinx.conf.sample diff --git a/plugins/SphinxSearch/SphinxSearchPlugin.php b/plugins/SphinxSearch/SphinxSearchPlugin.php new file mode 100644 index 000000000..7a27a4c04 --- /dev/null +++ b/plugins/SphinxSearch/SphinxSearchPlugin.php @@ -0,0 +1,100 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Brion Vibber + * @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('STATUSNET')) { + exit(1); +} + +// Set defaults if not already set in the config array... +global $config; +$sphinxDefaults = + array('enabled' => true, + 'server' => 'localhost', + 'port' => 3312); +foreach($sphinxDefaults as $key => $val) { + if (!isset($config['sphinx'][$key])) { + $config['sphinx'][$key] = $val; + } +} + + + +/** + * Plugin for Sphinx search backend. + * + * @category Plugin + * @package StatusNet + * @author Brion Vibber + * @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 SphinxSearchPlugin extends Plugin +{ + /** + * Automatically load any classes used + * + * @param string $cls the class + * @return boolean hook return + */ + function onAutoload($cls) + { + switch ($cls) { + case 'SphinxSearch': + include_once INSTALLDIR . '/plugins/SphinxSearch/' . + strtolower($cls) . '.php'; + return false; + default: + return true; + } + } + + /** + * Create sphinx search engine object for the given table type. + * + * @param Memcached_DataObject $target + * @param string $table + * @param out &$search_engine SearchEngine object on output if successful + * @ return boolean hook return + */ + function onGetSearchEngine(Memcached_DataObject $target, $table, &$search_engine) + { + if (common_config('sphinx', 'enabled')) { + if (!class_exists('SphinxClient')) { + throw new ServerException('Sphinx PHP extension must be installed.'); + } + $engine = new SphinxSearch($target, $table); + if ($engine->is_connected()) { + $search_engine = $engine; + return false; + } + } + // Sphinx disabled or disconnected + return true; + } +} diff --git a/plugins/SphinxSearch/scripts/gen_config.php b/plugins/SphinxSearch/scripts/gen_config.php new file mode 100755 index 000000000..d5a00b6b6 --- /dev/null +++ b/plugins/SphinxSearch/scripts/gen_config.php @@ -0,0 +1,126 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('base=', 'network'); + +$helptext = <<sitename} +# +source {$sn->dbname}_src_{$table} +{ + type = {$dbtype} + sql_host = {$sn->dbhost} + sql_user = {$sn->dbuser} + sql_pass = {$sn->dbpass} + sql_db = {$sn->dbname} + sql_query_pre = SET NAMES utf8; + sql_query = {$query} + sql_query_info = {$query_info} + sql_attr_timestamp = created_ts +} + +index {$sn->dbname}_{$table} +{ + source = {$sn->dbname}_src_{$table} + path = {$base}/data/{$sn->dbname}_{$table} + docinfo = extern + charset_type = utf-8 + min_word_len = 3 +} + + +END; +} diff --git a/plugins/SphinxSearch/scripts/index_update.php b/plugins/SphinxSearch/scripts/index_update.php new file mode 100755 index 000000000..23c60ced7 --- /dev/null +++ b/plugins/SphinxSearch/scripts/index_update.php @@ -0,0 +1,61 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('base=', 'network'); + +$helptext = <<. + */ + +function sphinx_use_network() +{ + return have_option('network'); +} + +function sphinx_base() +{ + if (have_option('base')) { + return get_option_value('base'); + } else { + return "/usr/local/sphinx"; + } +} + +function sphinx_iterate_sites($callback) +{ + if (sphinx_use_network()) { + // @fixme this should use, like, some kind of config + Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet'); + $sn = new Status_network(); + if (!$sn->find()) { + die("Confused... no sites in status_network table or lookup failed.\n"); + } + while ($sn->fetch()) { + $callback($sn); + } + } else { + if (preg_match('!^(mysqli?|pgsql)://(.*?):(.*?)@(.*?)/(.*?)$!', + common_config('db', 'database'), $matches)) { + list(/*all*/, $dbtype, $dbuser, $dbpass, $dbhost, $dbname) = $matches; + $sn = (object)array( + 'sitename' => common_config('site', 'name'), + 'dbhost' => $dbhost, + 'dbuser' => $dbuser, + 'dbpass' => $dbpass, + 'dbname' => $dbname); + $callback($sn); + } else { + print "Unrecognized database configuration string in config.php\n"; + exit(1); + } + } +} + diff --git a/plugins/SphinxSearch/scripts/sphinx.sh b/plugins/SphinxSearch/scripts/sphinx.sh new file mode 100755 index 000000000..b8edeb302 --- /dev/null +++ b/plugins/SphinxSearch/scripts/sphinx.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [[ $1 = "start" ]] +then + echo "Stopping any running daemons..." + /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null + echo "Starting sphinx search daemon..." + /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf 2> /dev/null +fi + +if [[ $1 = "stop" ]] +then + echo "Stopping sphinx search daemon..." + /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null +fi diff --git a/plugins/SphinxSearch/sphinx.conf.sample b/plugins/SphinxSearch/sphinx.conf.sample new file mode 100644 index 000000000..3de62f637 --- /dev/null +++ b/plugins/SphinxSearch/sphinx.conf.sample @@ -0,0 +1,71 @@ +# +# Minimal Sphinx configuration sample for statusnet +# + +source src1 +{ + type = mysql + sql_host = localhost + sql_user = USERNAME + sql_pass = PASSWORD + sql_db = identi_ca + sql_port = 3306 + sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, nickname, fullname, location, bio, homepage FROM profile + sql_query_info = SELECT * FROM profile where id = $id + sql_attr_timestamp = created_ts +} + + +source src2 +{ + type = mysql + sql_host = localhost + sql_user = USERNAME + sql_pass = PASSWORD + sql_db = identi_ca + sql_port = 3306 + sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, content FROM notice + sql_query_info = SELECT * FROM notice where notice.id = $id AND notice.is_local != -2 + sql_attr_timestamp = created_ts +} + +index identica_notices +{ + source = src2 + path = DIRECTORY/data/identica_notices + docinfo = extern + charset_type = utf-8 + min_word_len = 3 + stopwords = DIRECTORY/data/stopwords-en.txt +} + + +index identica_people +{ + source = src1 + path = DIRECTORY/data/identica_people + docinfo = extern + charset_type = utf-8 + min_word_len = 3 + stopwords = DIRECTORY/data/stopwords-en.txt +} + +indexer +{ + mem_limit = 32M +} + +searchd +{ + port = 3312 + log = DIRECTORY/log/searchd.log + query_log = DIRECTORY/log/query.log + read_timeout = 5 + max_children = 30 + pid_file = DIRECTORY/log/searchd.pid + max_matches = 1000 + seamless_rotate = 1 + preopen_indexes = 0 + unlink_old = 1 +} + diff --git a/plugins/SphinxSearch/sphinxsearch.php b/plugins/SphinxSearch/sphinxsearch.php new file mode 100644 index 000000000..71f330828 --- /dev/null +++ b/plugins/SphinxSearch/sphinxsearch.php @@ -0,0 +1,96 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class SphinxSearch extends SearchEngine +{ + private $sphinx; + private $connected; + + function __construct($target, $table) + { + $fp = @fsockopen(common_config('sphinx', 'server'), common_config('sphinx', 'port')); + if (!$fp) { + $this->connected = false; + return; + } + fclose($fp); + parent::__construct($target, $table); + $this->sphinx = new SphinxClient; + $this->sphinx->setServer(common_config('sphinx', 'server'), common_config('sphinx', 'port')); + $this->connected = true; + } + + function is_connected() + { + return $this->connected; + } + + function limit($offset, $count, $rss = false) + { + //FIXME without LARGEST_POSSIBLE, the most recent results aren't returned + // this probably has a large impact on performance + $LARGEST_POSSIBLE = 1e6; + + if ($rss) { + $this->sphinx->setLimits($offset, $count, $count, $LARGEST_POSSIBLE); + } + else { + // return at most 50 pages of results + $this->sphinx->setLimits($offset, $count, 50 * ($count - 1), $LARGEST_POSSIBLE); + } + + return $this->target->limit(0, $count); + } + + function query($q) + { + $result = $this->sphinx->query($q, $this->remote_table()); + if (!isset($result['matches'])) return false; + $id_set = join(', ', array_keys($result['matches'])); + $this->target->whereAdd("id in ($id_set)"); + return true; + } + + function set_sort_mode($mode) + { + if ('chron' === $mode) { + $this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts'); + return $this->target->orderBy('created desc'); + } + } + + function remote_table() + { + return $this->dbname() . '_' . $this->table; + } + + function dbname() + { + // @fixme there should be a less dreadful way to do this. + // DB objects won't give database back until they connect, it's confusing + if (preg_match('!^.*?://.*?:.*?@.*?/(.*?)$!', common_config('db', 'database'), $matches)) { + return $matches[1]; + } + throw new ServerException("Sphinx search could not identify database name"); + } +} diff --git a/scripts/sphinx-cron.sh b/scripts/sphinx-cron.sh deleted file mode 100755 index bc537af1a..000000000 --- a/scripts/sphinx-cron.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -# StatusNet - a distributed open-source microblogging tool - -# Copyright (C) 2008, 2009, StatusNet, 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 . - -# This program tries to start the daemons for StatusNet. -# Note that the 'maildaemon' needs to run as a mail filter. - -/usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all --rotate - diff --git a/scripts/sphinx-indexer.sh b/scripts/sphinx-indexer.sh deleted file mode 100755 index 1ec0826be..000000000 --- a/scripts/sphinx-indexer.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -# StatusNet - a distributed open-source microblogging tool - -# Copyright (C) 2008, 2009, StatusNet, 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 . - -# This program tries to start the daemons for StatusNet. -# Note that the 'maildaemon' needs to run as a mail filter. - -/usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all - diff --git a/scripts/sphinx.sh b/scripts/sphinx.sh deleted file mode 100755 index b8edeb302..000000000 --- a/scripts/sphinx.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -if [[ $1 = "start" ]] -then - echo "Stopping any running daemons..." - /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null - echo "Starting sphinx search daemon..." - /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf 2> /dev/null -fi - -if [[ $1 = "stop" ]] -then - echo "Stopping sphinx search daemon..." - /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null -fi diff --git a/sphinx.conf.sample b/sphinx.conf.sample deleted file mode 100644 index 3de62f637..000000000 --- a/sphinx.conf.sample +++ /dev/null @@ -1,71 +0,0 @@ -# -# Minimal Sphinx configuration sample for statusnet -# - -source src1 -{ - type = mysql - sql_host = localhost - sql_user = USERNAME - sql_pass = PASSWORD - sql_db = identi_ca - sql_port = 3306 - sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, nickname, fullname, location, bio, homepage FROM profile - sql_query_info = SELECT * FROM profile where id = $id - sql_attr_timestamp = created_ts -} - - -source src2 -{ - type = mysql - sql_host = localhost - sql_user = USERNAME - sql_pass = PASSWORD - sql_db = identi_ca - sql_port = 3306 - sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, content FROM notice - sql_query_info = SELECT * FROM notice where notice.id = $id AND notice.is_local != -2 - sql_attr_timestamp = created_ts -} - -index identica_notices -{ - source = src2 - path = DIRECTORY/data/identica_notices - docinfo = extern - charset_type = utf-8 - min_word_len = 3 - stopwords = DIRECTORY/data/stopwords-en.txt -} - - -index identica_people -{ - source = src1 - path = DIRECTORY/data/identica_people - docinfo = extern - charset_type = utf-8 - min_word_len = 3 - stopwords = DIRECTORY/data/stopwords-en.txt -} - -indexer -{ - mem_limit = 32M -} - -searchd -{ - port = 3312 - log = DIRECTORY/log/searchd.log - query_log = DIRECTORY/log/query.log - read_timeout = 5 - max_children = 30 - pid_file = DIRECTORY/log/searchd.pid - max_matches = 1000 - seamless_rotate = 1 - preopen_indexes = 0 - unlink_old = 1 -} - -- cgit v1.2.3-54-g00ecf From 7ee6160a5b4a843a266a3455351ab2a5d59294a2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Nov 2009 17:08:33 -0500 Subject: initial support for geourl.org --- plugins/GeoURLPlugin.php | 119 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 plugins/GeoURLPlugin.php (limited to 'plugins') diff --git a/plugins/GeoURLPlugin.php b/plugins/GeoURLPlugin.php new file mode 100644 index 000000000..30ff2c278 --- /dev/null +++ b/plugins/GeoURLPlugin.php @@ -0,0 +1,119 @@ +. + * + * @category Action + * @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://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin to add ICBM metadata to HTML pages and report data to GeoURL.org + * + * Adds metadata to notice and profile pages that geourl.org and others + * understand. Also, pings geourl.org when a new notice is saved or + * a profile is changed. + * + * @category Plugin + * @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://status.net/ + * + * @seeAlso Location + */ + +class GeoURLPlugin extends Plugin +{ + public $ping = 'http://geourl.org/ping/'; + + /** + * Add extra headers for certain pages that geourl.org understands + * + * @param Action $action page being shown + * + * @return boolean event handler flag + */ + + function onEndShowHeadElements($action) + { + $name = $action->trimmed('action'); + + $location = null; + + if ($name == 'showstream') { + $profile = $action->profile; + if (!empty($profile) && !empty($profile->lat) && !empty($profile->lon)) { + $location = $profile->lat . ', ' . $profile->lon; + } + } else if ($name == 'shownotice') { + $notice = $action->profile; + if (!empty($notice) && !empty($notice->lat) && !empty($notice->lon)) { + $location = $notice->lat . ', ' . $notice->lon; + } + } + + if (!empty($location)) { + $action->element('meta', array('name' => 'ICBM', + 'content' => $location)); + $action->element('meta', array('name' => 'DC.title', + 'content' => $action->title())); + } + + return true; + } + + /** + * Report local notices to GeoURL.org when they're created + * + * @param Notice &$notice queued notice + * + * @return boolean event handler flag + */ + + function onHandleQueuedNotice(&$notice) + { + if ($notice->is_local == 1) { + + $request = HTTPClient::start(); + + $url = common_local_url('shownotice', + array('notice' => $notice->id)); + + try { + $request->post($this->ping, + null, + array('p' => $url)); + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_WARNING, + "GeoURL.org ping failed for '$url' ($this->ping)"); + } + } + + return true; + } +} -- cgit v1.2.3-54-g00ecf From 91332cdadc20e721c22fcf22ca1773cedbde95c5 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Tue, 10 Nov 2009 17:54:24 -0500 Subject: Added a events for the settings menu items --- EVENTS.txt | 40 ++++++++++++++++++++--- lib/accountsettingsaction.php | 57 ++++++++++++++++---------------- plugins/Auth/AuthPlugin.php | 75 +++++++++++++++++++++++++++++-------------- plugins/Ldap/LdapPlugin.php | 13 +++++++- plugins/Ldap/README | 11 ++++--- 5 files changed, 136 insertions(+), 60 deletions(-) (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index 97b7de299..f75dcebca 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -162,6 +162,42 @@ StartAccountSettingsNav: Before showing the account settings menu EndAccountSettingsNav: After showing the account settings menu - $action: the current action +StartAccountSettingsProfileMenuItem: Before showing the Profile menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsProfileMenuItem: After showing the Profile menu item +- $widget: AccountSettingsNav instance being shown + +StartAccountSettingsAvatarMenuItem: Before showing the Avatar menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsAvatarMenuItem: After showing the Avatar menu item +- $widget: AccountSettingsNav instance being shown + +StartAccountSettingsPasswordMenuItem: Before showing the Password menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsPasswordMenuItem: After showing the Password menu item +- $widget: AccountSettingsNav instance being shown + +StartAccountSettingsEmailMenuItem: Before showing the Email menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsEmailMenuItem: After showing the Email menu item +- $widget: AccountSettingsNav instance being shown + +StartAccountSettingsDesignMenuItem: Before showing the Design menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsDesignMenuItem: After showing the Design menu item +- $widget: AccountSettingsNav instance being shown + +StartAccountSettingsOtherMenuItem: Before showing the Other menu item +- $widget: AccountSettingsNav instance being shown + +EndAccountSettingsOtherMenuItem: After showing the Other menu item +- $widget: AccountSettingsNav instance being shown + Autoload: When trying to autoload a class - $cls: the class being sought. A plugin might require_once the file for the class. @@ -499,10 +535,6 @@ StartChangePassword: Before changing a password EndChangePassword: After changing a password - $nickname: user's nickname -CanUserChangeField: Determines if a user is allowed to change a specific profile field -- $nickname: nickname of the user who would like to know which of their profile fields are mutable -- $field: name of the field the user wants to change (nickname, fullname, password, avatar, etc) - UserDeleteRelated: Specify additional tables to delete entries from when deleting users - $user: User object - &$related: array of DB_DataObject class names to delete entries on matching user_id. diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php index 9865e1748..c79a1f5d7 100644 --- a/lib/accountsettingsaction.php +++ b/lib/accountsettingsaction.php @@ -104,35 +104,29 @@ class AccountSettingsNav extends Widget if (Event::handle('StartAccountSettingsNav', array(&$this->action))) { $user = common_current_user(); - $menu = array(); - $menu['profilesettings'] = - array(_('Profile'), - _('Change your profile settings')); - if(Event::handle('CanUserChangeField', array($user->nickname, 'avatar'))){ - $menu['avatarsettings'] = - array(_('Avatar'), - _('Upload an avatar')); + if(Event::handle('StartAccountSettingsProfileMenuItem', array($this, &$menu))){ + $this->showMenuItem('profilesettings',_('Profile'),_('Change your profile settings')); + Event::handle('EndAccountSettingsProfileMenuItem', array($this, &$menu)); } - if(Event::handle('CanUserChangeField', array($user->nickname, 'password'))){ - $menu['passwordsettings'] = - array(_('Password'), - _('Change your password')); + if(Event::handle('StartAccountSettingsAvatarMenuItem', array($this, &$menu))){ + $this->showMenuItem('avatarsettings',_('Avatar'),_('Upload an avatar')); + Event::handle('EndAccountSettingsAvatarMenuItem', array($this, &$menu)); } - $menu['emailsettings'] = - array(_('Email'), - _('Change email handling')); - $menu['userdesignsettings'] = - array(_('Design'), - _('Design your profile')); - $menu['othersettings'] = - array(_('Other'), - _('Other options')); - - foreach ($menu as $menuaction => $menudesc) { - $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); + if(Event::handle('StartAccountSettingsPasswordMenuItem', array($this, &$menu))){ + $this->showMenuItem('passwordsettings',_('Password'),_('Change your password')); + Event::handle('EndAccountSettingsPasswordMenuItem', array($this, &$menu)); + } + if(Event::handle('StartAccountSettingsEmailMenuItem', array($this, &$menu))){ + $this->showMenuItem('emailsettings',_('Email'),_('Change email handling')); + Event::handle('EndAccountSettingsEmailMenuItem', array($this, &$menu)); + } + if(Event::handle('StartAccountSettingsDesignMenuItem', array($this, &$menu))){ + $this->showMenuItem('userdesignsettings',_('Design'),_('Design your profile')); + Event::handle('EndAccountSettingsDesignMenuItem', array($this, &$menu)); + } + if(Event::handle('StartAccountSettingsOtherMenuItem', array($this, &$menu))){ + $this->showMenuItem('othersettings',_('Other'),_('Other options')); + Event::handle('EndAccountSettingsOtherMenuItem', array($this, &$menu)); } Event::handle('EndAccountSettingsNav', array(&$this->action)); @@ -140,4 +134,13 @@ class AccountSettingsNav extends Widget $this->action->elementEnd('ul'); } + + function showMenuItem($menuaction, $desc1, $desc2) + { + $action_name = $this->action->trimmed('action'); + $this->action->menuItem(common_local_url($menuaction), + $desc1, + $desc2, + $action_name === $menuaction); + } } diff --git a/plugins/Auth/AuthPlugin.php b/plugins/Auth/AuthPlugin.php index 71e7ae4fb..cb52730f6 100644 --- a/plugins/Auth/AuthPlugin.php +++ b/plugins/Auth/AuthPlugin.php @@ -43,11 +43,17 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { abstract class AuthPlugin extends Plugin { //is this plugin authoritative for authentication? - protected $authn_authoritative = false; + public $authn_authoritative = false; //should accounts be automatically created after a successful login attempt? - protected $autoregistration = false; - + public $autoregistration = false; + + //can the user change their email address + public $email_changeable=true; + + //can the user change their email address + public $password_changeable=true; + //------------Auth plugin should implement some (or all) of these methods------------\\ /** * Check if a nickname/password combination is valid @@ -102,44 +108,65 @@ abstract class AuthPlugin extends Plugin } function StartCheckPassword($nickname, $password, &$authenticatedUser){ - $authenticated = $this->checkPassword($nickname, $password); - if($authenticated){ - $authenticatedUser = User::staticGet('nickname', $nickname); - if(!$authenticatedUser && $this->autoregistration){ - if($this->autoregister($nickname)){ - $authenticatedUser = User::staticGet('nickname', $nickname); + if($this->password_changeable){ + $authenticated = $this->checkPassword($nickname, $password); + if($authenticated){ + $authenticatedUser = User::staticGet('nickname', $nickname); + if(!$authenticatedUser && $this->autoregistration){ + if($this->autoregister($nickname)){ + $authenticatedUser = User::staticGet('nickname', $nickname); + } + } + return false; + }else{ + if($this->authn_authoritative){ + return false; } } - return false; + //we're not authoritative, so let other handlers try }else{ if($this->authn_authoritative){ - return false; + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing is not allowed')); } } - //we're not authoritative, so let other handlers try } function onStartChangePassword($nickname,$oldpassword,$newpassword) { - $authenticated = $this->checkPassword($nickname, $oldpassword); - if($authenticated){ - $result = $this->changePassword($nickname,$oldpassword,$newpassword); - if($result){ - //stop handling of other handlers, because what was requested was done - return false; + if($this->password_changeable){ + $authenticated = $this->checkPassword($nickname, $oldpassword); + if($authenticated){ + $result = $this->changePassword($nickname,$oldpassword,$newpassword); + if($result){ + //stop handling of other handlers, because what was requested was done + return false; + }else{ + throw new Exception(_('Password changing failed')); + } }else{ - throw new Exception(_('Password changing failed')); + if($this->authn_authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing failed')); + }else{ + //let another handler try + return null; + } } }else{ if($this->authn_authoritative){ //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing failed')); - }else{ - //let another handler try - return null; + throw new Exception(_('Password changing is not allowed')); } } - + } + + function onStartAccountSettingsPasswordMenuItem($widget) + { + if($this->authn_authoritative && !$this->password_changeable){ + //since we're authoritative, no other plugin could change passwords, so do render the menu item + return false; + } } } diff --git a/plugins/Ldap/LdapPlugin.php b/plugins/Ldap/LdapPlugin.php index 8a416bccc..88ca92b37 100644 --- a/plugins/Ldap/LdapPlugin.php +++ b/plugins/Ldap/LdapPlugin.php @@ -36,6 +36,17 @@ require_once 'Net/LDAP2.php'; class LdapPlugin extends AuthPlugin { + public $host=null; + public $port=null; + public $version=null; + public $starttls=null; + public $binddn=null; + public $bindpw=null; + public $basedn=null; + public $options=null; + public $filter=null; + public $scope=null; + public $attributes=array(); function __construct() { @@ -125,7 +136,7 @@ class LdapPlugin extends AuthPlugin $keys = array('host','port','version','starttls','binddn','bindpw','basedn','options','filter','scope'); foreach($keys as $key){ $value = $this->$key; - if($value!==false){ + if($value!==null){ $config[$key]=$value; } } diff --git a/plugins/Ldap/README b/plugins/Ldap/README index 1b6e3e75a..063286cef 100644 --- a/plugins/Ldap/README +++ b/plugins/Ldap/README @@ -4,12 +4,12 @@ Installation ============ add "addPlugin('ldap', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php - - Settings ======== -authn_authoritative: Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). -autoregistration: Set to true if users should be automatically created when they attempt to login. +authn_authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). +autoregistration (false): Set to true if users should be automatically created when they attempt to login. +email_changeable (true): Are users allowed to change their email address? (true or false) +password_changeable (true): Are users allowed to change their passwords? (true or false) host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right.. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php port: Port on the server. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php @@ -30,12 +30,15 @@ attributes: an array with the key being the StatusNet user attribute name, and t location * required +default values are in (parenthesis) Example ======= Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. addPlugin('ldap', array( + 'authn_authoritative'=>true, + 'autoregistration'=>true, 'binddn'=>'username', 'bindpw'=>'password', 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', -- cgit v1.2.3-54-g00ecf From d6cd083946be96bcfc64329fc0bfdd28261c82ef Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Nov 2009 12:21:27 +0000 Subject: Using mark-top styles --- plugins/Realtime/realtimeupdate.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index ca6ea891a..a2af4007b 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -45,15 +45,9 @@ RealtimeUpdate = { DT = document.title; $(window).blur(function() { - $('#notices_primary .notice').css({ - 'border-top-color':$('#notices_primary .notice:last').css('border-top-color'), - 'border-top-style':'dotted' - }); - - $('#notices_primary .notice:first').css({ - 'border-top-color':'#AAAAAA', - 'border-top-style':'solid' - }); + $('#notices_primary .notice').removeClass('mark-top'); + + $('#notices_primary .notice:first').addClass('mark-top'); RealtimeUpdate._updatecounter = 0; document.title = DT; -- cgit v1.2.3-54-g00ecf From 3874269a0b3ba81a1813cc282356618bb02d0f20 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Nov 2009 12:31:14 +0000 Subject: Calling selector once --- plugins/Realtime/realtimeupdate.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index a2af4007b..ebe94ca41 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -157,10 +157,12 @@ RealtimeUpdate = { addPopup: function(url, timeline, iconurl) { - $('#notices_primary').css({'position':'relative'}); - $('#notices_primary').prepend(''); + var NP = $('#notices_primary'); + NP.css({'position':'relative'}); + NP.prepend(''); - $('#realtime_timeline').css({ + var RT = $('#realtime_timeline'); + RT.css({ 'margin':'0 0 11px 0', 'background':'transparent url('+ iconurl + ') no-repeat 0% 30%', 'padding':'0 0 0 20px', @@ -170,12 +172,12 @@ RealtimeUpdate = { 'right':'0', 'border':'none', 'cursor':'pointer', - 'color':$("a").css("color"), + 'color':$('a').css('color'), 'font-weight':'bold', 'font-size':'1em' }); - $('#realtime_timeline').click(function() { + RT.click(function() { window.open(url, timeline, 'toolbar=no,resizable=yes,scrollbars=yes,status=yes'); -- cgit v1.2.3-54-g00ecf From af4a54b1674ae118afcc88c03bab93d47208b6f8 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Nov 2009 12:34:22 +0000 Subject: Set window resize before openning it up --- plugins/Realtime/realtimeupdate.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index ebe94ca41..c7ba2c531 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -180,7 +180,7 @@ RealtimeUpdate = { RT.click(function() { window.open(url, timeline, - 'toolbar=no,resizable=yes,scrollbars=yes,status=yes'); + 'toolbar=no,resizable=yes,scrollbars=yes,status=yes,width=500,height=550'); return false; }); @@ -188,7 +188,6 @@ RealtimeUpdate = { initPopupWindow: function() { - window.resizeTo(500, 550); $('address').hide(); $('#content').css({'width':'93.5%'}); -- cgit v1.2.3-54-g00ecf From dbe02049a829dd0f3200984f1c797907aac5423f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Nov 2009 12:57:48 +0000 Subject: Added margin-top for showstream page (space between entity_actions and notice_primary) --- plugins/Realtime/realtimeupdate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index c7ba2c531..9b9991b9e 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -164,7 +164,7 @@ RealtimeUpdate = { var RT = $('#realtime_timeline'); RT.css({ 'margin':'0 0 11px 0', - 'background':'transparent url('+ iconurl + ') no-repeat 0% 30%', + 'background':'transparent url('+ iconurl + ') no-repeat 0 30%', 'padding':'0 0 0 20px', 'display':'block', 'position':'absolute', @@ -176,6 +176,7 @@ RealtimeUpdate = { 'font-weight':'bold', 'font-size':'1em' }); + $('#showstream #notices_primary').css({'margin-top':'18px'}); RT.click(function() { window.open(url, -- cgit v1.2.3-54-g00ecf From 686c2e5e064573a70e38e8df6d5fb60061858599 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 11 Nov 2009 13:43:21 +0000 Subject: Added XHR for form_entity_flag --- plugins/UserFlag/UserFlagPlugin.php | 8 ++++++++ plugins/UserFlag/flagprofile.php | 16 +++++++++++++++- plugins/UserFlag/flagprofileform.php | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index fe4a74869..6410ee1ce 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -140,4 +140,12 @@ class UserFlagPlugin extends Plugin return true; } + + function onEndShowScripts($action) + { + $action->elementStart('script', array('type' => 'text/javascript')); + $action->raw('/**/'); + $action->elementEnd('script'); + return true; + } } diff --git a/plugins/UserFlag/flagprofile.php b/plugins/UserFlag/flagprofile.php index c72b74c6a..77c86b233 100644 --- a/plugins/UserFlag/flagprofile.php +++ b/plugins/UserFlag/flagprofile.php @@ -108,7 +108,21 @@ class FlagprofileAction extends Action parent::handle($args); $this->flagProfile(); - $this->returnTo(); + + if ($this->boolean('ajax')) { + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + $this->element('title', null, _('Flagged for review')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->element('p', 'flagged', _('Flagged')); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->returnTo(); + } } function title() { diff --git a/plugins/UserFlag/flagprofileform.php b/plugins/UserFlag/flagprofileform.php index 0811dbb9d..a8396e2d5 100644 --- a/plugins/UserFlag/flagprofileform.php +++ b/plugins/UserFlag/flagprofileform.php @@ -94,7 +94,7 @@ class FlagProfileForm extends Form function formClass() { - return 'form_profile_flag'; + return 'form_entity_flag'; } /** -- cgit v1.2.3-54-g00ecf From f600fa3b1a3a4c81f6573d05dbcf286e2834a389 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 11 Nov 2009 12:16:58 -0500 Subject: Rename the plugins, as I'm separating out Authn, Authz, and user information into separate plugins --- plugins/Auth/AuthPlugin.php | 172 ------------------ plugins/Authentication/AuthenticationPlugin.php | 172 ++++++++++++++++++ plugins/Ldap/LdapPlugin.php | 195 --------------------- plugins/Ldap/README | 50 ------ .../LdapAuthenticationPlugin.php | 195 +++++++++++++++++++++ plugins/LdapAuthentication/README | 50 ++++++ 6 files changed, 417 insertions(+), 417 deletions(-) delete mode 100644 plugins/Auth/AuthPlugin.php create mode 100644 plugins/Authentication/AuthenticationPlugin.php delete mode 100644 plugins/Ldap/LdapPlugin.php delete mode 100644 plugins/Ldap/README create mode 100644 plugins/LdapAuthentication/LdapAuthenticationPlugin.php create mode 100644 plugins/LdapAuthentication/README (limited to 'plugins') diff --git a/plugins/Auth/AuthPlugin.php b/plugins/Auth/AuthPlugin.php deleted file mode 100644 index cb52730f6..000000000 --- a/plugins/Auth/AuthPlugin.php +++ /dev/null @@ -1,172 +0,0 @@ -. - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @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 plugins that do authentication - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -abstract class AuthPlugin extends Plugin -{ - //is this plugin authoritative for authentication? - public $authn_authoritative = false; - - //should accounts be automatically created after a successful login attempt? - public $autoregistration = false; - - //can the user change their email address - public $email_changeable=true; - - //can the user change their email address - public $password_changeable=true; - - //------------Auth plugin should implement some (or all) of these methods------------\\ - /** - * Check if a nickname/password combination is valid - * @param nickname - * @param password - * @return boolean true if the credentials are valid, false if they are invalid. - */ - function checkPassword($nickname, $password) - { - return false; - } - - /** - * Automatically register a user when they attempt to login with valid credentials. - * User::register($data) is a very useful method for this implementation - * @param nickname - * @return boolean true if the user was created, false if autoregistration is not allowed, null if this plugin is not responsible for this nickname - */ - function autoRegister($nickname) - { - return null; - } - - /** - * Change a user's password - * The old password has been verified to be valid by this plugin before this call is made - * @param nickname - * @param oldpassword - * @param newpassword - * @return boolean true if the password was changed, false if password changing failed for some reason, null if this plugin is not responsible for this nickname - */ - function changePassword($nickname,$oldpassword,$newpassword) - { - return null; - } - - /** - * Can a user change this field in his own profile? - * @param nickname - * @param field - * @return boolean true if the field can be changed, false if not allowed to change it, null if this plugin is not responsible for this nickname - */ - function canUserChangeField($nickname, $field) - { - return null; - } - - //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ - function __construct() - { - parent::__construct(); - } - - function StartCheckPassword($nickname, $password, &$authenticatedUser){ - if($this->password_changeable){ - $authenticated = $this->checkPassword($nickname, $password); - if($authenticated){ - $authenticatedUser = User::staticGet('nickname', $nickname); - if(!$authenticatedUser && $this->autoregistration){ - if($this->autoregister($nickname)){ - $authenticatedUser = User::staticGet('nickname', $nickname); - } - } - return false; - }else{ - if($this->authn_authoritative){ - return false; - } - } - //we're not authoritative, so let other handlers try - }else{ - if($this->authn_authoritative){ - //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing is not allowed')); - } - } - } - - function onStartChangePassword($nickname,$oldpassword,$newpassword) - { - if($this->password_changeable){ - $authenticated = $this->checkPassword($nickname, $oldpassword); - if($authenticated){ - $result = $this->changePassword($nickname,$oldpassword,$newpassword); - if($result){ - //stop handling of other handlers, because what was requested was done - return false; - }else{ - throw new Exception(_('Password changing failed')); - } - }else{ - if($this->authn_authoritative){ - //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing failed')); - }else{ - //let another handler try - return null; - } - } - }else{ - if($this->authn_authoritative){ - //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing is not allowed')); - } - } - } - - function onStartAccountSettingsPasswordMenuItem($widget) - { - if($this->authn_authoritative && !$this->password_changeable){ - //since we're authoritative, no other plugin could change passwords, so do render the menu item - return false; - } - } -} - diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php new file mode 100644 index 000000000..ef78c7ce4 --- /dev/null +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -0,0 +1,172 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 plugins that do authentication + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class AuthenticationPlugin extends Plugin +{ + //is this plugin authoritative for authentication? + public $authoritative = false; + + //should accounts be automatically created after a successful login attempt? + public $autoregistration = false; + + //can the user change their email address + public $email_changeable=true; + + //can the user change their email address + public $password_changeable=true; + + //------------Auth plugin should implement some (or all) of these methods------------\\ + /** + * Check if a nickname/password combination is valid + * @param nickname + * @param password + * @return boolean true if the credentials are valid, false if they are invalid. + */ + function checkPassword($nickname, $password) + { + return false; + } + + /** + * Automatically register a user when they attempt to login with valid credentials. + * User::register($data) is a very useful method for this implementation + * @param nickname + * @return boolean true if the user was created, false if autoregistration is not allowed, null if this plugin is not responsible for this nickname + */ + function autoRegister($nickname) + { + return null; + } + + /** + * Change a user's password + * The old password has been verified to be valid by this plugin before this call is made + * @param nickname + * @param oldpassword + * @param newpassword + * @return boolean true if the password was changed, false if password changing failed for some reason, null if this plugin is not responsible for this nickname + */ + function changePassword($nickname,$oldpassword,$newpassword) + { + return null; + } + + /** + * Can a user change this field in his own profile? + * @param nickname + * @param field + * @return boolean true if the field can be changed, false if not allowed to change it, null if this plugin is not responsible for this nickname + */ + function canUserChangeField($nickname, $field) + { + return null; + } + + //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ + function __construct() + { + parent::__construct(); + } + + function StartCheckPassword($nickname, $password, &$authenticatedUser){ + if($this->password_changeable){ + $authenticated = $this->checkPassword($nickname, $password); + if($authenticated){ + $authenticatedUser = User::staticGet('nickname', $nickname); + if(!$authenticatedUser && $this->autoregistration){ + if($this->autoregister($nickname)){ + $authenticatedUser = User::staticGet('nickname', $nickname); + } + } + return false; + }else{ + if($this->authoritative){ + return false; + } + } + //we're not authoritative, so let other handlers try + }else{ + if($this->authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing is not allowed')); + } + } + } + + function onStartChangePassword($nickname,$oldpassword,$newpassword) + { + if($this->password_changeable){ + $authenticated = $this->checkPassword($nickname, $oldpassword); + if($authenticated){ + $result = $this->changePassword($nickname,$oldpassword,$newpassword); + if($result){ + //stop handling of other handlers, because what was requested was done + return false; + }else{ + throw new Exception(_('Password changing failed')); + } + }else{ + if($this->authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing failed')); + }else{ + //let another handler try + return null; + } + } + }else{ + if($this->authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing is not allowed')); + } + } + } + + function onStartAccountSettingsPasswordMenuItem($widget) + { + if($this->authoritative && !$this->password_changeable){ + //since we're authoritative, no other plugin could change passwords, so do render the menu item + return false; + } + } +} + diff --git a/plugins/Ldap/LdapPlugin.php b/plugins/Ldap/LdapPlugin.php deleted file mode 100644 index 88ca92b37..000000000 --- a/plugins/Ldap/LdapPlugin.php +++ /dev/null @@ -1,195 +0,0 @@ -. - * - * @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') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/plugins/Auth/AuthPlugin.php'; -require_once 'Net/LDAP2.php'; - -class LdapPlugin extends AuthPlugin -{ - public $host=null; - public $port=null; - public $version=null; - public $starttls=null; - public $binddn=null; - public $bindpw=null; - public $basedn=null; - public $options=null; - public $filter=null; - public $scope=null; - public $attributes=array(); - - function __construct() - { - parent::__construct(); - } - - //---interface implementation---// - - function checkPassword($nickname, $password) - { - $ldap = $this->ldap_get_connection(); - if(!$ldap){ - return false; - } - $entry = $this->ldap_get_user($nickname); - if(!$entry){ - return false; - }else{ - $config = $this->ldap_get_config(); - $config['binddn']=$entry->dn(); - $config['bindpw']=$password; - if($this->ldap_get_connection($config)){ - return true; - }else{ - return false; - } - } - } - - function autoRegister($nickname) - { - $attributes=array(); - $config_attributes = array('nickname','email','fullname','homepage','location'); - foreach($config_attributes as $config_attribute){ - $value = common_config('ldap', $config_attribute.'_attribute'); - if($value!==false){ - array_push($attributes,$value); - } - } - $entry = $this->ldap_get_user($nickname,$attributes); - if($entry){ - $registration_data = array(); - foreach($config_attributes as $config_attribute){ - $value = common_config('ldap', $config_attribute.'_attribute'); - if($value!==false){ - if($config_attribute=='email'){ - $registration_data[$config_attribute]=common_canonical_email($entry->getValue($value,'single')); - }else if($config_attribute=='nickname'){ - $registration_data[$config_attribute]=common_canonical_nickname($entry->getValue($value,'single')); - }else{ - $registration_data[$config_attribute]=$entry->getValue($value,'single'); - } - } - } - //set the database saved password to a random string. - $registration_data['password']=common_good_rand(16); - $user = User::register($registration_data); - return true; - }else{ - //user isn't in ldap, so we cannot register him - return null; - } - } - - function changePassword($nickname,$oldpassword,$newpassword) - { - //TODO implement this - throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); - - return false; - } - - function canUserChangeField($nickname, $field) - { - switch($field) - { - case 'password': - case 'nickname': - case 'email': - return false; - } - } - - //---utility functions---// - function ldap_get_config(){ - $config = array(); - $keys = array('host','port','version','starttls','binddn','bindpw','basedn','options','filter','scope'); - foreach($keys as $key){ - $value = $this->$key; - if($value!==null){ - $config[$key]=$value; - } - } - return $config; - } - - function ldap_get_connection($config = null){ - if($config == null){ - $config = $this->ldap_get_config(); - } - - //cannot use Net_LDAP2::connect() as StatusNet uses - //PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); - //PEAR handling can be overridden on instance objects, so we do that. - $ldap = new Net_LDAP2($config); - $ldap->setErrorHandling(PEAR_ERROR_RETURN); - $err=$ldap->bind(); - if (Net_LDAP2::isError($err)) { - common_log(LOG_WARNING, 'Could not connect to LDAP server: '.$err->getMessage()); - return false; - } - return $ldap; - } - - /** - * get an LDAP entry for a user with a given username - * - * @param string $username - * $param array $attributes LDAP attributes to retrieve - * @return string DN - */ - function ldap_get_user($username,$attributes=array()){ - $ldap = $this->ldap_get_connection(); - $filter = Net_LDAP2_Filter::create(common_config('ldap','nickname_attribute'), 'equals', $username); - $options = array( - 'scope' => 'sub', - 'attributes' => $attributes - ); - $search = $ldap->search(null,$filter,$options); - - if (PEAR::isError($search)) { - common_log(LOG_WARNING, 'Error while getting DN for user: '.$search->getMessage()); - return false; - } - - if($search->count()==0){ - return false; - }else if($search->count()==1){ - $entry = $search->shiftEntry(); - return $entry; - }else{ - common_log(LOG_WARNING, 'Found ' . $search->count() . ' ldap user with the username: ' . $username); - return false; - } - } -} diff --git a/plugins/Ldap/README b/plugins/Ldap/README deleted file mode 100644 index 063286cef..000000000 --- a/plugins/Ldap/README +++ /dev/null @@ -1,50 +0,0 @@ -The LDAP plugin allows for StatusNet to handle authentication, authorization, and user information through LDAP. - -Installation -============ -add "addPlugin('ldap', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php - -Settings -======== -authn_authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). -autoregistration (false): Set to true if users should be automatically created when they attempt to login. -email_changeable (true): Are users allowed to change their email address? (true or false) -password_changeable (true): Are users allowed to change their passwords? (true or false) - -host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right.. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -port: Port on the server. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -version: LDAP version. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -starttls: TLS is started after connecting. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -binddn: The distinguished name to bind as (username). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -bindpw: Password for the binddn. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -basedn*: LDAP base name (root directory). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -options: See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -filter: Default search filter. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php - -attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name - nickname* - email - fullname - homepage - location - -* required -default values are in (parenthesis) - -Example -======= -Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. - -addPlugin('ldap', array( - 'authn_authoritative'=>true, - 'autoregistration'=>true, - 'binddn'=>'username', - 'bindpw'=>'password', - 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', - 'host'=>array('server1', 'server2'), - 'attributes'=>array( - 'nickname'=>'sAMAccountName', - 'email'=>'mail', - 'fullname'=>'displayName') -)); diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php new file mode 100644 index 000000000..f14080b2d --- /dev/null +++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php @@ -0,0 +1,195 @@ +. + * + * @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') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php'; +require_once 'Net/LDAP2.php'; + +class LdapAuthenticatonPlugin extends AuthenticationPlugin +{ + public $host=null; + public $port=null; + public $version=null; + public $starttls=null; + public $binddn=null; + public $bindpw=null; + public $basedn=null; + public $options=null; + public $filter=null; + public $scope=null; + public $attributes=array(); + + function __construct() + { + parent::__construct(); + } + + //---interface implementation---// + + function checkPassword($nickname, $password) + { + $ldap = $this->ldap_get_connection(); + if(!$ldap){ + return false; + } + $entry = $this->ldap_get_user($nickname); + if(!$entry){ + return false; + }else{ + $config = $this->ldap_get_config(); + $config['binddn']=$entry->dn(); + $config['bindpw']=$password; + if($this->ldap_get_connection($config)){ + return true; + }else{ + return false; + } + } + } + + function autoRegister($nickname) + { + $attributes=array(); + $config_attributes = array('nickname','email','fullname','homepage','location'); + foreach($config_attributes as $config_attribute){ + $value = common_config('ldap', $config_attribute.'_attribute'); + if($value!==false){ + array_push($attributes,$value); + } + } + $entry = $this->ldap_get_user($nickname,$attributes); + if($entry){ + $registration_data = array(); + foreach($config_attributes as $config_attribute){ + $value = common_config('ldap', $config_attribute.'_attribute'); + if($value!==false){ + if($config_attribute=='email'){ + $registration_data[$config_attribute]=common_canonical_email($entry->getValue($value,'single')); + }else if($config_attribute=='nickname'){ + $registration_data[$config_attribute]=common_canonical_nickname($entry->getValue($value,'single')); + }else{ + $registration_data[$config_attribute]=$entry->getValue($value,'single'); + } + } + } + //set the database saved password to a random string. + $registration_data['password']=common_good_rand(16); + $user = User::register($registration_data); + return true; + }else{ + //user isn't in ldap, so we cannot register him + return null; + } + } + + function changePassword($nickname,$oldpassword,$newpassword) + { + //TODO implement this + throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); + + return false; + } + + function canUserChangeField($nickname, $field) + { + switch($field) + { + case 'password': + case 'nickname': + case 'email': + return false; + } + } + + //---utility functions---// + function ldap_get_config(){ + $config = array(); + $keys = array('host','port','version','starttls','binddn','bindpw','basedn','options','filter','scope'); + foreach($keys as $key){ + $value = $this->$key; + if($value!==null){ + $config[$key]=$value; + } + } + return $config; + } + + function ldap_get_connection($config = null){ + if($config == null){ + $config = $this->ldap_get_config(); + } + + //cannot use Net_LDAP2::connect() as StatusNet uses + //PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); + //PEAR handling can be overridden on instance objects, so we do that. + $ldap = new Net_LDAP2($config); + $ldap->setErrorHandling(PEAR_ERROR_RETURN); + $err=$ldap->bind(); + if (Net_LDAP2::isError($err)) { + common_log(LOG_WARNING, 'Could not connect to LDAP server: '.$err->getMessage()); + return false; + } + return $ldap; + } + + /** + * get an LDAP entry for a user with a given username + * + * @param string $username + * $param array $attributes LDAP attributes to retrieve + * @return string DN + */ + function ldap_get_user($username,$attributes=array()){ + $ldap = $this->ldap_get_connection(); + $filter = Net_LDAP2_Filter::create(common_config('ldap','nickname_attribute'), 'equals', $username); + $options = array( + 'scope' => 'sub', + 'attributes' => $attributes + ); + $search = $ldap->search(null,$filter,$options); + + if (PEAR::isError($search)) { + common_log(LOG_WARNING, 'Error while getting DN for user: '.$search->getMessage()); + return false; + } + + if($search->count()==0){ + return false; + }else if($search->count()==1){ + $entry = $search->shiftEntry(); + return $entry; + }else{ + common_log(LOG_WARNING, 'Found ' . $search->count() . ' ldap user with the username: ' . $username); + return false; + } + } +} diff --git a/plugins/LdapAuthentication/README b/plugins/LdapAuthentication/README new file mode 100644 index 000000000..03647e7c7 --- /dev/null +++ b/plugins/LdapAuthentication/README @@ -0,0 +1,50 @@ +The LDAP Authentication plugin allows for StatusNet to handle authentication through LDAP. + +Installation +============ +add "addPlugin('ldapAuthentication', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php + +Settings +======== +authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). +autoregistration (false): Set to true if users should be automatically created when they attempt to login. +email_changeable (true): Are users allowed to change their email address? (true or false) +password_changeable (true): Are users allowed to change their passwords? (true or false) + +host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right.. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +port: Port on the server. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +version: LDAP version. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +starttls: TLS is started after connecting. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +binddn: The distinguished name to bind as (username). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +bindpw: Password for the binddn. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +basedn*: LDAP base name (root directory). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +options: See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +filter: Default search filter. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php + +attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name + nickname* + email + fullname + homepage + location + +* required +default values are in (parenthesis) + +Example +======= +Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. + +addPlugin('ldapAuthentication', array( + 'authoritative'=>true, + 'autoregistration'=>true, + 'binddn'=>'username', + 'bindpw'=>'password', + 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', + 'host'=>array('server1', 'server2'), + 'attributes'=>array( + 'nickname'=>'sAMAccountName', + 'email'=>'mail', + 'fullname'=>'displayName') +)); -- cgit v1.2.3-54-g00ecf From 014d6b1d19b6ae5de8d87f055397993f80579f74 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 11 Nov 2009 14:02:57 -0500 Subject: Redid how URL shorteners work. This way is much more like how Evan wants events to work (and more like how the rest of SN works). --- EVENTS.txt | 14 ++++ actions/othersettings.php | 17 ++--- lib/Shorturl_api.php | 67 ------------------ lib/common.php | 1 - lib/util.php | 25 +++---- plugins/BitlyUrl/BitlyUrlPlugin.php | 31 ++++----- plugins/LilUrl/LilUrlPlugin.php | 38 +++++----- plugins/PtitUrl/PtitUrlPlugin.php | 31 ++++----- plugins/SimpleUrl/SimpleUrlPlugin.php | 41 +++-------- plugins/TightUrl/TightUrlPlugin.php | 31 ++++----- plugins/UrlShortener/UrlShortenerPlugin.php | 103 ++++++++++++++++++++++++++++ 11 files changed, 197 insertions(+), 202 deletions(-) delete mode 100644 lib/Shorturl_api.php create mode 100644 plugins/UrlShortener/UrlShortenerPlugin.php (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index f75dcebca..3acff277b 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -538,3 +538,17 @@ EndChangePassword: After changing a password UserDeleteRelated: Specify additional tables to delete entries from when deleting users - $user: User object - &$related: array of DB_DataObject class names to delete entries on matching user_id. + +GetUrlShorteners: Specify URL shorteners that are available for use +- &$shorteners: append your shortener to this array like so: $shorteners[shortenerName]=array('display'=>display, 'freeService'=>boolean) + +StartShortenUrl: About to shorten a URL +- $url: url to be shortened +- $shortenerName: name of the requested shortener +- &$shortenedUrl: short version of the url + +EndShortenUrl: After a URL has been shortened +- $url: url to be shortened +- $shortenerName: name of the requested shortener +- $shortenedUrl: short version of the url + diff --git a/actions/othersettings.php b/actions/othersettings.php index d32a2d651..d52a634ac 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -97,20 +97,15 @@ class OthersettingsAction extends AccountSettingsAction $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); - $services=array(); - global $_shorteners; - if($_shorteners){ - foreach($_shorteners as $name=>$value) - { - $services[$name]=$name; - if(!empty($value['info']['freeService'])){ - // I18N - $services[$name].=' (free service)'; - } + Event::handle('GetUrlShorteners', array(&$shorteners)); + foreach($shorteners as $name=>$value) + { + $services[$name]=$name; + if($value['freeService']){ + $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 deleted file mode 100644 index de4d55012..000000000 --- a/lib/Shorturl_api.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -abstract class ShortUrlApi -{ - protected $service_url; - protected $long_limit = 27; - - function __construct($service_url) - { - $this->service_url = $service_url; - } - - function shorten($url) - { - if ($this->is_long($url)) return $this->shorten_imp($url); - return $url; - } - - protected abstract function shorten_imp($url); - - protected function is_long($url) { - return strlen($url) >= common_config('site', 'shorturllength'); - } - - protected function http_post($data) - { - $request = HTTPClient::start(); - $response = $request->post($this->service_url, null, $data); - return $response->getBody(); - } - - protected function http_get($url) - { - $request = HTTPClient::start(); - $response = $request->get($this->service_url . urlencode($url)); - return $response->getBody(); - } - - protected function tidy($response) { - $response = str_replace(' ', ' ', $response); - $config = array('output-xhtml' => true); - $tidy = new tidy; - $tidy->parseString($response, $config, 'utf8'); - $tidy->cleanRepair(); - return (string)$tidy; - } -} - diff --git a/lib/common.php b/lib/common.php index 6aac46807..4958866d0 100644 --- a/lib/common.php +++ b/lib/common.php @@ -229,7 +229,6 @@ require_once INSTALLDIR.'/lib/util.php'; require_once INSTALLDIR.'/lib/action.php'; require_once INSTALLDIR.'/lib/mail.php'; require_once INSTALLDIR.'/lib/subs.php'; -require_once INSTALLDIR.'/lib/Shorturl_api.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; diff --git a/lib/util.php b/lib/util.php index 7aca4af8d..68f3520db 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1423,25 +1423,18 @@ function common_shorten_url($long_url) if (empty($user)) { // common current user does not find a user when called from the XMPP daemon // therefore we'll set one here fix, so that XMPP given URLs may be shortened - $svc = 'ur1.ca'; + $shortenerName = 'ur1.ca'; } else { - $svc = $user->urlshorteningservice; + $shortenerName = $user->urlshorteningservice; } - global $_shorteners; - if (!isset($_shorteners[$svc])) { - //the user selected service doesn't exist, so default to ur1.ca - $svc = 'ur1.ca'; - } - if (!isset($_shorteners[$svc])) { - // no shortener plugins installed. - return $long_url; - } - - $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; + if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){ + //URL wasn't shortened, so return the long url + return $long_url; + }else{ + //URL was shortened, so return the result + return $shortenedUrl; + } } function common_client_ip() diff --git a/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php index 478ef99d2..65d0f70e6 100644 --- a/plugins/BitlyUrl/BitlyUrlPlugin.php +++ b/plugins/BitlyUrl/BitlyUrlPlugin.php @@ -31,31 +31,24 @@ if (!defined('STATUSNET')) { exit(1); } -class BitlyUrlPlugin extends Plugin +require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; + +class BitlyUrlPlugin extends UrlShortenerPlugin { - function __construct() - { - parent::__construct(); - } + public $serviceUrl; function onInitializePlugin(){ - $this->registerUrlShortener( - 'bit.ly', - array(), - array('BitlyUrl',array('http://bit.ly/api?method=shorten&long_url=')) - ); + parent::onInitializePlugin(); + if(!isset($this->serviceUrl)){ + throw new Exception("must specify a serviceUrl"); + } } -} -class BitlyUrl extends ShortUrlApi -{ - protected function shorten_imp($url) { + protected function shorten($url) { $response = $this->http_get($url); - if(!$response){ - return $url; - }else{ - return current(json_decode($response)->results)->hashUrl; - } + if(!$response) return; + return current(json_decode($response)->results)->hashUrl; } } + diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index 852253b02..e906751e8 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -31,37 +31,31 @@ if (!defined('STATUSNET')) { exit(1); } -require_once(INSTALLDIR.'/lib/Shorturl_api.php'); +require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; -class LilUrlPlugin extends Plugin +class LilUrlPlugin extends UrlShortenerPlugin { - function __construct() - { - parent::__construct(); - } + public $serviceUrl; function onInitializePlugin(){ - $this->registerUrlShortener( - 'ur1.ca', - array('freeService'=>true), - array('LilUrl',array('http://ur1.ca/')) - ); + parent::onInitializePlugin(); + if(!isset($this->serviceUrl)){ + throw new Exception("must specify a serviceUrl"); + } } -} -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; + protected function shorten($url) { + $data = array('longurl' => $url); + + $responseBody = $this->http_post($this->serviceUrl,$data); + + if (!$responseBody) return; + $y = @simplexml_load_string($responseBody); + if (!isset($y->body)) return; $x = $y->body->p[0]->a->attributes(); if (isset($x['href'])) { - common_log(LOG_INFO, __CLASS__ . ": shortened $url to $x[href]"); return $x['href']; } - return $url; } } + diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php index f00d3e2f2..ef453e96d 100644 --- a/plugins/PtitUrl/PtitUrlPlugin.php +++ b/plugins/PtitUrl/PtitUrlPlugin.php @@ -30,33 +30,28 @@ if (!defined('STATUSNET')) { exit(1); } +require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; -class PtitUrlPlugin extends Plugin +class PtitUrlPlugin extends UrlShortenerPlugin { - function __construct() - { - parent::__construct(); - } + public $serviceUrl; function onInitializePlugin(){ - $this->registerUrlShortener( - 'ptiturl.com', - array(), - array('PtitUrl',array('http://ptiturl.com/?creer=oui&action=Reduire&url=')) - ); + parent::onInitializePlugin(); + if(!isset($this->serviceUrl)){ + throw new Exception("must specify a serviceUrl"); + } } -} -class PtitUrl extends ShortUrlApi -{ - protected function shorten_imp($url) { - $response = $this->http_get($url); - if (!$response) return $url; + protected function shorten($url) + { + $response = $this->http_get(sprintf($this->serviceUrl,urlencode($url))); + if (!$response) return; $response = $this->tidy($response); $y = @simplexml_load_string($response); - if (!isset($y->body)) return $url; + if (!isset($y->body)) return; $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 index d59d63e47..45b745b07 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -31,40 +31,21 @@ if (!defined('STATUSNET')) { exit(1); } -class SimpleUrlPlugin extends Plugin +require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; + +class SimpleUrlPlugin extends UrlShortenerPlugin { - function __construct() - { - parent::__construct(); - } + public $serviceUrl; 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=')) - ); + parent::onInitializePlugin(); + if(!isset($this->serviceUrl)){ + throw new Exception("must specify a serviceUrl"); + } } -} -class SimpleUrl extends ShortUrlApi -{ - protected function shorten_imp($url) { - return $this->http_get($url); + protected function shorten($url) { + return $this->http_get(sprintf($this->serviceUrl,urlencode($url))); } } + diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php index 48efb355f..56414c8c8 100644 --- a/plugins/TightUrl/TightUrlPlugin.php +++ b/plugins/TightUrl/TightUrlPlugin.php @@ -31,32 +31,27 @@ if (!defined('STATUSNET')) { exit(1); } -class TightUrlPlugin extends Plugin +require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; + +class TightUrlPlugin extends UrlShortenerPlugin { - function __construct() - { - parent::__construct(); - } + public $serviceUrl; function onInitializePlugin(){ - $this->registerUrlShortener( - '2tu.us', - array('freeService'=>true), - array('TightUrl',array('http://2tu.us/?save=y&url=')) - ); + parent::onInitializePlugin(); + if(!isset($this->serviceUrl)){ + throw new Exception("must specify a serviceUrl"); + } } -} -class TightUrl extends ShortUrlApi -{ - protected function shorten_imp($url) { - $response = $this->http_get($url); - if (!$response) return $url; + protected function shorten($url) + { + $response = $this->http_get(sprintf($this->serviceUrl,urlencode($url))); + if (!$response) return; $response = $this->tidy($response); $y = @simplexml_load_string($response); - if (!isset($y->body)) return $url; + if (!isset($y->body)) return; $xml = $y->body->p[0]->code[0]->a->attributes(); if (isset($xml['href'])) return $xml['href']; - return $url; } } diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/plugins/UrlShortener/UrlShortenerPlugin.php new file mode 100644 index 000000000..37206aa89 --- /dev/null +++ b/plugins/UrlShortener/UrlShortenerPlugin.php @@ -0,0 +1,103 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 plugins that do URL shortening + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class UrlShortenerPlugin extends Plugin +{ + public $shortenerName; + public $freeService=false; + //------------Url Shortener plugin should implement some (or all) of these methods------------\\ + + /** + * Short a URL + * @param url + * @return string shortened version of the url, or null if URL shortening failed + */ + protected abstract function shorten($url); + + //------------These methods may help you implement your plugin------------\\ + protected function http_get($url) + { + $request = HTTPClient::start(); + $response = $request->get($url); + return $response->getBody(); + } + + protected function http_post($url,$data) + { + $request = HTTPClient::start(); + $response = $request->post($url, null, $data); + return $response->getBody(); + } + + protected function tidy($response) { + $response = str_replace(' ', ' ', $response); + $config = array('output-xhtml' => true); + $tidy = new tidy; + $tidy->parseString($response, $config, 'utf8'); + $tidy->cleanRepair(); + return (string)$tidy; + } + //------------Below are the methods that connect StatusNet to the implementing Url Shortener plugin------------\\ + + function onInitializePlugin(){ + if(!isset($this->shortenerName)){ + throw new Exception("must specify a shortenerName"); + } + } + + function onGetUrlShorteners(&$shorteners) + { + $shorteners[$this->shortenerName]=array('freeService'=>$this->freeService); + } + + function onStartShortenUrl($url,$shortenerName,&$shortenedUrl) + { + if($shortenerName == $this->shortenerName && strlen($url) >= common_config('site', 'shorturllength')){ + $result = $this->shorten($url); + if(isset($result) && $result != null && $result !== false){ + $shortenedUrl=$result; + common_log(LOG_INFO, __CLASS__ . ": $this->shortenerName shortened $url to $shortenedUrl"); + return false; + } + } + } +} -- cgit v1.2.3-54-g00ecf From b43866d9aab10574ec215eea059ab54e5be38841 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 11 Nov 2009 14:04:32 -0500 Subject: Correct stupid spelling errors --- plugins/Authentication/AuthenticationPlugin.php | 2 +- plugins/LdapAuthentication/LdapAuthenticationPlugin.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index ef78c7ce4..e3e55fea6 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -107,7 +107,7 @@ abstract class AuthenticationPlugin extends Plugin parent::__construct(); } - function StartCheckPassword($nickname, $password, &$authenticatedUser){ + function onStartCheckPassword($nickname, $password, &$authenticatedUser){ if($this->password_changeable){ $authenticated = $this->checkPassword($nickname, $password); if($authenticated){ diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php index f14080b2d..d3ccd93b6 100644 --- a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php +++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php @@ -34,7 +34,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php'; require_once 'Net/LDAP2.php'; -class LdapAuthenticatonPlugin extends AuthenticationPlugin +class LdapAuthenticationPlugin extends AuthenticationPlugin { public $host=null; public $port=null; -- cgit v1.2.3-54-g00ecf From f6f5b5654a81f441bd3388832e0cb51b55c37328 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 11 Nov 2009 15:08:17 -0500 Subject: Don't use common_config anymore --- .../LdapAuthenticationPlugin.php | 29 +++++++--------------- 1 file changed, 9 insertions(+), 20 deletions(-) (limited to 'plugins') diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php index d3ccd93b6..ded5cf299 100644 --- a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php +++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php @@ -78,27 +78,16 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin function autoRegister($nickname) { - $attributes=array(); - $config_attributes = array('nickname','email','fullname','homepage','location'); - foreach($config_attributes as $config_attribute){ - $value = common_config('ldap', $config_attribute.'_attribute'); - if($value!==false){ - array_push($attributes,$value); - } - } - $entry = $this->ldap_get_user($nickname,$attributes); + $entry = $this->ldap_get_user($nickname,$this->attributes); if($entry){ $registration_data = array(); - foreach($config_attributes as $config_attribute){ - $value = common_config('ldap', $config_attribute.'_attribute'); - if($value!==false){ - if($config_attribute=='email'){ - $registration_data[$config_attribute]=common_canonical_email($entry->getValue($value,'single')); - }else if($config_attribute=='nickname'){ - $registration_data[$config_attribute]=common_canonical_nickname($entry->getValue($value,'single')); - }else{ - $registration_data[$config_attribute]=$entry->getValue($value,'single'); - } + foreach($this->attributes as $sn_attribute=>$ldap_attribute){ + if($sn_attribute=='email'){ + $registration_data[$sn_attribute]=common_canonical_email($entry->getValue($ldap_attribute,'single')); + }else if($sn_attribute=='nickname'){ + $registration_data[$sn_attribute]=common_canonical_nickname($entry->getValue($ldap_attribute,'single')); + }else{ + $registration_data[$sn_attribute]=$entry->getValue($ldap_attribute,'single'); } } //set the database saved password to a random string. @@ -170,7 +159,7 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin */ function ldap_get_user($username,$attributes=array()){ $ldap = $this->ldap_get_connection(); - $filter = Net_LDAP2_Filter::create(common_config('ldap','nickname_attribute'), 'equals', $username); + $filter = Net_LDAP2_Filter::create($this->attributes['nickname'], 'equals', $username); $options = array( 'scope' => 'sub', 'attributes' => $attributes -- cgit v1.2.3-54-g00ecf From cefbad0159c6be0aa6e75c85dd2e4b9f1e412116 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 12 Nov 2009 10:38:45 -0500 Subject: Also delete the OpenID provider data when a user is deleted --- plugins/OpenID/OpenIDPlugin.php | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index e4aed2ddb..55c0eadaf 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -302,6 +302,7 @@ class OpenIDPlugin extends Plugin function onUserDeleteRelated($user, &$tables) { $tables[] = 'User_openid'; + $tables[] = 'User_openid_trustroot'; return true; } } -- cgit v1.2.3-54-g00ecf From ed690615de8f6433a1a4d9a8fc7c28385af47d8a Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 12 Nov 2009 20:12:00 -0500 Subject: Added a User_username table that links the external username with a StatusNet user_id Added EmailAuthenticationPlugin Added ReverseUsernameAuthenticationPlugin Changed the StartChangePassword and EndChangePassword events to take a user, instead of a nickname User::allowed_nickname was declared non-static, but used as if it was static, so I made the declaration static --- EVENTS.txt | 4 +- actions/passwordsettings.php | 4 +- classes/User.php | 16 +- classes/statusnet.ini | 10 ++ index.php | 1 - lib/util.php | 1 + plugins/Authentication/AuthenticationPlugin.php | 177 ++++++++++++++------- plugins/Authentication/User_username.php | 25 +++ .../EmailAuthenticationPlugin.php | 54 +++++++ plugins/EmailAuthentication/README | 7 + .../LdapAuthenticationPlugin.php | 56 +++---- plugins/LdapAuthentication/README | 5 +- plugins/ReverseUsernameAuthentication/README | 26 +++ .../ReverseUsernameAuthenticationPlugin.php | 58 +++++++ 14 files changed, 349 insertions(+), 95 deletions(-) create mode 100644 plugins/Authentication/User_username.php create mode 100644 plugins/EmailAuthentication/EmailAuthenticationPlugin.php create mode 100644 plugins/EmailAuthentication/README create mode 100644 plugins/ReverseUsernameAuthentication/README create mode 100644 plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index 3acff277b..c788a9215 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -528,12 +528,12 @@ EndCheckPassword: After checking a username/password pair - $authenticatedUser: User object if credentials match a user, else null. StartChangePassword: Before changing a password -- $nickname: user's nickname +- $user: user - $oldpassword: the user's old password - $newpassword: the desired new password EndChangePassword: After changing a password -- $nickname: user's nickname +- $user: user UserDeleteRelated: Specify additional tables to delete entries from when deleting users - $user: User object diff --git a/actions/passwordsettings.php b/actions/passwordsettings.php index 9e79501e2..11d7bf785 100644 --- a/actions/passwordsettings.php +++ b/actions/passwordsettings.php @@ -170,7 +170,7 @@ class PasswordsettingsAction extends AccountSettingsAction } $success = false; - if(! Event::handle('StartChangePassword', array($user->nickname, $oldpassword, $newpassword))){ + if(! Event::handle('StartChangePassword', array($user, $oldpassword, $newpassword))){ //no handler changed the password, so change the password internally $original = clone($user); @@ -186,7 +186,7 @@ class PasswordsettingsAction extends AccountSettingsAction $this->serverError(_('Can\'t save new password.')); return; } - Event::handle('EndChangePassword', array($nickname)); + Event::handle('EndChangePassword', array($user)); } $this->showForm(_('Password saved.'), true); diff --git a/classes/User.php b/classes/User.php index 9b90ce61b..9f1ee53f4 100644 --- a/classes/User.php +++ b/classes/User.php @@ -114,7 +114,7 @@ class User extends Memcached_DataObject return $result; } - function allowed_nickname($nickname) + static function allowed_nickname($nickname) { // XXX: should already be validated for size, content, etc. $blacklist = common_config('nickname', 'blacklist'); @@ -190,7 +190,17 @@ class User extends Memcached_DataObject $profile->query('BEGIN'); + if(!empty($email)) + { + $email = common_canonical_email($email); + } + + $nickname = common_canonical_nickname($nickname); $profile->nickname = $nickname; + if(! User::allowed_nickname($nickname)){ + common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname), + __FILE__); + } $profile->profileurl = common_profile_url($nickname); if (!empty($fullname)) { @@ -242,6 +252,10 @@ class User extends Memcached_DataObject } } + if(isset($email_confirmed) && $email_confirmed) { + $user->email = $email; + } + // This flag is ignored but still set to 1 $user->inboxed = 1; diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 912d05cdf..19ab7bf97 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -566,3 +566,13 @@ modified = 384 user_id = K token = K +[user_username] +user_id = 129 +provider_name = 130 +username = 130 +created = 142 +modified = 384 + +[user_username__keys] +provider_name = K +username = K diff --git a/index.php b/index.php index b1e4f651e..577b491ed 100644 --- a/index.php +++ b/index.php @@ -68,7 +68,6 @@ function getPath($req) */ function handleError($error) { -//error_log(print_r($error,1)); if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { return; } diff --git a/lib/util.php b/lib/util.php index 68f3520db..4b2a25ead 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1058,6 +1058,7 @@ function common_log($priority, $msg, $filename=null) } } else { common_ensure_syslog(); + error_log($msg); syslog($priority, $msg); } } diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index e3e55fea6..99b61b808 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -48,20 +48,20 @@ abstract class AuthenticationPlugin extends Plugin //should accounts be automatically created after a successful login attempt? public $autoregistration = false; - //can the user change their email address - public $email_changeable=true; - //can the user change their email address public $password_changeable=true; + //unique name for this authentication provider + public $provider_name; + //------------Auth plugin should implement some (or all) of these methods------------\\ /** * Check if a nickname/password combination is valid - * @param nickname + * @param username * @param password * @return boolean true if the credentials are valid, false if they are invalid. */ - function checkPassword($nickname, $password) + function checkPassword($username, $password) { return false; } @@ -69,88 +69,116 @@ abstract class AuthenticationPlugin extends Plugin /** * Automatically register a user when they attempt to login with valid credentials. * User::register($data) is a very useful method for this implementation - * @param nickname - * @return boolean true if the user was created, false if autoregistration is not allowed, null if this plugin is not responsible for this nickname + * @param username + * @return boolean true if the user was created, false if not */ - function autoRegister($nickname) + function autoRegister($username) { - return null; + $registration_data = array(); + $registration_data['nickname'] = $username ; + return User::register($registration_data); } /** * Change a user's password * The old password has been verified to be valid by this plugin before this call is made - * @param nickname + * @param username * @param oldpassword * @param newpassword - * @return boolean true if the password was changed, false if password changing failed for some reason, null if this plugin is not responsible for this nickname + * @return boolean true if the password was changed, false if password changing failed for some reason */ - function changePassword($nickname,$oldpassword,$newpassword) + function changePassword($username,$oldpassword,$newpassword) { - return null; - } - - /** - * Can a user change this field in his own profile? - * @param nickname - * @param field - * @return boolean true if the field can be changed, false if not allowed to change it, null if this plugin is not responsible for this nickname - */ - function canUserChangeField($nickname, $field) - { - return null; + return false; } //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ - function __construct() - { - parent::__construct(); + function onInitializePlugin(){ + if(!isset($this->provider_name)){ + throw new Exception("must specify a provider_name for this authentication provider"); + } } - + function onStartCheckPassword($nickname, $password, &$authenticatedUser){ - if($this->password_changeable){ - $authenticated = $this->checkPassword($nickname, $password); + //map the nickname to a username + $user_username = new User_username(); + $user_username->username=$nickname; + $user_username->provider_name=$this->provider_name; + if($user_username->find() && $user_username->fetch()){ + $username = $user_username->username; + $authenticated = $this->checkPassword($username, $password); if($authenticated){ - $authenticatedUser = User::staticGet('nickname', $nickname); - if(!$authenticatedUser && $this->autoregistration){ - if($this->autoregister($nickname)){ + $authenticatedUser = User::staticGet('id', $user_username->user_id); + return false; + } + }else{ + $user = User::staticGet('nickname', $nickname); + if($user){ + //make sure a different provider isn't handling this nickname + $user_username = new User_username(); + $user_username->username=$nickname; + if(!$user_username->find()){ + //no other provider claims this username, so it's safe for us to handle it + $authenticated = $this->checkPassword($nickname, $password); + if($authenticated){ $authenticatedUser = User::staticGet('nickname', $nickname); + $user_username = new User_username(); + $user_username->user_id = $authenticatedUser->id; + $user_username->provider_name = $this->provider_name; + $user_username->username = $nickname; + $user_username->created = DB_DataObject_Cast::dateTime(); + $user_username->insert(); + return false; } } - return false; }else{ - if($this->authoritative){ - return false; + if($this->autoregistration){ + $authenticated = $this->checkPassword($nickname, $password); + if($authenticated && $this->autoregister($nickname)){ + $authenticatedUser = User::staticGet('nickname', $nickname); + $user_username = new User_username(); + $user_username->user_id = $authenticatedUser->id; + $user_username->provider_name = $this->provider_name; + $user_username->username = $nickname; + $user_username->created = DB_DataObject_Cast::dateTime(); + $user_username->insert(); + return false; + } } } - //we're not authoritative, so let other handlers try + } + if($this->authoritative){ + return false; }else{ - if($this->authoritative){ - //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing is not allowed')); - } + //we're not authoritative, so let other handlers try + return; } } - function onStartChangePassword($nickname,$oldpassword,$newpassword) + function onStartChangePassword($user,$oldpassword,$newpassword) { if($this->password_changeable){ - $authenticated = $this->checkPassword($nickname, $oldpassword); - if($authenticated){ - $result = $this->changePassword($nickname,$oldpassword,$newpassword); - if($result){ - //stop handling of other handlers, because what was requested was done - return false; - }else{ - throw new Exception(_('Password changing failed')); - } - }else{ - if($this->authoritative){ - //since we're authoritative, no other plugin could do this - throw new Exception(_('Password changing failed')); + $user_username = new User_username(); + $user_username->user_id=$user->id; + $user_username->provider_name=$this->provider_name; + if($user_username->find() && $user_username->fetch()){ + $authenticated = $this->checkPassword($user_username->username, $oldpassword); + if($authenticated){ + $result = $this->changePassword($user_username->username,$oldpassword,$newpassword); + if($result){ + //stop handling of other handlers, because what was requested was done + return false; + }else{ + throw new Exception(_('Password changing failed')); + } }else{ - //let another handler try - return null; + if($this->authoritative){ + //since we're authoritative, no other plugin could do this + throw new Exception(_('Password changing failed')); + }else{ + //let another handler try + return null; + } } } }else{ @@ -164,9 +192,42 @@ abstract class AuthenticationPlugin extends Plugin function onStartAccountSettingsPasswordMenuItem($widget) { if($this->authoritative && !$this->password_changeable){ - //since we're authoritative, no other plugin could change passwords, so do render the menu item + //since we're authoritative, no other plugin could change passwords, so do not render the menu item + return false; + } + } + + function onAutoload($cls) + { + switch ($cls) + { + case 'User_username': + require_once(INSTALLDIR.'/plugins/Authentication/User_username.php'); return false; + default: + return true; } } + + function onCheckSchema() { + $schema = Schema::get(); + $schema->ensureTable('user_username', + array(new ColumnDef('provider_name', 'varchar', + '255', false, 'PRI'), + new ColumnDef('username', 'varchar', + '255', false, 'PRI'), + new ColumnDef('user_id', 'integer', + null, false), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); + return true; + } + + function onUserDeleteRelated($user, &$tables) + { + $tables[] = 'User_username'; + return true; + } } diff --git a/plugins/Authentication/User_username.php b/plugins/Authentication/User_username.php new file mode 100644 index 000000000..79adeb189 --- /dev/null +++ b/plugins/Authentication/User_username.php @@ -0,0 +1,25 @@ +. + * + * @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') && !defined('LACONICA')) { + exit(1); +} + +class EmailAuthenticationPlugin extends Plugin +{ + //---interface implementation---// + + function onStartCheckPassword($nickname, $password, &$authenticatedUser) + { + if(strpos($nickname, '@')) + { + $user = User::staticGet('email',$nickname); + if($user && isset($user->email)) + { + if(common_check_user($user->nickname,$password)) + { + $authenticatedUser = $user; + return false; + } + } + } + } +} + diff --git a/plugins/EmailAuthentication/README b/plugins/EmailAuthentication/README new file mode 100644 index 000000000..320815689 --- /dev/null +++ b/plugins/EmailAuthentication/README @@ -0,0 +1,7 @@ +The Email Authentication plugin allows users to login using their email address. + +The provided email address is used to lookup the user's nickname, then that nickname and the provided password is checked. + +Installation +============ +add "addPlugin('emailAuthentication');" to the bottom of your config.php diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php index ded5cf299..865154730 100644 --- a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php +++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php @@ -48,20 +48,31 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin public $scope=null; public $attributes=array(); - function __construct() - { - parent::__construct(); + function onInitializePlugin(){ + parent::onInitializePlugin(); + if(!isset($this->host)){ + throw new Exception("must specify a host"); + } + if(!isset($this->basedn)){ + throw new Exception("must specify a basedn"); + } + if(!isset($this->attributes['nickname'])){ + throw new Exception("must specify a nickname attribute"); + } + if(!isset($this->attributes['username'])){ + throw new Exception("must specify a username attribute"); + } } //---interface implementation---// - function checkPassword($nickname, $password) + function checkPassword($username, $password) { $ldap = $this->ldap_get_connection(); if(!$ldap){ return false; } - $entry = $this->ldap_get_user($nickname); + $entry = $this->ldap_get_user($username); if(!$entry){ return false; }else{ @@ -76,48 +87,33 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin } } - function autoRegister($nickname) + function autoRegister($username) { - $entry = $this->ldap_get_user($nickname,$this->attributes); + $entry = $this->ldap_get_user($username,$this->attributes); if($entry){ $registration_data = array(); foreach($this->attributes as $sn_attribute=>$ldap_attribute){ - if($sn_attribute=='email'){ - $registration_data[$sn_attribute]=common_canonical_email($entry->getValue($ldap_attribute,'single')); - }else if($sn_attribute=='nickname'){ - $registration_data[$sn_attribute]=common_canonical_nickname($entry->getValue($ldap_attribute,'single')); - }else{ - $registration_data[$sn_attribute]=$entry->getValue($ldap_attribute,'single'); - } + $registration_data[$sn_attribute]=$entry->getValue($ldap_attribute,'single'); + } + if(isset($registration_data['email']) && !empty($registration_data['email'])){ + $registration_data['email_confirmed']=true; } //set the database saved password to a random string. $registration_data['password']=common_good_rand(16); - $user = User::register($registration_data); - return true; + return User::register($registration_data); }else{ //user isn't in ldap, so we cannot register him - return null; + return false; } } - function changePassword($nickname,$oldpassword,$newpassword) + function changePassword($username,$oldpassword,$newpassword) { //TODO implement this throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); return false; } - - function canUserChangeField($nickname, $field) - { - switch($field) - { - case 'password': - case 'nickname': - case 'email': - return false; - } - } //---utility functions---// function ldap_get_config(){ @@ -159,7 +155,7 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin */ function ldap_get_user($username,$attributes=array()){ $ldap = $this->ldap_get_connection(); - $filter = Net_LDAP2_Filter::create($this->attributes['nickname'], 'equals', $username); + $filter = Net_LDAP2_Filter::create($this->attributes['username'], 'equals', $username); $options = array( 'scope' => 'sub', 'attributes' => $attributes diff --git a/plugins/LdapAuthentication/README b/plugins/LdapAuthentication/README index 03647e7c7..b10a1eb93 100644 --- a/plugins/LdapAuthentication/README +++ b/plugins/LdapAuthentication/README @@ -6,7 +6,8 @@ add "addPlugin('ldapAuthentication', array('setting'=>'value', 'setting2'=>'valu Settings ======== -authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). +provider_name*: a unique name for this authentication provider. +authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check any other plugins or the internal password database). autoregistration (false): Set to true if users should be automatically created when they attempt to login. email_changeable (true): Are users allowed to change their email address? (true or false) password_changeable (true): Are users allowed to change their passwords? (true or false) @@ -23,6 +24,7 @@ filter: Default search filter. See http://pear.php.net/manual/en/package.network scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name + username* nickname* email fullname @@ -37,6 +39,7 @@ Example Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. addPlugin('ldapAuthentication', array( + 'provider_name'=>'Example', 'authoritative'=>true, 'autoregistration'=>true, 'binddn'=>'username', diff --git a/plugins/ReverseUsernameAuthentication/README b/plugins/ReverseUsernameAuthentication/README new file mode 100644 index 000000000..e9160ed9b --- /dev/null +++ b/plugins/ReverseUsernameAuthentication/README @@ -0,0 +1,26 @@ +The Reverse Username Authentication plugin allows for StatusNet to handle authentication by checking if the provided password is the same as the reverse of the username. + +THIS PLUGIN IS FOR TESTING PURPOSES ONLY + +Installation +============ +add "addPlugin('reverseUsernameAuthentication', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php + +Settings +======== +provider_name*: a unique name for this authentication provider. +password_changeable*: must be set to false. This plugin does not support changing passwords. +authoritative (false): Set to true if this plugin's responses are authoritative (meaning if this fails, do check any other plugins or the internal password database). +autoregistration (false): Set to true if users should be automatically created when they attempt to login. + +* required +default values are in (parenthesis) + +Example +======= +addPlugin('reverseUsernameAuthentication', array( + 'provider_name'=>'Example', + 'password_changeable'=>false, + 'authoritative'=>true, + 'autoregistration'=>true +)); diff --git a/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php b/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php new file mode 100644 index 000000000..d48283b2e --- /dev/null +++ b/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php @@ -0,0 +1,58 @@ +. + * + * @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') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php'; + +class ReverseUsernameAuthenticationPlugin extends AuthenticationPlugin +{ + //---interface implementation---// + + function onInitializePlugin(){ + parent::onInitializePlugin(); + if(!isset($this->password_changeable) && $this->password_changeable){ + throw new Exception("password_changeable cannot be set to true. This plugin does not support changing passwords."); + } + } + + function checkPassword($username, $password) + { + return $username == strrev($password); + } + + function autoRegister($username) + { + $registration_data = array(); + $registration_data['nickname'] = $username ; + return User::register($registration_data); + } +} -- cgit v1.2.3-54-g00ecf From 1bac324072a2d8d53b7e8c038d781b0af21eb99e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Fri, 13 Nov 2009 12:26:24 +0000 Subject: Moved class="pagination" to child element and removed element --- lib/action.php | 4 +--- plugins/Facebook/facebookaction.php | 4 +--- plugins/Facebook/facebookhome.php | 4 +--- plugins/InfiniteScroll/infinitescroll.js | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/lib/action.php b/lib/action.php index 80f398fbd..b5cf3240c 100644 --- a/lib/action.php +++ b/lib/action.php @@ -1048,8 +1048,7 @@ class Action extends HTMLOutputter // lawsuit { // 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->elementStart('dl', 'pagination'); $this->element('dt', null, _('Pagination')); $this->elementStart('dd', null); $this->elementStart('ul', array('class' => 'nav')); @@ -1074,7 +1073,6 @@ class Action extends HTMLOutputter // lawsuit $this->elementEnd('ul'); $this->elementEnd('dd'); $this->elementEnd('dl'); - $this->elementEnd('div'); } } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index a10fdf90d..c852bbf5e 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -382,8 +382,7 @@ class FacebookAction extends Action { // 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->elementStart('dl', 'pagination'); $this->element('dt', null, _('Pagination')); $this->elementStart('dd', null); $this->elementStart('ul', array('class' => 'nav')); @@ -408,7 +407,6 @@ class FacebookAction extends Action $this->elementEnd('ul'); $this->elementEnd('dd'); $this->elementEnd('dl'); - $this->elementEnd('div'); } } diff --git a/plugins/Facebook/facebookhome.php b/plugins/Facebook/facebookhome.php index 91c0cc6b8..ea141c2c2 100644 --- a/plugins/Facebook/facebookhome.php +++ b/plugins/Facebook/facebookhome.php @@ -244,8 +244,7 @@ class FacebookhomeAction extends FacebookAction // 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->elementStart('dl', 'pagination'); $this->element('dt', null, _('Pagination')); $this->elementStart('dd', null); $this->elementStart('ul', array('class' => 'nav')); @@ -270,7 +269,6 @@ class FacebookhomeAction extends FacebookAction $this->elementEnd('ul'); $this->elementEnd('dd'); $this->elementEnd('dl'); - $this->elementEnd('div'); } } diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index ae4d53d09..09b2d4f72 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -6,7 +6,7 @@ jQuery(document).ready(function($){ loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif', text : "Loading the next set of posts...", donetext : "Congratulations, you\'ve reached the end of the Internet.", - navSelector : "div.pagination", + navSelector : ".pagination", contentSelector : "#notices_primary ol.notices", itemSelector : "#notices_primary ol.notices li" },function(){ -- cgit v1.2.3-54-g00ecf From b9562cbb18a7b8f3f01bcbc8540933c4dfc10fb9 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 13 Nov 2009 12:54:27 -0500 Subject: autoregister returns the new user on success (not just true) --- plugins/Authentication/AuthenticationPlugin.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index 99b61b808..f061e456d 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -70,7 +70,7 @@ abstract class AuthenticationPlugin extends Plugin * Automatically register a user when they attempt to login with valid credentials. * User::register($data) is a very useful method for this implementation * @param username - * @return boolean true if the user was created, false if not + * @return mixed instance of User, or false (if user couldn't be created) */ function autoRegister($username) { @@ -134,15 +134,18 @@ abstract class AuthenticationPlugin extends Plugin }else{ if($this->autoregistration){ $authenticated = $this->checkPassword($nickname, $password); - if($authenticated && $this->autoregister($nickname)){ - $authenticatedUser = User::staticGet('nickname', $nickname); - $user_username = new User_username(); - $user_username->user_id = $authenticatedUser->id; - $user_username->provider_name = $this->provider_name; - $user_username->username = $nickname; - $user_username->created = DB_DataObject_Cast::dateTime(); - $user_username->insert(); - return false; + if($authenticated){ + $user = $this->autoregister($nickname); + if($user){ + $authenticatedUser = $user; + $user_username = new User_username(); + $user_username->user_id = $authenticatedUser->id; + $user_username->provider_name = $this->provider_name; + $user_username->username = $nickname; + $user_username->created = DB_DataObject_Cast::dateTime(); + $user_username->insert(); + return false; + } } } } -- cgit v1.2.3-54-g00ecf From 5494eb61465595466b897fdd88efda250c1107e2 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 13 Nov 2009 13:11:28 -0500 Subject: Refactor User_username object creation to reuse code --- plugins/Authentication/AuthenticationPlugin.php | 14 ++------------ plugins/Authentication/User_username.php | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) (limited to 'plugins') diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index f061e456d..a76848b04 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -122,12 +122,7 @@ abstract class AuthenticationPlugin extends Plugin $authenticated = $this->checkPassword($nickname, $password); if($authenticated){ $authenticatedUser = User::staticGet('nickname', $nickname); - $user_username = new User_username(); - $user_username->user_id = $authenticatedUser->id; - $user_username->provider_name = $this->provider_name; - $user_username->username = $nickname; - $user_username->created = DB_DataObject_Cast::dateTime(); - $user_username->insert(); + User_username::register($authenticatedUser,$nickname,$this->provider_name); return false; } } @@ -138,12 +133,7 @@ abstract class AuthenticationPlugin extends Plugin $user = $this->autoregister($nickname); if($user){ $authenticatedUser = $user; - $user_username = new User_username(); - $user_username->user_id = $authenticatedUser->id; - $user_username->provider_name = $this->provider_name; - $user_username->username = $nickname; - $user_username->created = DB_DataObject_Cast::dateTime(); - $user_username->insert(); + User_username::register($authenticatedUser,$nickname,$this->provider_name); return false; } } diff --git a/plugins/Authentication/User_username.php b/plugins/Authentication/User_username.php index 79adeb189..f30f60d83 100644 --- a/plugins/Authentication/User_username.php +++ b/plugins/Authentication/User_username.php @@ -22,4 +22,25 @@ class User_username extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + /** + * Register a user with a username on a given provider + * @param User User object + * @param string username on the given provider + * @param provider_name string name of the provider + * @return mixed User_username instance if the registration succeeded, false if it did not + */ + static function register($user, $username, $provider_name) + { + $user_username = new User_username(); + $user_username->user_id = $user->id; + $user_username->provider_name = $provider_name; + $user_username->username = $username; + $user_username->created = DB_DataObject_Cast::dateTime(); + if($user_username->insert()){ + return $user_username; + }else{ + return false; + } + } } -- cgit v1.2.3-54-g00ecf From acfa086d21c9a2624cb27182ff4011ec57ac2680 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 11 Nov 2009 15:38:28 -0500 Subject: Resetup the ajaxification for replies and favors after inf. scrolling Made the selector much more specific, so it only works on pages that have notices --- plugins/InfiniteScroll/infinitescroll.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index 09b2d4f72..82254613e 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -2,7 +2,13 @@ jQuery(document).ready(function($){ $('notices_primary').infinitescroll({ debug: true, infiniteScroll : false, - nextSelector : "li.nav_next a", + nextSelector : 'body#public li.nav_next a,'+ + 'body#all li.nav_next a,'+ + 'body#showstream li.nav_next a,'+ + 'body#replies li.nav_next a,'+ + 'body#showfavorites li.nav_next a,'+ + 'body#showgroup li.nav_next a,'+ + 'body#favorited li.nav_next a', loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif', text : "Loading the next set of posts...", donetext : "Congratulations, you\'ve reached the end of the Internet.", @@ -11,5 +17,7 @@ jQuery(document).ready(function($){ itemSelector : "#notices_primary ol.notices li" },function(){ NoticeAttachments(); + NoticeReply(); + NoticeFavors(); }); }); -- cgit v1.2.3-54-g00ecf From c621a9db72978bbcb452d103949047c20ec2a745 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 13 Nov 2009 17:56:14 -0500 Subject: Fix to work with csarven's changes to util.js --- plugins/InfiniteScroll/infinitescroll.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index 82254613e..0dafef6d5 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -16,8 +16,6 @@ jQuery(document).ready(function($){ contentSelector : "#notices_primary ol.notices", itemSelector : "#notices_primary ol.notices li" },function(){ - NoticeAttachments(); - NoticeReply(); - NoticeFavors(); + SN.Init.Notices(); }); }); -- cgit v1.2.3-54-g00ecf From 50234be398a376c1f62499c33f31d9eea398a486 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 13 Nov 2009 15:34:15 -0800 Subject: Fix regression in OpenID autosubmit page. Since core JS loads were moved to the bottom, the JavaScript was being run before jQuery was loaded, so the onload event never got set. Moved it down to the scripts section. --- plugins/OpenID/openid.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'plugins') diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index ff7a93899..dd628e773 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -280,6 +280,11 @@ class AutosubmitAction extends Action function showContent() { $this->raw($this->form_html); + } + + function showScripts() + { + parent::showScripts(); $this->element('script', null, '$(document).ready(function() { ' . ' $(\'#'. $this->form_id .'\').submit(); '. -- cgit v1.2.3-54-g00ecf From 84e427c6c147ec69f6d2f32ea64c1508e7c4dded Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Nov 2009 17:43:15 +0100 Subject: start showing actions for flagged profiles --- plugins/UserFlag/adminprofileflag.php | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'plugins') diff --git a/plugins/UserFlag/adminprofileflag.php b/plugins/UserFlag/adminprofileflag.php index b264beecb..629480991 100644 --- a/plugins/UserFlag/adminprofileflag.php +++ b/plugins/UserFlag/adminprofileflag.php @@ -53,6 +53,8 @@ class AdminprofileflagAction extends Action function prepare($args) { + parent::prepare($args); + return true; } @@ -83,6 +85,112 @@ class AdminprofileflagAction extends Action function showContent() { + $profile = $this->getProfiles(); + + $pl = new FlaggedProfileList($profile, $this); + + $pl->show(); + } + + function getProfiles() + { + $ufp = new User_flag_profile(); + + $ufp->selectAdd(); + $ufp->selectAdd('profile_id'); + $ufp->selectAdd('count(*) as flag_count'); + + $ufp->whereAdd('cleared is NULL'); + + $ufp->groupBy('profile_id'); + $ufp->orderBy('flag_count DESC'); + + $profiles = array(); + + if ($ufp->find()) { + while ($ufp->fetch()) { + $profile = Profile::staticGet('id', $ufp->profile_id); + if (!empty($profile)) { + $profiles[] = $profile; + } + } + } + + $ufp->free(); + + return new ArrayWrapper($profiles); } } +class FlaggedProfileList extends ProfileList { + + function newListItem($profile) + { + return new FlaggedProfileListItem($this->profile, $this->action); + } +} + +class FlaggedProfileListItem extends ProfileListItem +{ + var $user = null; + + function showActions() + { + $this->user = common_current_user(); + + $this->startActions(); + if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->showSandboxButton(); + $this->showSilenceButton(); + $this->showDeleteButton(); + $this->showClearButton(); + Event::handle('EndProfileListItemActionElements', array($this)); + } + $this->endActions(); + } + + function showSandboxButton() + { + if ($this->user->hasRight(Right::SANDBOXUSER)) { + $this->out->elementStart('li', 'entity_sandbox'); + if ($this->user->isSandboxed()) { + $usf = new UnSandboxForm($this->out, $this->profile); + $usf->show(); + } else { + $sf = new SandboxForm($this->out, $this->profile); + $sf->show(); + } + $this->out->elementEnd('li'); + } + } + + function showSilenceButton() + { + if ($this->user->hasRight(Right::SILENCEUSER)) { + $this->out->elementStart('li', 'entity_silence'); + if ($this->user->isSilenced()) { + $usf = new UnSilenceForm($this->out, $this->profile); + $usf->show(); + } else { + $sf = new SilenceForm($this->out, $this->profile); + $sf->show(); + } + $this->out->elementEnd('li'); + } + } + + function showDeleteButton() + { + + if ($this->user->hasRight(Right::DELETEUSER)) { + $this->out->elementStart('li', 'entity_delete'); + $df = new DeleteUserForm($this->out, $this->profile); + $df->show(); + $this->out->elementEnd('li'); + } + } + + function showClearButton() + { + } +} -- cgit v1.2.3-54-g00ecf From d080afebbe835c70ed5b6937f71df5a36aeb906e Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Mon, 16 Nov 2009 11:44:31 -0500 Subject: Refactored Recaptcha plugin to use addPlugin() Also nicer log messages --- plugins/Recaptcha/LICENSE | 22 +++ plugins/Recaptcha/README | 28 ++++ plugins/Recaptcha/RecaptchaPlugin.php | 106 +++++++++++++ plugins/Recaptcha/recaptchalib.php | 277 ++++++++++++++++++++++++++++++++++ plugins/recaptcha/LICENSE | 22 --- plugins/recaptcha/README | 23 --- plugins/recaptcha/recaptcha.php | 104 ------------- plugins/recaptcha/recaptchalib.php | 277 ---------------------------------- 8 files changed, 433 insertions(+), 426 deletions(-) create mode 100644 plugins/Recaptcha/LICENSE create mode 100644 plugins/Recaptcha/README create mode 100644 plugins/Recaptcha/RecaptchaPlugin.php create mode 100644 plugins/Recaptcha/recaptchalib.php delete mode 100644 plugins/recaptcha/LICENSE delete mode 100644 plugins/recaptcha/README delete mode 100644 plugins/recaptcha/recaptcha.php delete mode 100644 plugins/recaptcha/recaptchalib.php (limited to 'plugins') diff --git a/plugins/Recaptcha/LICENSE b/plugins/Recaptcha/LICENSE new file mode 100644 index 000000000..b612f71f0 --- /dev/null +++ b/plugins/Recaptcha/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net +AUTHORS: + Mike Crawford + Ben Maurer + +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. diff --git a/plugins/Recaptcha/README b/plugins/Recaptcha/README new file mode 100644 index 000000000..49c4b9c27 --- /dev/null +++ b/plugins/Recaptcha/README @@ -0,0 +1,28 @@ +StatusNet reCAPTCHA plugin 0.3 11/16/09 +======================================= +Adds a captcha to your registration page to reduce automated spam bots registering. + +Use: +1. Get an API key from http://recaptcha.net + +2. In config.php add: +addPlugin('recaptcha', array('private_key' => 'YourKeyHere', + 'public_key' => 'ReplaceWithYourKey')); +or +addPlugin('recaptcha', array('private_key' => 'YourKeyHere', + 'public_key' => 'ReplaceWithYourKey', + 'display_errors' => true)); +Changelog +========= +0.1 initial release +0.2 Work around for webkit browsers +0.3 Moved to new plugin arch for SN + **YOU WILL NEED TO CHANGE YOUR CONFIG.PHP!** + +reCAPTCHA Lib README +==================== + +The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation +for this library can be found at + + http://recaptcha.net/plugins/php diff --git a/plugins/Recaptcha/RecaptchaPlugin.php b/plugins/Recaptcha/RecaptchaPlugin.php new file mode 100644 index 000000000..1a51b16be --- /dev/null +++ b/plugins/Recaptcha/RecaptchaPlugin.php @@ -0,0 +1,106 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Eric Helgeson + * @copyright 2009 + * @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('RECAPTCHA', '0.2'); + +require_once(INSTALLDIR.'/plugins/Recaptcha/recaptchalib.php'); + +class RecaptchaPlugin extends Plugin +{ + var $private_key; + var $public_key; + var $display_errors; + var $failed; + var $ssl; + + function onInitializePlugin(){ + if(!isset($this->private_key)){ + common_log(LOG_ERR, "Recaptcha: Must specify private_key in config.php"); + } + if(!isset($this->public_key)){ + common_log(LOG_ERR, "Recaptcha: Must specify public_key in config.php"); + } + } + + function checkssl(){ + if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') { + return true; + } + return false; + } + + function onStartShowHTML($action) + { + //XXX: Horrible hack to make Safari, FF2, and Chrome work with + //reChapcha. reChapcha beaks xhtml strict + header('Content-Type: text/html'); + + $action->extraHeaders(); + + $action->startXML('html'); + + $action->raw(''); + return false; + } + + function onEndRegistrationFormData($action) + { + $action->elementStart('li'); + $action->raw(''); + if($this->checkssl() === true){ + $action->raw(recaptcha_get_html($this->public_key), null, true); + } else { + $action->raw(recaptcha_get_html($this->public_key)); + } + $action->elementEnd('li'); + return true; + } + + function onStartRegistrationTry($action) + { + $resp = recaptcha_check_answer ($this->private_key, + $_SERVER["REMOTE_ADDR"], + $action->trimmed('recaptcha_challenge_field'), + $action->trimmed('recaptcha_response_field')); + + if (!$resp->is_valid) + { + if($this->display_errors) + { + $action->showForm ("(reCAPTCHA said: " . $resp->error . ")"); + } + $action->showForm("Captcha does not match!"); + return false; + } + } +} diff --git a/plugins/Recaptcha/recaptchalib.php b/plugins/Recaptcha/recaptchalib.php new file mode 100644 index 000000000..897c50981 --- /dev/null +++ b/plugins/Recaptcha/recaptchalib.php @@ -0,0 +1,277 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://mailhide.recaptcha.net/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://mailhide.recaptcha.net/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/plugins/recaptcha/LICENSE b/plugins/recaptcha/LICENSE deleted file mode 100644 index b612f71f0..000000000 --- a/plugins/recaptcha/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net -AUTHORS: - Mike Crawford - Ben Maurer - -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. diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README deleted file mode 100644 index b996f96cc..000000000 --- a/plugins/recaptcha/README +++ /dev/null @@ -1,23 +0,0 @@ -StatusNet reCAPTCHA plugin 0.2 8/3/09 -==================================== -Adds a captcha to your registration page to reduce automated spam bots registering. - -Use: -1. Get an API key from http://recaptcha.net - -2. In config.php add: -include_once('plugins/recaptcha/recaptcha.php'); -$captcha = new recaptcha(publickey, privatekey, showErrors); - -Changelog -========= -0.1 initial release -0.2 Work around for webkit browsers - -reCAPTCHA README -================ - -The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation -for this library can be found at - - http://recaptcha.net/plugins/php diff --git a/plugins/recaptcha/recaptcha.php b/plugins/recaptcha/recaptcha.php deleted file mode 100644 index 94cf0ccd1..000000000 --- a/plugins/recaptcha/recaptcha.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - * @category Plugin - * @package StatusNet - * @author Eric Helgeson - * @copyright 2009 - * @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('RECAPTCHA', '0.2'); - -class recaptcha extends Plugin -{ - var $private_key; - var $public_key; - var $display_errors; - var $failed; - var $ssl; - - function __construct($public_key, $private_key, $display_errors=false) - { - parent::__construct(); - require_once(INSTALLDIR.'/plugins/recaptcha/recaptchalib.php'); - $this->public_key = $public_key; - $this->private_key = $private_key; - $this->display_errors = $display_errors; - } - - function checkssl(){ - if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') { - return true; - } - return false; - } - - function onStartShowHTML($action) - { - //XXX: Horrible hack to make Safari, FF2, and Chrome work with - //reChapcha. reChapcha beaks xhtml strict - header('Content-Type: text/html'); - - $action->extraHeaders(); - - $action->startXML('html'); - - $action->raw(''); - return false; - } - - function onEndRegistrationFormData($action) - { - $action->elementStart('li'); - $action->raw(''); - if($this->checkssl() === true){ - $action->raw(recaptcha_get_html($this->public_key), null, true); - } else { - $action->raw(recaptcha_get_html($this->public_key)); - } - $action->elementEnd('li'); - return true; - } - - function onStartRegistrationTry($action) - { - $resp = recaptcha_check_answer ($this->private_key, - $_SERVER["REMOTE_ADDR"], - $action->trimmed('recaptcha_challenge_field'), - $action->trimmed('recaptcha_response_field')); - - if (!$resp->is_valid) - { - if($this->display_errors) - { - $action->showForm ("(reCAPTCHA said: " . $resp->error . ")"); - } - $action->showForm("Captcha does not match!"); - return false; - } - } -} diff --git a/plugins/recaptcha/recaptchalib.php b/plugins/recaptcha/recaptchalib.php deleted file mode 100644 index 897c50981..000000000 --- a/plugins/recaptcha/recaptchalib.php +++ /dev/null @@ -1,277 +0,0 @@ - $value ) - $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; - - // Cut the last '&' - $req=substr($req,0,strlen($req)-1); - return $req; -} - - - -/** - * Submits an HTTP POST to a reCAPTCHA server - * @param string $host - * @param string $path - * @param array $data - * @param int port - * @return array response - */ -function _recaptcha_http_post($host, $path, $data, $port = 80) { - - $req = _recaptcha_qsencode ($data); - - $http_request = "POST $path HTTP/1.0\r\n"; - $http_request .= "Host: $host\r\n"; - $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; - $http_request .= "Content-Length: " . strlen($req) . "\r\n"; - $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; - $http_request .= "\r\n"; - $http_request .= $req; - - $response = ''; - if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { - die ('Could not open socket'); - } - - fwrite($fs, $http_request); - - while ( !feof($fs) ) - $response .= fgets($fs, 1160); // One TCP-IP packet - fclose($fs); - $response = explode("\r\n\r\n", $response, 2); - - return $response; -} - - - -/** - * Gets the challenge HTML (javascript and non-javascript version). - * This is called from the browser, and the resulting reCAPTCHA HTML widget - * is embedded within the HTML form it was called from. - * @param string $pubkey A public key for reCAPTCHA - * @param string $error The error given by reCAPTCHA (optional, default is null) - * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) - - * @return string - The HTML to be embedded in the user's form. - */ -function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) -{ - if ($pubkey == null || $pubkey == '') { - die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); - } - - if ($use_ssl) { - $server = RECAPTCHA_API_SECURE_SERVER; - } else { - $server = RECAPTCHA_API_SERVER; - } - - $errorpart = ""; - if ($error) { - $errorpart = "&error=" . $error; - } - return ' - - '; -} - - - - -/** - * A ReCaptchaResponse is returned from recaptcha_check_answer() - */ -class ReCaptchaResponse { - var $is_valid; - var $error; -} - - -/** - * Calls an HTTP POST function to verify if the user's guess was correct - * @param string $privkey - * @param string $remoteip - * @param string $challenge - * @param string $response - * @param array $extra_params an array of extra variables to post to the server - * @return ReCaptchaResponse - */ -function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) -{ - if ($privkey == null || $privkey == '') { - die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); - } - - if ($remoteip == null || $remoteip == '') { - die ("For security reasons, you must pass the remote ip to reCAPTCHA"); - } - - - - //discard spam submissions - if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { - $recaptcha_response = new ReCaptchaResponse(); - $recaptcha_response->is_valid = false; - $recaptcha_response->error = 'incorrect-captcha-sol'; - return $recaptcha_response; - } - - $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify", - array ( - 'privatekey' => $privkey, - 'remoteip' => $remoteip, - 'challenge' => $challenge, - 'response' => $response - ) + $extra_params - ); - - $answers = explode ("\n", $response [1]); - $recaptcha_response = new ReCaptchaResponse(); - - if (trim ($answers [0]) == 'true') { - $recaptcha_response->is_valid = true; - } - else { - $recaptcha_response->is_valid = false; - $recaptcha_response->error = $answers [1]; - } - return $recaptcha_response; - -} - -/** - * gets a URL where the user can sign up for reCAPTCHA. If your application - * has a configuration page where you enter a key, you should provide a link - * using this function. - * @param string $domain The domain where the page is hosted - * @param string $appname The name of your application - */ -function recaptcha_get_signup_url ($domain = null, $appname = null) { - return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname)); -} - -function _recaptcha_aes_pad($val) { - $block_size = 16; - $numpad = $block_size - (strlen ($val) % $block_size); - return str_pad($val, strlen ($val) + $numpad, chr($numpad)); -} - -/* Mailhide related code */ - -function _recaptcha_aes_encrypt($val,$ky) { - if (! function_exists ("mcrypt_encrypt")) { - die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); - } - $mode=MCRYPT_MODE_CBC; - $enc=MCRYPT_RIJNDAEL_128; - $val=_recaptcha_aes_pad($val); - return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); -} - - -function _recaptcha_mailhide_urlbase64 ($x) { - return strtr(base64_encode ($x), '+/', '-_'); -} - -/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ -function recaptcha_mailhide_url($pubkey, $privkey, $email) { - if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { - die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . - "you can do so at http://mailhide.recaptcha.net/apikey"); - } - - - $ky = pack('H*', $privkey); - $cryptmail = _recaptcha_aes_encrypt ($email, $ky); - - return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); -} - -/** - * gets the parts of the email to expose to the user. - * eg, given johndoe@example,com return ["john", "example.com"]. - * the email is then displayed as john...@example.com - */ -function _recaptcha_mailhide_email_parts ($email) { - $arr = preg_split("/@/", $email ); - - if (strlen ($arr[0]) <= 4) { - $arr[0] = substr ($arr[0], 0, 1); - } else if (strlen ($arr[0]) <= 6) { - $arr[0] = substr ($arr[0], 0, 3); - } else { - $arr[0] = substr ($arr[0], 0, 4); - } - return $arr; -} - -/** - * Gets html to display an email address given a public an private key. - * to get a key, go to: - * - * http://mailhide.recaptcha.net/apikey - */ -function recaptcha_mailhide_html($pubkey, $privkey, $email) { - $emailparts = _recaptcha_mailhide_email_parts ($email); - $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); - - return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); - -} - - -?> -- cgit v1.2.3-54-g00ecf From bea580873f10c186a3e12047c28d693d3dcb26d2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Nov 2009 17:55:00 +0100 Subject: use return-to method for adminprofileflag --- plugins/UserFlag/adminprofileflag.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'plugins') diff --git a/plugins/UserFlag/adminprofileflag.php b/plugins/UserFlag/adminprofileflag.php index 629480991..1ac76b506 100644 --- a/plugins/UserFlag/adminprofileflag.php +++ b/plugins/UserFlag/adminprofileflag.php @@ -133,11 +133,16 @@ class FlaggedProfileList extends ProfileList { class FlaggedProfileListItem extends ProfileListItem { var $user = null; + var $r2args = null; function showActions() { $this->user = common_current_user(); + list($action, $this->r2args) = $this->out->returnToArgs(); + + $this->r2args['action'] = $action; + $this->startActions(); if (Event::handle('StartProfileListItemActionElements', array($this))) { $this->showSandboxButton(); @@ -153,11 +158,11 @@ class FlaggedProfileListItem extends ProfileListItem { if ($this->user->hasRight(Right::SANDBOXUSER)) { $this->out->elementStart('li', 'entity_sandbox'); - if ($this->user->isSandboxed()) { - $usf = new UnSandboxForm($this->out, $this->profile); + if ($this->profile->isSandboxed()) { + $usf = new UnSandboxForm($this->out, $this->profile, $this->r2args); $usf->show(); } else { - $sf = new SandboxForm($this->out, $this->profile); + $sf = new SandboxForm($this->out, $this->profile, $this->r2args); $sf->show(); } $this->out->elementEnd('li'); @@ -168,11 +173,11 @@ class FlaggedProfileListItem extends ProfileListItem { if ($this->user->hasRight(Right::SILENCEUSER)) { $this->out->elementStart('li', 'entity_silence'); - if ($this->user->isSilenced()) { - $usf = new UnSilenceForm($this->out, $this->profile); + if ($this->profile->isSilenced()) { + $usf = new UnSilenceForm($this->out, $this->profile, $this->r2args); $usf->show(); } else { - $sf = new SilenceForm($this->out, $this->profile); + $sf = new SilenceForm($this->out, $this->profile, $this->r2args); $sf->show(); } $this->out->elementEnd('li'); @@ -184,7 +189,7 @@ class FlaggedProfileListItem extends ProfileListItem if ($this->user->hasRight(Right::DELETEUSER)) { $this->out->elementStart('li', 'entity_delete'); - $df = new DeleteUserForm($this->out, $this->profile); + $df = new DeleteUserForm($this->out, $this->profile, $this->r2args); $df->show(); $this->out->elementEnd('li'); } -- cgit v1.2.3-54-g00ecf From 55d00a3a907fa275a0df580a82cea20da95889c6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Nov 2009 18:24:59 +0100 Subject: make profile flag actions work like other profile actions --- plugins/UserFlag/flagprofile.php | 94 +++++++---------------------------- plugins/UserFlag/flagprofileform.php | 96 +++++------------------------------- 2 files changed, 31 insertions(+), 159 deletions(-) (limited to 'plugins') diff --git a/plugins/UserFlag/flagprofile.php b/plugins/UserFlag/flagprofile.php index 77c86b233..8ff2f1f72 100644 --- a/plugins/UserFlag/flagprofile.php +++ b/plugins/UserFlag/flagprofile.php @@ -41,11 +41,8 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class FlagprofileAction extends Action +class FlagprofileAction extends ProfileFormAction { - var $profile = null; - var $flag = null; - /** * Take arguments for running * @@ -56,34 +53,14 @@ class FlagprofileAction extends Action function prepare($args) { - parent::prepare($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - throw new ClientException(_('Action only accepts POST')); - } - - if (!common_logged_in()) { - $this->clientError(_('Not logged in.')); - return false; - } - - $id = $this->trimmed('flagprofileto'); - - if (!$id) { - $this->clientError(_('No profile specified.')); - return false; - } - - $this->profile = Profile::staticGet('id', $id); - - if (empty($this->profile)) { - $this->clientError(_('No profile with that ID.')); + if (!parent::prepare($args)) { return false; } $user = common_current_user(); assert(!empty($user)); // checked above + assert(!empty($this->profile)); // checked above if (User_flag_profile::exists($this->profile->id, $user->id)) @@ -96,46 +73,12 @@ class FlagprofileAction extends Action } /** - * Handle request - * - * @param array $args $_REQUEST args; handled in prepare() + * Handle POST * * @return void */ - function handle($args) - { - parent::handle($args); - - $this->flagProfile(); - - if ($this->boolean('ajax')) { - header('Content-Type: text/xml;charset=utf-8'); - $this->xw->startDocument('1.0', 'UTF-8'); - $this->elementStart('html'); - $this->elementStart('head'); - $this->element('title', null, _('Flagged for review')); - $this->elementEnd('head'); - $this->elementStart('body'); - $this->element('p', 'flagged', _('Flagged')); - $this->elementEnd('body'); - $this->elementEnd('html'); - } else { - $this->returnTo(); - } - } - - function title() { - return _('Flag profile'); - } - - /** - * save the profile flag - * - * @return void - */ - - function flagProfile() + function handlePost() { $user = common_current_user(); @@ -149,25 +92,24 @@ class FlagprofileAction extends Action $ufp->created = common_sql_now(); if (!$ufp->insert()) { - throw new ServerException(sprintf(_("Couldn't flag profile '%s' with flag '%s'."), - $this->profile->nickname, $this->flag)); + throw new ServerException(sprintf(_("Couldn't flag profile '%s' for review."), + $this->profile->nickname)); } $ufp->free(); } - function returnTo() - { - // Now, gotta figure where we go back to - foreach ($this->args as $k => $v) { - if ($k == 'returnto-action') { - $action = $v; - } elseif (substr($k, 0, 9) == 'returnto-') { - $args[substr($k, 9)] = $v; - } - } - - common_redirect(common_local_url($action, $args), 303); + function ajaxResults() { + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + $this->element('title', null, _('Flagged for review')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->element('p', 'flagged', _('Flagged')); + $this->elementEnd('body'); + $this->elementEnd('html'); } } diff --git a/plugins/UserFlag/flagprofileform.php b/plugins/UserFlag/flagprofileform.php index a8396e2d5..262dad4a7 100644 --- a/plugins/UserFlag/flagprofileform.php +++ b/plugins/UserFlag/flagprofileform.php @@ -45,108 +45,38 @@ require_once INSTALLDIR.'/lib/form.php'; * @link http://status.net/ */ -class FlagProfileForm extends Form +class FlagProfileForm extends ProfileActionForm { /** - * Profile of profile to flag - */ - - var $profile = null; - - /** - * Return-to args - */ - - var $args = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Profile $profile profile of user to flag - * @param array $args return-to args - */ - - function __construct($out=null, $profile=null, $args=null) - { - parent::__construct($out); - - $this->profile = $profile; - $this->args = $args; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - - function id() - { - return 'flagprofile-' . $this->profile->id; - } - - /** - * class of the form + * Action this form provides * - * @return string class of the form + * @return string Name of the action, lowercased. */ - function formClass() + function target() { - return 'form_entity_flag'; + return 'flagprofile'; } /** - * Action of the form + * Title of the form * - * @return string URL of the action + * @return string Title of the form, internationalized */ - function action() - { - return common_local_url('flagprofile'); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() + function title() { - $this->out->element('legend', null, _('Flag profile for review')); - } - - /** - * Data elements of the form - * - * @return void - */ - - function formData() - { - // TODO: let the user choose a flag - - $this->out->hidden('flagprofileto-' . $this->profile->id, - $this->profile->id, - 'flagprofileto'); - - if ($this->args) { - foreach ($this->args as $k => $v) { - $this->out->hidden('returnto-' . $k, $v); - } - } + return _('Flag'); } /** - * Action elements + * Description of the form * - * @return void + * @return string description of the form, internationalized */ - function formActions() + function description() { - $this->out->submit('submit', _('Flag'), 'submit', null, _('Flag profile for review')); + return _('Flag profile for review'); } } -- cgit v1.2.3-54-g00ecf From 02cc7af1b6a6f8c460550ad0f884bf5e7a18d176 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 16 Nov 2009 18:29:13 +0100 Subject: try to return to the correct page from FlagProfileForm --- plugins/UserFlag/UserFlagPlugin.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 6410ee1ce..fd0c51f95 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -122,7 +122,11 @@ class UserFlagPlugin extends Plugin if (!empty($user)) { - $form = new FlagProfileForm($item->action, $item->profile); + list($action, $args) = $item->action->returnToArgs(); + + $args['action'] = $action; + + $form = new FlagProfileForm($item->action, $item->profile, $args); $form->show(); } -- cgit v1.2.3-54-g00ecf From a373d07ae00b878f47970f2e4a7d86c6ec3a65cf Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 16 Nov 2009 15:24:25 -0500 Subject: Allow plugin DB_DataObject classes to not have to use the .ini file by overriding keys(), table(), and sequenceKey() for them --- classes/Plugin_DataObject.php | 195 ++++++++++++++++++++++++ classes/statusnet.ini | 31 ---- lib/schema.php | 41 ++++- plugins/Authentication/AuthenticationPlugin.php | 11 +- plugins/Authentication/User_username.php | 22 ++- plugins/OpenID/OpenIDPlugin.php | 23 +-- plugins/OpenID/User_openid.php | 22 ++- plugins/OpenID/User_openid_trustroot.php | 20 ++- plugins/UserFlag/UserFlagPlugin.php | 11 +- plugins/UserFlag/User_flag_profile.php | 21 ++- 10 files changed, 318 insertions(+), 79 deletions(-) create mode 100644 classes/Plugin_DataObject.php (limited to 'plugins') diff --git a/classes/Plugin_DataObject.php b/classes/Plugin_DataObject.php new file mode 100644 index 000000000..d5cecf0f7 --- /dev/null +++ b/classes/Plugin_DataObject.php @@ -0,0 +1,195 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +abstract class Plugin_DataObject extends Memcached_DataObject +{ + function table() { + static $table = null; + if($table == null) { + $table = array(); + $DB = $this->getDatabaseConnection(); + $dbtype = $DB->phptype; + $tableDef = $this->tableDef(); + foreach($tableDef->columns as $columnDef){ + switch(strtoupper($columnDef->type)) { + /*shamelessly copied from DB_DataObject_Generator*/ + case 'INT': + case 'INT2': // postgres + case 'INT4': // postgres + case 'INT8': // postgres + case 'SERIAL4': // postgres + case 'SERIAL8': // postgres + case 'INTEGER': + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'BIGINT': + $type = DB_DATAOBJECT_INT; + if ($columnDef->size == 1) { + $type += DB_DATAOBJECT_BOOL; + } + break; + + case 'REAL': + case 'DOUBLE': + case 'DOUBLE PRECISION': // double precision (firebird) + case 'FLOAT': + case 'FLOAT4': // real (postgres) + case 'FLOAT8': // double precision (postgres) + case 'DECIMAL': + case 'MONEY': // mssql and maybe others + case 'NUMERIC': + case 'NUMBER': // oci8 + $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY... + break; + + case 'YEAR': + $type = DB_DATAOBJECT_INT; + break; + + case 'BIT': + case 'BOOL': + case 'BOOLEAN': + + $type = DB_DATAOBJECT_BOOL; + // postgres needs to quote '0' + if ($dbtype == 'pgsql') { + $type += DB_DATAOBJECT_STR; + } + break; + + case 'STRING': + case 'CHAR': + case 'VARCHAR': + case 'VARCHAR2': + case 'TINYTEXT': + + case 'ENUM': + case 'SET': // not really but oh well + + case 'POINT': // mysql geometry stuff - not really string - but will do.. + + case 'TIMESTAMPTZ': // postgres + case 'BPCHAR': // postgres + case 'INTERVAL': // postgres (eg. '12 days') + + case 'CIDR': // postgres IP net spec + case 'INET': // postgres IP + case 'MACADDR': // postgress network Mac address. + + case 'INTEGER[]': // postgres type + case 'BOOLEAN[]': // postgres type + + $type = DB_DATAOBJECT_STR; + break; + + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT; + break; + + + case 'DATE': + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE; + break; + + case 'TIME': + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME; + break; + + + case 'DATETIME': + + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; + break; + + case 'TIMESTAMP': // do other databases use this??? + + $type = ($dbtype == 'mysql') ? + DB_DATAOBJECT_MYSQLTIMESTAMP : + DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; + break; + + + case 'BLOB': /// these should really be ignored!!!??? + case 'TINYBLOB': + case 'MEDIUMBLOB': + case 'LONGBLOB': + + case 'CLOB': // oracle character lob support + + case 'BYTEA': // postgres blob support.. + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB; + break; + + default: + throw new Exception("Cannot handle datatype: $columnDef->type"); + } + if(! $columnDef->nullable) { + $type+=DB_DATAOBJECT_NOTNULL; + } + $table[$columnDef->name]=$type; + } + } + return $table; + } + + function keys() { + static $keys = null; + if($keys == null) { + $keys = array(); + $tableDef = $this->tableDef(); + foreach($tableDef->columns as $columnDef){ + if($columnDef->key != null){ + $keys[] = $columnDef->name; + } + } + } + return $keys; + } + + function sequenceKey() { + static $sequenceKey = null; + if($sequenceKey == null) { + $sequenceKey = array(false,false); + $tableDef = $this->tableDef(); + foreach($tableDef->columns as $columnDef){ + if($columnDef->key == 'PRI' && $columnDef->auto_increment){ + $sequenceKey=array($columnDef->name,true); + } + } + } + return $sequenceKey; + } + + /** + * Get the TableDef object that represents the table backing this class + * Ideally, this function would a static function, but PHP doesn't allow + * abstract static functions + * @return TableDef TableDef instance + */ + abstract function tableDef(); +} + diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 19ab7bf97..8572ea8ac 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -526,27 +526,6 @@ modified = 384 [user_group__keys] id = N -[user_openid] -canonical = 130 -display = 130 -user_id = 129 -created = 142 -modified = 384 - -[user_openid__keys] -canonical = K -display = U - -[user_openid_trustroot] -trustroot = 130 -user_id = 129 -created = 142 -modified = 384 - -[user_openid__keys] -trustroot = K -user_id = K - [user_role] user_id = 129 role = 130 @@ -566,13 +545,3 @@ modified = 384 user_id = K token = K -[user_username] -user_id = 129 -provider_name = 130 -username = 130 -created = 142 -modified = 384 - -[user_username__keys] -provider_name = K -username = K diff --git a/lib/schema.php b/lib/schema.php index 1e0c1f3e9..560884d9f 100644 --- a/lib/schema.php +++ b/lib/schema.php @@ -372,6 +372,26 @@ class Schema return true; } + /** + * Ensures that the table that backs a given + * Plugin_DataObject class exists. + * + * If the table does not yet exist, it will + * create the table. If it does exist, it will + * alter the table to match the column definitions. + * + * @param Plugin_DataObject $dataObjectClass + * + * @return boolean success flag + */ + + public function ensureDataObject($dataObjectClass) + { + $obj = new $dataObjectClass(); + $tableDef = $obj->tableDef(); + return $this->ensureTable($tableDef->name,$tableDef->columns); + } + /** * Ensures that a table exists with the given * name and the given column definitions. @@ -544,6 +564,19 @@ class TableDef public $name; /** array of ColumnDef objects for the columns. */ public $columns; + + /** + * Constructor. + * + * @param string $name name of the table + * @param array $columns columns in the table + */ + + function __construct($name=null,$columns=null) + { + $this->name = $name; + $this->columns = $columns; + } } /** @@ -576,6 +609,8 @@ class ColumnDef /** 'extra' stuff. Returned by MySQL, largely * unused. */ public $extra; + /** auto increment this field if no value is specific for it during an insert **/ + public $auto_increment; /** * Constructor. @@ -591,7 +626,7 @@ class ColumnDef function __construct($name=null, $type=null, $size=null, $nullable=true, $key=null, $default=null, - $extra=null) + $extra=null, $auto_increment=false) { $this->name = strtolower($name); $this->type = strtolower($type); @@ -600,6 +635,7 @@ class ColumnDef $this->key = $key; $this->default = $default; $this->extra = $extra; + $this->auto_increment = $auto_increment; } /** @@ -617,7 +653,8 @@ class ColumnDef $this->_typeMatch($other) && $this->_defaultMatch($other) && $this->_nullMatch($other) && - $this->key == $other->key); + $this->key == $other->key && + $this->auto_increment == $other->auto_increment); } /** diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index a76848b04..a80da901c 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -204,16 +204,7 @@ abstract class AuthenticationPlugin extends Plugin function onCheckSchema() { $schema = Schema::get(); - $schema->ensureTable('user_username', - array(new ColumnDef('provider_name', 'varchar', - '255', false, 'PRI'), - new ColumnDef('username', 'varchar', - '255', false, 'PRI'), - new ColumnDef('user_id', 'integer', - null, false), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'timestamp'))); + $schema->ensureDataObject(User_username); return true; } diff --git a/plugins/Authentication/User_username.php b/plugins/Authentication/User_username.php index f30f60d83..6826f2681 100644 --- a/plugins/Authentication/User_username.php +++ b/plugins/Authentication/User_username.php @@ -2,9 +2,9 @@ /** * Table Definition for user_username */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; -class User_username extends Memcached_DataObject +class User_username extends Plugin_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -43,4 +43,22 @@ class User_username extends Memcached_DataObject return false; } } + + /** + * Get the TableDef object that represents the table backing this class + * @return TableDef TableDef instance + */ + function tableDef() + { + return new TableDef($this->__table, + array(new ColumnDef('provider_name', 'varchar', + '255', false, 'PRI'), + new ColumnDef('username', 'varchar', + '255', false, 'PRI'), + new ColumnDef('user_id', 'integer', + null, false), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); + } } diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 55c0eadaf..88e23ea3e 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -156,6 +156,9 @@ class OpenIDPlugin extends Plugin case 'User_openid': require_once(INSTALLDIR.'/plugins/OpenID/User_openid.php'); return false; + case 'User_openid_trustroot': + require_once(INSTALLDIR.'/plugins/OpenID/User_openid_trustroot.php'); + return false; default: return true; } @@ -278,24 +281,8 @@ class OpenIDPlugin extends Plugin 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'))); - $schema->ensureTable('user_openid_trustroot', - array(new ColumnDef('trustroot', 'varchar', - '255', false, 'PRI'), - new ColumnDef('user_id', 'integer', - null, false, 'PRI'), - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'timestamp'))); + $schema->ensureDataObject(User_openid); + $schema->ensureDataObject(User_openid_trustroot); return true; } diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php index 338e0f6e9..c3624118e 100644 --- a/plugins/OpenID/User_openid.php +++ b/plugins/OpenID/User_openid.php @@ -2,9 +2,9 @@ /** * Table Definition for user_openid */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; -class User_openid extends Memcached_DataObject +class User_openid extends Plugin_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -33,4 +33,22 @@ class User_openid extends Memcached_DataObject return ($cnt > 0); } + + /** + * Get the TableDef object that represents the table backing this class + * @return TableDef TableDef instance + */ + function tableDef() + { + return new TableDef($this->__table, + 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'))); + } } diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php index 4654b72df..b208dddfd 100644 --- a/plugins/OpenID/User_openid_trustroot.php +++ b/plugins/OpenID/User_openid_trustroot.php @@ -2,9 +2,9 @@ /** * Table Definition for user_openid_trustroot */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; -class User_openid_trustroot extends Memcached_DataObject +class User_openid_trustroot extends Plugin_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -26,4 +26,20 @@ class User_openid_trustroot extends Memcached_DataObject { return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv); } + + /** + * Get the TableDef object that represents the table backing this class + * @return TableDef TableDef instance + */ + function tableDef() + { + return new TableDef($this->__table, + array(new ColumnDef('trustroot', 'varchar', + '255', false, 'PRI'), + new ColumnDef('user_id', 'integer', + null, false, 'PRI'), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); + } } diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 6410ee1ce..df7eac7a2 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -48,16 +48,7 @@ class UserFlagPlugin extends Plugin $schema = Schema::get(); // For storing user-submitted flags on profiles - - $schema->ensureTable('user_flag_profile', - array(new ColumnDef('profile_id', 'integer', null, - false, 'PRI'), - new ColumnDef('user_id', 'integer', null, - false, 'PRI'), - new ColumnDef('created', 'datetime', null, - false, 'MUL'), - new ColumnDef('cleared', 'datetime', null, - true, 'MUL'))); + $schema->ensureDataObject(User_flag_profile); return true; } diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php index 30bd4ae68..2fb27912d 100644 --- a/plugins/UserFlag/User_flag_profile.php +++ b/plugins/UserFlag/User_flag_profile.php @@ -21,9 +21,9 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; +require_once INSTALLDIR.'/classes/Plugin_DataObject.php'; -class User_flag_profile extends Memcached_DataObject +class User_flag_profile extends Plugin_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -65,4 +65,21 @@ class User_flag_profile extends Memcached_DataObject return !empty($ufp); } + + /** + * Get the TableDef object that represents the table backing this class + * @return TableDef TableDef instance + */ + function tableDef() + { + return new TableDef($this->__table, + array(new ColumnDef('profile_id', 'integer', null, + false, 'PRI'), + new ColumnDef('user_id', 'integer', null, + false, 'PRI'), + new ColumnDef('created', 'datetime', null, + false, 'MUL'), + new ColumnDef('cleared', 'datetime', null, + true, 'MUL'))); + } } -- cgit v1.2.3-54-g00ecf From 6f9b90921188f477f8b8224c12d6ea7e8b3cc1d3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 16 Nov 2009 15:36:30 -0800 Subject: Fix for PHP spewing notices from commit a373d07ae00b878f47970f2e4a7d86c6ec3a65cf Please test with error_reporting set to E_ALL! Classnames and function names aren't first-class objects in PHP and need to be referenced as strings here. :( --- plugins/Authentication/AuthenticationPlugin.php | 2 +- plugins/OpenID/OpenIDPlugin.php | 4 ++-- plugins/UserFlag/UserFlagPlugin.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php index a80da901c..1b9084187 100644 --- a/plugins/Authentication/AuthenticationPlugin.php +++ b/plugins/Authentication/AuthenticationPlugin.php @@ -204,7 +204,7 @@ abstract class AuthenticationPlugin extends Plugin function onCheckSchema() { $schema = Schema::get(); - $schema->ensureDataObject(User_username); + $schema->ensureDataObject('User_username'); return true; } diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 88e23ea3e..6dd8a3f5a 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -281,8 +281,8 @@ class OpenIDPlugin extends Plugin function onCheckSchema() { $schema = Schema::get(); - $schema->ensureDataObject(User_openid); - $schema->ensureDataObject(User_openid_trustroot); + $schema->ensureDataObject('User_openid'); + $schema->ensureDataObject('User_openid_trustroot'); return true; } diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index df7eac7a2..b4d48c74b 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -48,7 +48,7 @@ class UserFlagPlugin extends Plugin $schema = Schema::get(); // For storing user-submitted flags on profiles - $schema->ensureDataObject(User_flag_profile); + $schema->ensureDataObject('User_flag_profile'); return true; } -- cgit v1.2.3-54-g00ecf From 8df388ce1276d6c4c479ba796f4e45213e428f85 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 17 Nov 2009 16:45:51 +0000 Subject: Added li for entity_flag --- plugins/UserFlag/UserFlagPlugin.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'plugins') diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php index 4d8671c72..c276c4b9f 100644 --- a/plugins/UserFlag/UserFlagPlugin.php +++ b/plugins/UserFlag/UserFlagPlugin.php @@ -119,7 +119,9 @@ class UserFlagPlugin extends Plugin $form = new FlagProfileForm($item->action, $item->profile, $args); + $item->action->elementStart('li', 'entity_flag'); $form->show(); + $item->action->elementEnd('li'); } return true; -- cgit v1.2.3-54-g00ecf From 4ff2d37b10977f77fe3e627f93176657a45270bb Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Tue, 17 Nov 2009 13:00:45 -0500 Subject: Reformatted for 80 character width, and clarified the username/nickname attribute difference --- plugins/LdapAuthentication/README | 65 ++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 21 deletions(-) (limited to 'plugins') diff --git a/plugins/LdapAuthentication/README b/plugins/LdapAuthentication/README index b10a1eb93..dc3f4ba88 100644 --- a/plugins/LdapAuthentication/README +++ b/plugins/LdapAuthentication/README @@ -1,42 +1,63 @@ -The LDAP Authentication plugin allows for StatusNet to handle authentication through LDAP. +The LDAP Authentication plugin allows for StatusNet to handle authentication +through LDAP. Installation ============ -add "addPlugin('ldapAuthentication', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php +add "addPlugin('ldapAuthentication', + array('setting'=>'value', 'setting2'=>'value2', ...);" +to the bottom of your config.php Settings ======== provider_name*: a unique name for this authentication provider. -authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check any other plugins or the internal password database). -autoregistration (false): Set to true if users should be automatically created when they attempt to login. -email_changeable (true): Are users allowed to change their email address? (true or false) -password_changeable (true): Are users allowed to change their passwords? (true or false) - -host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right.. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -port: Port on the server. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -version: LDAP version. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -starttls: TLS is started after connecting. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -binddn: The distinguished name to bind as (username). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -bindpw: Password for the binddn. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -basedn*: LDAP base name (root directory). See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +authoritative (false): Set to true if LDAP's responses are authoritative + (if authorative and LDAP fails, no other password checking will be done). +autoregistration (false): Set to true if users should be automatically created + when they attempt to login. +email_changeable (true): Are users allowed to change their email address? + (true or false) +password_changeable (true): Are users allowed to change their passwords? + (true or false) + +host*: LDAP server name to connect to. You can provide several hosts in an + array in which case the hosts are tried from left to right. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +port: Port on the server. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +version: LDAP version. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +starttls: TLS is started after connecting. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +binddn: The distinguished name to bind as (username). + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +bindpw: Password for the binddn. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +basedn*: LDAP base name (root directory). + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php options: See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -filter: Default search filter. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +filter: Default search filter. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php +scope: Default search scope. + See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php -attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name - username* - nickname* +attributes: an array that relates StatusNet user attributes to LDAP ones + username*: LDAP attribute value entered when authenticating to StatusNet + nickname*: LDAP attribute value shown as the user's nickname email fullname homepage location - + * required default values are in (parenthesis) +For most LDAP installations, the "nickname" and "username" attributes should + be the same. + Example ======= -Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. +Here's an example of an LDAP plugin configuration that connects to + Microsoft Active Directory. addPlugin('ldapAuthentication', array( 'provider_name'=>'Example', @@ -47,7 +68,9 @@ addPlugin('ldapAuthentication', array( 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', 'host'=>array('server1', 'server2'), 'attributes'=>array( + 'username'=>'sAMAccountName', 'nickname'=>'sAMAccountName', 'email'=>'mail', 'fullname'=>'displayName') )); + -- cgit v1.2.3-54-g00ecf From 88ff0eefb411e05e3d622861d90760c12bbba9f0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 17 Nov 2009 16:56:43 -0800 Subject: Renaming GeonamesPlugin::NAMESPACE to GeonamesPlugin::LOCATION_NS to avoid parse errors; 'namespace' is a reserved keyword in PHP 5.3 and later. --- plugins/GeonamesPlugin.php | 14 +++++++------- tests/LocationTest.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index e18957c36..1d7381a80 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -49,7 +49,7 @@ if (!defined('STATUSNET')) { class GeonamesPlugin extends Plugin { - const NAMESPACE = 1; + const LOCATION_NS = 1; /** * convert a name into a Location object @@ -85,7 +85,7 @@ class GeonamesPlugin extends Plugin $location->lon = $n->lng; $location->names[$language] = $n->name; $location->location_id = $n->geonameId; - $location->location_ns = self::NAMESPACE; + $location->location_ns = self::LOCATION_NS; // handled, don't continue processing! return false; @@ -109,7 +109,7 @@ class GeonamesPlugin extends Plugin function onLocationFromId($id, $ns, $language, &$location) { - if ($ns != self::NAMESPACE) { + if ($ns != self::LOCATION_NS) { // It's not one of our IDs... keep processing return true; } @@ -144,7 +144,7 @@ class GeonamesPlugin extends Plugin $location = new Location(); $location->location_id = $last->geonameId; - $location->location_ns = self::NAMESPACE; + $location->location_ns = self::LOCATION_NS; $location->lat = $last->lat; $location->lon = $last->lng; $location->names[$language] = implode(', ', array_reverse($parts)); @@ -205,7 +205,7 @@ class GeonamesPlugin extends Plugin } $location->location_id = $n->geonameId; - $location->location_ns = self::NAMESPACE; + $location->location_ns = self::LOCATION_NS; $location->lat = $lat; $location->lon = $lon; @@ -237,7 +237,7 @@ class GeonamesPlugin extends Plugin function onLocationNameLanguage($location, $language, &$name) { - if ($location->location_ns != self::NAMESPACE) { + if ($location->location_ns != self::LOCATION_NS) { // It's not one of our IDs... keep processing return true; } @@ -292,7 +292,7 @@ class GeonamesPlugin extends Plugin function onLocationUrl($location, &$url) { - if ($location->location_ns != self::NAMESPACE) { + if ($location->location_ns != self::LOCATION_NS) { // It's not one of our IDs... keep processing return true; } diff --git a/tests/LocationTest.php b/tests/LocationTest.php index 62849eb9f..1badecb5d 100644 --- a/tests/LocationTest.php +++ b/tests/LocationTest.php @@ -48,8 +48,8 @@ class LocationTest extends PHPUnit_Framework_TestCase static public function locationIds() { - return array(array(6077243, GeonamesPlugin::NAMESPACE, 'en', null), - array(5391959, GeonamesPlugin::NAMESPACE, 'en', null)); + return array(array(6077243, GeonamesPlugin::LOCATION_NS, 'en', null), + array(5391959, GeonamesPlugin::LOCATION_NS, 'en', null)); } /** -- cgit v1.2.3-54-g00ecf From 7dfce35bea3e9476358048083ed469b2674b41a4 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Tue, 17 Nov 2009 23:22:19 -0500 Subject: (Theoretically) allow users to change their passwords. I cannot test this... but I hope it works :-) --- .../LdapAuthenticationPlugin.php | 164 ++++++++++++++++++++- plugins/LdapAuthentication/README | 8 +- 2 files changed, 163 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php index 865154730..ad5dd3a02 100644 --- a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php +++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php @@ -46,6 +46,7 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin public $options=null; public $filter=null; public $scope=null; + public $password_encoding=null; public $attributes=array(); function onInitializePlugin(){ @@ -68,10 +69,6 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin function checkPassword($username, $password) { - $ldap = $this->ldap_get_connection(); - if(!$ldap){ - return false; - } $entry = $this->ldap_get_user($username); if(!$entry){ return false; @@ -109,8 +106,38 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin function changePassword($username,$oldpassword,$newpassword) { - //TODO implement this - throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); + if(! isset($this->attributes['password']) || !isset($this->password_encoding)){ + //throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); + return false; + } + $entry = $this->ldap_get_user($username); + if(!$entry){ + return false; + }else{ + $config = $this->ldap_get_config(); + $config['binddn']=$entry->dn(); + $config['bindpw']=$oldpassword; + if($ldap = $this->ldap_get_connection($config)){ + $entry = $this->ldap_get_user($username,array(),$ldap); + + $newCryptedPassword = $this->hashPassword($newpassword, $this->password_encoding); + if ($newCryptedPassword===false) { + return false; + } + if($this->password_encoding=='ad') { + //TODO I believe this code will work once this bug is fixed: http://pear.php.net/bugs/bug.php?id=16796 + $oldCryptedPassword = $this->hashPassword($oldpassword, $this->password_encoding); + $entry->delete( array($this->attributes['password'] => $oldCryptedPassword )); + } + $entry->replace( array($this->attributes['password'] => $newCryptedPassword ), true); + if( Net_LDAP2::isError($entry->upate()) ) { + return false; + } + return true; + }else{ + return false; + } + } return false; } @@ -153,8 +180,10 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin * $param array $attributes LDAP attributes to retrieve * @return string DN */ - function ldap_get_user($username,$attributes=array()){ - $ldap = $this->ldap_get_connection(); + function ldap_get_user($username,$attributes=array(),$ldap=null){ + if($ldap==null) { + $ldap = $this->ldap_get_connection(); + } $filter = Net_LDAP2_Filter::create($this->attributes['username'], 'equals', $username); $options = array( 'scope' => 'sub', @@ -177,4 +206,123 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin return false; } } + + /** + * Code originaly from the phpLDAPadmin development team + * http://phpldapadmin.sourceforge.net/ + * + * Hashes a password and returns the hash based on the specified enc_type. + * + * @param string $passwordClear The password to hash in clear text. + * @param string $encodageType Standard LDAP encryption type which must be one of + * crypt, ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear. + * @return string The hashed password. + * + */ + + function hashPassword( $passwordClear, $encodageType ) + { + $encodageType = strtolower( $encodageType ); + switch( $encodageType ) { + case 'crypt': + $cryptedPassword = '{CRYPT}' . crypt($passwordClear,$this->randomSalt(2)); + break; + + case 'ext_des': + // extended des crypt. see OpenBSD crypt man page. + if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {return FALSE;} //Your system crypt library does not support extended DES encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear, '_' . $this->randomSalt(8) ); + break; + + case 'md5crypt': + if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {return FALSE;} //Your system crypt library does not support md5crypt encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$1$' . $this->randomSalt(9) ); + break; + + case 'blowfish': + if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {return FALSE;} //Your system crypt library does not support blowfish encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$2a$12$' . $this->randomSalt(13) ); // hardcoded to second blowfish version and set number of rounds + break; + + case 'md5': + $cryptedPassword = '{MD5}' . base64_encode( pack( 'H*' , md5( $passwordClear) ) ); + break; + + case 'sha': + if( function_exists('sha1') ) { + // use php 4.3.0+ sha1 function, if it is available. + $cryptedPassword = '{SHA}' . base64_encode( pack( 'H*' , sha1( $passwordClear) ) ); + } elseif( function_exists( 'mhash' ) ) { + $cryptedPassword = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $passwordClear) ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'ssha': + if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) { + mt_srand( (double) microtime() * 1000000 ); + $salt = mhash_keygen_s2k( MHASH_SHA1, $passwordClear, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 ); + $cryptedPassword = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $passwordClear.$salt ).$salt ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'smd5': + if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) { + mt_srand( (double) microtime() * 1000000 ); + $salt = mhash_keygen_s2k( MHASH_MD5, $passwordClear, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 ); + $cryptedPassword = "{SMD5}".base64_encode( mhash( MHASH_MD5, $passwordClear.$salt ).$salt ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'ad': + $cryptedPassword = ''; + $passwordClear = "\"" . $passwordClear . "\""; + $len = strlen($passwordClear); + for ($i = 0; $i < $len; $i++) { + $cryptedPassword .= "{$passwordClear{$i}}\000"; + } + + case 'clear': + default: + $cryptedPassword = $passwordClear; + } + + return $cryptedPassword; + } + + /** + * Code originaly from the phpLDAPadmin development team + * http://phpldapadmin.sourceforge.net/ + * + * Used to generate a random salt for crypt-style passwords. Salt strings are used + * to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses + * not only the user's password but also a randomly generated string. The string is + * stored as the first N characters of the hash for reference of hashing algorithms later. + * + * --- added 20021125 by bayu irawan --- + * --- ammended 20030625 by S C Rigler --- + * + * @param int $length The length of the salt string to generate. + * @return string The generated salt string. + */ + + function randomSalt( $length ) + { + $possible = '0123456789'. + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + './'; + $str = ""; + mt_srand((double)microtime() * 1000000); + + while( strlen( $str ) < $length ) + $str .= substr( $possible, ( rand() % strlen( $possible ) ), 1 ); + + return $str; + } } diff --git a/plugins/LdapAuthentication/README b/plugins/LdapAuthentication/README index dc3f4ba88..2226159c2 100644 --- a/plugins/LdapAuthentication/README +++ b/plugins/LdapAuthentication/README @@ -18,6 +18,9 @@ email_changeable (true): Are users allowed to change their email address? (true or false) password_changeable (true): Are users allowed to change their passwords? (true or false) +password_encoding: required if users are to be able to change their passwords + Possible values are: crypt, ext_des, md5crypt, blowfish, md5, sha, ssha, + smd5, ad, clear host*: LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right. @@ -47,6 +50,7 @@ attributes: an array that relates StatusNet user attributes to LDAP ones fullname homepage location + password: required if users are to be able to change their passwords * required default values are in (parenthesis) @@ -67,10 +71,12 @@ addPlugin('ldapAuthentication', array( 'bindpw'=>'password', 'basedn'=>'OU=Users,OU=StatusNet,OU=US,DC=americas,DC=global,DC=loc', 'host'=>array('server1', 'server2'), + 'password_encoding'=>'ad', 'attributes'=>array( 'username'=>'sAMAccountName', 'nickname'=>'sAMAccountName', 'email'=>'mail', - 'fullname'=>'displayName') + 'fullname'=>'displayName', + 'password'=>'unicodePwd') )); -- cgit v1.2.3-54-g00ecf From fc08a5c8803fd79d58126528fa4a593ccfb8d512 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 17 Nov 2009 23:23:13 -0500 Subject: first pass at Mapstraction plugin --- plugins/Mapstraction/MapstractionPlugin.php | 149 ++ plugins/Mapstraction/js/mxn.(provider).core.js | 289 ++++ plugins/Mapstraction/js/mxn.cloudmade.core.js | 357 +++++ plugins/Mapstraction/js/mxn.core.js | 1758 ++++++++++++++++++++++++ plugins/Mapstraction/js/mxn.geocommons.core.js | 233 ++++ plugins/Mapstraction/js/mxn.google.core.js | 519 +++++++ plugins/Mapstraction/js/mxn.google.geocoder.js | 179 +++ plugins/Mapstraction/js/mxn.googlev3.core.js | 443 ++++++ plugins/Mapstraction/js/mxn.js | 505 +++++++ plugins/Mapstraction/js/mxn.microsoft.core.js | 402 ++++++ plugins/Mapstraction/js/mxn.openlayers.core.js | 513 +++++++ plugins/Mapstraction/js/mxn.yahoo.core.js | 391 ++++++ 12 files changed, 5738 insertions(+) create mode 100644 plugins/Mapstraction/MapstractionPlugin.php create mode 100644 plugins/Mapstraction/js/mxn.(provider).core.js create mode 100644 plugins/Mapstraction/js/mxn.cloudmade.core.js create mode 100644 plugins/Mapstraction/js/mxn.core.js create mode 100644 plugins/Mapstraction/js/mxn.geocommons.core.js create mode 100644 plugins/Mapstraction/js/mxn.google.core.js create mode 100644 plugins/Mapstraction/js/mxn.google.geocoder.js create mode 100644 plugins/Mapstraction/js/mxn.googlev3.core.js create mode 100644 plugins/Mapstraction/js/mxn.js create mode 100644 plugins/Mapstraction/js/mxn.microsoft.core.js create mode 100644 plugins/Mapstraction/js/mxn.openlayers.core.js create mode 100644 plugins/Mapstraction/js/mxn.yahoo.core.js (limited to 'plugins') diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php new file mode 100644 index 000000000..9a99d6742 --- /dev/null +++ b/plugins/Mapstraction/MapstractionPlugin.php @@ -0,0 +1,149 @@ +. + * + * @category Action + * @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://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin to provide map visualization of location data + * + * This plugin uses the Mapstraction JavaScript library to + * + * @category Plugin + * @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://status.net/ + * + * @seeAlso Location + */ + +class MapstractionPlugin extends Plugin +{ + /** provider name, one of: + 'cloudmade', 'google', 'microsoft', 'openlayers', 'yahoo' */ + public $provider = 'openlayers'; + /** provider API key (or 'appid'), if required ('google' and 'yahoo' only) */ + public $apikey = null; + + /** + * Hook for new URLs + * + * The way to register new actions from a plugin. + * + * @param Router &$m reference to router + * + * @return boolean event handler return + */ + + function onRouterInitialized(&$m) + { + $m->connect(':nickname/all/map', + array('action' => 'allmap'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/map', + array('action' => 'usermap'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + return true; + } + + /** + * Hook for autoloading classes + * + * This makes sure our classes get autoloaded from our directory + * + * @param string $cls name of class being used + * + * @return boolean event handler return + */ + + function onAutoload($cls) + { + switch ($cls) + { + case 'AllmapAction': + case 'UsermapAction': + include_once INSTALLDIR.'/plugins/Mapstraction/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + default: + return true; + } + } + + /** + * Hook for adding extra JavaScript + * + * This makes sure our scripts get loaded for map-related pages + * + * @param Action $action Action object for the page + * + * @return boolean event handler return + */ + + function onEndShowScripts($action) + { + // These are the ones that have maps on 'em + if (!in_array($action->trimmed('action'), + array('showstream', 'all', 'allmap', 'usermap'))) { + return true; + } + + switch ($this->provider) + { + case 'cloudmade': + $action->script('http://tile.cloudmade.com/wml/0.2/web-maps-lite.js'); + break; + case 'google': + $action->script(sprintf('http://maps.google.com/maps?file=api&v=2&sensor=false&key=%s', + $this->apikey)); + break; + case 'microsoft': + $action->script('http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6'); + break; + case 'openlayers': + // XXX: is this not nice...? + $action->script('http://openlayers.org/api/OpenLayers.js'); + break; + case 'yahoo': + $action->script(sprintf('http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=%s', + $this->apikey)); + break; + case 'geocommons': // don't support this yet + default: + return true; + } + + $action->script(sprintf('%s?(%s)', + common_path('plugins/Mapstraction/js/mxn.js'), + $this->provider)); + + return true; + } +} diff --git a/plugins/Mapstraction/js/mxn.(provider).core.js b/plugins/Mapstraction/js/mxn.(provider).core.js new file mode 100644 index 000000000..cc9752ffd --- /dev/null +++ b/plugins/Mapstraction/js/mxn.(provider).core.js @@ -0,0 +1,289 @@ +mxn.register('{{api_id}}', { + +Mapstraction: { + + init: function(element, api) { + var me = this; + + // TODO: Add provider code + }, + + applyOptions: function(){ + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + resizeTo: function(width, height){ + // TODO: Add provider code + }, + + addControls: function( args ) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + addSmallControls: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + addLargeControls: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + addMapTypeControls: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + setCenterAndZoom: function(point, zoom) { + var map = this.maps[this.api]; + var pt = point.toProprietary(this.api); + + // TODO: Add provider code + }, + + addMarker: function(marker, old) { + var map = this.maps[this.api]; + var pin = marker.toProprietary(this.api); + + // TODO: Add provider code + + return pin; + }, + + removeMarker: function(marker) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + removeAllMarkers: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + declutterMarkers: function(opts) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + addPolyline: function(polyline, old) { + var map = this.maps[this.api]; + var pl = polyline.toProprietary(this.api); + + // TODO: Add provider code + + return pl; + }, + + removePolyline: function(polyline) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + getCenter: function() { + var point; + var map = this.maps[this.api]; + + // TODO: Add provider code + + return point; + }, + + setCenter: function(point, options) { + var map = this.maps[this.api]; + var pt = point.toProprietary(this.api); + if(options && options.pan) { + // TODO: Add provider code + } + else { + // TODO: Add provider code + } + }, + + setZoom: function(zoom) { + var map = this.maps[this.api]; + + // TODO: Add provider code + + }, + + getZoom: function() { + var map = this.maps[this.api]; + var zoom; + + // TODO: Add provider code + + return zoom; + }, + + getZoomLevelForBoundingBox: function( bbox ) { + var map = this.maps[this.api]; + // NE and SW points from the bounding box. + var ne = bbox.getNorthEast(); + var sw = bbox.getSouthWest(); + var zoom; + + // TODO: Add provider code + + return zoom; + }, + + setMapType: function(type) { + var map = this.maps[this.api]; + switch(type) { + case mxn.Mapstraction.ROAD: + // TODO: Add provider code + break; + case mxn.Mapstraction.SATELLITE: + // TODO: Add provider code + break; + case mxn.Mapstraction.HYBRID: + // TODO: Add provider code + break; + default: + // TODO: Add provider code + } + }, + + getMapType: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + + //return mxn.Mapstraction.ROAD; + //return mxn.Mapstraction.SATELLITE; + //return mxn.Mapstraction.HYBRID; + + }, + + getBounds: function () { + var map = this.maps[this.api]; + + // TODO: Add provider code + + //return new mxn.BoundingBox( , , , ); + }, + + setBounds: function(bounds){ + var map = this.maps[this.api]; + var sw = bounds.getSouthWest(); + var ne = bounds.getNorthEast(); + + // TODO: Add provider code + + }, + + addImageOverlay: function(id, src, opacity, west, south, east, north, oContext) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + setImagePosition: function(id, oContext) { + var map = this.maps[this.api]; + var topLeftPoint; var bottomRightPoint; + + // TODO: Add provider code + + //oContext.pixels.top = ...; + //oContext.pixels.left = ...; + //oContext.pixels.bottom = ...; + //oContext.pixels.right = ...; + }, + + addOverlay: function(url, autoCenterAndZoom) { + var map = this.maps[this.api]; + + // TODO: Add provider code + + }, + + addTileLayer: function(tile_url, opacity, copyright_text, min_zoom, max_zoom) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + toggleTileLayer: function(tile_url) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + getPixelRatio: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + mousePosition: function(element) { + var map = this.maps[this.api]; + + // TODO: Add provider code + } +}, + +LatLonPoint: { + + toProprietary: function() { + // TODO: Add provider code + }, + + fromProprietary: function(googlePoint) { + // TODO: Add provider code + } + +}, + +Marker: { + + toProprietary: function() { + // TODO: Add provider code + }, + + openBubble: function() { + // TODO: Add provider code + }, + + hide: function() { + // TODO: Add provider code + }, + + show: function() { + // TODO: Add provider code + }, + + update: function() { + // TODO: Add provider code + } + +}, + +Polyline: { + + toProprietary: function() { + // TODO: Add provider code + }, + + show: function() { + // TODO: Add provider code + }, + + hide: function() { + // TODO: Add provider code + } + +} + +}); \ No newline at end of file diff --git a/plugins/Mapstraction/js/mxn.cloudmade.core.js b/plugins/Mapstraction/js/mxn.cloudmade.core.js new file mode 100644 index 000000000..b6ee70b8f --- /dev/null +++ b/plugins/Mapstraction/js/mxn.cloudmade.core.js @@ -0,0 +1,357 @@ +mxn.register('cloudmade', { + + Mapstraction: { + + init: function(element, api) { + var me = this; + var cloudmade = new CM.Tiles.CloudMade.Web({key: cloudmade_key}); + this.maps[api] = new CM.Map(element, cloudmade); + this.loaded[api] = true; + + CM.Event.addListener(this.maps[api], 'click', function(location,marker) { + if ( marker && marker.mapstraction_marker ) { + marker.mapstraction_marker.click.fire(); + } + else if ( location ) { + me.click.fire({'location': new mxn.LatLonPoint(location.lat(), location.lng())}); + } + + // If the user puts their own Google markers directly on the map + // then there is no location and this event should not fire. + if ( location ) { + me.clickHandler(location.lat(),location.lng(),location,me); + } + }); + }, + + applyOptions: function(){ + var map = this.maps[this.api]; + if(this.options.enableScrollWheelZoom){ + map.enableScrollWheelZoom(); + } + }, + + resizeTo: function(width, height){ + this.maps[this.api].checkResize(); + }, + + addControls: function( args ) { + var map = this.maps[this.api]; + + var c = this.addControlsArgs; + switch (c.zoom) { + case 'large': + this.addLargeControls(); + break; + case 'small': + this.addSmallControls(); + break; + } + + if (c.map_type) { + this.addMapTypeControls(); + } + if (c.scale) { + map.addControl(new CM.ScaleControl()); + this.addControlsArgs.scale = true; + } + }, + + addSmallControls: function() { + var map = this.maps[this.api]; + map.addControl(new CM.SmallMapControl()); + this.addControlsArgs.zoom = 'small'; + }, + + addLargeControls: function() { + var map = this.maps[this.api]; + map.addControl(new CM.LargeMapControl()); + this.addControlsArgs.zoom = 'large'; + }, + + addMapTypeControls: function() { + var map = this.maps[this.api]; + + map.addControl(new CM.TileLayerControl()); + this.addControlsArgs.map_type = true; + }, + + dragging: function(on) { + var map = this.maps[this.api]; + + if (on) { + map.enableDragging(); + } else { + map.disableDragging(); + } + }, + + setCenterAndZoom: function(point, zoom) { + var map = this.maps[this.api]; + var pt = point.toProprietary(this.api); + map.setCenter(pt, zoom); + + }, + + addMarker: function(marker, old) { + var map = this.maps[this.api]; + var pin = marker.toProprietary(this.api); + map.addOverlay(pin); + return pin; + }, + + removeMarker: function(marker) { + var map = this.maps[this.api]; + marker.proprietary_marker.closeInfoWindow(); + map.removeOverlay(marker.proprietary_marker); + }, + + removeAllMarkers: function() { + // Done in mxn.core.js + }, + + declutterMarkers: function(opts) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + addPolyline: function(polyline, old) { + var map = this.maps[this.api]; + var pl = polyline.toProprietary(this.api); + map.addOverlay(pl); + return pl; + }, + + removePolyline: function(polyline) { + var map = this.maps[this.api]; + map.removeOverlay(polyline.proprietary_polyline); + }, + + getCenter: function() { + var map = this.maps[this.api]; + var pt = map.getCenter(); + + return new mxn.LatLonPoint(pt.lat(), pt.lng()); + }, + + setCenter: function(point, options) { + var map = this.maps[this.api]; + var pt = point.toProprietary(this.api); + if(options !== null && options.pan) { map.panTo(pt); } + else { map.setCenter(pt); } + }, + + setZoom: function(zoom) { + var map = this.maps[this.api]; + map.setZoom(zoom); + }, + + getZoom: function() { + var map = this.maps[this.api]; + return map.getZoom(); + }, + + getZoomLevelForBoundingBox: function( bbox ) { + var map = this.maps[this.api]; + // NE and SW points from the bounding box. + var ne = bbox.getNorthEast(); + var sw = bbox.getSouthWest(); + + var zoom = map.getBoundsZoomLevel(new CM.LatLngBounds(sw.toProprietary(this.api), ne.toProprietary(this.api))); + return zoom; + }, + + setMapType: function(type) { + var map = this.maps[this.api]; + + // TODO: Are there any MapTypes for Cloudmade? + + switch(type) { + case mxn.Mapstraction.ROAD: + // TODO: Add provider code + break; + case mxn.Mapstraction.SATELLITE: + // TODO: Add provider code + break; + case mxn.Mapstraction.HYBRID: + // TODO: Add provider code + break; + default: + // TODO: Add provider code + } + }, + + getMapType: function() { + var map = this.maps[this.api]; + + // TODO: Are there any MapTypes for Cloudmade? + + return mxn.Mapstraction.ROAD; + //return mxn.Mapstraction.SATELLITE; + //return mxn.Mapstraction.HYBRID; + + }, + + getBounds: function () { + var map = this.maps[this.api]; + + var box = map.getBounds(); + var sw = box.getSouthWest(); + var ne = box.getNorthEast(); + + return new mxn.BoundingBox(sw.lat(), sw.lng(), ne.lat(), ne.lng()); + }, + + setBounds: function(bounds){ + var map = this.maps[this.api]; + var sw = bounds.getSouthWest(); + var ne = bounds.getNorthEast(); + + map.zoomToBounds(new CM.LatLngBounds(sw.toProprietary(this.api), ne.toProprietary(this.api))); + }, + + addImageOverlay: function(id, src, opacity, west, south, east, north, oContext) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + setImagePosition: function(id, oContext) { + var map = this.maps[this.api]; + var topLeftPoint; var bottomRightPoint; + + // TODO: Add provider code + + }, + + addOverlay: function(url, autoCenterAndZoom) { + var map = this.maps[this.api]; + + // TODO: Add provider code + + }, + + addTileLayer: function(tile_url, opacity, copyright_text, min_zoom, max_zoom) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + toggleTileLayer: function(tile_url) { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + getPixelRatio: function() { + var map = this.maps[this.api]; + + // TODO: Add provider code + }, + + mousePosition: function(element) { + var map = this.maps[this.api]; + + // TODO: Add provider code + } + }, + + LatLonPoint: { + + toProprietary: function() { + var cll = new CM.LatLng(this.lat,this.lon); + return cll; + }, + + fromProprietary: function(point) { + return new mxn.LatLonPoint(point.lat(),point.lng()); + } + + }, + + Marker: { + + toProprietary: function() { + var pt = this.location.toProprietary(this.api); + var options = {}; + + if (this.iconUrl) { + var cicon = new CM.Icon(); + cicon.image = this.iconUrl; + if (this.iconSize) { + cicon.iconSize = new CM.Size(this.iconSize[0], this.iconSize[1]); + if (this.iconAnchor) { + cicon.iconAnchor = new CM.Point(this.iconAnchor[0], this.iconAnchor[1]); + } + } + if (this.iconShadowUrl) { + cicon.shadow = this.iconShadowUrl; + if (this.iconShadowSize) { + cicon.shadowSize = new CM.Size(this.iconShadowSize[0], this.iconShadowSize[1]); + } + } + options.icon = cicon; + } + if (this.labelText) { + options.title = this.labelText; + } + var cmarker = new CM.Marker(pt, options); + + if (this.infoBubble) { + cmarker.bindInfoWindow(this.infoBubble); + } + + + return cmarker; + }, + + openBubble: function() { + var pin = this.proprietary_marker; + pin.openInfoWindow(this.infoBubble); + }, + + hide: function() { + var pin = this.proprietary_marker; + pin.hide(); + }, + + show: function() { + var pin = this.proprietary_marker; + pin.show(); + }, + + update: function() { + // TODO: Add provider code + } + + }, + + Polyline: { + + toProprietary: function() { + var pts = []; + var poly; + + for (var i = 0, length = this.points.length ; i< length; i++){ + pts.push(this.points[i].toProprietary(this.api)); + } + if (this.closed || pts[0].equals(pts[pts.length-1])) { + poly = new CM.Polygon(pts, this.color, this.width, this.opacity, this.fillColor || "#5462E3", this.opacity || "0.3"); + } else { + poly = new CM.Polyline(pts, this.color, this.width, this.opacity); + } + return poly; + }, + + show: function() { + this.proprietary_polyline.show(); + }, + + hide: function() { + this.proprietary_polyline.hide(); + } + + } + +}); diff --git a/plugins/Mapstraction/js/mxn.core.js b/plugins/Mapstraction/js/mxn.core.js new file mode 100644 index 000000000..c75d0969e --- /dev/null +++ b/plugins/Mapstraction/js/mxn.core.js @@ -0,0 +1,1758 @@ +(function(){ + +/** + * @exports mxn.util.$m as $m + */ +var $m = mxn.util.$m; + +/** + * Initialise our provider. This function should only be called + * from within mapstraction code, not exposed as part of the API. + * @private + */ +var init = function() { + this.invoker.go('init', [ this.currentElement, this.api ]); + this.applyOptions(); +}; + +/** + * Mapstraction instantiates a map with some API choice into the HTML element given + * @name mxn.Mapstraction + * @constructor + * @param {String} element The HTML element to replace with a map + * @param {String} api The API to use, one of 'google', 'googlev3', 'yahoo', 'microsoft', 'openstreetmap', 'multimap', 'map24', 'openlayers', 'mapquest'. If omitted, first loaded provider implementation is used. + * @param {Bool} debug optional parameter to turn on debug support - this uses alert panels for unsupported actions + * @exports Mapstraction as mxn.Mapstraction + */ +var Mapstraction = mxn.Mapstraction = function(element, api, debug) { + if (!api){ + api = mxn.util.getAvailableProviders()[0]; + } + this.api = api; + this.maps = {}; + this.currentElement = $m(element); + this.eventListeners = []; + this.markers = []; + this.layers = []; + this.polylines = []; + this.images = []; + this.controls = []; + this.loaded = {}; + this.onload = {}; + this.element = element; + + // option defaults + this.options = { + enableScrollWheelZoom: false, + enableDragging: true + }; + + this.addControlsArgs = {}; + + // set up our invoker for calling API methods + this.invoker = new mxn.Invoker(this, 'Mapstraction', function(){ return this.api; }); + + // Adding our events + mxn.addEvents(this, [ + + /** + * Map has loaded + * @name mxn.Mapstraction#load + * @event + */ + 'load', + + /** + * Map is clicked {location: LatLonPoint} + * @name mxn.Mapstraction#click + * @event + */ + 'click', + + /** + * Map is panned + * @name mxn.Mapstraction#endPan + * @event + */ + 'endPan', + + /** + * Zoom is changed + * @name mxn.Mapstraction#changeZoom + * @event + */ + 'changeZoom', + + /** + * Marker is removed {marker: Marker} + * @name mxn.Mapstraction#markerAdded + * @event + */ + 'markerAdded', + + /** + * Marker is removed {marker: Marker} + * @name mxn.Mapstraction#markerRemoved + * @event + */ + 'markerRemoved', + + /** + * Polyline is added {polyline: Polyline} + * @name mxn.Mapstraction#polylineAdded + * @event + */ + 'polylineAdded', + + /** + * Polyline is removed {polyline: Polyline} + * @name mxn.Mapstraction#polylineRemoved + * @event + */ + 'polylineRemoved' + ]); + + // finally initialize our proper API map + init.apply(this); +}; + +// Map type constants +Mapstraction.ROAD = 1; +Mapstraction.SATELLITE = 2; +Mapstraction.HYBRID = 3; + +// methods that have no implementation in mapstraction core +mxn.addProxyMethods(Mapstraction, [ + /** + * Adds a large map panning control and zoom buttons to the map + * @name mxn.Mapstraction#addLargeControls + * @function + */ + 'addLargeControls', + + /** + * Adds a map type control to the map (streets, aerial imagery etc) + * @name mxn.Mapstraction#addMapTypeControls + * @function + */ + 'addMapTypeControls', + + /** + * Adds a GeoRSS or KML overlay to the map + * some flavors of GeoRSS and KML are not supported by some of the Map providers + * @name mxn.Mapstraction#addOverlay + * @function + * @param {String} url GeoRSS or KML feed URL + * @param {Boolean} autoCenterAndZoom Set true to auto center and zoom after the feed is loaded + */ + 'addOverlay', + + /** + * Adds a small map panning control and zoom buttons to the map + * @name mxn.Mapstraction#addSmallControls + * @function + */ + 'addSmallControls', + + /** + * Applies the current option settings + * @name mxn.Mapstraction#applyOptions + * @function + */ + 'applyOptions', + + /** + * Gets the BoundingBox of the map + * @name mxn.Mapstraction#getBounds + * @function + * @returns {BoundingBox} The bounding box for the current map state + */ + 'getBounds', + + /** + * Gets the central point of the map + * @name mxn.Mapstraction#getCenter + * @function + * @returns {LatLonPoint} The center point of the map + */ + 'getCenter', + + /** + * Gets the imagery type for the map. + * The type can be one of: + * mxn.Mapstraction.ROAD + * mxn.Mapstraction.SATELLITE + * mxn.Mapstraction.HYBRID + * @name mxn.Mapstraction#getMapType + * @function + * @returns {Number} + */ + 'getMapType', + + /** + * Returns a ratio to turn distance into pixels based on current projection + * @name mxn.Mapstraction#getPixelRatio + * @function + * @returns {Float} ratio + */ + 'getPixelRatio', + + /** + * Returns the zoom level of the map + * @name mxn.Mapstraction#getZoom + * @function + * @returns {Integer} The zoom level of the map + */ + 'getZoom', + + /** + * Returns the best zoom level for bounds given + * @name mxn.Mapstraction#getZoomLevelForBoundingBox + * @function + * @param {BoundingBox} bbox The bounds to fit + * @returns {Integer} The closest zoom level that contains the bounding box + */ + 'getZoomLevelForBoundingBox', + + /** + * Displays the coordinates of the cursor in the HTML element + * @name mxn.Mapstraction#mousePosition + * @function + * @param {String} element ID of the HTML element to display the coordinates in + */ + 'mousePosition', + + /** + * Resize the current map to the specified width and height + * (since it is actually on a child div of the mapElement passed + * as argument to the Mapstraction constructor, the resizing of this + * mapElement may have no effect on the size of the actual map) + * @name mxn.Mapstraction#resizeTo + * @function + * @param {Integer} width The width the map should be. + * @param {Integer} height The width the map should be. + */ + 'resizeTo', + + /** + * Sets the map to the appropriate location and zoom for a given BoundingBox + * @name mxn.Mapstraction#setBounds + * @function + * @param {BoundingBox} bounds The bounding box you want the map to show + */ + 'setBounds', + + /** + * setCenter sets the central point of the map + * @name mxn.Mapstraction#setCenter + * @function + * @param {LatLonPoint} point The point at which to center the map + * @param {Object} options Optional parameters + * @param {Boolean} options.pan Whether the map should move to the locations using a pan or just jump straight there + */ + 'setCenter', + + /** + * Centers the map to some place and zoom level + * @name mxn.Mapstraction#setCenterAndZoom + * @function + * @param {LatLonPoint} point Where the center of the map should be + * @param {Integer} zoom The zoom level where 0 is all the way out. + */ + 'setCenterAndZoom', + + /** + * Sets the imagery type for the map + * The type can be one of: + * mxn.Mapstraction.ROAD + * mxn.Mapstraction.SATELLITE + * mxn.Mapstraction.HYBRID + * @name mxn.Mapstraction#setMapType + * @function + * @param {Number} type + */ + 'setMapType', + + /** + * Sets the zoom level for the map + * MS doesn't seem to do zoom=0, and Gg's sat goes closer than it's maps, and MS's sat goes closer than Y!'s + * TODO: Mapstraction.prototype.getZoomLevels or something. + * @name mxn.Mapstraction#setZoom + * @function + * @param {Number} zoom The (native to the map) level zoom the map to. + */ + 'setZoom', + + /** + * Turns a Tile Layer on or off + * @name mxn.Mapstraction#toggleTileLayer + * @function + * @param {tile_url} url of the tile layer that was created. + */ + 'toggleTileLayer' +]); + +/** + * Sets the current options to those specified in oOpts and applies them + * @param {Object} oOpts Hash of options to set + */ +Mapstraction.prototype.setOptions = function(oOpts){ + mxn.util.merge(this.options, oOpts); + this.applyOptions(); +}; + +/** + * Sets an option and applies it. + * @param {String} sOptName Option name + * @param vVal Option value + */ +Mapstraction.prototype.setOption = function(sOptName, vVal){ + this.options[sOptName] = vVal; + this.applyOptions(); +}; + +/** + * Enable scroll wheel zooming + * @deprecated Use setOption instead. + */ +Mapstraction.prototype.enableScrollWheelZoom = function() { + this.setOption('enableScrollWheelZoom', true); +}; + +/** + * Enable/disable dragging of the map + * @param {Boolean} on + * @deprecated Use setOption instead. + */ +Mapstraction.prototype.dragging = function(on) { + this.setOption('enableDragging', on); +}; + +/** + * Change the current api on the fly + * @param {String} api The API to swap to + * @param element + */ +Mapstraction.prototype.swap = function(element,api) { + if (this.api === api) { + return; + } + + var center = this.getCenter(); + var zoom = this.getZoom(); + + this.currentElement.style.visibility = 'hidden'; + this.currentElement.style.display = 'none'; + + this.currentElement = $m(element); + this.currentElement.style.visibility = 'visible'; + this.currentElement.style.display = 'block'; + + this.api = api; + + if (this.maps[this.api] === undefined) { + init.apply(this); + + this.setCenterAndZoom(center,zoom); + + for (var i = 0; i < this.markers.length; i++) { + this.addMarker(this.markers[i], true); + } + + for (var j = 0; j < this.polylines.length; j++) { + this.addPolyline( this.polylines[j], true); + } + } + else { + + //sync the view + this.setCenterAndZoom(center,zoom); + + //TODO synchronize the markers and polylines too + // (any overlays created after api instantiation are not sync'd) + } + + this.addControls(this.addControlsArgs); + +}; + +/** + * Returns the loaded state of a Map Provider + * @param {String} api Optional API to query for. If not specified, returns state of the originally created API + */ +Mapstraction.prototype.isLoaded = function(api){ + if (api === null) { + api = this.api; + } + return this.loaded[api]; +}; + +/** + * Set the debugging on or off - shows alert panels for functions that don't exist in Mapstraction + * @param {Boolean} debug true to turn on debugging, false to turn it off + */ +Mapstraction.prototype.setDebug = function(debug){ + if(debug !== null) { + this.debug = debug; + } + return this.debug; +}; + + +///////////////////////// +// +// Event Handling +// +// FIXME need to consolidate some of these handlers... +// +/////////////////////////// + +// Click handler attached to native API +Mapstraction.prototype.clickHandler = function(lat, lon, me) { + this.callEventListeners('click', { + location: new LatLonPoint(lat, lon) + }); +}; + +// Move and zoom handler attached to native API +Mapstraction.prototype.moveendHandler = function(me) { + this.callEventListeners('moveend', {}); +}; + +/** + * Add a listener for an event. + * @param {String} type Event type to attach listener to + * @param {Function} func Callback function + * @param {Object} caller Callback object + */ +Mapstraction.prototype.addEventListener = function() { + var listener = {}; + listener.event_type = arguments[0]; + listener.callback_function = arguments[1]; + + // added the calling object so we can retain scope of callback function + if(arguments.length == 3) { + listener.back_compat_mode = false; + listener.callback_object = arguments[2]; + } + else { + listener.back_compat_mode = true; + listener.callback_object = null; + } + this.eventListeners.push(listener); +}; + +/** + * Call listeners for a particular event. + * @param {String} sEventType Call listeners of this event type + * @param {Object} oEventArgs Event args object to pass back to the callback + */ +Mapstraction.prototype.callEventListeners = function(sEventType, oEventArgs) { + oEventArgs.source = this; + for(var i = 0; i < this.eventListeners.length; i++) { + var evLi = this.eventListeners[i]; + if(evLi.event_type == sEventType) { + // only two cases for this, click and move + if(evLi.back_compat_mode) { + if(evLi.event_type == 'click') { + evLi.callback_function(oEventArgs.location); + } + else { + evLi.callback_function(); + } + } + else { + var scope = evLi.callback_object || this; + evLi.callback_function.call(scope, oEventArgs); + } + } + } +}; + + +//////////////////// +// +// map manipulation +// +///////////////////// + + +/** + * addControls adds controls to the map. You specify which controls to add in + * the associative array that is the only argument. + * addControls can be called multiple time, with different args, to dynamically change controls. + * + * args = { + * pan: true, + * zoom: 'large' || 'small', + * overview: true, + * scale: true, + * map_type: true, + * } + * @param {array} args Which controls to switch on + */ +Mapstraction.prototype.addControls = function( args ) { + this.addControlsArgs = args; + this.invoker.go('addControls', arguments); +}; + +/** + * Adds a marker pin to the map + * @param {Marker} marker The marker to add + * @param {Boolean} old If true, doesn't add this marker to the markers array. Used by the "swap" method + */ +Mapstraction.prototype.addMarker = function(marker, old) { + marker.mapstraction = this; + marker.api = this.api; + marker.location.api = this.api; + marker.map = this.maps[this.api]; + var propMarker = this.invoker.go('addMarker', arguments); + marker.setChild(propMarker); + if (!old) { + this.markers.push(marker); + } + this.markerAdded.fire({'marker': marker}); +}; + +/** + * addMarkerWithData will addData to the marker, then add it to the map + * @param {Marker} marker The marker to add + * @param {Object} data A data has to add + */ +Mapstraction.prototype.addMarkerWithData = function(marker, data) { + marker.addData(data); + this.addMarker(marker); +}; + +/** + * addPolylineWithData will addData to the polyline, then add it to the map + * @param {Polyline} polyline The polyline to add + * @param {Object} data A data has to add + */ +Mapstraction.prototype.addPolylineWithData = function(polyline, data) { + polyline.addData(data); + this.addPolyline(polyline); +}; + +/** + * removeMarker removes a Marker from the map + * @param {Marker} marker The marker to remove + */ +Mapstraction.prototype.removeMarker = function(marker) { + var current_marker; + for(var i = 0; i < this.markers.length; i++){ + current_marker = this.markers[i]; + if(marker == current_marker) { + this.invoker.go('removeMarker', arguments); + marker.onmap = false; + this.markers.splice(i, 1); + this.markerRemoved.fire({'marker': marker}); + break; + } + } +}; + +/** + * removeAllMarkers removes all the Markers on a map + */ +Mapstraction.prototype.removeAllMarkers = function() { + var current_marker; + while(this.markers.length > 0) { + current_marker = this.markers.pop(); + this.invoker.go('removeMarker', [current_marker]); + } +}; + +/** + * Declutter the markers on the map, group together overlapping markers. + * @param {Object} opts Declutter options + */ +Mapstraction.prototype.declutterMarkers = function(opts) { + if(this.loaded[this.api] === false) { + var me = this; + this.onload[this.api].push( function() { + me.declutterMarkers(opts); + } ); + return; + } + + var map = this.maps[this.api]; + + switch(this.api) + { + // case 'yahoo': + // + // break; + // case 'google': + // + // break; + // case 'openstreetmap': + // + // break; + // case 'microsoft': + // + // break; + // case 'openlayers': + // + // break; + case 'multimap': + /* + * Multimap supports quite a lot of decluttering options such as whether + * to use an accurate of fast declutter algorithm and what icon to use to + * represent a cluster. Using all this would mean abstracting all the enums + * etc so we're only implementing the group name function at the moment. + */ + map.declutterGroup(opts.groupName); + break; + // case 'mapquest': + // + // break; + // case 'map24': + // + // break; + case ' dummy': + break; + default: + if(this.debug) { + alert(this.api + ' not supported by Mapstraction.declutterMarkers'); + } + } +}; + +/** + * Add a polyline to the map + * @param {Polyline} polyline The Polyline to add to the map + * @param {Boolean} old If true replaces an existing Polyline + */ +Mapstraction.prototype.addPolyline = function(polyline, old) { + polyline.api = this.api; + polyline.map = this.maps[this.api]; + var propPoly = this.invoker.go('addPolyline', arguments); + polyline.setChild(propPoly); + if(!old) { + this.polylines.push(polyline); + } + this.polylineAdded.fire({'polyline': polyline}); +}; + +// Private remove implementation +var removePolylineImpl = function(polyline) { + this.invoker.go('removePolyline', arguments); + polyline.onmap = false; + this.polylineRemoved.fire({'polyline': polyline}); +}; + +/** + * Remove the polyline from the map + * @param {Polyline} polyline The Polyline to remove from the map + */ +Mapstraction.prototype.removePolyline = function(polyline) { + var current_polyline; + for(var i = 0; i < this.polylines.length; i++){ + current_polyline = this.polylines[i]; + if(polyline == current_polyline) { + this.polylines.splice(i, 1); + removePolylineImpl.call(this, polyline); + break; + } + } +}; + +/** + * Removes all polylines from the map + */ +Mapstraction.prototype.removeAllPolylines = function() { + var current_polyline; + while(this.polylines.length > 0) { + current_polyline = this.polylines.pop(); + removePolylineImpl.call(this, current_polyline); + } +}; + +/** + * autoCenterAndZoom sets the center and zoom of the map to the smallest bounding box + * containing all markers + */ +Mapstraction.prototype.autoCenterAndZoom = function() { + var lat_max = -90; + var lat_min = 90; + var lon_max = -180; + var lon_min = 180; + var lat, lon; + var checkMinMax = function(){ + if (lat > lat_max) { + lat_max = lat; + } + if (lat < lat_min) { + lat_min = lat; + } + if (lon > lon_max) { + lon_max = lon; + } + if (lon < lon_min) { + lon_min = lon; + } + }; + for (var i = 0; i < this.markers.length; i++) { + lat = this.markers[i].location.lat; + lon = this.markers[i].location.lon; + checkMinMax(); + } + for(i = 0; i < this.polylines.length; i++) { + for (var j = 0; j < this.polylines[i].points.length; j++) { + lat = this.polylines[i].points[j].lat; + lon = this.polylines[i].points[j].lon; + checkMinMax(); + } + } + this.setBounds( new BoundingBox(lat_min, lon_min, lat_max, lon_max) ); +}; + +/** + * centerAndZoomOnPoints sets the center and zoom of the map from an array of points + * + * This is useful if you don't want to have to add markers to the map + */ +Mapstraction.prototype.centerAndZoomOnPoints = function(points) { + var bounds = new BoundingBox(points[0].lat,points[0].lon,points[0].lat,points[0].lon); + + for (var i=1, len = points.length ; i lat_max) { + lat_max = lat; + } + if (lat < lat_min) { + lat_min = lat; + } + if (lon > lon_max) { + lon_max = lon; + } + if (lon < lon_min) { + lon_min = lon; + } + }; + for (var i=0; i 0) + { + latConv = (radius / mapstraction.polylines[i].points[j].latConv()); + lonConv = (radius / mapstraction.polylines[i].points[j].lonConv()); + } + + if ((lat + latConv) > lat_max) { + lat_max = (lat + latConv); + } + if ((lat - latConv) < lat_min) { + lat_min = (lat - latConv); + } + if ((lon + lonConv) > lon_max) { + lon_max = (lon + lonConv); + } + if ((lon - lonConv) < lon_min) { + lon_min = (lon - lonConv); + } + } + } + + this.setBounds(new BoundingBox(lat_min, lon_min, lat_max, lon_max)); +}; + +/** + * addImageOverlay layers an georeferenced image over the map + * @param {id} unique DOM identifier + * @param {src} url of image + * @param {opacity} opacity 0-100 + * @param {west} west boundary + * @param {south} south boundary + * @param {east} east boundary + * @param {north} north boundary + */ +Mapstraction.prototype.addImageOverlay = function(id, src, opacity, west, south, east, north) { + + var b = document.createElement("img"); + b.style.display = 'block'; + b.setAttribute('id',id); + b.setAttribute('src',src); + b.style.position = 'absolute'; + b.style.zIndex = 1; + b.setAttribute('west',west); + b.setAttribute('south',south); + b.setAttribute('east',east); + b.setAttribute('north',north); + + var oContext = { + imgElm: b + }; + + this.invoker.go('addImageOverlay', arguments, { context: oContext }); +}; + +Mapstraction.prototype.setImageOpacity = function(id, opacity) { + if (opacity < 0) { + opacity = 0; + } + if (opacity >= 100) { + opacity = 100; + } + var c = opacity / 100; + var d = document.getElementById(id); + if(typeof(d.style.filter)=='string'){ + d.style.filter='alpha(opacity:'+opacity+')'; + } + if(typeof(d.style.KHTMLOpacity)=='string'){ + d.style.KHTMLOpacity=c; + } + if(typeof(d.style.MozOpacity)=='string'){ + d.style.MozOpacity=c; + } + if(typeof(d.style.opacity)=='string'){ + d.style.opacity=c; + } +}; + +Mapstraction.prototype.setImagePosition = function(id) { + var imgElement = document.getElementById(id); + var oContext = { + latLng: { + top: imgElement.getAttribute('north'), + left: imgElement.getAttribute('west'), + bottom: imgElement.getAttribute('south'), + right: imgElement.getAttribute('east') + }, + pixels: { top: 0, right: 0, bottom: 0, left: 0 } + }; + + this.invoker.go('setImagePosition', arguments, { context: oContext }); + + imgElement.style.top = oContext.pixels.top.toString() + 'px'; + imgElement.style.left = oContext.pixels.left.toString() + 'px'; + imgElement.style.width = (oContext.pixels.right - oContext.pixels.left).toString() + 'px'; + imgElement.style.height = (oContext.pixels.bottom - oContext.pixels.top).toString() + 'px'; +}; + +Mapstraction.prototype.addJSON = function(json) { + var features; + if (typeof(json) == "string") { + features = eval('(' + json + ')'); + } else { + features = json; + } + features = features.features; + var map = this.maps[this.api]; + var html = ""; + var item; + var polyline; + var marker; + var markers = []; + + if(features.type == "FeatureCollection") { + this.addJSON(features.features); + } + + for (var i = 0; i < features.length; i++) { + item = features[i]; + switch(item.geometry.type) { + case "Point": + html = "" + item.title + "

    " + item.description + "

    "; + marker = new Marker(new LatLonPoint(item.geometry.coordinates[1],item.geometry.coordinates[0])); + markers.push(marker); + this.addMarkerWithData(marker,{ + infoBubble : html, + label : item.title, + date : "new Date(\""+item.date+"\")", + iconShadow : item.icon_shadow, + marker : item.id, + iconShadowSize : item.icon_shadow_size, + icon : "http://boston.openguides.org/markers/AQUA.png", + iconSize : item.icon_size, + category : item.source_id, + draggable : false, + hover : false + }); + break; + case "Polygon": + var points = []; + polyline = new Polyline(points); + mapstraction.addPolylineWithData(polyline,{ + fillColor : item.poly_color, + date : "new Date(\""+item.date+"\")", + category : item.source_id, + width : item.line_width, + opacity : item.line_opacity, + color : item.line_color, + polygon : true + }); + markers.push(polyline); + break; + default: + // console.log("Geometry: " + features.items[i].geometry.type); + } + } + return markers; +}; + +/** + * Adds a Tile Layer to the map + * + * Requires providing a parameterized tile url. Use {Z}, {X}, and {Y} to specify where the parameters + * should go in the URL. + * + * For example, the OpenStreetMap tiles are: + * m.addTileLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png", 1.0, "OSM", 1, 19, true); + * + * @param {tile_url} template url of the tiles. + * @param {opacity} opacity of the tile layer - 0 is transparent, 1 is opaque. (default=0.6) + * @param {copyright_text} copyright text to use for the tile layer. (default=Mapstraction) + * @param {min_zoom} Minimum (furtherest out) zoom level that tiles are available (default=1) + * @param {max_zoom} Maximum (closest) zoom level that the tiles are available (default=18) + * @param {map_type} Should the tile layer be a selectable map type in the layers palette (default=false) + */ +Mapstraction.prototype.addTileLayer = function(tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type) { + if(!tile_url) { + return; + } + + this.tileLayers = this.tileLayers || []; + opacity = opacity || 0.6; + copyright_text = copyright_text || "Mapstraction"; + min_zoom = min_zoom || 1; + max_zoom = max_zoom || 18; + map_type = map_type || false; + + return this.invoker.go('addTileLayer', [ tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type] ); +}; + +/** + * addFilter adds a marker filter + * @param {field} name of attribute to filter on + * @param {operator} presently only "ge" or "le" + * @param {value} the value to compare against + */ +Mapstraction.prototype.addFilter = function(field, operator, value) { + if (!this.filters) { + this.filters = []; + } + this.filters.push( [field, operator, value] ); +}; + +/** + * Remove the specified filter + * @param {Object} field + * @param {Object} operator + * @param {Object} value + */ +Mapstraction.prototype.removeFilter = function(field, operator, value) { + if (!this.filters) { + return; + } + + var del; + for (var f=0; f