Merge commit 'mainline/0.9.x' into 0.9.x

This commit is contained in:
Brenda Wallace 2010-01-18 11:48:11 +13:00
commit 02a6006baf
230 changed files with 40752 additions and 21998 deletions

View File

@ -655,3 +655,35 @@ StartUnblockProfile: when we're about to unblock
EndUnblockProfile: when an unblock has succeeded EndUnblockProfile: when an unblock has succeeded
- $user: the person doing the unblock - $user: the person doing the unblock
- $profile: the person unblocked, can be remote - $profile: the person unblocked, can be remote
StartSubscribe: when a subscription is starting
- $user: the person subscribing
- $other: the person being subscribed to
EndSubscribe: when a subscription is finished
- $user: the person subscribing
- $other: the person being subscribed to
StartUnsubscribe: when an unsubscribe is starting
- $user: the person unsubscribing
- $other: the person being unsubscribed from
EndUnsubscribe: when an unsubscribe is done
- $user: the person unsubscribing
- $other: the person being unsubscribed to
StartJoinGroup: when a user is joining a group
- $group: the group being joined
- $user: the user joining
EndJoinGroup: when a user finishes joining a group
- $group: the group being joined
- $user: the user joining
StartLeaveGroup: when a user is leaving a group
- $group: the group being left
- $user: the user leaving
EndLeaveGroup: when a user has left a group
- $group: the group being left
- $user: the user leaving

View File

@ -81,7 +81,7 @@ class AllAction extends ProfileAction
function title() function title()
{ {
if ($this->page > 1) { if ($this->page > 1) {
return sprintf(_("%1$s and friends, page %2$d"), $this->user->nickname, $this->page); return sprintf(_('%1$s and friends, page %2$d'), $this->user->nickname, $this->page);
} else { } else {
return sprintf(_("%s and friends"), $this->user->nickname); return sprintf(_("%s and friends"), $this->user->nickname);
} }

View File

@ -82,4 +82,18 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction
} }
/**
* Is this action read only?
*
* @param array $args other arguments
*
* @return boolean true
*
**/
function isReadOnly($args)
{
return true;
}
} }

View File

@ -153,7 +153,7 @@ class ApiDirectMessageAction extends ApiAuthAction
$this->showJsonDirectMessages(); $this->showJsonDirectMessages();
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -96,7 +96,7 @@ class ApiFavoriteCreateAction extends ApiAuthAction
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );
@ -116,7 +116,7 @@ class ApiFavoriteCreateAction extends ApiAuthAction
if ($this->user->hasFave($this->notice)) { if ($this->user->hasFave($this->notice)) {
$this->clientError( $this->clientError(
_('This status is already a favorite!'), _('This status is already a favorite.'),
403, 403,
$this->format $this->format
); );

View File

@ -97,7 +97,7 @@ class ApiFavoriteDestroyAction extends ApiAuthAction
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );
@ -119,7 +119,7 @@ class ApiFavoriteDestroyAction extends ApiAuthAction
if (!$fave->find(true)) { if (!$fave->find(true)) {
$this->clientError( $this->clientError(
_('That status is not a favorite!'), _('That status is not a favorite.'),
403, 403,
$this->favorite $this->favorite
); );

View File

@ -97,7 +97,7 @@ class ApiFriendshipsCreateAction extends ApiAuthAction
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -97,7 +97,7 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );
@ -117,7 +117,7 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction
if ($this->user->id == $this->other->id) { if ($this->user->id == $this->other->id) {
$this->clientError( $this->clientError(
_("You cannot unfollow yourself!"), _("You cannot unfollow yourself."),
403, 403,
$this->format $this->format
); );

View File

@ -126,7 +126,7 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction
parent::handle($args); parent::handle($args);
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found!'), 404); $this->clientError(_('API method not found.'), 404);
return; return;
} }

View File

@ -133,7 +133,7 @@ class ApiGroupCreateAction extends ApiAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -111,7 +111,7 @@ class ApiGroupIsMemberAction extends ApiBareAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
400, 400,
$this->format $this->format
); );

View File

@ -145,14 +145,14 @@ class ApiGroupJoinAction extends ApiAuthAction
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->show_single_xml_group($this->group); $this->showSingleXmlGroup($this->group);
break; break;
case 'json': case 'json':
$this->showSingleJsonGroup($this->group); $this->showSingleJsonGroup($this->group);
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -131,14 +131,14 @@ class ApiGroupLeaveAction extends ApiAuthAction
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
$this->show_single_xml_group($this->group); $this->showSingleXmlGroup($this->group);
break; break;
case 'json': case 'json':
$this->showSingleJsonGroup($this->group); $this->showSingleJsonGroup($this->group);
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -129,7 +129,7 @@ class ApiGroupListAction extends ApiBareAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -117,7 +117,7 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -103,7 +103,7 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -102,7 +102,7 @@ class ApiGroupShowAction extends ApiPrivateAuthAction
$this->showSingleJsonGroup($this->group); $this->showSingleJsonGroup($this->group);
break; break;
default: default:
$this->clientError(_('API method not found!'), 404, $this->format); $this->clientError(_('API method not found.'), 404, $this->format);
break; break;
} }

View File

@ -85,7 +85,7 @@ class ApiHelpTestAction extends ApiPrivateAuthAction
$this->endDocument('json'); $this->endDocument('json');
} else { } else {
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -0,0 +1,94 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Exchange an authorized OAuth request token for an access token
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 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);
}
require_once INSTALLDIR . '/lib/apioauth.php';
/**
* Exchange an authorized OAuth request token for an access token
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApiOauthAccessTokenAction extends ApiOauthAction
{
/**
* Class handler.
*
* @param array $args array of arguments
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$datastore = new ApiStatusNetOAuthDataStore();
$server = new OAuthServer($datastore);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($hmac_method);
$atok = null;
try {
$req = OAuthRequest::from_request();
$atok = $server->fetch_access_token($req);
} catch (OAuthException $e) {
common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
common_debug(var_export($req, true));
$this->outputError($e->getMessage());
return;
}
if (empty($atok)) {
common_debug('couldn\'t get access token.');
print "Token exchange failed. Has the request token been authorized?\n";
} else {
print $atok;
}
}
function outputError($msg)
{
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: text/html; charset=utf-8');
print $msg . "\n";
}
}

View File

@ -0,0 +1,376 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Authorize an OAuth request token
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 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);
}
require_once INSTALLDIR . '/lib/apioauth.php';
/**
* Authorize an OAuth request token
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApiOauthAuthorizeAction extends ApiOauthAction
{
var $oauth_token;
var $callback;
var $app;
var $nickname;
var $password;
var $store;
/**
* Is this a read-only action?
*
* @return boolean false
*/
function isReadOnly($args)
{
return false;
}
function prepare($args)
{
parent::prepare($args);
common_debug("apioauthauthorize");
$this->nickname = $this->trimmed('nickname');
$this->password = $this->arg('password');
$this->oauth_token = $this->arg('oauth_token');
$this->callback = $this->arg('oauth_callback');
$this->store = new ApiStatusNetOAuthDataStore();
$this->app = $this->store->getAppByRequestToken($this->oauth_token);
return true;
}
/**
* Handle input, produce output
*
* Switches on request method; either shows the form or handles its input.
*
* @param array $args $_REQUEST data
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
} else {
// XXX: make better error messages
if (empty($this->oauth_token)) {
common_debug("No request token found.");
$this->clientError(_('Bad request.'));
return;
}
if (empty($this->app)) {
common_debug('No app for that token.');
$this->clientError(_('Bad request.'));
return;
}
$name = $this->app->name;
common_debug("Requesting auth for app: " . $name);
$this->showForm();
}
}
function handlePost()
{
common_debug("handlePost()");
// check session token for 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;
}
// check creds
$user = null;
if (!common_logged_in()) {
$user = common_check_user($this->nickname, $this->password);
if (empty($user)) {
$this->showForm(_("Invalid nickname / password!"));
return;
}
} else {
$user = common_current_user();
}
if ($this->arg('allow')) {
// mark the req token as authorized
$this->store->authorize_token($this->oauth_token);
// Check to see if there was a previous token associated
// with this user/app and kill it. If the user is doing this she
// probably doesn't want any old tokens anyway.
$appUser = Oauth_application_user::getByKeys($user, $this->app);
if (!empty($appUser)) {
$result = $appUser->delete();
if (!$result) {
common_log_db_error($appUser, 'DELETE', __FILE__);
throw new ServerException(_('DB error deleting OAuth app user.'));
return;
}
}
// associated the authorized req token with the user and the app
$appUser = new Oauth_application_user();
$appUser->profile_id = $user->id;
$appUser->application_id = $this->app->id;
// Note: do not copy the access type from the application.
// The access type should always be 0 when the OAuth app
// user record has a request token associated with it.
// Access type gets assigned once an access token has been
// granted. The OAuth app user record then gets updated
// with the new access token and access type.
$appUser->token = $this->oauth_token;
$appUser->created = common_sql_now();
$result = $appUser->insert();
if (!$result) {
common_log_db_error($appUser, 'INSERT', __FILE__);
throw new ServerException(_('DB error inserting OAuth app user.'));
return;
}
// if we have a callback redirect and provide the token
// A callback specified in the app setup overrides whatever
// is passed in with the request.
common_debug("Req token is authorized - doing callback");
if (!empty($this->app->callback_url)) {
$this->callback = $this->app->callback_url;
}
if (!empty($this->callback)) {
// XXX: Need better way to build this redirect url.
$target_url = $this->getCallback($this->callback,
array('oauth_token' => $this->oauth_token));
common_debug("Doing callback to $target_url");
common_redirect($target_url, 303);
} else {
common_debug("callback was empty!");
}
// otherwise inform the user that the rt was authorized
$this->elementStart('p');
// XXX: Do OAuth 1.0a verifier code
$this->raw(sprintf(_("The request token %s has been authorized. " .
'Please exchange it for an access token.'),
$this->oauth_token));
$this->elementEnd('p');
} else if ($this->arg('deny')) {
$this->elementStart('p');
$this->raw(sprintf(_("The request token %s has been denied."),
$this->oauth_token));
$this->elementEnd('p');
} else {
$this->clientError(_('Unexpected form submission.'));
return;
}
}
function showForm($error=null)
{
$this->error = $error;
$this->showPage();
}
function showScripts()
{
parent::showScripts();
if (!common_logged_in()) {
$this->autofocus('nickname');
}
}
/**
* Title of the page
*
* @return string title of the page
*/
function title()
{
return _('An application would like to connect to your account');
}
/**
* Shows the authorization form.
*
* @return void
*/
function showContent()
{
$this->elementStart('form', array('method' => 'post',
'id' => 'form_apioauthauthorize',
'class' => 'form_settings',
'action' => common_local_url('apioauthauthorize')));
$this->elementStart('fieldset');
$this->element('legend', array('id' => 'apioauthauthorize_allowdeny'),
_('Allow or deny access'));
$this->hidden('token', common_session_token());
$this->hidden('oauth_token', $this->oauth_token);
$this->hidden('oauth_callback', $this->callback);
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->elementStart('p');
if (!empty($this->app->icon)) {
$this->element('img', array('src' => $this->app->icon));
}
$access = ($this->app->access_type & Oauth_application::$writeAccess) ?
'access and update' : 'access';
$msg = _("The application <strong>%1$s</strong> by <strong>%2$s</strong> would like " .
"the ability to <strong>%3$s</strong> your account data.");
$this->raw(sprintf($msg,
$this->app->name,
$this->app->organization,
$access));
$this->elementEnd('p');
$this->elementEnd('li');
$this->elementEnd('ul');
if (!common_logged_in()) {
$this->elementStart('fieldset');
$this->element('legend', null, _('Account'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('nickname', _('Nickname'));
$this->elementEnd('li');
$this->elementStart('li');
$this->password('password', _('Password'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('fieldset');
}
$this->element('input', array('id' => 'deny_submit',
'class' => 'submit submit form_action-primary',
'name' => 'deny',
'type' => 'submit',
'value' => _('Deny')));
$this->element('input', array('id' => 'allow_submit',
'class' => 'submit submit form_action-secondary',
'name' => 'allow',
'type' => 'submit',
'value' => _('Allow')));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Instructions for using the form
*
* For "remembered" logins, we make the user re-login when they
* try to change settings. Different instructions for this case.
*
* @return void
*/
function getInstructions()
{
return _('Allow or deny access to your account information.');
}
/**
* A local menu
*
* Shows different login/register actions.
*
* @return void
*/
function showLocalNav()
{
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Get an OAuth request token
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 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);
}
require_once INSTALLDIR . '/lib/apioauth.php';
/**
* Get an OAuth request token
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApiOauthRequestTokenAction extends ApiOauthAction
{
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*
*/
function prepare($args)
{
parent::prepare($args);
$this->callback = $this->arg('oauth_callback');
if (!empty($this->callback)) {
common_debug("callback: $this->callback");
}
return true;
}
/**
* Class handler.
*
* @param array $args array of arguments
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$datastore = new ApiStatusNetOAuthDataStore();
$server = new OAuthServer($datastore);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($hmac_method);
try {
$req = OAuthRequest::from_request();
$token = $server->fetch_request_token($req);
print $token;
} catch (OAuthException $e) {
common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: text/html; charset=utf-8');
print $e->getMessage() . "\n";
}
}
}

View File

@ -99,7 +99,7 @@ class ApiStatusesDestroyAction extends ApiAuthAction
parent::handle($args); parent::handle($args);
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
return; return;
} }

View File

@ -109,7 +109,7 @@ class ApiStatusesRetweetsAction extends ApiAuthAction
$this->showJsonTimeline($strm); $this->showJsonTimeline($strm);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -105,7 +105,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
parent::handle($args); parent::handle($args);
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
return; return;
} }

View File

@ -85,6 +85,11 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->lat = $this->trimmed('lat'); $this->lat = $this->trimmed('lat');
$this->lon = $this->trimmed('long'); $this->lon = $this->trimmed('long');
// try to set the source attr from OAuth app
if (empty($this->source)) {
$this->source = $this->oauth_source;
}
if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { if (empty($this->source) || in_array($this->source, self::$reserved_sources)) {
$this->source = 'api'; $this->source = 'api';
} }

View File

@ -130,7 +130,7 @@ class ApiStatusnetConfigAction extends ApiAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -90,7 +90,7 @@ class ApiStatusnetVersionAction extends ApiPrivateAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -108,7 +108,7 @@ class ApiSubscriptionsAction extends ApiBareAuthAction
parent::handle($args); parent::handle($args);
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
return; return;
} }

View File

@ -143,7 +143,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -72,7 +72,6 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
common_debug("api friends_timeline");
$this->user = $this->getTargetUser($this->arg('id')); $this->user = $this->getTargetUser($this->arg('id'));
if (empty($this->user)) { if (empty($this->user)) {
@ -153,7 +152,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -147,7 +147,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
break; break;
default: default:
$this->clientError( $this->clientError(
_('API method not found!'), _('API method not found.'),
404, 404,
$this->format $this->format
); );

View File

@ -153,7 +153,7 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -148,7 +148,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -128,7 +128,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -119,7 +119,7 @@ class ApiTimelineRetweetedByMeAction extends ApiAuthAction
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -118,7 +118,7 @@ class ApiTimelineRetweetedToMeAction extends ApiAuthAction
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -119,7 +119,7 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -138,7 +138,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }
} }

View File

@ -162,7 +162,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
default: default:
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }

View File

@ -98,7 +98,7 @@ class ApiUserShowAction extends ApiPrivateAuthAction
} }
if (!in_array($this->format, array('xml', 'json'))) { if (!in_array($this->format, array('xml', 'json'))) {
$this->clientError(_('API method not found!'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
return; return;
} }

View File

@ -70,14 +70,14 @@ class BlockedfromgroupAction extends GroupDesignAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
$this->group = User_group::staticGet('nickname', $nickname); $this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }

View File

@ -141,7 +141,7 @@ class ConfirmaddressAction extends Action
function title() function title()
{ {
return _('Confirm Address'); return _('Confirm address');
} }
/** /**

264
actions/editapplication.php Normal file
View File

@ -0,0 +1,264 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Edit an OAuth Application
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Applications
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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);
}
/**
* Edit the details of an OAuth application
*
* This is the form for editing an application
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class EditApplicationAction extends OwnerDesignAction
{
var $msg = null;
var $owner = null;
var $app = null;
function title()
{
return _('Edit application');
}
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
$this->clientError(_('You must be logged in to edit an application.'));
return false;
}
$id = (int)$this->arg('id');
$this->app = Oauth_application::staticGet($id);
$this->owner = User::staticGet($this->app->owner);
$cur = common_current_user();
if ($cur->id != $this->owner->id) {
$this->clientError(_('You are not the owner of this application.'), 401);
}
if (!$this->app) {
$this->clientError(_('No such application.'));
return false;
}
return true;
}
/**
* Handle the request
*
* On GET, show the form. On POST, try to save the app.
*
* @param array $args unused
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost($args);
} else {
$this->showForm();
}
}
function handlePost($args)
{
// Workaround for PHP returning empty $_POST and $_FILES when POST
// length > post_max_size in php.ini
if (empty($_FILES)
&& empty($_POST)
&& ($_SERVER['CONTENT_LENGTH'] > 0)
) {
$msg = _('The server was unable to handle that much POST ' .
'data (%s bytes) due to its current configuration.');
$this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
return;
}
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token.'));
return;
}
$cur = common_current_user();
if ($this->arg('cancel')) {
common_redirect(common_local_url('showapplication',
array('id' => $this->app->id)), 303);
} elseif ($this->arg('save')) {
$this->trySave();
} else {
$this->clientError(_('Unexpected form submission.'));
}
}
function showForm($msg=null)
{
$this->msg = $msg;
$this->showPage();
}
function showContent()
{
$form = new ApplicationEditForm($this, $this->app);
$form->show();
}
function showPageNotice()
{
if (!empty($this->msg)) {
$this->element('p', 'error', $this->msg);
} else {
$this->element('p', 'instructions',
_('Use this form to edit your application.'));
}
}
function trySave()
{
$name = $this->trimmed('name');
$description = $this->trimmed('description');
$source_url = $this->trimmed('source_url');
$organization = $this->trimmed('organization');
$homepage = $this->trimmed('homepage');
$callback_url = $this->trimmed('callback_url');
$type = $this->arg('app_type');
$access_type = $this->arg('default_access_type');
if (empty($name)) {
$this->showForm(_('Name is required.'));
return;
} elseif (mb_strlen($name) > 255) {
$this->showForm(_('Name is too long (max 255 chars).'));
return;
} elseif (empty($description)) {
$this->showForm(_('Description is required.'));
return;
} elseif (Oauth_application::descriptionTooLong($description)) {
$this->showForm(sprintf(
_('Description is too long (max %d chars).'),
Oauth_application::maxDescription()));
return;
} elseif (mb_strlen($source_url) > 255) {
$this->showForm(_('Source URL is too long.'));
return;
} elseif ((mb_strlen($source_url) > 0)
&& !Validate::uri($source_url,
array('allowed_schemes' => array('http', 'https'))))
{
$this->showForm(_('Source URL is not valid.'));
return;
} elseif (empty($organization)) {
$this->showForm(_('Organization is required.'));
return;
} elseif (mb_strlen($organization) > 255) {
$this->showForm(_('Organization is too long (max 255 chars).'));
return;
} elseif (empty($homepage)) {
$this->showForm(_('Organization homepage is required.'));
return;
} elseif ((mb_strlen($homepage) > 0)
&& !Validate::uri($homepage,
array('allowed_schemes' => array('http', 'https'))))
{
$this->showForm(_('Homepage is not a valid URL.'));
return;
} elseif (mb_strlen($callback_url) > 255) {
$this->showForm(_('Callback is too long.'));
return;
} elseif (mb_strlen($callback_url) > 0
&& !Validate::uri($source_url,
array('allowed_schemes' => array('http', 'https'))
))
{
$this->showForm(_('Callback URL is not valid.'));
return;
}
$cur = common_current_user();
// Checked in prepare() above
assert(!is_null($cur));
assert(!is_null($this->app));
$orig = clone($this->app);
$this->app->name = $name;
$this->app->description = $description;
$this->app->source_url = $source_url;
$this->app->organization = $organization;
$this->app->homepage = $homepage;
$this->app->callback_url = $callback_url;
$this->app->type = $type;
common_debug("access_type = $access_type");
if ($access_type == 'r') {
$this->app->access_type = 1;
} else {
$this->app->access_type = 3;
}
$result = $this->app->update($orig);
if (!$result) {
common_log_db_error($this->app, 'UPDATE', __FILE__);
$this->serverError(_('Could not update application.'));
}
$this->app->uploadLogo();
common_redirect(common_local_url('oauthappssettings'), 303);
}
}

View File

@ -81,7 +81,7 @@ class EditgroupAction extends GroupDesignAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
@ -93,14 +93,14 @@ class EditgroupAction extends GroupDesignAction
} }
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }
$cur = common_current_user(); $cur = common_current_user();
if (!$cur->isAdmin($this->group)) { if (!$cur->isAdmin($this->group)) {
$this->clientError(_('You must be an admin to edit the group'), 403); $this->clientError(_('You must be an admin to edit the group.'), 403);
return false; return false;
} }
@ -165,7 +165,7 @@ class EditgroupAction extends GroupDesignAction
{ {
$cur = common_current_user(); $cur = common_current_user();
if (!$cur->isAdmin($this->group)) { if (!$cur->isAdmin($this->group)) {
$this->clientError(_('You must be an admin to edit the group'), 403); $this->clientError(_('You must be an admin to edit the group.'), 403);
return; return;
} }

View File

@ -57,7 +57,7 @@ class EmailsettingsAction extends AccountSettingsAction
function title() function title()
{ {
return _('Email Settings'); return _('Email settings');
} }
/** /**
@ -118,7 +118,7 @@ class EmailsettingsAction extends AccountSettingsAction
} else { } else {
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->input('email', _('Email Address'), $this->input('email', _('Email address'),
($this->arg('email')) ? $this->arg('email') : null, ($this->arg('email')) ? $this->arg('email') : null,
_('Email address, like "UserName@example.org"')); _('Email address, like "UserName@example.org"'));
$this->elementEnd('li'); $this->elementEnd('li');
@ -328,7 +328,7 @@ class EmailsettingsAction extends AccountSettingsAction
return; return;
} }
if (!Validate::email($email, common_config('email', 'check_domain'))) { if (!Validate::email($email, common_config('email', 'check_domain'))) {
$this->showForm(_('Not a valid email address')); $this->showForm(_('Not a valid email address.'));
return; return;
} else if ($user->email == $email) { } else if ($user->email == $email) {
$this->showForm(_('That is already your email address.')); $this->showForm(_('That is already your email address.'));

View File

@ -71,7 +71,7 @@ class GroupbyidAction extends Action
$id = $this->arg('id'); $id = $this->arg('id');
if (!$id) { if (!$id) {
$this->clientError(_('No ID')); $this->clientError(_('No ID.'));
return false; return false;
} }
@ -80,7 +80,7 @@ class GroupbyidAction extends Action
$this->group = User_group::staticGet('id', $id); $this->group = User_group::staticGet('id', $id);
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }

View File

@ -81,7 +81,7 @@ class GroupDesignSettingsAction extends DesignSettingsAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
@ -94,14 +94,14 @@ class GroupDesignSettingsAction extends DesignSettingsAction
} }
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }
$cur = common_current_user(); $cur = common_current_user();
if (!$cur->isAdmin($this->group)) { if (!$cur->isAdmin($this->group)) {
$this->clientError(_('You must be an admin to edit the group'), 403); $this->clientError(_('You must be an admin to edit the group.'), 403);
return false; return false;
} }
@ -284,7 +284,7 @@ class GroupDesignSettingsAction extends DesignSettingsAction
if (empty($id)) { if (empty($id)) {
common_log_db_error($id, 'INSERT', __FILE__); common_log_db_error($id, 'INSERT', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
return; return;
} }
@ -294,7 +294,7 @@ class GroupDesignSettingsAction extends DesignSettingsAction
if (empty($result)) { if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__); common_log_db_error($original, 'UPDATE', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
$this->group->query('ROLLBACK'); $this->group->query('ROLLBACK');
return; return;
} }

View File

@ -83,7 +83,7 @@ class GrouplogoAction extends GroupDesignAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
@ -96,14 +96,14 @@ class GrouplogoAction extends GroupDesignAction
} }
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }
$cur = common_current_user(); $cur = common_current_user();
if (!$cur->isAdmin($this->group)) { if (!$cur->isAdmin($this->group)) {
$this->clientError(_('You must be an admin to edit the group'), 403); $this->clientError(_('You must be an admin to edit the group.'), 403);
return false; return false;
} }
@ -175,7 +175,7 @@ class GrouplogoAction extends GroupDesignAction
if (!$profile) { if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__); common_log_db_error($user, 'SELECT', __FILE__);
$this->serverError(_('User without matching profile')); $this->serverError(_('User without matching profile.'));
return; return;
} }

View File

@ -73,14 +73,14 @@ class GroupmembersAction extends GroupDesignAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
$this->group = User_group::staticGet('nickname', $nickname); $this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }

View File

@ -56,7 +56,7 @@ class ImsettingsAction extends ConnectSettingsAction
function title() function title()
{ {
return _('IM Settings'); return _('IM settings');
} }
/** /**
@ -121,7 +121,7 @@ class ImsettingsAction extends ConnectSettingsAction
} else { } else {
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->input('jabber', _('IM Address'), $this->input('jabber', _('IM address'),
($this->arg('jabber')) ? $this->arg('jabber') : null, ($this->arg('jabber')) ? $this->arg('jabber') : null,
sprintf(_('Jabber or GTalk address, '. sprintf(_('Jabber or GTalk address, '.
'like "UserName@example.org". '. 'like "UserName@example.org". '.

View File

@ -73,21 +73,21 @@ class JoingroupAction extends Action
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
$this->group = User_group::staticGet('nickname', $nickname); $this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) { if (!$this->group) {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }
$cur = common_current_user(); $cur = common_current_user();
if ($cur->isMember($this->group)) { if ($cur->isMember($this->group)) {
$this->clientError(_('You are already a member of that group'), 403); $this->clientError(_('You are already a member of that group.'), 403);
return false; return false;
} }
@ -115,17 +115,13 @@ class JoingroupAction extends Action
$cur = common_current_user(); $cur = common_current_user();
$member = new Group_member(); try {
if (Event::handle('StartJoinGroup', array($this->group, $cur))) {
$member->group_id = $this->group->id; Group_member::join($this->group->id, $cur->id);
$member->profile_id = $cur->id; Event::handle('EndJoinGroup', array($this->group, $cur));
$member->created = common_sql_now(); }
} catch (Exception $e) {
$result = $member->insert(); $this->serverError(sprintf(_('Could not join user %1$s to group %2$s.'),
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
$this->serverError(sprintf(_('Could not join user %1$s to group %2$s'),
$cur->nickname, $this->group->nickname)); $cur->nickname, $this->group->nickname));
} }

View File

@ -110,22 +110,15 @@ class LeavegroupAction extends Action
$cur = common_current_user(); $cur = common_current_user();
$member = new Group_member(); try {
if (Event::handle('StartLeaveGroup', array($this->group, $cur))) {
$member->group_id = $this->group->id; Group_member::leave($this->group->id, $cur->id);
$member->profile_id = $cur->id; Event::handle('EndLeaveGroup', array($this->group, $cur));
if (!$member->find(true)) {
$this->serverError(_('Could not find membership record.'));
return;
} }
} catch (Exception $e) {
$result = $member->delete();
if (!$result) {
common_log_db_error($member, 'DELETE', __FILE__);
$this->serverError(sprintf(_('Could not remove user %1$s from group %2$s.'), $this->serverError(sprintf(_('Could not remove user %1$s from group %2$s.'),
$cur->nickname, $this->group->nickname)); $cur->nickname, $this->group->nickname));
return;
} }
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {

View File

@ -76,15 +76,10 @@ class LoginAction extends Action
{ {
parent::handle($args); parent::handle($args);
$disabled = common_config('logincommand','disabled');
$disabled = isset($disabled) && $disabled;
if (common_is_real_login()) { if (common_is_real_login()) {
$this->clientError(_('Already logged in.')); $this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') { } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->checkLogin(); $this->checkLogin();
} else if (!$disabled && isset($args['user_id']) && isset($args['token'])){
$this->checkLogin($args['user_id'],$args['token']);
} else { } else {
common_ensure_session(); common_ensure_session();
$this->showForm(); $this->showForm();
@ -103,35 +98,20 @@ class LoginAction extends Action
function checkLogin($user_id=null, $token=null) function checkLogin($user_id=null, $token=null)
{ {
if(isset($token) && isset($user_id)){
//Token based login (from the LoginCommand)
$login_token = Login_token::staticGet('user_id',$user_id);
if($login_token && $login_token->token == $token){
if($login_token->modified > time()+2*60){
//token has expired
//delete the token as it is useless
$login_token->delete();
$this->showForm(_('Invalid or expired token.'));
return;
}else{
//delete the token so it cannot be reused
$login_token->delete();
//it's a valid token - let them log in
$user = User::staticGet('id', $user_id);
//$user = User::staticGet('nickname', "candrews");
}
}else{
$this->showForm(_('Invalid or expired token.'));
return;
}
}else{
// Regular form submission login
// XXX: login throttle // XXX: login throttle
// CSRF protection - token set in NoticeForm // CSRF protection - token set in NoticeForm
$token = $this->trimmed('token'); $token = $this->trimmed('token');
if (!$token || $token != common_session_token()) { if (!$token || $token != common_session_token()) {
$st = common_session_token();
if (empty($token)) {
common_log(LOG_WARNING, 'No token provided by client.');
} else if (empty($st)) {
common_log(LOG_WARNING, 'No session token stored.');
} else {
common_log(LOG_WARNING, 'Token = ' . $token . ' and session token = ' . $st);
}
$this->clientError(_('There was a problem with your session token. '. $this->clientError(_('There was a problem with your session token. '.
'Try again, please.')); 'Try again, please.'));
return; return;
@ -141,7 +121,6 @@ class LoginAction extends Action
$password = $this->arg('password'); $password = $this->arg('password');
$user = common_check_user($nickname, $password); $user = common_check_user($nickname, $password);
}
if (!$user) { if (!$user) {
$this->showForm(_('Incorrect username or password.')); $this->showForm(_('Incorrect username or password.'));
@ -165,6 +144,7 @@ class LoginAction extends Action
if ($url) { if ($url) {
// We don't have to return to it again // We don't have to return to it again
common_set_returnto(null); common_set_returnto(null);
$url = common_inject_session($url);
} else { } else {
$url = common_local_url('all', $url = common_local_url('all',
array('nickname' => array('nickname' =>

View File

@ -129,7 +129,7 @@ class MakeadminAction extends Action
'profile_id' => $this->profile->id)); 'profile_id' => $this->profile->id));
if (empty($member)) { if (empty($member)) {
$this->serverError(_('Can\'t get membership record for %1$s in group %2$s'), $this->serverError(_('Can\'t get membership record for %1$s in group %2$s.'),
$this->profile->getBestName(), $this->profile->getBestName(),
$this->group->getBestName()); $this->group->getBestName());
} }
@ -142,7 +142,7 @@ class MakeadminAction extends Action
if (!$result) { if (!$result) {
common_log_db_error($member, 'UPDATE', __FILE__); common_log_db_error($member, 'UPDATE', __FILE__);
$this->serverError(_('Can\'t make %1$s an admin for group %2$s'), $this->serverError(_('Can\'t make %1$s an admin for group %2$s.'),
$this->profile->getBestName(), $this->profile->getBestName(),
$this->group->getBestName()); $this->group->getBestName());
} }

277
actions/newapplication.php Normal file
View File

@ -0,0 +1,277 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Register a new OAuth Application
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Applications
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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);
}
/**
* Add a new application
*
* This is the form for adding a new application
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class NewApplicationAction extends OwnerDesignAction
{
var $msg;
function title()
{
return _('New application');
}
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
$this->clientError(_('You must be logged in to register an application.'));
return false;
}
return true;
}
/**
* Handle the request
*
* On GET, show the form. On POST, try to save the app.
*
* @param array $args unused
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost($args);
} else {
$this->showForm();
}
}
function handlePost($args)
{
// Workaround for PHP returning empty $_POST and $_FILES when POST
// length > post_max_size in php.ini
if (empty($_FILES)
&& empty($_POST)
&& ($_SERVER['CONTENT_LENGTH'] > 0)
) {
$msg = _('The server was unable to handle that much POST ' .
'data (%s bytes) due to its current configuration.');
$this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
return;
}
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token.'));
return;
}
$cur = common_current_user();
if ($this->arg('cancel')) {
common_redirect(common_local_url('oauthappssettings'), 303);
} elseif ($this->arg('save')) {
$this->trySave();
} else {
$this->clientError(_('Unexpected form submission.'));
}
}
function showForm($msg=null)
{
$this->msg = $msg;
$this->showPage();
}
function showContent()
{
$form = new ApplicationEditForm($this);
$form->show();
}
function showPageNotice()
{
if ($this->msg) {
$this->element('p', 'error', $this->msg);
} else {
$this->element('p', 'instructions',
_('Use this form to register a new application.'));
}
}
function trySave()
{
$name = $this->trimmed('name');
$description = $this->trimmed('description');
$source_url = $this->trimmed('source_url');
$organization = $this->trimmed('organization');
$homepage = $this->trimmed('homepage');
$callback_url = $this->trimmed('callback_url');
$type = $this->arg('app_type');
$access_type = $this->arg('default_access_type');
if (empty($name)) {
$this->showForm(_('Name is required.'));
return;
} elseif (mb_strlen($name) > 255) {
$this->showForm(_('Name is too long (max 255 chars).'));
return;
} elseif (empty($description)) {
$this->showForm(_('Description is required.'));
return;
} elseif (Oauth_application::descriptionTooLong($description)) {
$this->showForm(sprintf(
_('Description is too long (max %d chars).'),
Oauth_application::maxDescription()));
return;
} elseif (empty($source_url)) {
$this->showForm(_('Source URL is required.'));
return;
} elseif ((strlen($source_url) > 0)
&& !Validate::uri(
$source_url,
array('allowed_schemes' => array('http', 'https'))
)
)
{
$this->showForm(_('Source URL is not valid.'));
return;
} elseif (empty($organization)) {
$this->showForm(_('Organization is required.'));
return;
} elseif (mb_strlen($organization) > 255) {
$this->showForm(_('Organization is too long (max 255 chars).'));
return;
} elseif (empty($homepage)) {
$this->showForm(_('Organization homepage is required.'));
return;
} elseif ((strlen($homepage) > 0)
&& !Validate::uri(
$homepage,
array('allowed_schemes' => array('http', 'https'))
)
)
{
$this->showForm(_('Homepage is not a valid URL.'));
return;
} elseif (mb_strlen($callback_url) > 255) {
$this->showForm(_('Callback is too long.'));
return;
} elseif (strlen($callback_url) > 0
&& !Validate::uri(
$source_url,
array('allowed_schemes' => array('http', 'https'))
)
)
{
$this->showForm(_('Callback URL is not valid.'));
return;
}
$cur = common_current_user();
// Checked in prepare() above
assert(!is_null($cur));
$app = new Oauth_application();
$app->query('BEGIN');
$app->name = $name;
$app->owner = $cur->id;
$app->description = $description;
$app->source_url = $source_url;
$app->organization = $organization;
$app->homepage = $homepage;
$app->callback_url = $callback_url;
$app->type = $type;
// Yeah, I dunno why I chose bit flags. I guess so I could
// copy this value directly to Oauth_application_user
// access_type which I think does need bit flags -- Z
if ($access_type == 'r') {
$app->setAccessFlags(true, false);
} else {
$app->setAccessFlags(true, true);
}
$app->created = common_sql_now();
// generate consumer key and secret
$consumer = Consumer::generateNew();
$result = $consumer->insert();
if (!$result) {
common_log_db_error($consumer, 'INSERT', __FILE__);
$this->serverError(_('Could not create application.'));
}
$app->consumer_key = $consumer->consumer_key;
$this->app_id = $app->insert();
if (!$this->app_id) {
common_log_db_error($app, 'INSERT', __FILE__);
$this->serverError(_('Could not create application.'));
$app->query('ROLLBACK');
}
$app->uploadLogo();
$app->query('COMMIT');
common_redirect(common_local_url('oauthappssettings'), 303);
}
}

View File

@ -182,7 +182,7 @@ class NewmessageAction extends Action
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');
$this->element('p', array('id' => 'command_result'), $this->element('p', array('id' => 'command_result'),
sprintf(_('Direct message to %s sent'), sprintf(_('Direct message to %s sent.'),
$this->other->nickname)); $this->other->nickname));
$this->elementEnd('body'); $this->elementEnd('body');
$this->elementEnd('html'); $this->elementEnd('html');

View File

@ -0,0 +1,166 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* List the OAuth applications that a user has registered with this instance
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 . '/lib/settingsaction.php';
require_once INSTALLDIR . '/lib/applicationlist.php';
/**
* Show a user's registered OAuth applications
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see SettingsAction
*/
class OauthappssettingsAction extends SettingsAction
{
var $page = 0;
function prepare($args)
{
parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
if (!common_logged_in()) {
$this->clientError(_('You must be logged in to list your applications.'));
return false;
}
return true;
}
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
return _('OAuth applications');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _('Applications you have registered');
}
/**
* Content area of the page
*
* @return void
*/
function showContent()
{
$user = common_current_user();
$offset = ($this->page - 1) * APPS_PER_PAGE;
$limit = APPS_PER_PAGE + 1;
$application = new Oauth_application();
$application->owner = $user->id;
$application->limit($offset, $limit);
$application->orderBy('created DESC');
$application->find();
$cnt = 0;
if ($application) {
$al = new ApplicationList($application, $user, $this);
$cnt = $al->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
}
$this->elementStart('p', array('id' => 'application_register'));
$this->element('a',
array('href' => common_local_url('newapplication'),
'class' => 'more'
),
'Register a new application');
$this->elementEnd('p');
$this->pagination(
$this->page > 1,
$cnt > APPS_PER_PAGE,
$this->page,
'oauthappssettings'
);
}
function showEmptyListMessage()
{
$message = sprintf(_('You have not registered any applications yet.'));
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
/**
* 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;
}
}
}

View File

@ -0,0 +1,212 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* List a user's OAuth connected applications
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 . '/lib/connectsettingsaction.php';
require_once INSTALLDIR . '/lib/applicationlist.php';
/**
* Show connected OAuth applications
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see SettingsAction
*/
class OauthconnectionssettingsAction extends ConnectSettingsAction
{
var $page = null;
var $id = null;
function prepare($args)
{
parent::prepare($args);
$this->id = (int)$this->arg('id');
$this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
return true;
}
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
return _('Connected applications');
}
function isReadOnly($args)
{
return true;
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _('You have allowed the following applications to access you account.');
}
/**
* Content area of the page
*
* @return void
*/
function showContent()
{
$user = common_current_user();
$profile = $user->getProfile();
$offset = ($this->page - 1) * APPS_PER_PAGE;
$limit = APPS_PER_PAGE + 1;
$application = $profile->getApplications($offset, $limit);
$cnt == 0;
if (!empty($application)) {
$al = new ApplicationList($application, $user, $this, true);
$cnt = $al->show();
}
if ($cnt == 0) {
$this->showEmptyListMessage();
}
$this->pagination($this->page > 1, $cnt > APPS_PER_PAGE,
$this->page, 'connectionssettings',
array('nickname' => $this->user->nickname));
}
/**
* 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('revoke')) {
$this->revokeAccess($this->id);
// XXX: Show some indicator to the user of what's been done.
$this->showPage();
} else {
$this->clientError(_('Unexpected form submission.'), 401);
return false;
}
}
function revokeAccess($appId)
{
$cur = common_current_user();
$app = Oauth_application::staticGet('id', $appId);
if (empty($app)) {
$this->clientError(_('No such application.'), 404);
return false;
}
$appUser = Oauth_application_user::getByKeys($cur, $app);
if (empty($appUser)) {
$this->clientError(_('You are not a user of that application.'), 401);
return false;
}
$orig = clone($appUser);
$appUser->access_type = 0; // No access
$result = $appUser->update();
if (!$result) {
common_log_db_error($orig, 'UPDATE', __FILE__);
$this->clientError(_('Unable to revoke access for app: ' . $app->id));
return false;
}
$msg = 'User %s (id: %d) revoked access to app %s (id: %d)';
common_log(LOG_INFO, sprintf($msg, $cur->nickname,
$cur->id, $app->name, $app->id));
}
function showEmptyListMessage()
{
$message = sprintf(_('You have not authorized any applications to use your account.'));
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
function showSections()
{
$cur = common_current_user();
$this->element('h2', null, 'Developers');
$this->elementStart('p');
$this->raw(_('Developers can edit the registration settings for their applications '));
$this->element('a',
array('href' => common_local_url('oauthappssettings')),
'here.');
$this->elementEnd('p');
}
}

View File

@ -57,7 +57,7 @@ class OthersettingsAction extends AccountSettingsAction
function title() function title()
{ {
return _('Other Settings'); return _('Other settings');
} }
/** /**

145
actions/otp.php Normal file
View File

@ -0,0 +1,145 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Allow one-time password login
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Login
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Allow one-time password login
*
* This action will automatically log in the user identified by the user_id
* parameter. A login_token record must be constructed beforehand, typically
* by code where the user is already authenticated.
*
* @category Login
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/
class OtpAction extends Action
{
var $user;
var $token;
var $rememberme;
var $returnto;
var $lt;
function prepare($args)
{
parent::prepare($args);
if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
return false;
}
$id = $this->trimmed('user_id');
if (empty($id)) {
$this->clientError(_('No user ID specified.'));
return false;
}
$this->user = User::staticGet('id', $id);
if (empty($this->user)) {
$this->clientError(_('No such user.'));
return false;
}
$this->token = $this->trimmed('token');
if (empty($this->token)) {
$this->clientError(_('No login token specified.'));
return false;
}
$this->lt = Login_token::staticGet('user_id', $id);
if (empty($this->lt)) {
$this->clientError(_('No login token requested.'));
return false;
}
if ($this->lt->token != $this->token) {
$this->clientError(_('Invalid login token specified.'));
return false;
}
if ($this->lt->modified > time() + Login_token::TIMEOUT) {
//token has expired
//delete the token as it is useless
$this->lt->delete();
$this->lt = null;
$this->clientError(_('Login token expired.'));
return false;
}
$this->rememberme = $this->boolean('rememberme');
$this->returnto = $this->trimmed('returnto');
return true;
}
function handle($args)
{
parent::handle($args);
// success!
if (!common_set_user($this->user)) {
$this->serverError(_('Error setting user. You are probably not authorized.'));
return;
}
// We're now logged in; disable the lt
$this->lt->delete();
$this->lt = null;
if ($this->rememberme) {
common_rememberme($this->user);
}
if (!empty($this->returnto)) {
$url = $this->returnto;
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('all',
array('nickname' =>
$this->user->nickname));
}
common_redirect($url, 303);
}
}

View File

@ -305,7 +305,7 @@ class PathsAdminPanelForm extends AdminForm
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('sslserver', _('SSL Server'), $this->input('sslserver', _('SSL server'),
_('Server to direct SSL requests to'), 'site'); _('Server to direct SSL requests to'), 'site');
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');

View File

@ -259,6 +259,7 @@ class RegisterAction extends Action
// Re-init language env in case it changed (not yet, but soon) // Re-init language env in case it changed (not yet, but soon)
common_init_language(); common_init_language();
$this->showSuccess(); $this->showSuccess();
} else { } else {
$this->showForm(_('Invalid username or password.')); $this->showForm(_('Invalid username or password.'));

327
actions/showapplication.php Normal file
View File

@ -0,0 +1,327 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Show an OAuth application
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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);
}
/**
* Show an OAuth application
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ShowApplicationAction extends OwnerDesignAction
{
/**
* Application to show
*/
var $application = null;
/**
* User who owns the app
*/
var $owner = null;
var $msg = null;
var $success = null;
/**
* Load attributes based on database arguments
*
* Loads all the DB stuff
*
* @param array $args $_REQUEST array
*
* @return success flag
*/
function prepare($args)
{
parent::prepare($args);
$id = (int)$this->arg('id');
$this->application = Oauth_application::staticGet($id);
$this->owner = User::staticGet($this->application->owner);
if (!common_logged_in()) {
$this->clientError(_('You must be logged in to view an application.'));
return false;
}
if (empty($this->application)) {
$this->clientError(_('No such application.'), 404);
return false;
}
$cur = common_current_user();
if ($cur->id != $this->owner->id) {
$this->clientError(_('You are not the owner of this application.'), 401);
return false;
}
return true;
}
/**
* Handle the request
*
* Shows info about the app
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token.'));
return;
}
if ($this->arg('reset')) {
$this->resetKey();
}
} else {
$this->showPage();
}
}
/**
* Title of the page
*
* @return string title of the page
*/
function title()
{
if (!empty($this->application->name)) {
return 'Application: ' . $this->application->name;
}
}
function showPageNotice()
{
if (!empty($this->msg)) {
$this->element('div', ($this->success) ? 'success' : 'error', $this->msg);
}
}
function showContent()
{
$cur = common_current_user();
$consumer = $this->application->getConsumer();
$this->elementStart('div', 'entity_profile vcard');
$this->element('h2', null, _('Application profile'));
$this->elementStart('dl', 'entity_depiction');
$this->element('dt', null, _('Icon'));
$this->elementStart('dd');
if (!empty($this->application->icon)) {
$this->element('img', array('src' => $this->application->icon,
'class' => 'photo logo'));
}
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_fn');
$this->element('dt', null, _('Name'));
$this->elementStart('dd');
$this->element('a', array('href' => $this->application->source_url,
'class' => 'url fn'),
$this->application->name);
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_org');
$this->element('dt', null, _('Organization'));
$this->elementStart('dd');
$this->element('a', array('href' => $this->application->homepage,
'class' => 'url'),
$this->application->organization);
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_note');
$this->element('dt', null, _('Description'));
$this->element('dd', 'note', $this->application->description);
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_statistics');
$this->element('dt', null, _('Statistics'));
$this->elementStart('dd');
$defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess)
? 'read-write' : 'read-only';
$profile = Profile::staticGet($this->application->owner);
$appUsers = new Oauth_application_user();
$appUsers->application_id = $this->application->id;
$userCnt = $appUsers->count();
$this->raw(sprintf(
_('created by %1$s - %2$s access by default - %3$d users'),
$profile->getBestName(),
$defaultAccess,
$userCnt
));
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementEnd('div');
$this->elementStart('div', 'entity_actions');
$this->element('h2', null, _('Application actions'));
$this->elementStart('ul');
$this->elementStart('li', 'entity_edit');
$this->element('a',
array('href' => common_local_url('editapplication',
array('id' => $this->application->id))),
'Edit');
$this->elementEnd('li');
$this->elementStart('li', 'entity_reset_keysecret');
$this->elementStart('form', array(
'id' => 'forma_reset_key',
'class' => 'form_reset_key',
'method' => 'POST',
'action' => common_local_url('showapplication',
array('id' => $this->application->id))));
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->submit('reset', _('Reset key & secret'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('div');
$this->elementStart('div', 'entity_data');
$this->element('h2', null, _('Application info'));
$this->elementStart('dl', 'entity_consumer_key');
$this->element('dt', null, _('Consumer key'));
$this->element('dd', null, $consumer->consumer_key);
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_consumer_secret');
$this->element('dt', null, _('Consumer secret'));
$this->element('dd', null, $consumer->consumer_secret);
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_request_token_url');
$this->element('dt', null, _('Request token URL'));
$this->element('dd', null, common_local_url('apioauthrequesttoken'));
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_access_token_url');
$this->element('dt', null, _('Access token URL'));
$this->element('dd', null, common_local_url('apioauthaccesstoken'));
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_authorize_url');
$this->element('dt', null, _('Authorize URL'));
$this->element('dd', null, common_local_url('apioauthauthorize'));
$this->elementEnd('dl');
$this->element('p', 'note',
_('Note: We support HMAC-SHA1 signatures. We do not support the plaintext signature method.'));
$this->elementEnd('div');
$this->elementStart('p', array('id' => 'application_action'));
$this->element('a',
array('href' => common_local_url('oauthappssettings'),
'class' => 'more'),
'View your applications');
$this->elementEnd('p');
}
function resetKey()
{
$this->application->query('BEGIN');
$consumer = $this->application->getConsumer();
$result = $consumer->delete();
if (!$result) {
common_log_db_error($consumer, 'DELETE', __FILE__);
$this->success = false;
$this->msg = ('Unable to reset consumer key and secret.');
$this->showPage();
return;
}
$consumer = Consumer::generateNew();
$result = $consumer->insert();
if (!$result) {
common_log_db_error($consumer, 'INSERT', __FILE__);
$this->application->query('ROLLBACK');
$this->success = false;
$this->msg = ('Unable to reset consumer key and secret.');
$this->showPage();
return;
}
$orig = clone($this->application);
$this->application->consumer_key = $consumer->consumer_key;
$result = $this->application->update($orig);
if (!$result) {
common_log_db_error($application, 'UPDATE', __FILE__);
$this->application->query('ROLLBACK');
$this->success = false;
$this->msg = ('Unable to reset consumer key and secret.');
$this->showPage();
return;
}
$this->application->query('COMMIT');
$this->success = true;
$this->msg = ('Consumer key and secret reset.');
$this->showPage();
}
}

View File

@ -118,7 +118,7 @@ class ShowgroupAction extends GroupDesignAction
} }
if (!$nickname) { if (!$nickname) {
$this->clientError(_('No nickname'), 404); $this->clientError(_('No nickname.'), 404);
return false; return false;
} }
@ -134,7 +134,7 @@ class ShowgroupAction extends GroupDesignAction
common_redirect(common_local_url('groupbyid', $args), 301); common_redirect(common_local_url('groupbyid', $args), 301);
return false; return false;
} else { } else {
$this->clientError(_('No such group'), 404); $this->clientError(_('No such group.'), 404);
return false; return false;
} }
} }

View File

@ -151,10 +151,10 @@ class SiteadminpanelAction extends AdminPanelAction
$values['site']['email'] = common_canonical_email($values['site']['email']); $values['site']['email'] = common_canonical_email($values['site']['email']);
if (empty($values['site']['email'])) { if (empty($values['site']['email'])) {
$this->clientError(_('You must have a valid contact email address')); $this->clientError(_('You must have a valid contact email address.'));
} }
if (!Validate::email($values['site']['email'], common_config('email', 'check_domain'))) { if (!Validate::email($values['site']['email'], common_config('email', 'check_domain'))) {
$this->clientError(_('Not a valid email address')); $this->clientError(_('Not a valid email address.'));
} }
// Validate timezone // Validate timezone
@ -169,7 +169,7 @@ class SiteadminpanelAction extends AdminPanelAction
if (!is_null($values['site']['language']) && if (!is_null($values['site']['language']) &&
!in_array($values['site']['language'], array_keys(get_nice_language_list()))) { !in_array($values['site']['language'], array_keys(get_nice_language_list()))) {
$this->clientError(sprintf(_('Unknown language "%s"'), $values['site']['language'])); $this->clientError(sprintf(_('Unknown language "%s".'), $values['site']['language']));
} }
// Validate report URL // Validate report URL

View File

@ -55,7 +55,7 @@ class SmssettingsAction extends ConnectSettingsAction
function title() function title()
{ {
return _('SMS Settings'); return _('SMS settings');
} }
/** /**
@ -135,7 +135,7 @@ class SmssettingsAction extends ConnectSettingsAction
} else { } else {
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->input('sms', _('SMS Phone number'), $this->input('sms', _('SMS phone number'),
($this->arg('sms')) ? $this->arg('sms') : null, ($this->arg('sms')) ? $this->arg('sms') : null,
_('Phone number, no punctuation or spaces, '. _('Phone number, no punctuation or spaces, '.
'with area code')); 'with area code'));

View File

@ -58,7 +58,7 @@ class SubscribeAction extends Action
$result = subs_subscribe_to($user, $other); $result = subs_subscribe_to($user, $other);
if($result != true) { if (is_string($result)) {
$this->clientError($result); $this->clientError($result);
return; return;
} }

View File

@ -81,13 +81,13 @@ class UnsubscribeAction extends Action
$other = Profile::staticGet('id', $other_id); $other = Profile::staticGet('id', $other_id);
if (!$other) { if (!$other) {
$this->clientError(_('No profile with that id.')); $this->clientError(_('No profile with that ID.'));
return; return;
} }
$result = subs_unsubscribe_to($user, $other); $result = subs_unsubscribe_to($user, $other);
if ($result != true) { if (is_string($result)) {
$this->clientError($result); $this->clientError($result);
return; return;
} }

View File

@ -207,7 +207,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
if (empty($id)) { if (empty($id)) {
common_log_db_error($id, 'INSERT', __FILE__); common_log_db_error($id, 'INSERT', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
return; return;
} }
@ -217,7 +217,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
if (empty($result)) { if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__); common_log_db_error($original, 'UPDATE', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
$user->query('ROLLBACK'); $user->query('ROLLBACK');
return; return;
} }
@ -260,7 +260,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
if (empty($id)) { if (empty($id)) {
common_log_db_error($id, 'INSERT', __FILE__); common_log_db_error($id, 'INSERT', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
return; return;
} }
@ -270,7 +270,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
if (empty($result)) { if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__); common_log_db_error($original, 'UPDATE', __FILE__);
$this->showForm(_('Unable to save your design settings!')); $this->showForm(_('Unable to save your design settings.'));
$user->query('ROLLBACK'); $user->query('ROLLBACK');
return; return;
} }

View File

@ -266,5 +266,6 @@ class VersionAction extends Action
'Craig Andrews', 'Craig Andrews',
'mEDI', 'mEDI',
'Brett Taylor', 'Brett Taylor',
'Brigitte Schuster'); 'Brigitte Schuster',
'Brion Vibber');
} }

View File

@ -11,9 +11,10 @@ class Consumer extends Memcached_DataObject
public $__table = 'consumer'; // table name public $__table = 'consumer'; // table name
public $consumer_key; // varchar(255) primary_key not_null public $consumer_key; // varchar(255) primary_key not_null
public $consumer_secret; // varchar(255) not_null
public $seed; // char(32) not_null public $seed; // char(32) not_null
public $created; // datetime() not_null public $created; // datetime not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */ /* Static get */
function staticGet($k,$v=null) function staticGet($k,$v=null)
@ -21,4 +22,18 @@ class Consumer extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
static function generateNew()
{
$cons = new Consumer();
$rand = common_good_rand(16);
$cons->seed = $rand;
$cons->consumer_key = md5(time() + $rand);
$cons->consumer_secret = md5(md5(time() + time() + $rand));
$cons->created = common_sql_now();
return $cons;
}
} }

View File

@ -80,7 +80,14 @@ class File extends Memcached_DataObject
if (isset($redir_data['type']) if (isset($redir_data['type'])
&& (('text/html' === substr($redir_data['type'], 0, 9) || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21))) && (('text/html' === substr($redir_data['type'], 0, 9) || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21)))
&& ($oembed_data = File_oembed::_getOembed($given_url))) { && ($oembed_data = File_oembed::_getOembed($given_url))) {
$fo = File_oembed::staticGet('file_id', $file_id);
if (empty($fo)) {
File_oembed::saveNew($oembed_data, $file_id); File_oembed::saveNew($oembed_data, $file_id);
} else {
common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__);
}
} }
return $x; return $x;
} }

View File

@ -115,8 +115,14 @@ class File_oembed extends Memcached_DataObject
} }
$file_oembed->insert(); $file_oembed->insert();
if (!empty($data->thumbnail_url)) { if (!empty($data->thumbnail_url)) {
$ft = File_thumbnail::staticGet('file_id', $file_id);
if (!empty($ft)) {
common_log(LOG_WARNING, "Strangely, a File_thumbnail object exists for new file $file_id",
__FILE__);
} else {
File_thumbnail::saveNew($data, $file_id); File_thumbnail::saveNew($data, $file_id);
} }
} }
} }
}

View File

@ -25,4 +25,41 @@ class Group_member extends Memcached_DataObject
{ {
return Memcached_DataObject::pkeyGet('Group_member', $kv); return Memcached_DataObject::pkeyGet('Group_member', $kv);
} }
static function join($group_id, $profile_id)
{
$member = new Group_member();
$member->group_id = $group_id;
$member->profile_id = $profile_id;
$member->created = common_sql_now();
$result = $member->insert();
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
throw new Exception(_("Group join failed."));
}
return true;
}
static function leave($group_id, $profile_id)
{
$member = Group_member::pkeyGet(array('group_id' => $group_id,
'profile_id' => $profile_id));
if (empty($member)) {
throw new Exception(_("Not part of group."));
}
$result = $member->delete();
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
throw new Exception(_("Group leave failed."));
}
return true;
}
} }

180
classes/Inbox.php Normal file
View File

@ -0,0 +1,180 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Data class for user location preferences
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Data
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @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.'/classes/Memcached_DataObject.php';
class Inbox extends Memcached_DataObject
{
const BOXCAR = 128;
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'inbox'; // table name
public $user_id; // int(4) primary_key not_null
public $notice_ids; // blob
/* Static get */
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Inbox',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function sequenceKey()
{
return array(false, false, false);
}
/**
* Create a new inbox from existing Notice_inbox stuff
*/
static function initialize($user_id)
{
$inbox = Inbox::fromNoticeInbox($user_id);
unset($inbox->fake);
$result = $inbox->insert();
if (!$result) {
common_log_db_error($inbox, 'INSERT', __FILE__);
return null;
}
return $inbox;
}
static function fromNoticeInbox($user_id)
{
$ids = array();
$ni = new Notice_inbox();
$ni->user_id = $user_id;
$ni->selectAdd();
$ni->selectAdd('notice_id');
$ni->orderBy('notice_id DESC');
$ni->limit(0, 1024);
if ($ni->find()) {
while($ni->fetch()) {
$ids[] = $ni->notice_id;
}
}
$ni->free();
unset($ni);
$inbox = new Inbox();
$inbox->user_id = $user_id;
$inbox->notice_ids = call_user_func_array('pack', array_merge(array('N*'), $ids));
$inbox->fake = true;
return $inbox;
}
static function insertNotice($user_id, $notice_id)
{
$inbox = DB_DataObject::staticGet('inbox', 'user_id', $user_id);
if (empty($inbox)) {
$inbox = Inbox::initialize($user_id);
}
if (empty($inbox)) {
return false;
}
$result = $inbox->query(sprintf('UPDATE inbox '.
'set notice_ids = concat(cast(0x%08x as binary(4)), '.
'substr(notice_ids, 1, 4092)) '.
'WHERE user_id = %d',
$notice_id, $user_id));
if ($result) {
$c = self::memcache();
if (!empty($c)) {
$c->delete(self::cacheKey('inbox', 'user_id', $user_id));
}
}
return $result;
}
static function bulkInsert($notice_id, $user_ids)
{
foreach ($user_ids as $user_id)
{
Inbox::insertNotice($user_id, $notice_id);
}
}
function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
{
$inbox = Inbox::staticGet('user_id', $user_id);
if (empty($inbox)) {
$inbox = Inbox::fromNoticeInbox($user_id);
if (empty($inbox)) {
return array();
} else {
$inbox->encache();
}
}
$ids = unpack('N*', $inbox->notice_ids);
if (!empty($since_id)) {
$newids = array();
foreach ($ids as $id) {
if ($id > $since_id) {
$newids[] = $id;
}
}
$ids = $newids;
}
if (!empty($max_id)) {
$newids = array();
foreach ($ids as $id) {
if ($id <= $max_id) {
$newids[] = $id;
}
}
$ids = $newids;
}
$ids = array_slice($ids, $offset, $limit);
return $ids;
}
}

View File

@ -40,6 +40,8 @@ class Login_token extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
const TIMEOUT = 120; // seconds after which to timeout the token
/* /*
DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function. DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function.
In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but
@ -52,4 +54,29 @@ class Login_token extends Memcached_DataObject
{ {
return array(false,false); return array(false,false);
} }
function makeNew($user)
{
$login_token = Login_token::staticGet('user_id', $user->id);
if (!empty($login_token)) {
$login_token->delete();
}
$login_token = new Login_token();
$login_token->user_id = $user->id;
$login_token->token = common_good_rand(16);
$login_token->created = common_sql_now();
$result = $login_token->insert();
if (!$result) {
common_log_db_error($login_token, 'INSERT', __FILE__);
throw new Exception(sprintf(_('Could not create login token for %s'),
$user->nickname));
}
return $login_token;
}
} }

View File

@ -19,6 +19,8 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Memcached_DataObject extends DB_DataObject class Memcached_DataObject extends DB_DataObject
{ {
/** /**
@ -66,6 +68,7 @@ class Memcached_DataObject extends DB_DataObject
// Clear this out so we don't accidentally break global // Clear this out so we don't accidentally break global
// state in *this* process. // state in *this* process.
$this->_DB_resultid = null; $this->_DB_resultid = null;
// We don't have any local DBO refs, so clear these out. // We don't have any local DBO refs, so clear these out.
$this->_link_loaded = false; $this->_link_loaded = false;
} }
@ -90,7 +93,9 @@ class Memcached_DataObject extends DB_DataObject
unset($i); unset($i);
} }
$i = Memcached_DataObject::getcached($cls, $k, $v); $i = Memcached_DataObject::getcached($cls, $k, $v);
if ($i === false) { // false == cache miss if ($i) {
return $i;
} else {
$i = DB_DataObject::factory($cls); $i = DB_DataObject::factory($cls);
if (empty($i)) { if (empty($i)) {
$i = false; $i = false;
@ -98,34 +103,22 @@ class Memcached_DataObject extends DB_DataObject
} }
$result = $i->get($k, $v); $result = $i->get($k, $v);
if ($result) { if ($result) {
// Hit!
$i->encache(); $i->encache();
} else {
// save the fact that no such row exists
$c = self::memcache();
if (!empty($c)) {
$ck = self::cachekey($cls, $k, $v);
$c->set($ck, null);
}
$i = false;
}
}
return $i; return $i;
} else {
$i = false;
return $i;
}
}
} }
/** function &pkeyGet($cls, $kv)
* @fixme Should this return false on lookup fail to match staticGet?
*/
function pkeyGet($cls, $kv)
{ {
$i = Memcached_DataObject::multicache($cls, $kv); $i = Memcached_DataObject::multicache($cls, $kv);
if ($i !== false) { // false == cache miss if ($i) {
return $i; return $i;
} else { } else {
$i = DB_DataObject::factory($cls); $i = new $cls();
if (empty($i)) {
return false;
}
foreach ($kv as $k => $v) { foreach ($kv as $k => $v) {
$i->$k = $v; $i->$k = $v;
} }
@ -133,11 +126,6 @@ class Memcached_DataObject extends DB_DataObject
$i->encache(); $i->encache();
} else { } else {
$i = null; $i = null;
$c = self::memcache();
if (!empty($c)) {
$ck = self::multicacheKey($cls, $kv);
$c->set($ck, null);
}
} }
return $i; return $i;
} }
@ -146,9 +134,6 @@ class Memcached_DataObject extends DB_DataObject
function insert() function insert()
{ {
$result = parent::insert(); $result = parent::insert();
if ($result) {
$this->encache(); // in case of cached negative lookups
}
return $result; return $result;
} }
@ -188,23 +173,21 @@ class Memcached_DataObject extends DB_DataObject
if (!$c) { if (!$c) {
return false; return false;
} else { } else {
return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); $obj = $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
if (0 == strcasecmp($cls, 'User')) {
// Special case for User
if (is_object($obj) && is_object($obj->id)) {
common_log(LOG_ERR, "User " . $obj->nickname . " was cached with User as ID; deleting");
$c->delete(Memcached_DataObject::cacheKey($cls, $k, $v));
return false;
}
}
return $obj;
} }
} }
function keyTypes() function keyTypes()
{ {
// ini-based classes return number-indexed arrays. handbuilt
// classes return column => keytype. Make this uniform.
$keys = $this->keys();
$keyskeys = array_keys($keys);
if (is_string($keyskeys[0])) {
return $keys;
}
global $_DB_DATAOBJECT; global $_DB_DATAOBJECT;
if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
$this->databaseStructure(); $this->databaseStructure();
@ -216,88 +199,71 @@ class Memcached_DataObject extends DB_DataObject
function encache() function encache()
{ {
$c = $this->memcache(); $c = $this->memcache();
if (!$c) { if (!$c) {
return false; return false;
} else if ($this->tableName() == 'user' && is_object($this->id)) {
// Special case for User bug
$e = new Exception();
common_log(LOG_ERR, __METHOD__ . ' caching user with User object as ID ' .
str_replace("\n", " ", $e->getTraceAsString()));
return false;
} else {
$pkey = array();
$pval = array();
$types = $this->keyTypes();
ksort($types);
foreach ($types as $key => $type) {
if ($type == 'K') {
$pkey[] = $key;
$pval[] = $this->$key;
} else {
$c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this);
} }
}
$keys = $this->_allCacheKeys(); # XXX: should work for both compound and scalar pkeys
$pvals = implode(',', $pval);
foreach ($keys as $key) { $pkeys = implode(',', $pkey);
$c->set($key, $this); $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this);
} }
} }
function decache() function decache()
{ {
$c = $this->memcache(); $c = $this->memcache();
if (!$c) { if (!$c) {
return false; return false;
} } else {
$keys = $this->_allCacheKeys();
foreach ($keys as $key) {
$c->delete($key, $this);
}
}
function _allCacheKeys()
{
$ckeys = array();
$types = $this->keyTypes();
ksort($types);
$pkey = array(); $pkey = array();
$pval = array(); $pval = array();
$types = $this->keyTypes();
ksort($types);
foreach ($types as $key => $type) { foreach ($types as $key => $type) {
if ($type == 'K') {
assert(!empty($key));
if ($type == 'U') {
if (empty($this->$key)) {
continue;
}
$ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key);
} else if ($type == 'K' || $type == 'N') {
$pkey[] = $key; $pkey[] = $key;
$pval[] = $this->$key; $pval[] = $this->$key;
} else { } else {
throw new Exception("Unknown key type $key => $type for " . $this->tableName()); $c->delete($this->cacheKey($this->tableName(), $key, $this->$key));
} }
} }
# should work for both compound and scalar pkeys
assert(count($pkey) > 0); # XXX: comma works for now but may not be safe separator for future keys
// XXX: should work for both compound and scalar pkeys
$pvals = implode(',', $pval); $pvals = implode(',', $pval);
$pkeys = implode(',', $pkey); $pkeys = implode(',', $pkey);
$c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals));
$ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals); }
return $ckeys;
} }
function multicache($cls, $kv) function multicache($cls, $kv)
{ {
ksort($kv); ksort($kv);
$c = self::memcache(); $c = Memcached_DataObject::memcache();
if (!$c) { if (!$c) {
return false; return false;
} else { } else {
return $c->get(self::multicacheKey($cls, $kv));
}
}
static function multicacheKey($cls, $kv)
{
ksort($kv);
$pkeys = implode(',', array_keys($kv)); $pkeys = implode(',', array_keys($kv));
$pvals = implode(',', array_values($kv)); $pvals = implode(',', array_values($kv));
return self::cacheKey($cls, $pkeys, $pvals); return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals));
}
} }
function getSearchEngine($table) function getSearchEngine($table)
@ -334,8 +300,7 @@ class Memcached_DataObject extends DB_DataObject
$key_part = common_keyize($cls).':'.md5($qry); $key_part = common_keyize($cls).':'.md5($qry);
$ckey = common_cache_key($key_part); $ckey = common_cache_key($key_part);
$stored = $c->get($ckey); $stored = $c->get($ckey);
if ($stored) {
if ($stored !== false) {
return new ArrayWrapper($stored); return new ArrayWrapper($stored);
} }
@ -366,6 +331,29 @@ class Memcached_DataObject extends DB_DataObject
$exists = false; $exists = false;
} }
// @fixme horrible evil hack!
//
// In multisite configuration we don't want to keep around a separate
// connection for every database; we could end up with thousands of
// connections open per thread. In an ideal world we might keep
// a connection per server and select different databases, but that'd
// be reliant on having the same db username/pass as well.
//
// MySQL connections are cheap enough we're going to try just
// closing out the old connection and reopening when we encounter
// a new DSN.
//
// WARNING WARNING if we end up actually using multiple DBs at a time
// we'll need some fancier logic here.
if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) {
foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
if (!empty($conn)) {
$conn->disconnect();
}
unset($_DB_DATAOBJECT['CONNECTIONS'][$index]);
}
}
$result = parent::_connect(); $result = parent::_connect();
if ($result && !$exists) { if ($result && !$exists) {

View File

@ -125,8 +125,7 @@ class Notice extends Memcached_DataObject
'Fave', 'Fave',
'Notice_tag', 'Notice_tag',
'Group_inbox', 'Group_inbox',
'Queue_item', 'Queue_item');
'Notice_inbox');
foreach ($related as $cls) { foreach ($related as $cls) {
$inst = new $cls(); $inst = new $cls();
@ -276,7 +275,6 @@ class Notice extends Memcached_DataObject
if (isset($repeat_of)) { if (isset($repeat_of)) {
$notice->repeat_of = $repeat_of; $notice->repeat_of = $repeat_of;
$notice->reply_to = $repeat_of;
} else { } else {
$notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
} }
@ -300,8 +298,6 @@ class Notice extends Memcached_DataObject
// XXX: some of these functions write to the DB // XXX: some of these functions write to the DB
$notice->query('BEGIN');
$id = $notice->insert(); $id = $notice->insert();
if (!$id) { if (!$id) {
@ -339,12 +335,14 @@ class Notice extends Memcached_DataObject
$notice->saveTags(); $notice->saveTags();
$notice->addToInboxes(); $groups = $notice->saveGroups();
$recipients = $notice->saveReplies();
$notice->addToInboxes($groups, $recipients);
$notice->saveUrls(); $notice->saveUrls();
$notice->query('COMMIT');
Event::handle('EndNoticeSave', array($notice)); Event::handle('EndNoticeSave', array($notice));
} }
@ -503,20 +501,6 @@ class Notice extends Memcached_DataObject
$original->free(); $original->free();
unset($original); unset($original);
} }
$ni = new Notice_inbox();
$ni->notice_id = $this->id;
if ($ni->find()) {
while ($ni->fetch()) {
$tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id);
$cache->delete($tmk);
}
}
$ni->free();
unset($ni);
} }
} }
} }
@ -842,11 +826,28 @@ class Notice extends Memcached_DataObject
return $ids; return $ids;
} }
function addToInboxes() /**
* @param $groups array of Group *objects*
* @param $recipients array of profile *ids*
*/
function whoGets($groups=null, $recipients=null)
{ {
// XXX: loads constants $c = self::memcache();
$inbox = new Notice_inbox(); if (!empty($c)) {
$ni = $c->get(common_cache_key('notice:who_gets:'.$this->id));
if ($ni !== false) {
return $ni;
}
}
if (is_null($groups)) {
$groups = $this->getGroups();
}
if (is_null($recipients)) {
$recipients = $this->getReplies();
}
$users = $this->getSubscribedUsers(); $users = $this->getSubscribedUsers();
@ -860,7 +861,6 @@ class Notice extends Memcached_DataObject
$ni[$id] = NOTICE_INBOX_SOURCE_SUB; $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
} }
$groups = $this->saveGroups();
$profile = $this->getProfile(); $profile = $this->getProfile();
foreach ($groups as $group) { foreach ($groups as $group) {
@ -875,8 +875,6 @@ class Notice extends Memcached_DataObject
} }
} }
$recipients = $this->saveReplies();
foreach ($recipients as $recipient) { foreach ($recipients as $recipient) {
if (!array_key_exists($recipient, $ni)) { if (!array_key_exists($recipient, $ni)) {
@ -887,7 +885,19 @@ class Notice extends Memcached_DataObject
} }
} }
Notice_inbox::bulkInsert($this->id, $this->created, $ni); if (!empty($c)) {
// XXX: pack this data better
$c->set(common_cache_key('notice:who_gets:'.$this->id), $ni);
}
return $ni;
}
function addToInboxes($groups, $recipients)
{
$ni = $this->whoGets($groups, $recipients);
Inbox::bulkInsert($this->id, array_keys($ni));
return; return;
} }
@ -919,8 +929,17 @@ class Notice extends Memcached_DataObject
return $ids; return $ids;
} }
/**
* @return array of Group objects
*/
function saveGroups() function saveGroups()
{ {
// Don't save groups for repeats
if (!empty($this->repeat_of)) {
return array();
}
$groups = array(); $groups = array();
/* extract all !group */ /* extract all !group */
@ -991,6 +1010,12 @@ class Notice extends Memcached_DataObject
*/ */
function saveReplies() function saveReplies()
{ {
// Don't save reply data for repeats
if (!empty($this->repeat_of)) {
return array();
}
// Alternative reply format // Alternative reply format
$tname = false; $tname = false;
if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) { if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) {
@ -1077,6 +1102,63 @@ class Notice extends Memcached_DataObject
return $recipientIds; return $recipientIds;
} }
function getReplies()
{
// XXX: cache me
$ids = array();
$reply = new Reply();
$reply->selectAdd();
$reply->selectAdd('profile_id');
$reply->notice_id = $this->id;
if ($reply->find()) {
while($reply->fetch()) {
$ids[] = $reply->profile_id;
}
}
$reply->free();
return $ids;
}
/**
* Same calculation as saveGroups but without the saving
* @fixme merge the functions
* @return array of Group objects
*/
function getGroups()
{
// Don't save groups for repeats
if (!empty($this->repeat_of)) {
return array();
}
// XXX: cache me
$groups = array();
$gi = new Group_inbox();
$gi->selectAdd();
$gi->selectAdd('group_id');
$gi->notice_id = $this->id;
if ($gi->find()) {
while ($gi->fetch()) {
$groups[] = clone($gi);
}
}
$gi->free();
return $groups;
}
function asAtomEntry($namespace=false, $source=false) function asAtomEntry($namespace=false, $source=false)
{ {
$profile = $this->getProfile(); $profile = $this->getProfile();

View File

@ -1,7 +1,7 @@
<?php <?php
/* /*
* StatusNet - the distributed open-source microblogging tool * StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc. * Copyright (C) 2008-2010, StatusNet, Inc.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -17,7 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (!defined('STATUSNET')) {
exit(1);
}
require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
@ -29,12 +31,6 @@ define('NOTICE_INBOX_GC_MAX', 12800);
define('NOTICE_INBOX_LIMIT', 1000); define('NOTICE_INBOX_LIMIT', 1000);
define('NOTICE_INBOX_SOFT_LIMIT', 1000); define('NOTICE_INBOX_SOFT_LIMIT', 1000);
define('NOTICE_INBOX_SOURCE_SUB', 1);
define('NOTICE_INBOX_SOURCE_GROUP', 2);
define('NOTICE_INBOX_SOURCE_REPLY', 3);
define('NOTICE_INBOX_SOURCE_FORWARD', 4);
define('NOTICE_INBOX_SOURCE_GATEWAY', -1);
class Notice_inbox extends Memcached_DataObject class Notice_inbox extends Memcached_DataObject
{ {
###START_AUTOCODE ###START_AUTOCODE
@ -55,139 +51,31 @@ class Notice_inbox extends Memcached_DataObject
function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
{ {
return Notice::stream(array('Notice_inbox', '_streamDirect'), throw new Exception('Notice_inbox no longer used; use Inbox');
array($user_id, $own),
($own) ? 'notice_inbox:by_user:'.$user_id :
'notice_inbox:by_user_own:'.$user_id,
$offset, $limit, $since_id, $max_id, $since);
} }
function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since)
{ {
$inbox = new Notice_inbox(); throw new Exception('Notice_inbox no longer used; use Inbox');
$inbox->user_id = $user_id;
if (!$own) {
$inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY);
} }
if ($since_id != 0) { function &pkeyGet($kv)
$inbox->whereAdd('notice_id > ' . $since_id);
}
if ($max_id != 0) {
$inbox->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {
$inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$inbox->orderBy('created DESC');
if (!is_null($offset)) {
$inbox->limit($offset, $limit);
}
$ids = array();
if ($inbox->find()) {
while ($inbox->fetch()) {
$ids[] = $inbox->notice_id;
}
}
return $ids;
}
function pkeyGet($kv)
{ {
return Memcached_DataObject::pkeyGet('Notice_inbox', $kv); return Memcached_DataObject::pkeyGet('Notice_inbox', $kv);
} }
/**
* Trim inbox for a given user to latest NOTICE_INBOX_LIMIT items
* (up to NOTICE_INBOX_GC_MAX will be deleted).
*
* @param int $user_id
* @return int count of notices dropped from the inbox, if any
*/
static function gc($user_id) static function gc($user_id)
{ {
$entry = new Notice_inbox(); throw new Exception('Notice_inbox no longer used; use Inbox');
$entry->user_id = $user_id;
$entry->orderBy('created DESC');
$entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX);
$total = $entry->find();
if ($total > 0) {
$notices = array();
$cnt = 0;
while ($entry->fetch()) {
$notices[] = $entry->notice_id;
$cnt++;
if ($cnt >= NOTICE_INBOX_GC_BOXCAR) {
self::deleteMatching($user_id, $notices);
$notices = array();
$cnt = 0;
}
}
if ($cnt > 0) {
self::deleteMatching($user_id, $notices);
$notices = array();
}
}
return $total;
} }
static function deleteMatching($user_id, $notices) static function deleteMatching($user_id, $notices)
{ {
$entry = new Notice_inbox(); throw new Exception('Notice_inbox no longer used; use Inbox');
return $entry->query('DELETE FROM notice_inbox '.
'WHERE user_id = ' . $user_id . ' ' .
'AND notice_id in ('.implode(',', $notices).')');
} }
static function bulkInsert($notice_id, $created, $ni) static function bulkInsert($notice_id, $created, $ni)
{ {
$cnt = 0; throw new Exception('Notice_inbox no longer used; use Inbox');
$qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
$qry = $qryhdr;
foreach ($ni as $id => $source) {
if ($cnt > 0) {
$qry .= ', ';
}
$qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') ";
$cnt++;
if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
// FIXME: Causes lag in replicated servers
// Notice_inbox::gc($id);
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
$qry = $qryhdr;
$cnt = 0;
}
}
if ($cnt > 0) {
$inbox = new Notice_inbox();
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
}
return;
} }
} }

View File

@ -0,0 +1,140 @@
<?php
/**
* Table Definition for oauth_application
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Oauth_application extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'oauth_application'; // table name
public $id; // int(4) primary_key not_null
public $owner; // int(4) not_null
public $consumer_key; // varchar(255) not_null
public $name; // varchar(255) not_null
public $description; // varchar(255)
public $icon; // varchar(255) not_null
public $source_url; // varchar(255)
public $organization; // varchar(255)
public $homepage; // varchar(255)
public $callback_url; // varchar(255) not_null
public $type; // tinyint(1)
public $access_type; // tinyint(1)
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) {
return Memcached_DataObject::staticGet('Oauth_application',$k,$v);
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
// Bit flags
public static $readAccess = 1;
public static $writeAccess = 2;
public static $browser = 1;
public static $desktop = 2;
function getConsumer()
{
return Consumer::staticGet('consumer_key', $this->consumer_key);
}
static function maxDesc()
{
$desclimit = common_config('application', 'desclimit');
// null => use global limit (distinct from 0!)
if (is_null($desclimit)) {
$desclimit = common_config('site', 'textlimit');
}
return $desclimit;
}
static function descriptionTooLong($desc)
{
$desclimit = self::maxDesc();
return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
}
function setAccessFlags($read, $write)
{
if ($read) {
$this->access_type |= self::$readAccess;
} else {
$this->access_type &= ~self::$readAccess;
}
if ($write) {
$this->access_type |= self::$writeAccess;
} else {
$this->access_type &= ~self::$writeAccess;
}
}
function setOriginal($filename)
{
$imagefile = new ImageFile($this->id, Avatar::path($filename));
// XXX: Do we want to have a bunch of different size icons? homepage, stream, mini?
// or just one and control size via CSS? --Zach
$orig = clone($this);
$this->icon = Avatar::url($filename);
common_debug(common_log_objstring($this));
return $this->update($orig);
}
static function getByConsumerKey($key)
{
if (empty($key)) {
return null;
}
$app = new Oauth_application();
$app->consumer_key = $key;
$app->limit(1);
$result = $app->find(true);
return empty($result) ? null : $app;
}
/**
* Handle an image upload
*
* Does all the magic for handling an image upload, and crops the
* image by default.
*
* @return void
*/
function uploadLogo()
{
if ($_FILES['app_icon']['error'] ==
UPLOAD_ERR_OK) {
try {
$imagefile = ImageFile::fromUpload('app_icon');
} catch (Exception $e) {
common_debug("damn that sucks");
$this->showForm($e->getMessage());
return;
}
$filename = Avatar::filename($this->id,
image_type_to_extension($imagefile->type),
null,
'oauth-app-icon-'.common_timestamp());
$filepath = Avatar::path($filename);
move_uploaded_file($imagefile->filepath, $filepath);
$this->setOriginal($filename);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Table Definition for oauth_application_user
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Oauth_application_user extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'oauth_application_user'; // table name
public $profile_id; // int(4) primary_key not_null
public $application_id; // int(4) primary_key not_null
public $access_type; // tinyint(1)
public $token; // varchar(255)
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) {
return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v);
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
static function getByKeys($user, $app)
{
if (empty($user) || empty($app)) {
return null;
}
$oau = new Oauth_application_user();
$oau->profile_id = $user->id;
$oau->application_id = $app->id;
$oau->limit(1);
$result = $oau->find(true);
return empty($result) ? null : $oau;
}
}

View File

@ -352,6 +352,31 @@ class Profile extends Memcached_DataObject
return $profile; return $profile;
} }
function getApplications($offset = 0, $limit = null)
{
$qry =
'SELECT a.* ' .
'FROM oauth_application_user u, oauth_application a ' .
'WHERE u.profile_id = %d ' .
'AND a.id = u.application_id ' .
'AND u.access_type > 0 ' .
'ORDER BY u.created DESC ';
if ($offset > 0) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$application = new Oauth_application();
$cnt = $application->query(sprintf($qry, $this->id));
return $application;
}
function subscriptionCount() function subscriptionCount()
{ {
$c = common_memcache(); $c = common_memcache();

View File

@ -25,10 +25,12 @@ class Queue_item extends Memcached_DataObject
function sequenceKey() function sequenceKey()
{ return array(false, false); } { return array(false, false); }
static function top($transport) { static function top($transport=null) {
$qi = new Queue_item(); $qi = new Queue_item();
if ($transport) {
$qi->transport = $transport; $qi->transport = $transport;
}
$qi->orderBy('created'); $qi->orderBy('created');
$qi->whereAdd('claimed is null'); $qi->whereAdd('claimed is null');
@ -40,7 +42,8 @@ class Queue_item extends Memcached_DataObject
# XXX: potential race condition # XXX: potential race condition
# can we force it to only update if claimed is still null # can we force it to only update if claimed is still null
# (or old)? # (or old)?
common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport); common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id .
' for transport ' . $qi->transport);
$orig = clone($qi); $orig = clone($qi);
$qi->claimed = common_sql_now(); $qi->claimed = common_sql_now();
$result = $qi->update($orig); $result = $qi->update($orig);

View File

@ -49,6 +49,13 @@ class Status_network extends DB_DataObject
static $cache = null; static $cache = null;
static $base = null; static $base = null;
/**
* @param string $dbhost
* @param string $dbuser
* @param string $dbpass
* @param string $dbname
* @param array $servers memcached servers to use for caching config info
*/
static function setupDB($dbhost, $dbuser, $dbpass, $dbname, $servers) static function setupDB($dbhost, $dbuser, $dbpass, $dbname, $servers)
{ {
global $config; global $config;
@ -60,12 +67,17 @@ class Status_network extends DB_DataObject
if (class_exists('Memcache')) { if (class_exists('Memcache')) {
self::$cache = new Memcache(); self::$cache = new Memcache();
// Can't close persistent connections, making forking painful.
//
// @fixme only do this in *parent* CLI processes.
// single-process and child-processes *should* use persistent.
$persist = php_sapi_name() != 'cli';
if (is_array($servers)) { if (is_array($servers)) {
foreach($servers as $server) { foreach($servers as $server) {
self::$cache->addServer($server); self::$cache->addServer($server, 11211, $persist);
} }
} else { } else {
self::$cache->addServer($servers); self::$cache->addServer($servers, 11211, $persist);
} }
} }
@ -89,7 +101,7 @@ class Status_network extends DB_DataObject
if (empty($sn)) { if (empty($sn)) {
$sn = self::staticGet($k, $v); $sn = self::staticGet($k, $v);
if (!empty($sn)) { if (!empty($sn)) {
self::$cache->set($ck, $sn); self::$cache->set($ck, clone($sn));
} }
} }
@ -121,6 +133,11 @@ class Status_network extends DB_DataObject
return parent::delete(); return parent::delete();
} }
/**
* @param string $servername hostname
* @param string $pathname URL base path
* @param string $wildcard hostname suffix to match wildcard config
*/
static function setupSite($servername, $pathname, $wildcard) static function setupSite($servername, $pathname, $wildcard)
{ {
global $config; global $config;
@ -150,9 +167,19 @@ class Status_network extends DB_DataObject
} }
if (!empty($sn)) { if (!empty($sn)) {
if (!empty($sn->hostname) && 0 != strcasecmp($sn->hostname, $servername)) {
$sn->redirectToHostname(); // Redirect to the right URL
if (!empty($sn->hostname) &&
empty($_SERVER['HTTPS']) &&
0 != strcasecmp($sn->hostname, $servername)) {
$sn->redirectTo('http://'.$sn->hostname.$_SERVER['REQUEST_URI']);
} else if (!empty($_SERVER['HTTPS']) &&
0 != strcasecmp($sn->hostname, $servername) &&
0 != strcasecmp($sn->nickname.'.'.$wildcard, $servername)) {
$sn->redirectTo('https://'.$sn->nickname.'.'.$wildcard.$_SERVER['REQUEST_URI']);
} }
$dbhost = (empty($sn->dbhost)) ? 'localhost' : $sn->dbhost; $dbhost = (empty($sn->dbhost)) ? 'localhost' : $sn->dbhost;
$dbuser = (empty($sn->dbuser)) ? $sn->nickname : $sn->dbuser; $dbuser = (empty($sn->dbuser)) ? $sn->nickname : $sn->dbuser;
$dbpass = $sn->dbpass; $dbpass = $sn->dbpass;
@ -162,6 +189,10 @@ class Status_network extends DB_DataObject
$config['site']['name'] = $sn->sitename; $config['site']['name'] = $sn->sitename;
if (!empty($sn->hostname)) {
$config['site']['server'] = $sn->hostname;
}
if (!empty($sn->theme)) { if (!empty($sn->theme)) {
$config['site']['theme'] = $sn->theme; $config['site']['theme'] = $sn->theme;
} }
@ -179,11 +210,8 @@ class Status_network extends DB_DataObject
// (C) 2006 by Heiko Richler http://www.richler.de/ // (C) 2006 by Heiko Richler http://www.richler.de/
// LGPL // LGPL
function redirectToHostname() function redirectTo($destination)
{ {
$destination = 'http://'.$this->hostname;
$destination .= $_SERVER['REQUEST_URI'];
$old = 'http'. $old = 'http'.
(($_SERVER['HTTPS'] == 'on') ? 'S' : ''). (($_SERVER['HTTPS'] == 'on') ? 'S' : '').
'://'. '://'.

View File

@ -15,6 +15,8 @@ class Token extends Memcached_DataObject
public $secret; // char(32) not_null public $secret; // char(32) not_null
public $type; // tinyint(1) not_null public $type; // tinyint(1) not_null
public $state; // tinyint(1) public $state; // tinyint(1)
public $verifier; // varchar(255)
public $verified_callback; // varchar(255)
public $created; // datetime() not_null public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP

View File

@ -291,6 +291,20 @@ class User extends Memcached_DataObject
return false; return false;
} }
// Everyone gets an inbox
$inbox = new Inbox();
$inbox->user_id = $user->id;
$inbox->notice_ids = '';
$result = $inbox->insert();
if (!$result) {
common_log_db_error($inbox, 'INSERT', __FILE__);
return false;
}
// Everyone is subscribed to themself // Everyone is subscribed to themself
$subscription = new Subscription(); $subscription = new Subscription();
@ -482,89 +496,30 @@ class User extends Memcached_DataObject
function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{ {
$ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{ {
$ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{ {
$ids = Notice::stream(array($this, '_friendsTimelineDirect'), $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false);
array(false),
'user:friends_timeline:'.$this->id,
$offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{ {
$ids = Notice::stream(array($this, '_friendsTimelineDirect'), $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true);
array(true),
'user:friends_timeline_own:'.$this->id,
$offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since)
{
$qry =
'SELECT notice.id AS id ' .
'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
'WHERE notice_inbox.user_id = ' . $this->id . ' ' .
'AND notice.repeat_of IS NULL ';
if (!$own) {
// XXX: autoload notice inbox for constant
$inbox = new Notice_inbox();
$qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' ';
}
if ($since_id != 0) {
$qry .= 'AND notice.id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice.id <= ' . $max_id . ' ';
}
if (!is_null($since)) {
$qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY notice_id DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
$ids = array();
$notice = new Notice();
$notice->query($qry);
while ($notice->fetch()) {
$ids[] = $notice->id;
}
$notice->free();
$notice = NULL;
return $ids;
}
function blowFavesCache() function blowFavesCache()
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -777,7 +732,6 @@ class User extends Memcached_DataObject
'Remember_me', 'Remember_me',
'Foreign_link', 'Foreign_link',
'Invitation', 'Invitation',
'Notice_inbox',
); );
Event::handle('UserDeleteRelated', array($this, &$related)); Event::handle('UserDeleteRelated', array($this, &$related));
@ -945,56 +899,7 @@ class User extends Memcached_DataObject
function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{ {
$ids = Notice::stream(array($this, '_repeatedToMeDirect'), throw new Exception("Not implemented since inbox change.");
array(),
'user:repeated_to_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
return Notice::getStreamByIds($ids);
}
function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since)
{
$qry =
'SELECT notice.id AS id ' .
'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
'WHERE notice_inbox.user_id = ' . $this->id . ' ' .
'AND notice.repeat_of IS NOT NULL ';
if ($since_id != 0) {
$qry .= 'AND notice.id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice.id <= ' . $max_id . ' ';
}
if (!is_null($since)) {
$qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY notice.id DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
$ids = array();
$notice = new Notice();
$notice->query($qry);
while ($notice->fetch()) {
$ids[] = $notice->id;
}
$notice->free();
$notice = NULL;
return $ids;
} }
function shareLocation() function shareLocation()

View File

@ -39,6 +39,7 @@ code = K
[consumer] [consumer]
consumer_key = 130 consumer_key = 130
consumer_secret = 130
seed = 130 seed = 130
created = 142 created = 142
modified = 384 modified = 384
@ -241,6 +242,13 @@ address = 130
address_type = 130 address_type = 130
created = 142 created = 142
[inbox]
user_id = 129
notice_ids = 66
[inbox__keys]
user_id = K
[invitation__keys] [invitation__keys]
code = K code = K
@ -341,6 +349,37 @@ created = 142
tag = K tag = K
notice_id = K notice_id = K
[oauth_application]
id = 129
owner = 129
consumer_key = 130
name = 130
description = 2
icon = 130
source_url = 2
organization = 2
homepage = 2
callback_url = 130
type = 17
access_type = 17
created = 142
modified = 384
[oauth_application__keys]
id = N
[oauth_application_user]
profile_id = 129
application_id = 129
access_type = 17
token = 2
created = 142
modified = 384
[oauth_application_user__keys]
profile_id = K
application_id = K
[profile] [profile]
id = 129 id = 129
nickname = 130 nickname = 130
@ -477,6 +516,8 @@ tok = 130
secret = 130 secret = 130
type = 145 type = 145
state = 17 state = 17
verifier = 2
verified_callback = 2
created = 142 created = 142
modified = 384 modified = 384

17
db/rc2torc3.sql Normal file
View File

@ -0,0 +1,17 @@
create table user_location_prefs (
user_id integer not null comment 'user who has the preference' references user (id),
share_location tinyint default 1 comment 'Whether to share location data',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table inbox (
user_id integer not null comment 'user receiving the notice' references user (id),
notice_ids blob comment 'packed list of notice ids',
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -176,6 +176,7 @@ create table fave (
create table consumer ( create table consumer (
consumer_key varchar(255) primary key comment 'unique identifier, root URL', consumer_key varchar(255) primary key comment 'unique identifier, root URL',
consumer_secret varchar(255) not null comment 'secret value',
seed char(32) not null comment 'seed for new tokens by this consumer', seed char(32) not null comment 'seed for new tokens by this consumer',
created datetime not null comment 'date this record was created', created datetime not null comment 'date this record was created',
@ -188,6 +189,8 @@ create table token (
secret char(32) not null comment 'secret value', secret char(32) not null comment 'secret value',
type tinyint not null default 0 comment 'request or access', type tinyint not null default 0 comment 'request or access',
state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used', state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used',
verifier varchar(255) comment 'verifier string for OAuth 1.0a',
verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a',
created datetime not null comment 'date this record was created', created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified', modified timestamp comment 'date this record was modified',
@ -207,6 +210,33 @@ create table nonce (
constraint primary key (consumer_key, ts, nonce) constraint primary key (consumer_key, ts, nonce)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table oauth_application (
id integer auto_increment primary key comment 'unique identifier',
owner integer not null comment 'owner of the application' references profile (id),
consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key),
name varchar(255) not null comment 'name of the application',
description varchar(255) comment 'description of the application',
icon varchar(255) not null comment 'application icon',
source_url varchar(255) comment 'application homepage - used for source link',
organization varchar(255) comment 'name of the organization running the application',
homepage varchar(255) comment 'homepage for the organization',
callback_url varchar(255) comment 'url to redirect to after authentication',
type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop',
access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table oauth_application_user (
profile_id integer not null comment 'user of the application' references profile (id),
application_id integer not null comment 'id of the application' references oauth_application (id),
access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
token varchar(255) comment 'request or access token',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (profile_id, application_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
/* These are used by JanRain OpenID library */ /* These are used by JanRain OpenID library */
create table oid_associations ( create table oid_associations (
@ -596,3 +626,11 @@ create table user_location_prefs (
constraint primary key (user_id) constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table inbox (
user_id integer not null comment 'user receiving the notice' references user (id),
notice_ids blob comment 'packed list of notice ids',
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -1,5 +1,5 @@
// A shim to implement the W3C Geolocation API Specification using Gears // A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API
if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){ if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) { (function(){
// -- BEGIN GEARS_INIT // -- BEGIN GEARS_INIT
(function() { (function() {
@ -23,8 +23,7 @@ if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim )
} }
} catch (e) { } catch (e) {
// Safari // Safari
if ((typeof navigator.mimeTypes != 'undefined') if ((typeof navigator.mimeTypes != 'undefined') && navigator.mimeTypes["application/x-googlegears"]) {
&& navigator.mimeTypes["application/x-googlegears"]) {
factory = document.createElement("object"); factory = document.createElement("object");
factory.style.display = "none"; factory.style.display = "none";
factory.width = 0; factory.width = 0;
@ -64,8 +63,8 @@ var GearsGeoLocation = (function() {
return function(position) { return function(position) {
callback(position); callback(position);
self.lastPosition = position; self.lastPosition = position;
} };
} };
// -- PUBLIC // -- PUBLIC
return { return {
@ -96,9 +95,123 @@ var GearsGeoLocation = (function() {
}; };
}); });
// If you have Gears installed use that var AjaxGeoLocation = (function() {
if (window.google && google.gears) { // -- PRIVATE
navigator.geolocation = GearsGeoLocation(); var loading = false;
var loadGoogleLoader = function() {
if (!hasGoogleLoader() && !loading) {
loading = true;
var s = document.createElement('script');
s.src = (document.location.protocol == "https:"?"https://":"http://") + 'www.google.com/jsapi?callback=_google_loader_apiLoaded';
s.type = "text/javascript";
document.getElementsByTagName('body')[0].appendChild(s);
}
};
var queue = [];
var addLocationQueue = function(callback) {
queue.push(callback);
};
var runLocationQueue = function() {
if (hasGoogleLoader()) {
while (queue.length > 0) {
var call = queue.pop();
call();
}
}
};
window['_google_loader_apiLoaded'] = function() {
runLocationQueue();
};
var hasGoogleLoader = function() {
return (window['google'] && google['loader']);
};
var checkGoogleLoader = function(callback) {
if (hasGoogleLoader()) { return true; }
addLocationQueue(callback);
loadGoogleLoader();
return false;
};
loadGoogleLoader(); // start to load as soon as possible just in case
// -- PUBLIC
return {
shim: true,
type: "ClientLocation",
lastPosition: null,
getCurrentPosition: function(successCallback, errorCallback, options) {
var self = this;
if (!checkGoogleLoader(function() {
self.getCurrentPosition(successCallback, errorCallback, options);
})) { return; }
if (google.loader.ClientLocation) {
var cl = google.loader.ClientLocation;
var position = {
coords: {
latitude: cl.latitude,
longitude: cl.longitude,
altitude: null,
accuracy: 43000, // same as Gears accuracy over wifi?
altitudeAccuracy: null,
heading: null,
speed: null
},
// extra info that is outside of the bounds of the core API
address: {
city: cl.address.city,
country: cl.address.country,
country_code: cl.address.country_code,
region: cl.address.region
},
timestamp: new Date()
};
successCallback(position);
this.lastPosition = position;
} else if (errorCallback === "function") {
errorCallback({ code: 3, message: "Using the Google ClientLocation API and it is not able to calculate a location."});
}
},
watchPosition: function(successCallback, errorCallback, options) {
this.getCurrentPosition(successCallback, errorCallback, options);
var self = this;
var watchId = setInterval(function() {
self.getCurrentPosition(successCallback, errorCallback, options);
}, 10000);
return watchId;
},
clearWatch: function(watchId) {
clearInterval(watchId);
},
getPermission: function(siteName, imageUrl, extraMessage) {
// for now just say yes :)
return true;
} }
};
});
// If you have Gears installed use that, else use Ajax ClientLocation
navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation();
})(); })();
}

View File

@ -289,6 +289,7 @@ var SN = { // StatusNet
} }
} }
$('#'+form_id).resetForm(); $('#'+form_id).resetForm();
$('#'+form_id+' #'+SN.C.S.NoticeInReplyTo).val('');
$('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove(); $('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements($('#'+form_id)); SN.U.FormNoticeEnhancements($('#'+form_id));
} }
@ -480,8 +481,9 @@ var SN = { // StatusNet
var NDGe = $('#'+SN.C.S.NoticeDataGeo); var NDGe = $('#'+SN.C.S.NoticeDataGeo);
function removeNoticeDataGeo() { function removeNoticeDataGeo() {
$('label[for='+SN.C.S.NoticeDataGeo+']').removeClass('checked').attr('title', jQuery.trim($('label[for='+SN.C.S.NoticeDataGeo+']').text())); $('label[for='+SN.C.S.NoticeDataGeo+']')
$('#'+SN.C.S.NoticeDataGeoSelected).hide(); .attr('title', jQuery.trim($('label[for='+SN.C.S.NoticeDataGeo+']').text()))
.removeClass('checked');
$('#'+SN.C.S.NoticeLat).val(''); $('#'+SN.C.S.NoticeLat).val('');
$('#'+SN.C.S.NoticeLon).val(''); $('#'+SN.C.S.NoticeLon).val('');
@ -492,7 +494,7 @@ var SN = { // StatusNet
$.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled');
} }
function getJSONgeocodeURL(geocodeURL, data) { function getJSONgeocodeURL(geocodeURL, data, position) {
$.getJSON(geocodeURL, data, function(location) { $.getJSON(geocodeURL, data, function(location) {
var lns, lid; var lns, lid;
@ -513,17 +515,8 @@ var SN = { // StatusNet
NLN_text = location.name; NLN_text = location.name;
} }
$('#'+SN.C.S.NoticeGeoName) $('label[for='+SN.C.S.NoticeDataGeo+']')
.replaceWith('<a id="notice_data-geo_name"/>'); .attr('title', NoticeDataGeo_text.ShareDisable + ' (' + NLN_text + ')');
$('#'+SN.C.S.NoticeGeoName)
.attr('href', location.url)
.text(NLN_text)
.click(function() {
window.open(location.url);
return false;
});
$('#'+SN.C.S.NoticeLat).val(data.lat); $('#'+SN.C.S.NoticeLat).val(data.lat);
$('#'+SN.C.S.NoticeLon).val(data.lon); $('#'+SN.C.S.NoticeLon).val(data.lon);
@ -532,14 +525,13 @@ var SN = { // StatusNet
$('#'+SN.C.S.NoticeDataGeo).attr('checked', true); $('#'+SN.C.S.NoticeDataGeo).attr('checked', true);
var cookieValue = { var cookieValue = {
'NLat': data.lat, NLat: data.lat,
'NLon': data.lon, NLon: data.lon,
'NLNS': lns, NLNS: lns,
'NLID': lid, NLID: lid,
'NLN': NLN_text, NLN: NLN_text,
'NLNU': location.url, NLNU: location.url,
'NDG': true, NDG: true
'NDGSM': false
}; };
$.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue));
}); });
@ -557,62 +549,14 @@ var SN = { // StatusNet
var geocodeURL = NGW.attr('title'); var geocodeURL = NGW.attr('title');
NGW.removeAttr('title'); NGW.removeAttr('title');
$('label[for='+SN.C.S.NoticeDataGeo+']').attr('title', jQuery.trim($('label[for='+SN.C.S.NoticeDataGeo+']').text())); $('label[for='+SN.C.S.NoticeDataGeo+']')
.attr('title', jQuery.trim($('label[for='+SN.C.S.NoticeDataGeo+']').text()));
NDGe.change(function() { NDGe.change(function() {
var NLN = $('#'+SN.C.S.NoticeGeoName);
if (NLN.length > 0) {
NLN.remove();
}
if ($('#'+SN.C.S.NoticeDataGeo).attr('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === null) { if ($('#'+SN.C.S.NoticeDataGeo).attr('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === null) {
$('label[for='+SN.C.S.NoticeDataGeo+']').addClass('checked').attr('title', NoticeDataGeoShareDisable_text); $('label[for='+SN.C.S.NoticeDataGeo+']')
.attr('title', NoticeDataGeo_text.ShareDisable)
var S = '<div id="'+SN.C.S.NoticeDataGeoSelected+'" class="'+SN.C.S.Success+'"/>'; .addClass('checked');
var NDGS = $('#'+SN.C.S.NoticeDataGeoSelected);
if (NDGS.length > 0) {
NDGS.replaceWith(S);
}
else {
$('#'+SN.C.S.FormNotice).append(S);
}
NDGS = $('#'+SN.C.S.NoticeDataGeoSelected);
NDGS.prepend('<span id="'+SN.C.S.NoticeGeoName+'">Geo</span> <button class="minimize" title="'+NoticeDataGeoInfoMinimize_text+'">&#95;</button> <button class="close" title="'+NoticeDataGeoShareDisable_text+'">&#215;</button>');
var NLN = $('#'+SN.C.S.NoticeGeoName);
NLN.addClass('processing');
$('#'+SN.C.S.NoticeDataGeoSelected+' button.close').click(function(){
removeNoticeDataGeo();
$('#'+SN.C.S.NoticeDataGeoSelected).remove();
$('#'+SN.C.S.NoticeDataText).focus();
return false;
});
$('#'+SN.C.S.NoticeDataGeoSelected+' button.minimize').click(function(){
$('#'+SN.C.S.NoticeDataGeoSelected).hide();
var cookieValue = {
'NLat': $('#'+SN.C.S.NoticeLat).val(),
'NLon': $('#'+SN.C.S.NoticeLat).val(),
'NLNS': $('#'+SN.C.S.NoticeLocationNs).val(),
'NLID': $('#'+SN.C.S.NoticeLocationId).val(),
'NLN': $('#'+SN.C.S.NoticeGeoName).text(),
'NLNU': $('#'+SN.C.S.NoticeGeoName).attr('href'),
'NDG': true,
'NDGSM': true
};
$.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue));
$('#'+SN.C.S.NoticeDataText).focus();
return false;
});
if ($.cookie(SN.C.S.NoticeDataGeoCookie) === null || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') { if ($.cookie(SN.C.S.NoticeDataGeoCookie) === null || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') {
if (navigator.geolocation) { if (navigator.geolocation) {
@ -622,18 +566,27 @@ var SN = { // StatusNet
$('#'+SN.C.S.NoticeLon).val(position.coords.longitude); $('#'+SN.C.S.NoticeLon).val(position.coords.longitude);
var data = { var data = {
'lat': position.coords.latitude, lat: position.coords.latitude,
'lon': position.coords.longitude, lon: position.coords.longitude,
'token': $('#token').val() token: $('#token').val()
}; };
getJSONgeocodeURL(geocodeURL, data); getJSONgeocodeURL(geocodeURL, data, position);
}, },
function(error) { function(error) {
if (error.PERMISSION_DENIED == 1) { switch(error.code) {
case error.PERMISSION_DENIED:
removeNoticeDataGeo(); removeNoticeDataGeo();
break;
case error.TIMEOUT:
$('#'+SN.C.S.NoticeDataGeo).attr('checked', false);
break;
} }
},
{
timeout: 10000
} }
); );
} }
@ -645,7 +598,7 @@ var SN = { // StatusNet
'token': $('#token').val() 'token': $('#token').val()
}; };
getJSONgeocodeURL(geocodeURL, data); getJSONgeocodeURL(geocodeURL, data, position);
} }
else { else {
removeNoticeDataGeo(); removeNoticeDataGeo();
@ -657,34 +610,20 @@ var SN = { // StatusNet
else { else {
var cookieValue = JSON.parse($.cookie(SN.C.S.NoticeDataGeoCookie)); var cookieValue = JSON.parse($.cookie(SN.C.S.NoticeDataGeoCookie));
if (cookieValue.NDGSM === true) {
$('#'+SN.C.S.NoticeDataGeoSelected).hide();
}
$('#'+SN.C.S.NoticeLat).val(cookieValue.NLat); $('#'+SN.C.S.NoticeLat).val(cookieValue.NLat);
$('#'+SN.C.S.NoticeLon).val(cookieValue.NLon); $('#'+SN.C.S.NoticeLon).val(cookieValue.NLon);
$('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS); $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS);
$('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID); $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID);
$('#'+SN.C.S.NoticeDataGeo).attr('checked', cookieValue.NDG); $('#'+SN.C.S.NoticeDataGeo).attr('checked', cookieValue.NDG);
$('#'+SN.C.S.NoticeGeoName) $('label[for='+SN.C.S.NoticeDataGeo+']')
.replaceWith('<a id="notice_data-geo_name"/>'); .attr('title', NoticeDataGeo_text.ShareDisable + ' (' + cookieValue.NLN + ')')
.addClass('checked');
$('#'+SN.C.S.NoticeGeoName)
.attr('href', cookieValue.NLNU)
.text(cookieValue.NLN)
.click(function() {
window.open($(this).attr('href'));
return false;
});
} }
} }
else { else {
removeNoticeDataGeo(); removeNoticeDataGeo();
} }
$('#'+SN.C.S.NoticeDataText).focus();
}).change(); }).change();
} }
}, },

View File

@ -141,7 +141,7 @@ class Action extends HTMLOutputter // lawsuit
function showTitle() function showTitle()
{ {
$this->element('title', null, $this->element('title', null,
sprintf(_("%1$s - %2$s"), sprintf(_("%1\$s - %2\$s"),
$this->title(), $this->title(),
common_config('site', 'name'))); common_config('site', 'name')));
} }

View File

@ -53,6 +53,9 @@ if (!defined('STATUSNET')) {
class ApiAction extends Action class ApiAction extends Action
{ {
const READ_ONLY = 1;
const READ_WRITE = 2;
var $format = null; var $format = null;
var $user = null; var $user = null;
var $auth_user = null; var $auth_user = null;
@ -62,6 +65,8 @@ class ApiAction extends Action
var $since_id = null; var $since_id = null;
var $since = null; var $since = null;
var $access = self::READ_ONLY; // read (default) or read-write
/** /**
* Initialization. * Initialization.
* *
@ -140,12 +145,14 @@ class ApiAction extends Action
// Note: some profiles don't have an associated user // Note: some profiles don't have an associated user
$defaultDesign = Design::siteDesign();
if (!empty($user)) { if (!empty($user)) {
$design = $user->getDesign(); $design = $user->getDesign();
} }
if (empty($design)) { if (empty($design)) {
$design = Design::siteDesign(); $design = $defaultDesign;
} }
$color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor);
@ -166,7 +173,7 @@ class ApiAction extends Action
$timezone = 'UTC'; $timezone = 'UTC';
if ($user->timezone) { if (!empty($user) && $user->timezone) {
$timezone = $user->timezone; $timezone = $user->timezone;
} }

View File

@ -39,6 +39,7 @@ if (!defined('STATUSNET')) {
} }
require_once INSTALLDIR . '/lib/api.php'; require_once INSTALLDIR . '/lib/api.php';
require_once INSTALLDIR . '/lib/apioauth.php';
/** /**
* Actions extending this class will require auth * Actions extending this class will require auth
@ -52,6 +53,9 @@ require_once INSTALLDIR . '/lib/api.php';
class ApiAuthAction extends ApiAction class ApiAuthAction extends ApiAction
{ {
var $access_token;
var $oauth_access_type;
var $oauth_source;
/** /**
* Take arguments for running, and output basic auth header if needed * Take arguments for running, and output basic auth header if needed
@ -67,12 +71,115 @@ class ApiAuthAction extends ApiAction
parent::prepare($args); parent::prepare($args);
if ($this->requiresAuth()) { if ($this->requiresAuth()) {
$this->consumer_key = $this->arg('oauth_consumer_key');
$this->access_token = $this->arg('oauth_token');
if (!empty($this->access_token)) {
$this->checkOAuthRequest();
} else {
$this->checkBasicAuthUser(); $this->checkBasicAuthUser();
// By default, all basic auth users have read and write access
$this->access = self::READ_WRITE;
}
} }
return true; return true;
} }
function handle($args)
{
parent::handle($args);
}
function checkOAuthRequest()
{
common_debug("We have an OAuth request.");
$datastore = new ApiStatusNetOAuthDataStore();
$server = new OAuthServer($datastore);
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($hmac_method);
ApiOauthAction::cleanRequest();
try {
$req = OAuthRequest::from_request();
$server->verify_request($req);
$app = Oauth_application::getByConsumerKey($this->consumer_key);
if (empty($app)) {
// this should really not happen
common_log(LOG_WARN,
"Couldn't find the OAuth app for consumer key: $this->consumer_key");
throw new OAuthException('No application for that consumer key.');
}
// set the source attr
$this->oauth_source = $app->name;
$appUser = Oauth_application_user::staticGet('token',
$this->access_token);
// XXX: check that app->id and appUser->application_id and consumer all
// match?
if (!empty($appUser)) {
// read or read-write
$this->oauth_access_type = $appUser->access_type;
// If access_type == 0 we have either a request token
// or a bad / revoked access token
if ($this->oauth_access_type != 0) {
// Set the read or read-write access for the api call
$this->access = ($appUser->access_type & Oauth_application::$writeAccess)
? self::READ_WRITE : self::READ_ONLY;
$this->auth_user = User::staticGet('id', $appUser->profile_id);
$msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " .
"application '%s' (id: %d).";
common_log(LOG_INFO, sprintf($msg,
$this->auth_user->nickname,
$this->auth_user->id,
$app->name,
$app->id));
return true;
} else {
throw new OAuthException('Bad access token.');
}
} else {
// also should not happen
throw new OAuthException('No user for that token.');
}
} catch (OAuthException $e) {
common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage());
common_debug(var_export($req, true));
$this->showOAuthError($e->getMessage());
exit();
}
}
function showOAuthError($msg)
{
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: text/html; charset=utf-8');
print $msg . "\n";
}
/** /**
* Does this API resource require authentication? * Does this API resource require authentication?
* *
@ -128,6 +235,7 @@ class ApiAuthAction extends ApiAction
exit; exit;
} }
} }
return true; return true;
} }

122
lib/apioauth.php Normal file
View File

@ -0,0 +1,122 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Base action for OAuth API endpoints
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 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);
}
require_once INSTALLDIR . '/lib/apioauthstore.php';
/**
* Base action for API OAuth enpoints. Clean up the
* the request, and possibly some other common things
* here.
*
* @category API
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApiOauthAction extends Action
{
/**
* Is this a read-only action?
*
* @return boolean false
*/
function isReadOnly($args)
{
return false;
}
function prepare($args)
{
parent::prepare($args);
return true;
}
/**
* Handle input, produce output
*
* Switches on request method; either shows the form or handles its input.
*
* @param array $args $_REQUEST data
*
* @return void
*/
function handle($args)
{
parent::handle($args);
self::cleanRequest();
}
static function cleanRequest()
{
// kill evil effects of magical slashing
if (get_magic_quotes_gpc() == 1) {
$_POST = array_map('stripslashes', $_POST);
$_GET = array_map('stripslashes', $_GET);
}
// strip out the p param added in index.php
// XXX: should we strip anything else? Or alternatively
// only allow a known list of params?
unset($_GET['p']);
unset($_POST['p']);
}
function getCallback($url, $params)
{
foreach ($params as $k => $v) {
$url = $this->appendQueryVar($url,
OAuthUtil::urlencode_rfc3986($k),
OAuthUtil::urlencode_rfc3986($v));
}
return $url;
}
function appendQueryVar($url, $k, $v) {
$url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
$url = substr($url, 0, -1);
if (strpos($url, '?') === false) {
return ($url . '?' . $k . '=' . $v);
} else {
return ($url . '&' . $k . '=' . $v);
}
}
}

163
lib/apioauthstore.php Normal file
View File

@ -0,0 +1,163 @@
<?php
/*
* StatusNet - the 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 <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
require_once INSTALLDIR . '/lib/oauthstore.php';
class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
{
function lookup_consumer($consumer_key)
{
$con = Consumer::staticGet('consumer_key', $consumer_key);
if (!$con) {
return null;
}
return new OAuthConsumer($con->consumer_key,
$con->consumer_secret);
}
function getAppByRequestToken($token_key)
{
// Look up the full req tokenx
$req_token = $this->lookup_token(null,
'request',
$token_key);
if (empty($req_token)) {
common_debug("couldn't get request token from oauth datastore");
return null;
}
// Look up the full Token
$token = new Token();
$token->tok = $req_token->key;
$result = $token->find(true);
if (empty($result)) {
common_debug('Couldn\'t find req token in the token table.');
return null;
}
// Look up the app
$app = new Oauth_application();
$app->consumer_key = $token->consumer_key;
$result = $app->find(true);
if (!empty($result)) {
return $app;
} else {
common_debug("Couldn't find the app!");
return null;
}
}
function new_access_token($token, $consumer)
{
common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
$rt = new Token();
$rt->consumer_key = $consumer->key;
$rt->tok = $token->key;
$rt->type = 0; // request
$app = Oauth_application::getByConsumerKey($consumer->key);
if (empty($app)) {
common_debug("empty app!");
}
if ($rt->find(true) && $rt->state == 1) { // authorized
common_debug('request token found.', __FILE__);
// find the associated user of the app
$appUser = new Oauth_application_user();
$appUser->application_id = $app->id;
$appUser->token = $rt->tok;
$result = $appUser->find(true);
if (!empty($result)) {
common_debug("Oath app user found.");
} else {
common_debug("Oauth app user not found. app id $app->id token $rt->tok");
return null;
}
// go ahead and make the access token
$at = new Token();
$at->consumer_key = $consumer->key;
$at->tok = common_good_rand(16);
$at->secret = common_good_rand(16);
$at->type = 1; // access
$at->created = DB_DataObject_Cast::dateTime();
if (!$at->insert()) {
$e = $at->_lastError;
common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
return null;
} else {
common_debug('access token "'.$at->tok.'" inserted', __FILE__);
// burn the old one
$orig_rt = clone($rt);
$rt->state = 2; // used
if (!$rt->update($orig_rt)) {
return null;
}
common_debug('request token "'.$rt->tok.'" updated', __FILE__);
// update the token from req to access for the user
$orig = clone($appUser);
$appUser->token = $at->tok;
// It's at this point that we change the access type
// to whatever the application's access is. Request
// tokens should always have an access type of 0, and
// therefore be unuseable for making requests for
// protected resources.
$appUser->access_type = $app->access_type;
$result = $appUser->update($orig);
if (empty($result)) {
common_debug('couldn\'t update OAuth app user.');
return null;
}
// Okay, good
return new OAuthToken($at->tok, $at->secret);
}
} else {
return null;
}
}
}

338
lib/applicationeditform.php Normal file
View File

@ -0,0 +1,338 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for editing an application
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Form
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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/form.php';
/**
* Form for editing an application
*
* @category Form
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
*/
class ApplicationEditForm extends Form
{
/**
* group for user to join
*/
var $application = null;
/**
* Constructor
*
* @param Action $out output channel
* @param User_group $group group to join
*/
function __construct($out=null, $application=null)
{
parent::__construct($out);
$this->application = $application;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
if ($this->application) {
return 'form_application_edit-' . $this->application->id;
} else {
return 'form_application_add';
}
}
/**
* HTTP method used to submit the form
*
* For image data we need to send multipart/form-data
* so we set that here too
*
* @return string the method to use for submitting
*/
function method()
{
$this->enctype = 'multipart/form-data';
return 'post';
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
$cur = common_current_user();
if (!empty($this->application)) {
return common_local_url('editapplication',
array('id' => $this->application->id));
} else {
return common_local_url('newapplication');
}
}
/**
* Name of the form
*
* @return void
*/
function formLegend()
{
$this->out->element('legend', null, _('Edit application'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
if ($this->application) {
$id = $this->application->id;
$icon = $this->application->icon;
$name = $this->application->name;
$description = $this->application->description;
$source_url = $this->application->source_url;
$organization = $this->application->organization;
$homepage = $this->application->homepage;
$callback_url = $this->application->callback_url;
$this->type = $this->application->type;
$this->access_type = $this->application->access_type;
} else {
$id = '';
$icon = '';
$name = '';
$description = '';
$source_url = '';
$organization = '';
$homepage = '';
$callback_url = '';
$this->type = '';
$this->access_type = '';
}
$this->out->hidden('token', common_session_token());
$this->out->elementStart('ul', 'form_data');
$this->out->elementStart('li', array('id' => 'application_icon'));
if (!empty($icon)) {
$this->out->element('img', array('src' => $icon));
}
$this->out->element('label', array('for' => 'app_icon'),
_('Icon'));
$this->out->element('input', array('name' => 'app_icon',
'type' => 'file',
'id' => 'app_icon'));
$this->out->element('p', 'form_guide', _('Icon for this application'));
$this->out->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->out->elementEnd('li');
$this->out->elementStart('li');
$this->out->hidden('application_id', $id);
$this->out->input('name', _('Name'),
($this->out->arg('name')) ? $this->out->arg('name') : $name);
$this->out->elementEnd('li');
$this->out->elementStart('li');
$maxDesc = Oauth_application::maxDesc();
if ($maxDesc > 0) {
$descInstr = sprintf(_('Describe your application in %d characters'),
$maxDesc);
} else {
$descInstr = _('Describe your application');
}
$this->out->textarea('description', _('Description'),
($this->out->arg('description')) ? $this->out->arg('description') : $description,
$descInstr);
$this->out->elementEnd('li');
$this->out->elementStart('li');
$this->out->input('source_url', _('Source URL'),
($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url,
_('URL of the homepage of this application'));
$this->out->elementEnd('li');
$this->out->elementStart('li');
$this->out->input('organization', _('Organization'),
($this->out->arg('organization')) ? $this->out->arg('organization') : $organization,
_('Organization responsible for this application'));
$this->out->elementEnd('li');
$this->out->elementStart('li');
$this->out->input('homepage', _('Homepage'),
($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
_('URL for the homepage of the organization'));
$this->out->elementEnd('li');
$this->out->elementStart('li');
$this->out->input('callback_url', ('Callback URL'),
($this->out->arg('callback_url')) ? $this->out->arg('callback_url') : $callback_url,
_('URL to redirect to after authentication'));
$this->out->elementEnd('li');
$this->out->elementStart('li', array('id' => 'application_types'));
$attrs = array('name' => 'app_type',
'type' => 'radio',
'id' => 'app_type-browser',
'class' => 'radio',
'value' => Oauth_application::$browser);
// Default to Browser
if ($this->application->type == Oauth_application::$browser
|| empty($this->application->type)) {
$attrs['checked'] = 'checked';
}
$this->out->element('input', $attrs);
$this->out->element('label', array('for' => 'app_type-browser',
'class' => 'radio'),
_('Browser'));
$attrs = array('name' => 'app_type',
'type' => 'radio',
'id' => 'app_type-dekstop',
'class' => 'radio',
'value' => Oauth_application::$desktop);
if ($this->application->type == Oauth_application::$desktop) {
$attrs['checked'] = 'checked';
}
$this->out->element('input', $attrs);
$this->out->element('label', array('for' => 'app_type-desktop',
'class' => 'radio'),
_('Desktop'));
$this->out->element('p', 'form_guide', _('Type of application, browser or desktop'));
$this->out->elementEnd('li');
$this->out->elementStart('li', array('id' => 'default_access_types'));
$attrs = array('name' => 'default_access_type',
'type' => 'radio',
'id' => 'default_access_type-r',
'class' => 'radio',
'value' => 'r');
// default to read-only access
if ($this->application->access_type & Oauth_application::$readAccess
|| empty($this->application->access_type)) {
$attrs['checked'] = 'checked';
}
$this->out->element('input', $attrs);
$this->out->element('label', array('for' => 'default_access_type-ro',
'class' => 'radio'),
_('Read-only'));
$attrs = array('name' => 'default_access_type',
'type' => 'radio',
'id' => 'default_access_type-rw',
'class' => 'radio',
'value' => 'rw');
if ($this->application->access_type & Oauth_application::$readAccess
&& $this->application->access_type & Oauth_application::$writeAccess
) {
$attrs['checked'] = 'checked';
}
$this->out->element('input', $attrs);
$this->out->element('label', array('for' => 'default_access_type-rw',
'class' => 'radio'),
_('Read-write'));
$this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write'));
$this->out->elementEnd('li');
$this->out->elementEnd('ul');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('cancel', _('Cancel'), 'submit form_action-primary',
'cancel', _('Cancel'));
$this->out->submit('save', _('Save'), 'submit form_action-secondary',
'save', _('Save'));
}
}

168
lib/applicationlist.php Normal file
View File

@ -0,0 +1,168 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Widget to show a list of OAuth applications
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 . '/lib/widget.php';
define('APPS_PER_PAGE', 20);
/**
* Widget to show a list of OAuth applications
*
* @category Application
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApplicationList extends Widget
{
/** Current application, application query */
var $application = null;
/** Owner of this list */
var $owner = null;
/** Action object using us. */
var $action = null;
function __construct($application, $owner=null, $action=null, $connections = false)
{
parent::__construct($action);
$this->application = $application;
$this->owner = $owner;
$this->action = $action;
$this->connections = $connections;
}
function show()
{
$this->out->elementStart('ul', 'applications');
$cnt = 0;
while ($this->application->fetch()) {
$cnt++;
if($cnt > APPS_PER_PAGE) {
break;
}
$this->showapplication();
}
$this->out->elementEnd('ul');
return $cnt;
}
function showApplication()
{
$user = common_current_user();
$this->out->elementStart('li', array('class' => 'application',
'id' => 'oauthclient-' . $this->application->id));
$this->out->elementStart('span', 'vcard author');
if (!$this->connections) {
$this->out->elementStart('a',
array('href' => common_local_url('showapplication',
array('id' => $this->application->id)),
'class' => 'url'));
} else {
$this->out->elementStart('a', array('href' => $this->application->source_url,
'class' => 'url'));
}
if (!empty($this->application->icon)) {
$this->out->element('img', array('src' => $this->application->icon,
'class' => 'photo avatar'));
}
$this->out->element('span', 'fn', $this->application->name);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
$this->out->raw(' by ');
$this->out->element('a', array('href' => $this->application->homepage,
'class' => 'url'),
$this->application->organization);
$this->out->element('p', 'note', $this->application->description);
$this->out->elementEnd('li');
if ($this->connections) {
$appUser = Oauth_application_user::getByKeys($this->owner, $this->application);
if (empty($appUser)) {
common_debug("empty appUser!");
}
$this->out->elementStart('li');
$access = ($this->application->access_type & Oauth_application::$writeAccess)
? 'read-write' : 'read-only';
$txt = 'Approved ' . common_date_string($appUser->modified) .
" - $access access.";
$this->out->raw($txt);
$this->out->elementEnd('li');
$this->out->elementStart('li', 'entity_revoke');
$this->out->elementStart('form', array('id' => 'form_revoke_app',
'class' => 'form_revoke_app',
'method' => 'POST',
'action' =>
common_local_url('oauthconnectionssettings')));
$this->out->elementStart('fieldset');
$this->out->hidden('id', $this->application->id);
$this->out->hidden('token', common_session_token());
$this->out->submit('revoke', _('Revoke'));
$this->out->elementEnd('fieldset');
$this->out->elementEnd('form');
$this->out->elementEnd('li');
}
}
/* Override this in subclasses. */
function showOwnerControls()
{
return;
}
}

View File

@ -92,6 +92,19 @@ abstract class AuthenticationPlugin extends Plugin
return false; return false;
} }
/**
* Given a username, suggest what the nickname should be
* Used during autoregistration
* Useful if your usernames are ugly, and you want to suggest
* nice looking nicknames when users initially sign on
* @param username
* @return string nickname
*/
function suggestNicknameForUsername($username)
{
return $username;
}
//------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
function onInitializePlugin(){ function onInitializePlugin(){
if(!isset($this->provider_name)){ if(!isset($this->provider_name)){
@ -108,13 +121,25 @@ abstract class AuthenticationPlugin extends Plugin
function onAutoRegister($nickname, $provider_name, &$user) function onAutoRegister($nickname, $provider_name, &$user)
{ {
if($provider_name == $this->provider_name && $this->autoregistration){ if($provider_name == $this->provider_name && $this->autoregistration){
$user = $this->autoregister($nickname); $suggested_nickname = $this->suggestNicknameForUsername($nickname);
$test_user = User::staticGet('nickname', $suggested_nickname);
if($test_user) {
//someone already exists with the suggested nickname, so used the passed nickname
$suggested_nickname = $nickname;
}
$test_user = User::staticGet('nickname', $suggested_nickname);
if($test_user) {
//someone already exists with the suggested nickname
//not much else we can do
}else{
$user = $this->autoregister($suggested_nickname);
if($user){ if($user){
User_username::register($user,$nickname,$this->provider_name); User_username::register($user,$nickname,$this->provider_name);
return false; return false;
} }
} }
} }
}
function onStartCheckPassword($nickname, $password, &$authenticatedUser){ function onStartCheckPassword($nickname, $password, &$authenticatedUser){
//map the nickname to a username //map the nickname to a username
@ -122,23 +147,30 @@ abstract class AuthenticationPlugin extends Plugin
$user_username->username=$nickname; $user_username->username=$nickname;
$user_username->provider_name=$this->provider_name; $user_username->provider_name=$this->provider_name;
if($user_username->find() && $user_username->fetch()){ if($user_username->find() && $user_username->fetch()){
$username = $user_username->username; $authenticated = $this->checkPassword($user_username->username, $password);
$authenticated = $this->checkPassword($username, $password);
if($authenticated){ if($authenticated){
$authenticatedUser = User::staticGet('id', $user_username->user_id); $authenticatedUser = User::staticGet('id', $user_username->user_id);
return false; return false;
} }
}else{ }else{
$user = User::staticGet('nickname', $nickname); //$nickname is the username used to login
//$suggested_nickname is the nickname the auth provider suggests for that username
$suggested_nickname = $this->suggestNicknameForUsername($nickname);
$user = User::staticGet('nickname', $suggested_nickname);
if($user){ if($user){
//make sure a different provider isn't handling this nickname //make sure this user isn't claimed
$user_username = new User_username(); $user_username = new User_username();
$user_username->username=$nickname; $user_username->user_id=$user->id;
if(!$user_username->find()){ $we_can_handle = false;
//no other provider claims this username, so it's safe for us to handle it if($user_username->find()){
//either this provider, or another one, has already claimed this user
//so we cannot. Let another plugin try.
return;
}else{
//no other provider claims this user, so it's safe for us to handle it
$authenticated = $this->checkPassword($nickname, $password); $authenticated = $this->checkPassword($nickname, $password);
if($authenticated){ if($authenticated){
$authenticatedUser = User::staticGet('nickname', $nickname); $authenticatedUser = $user;
User_username::register($authenticatedUser,$nickname,$this->provider_name); User_username::register($authenticatedUser,$nickname,$this->provider_name);
return false; return false;
} }

View File

@ -179,4 +179,23 @@ class Cache
return $success; return $success;
} }
/**
* Close or reconnect any remote connections, such as to give
* daemon processes a chance to reconnect on a fresh socket.
*
* @return boolean success flag
*/
function reconnect()
{
$success = false;
if (Event::handle('StartCacheReconnect', array(&$success))) {
$success = true;
Event::handle('EndCacheReconnect', array());
}
return $success;
}
} }

Some files were not shown because too many files have changed in this diff Show More