Merge branch '0.9.x' into openidplugin

Conflicts:
	actions/login.php
	actions/register.php
This commit is contained in:
Evan Prodromou 2009-08-21 16:27:43 -04:00
commit 9f356b55c6
175 changed files with 11989 additions and 3118 deletions

118
README
View File

@ -553,25 +553,53 @@ our kind of hacky home-grown DB-based queue solution. See the "queues"
config section below for how to configure to use STOMP. As of this
writing, the software has been tested with ActiveMQ (
Twitter Friends Syncing
-----------------------
Twitter Bridge
--------------
As of Laconica 0.6.3, users may set a flag in their settings ("Subscribe
to my Twitter friends here" under the Twitter tab) to have Laconica
attempt to locate and subscribe to "friends" (people they "follow") on
Twitter who also have accounts on your Laconica system, and who have
previously set up a link for automatically posting notices to Twitter.
* OAuth
Optionally, there is a script (./scripts/synctwitterfriends.php), meant
to be run periodically from a job scheduler (e.g.: cron under Unix), to
look for new additions to users' friends lists. Note that the friends
syncing only subscribes users to each other, it does not unsubscribe
users when they stop following each other on Twitter.
As of 0.8.1, OAuth is used to to access protected resources on Twitter
instead of HTTP Basic Auth. To use Twitter bridging you will need
to register your instance of Laconica as an application on Twitter
(http://twitter.com/apps), and update the following variables in your
config.php with the consumer key and secret Twitter generates for you:
Sample cron job:
$config['twitter']['consumer_key'] = 'YOURKEY';
$config['twitter']['consumer_secret'] = 'YOURSECRET';
# Update Twitter friends subscriptions every half hour
0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
When registering your application with Twitter set the type to "Browser"
and your Callback URL to:
http://example.org/mublog/twitter/authorization
The default access type should be, "Read & Write".
* Importing statuses from Twitter
To allow your users to import their friends' Twitter statuses, you will
need to enable the bidirectional Twitter bridge in config.php:
$config['twitterbridge']['enabled'] = true;
and run the TwitterStatusFetcher daemon (scripts/twitterstatusfetcher.php).
Additionally, you will want to set the integration source variable,
which will keep notices posted to Twitter via Laconica from looping
back. The integration source should be set to the name of your
application, exactly as you specified it on the settings page for your
Laconica application on Twitter, e.g.:
$config['integration']['source'] = 'YourApp';
* Twitter Friends Syncing
Users may set a flag in their settings ("Subscribe to my Twitter friends
here" under the Twitter tab) to have Laconica attempt to locate and
subscribe to "friends" (people they "follow") on Twitter who also have
accounts on your Laconica system, and who have previously set up a link
for automatically posting notices to Twitter.
As of 0.8.0, this is no longer accomplished via a cron job. Instead you
must run the SyncTwitterFriends daemon (scripts/synctwitterfreinds.php).
Built-in Facebook Application
-----------------------------
@ -940,6 +968,8 @@ closed: If set to 'true', will disallow registration on your site.
the service, *then* set this variable to 'true'.
inviteonly: If set to 'true', will only allow registration if the user
was invited by an existing user.
openidonly: If set to 'true', will only allow registrations and logins
through OpenID.
private: If set to 'true', anonymous users will be redirected to the
'login' page. Also, API methods that normally require no
authentication will require it. Note that this does not turn
@ -967,6 +997,9 @@ shorturllength: Length of URL at which URLs in a message exceeding 140
dupelimit: minimum time allowed for one person to say the same thing
twice. Default 60s. Anything lower is considered a user
or UI error.
textlimit: default max size for texts in the site. Defaults to 140.
0 means no limit. Can be fine-tuned for notices, messages,
profile bios and group descriptions.
db
--
@ -1167,6 +1200,14 @@ For configuring invites.
enabled: Whether to allow users to send invites. Default true.
openid
------
For configuring OpenID.
enabled: Whether to allow users to register and login using OpenID. Default
true.
tag
---
@ -1228,6 +1269,30 @@ enabled: Set to true to enable. Default false.
server: a string with the hostname of the sphinx server.
port: an integer with the port number of the sphinx server.
emailpost
---------
For post-by-email.
enabled: Whether to enable post-by-email. Defaults to true. You will
also need to set up maildaemon.php.
sms
---
For SMS integration.
enabled: Whether to enable SMS integration. Defaults to true. Queues
should also be enabled.
twitter
-------
For Twitter integration
enabled: Whether to enable Twitter integration. Defaults to true.
Queues should also be enabled.
integration
-----------
@ -1269,6 +1334,8 @@ banned: an array of usernames and/or profile IDs of 'banned' profiles.
The site will reject any notices by these users -- they will
not be accepted at all. (Compare with blacklisted users above,
whose posts just won't show up in the public stream.)
biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default.
newuser
-------
@ -1365,6 +1432,9 @@ Options for group functionality.
maxaliases: maximum number of aliases a group can have. Default 3. Set
to 0 or less to prevent aliases in a group.
desclimit: maximum number of characters to allow in group descriptions.
null (default) means to use the site-wide text limits. 0
means no limit.
oohembed
--------
@ -1443,6 +1513,24 @@ linkcolor: Hex color of all links.
backgroundimage: Image to use for the background.
disposition: Flags for whether or not to tile the background image.
notice
------
Configuration options specific to notices.
contentlimit: max length of the plain-text content of a notice.
Default is null, meaning to use the site-wide text limit.
0 means no limit.
message
-------
Configuration options specific to messages.
contentlimit: max length of the plain-text content of a message.
Default is null, meaning to use the site-wide text limit.
0 means no limit.
Plugins
=======

View File

@ -1,6 +1,6 @@
<?php
/**
* Access token class.
* Access token class
*
* PHP version 5
*
@ -32,10 +32,11 @@ if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
require_once INSTALLDIR.'/lib/omb.php';
/**
* Access token class.
* Access token class
*
* @category Action
* @package Laconica
@ -47,28 +48,23 @@ require_once INSTALLDIR.'/lib/omb.php';
class AccesstokenAction extends Action
{
/**
* Class handler.
* Class handler
*
* @param array $args query arguments
*
* @return boolean false if user doesn't exist
*/
* @return nothing
*
**/
function handle($args)
{
parent::handle($args);
try {
common_debug('getting request from env variables', __FILE__);
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('accesstoken'));
common_debug('getting a server', __FILE__);
$server = omb_oauth_server();
common_debug('fetching the access token', __FILE__);
$token = $server->fetch_access_token($req);
common_debug('got this token: "'.print_r($token, true).'"', __FILE__);
common_debug('printing the access token', __FILE__);
print $token;
} catch (OAuthException $e) {
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server());
$srv->writeAccessToken();
} catch (Exception $e) {
$this->serverError($e->getMessage());
}
}
}
?>

View File

@ -25,11 +25,31 @@ require_once INSTALLDIR.'/lib/feedlist.php';
class AllAction extends ProfileAction
{
var $notice;
function isReadOnly($args)
{
return true;
}
function prepare($args)
{
parent::prepare($args);
$cur = common_current_user();
if (!empty($cur) && $cur->id == $this->user->id) {
$this->notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
} else {
$this->notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
}
if($this->page > 1 && $this->notice->N == 0){
$this->serverError(_('No such page'),$code=404);
}
return true;
}
function handle($args)
{
parent::handle($args);
@ -88,7 +108,9 @@ class AllAction extends ProfileAction
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
$this->user->nickname);
}
$this->elementStart('div', 'guide');
@ -98,15 +120,7 @@ class AllAction extends ProfileAction
function showContent()
{
$cur = common_current_user();
if (!empty($cur) && $cur->id == $this->user->id) {
$notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
} else {
$notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
}
$nl = new NoticeList($notice, $this);
$nl = new NoticeList($this->notice, $this);
$cnt = $nl->show();

View File

@ -115,8 +115,8 @@ class AllrssAction extends Rss10Action
'link' => common_local_url('all',
array('nickname' =>
$user->nickname)),
'description' => sprintf(_('Feed for friends of %s'),
$user->nickname));
'description' => sprintf(_('Updates from %1$s and friends on %2$s!'),
$user->nickname, common_config('site', 'name')));
return $c;
}

View File

@ -131,6 +131,8 @@ class ApiAction extends Action
'tags/timeline',
'oembed/oembed',
'groups/show',
'groups/timeline',
'groups/list_all',
'groups/timeline');
static $bareauth = array('statuses/user_timeline',
@ -140,7 +142,8 @@ class ApiAction extends Action
'statuses/mentions',
'statuses/followers',
'favorites/favorites',
'friendships/show');
'friendships/show',
'groups/list_groups');
$fullname = "$this->api_action/$this->api_method";

View File

@ -103,18 +103,18 @@ class AttachmentAction extends Action
$this->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.json'),
array('url'=>
'oembed',
array(),
array('format'=>'json', 'url'=>
common_local_url('attachment',
array('attachment' => $this->attachment->id)))),
'title'=>'oEmbed'),null);
$this->element('link',array('rel'=>'alternate',
'type'=>'text/xml+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.xml'),
array('url'=>
'oembed',
array(),
array('format'=>'xml','url'=>
common_local_url('attachment',
array('attachment' => $this->attachment->id)))),
'title'=>'oEmbed'),null);

View File

@ -382,13 +382,7 @@ class AvatarsettingsAction extends AccountSettingsAction
function showStylesheets()
{
parent::showStylesheets();
$jcropStyle =
common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION);
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => $jcropStyle,
'media' => 'screen, projection, tv'));
$this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
}
/**
@ -402,13 +396,8 @@ class AvatarsettingsAction extends AccountSettingsAction
parent::showScripts();
if ($this->mode == 'crop') {
$jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js');
$jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js');
$this->element('script', array('type' => 'text/javascript',
'src' => $jcropPack));
$this->element('script', array('type' => 'text/javascript',
'src' => $jcropGo));
$this->script('js/jcrop/jquery.Jcrop.min.js');
$this->script('js/jcrop/jquery.Jcrop.go.js');
}
}
}

View File

@ -67,7 +67,11 @@ class ConfirmaddressAction extends Action
parent::handle($args);
if (!common_logged_in()) {
common_set_returnto($this->selfUrl());
if (!common_config('site', 'openidonly')) {
common_redirect(common_local_url('login'));
} else {
common_redirect(common_local_url('openidlogin'));
}
return;
}
$code = $this->trimmed('code');

View File

@ -196,8 +196,8 @@ class EditgroupAction extends GroupDesignAction
} else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).'));
return;
} else if (!is_null($description) && mb_strlen($description) > 140) {
$this->showForm(_('description is too long (max 140 chars).'));
} else if (User_group::descriptionTooLong($description)) {
$this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription()));
return;
} else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).'));

View File

@ -122,7 +122,7 @@ class EmailsettingsAction extends AccountSettingsAction
}
$this->elementEnd('fieldset');
if ($user->email) {
if (common_config('emailpost', 'enabled') && $user->email) {
$this->elementStart('fieldset', array('id' => 'settings_email_incoming'));
$this->element('legend',_('Incoming email'));
if ($user->incomingemail) {
@ -173,11 +173,13 @@ class EmailsettingsAction extends AccountSettingsAction
_('Allow friends to nudge me and send me an email.'),
$user->emailnotifynudge);
$this->elementEnd('li');
if (common_config('emailpost', 'enabled')) {
$this->elementStart('li');
$this->checkbox('emailpost',
_('I want to post notices by email.'),
$user->emailpost);
$this->elementEnd('li');
}
$this->elementStart('li');
$this->checkbox('emailmicroid',
_('Publish a MicroID for my email address.'),

View File

@ -153,7 +153,8 @@ class FavoritedAction extends Action
$message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.');
}
else {
$message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to add a notice to your favorites!'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
$this->elementStart('div', 'guide');

View File

@ -111,8 +111,8 @@ class FavoritesrssAction extends Rss10Action
'link' => common_local_url('showfavorites',
array('nickname' =>
$user->nickname)),
'description' => sprintf(_('Feed of favorite notices of %s'),
$user->nickname));
'description' => sprintf(_('Updates favored by %1$s on %2$s!'),
$user->nickname, common_config('site', 'name')));
return $c;
}

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Handler for remote subscription finish callback
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -15,285 +26,123 @@
*
* 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('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
require_once INSTALLDIR.'/lib/omb.php';
/**
* Handler for remote subscription finish callback
*
* When a remote user subscribes a local user, a redirect to this action is
* issued after the remote user authorized his service to subscribe.
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/omb.php');
class FinishremotesubscribeAction extends Action
{
/**
* Class handler.
*
* @param array $args query arguments
*
* @return nothing
*
**/
function handle($args)
{
parent::handle($args);
if (common_logged_in()) {
$this->clientError(_('You can use the local subscription!'));
return;
}
/* Restore session data. RemotesubscribeAction should have stored
this entry. */
$service = unserialize($_SESSION['oauth_authorization_request']);
$omb = $_SESSION['oauth_authorization_request'];
if (!$omb) {
if (!$service) {
$this->clientError(_('Not expecting this response!'));
return;
}
common_debug('stored request: '.print_r($omb,true), __FILE__);
common_debug('stored request: '. print_r($service, true), __FILE__);
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization'));
$token = $req->get_parameter('oauth_token');
# I think this is the success metric
if ($token != $omb['token']) {
$this->clientError(_('Not authorized.'));
return;
}
$version = $req->get_parameter('omb_version');
if ($version != OMB_VERSION_01) {
$this->clientError(_('Unknown version of OMB protocol.'));
return;
}
$nickname = $req->get_parameter('omb_listener_nickname');
if (!$nickname) {
$this->clientError(_('No nickname provided by remote server.'));
return;
}
$profile_url = $req->get_parameter('omb_listener_profile');
if (!$profile_url) {
$this->clientError(_('No profile URL returned by server.'));
return;
}
if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) {
$this->clientError(_('Invalid profile URL returned by server.'));
return;
}
if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) {
$this->clientError(_('You can use the local subscription!'));
return;
}
common_debug('listenee: "'.$omb['listenee'].'"', __FILE__);
$user = User::staticGet('nickname', $omb['listenee']);
/* Create user objects for both users. Do it early for request
validation. */
$user = User::staticGet('uri', $service->getListeneeURI());
if (!$user) {
$this->clientError(_('User being listened to doesn\'t exist.'));
$this->clientError(_('User being listened to does not exist.'));
return;
}
$other = User::staticGet('uri', $omb['listener']);
$other = User::staticGet('uri', $service->getListenerURI());
if ($other) {
$this->clientError(_('You can use the local subscription!'));
return;
}
$fullname = $req->get_parameter('omb_listener_fullname');
$homepage = $req->get_parameter('omb_listener_homepage');
$bio = $req->get_parameter('omb_listener_bio');
$location = $req->get_parameter('omb_listener_location');
$avatar_url = $req->get_parameter('omb_listener_avatar');
$remote = Remote_profile::staticGet('uri', $service->getListenerURI());
list($newtok, $newsecret) = $this->access_token($omb);
if (!$newtok || !$newsecret) {
$this->clientError(_('Couldn\'t convert request tokens to access tokens.'));
return;
}
# XXX: possible attack point; subscribe and return someone else's profile URI
$remote = Remote_profile::staticGet('uri', $omb['listener']);
if ($remote) {
$exists = true;
$profile = Profile::staticGet($remote->id);
$orig_remote = clone($remote);
$orig_profile = clone($profile);
# XXX: compare current postNotice and updateProfile URLs to the ones
# stored in the DB to avoid (possibly...) above attack
} else {
$exists = false;
$remote = new Remote_profile();
$remote->uri = $omb['listener'];
$profile = new Profile();
}
$profile->nickname = $nickname;
$profile->profileurl = $profile_url;
if (!is_null($fullname)) {
$profile->fullname = $fullname;
}
if (!is_null($homepage)) {
$profile->homepage = $homepage;
}
if (!is_null($bio)) {
$profile->bio = $bio;
}
if (!is_null($location)) {
$profile->location = $location;
}
if ($exists) {
$profile->update($orig_profile);
} else {
$profile->created = DB_DataObject_Cast::dateTime(); # current time
$id = $profile->insert();
if (!$id) {
$this->serverError(_('Error inserting new profile'));
return;
}
$remote->id = $id;
}
if ($avatar_url) {
if (!$this->add_avatar($profile, $avatar_url)) {
$this->serverError(_('Error inserting avatar'));
return;
}
}
$remote->postnoticeurl = $omb['post_notice_url'];
$remote->updateprofileurl = $omb['update_profile_url'];
if ($exists) {
if (!$remote->update($orig_remote)) {
$this->serverError(_('Error updating remote profile'));
return;
}
} else {
$remote->created = DB_DataObject_Cast::dateTime(); # current time
if (!$remote->insert()) {
$this->serverError(_('Error inserting remote profile'));
return;
}
}
if ($user->hasBlocked($profile)) {
$this->clientError(_('That user has blocked you from subscribing.'));
return;
}
$sub = new Subscription();
$sub->subscriber = $remote->id;
$sub->subscribed = $user->id;
$sub_exists = false;
if ($sub->find(true)) {
$sub_exists = true;
$orig_sub = clone($sub);
/* Perform the handling itself via libomb. */
try {
$service->finishAuthorization();
} catch (OAuthException $e) {
if ($e->getMessage() == 'The authorized token does not equal the ' .
'submitted token.') {
$this->clientError(_('You are not authorized.'));
return;
} else {
$sub_exists = false;
$sub->created = DB_DataObject_Cast::dateTime(); # current time
$this->clientError(_('Could not convert request token to ' .
'access token.'));
return;
}
$sub->token = $newtok;
$sub->secret = $newsecret;
if ($sub_exists) {
$result = $sub->update($orig_sub);
} else {
$result = $sub->insert();
}
if (!$result) {
common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
$this->clientError(_('Couldn\'t insert new subscription.'));
} catch (OMB_RemoteServiceException $e) {
$this->clientError(_('Remote service uses unknown version of ' .
'OMB protocol.'));
return;
} catch (Exception $e) {
common_debug('Got exception ' . print_r($e, true), __FILE__);
$this->clientError($e->getMessage());
return;
}
# Notify user, if necessary
/* The service URLs are not accessible from datastore, so setting them
after insertion of the profile. */
$orig_remote = clone($remote);
mail_subscribe_notify_profile($user, $profile);
$remote->postnoticeurl =
$service->getServiceURI(OMB_ENDPOINT_POSTNOTICE);
$remote->updateprofileurl =
$service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE);
# Clear the data
if (!$remote->update($orig_remote)) {
$this->serverError(_('Error updating remote profile'));
return;
}
/* Clear the session data. */
unset($_SESSION['oauth_authorization_request']);
# If we show subscriptions in reverse chron order, this should
# show up close to the top of the page
/* If we show subscriptions in reverse chronological order, the new one
should show up close to the top of the page. */
common_redirect(common_local_url('subscribers', array('nickname' =>
$user->nickname)),
303);
}
function add_avatar($profile, $url)
{
$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
copy($url, $temp_filename);
$imagefile = new ImageFile($profile->id, $temp_filename);
$filename = Avatar::filename($profile->id,
image_type_to_extension($imagefile->type),
null,
common_timestamp());
rename($temp_filename, Avatar::path($filename));
return $profile->setOriginal($filename);
}
function access_token($omb)
{
common_debug('starting request for access token', __FILE__);
$con = omb_oauth_consumer();
$tok = new OAuthToken($omb['token'], $omb['secret']);
common_debug('using request token "'.$tok.'"', __FILE__);
$url = $omb['access_token_url'];
common_debug('using access token url "'.$url.'"', __FILE__);
# XXX: Is this the right thing to do? Strip off GET params and make them
# POST params? Seems wrong to me.
$parsed = parse_url($url);
$params = array();
parse_str($parsed['query'], $params);
$req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
$req->set_parameter('omb_version', OMB_VERSION_01);
# XXX: test to see if endpoint accepts this signature method
$req->sign_request(omb_hmac_sha1(), $con, $tok);
# We re-use this tool's fetcher, since it's pretty good
common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
$result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('got result: "'.print_r($result,true).'"', __FILE__);
if ($result->status != 200) {
return null;
}
parse_str($result->body, $return);
return array($return['oauth_token'], $return['oauth_token_secret']);
}
}

View File

@ -428,13 +428,7 @@ class GrouplogoAction extends GroupDesignAction
function showStylesheets()
{
parent::showStylesheets();
$jcropStyle =
common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION);
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => $jcropStyle,
'media' => 'screen, projection, tv'));
$this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
}
/**
@ -448,13 +442,8 @@ class GrouplogoAction extends GroupDesignAction
parent::showScripts();
if ($this->mode == 'crop') {
$jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js');
$jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js');
$this->element('script', array('type' => 'text/javascript',
'src' => $jcropPack));
$this->element('script', array('type' => 'text/javascript',
'src' => $jcropGo));
$this->script('js/jcrop/jquery.Jcrop.min.js');
$this->script('js/jcrop/jquery.Jcrop.go.js');
}
}

View File

@ -132,9 +132,10 @@ class groupRssAction extends Rss10Action
$c = array('url' => common_local_url('grouprss',
array('nickname' =>
$group->nickname)),
'title' => $group->nickname,
'title' => sprintf(_('%s timeline'), $group->nickname),
'link' => common_local_url('showgroup', array('nickname' => $group->nickname)),
'description' => sprintf(_('Microblog by %s group'), $group->nickname));
'description' => sprintf(_('Updates from members of %1$s on %2$s!'),
$group->nickname, common_config('site', 'name')));
return $c;
}

View File

@ -82,7 +82,8 @@ class GroupsearchAction extends SearchAction
$message = _('If you can\'t find the group you\'re looking for, you can [create it](%%action.newgroup%%) yourself.');
}
else {
$message = _('Why not [register an account](%%action.register%%) and [create the group](%%action.newgroup%%) yourself!');
$message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and [create the group](%%%%action.newgroup%%%%) yourself!'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));

View File

@ -84,6 +84,12 @@ class ImsettingsAction extends ConnectSettingsAction
function showContent()
{
if (!common_config('xmpp', 'enabled')) {
$this->element('div', array('class' => 'error'),
_('IM is not available.'));
return;
}
$user = common_current_user();
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_im',

View File

@ -235,7 +235,7 @@ class InviteAction extends CurrentUserDesignAction
common_root_url(),
$personal,
common_local_url('showstream', array('nickname' => $user->nickname)),
common_local_url('register', array('code' => $invite->code)));
common_local_url((!common_config('site', 'openidonly')) ? 'register' : 'openidlogin', array('code' => $invite->code)));
mail_send($recipients, $headers, $body);
}

View File

@ -247,7 +247,7 @@ class LoginAction extends Action
return _('For security reasons, please re-enter your ' .
'user name and password ' .
'before changing your settings.');
} else {
} else if (common_config('openid', 'enabled')) {
return _('Login with your username and password. ' .
'Don\'t have a username yet? ' .
'[Register](%%action.register%%) a new account.');

View File

@ -146,8 +146,8 @@ class NewgroupAction extends Action
} else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).'));
return;
} else if (!is_null($description) && mb_strlen($description) > 140) {
$this->showForm(_('description is too long (max 140 chars).'));
} else if (User_group::descriptionTooLong($description)) {
$this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription()));
return;
} else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).'));

View File

@ -144,9 +144,10 @@ class NewmessageAction extends Action
} else {
$content_shortened = common_shorten_links($this->content);
if (mb_strlen($content_shortened) > 140) {
$this->showForm(_('That\'s too long. ' .
'Max message size is 140 chars.'));
if (Message::contentTooLong($content_shortened)) {
$this->showForm(sprintf(_('That\'s too long. ' .
'Max message size is %d chars.'),
Message::maxContent()));
return;
}
}

View File

@ -162,9 +162,10 @@ class NewnoticeAction extends Action
$this->clientError(_('No content!'));
} else {
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$this->clientError(_('That\'s too long. '.
'Max notice size is 140 chars.'));
if (Notice::contentTooLong($content_shortened)) {
$this->clientError(sprintf(_('That\'s too long. '.
'Max notice size is %d chars.'),
Notice::maxContent()));
}
}
@ -241,9 +242,10 @@ class NewnoticeAction extends Action
$short_fileurl = common_shorten_url($fileurl);
$content_shortened .= ' ' . $short_fileurl;
if (mb_strlen($content_shortened) > 140) {
if (Notice::contentTooLong($content_shortened)) {
$this->deleteFile($filename);
$this->clientError(_('Max notice size is 140 chars, including attachment URL.'));
$this->clientError(sprintf(_('Max notice size is %d chars, including attachment URL.'),
Notice::maxContent()));
}
// Also, not sure this is necessary -- Zach

View File

@ -121,7 +121,9 @@ class NoticesearchAction extends SearchAction
$message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
}
else {
$message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
$message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
urlencode($q));
}
$this->elementStart('div', 'guide');

View File

@ -86,9 +86,10 @@ class NoticesearchrssAction extends Rss10Action
{
$q = $this->trimmed('q');
$c = array('url' => common_local_url('noticesearchrss', array('q' => $q)),
'title' => common_config('site', 'name') . sprintf(_(' Search Stream for "%s"'), $q),
'title' => sprintf(_('Updates with "%s"'), $q),
'link' => common_local_url('noticesearch', array('q' => $q)),
'description' => sprintf(_('All updates matching search term "%s"'), $q));
'description' => sprintf(_('Updates matching search term "%1$s" on %2$s!'),
$q, common_config('site', 'name')));
return $c;
}

View File

@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/twitterapi.php';
/**
* Oembed provider implementation
*
@ -46,17 +44,13 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
* @link http://laconi.ca/
*/
class TwitapioembedAction extends TwitterapiAction
class OembedAction extends Action
{
function oembed($args, $apidata)
function handle($args)
{
parent::handle($args);
common_debug("in oembed api action");
$this->auth_user = $apidata['user'];
$url = $args['url'];
if( substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url()) ){
$path = substr($url,strlen(common_root_url()));
@ -131,8 +125,7 @@ class TwitapioembedAction extends TwitterapiAction
default:
$this->serverError(_("$path not supported for oembed requests"), 501);
}
switch($apidata['content-type']){
switch($args['format']){
case 'xml':
$this->init_document('xml');
$this->elementStart('oembed');
@ -152,11 +145,10 @@ class TwitapioembedAction extends TwitterapiAction
if($oembed['thumbnail_width']) $this->element('thumbnail_width',null,$oembed['thumbnail_width']);
if($oembed['thumbnail_height']) $this->element('thumbnail_height',null,$oembed['thumbnail_height']);
$this->elementEnd('oembed');
$this->end_document('xml');
break;
case 'json':
case 'json': case '':
$this->init_document('json');
print(json_encode($oembed));
$this->end_document('json');
@ -164,10 +156,51 @@ class TwitapioembedAction extends TwitterapiAction
default:
$this->serverError(_('content type ' . $apidata['content-type'] . ' not supported'), 501);
}
}else{
$this->serverError(_('Only ' . common_root_url() . ' urls over plain http please'), 404);
}
}
}
function init_document($type)
{
switch ($type) {
case 'xml':
header('Content-Type: application/xml; charset=utf-8');
$this->startXML();
break;
case 'json':
header('Content-Type: application/json; charset=utf-8');
// Check for JSONP callback
$callback = $this->arg('callback');
if ($callback) {
print $callback . '(';
}
break;
default:
$this->serverError(_('Not a supported data format.'), 501);
break;
}
}
function end_document($type='xml')
{
switch ($type) {
case 'xml':
$this->endXML();
break;
case 'json':
// Check for JSONP callback
$callback = $this->arg('callback');
if ($callback) {
print ')';
}
break;
default:
$this->serverError(_('Not a supported data format.'), 501);
break;
}
return;
}
}

View File

@ -66,7 +66,7 @@ class OpensearchAction extends Action
$type = 'noticesearch';
$short_name = _('Notice Search');
}
header('Content-Type: text/html');
header('Content-Type: application/opensearchdescription+xml');
$this->startXML();
$this->elementStart('OpenSearchDescription', array('xmlns' => 'http://a9.com/-/spec/opensearch/1.1/'));
$short_name = common_config('site', 'name').' '.$short_name;

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Handle postnotice action
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -17,75 +28,71 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/omb.php');
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
/**
* Handler for postnotice action
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class PostnoticeAction extends Action
{
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
try {
$this->checkNotice();
} catch (Exception $e) {
$this->clientError($e->getMessage());
return false;
}
return true;
}
function handle($args)
{
parent::handle($args);
try {
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('postnotice'));
# Note: server-to-server function!
$server = omb_oauth_server();
list($consumer, $token) = $server->verify_request($req);
if ($this->save_notice($req, $consumer, $token)) {
print "omb_version=".OMB_VERSION_01;
}
} catch (OAuthException $e) {
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server());
$srv->handlePostNotice();
} catch (Exception $e) {
$this->serverError($e->getMessage());
return;
}
}
function save_notice(&$req, &$consumer, &$token)
function checkNotice()
{
$version = $req->get_parameter('omb_version');
if ($version != OMB_VERSION_01) {
$this->clientError(_('Unsupported OMB version'), 400);
return false;
}
# First, check to see
$listenee = $req->get_parameter('omb_listenee');
$remote_profile = Remote_profile::staticGet('uri', $listenee);
if (!$remote_profile) {
$this->clientError(_('Profile unknown'), 403);
return false;
}
$sub = Subscription::staticGet('token', $token->key);
if (!$sub) {
$this->clientError(_('No such subscription'), 403);
return false;
}
$content = $req->get_parameter('omb_notice_content');
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$content = common_shorten_links($_POST['omb_notice_content']);
if (Notice::contentTooLong($content)) {
$this->clientError(_('Invalid notice content'), 400);
return false;
}
$notice_uri = $req->get_parameter('omb_notice');
if (!Validate::uri($notice_uri) &&
!common_valid_tag($notice_uri)) {
$this->clientError(_('Invalid notice uri'), 400);
return false;
$license = $_POST['omb_notice_license'];
$site_license = common_config('license', 'url');
if ($license && !common_compatible_license($license, $site_license)) {
throw new Exception(sprintf(_('Notice license %s is not ' .
'compatible with site license %s.'),
$license, $site_license));
}
$notice_url = $req->get_parameter('omb_notice_url');
if ($notice_url && !common_valid_http_url($notice_url)) {
$this->clientError(_('Invalid notice url'), 400);
return false;
}
$notice = Notice::staticGet('uri', $notice_uri);
if (!$notice) {
$notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, null, $notice_uri);
if (is_string($notice)) {
common_server_serror($notice, 500);
return false;
}
common_broadcast_notice($notice, true);
}
return true;
}
}
?>

View File

@ -109,9 +109,16 @@ class ProfilesettingsAction extends AccountSettingsAction
_('URL of your homepage, blog, or profile on another site'));
$this->elementEnd('li');
$this->elementStart('li');
$maxBio = Profile::maxBio();
if ($maxBio > 0) {
$bioInstr = sprintf(_('Describe yourself and your interests in %d chars'),
$maxBio);
} else {
$bioInstr = _('Describe yourself and your interests');
}
$this->textarea('bio', _('Bio'),
($this->arg('bio')) ? $this->arg('bio') : $profile->bio,
_('Describe yourself and your interests in 140 chars'));
$bioInstr);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('location', _('Location'),
@ -189,7 +196,7 @@ class ProfilesettingsAction extends AccountSettingsAction
// Some validation
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
'format' => NICKNAME_FMT))) {
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
} else if (!User::allowed_nickname($nickname)) {
@ -202,8 +209,9 @@ class ProfilesettingsAction extends AccountSettingsAction
} else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).'));
return;
} else if (!is_null($bio) && mb_strlen($bio) > 140) {
$this->showForm(_('Bio is too long (max 140 chars).'));
} else if (Profile::bioTooLong($bio)) {
$this->showForm(sprintf(_('Bio is too long (max %d chars).'),
Profile::maxBio()));
return;
} else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).'));

View File

@ -59,6 +59,7 @@ class PublicAction extends Action
*/
var $page = null;
var $notice;
function isReadOnly($args)
{
@ -84,6 +85,18 @@ class PublicAction extends Action
common_set_returnto($this->selfUrl());
$this->notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
if (!$this->notice) {
$this->serverError(_('Could not retrieve public stream.'));
return;
}
if($this->page > 1 && $this->notice->N == 0){
$this->serverError(_('No such page'),$code=404);
}
return true;
}
@ -165,7 +178,8 @@ class PublicAction extends Action
}
else {
if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
$message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post!'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
}
@ -185,15 +199,7 @@ class PublicAction extends Action
function showContent()
{
$notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
if (!$notice) {
$this->serverError(_('Could not retrieve public stream.'));
return;
}
$nl = new NoticeList($notice, $this);
$nl = new NoticeList($this->notice, $this);
$cnt = $nl->show();
@ -220,9 +226,11 @@ class PublicAction extends Action
function showAnonymousMessage()
{
if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
$m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
$m = sprintf(_('This is %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
'[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ([Read more](%%doc.help%%))');
'[Join now](%%%%action.%s%%%%) to share notices about yourself with friends, family, and colleagues! ' .
'([Read more](%%%%doc.help%%%%))'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
} else {
$m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool.');

View File

@ -86,9 +86,9 @@ class PublicrssAction extends Rss10Action
{
$c = array(
'url' => common_local_url('publicrss')
, 'title' => sprintf(_('%s Public Stream'), common_config('site', 'name'))
, 'title' => sprintf(_('%s public timeline'), common_config('site', 'name'))
, 'link' => common_local_url('public')
, 'description' => sprintf(_('All updates for %s'), common_config('site', 'name')));
, 'description' => sprintf(_('%s updates from everyone!'), common_config('site', 'name')));
return $c;
}

View File

@ -72,7 +72,8 @@ class PublictagcloudAction extends Action
$message .= _('Be the first to post one!');
}
else {
$message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post one!'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
$this->elementStart('div', 'guide');

View File

@ -207,8 +207,9 @@ class RegisterAction extends Action
} else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
$this->showForm(_('Full name is too long (max 255 chars).'));
return;
} else if (!is_null($bio) && mb_strlen($bio) > 140) {
$this->showForm(_('Bio is too long (max 140 chars).'));
} else if (Profile::bioTooLong($bio)) {
$this->showForm(sprintf(_('Bio is too long (max %d chars).'),
Profile::maxBio()));
return;
} else if (!is_null($location) && mb_strlen($location) > 255) {
$this->showForm(_('Location is too long (max 255 chars).'));
@ -442,10 +443,16 @@ class RegisterAction extends Action
'or profile on another site'));
$this->elementEnd('li');
$this->elementStart('li');
$maxBio = Profile::maxBio();
if ($maxBio > 0) {
$bioInstr = sprintf(_('Describe yourself and your interests in %d chars'),
$maxBio);
} else {
$bioInstr = _('Describe yourself and your interests');
}
$this->textarea('bio', _('Bio'),
$this->trimmed('bio'),
_('Describe yourself and your '.
'interests in 140 chars'));
$bioInstr);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('location', _('Location'),

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Handler for remote subscription
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -15,12 +26,27 @@
*
* 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('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
require_once INSTALLDIR.'/extlib/libomb/profile.php';
/**
* Handler for remote subscription
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/omb.php');
class RemotesubscribeAction extends Action
{
var $nickname;
@ -47,7 +73,7 @@ class RemotesubscribeAction extends Action
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
# CSRF protection
/* Use a 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. '.
@ -71,11 +97,13 @@ class RemotesubscribeAction extends Action
if ($this->err) {
$this->element('div', 'error', $this->err);
} else {
$inst = _('To subscribe, you can [login](%%action.login%%),' .
' or [register](%%action.register%%) a new ' .
$inst = sprintf(_('To subscribe, you can [login](%%%%action.%s%%%%),' .
' or [register](%%%%action.%s%%%%) a new ' .
' account. If you already have an account ' .
' on a [compatible microblogging site](%%doc.openmublog%%), ' .
' enter your profile URL below.');
' enter your profile URL below.'),
(!common_config('site','openidonly')) ? 'login' : 'openidlogin',
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
$output = common_markup_to_html($inst);
$this->elementStart('div', 'instructions');
$this->raw($output);
@ -90,8 +118,8 @@ class RemotesubscribeAction extends Action
function showContent()
{
# id = remotesubscribe conflicts with the
# button on profile page
/* The id 'remotesubscribe' conflicts with the
button on profile page. */
$this->elementStart('form', array('id' => 'form_remote_subscribe',
'method' => 'post',
'class' => 'form_settings',
@ -117,247 +145,50 @@ class RemotesubscribeAction extends Action
function remoteSubscription()
{
$user = $this->getUser();
if (!$user) {
if (!$this->nickname) {
$this->showForm(_('No such user.'));
return;
}
$user = User::staticGet('nickname', $this->nickname);
$this->profile_url = $this->trimmed('profile_url');
if (!$this->profile_url) {
$this->showForm(_('No such user.'));
$this->showForm(_('No such user'));
return;
}
if (!Validate::uri($this->profile_url, array('allowed_schemes' => array('http', 'https')))) {
if (!common_valid_http_url($this->profile_url)) {
$this->showForm(_('Invalid profile URL (bad format)'));
return;
}
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
$yadis = Auth_Yadis_Yadis::discover($this->profile_url, $fetcher);
if (!$yadis || $yadis->failed) {
$this->showForm(_('Not a valid profile URL (no YADIS document).'));
try {
$service = new OMB_Service_Consumer($this->profile_url,
common_root_url(),
omb_oauth_datastore());
} catch (OMB_InvalidYadisException $e) {
$this->showForm(_('Not a valid profile URL (no YADIS document or ' .
'no or invalid XRDS defined).'));
return;
}
# XXX: a little liberal for sites that accidentally put whitespace before the xml declaration
$xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text));
if (!$xrds) {
$this->showForm(_('Not a valid profile URL (no XRDS defined).'));
if ($service->getServiceURI(OAUTH_ENDPOINT_REQUEST) ==
common_local_url('requesttoken') ||
User::staticGet('uri', $service->getRemoteUserURI())) {
$this->showForm(_('Thats a local profile! Login to subscribe.'));
return;
}
$omb = $this->getOmb($xrds);
if (!$omb) {
$this->showForm(_('Not a valid profile URL (incorrect services).'));
try {
$service->requestToken();
} catch (OMB_RemoteServiceException $e) {
$this->showForm(_('Couldnt get a request token.'));
return;
}
if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) ==
common_local_url('requesttoken'))
{
$this->showForm(_('That\'s a local profile! Login to subscribe.'));
return;
}
if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) {
$this->showForm(_('That\'s a local profile! Login to subscribe.'));
return;
}
list($token, $secret) = $this->requestToken($omb);
if (!$token || !$secret) {
$this->showForm(_('Couldn\'t get a request token.'));
return;
}
$this->requestAuthorization($user, $omb, $token, $secret);
}
function getUser()
{
$user = null;
if ($this->nickname) {
$user = User::staticGet('nickname', $this->nickname);
}
return $user;
}
function getOmb($xrds)
{
static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE,
OAUTH_ENDPOINT_ACCESS);
$omb = array();
# XXX: the following code could probably be refactored to eliminate dupes
$oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY);
if (!$oauth_services) {
return null;
}
$oauth_service = $oauth_services[0];
$oauth_xrd = $this->getXRD($oauth_service, $xrds);
if (!$oauth_xrd) {
return null;
}
if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) {
return null;
}
$omb_services = omb_get_services($xrds, OMB_NAMESPACE);
if (!$omb_services) {
return null;
}
$omb_service = $omb_services[0];
$omb_xrd = $this->getXRD($omb_service, $xrds);
if (!$omb_xrd) {
return null;
}
if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) {
return null;
}
# XXX: check that we got all the services we needed
foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) {
if (!array_key_exists($type, $omb) || !$omb[$type]) {
return null;
}
}
if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) {
return null;
}
return $omb;
}
function getXRD($main_service, $main_xrds)
{
$uri = omb_service_uri($main_service);
if (strpos($uri, "#") !== 0) {
# FIXME: more rigorous handling of external service definitions
return null;
}
$id = substr($uri, 1);
$nodes = $main_xrds->allXrdNodes;
$parser = $main_xrds->parser;
foreach ($nodes as $node) {
$attrs = $parser->attributes($node);
if (array_key_exists('xml:id', $attrs) &&
$attrs['xml:id'] == $id) {
# XXX: trick the constructor into thinking this is the only node
$bogus_nodes = array($node);
return new Auth_Yadis_XRDS($parser, $bogus_nodes);
}
}
return null;
}
function addServices($xrd, $types, &$omb)
{
foreach ($types as $type) {
$matches = omb_get_services($xrd, $type);
if ($matches) {
$omb[$type] = $matches[0];
} else {
# no match for type
return false;
}
}
return true;
}
function requestToken($omb)
{
$con = omb_oauth_consumer();
$url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]);
# XXX: Is this the right thing to do? Strip off GET params and make them
# POST params? Seems wrong to me.
$parsed = parse_url($url);
$params = array();
parse_str($parsed['query'], $params);
$req = OAuthRequest::from_consumer_and_token($con, null, "POST", $url, $params);
$listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
if (!$listener) {
return null;
}
$req->set_parameter('omb_listener', $listener);
$req->set_parameter('omb_version', OMB_VERSION_01);
# XXX: test to see if endpoint accepts this signature method
$req->sign_request(omb_hmac_sha1(), $con, null);
# We re-use this tool's fetcher, since it's pretty good
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
$result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION));
if ($result->status != 200) {
return null;
}
parse_str($result->body, $return);
return array($return['oauth_token'], $return['oauth_token_secret']);
}
function requestAuthorization($user, $omb, $token, $secret)
{
$con = omb_oauth_consumer();
$tok = new OAuthToken($token, $secret);
$url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]);
# XXX: Is this the right thing to do? Strip off GET params and make them
# POST params? Seems wrong to me.
$parsed = parse_url($url);
$params = array();
parse_str($parsed['query'], $params);
$req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params);
# We send over a ton of information. This lets the other
# server store info about our user, and it lets the current
# user decide if they really want to authorize the subscription.
$req->set_parameter('omb_version', OMB_VERSION_01);
$req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]));
$req->set_parameter('omb_listenee', $user->uri);
$req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname));
$req->set_parameter('omb_listenee_nickname', $user->nickname);
$req->set_parameter('omb_listenee_license', common_config('license', 'url'));
/* Create an OMB_Profile from $user. */
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
@ -365,49 +196,16 @@ class RemotesubscribeAction extends Action
return;
}
if (!is_null($profile->fullname)) {
$req->set_parameter('omb_listenee_fullname', $profile->fullname);
}
if (!is_null($profile->homepage)) {
$req->set_parameter('omb_listenee_homepage', $profile->homepage);
}
if (!is_null($profile->bio)) {
$req->set_parameter('omb_listenee_bio', $profile->bio);
}
if (!is_null($profile->location)) {
$req->set_parameter('omb_listenee_location', $profile->location);
}
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if ($avatar) {
$req->set_parameter('omb_listenee_avatar', $avatar->url);
}
# XXX: add a nonce to prevent replay attacks
$req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe'));
# XXX: test to see if endpoint accepts this signature method
$req->sign_request(omb_hmac_sha1(), $con, $tok);
# store all our info here
$omb['listenee'] = $user->nickname;
$omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
$omb['token'] = $token;
$omb['secret'] = $secret;
# call doesn't work after bounce back so we cache; maybe serialization issue...?
$omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]);
$omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]);
$omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]);
$target_url = $service->requestAuthorization(
profile_to_omb_profile($user->uri, $profile),
common_local_url('finishremotesubscribe'));
common_ensure_session();
$_SESSION['oauth_authorization_request'] = $omb;
$_SESSION['oauth_authorization_request'] = serialize($service);
# Redirect to authorization service
common_redirect($req->to_url(), 303);
return;
/* Redirect to the remote service for authorization. */
common_redirect($target_url, 303);
}
}
?>

View File

@ -48,6 +48,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
class RepliesAction extends OwnerDesignAction
{
var $page = null;
var $notice;
/**
* Prepare the object
@ -84,6 +85,13 @@ class RepliesAction extends OwnerDesignAction
common_set_returnto($this->selfUrl());
$this->notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
if($this->page > 1 && $this->notice->N == 0){
$this->serverError(_('No such page'),$code=404);
}
return true;
}
@ -159,10 +167,7 @@ class RepliesAction extends OwnerDesignAction
function showContent()
{
$notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
$nl = new NoticeList($notice, $this);
$nl = new NoticeList($this->notice, $this);
$cnt = $nl->show();
if (0 === $cnt) {
@ -187,7 +192,9 @@ class RepliesAction extends OwnerDesignAction
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
$this->user->nickname);
}
$this->elementStart('div', 'guide');

View File

@ -68,7 +68,8 @@ class RepliesrssAction extends Rss10Action
'link' => common_local_url('replies',
array('nickname' =>
$user->nickname)),
'description' => sprintf(_('Feed for replies to %s'), $user->nickname));
'description' => sprintf(_('Replies to %1$s on %2$s!'),
$user->nickname, common_config('site', 'name')));
return $c;
}

View File

@ -34,6 +34,7 @@ if (!defined('LACONICA')) {
}
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
/**
* Request token action class.
@ -52,7 +53,7 @@ class RequesttokenAction extends Action
*
* @return boolean false
*/
function isReadOnly($args)
function isReadOnly()
{
return false;
}
@ -68,14 +69,12 @@ class RequesttokenAction extends Action
{
parent::handle($args);
try {
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('requesttoken'));
$server = omb_oauth_server();
$token = $server->fetch_request_token($req);
print $token;
} catch (OAuthException $e) {
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server());
$srv->writeRequestToken();
} catch (Exception $e) {
$this->serverError($e->getMessage());
}
}
}
?>

View File

@ -114,6 +114,29 @@ class ShowfavoritesAction extends OwnerDesignAction
common_set_returnto($this->selfUrl());
$cur = common_current_user();
if (!empty($cur) && $cur->id == $this->user->id) {
// Show imported/gateway notices as well as local if
// the user is looking at his own favorites
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, true);
} else {
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, false);
}
if (empty($this->notice)) {
$this->serverError(_('Could not retrieve favorite notices.'));
return;
}
if($this->page > 1 && $this->notice->N == 0){
$this->serverError(_('No such page'),$code=404);
}
return true;
}
@ -173,7 +196,9 @@ class ShowfavoritesAction extends OwnerDesignAction
}
}
else {
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to thier favorites :)'), $this->user->nickname);
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.%s%%%%) and then post something interesting they would add to their favorites :)'),
$this->user->nickname,
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
$this->elementStart('div', 'guide');
@ -191,26 +216,7 @@ class ShowfavoritesAction extends OwnerDesignAction
function showContent()
{
$cur = common_current_user();
if (!empty($cur) && $cur->id == $this->user->id) {
// Show imported/gateway notices as well as local if
// the user is looking at his own favorites
$notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, true);
} else {
$notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1, false);
}
if (empty($notice)) {
$this->serverError(_('Could not retrieve favorite notices.'));
return;
}
$nl = new NoticeList($notice, $this);
$nl = new NoticeList($this->notice, $this);
$cnt = $nl->show();
if (0 == $cnt) {

View File

@ -130,9 +130,19 @@ class ShowgroupAction extends GroupDesignAction
$this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) {
$alias = Group_alias::staticGet('alias', $nickname);
if ($alias) {
$args = array('id' => $alias->group_id);
if ($this->page != 1) {
$args['page'] = $this->page;
}
common_redirect(common_local_url('groupbyid', $args), 301);
return false;
} else {
$this->clientError(_('No such group'), 404);
return false;
}
}
common_set_returnto($this->selfUrl());
@ -440,8 +450,9 @@ class ShowgroupAction extends GroupDesignAction
$m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' .
'short messages about their life and interests. '.
'[Join now](%%%%action.register%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
$this->group->nickname);
'[Join now](%%%%action.%s%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
$this->group->nickname,
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
} else {
$m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' .

View File

@ -103,8 +103,8 @@ class ShownoticeAction extends OwnerDesignAction
$this->user = User::staticGet('id', $this->profile->id);
if (empty($this->user)) {
$this->serverError(_('Not a local notice'), 500);
if (! $this->notice->is_local) {
common_redirect($this->notice->uri);
return false;
}
@ -196,7 +196,7 @@ class ShownoticeAction extends OwnerDesignAction
{
parent::handle($args);
if ($this->notice->is_local == 0) {
if ($this->notice->is_local == Notice::REMOTE_OMB) {
if (!empty($this->notice->url)) {
common_redirect($this->notice->url, 301);
} else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) {
@ -284,16 +284,16 @@ class ShownoticeAction extends OwnerDesignAction
$this->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.json'),
array('url'=>$this->notice->uri)),
'oembed',
array(),
array('format'=>'json','url'=>$this->notice->uri)),
'title'=>'oEmbed'),null);
$this->element('link',array('rel'=>'alternate',
'type'=>'text/xml+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.xml'),
array('url'=>$this->notice->uri)),
'oembed',
array(),
array('format'=>'xml','url'=>$this->notice->uri)),
'title'=>'oEmbed'),null);
}
}

View File

@ -358,7 +358,9 @@ class ShowstreamAction extends ProfileAction
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
$this->user->nickname);
}
$this->elementStart('div', 'guide');
@ -387,8 +389,10 @@ class ShowstreamAction extends ProfileAction
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
'[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
$this->user->nickname, $this->user->nickname);
'[Join now](%%%%action.%s%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
$this->user->nickname,
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
$this->user->nickname);
} else {
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [Laconica](http://laconi.ca/) tool. '),

View File

@ -80,6 +80,12 @@ class SmssettingsAction extends ConnectSettingsAction
function showContent()
{
if (!common_config('sms', 'enabled')) {
$this->element('div', array('class' => 'error'),
_('SMS is not available.'));
return;
}
$user = common_current_user();
$this->elementStart('form', array('method' => 'post',

View File

@ -111,7 +111,9 @@ class SubscribersAction extends GalleryAction
}
}
else {
$message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.register%%%%) and be the first?'), $this->user->nickname);
$message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.%s%%%%) and be the first?'),
$this->user->nickname,
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
}
$this->elementStart('div', 'guide');

View File

@ -174,14 +174,26 @@ class SubscriptionsListItem extends SubscriptionListItem
return;
}
if (!common_config('xmpp', 'enabled') && !common_config('sms', 'enabled')) {
return;
}
$this->out->elementStart('form', array('id' => 'subedit-' . $this->profile->id,
'method' => 'post',
'class' => 'form_subscription_edit',
'action' => common_local_url('subedit')));
$this->out->hidden('token', common_session_token());
$this->out->hidden('profile', $this->profile->id);
if (common_config('xmpp', 'enabled')) {
$this->out->checkbox('jabber', _('Jabber'), $sub->jabber);
} else {
$this->out->hidden('jabber', $sub->jabber);
}
if (common_config('sms', 'enabled')) {
$this->out->checkbox('sms', _('SMS'), $sub->sms);
} else {
$this->out->hidden('sms', $sub->sms);
}
$this->out->submit('save', _('Save'));
$this->out->elementEnd('form');
return;

View File

@ -21,6 +21,9 @@ if (!defined('LACONICA')) { exit(1); }
class TagAction extends Action
{
var $notice;
function prepare($args)
{
parent::prepare($args);
@ -42,6 +45,12 @@ class TagAction extends Action
common_set_returnto($this->selfUrl());
$this->notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
if($this->page > 1 && $this->notice->N == 0){
$this->serverError(_('No such page'),$code=404);
}
return true;
}
@ -94,9 +103,7 @@ class TagAction extends Action
function showContent()
{
$notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
$nl = new NoticeList($notice, $this);
$nl = new NoticeList($this->notice, $this);
$cnt = $nl->show();

View File

@ -61,7 +61,8 @@ class TagrssAction extends Rss10Action
$c = array('url' => common_local_url('tagrss', array('tag' => $tagname)),
'title' => $tagname,
'link' => common_local_url('tagrss', array('tag' => $tagname)),
'description' => sprintf(_('Microblog tagged with %s'), $tagname));
'description' => sprintf(_('Updates tagged with %1$s on %2$s!'),
$tagname, common_config('site', 'name')));
return $c;
}

View File

@ -141,8 +141,9 @@ class Twitapidirect_messagesAction extends TwitterapiAction
$code = 406, $apidata['content-type']);
} else {
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$this->clientError(_('That\'s too long. Max message size is 140 chars.'),
if (Message::contentTooLong($content_shortened)) {
$this->clientError(sprintf(_('That\'s too long. Max message size is %d chars.'),
Message::maxContent()),
$code = 406, $apidata['content-type']);
return;
}

View File

@ -51,6 +51,103 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
class TwitapigroupsAction extends TwitterapiAction
{
function list_groups($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$this->auth_user = $apidata['user'];
$user = $this->get_user($apidata['api_arg'], $apidata);
if (empty($user)) {
$this->clientError('Not Found', 404, $apidata['content-type']);
return;
}
$page = (int)$this->arg('page', 1);
$count = (int)$this->arg('count', 20);
$max_id = (int)$this->arg('max_id', 0);
$since_id = (int)$this->arg('since_id', 0);
$since = $this->arg('since');
$group = $user->getGroups(($page-1)*$count,
$count, $since_id, $max_id, $since);
$sitename = common_config('site', 'name');
$title = sprintf(_("%s's groups"), $user->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:Groups";
$link = common_root_url();
$subtitle = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename);
switch($apidata['content-type']) {
case 'xml':
$this->show_xml_groups($group);
break;
case 'rss':
$this->show_rss_groups($group, $title, $link, $subtitle);
break;
case 'atom':
$selfuri = common_root_url() . 'api/laconica/groups/list/' . $user->id . '.atom';
$this->show_atom_groups($group, $title, $id, $link,
$subtitle, $selfuri);
break;
case 'json':
$this->show_json_groups($group);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
function list_all($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$page = (int)$this->arg('page', 1);
$count = (int)$this->arg('count', 20);
$max_id = (int)$this->arg('max_id', 0);
$since_id = (int)$this->arg('since_id', 0);
$since = $this->arg('since');
/* TODO:
Use the $page, $count, $max_id, $since_id, and $since parameters
*/
$group = new User_group();
$group->orderBy('created DESC');
$group->find();
$sitename = common_config('site', 'name');
$title = sprintf(_("%s groups"), $sitename);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:Groups";
$link = common_root_url();
$subtitle = sprintf(_("groups on %s"), $sitename);
switch($apidata['content-type']) {
case 'xml':
$this->show_xml_groups($group);
break;
case 'rss':
$this->show_rss_groups($group, $title, $link, $subtitle);
break;
case 'atom':
$selfuri = common_root_url() . 'api/laconica/groups/list_all.atom';
$this->show_atom_groups($group, $title, $id, $link,
$subtitle, $selfuri);
break;
case 'json':
$this->show_json_groups($group);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
break;
}
}
function show($args, $apidata)
{
parent::handle($args);

View File

@ -242,13 +242,14 @@ class TwitapistatusesAction extends TwitterapiAction
$status_shortened = common_shorten_links($status);
if (mb_strlen($status_shortened) > 140) {
if (Notice::contentTooLong($status_shortened)) {
// XXX: Twitter truncates anything over 140, flags the status
// as "truncated." Sending this error may screw up some clients
// that assume Twitter will truncate for them. Should we just
// truncate too? -- Zach
$this->clientError(_('That\'s too long. Max notice size is 140 chars.'),
$this->clientError(sprintf(_('That\'s too long. Max notice size is %d chars.'),
Notice::maxContent()),
$code = 406, $apidata['content-type']);
return;
}
@ -455,7 +456,8 @@ class TwitapistatusesAction extends TwitterapiAction
function friends($args, $apidata)
{
parent::handle($args);
return $this->subscriptions($apidata, 'subscribed', 'subscriber');
$includeStatuses=! (boolean) $args['lite'];
return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses);
}
function friendsIDs($args, $apidata)
@ -467,7 +469,8 @@ class TwitapistatusesAction extends TwitterapiAction
function followers($args, $apidata)
{
parent::handle($args);
return $this->subscriptions($apidata, 'subscriber', 'subscribed');
$includeStatuses=! (boolean) $args['lite'];
return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses);
}
function followersIDs($args, $apidata)
@ -476,7 +479,7 @@ class TwitapistatusesAction extends TwitterapiAction
return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
}
function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false)
function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true)
{
$this->auth_user = $apidata['user'];
$user = $this->get_user($apidata['api_arg'], $apidata);
@ -532,26 +535,26 @@ class TwitapistatusesAction extends TwitterapiAction
if ($onlyIDs) {
$this->showIDs($others, $type);
} else {
$this->show_profiles($others, $type);
$this->show_profiles($others, $type, $includeStatuses);
}
$this->end_document($type);
}
function show_profiles($profiles, $type)
function show_profiles($profiles, $type, $includeStatuses)
{
switch ($type) {
case 'xml':
$this->elementStart('users', array('type' => 'array'));
foreach ($profiles as $profile) {
$this->show_profile($profile);
$this->show_profile($profile,$type,null,$includeStatuses);
}
$this->elementEnd('users');
break;
case 'json':
$arrays = array();
foreach ($profiles as $profile) {
$arrays[] = $this->twitter_user_array($profile, true);
$arrays[] = $this->twitter_user_array($profile, $includeStatuses);
}
print json_encode($arrays);
break;

View File

@ -0,0 +1,222 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Class for doing OAuth authentication against Twitter
*
* 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 Twitter
* @package Laconica
* @author Zach Copely <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Class for doing OAuth authentication against Twitter
*
* Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
* authorizes it, and exchanges it for an access token. It also creates a link
* (Foreign_link) between the Laconica user and Twitter user and stores the
* access token and secret in the link.
*
* @category Twitter
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
*/
class TwitterauthorizationAction extends Action
{
/**
* Initialize class members. Looks for 'oauth_token' parameter.
*
* @param array $args misc. arguments
*
* @return boolean true
*/
function prepare($args)
{
parent::prepare($args);
$this->oauth_token = $this->arg('oauth_token');
return true;
}
/**
* Handler method
*
* @param array $args is ignored since it's now passed in in prepare()
*
* @return nothing
*/
function handle($args)
{
parent::handle($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'), 403);
}
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
// If there's already a foreign link record, it means we already
// have an access token, and this is unecessary. So go back.
if (isset($flink)) {
common_redirect(common_local_url('twittersettings'));
}
// $this->oauth_token is only populated once Twitter authorizes our
// request token. If it's empty we're at the beginning of the auth
// process
if (empty($this->oauth_token)) {
$this->authorizeRequestToken();
} else {
$this->saveAccessToken();
}
}
/**
* Asks Twitter for a request token, and then redirects to Twitter
* to authorize it.
*
* @return nothing
*/
function authorizeRequestToken()
{
try {
// Get a new request token and authorize it
$client = new TwitterOAuthClient();
$req_tok =
$client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
// Sock the request token away in the session temporarily
$_SESSION['twitter_request_token'] = $req_tok->key;
$_SESSION['twitter_request_token_secret'] = $req_tok->secret;
$auth_link = $client->getAuthorizeLink($req_tok);
} catch (TwitterOAuthClientException $e) {
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
$e->getCode(), $e->getMessage());
$this->serverError(_('Couldn\'t link your Twitter account.'));
}
common_redirect($auth_link);
}
/**
* Called when Twitter returns an authorized request token. Exchanges
* it for an access token and stores it.
*
* @return nothing
*/
function saveAccessToken()
{
// Check to make sure Twitter returned the same request
// token we sent them
if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
$this->serverError(_('Couldn\'t link your Twitter account.'));
}
try {
$client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
$_SESSION['twitter_request_token_secret']);
// Exchange the request token for an access token
$atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
// Test the access token and get the user's Twitter info
$client = new TwitterOAuthClient($atok->key, $atok->secret);
$twitter_user = $client->verifyCredentials();
} catch (OAuthClientException $e) {
$msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
$e->getCode(), $e->getMessage());
$this->serverError(_('Couldn\'t link your Twitter account.'));
}
// Save the access token and Twitter user info
$this->saveForeignLink($atok, $twitter_user);
// Clean up the the mess we made in the session
unset($_SESSION['twitter_request_token']);
unset($_SESSION['twitter_request_token_secret']);
common_redirect(common_local_url('twittersettings'));
}
/**
* Saves a Foreign_link between Twitter user and local user,
* which includes the access token and secret.
*
* @param OAuthToken $access_token the access token to save
* @param mixed $twitter_user twitter API user object
*
* @return nothing
*/
function saveForeignLink($access_token, $twitter_user)
{
$user = common_current_user();
$flink = new Foreign_link();
$flink->user_id = $user->id;
$flink->foreign_id = $twitter_user->id;
$flink->service = TWITTER_SERVICE;
$creds = TwitterOAuthClient::packToken($access_token);
$flink->credentials = $creds;
$flink->created = common_sql_now();
// Defaults: noticesync on, everything else off
$flink->set_flags(true, false, false, false);
$flink_id = $flink->insert();
if (empty($flink_id)) {
common_log_db_error($flink, 'INSERT', __FILE__);
$this->serverError(_('Couldn\'t link your Twitter account.'));
}
save_twitter_user($twitter_user->id, $twitter_user->screen_name);
}
}

View File

@ -34,8 +34,6 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/connectsettingsaction.php';
require_once INSTALLDIR.'/lib/twitter.php';
define('SUBSCRIPTIONS', 80);
/**
* Settings for Twitter integration
*
@ -69,9 +67,8 @@ class TwittersettingsAction extends ConnectSettingsAction
function getInstructions()
{
return _('Add your Twitter account to automatically send '.
' your notices to Twitter, ' .
'and subscribe to Twitter friends already here.');
return _('Connect your Twitter account to share your updates ' .
'with your Twitter friends and vice-versa.');
}
/**
@ -85,6 +82,12 @@ class TwittersettingsAction extends ConnectSettingsAction
function showContent()
{
if (!common_config('twitter', 'enabled')) {
$this->element('div', array('class' => 'error'),
_('Twitter is not available.'));
return;
}
$user = common_current_user();
$profile = $user->getProfile();
@ -93,7 +96,7 @@ class TwittersettingsAction extends ConnectSettingsAction
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
if ($flink) {
if (!empty($flink)) {
$fuser = $flink->getForeignUser();
}
@ -102,40 +105,35 @@ class TwittersettingsAction extends ConnectSettingsAction
'class' => 'form_settings',
'action' =>
common_local_url('twittersettings')));
$this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
$this->element('legend', null, _('Twitter Account'));
$this->hidden('token', common_session_token());
if ($fuser) {
$this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
if (empty($fuser)) {
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'settings_twitter_remove'));
$this->element('span', 'twitter_user', $fuser->nickname);
$this->element('a', array('href' => $fuser->uri), $fuser->uri);
$this->element('p', 'form_note',
_('Current verified Twitter account.'));
$this->hidden('flink_foreign_id', $flink->foreign_id);
$this->elementStart('li', array('id' => 'settings_twitter_login_button'));
$this->element('a', array('href' => common_local_url('twitterauthorization')),
'Connect my Twitter account');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('remove', _('Remove'));
$this->elementEnd('fieldset');
} else {
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'settings_twitter_login'));
$this->input('twitter_username', _('Twitter user name'),
($this->arg('twitter_username')) ?
$this->arg('twitter_username') :
$profile->nickname,
_('No spaces, please.')); // hey, it's what Twitter says
$this->elementEnd('li');
$this->elementStart('li');
$this->password('twitter_password', _('Twitter password'));
$this->elementend('li');
$this->elementEnd('ul');
}
$this->element('legend', null, _('Twitter account'));
$this->elementStart('p', array('id' => 'form_confirmed'));
$this->element('a', array('href' => $fuser->uri), $fuser->nickname);
$this->elementEnd('p');
$this->element('p', 'form_note',
_('Connected Twitter account'));
$this->submit('remove', _('Remove'));
$this->elementEnd('fieldset');
$this->elementStart('fieldset',
array('id' => 'settings_twitter_preferences'));
$this->element('legend', null, _('Preferences'));
$this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
$this->element('legend', null, _('Preferences'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->checkbox('noticesend',
@ -167,8 +165,9 @@ class TwittersettingsAction extends ConnectSettingsAction
($flink->noticesync & FOREIGN_NOTICE_RECV) :
false);
$this->elementEnd('li');
} else {
// preserve setting even if bidrection bridge toggled off
if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
$this->hidden('noticerecv', true, 'noticerecv');
}
@ -181,115 +180,13 @@ class TwittersettingsAction extends ConnectSettingsAction
} else {
$this->submit('add', _('Add'));
}
$this->elementEnd('fieldset');
$this->showTwitterSubscriptions();
$this->elementEnd('fieldset');
}
$this->elementEnd('form');
}
/**
* Gets some of the user's Twitter friends
*
* Gets the number of Twitter friends that are on this
* instance of Laconica.
*
* @return array array of User objects
*/
function subscribedTwitterUsers()
{
$current_user = common_current_user();
$qry = 'SELECT "user".* ' .
'FROM subscription ' .
'JOIN "user" ON subscription.subscribed = "user".id ' .
'JOIN foreign_link ON foreign_link.user_id = "user".id ' .
'WHERE subscriber = %d ' .
'ORDER BY "user".nickname';
$user = new User();
$user->query(sprintf($qry, $current_user->id));
$users = array();
while ($user->fetch()) {
// Don't include the user's own self-subscription
if ($user->id != $current_user->id) {
$users[] = clone($user);
}
}
return $users;
}
/**
* Show user's Twitter friends
*
* Gets the number of Twitter friends that are on this
* instance of Laconica, and shows their mini-avatars.
*
* @return void
*/
function showTwitterSubscriptions()
{
$friends = $this->subscribedTwitterUsers();
$friends_count = count($friends);
if ($friends_count > 0) {
$this->elementStart('div', array('id' => 'entity_subscriptions',
'class' => 'section'));
$this->element('h2', null, _('Twitter Friends'));
$this->elementStart('ul', 'entities users xoxo');
for ($i = 0; $i < min($friends_count, SUBSCRIPTIONS); $i++) {
$other = Profile::staticGet($friends[$i]->id);
if (!$other) {
common_log_db_error($subs, 'SELECT', __FILE__);
continue;
}
$this->elementStart('li', 'vcard');
$this->elementStart('a', array('title' => ($other->fullname) ?
$other->fullname :
$other->nickname,
'href' => $other->profileurl,
'class' => 'url'));
$avatar = $other->getAvatar(AVATAR_MINI_SIZE);
$avatar_url = ($avatar) ?
$avatar->displayUrl() :
Avatar::defaultImage(AVATAR_MINI_SIZE);
$this->element('img', array('src' => $avatar_url,
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
'class' => 'avatar photo',
'alt' => ($other->fullname) ?
$other->fullname :
$other->nickname));
$this->element('span', 'fn nickname', $other->nickname);
$this->elementEnd('a');
$this->elementEnd('li');
}
$this->elementEnd('ul');
$this->elementEnd('div');
}
}
/**
* Handle posts to this form
*
@ -303,7 +200,6 @@ class TwittersettingsAction extends ConnectSettingsAction
function handlePost()
{
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
@ -314,8 +210,6 @@ class TwittersettingsAction extends ConnectSettingsAction
if ($this->arg('save')) {
$this->savePreferences();
} else if ($this->arg('add')) {
$this->addTwitterAccount();
} else if ($this->arg('remove')) {
$this->removeTwitterAccount();
} else {
@ -323,82 +217,6 @@ class TwittersettingsAction extends ConnectSettingsAction
}
}
/**
* Associate a Twitter account with the user's account
*
* Validates post input; verifies it against Twitter; and if
* successful stores in the database.
*
* @return void
*/
function addTwitterAccount()
{
$screen_name = $this->trimmed('twitter_username');
$password = $this->trimmed('twitter_password');
$noticesend = $this->boolean('noticesend');
$noticerecv = $this->boolean('noticerecv');
$replysync = $this->boolean('replysync');
$friendsync = $this->boolean('friendsync');
if (!Validate::string($screen_name,
array('min_length' => 1,
'max_length' => 15,
'format' => VALIDATE_NUM.VALIDATE_ALPHA.'_'))) {
$this->showForm(_('Username must have only numbers, '.
'upper- and lowercase letters, '.
'and underscore (_). 15 chars max.'));
return;
}
if (!$this->verifyCredentials($screen_name, $password)) {
$this->showForm(_('Could not verify your Twitter credentials!'));
return;
}
$twit_user = twitter_user_info($screen_name, $password);
if (!$twit_user) {
$this->showForm(sprintf(_('Unable to retrieve account information '.
'For "%s" from Twitter.'),
$screen_name));
return;
}
if (!save_twitter_user($twit_user->id, $screen_name)) {
$this->showForm(_('Unable to save your Twitter settings!'));
return;
}
$user = common_current_user();
$flink = new Foreign_link();
$flink->user_id = $user->id;
$flink->foreign_id = $twit_user->id;
$flink->service = TWITTER_SERVICE;
$flink->credentials = $password;
$flink->created = common_sql_now();
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
$flink_id = $flink->insert();
if (!$flink_id) {
common_log_db_error($flink, 'INSERT', __FILE__);
$this->showForm(_('Unable to save your Twitter settings!'));
return;
}
if ($friendsync) {
save_twitter_friends($user, $twit_user->id, $screen_name, $password);
$flink->last_friendsync = common_sql_now();
$flink->update();
}
$this->showForm(_('Twitter settings saved.'), true);
}
/**
* Disassociate an existing Twitter account from this account
*
@ -408,20 +226,11 @@ class TwittersettingsAction extends ConnectSettingsAction
function removeTwitterAccount()
{
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, 1);
$flink_foreign_id = $this->arg('flink_foreign_id');
// Maybe an old tab open...?
if ($flink->foreign_id != $flink_foreign_id) {
$this->showForm(_('That is not your Twitter account.'));
return;
}
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
$result = $flink->delete();
if (!$result) {
if (empty($result)) {
common_log_db_error($flink, 'DELETE', __FILE__);
$this->serverError(_('Couldn\'t remove Twitter user.'));
return;
@ -444,32 +253,16 @@ class TwittersettingsAction extends ConnectSettingsAction
$replysync = $this->boolean('replysync');
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
$flink = Foreign_link::getByUserID($user->id, 1);
if (!$flink) {
if (empty($flink)) {
common_log_db_error($flink, 'SELECT', __FILE__);
$this->showForm(_('Couldn\'t save Twitter preferences.'));
return;
}
$twitter_id = $flink->foreign_id;
$password = $flink->credentials;
$fuser = $flink->getForeignUser();
if (!$fuser) {
common_log_db_error($fuser, 'SELECT', __FILE__);
$this->showForm(_('Couldn\'t save Twitter preferences.'));
return;
}
$screen_name = $fuser->nickname;
$original = clone($flink);
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
$result = $flink->update($original);
if ($result === false) {
@ -478,45 +271,7 @@ class TwittersettingsAction extends ConnectSettingsAction
return;
}
if ($friendsync) {
save_twitter_friends($user, $flink->foreign_id, $screen_name, $password);
}
$this->showForm(_('Twitter preferences saved.'), true);
}
/**
* Verifies a username and password against Twitter's API
*
* @param string $screen_name Twitter user name
* @param string $password Twitter password
*
* @return boolean success flag
*/
function verifyCredentials($screen_name, $password)
{
$uri = 'http://twitter.com/account/verify_credentials.json';
$data = get_twitter_data($uri, $screen_name, $password);
if (!$data) {
return false;
}
$user = json_decode($data);
if (!$user) {
return false;
}
$twitter_id = $user->id;
if ($twitter_id) {
return $twitter_id;
}
return false;
}
}

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Unsubscribe handler
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -17,6 +28,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Unsubscribe handler
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class UnsubscribeAction extends Action
{
@ -31,16 +56,18 @@ class UnsubscribeAction extends Action
$user = common_current_user();
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)));
return;
}
# CSRF protection
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. Try again, please.'));
$this->clientError(_('There was a problem with your session token. ' .
'Try again, please.'));
return;
}
@ -53,7 +80,7 @@ class UnsubscribeAction extends Action
$other = Profile::staticGet('id', $other_id);
if (!$other_id) {
if (!$other) {
$this->clientError(_('No profile with that id.'));
return;
}
@ -76,8 +103,8 @@ class UnsubscribeAction extends Action
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions', array('nickname' =>
$user->nickname)),
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)),
303);
}
}

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Handle an updateprofile action
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -17,34 +28,34 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/omb.php');
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
/**
* Handle an updateprofile action
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class UpdateprofileAction extends Action
{
function handle($args)
{
parent::handle($args);
try {
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('updateprofile'));
# Note: server-to-server function!
$server = omb_oauth_server();
list($consumer, $token) = $server->verify_request($req);
if ($this->update_profile($req, $consumer, $token)) {
header('HTTP/1.1 200 OK');
header('Content-type: text/plain');
print "omb_version=".OMB_VERSION_01;
}
} catch (OAuthException $e) {
$this->serverError($e->getMessage());
return;
}
}
function update_profile($req, $consumer, $token)
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
$version = $req->get_parameter('omb_version');
if ($version != OMB_VERSION_01) {
@ -79,7 +90,7 @@ class UpdateprofileAction extends Action
$nickname = $req->get_parameter('omb_listenee_nickname');
if ($nickname && !Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
'format' => NICKNAME_FMT))) {
$this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return false;
}
@ -88,96 +99,20 @@ class UpdateprofileAction extends Action
$this->clientError(sprintf(_("Invalid license URL '%s'"), $license));
return false;
}
$profile_url = $req->get_parameter('omb_listenee_profile');
if ($profile_url && !common_valid_http_url($profile_url)) {
$this->clientError(sprintf(_("Invalid profile URL '%s'."), $profile_url));
return false;
}
# optional stuff
$fullname = $req->get_parameter('omb_listenee_fullname');
if ($fullname && mb_strlen($fullname) > 255) {
$this->clientError(_("Full name is too long (max 255 chars)."));
return false;
}
$homepage = $req->get_parameter('omb_listenee_homepage');
if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
$this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage));
return false;
}
$bio = $req->get_parameter('omb_listenee_bio');
if ($bio && mb_strlen($bio) > 140) {
$this->clientError(_("Bio is too long (max 140 chars)."));
return false;
}
$location = $req->get_parameter('omb_listenee_location');
if ($location && mb_strlen($location) > 255) {
$this->clientError(_("Location is too long (max 255 chars)."));
return false;
}
$avatar = $req->get_parameter('omb_listenee_avatar');
if ($avatar) {
if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
$this->clientError(sprintf(_("Invalid avatar URL '%s'"), $avatar));
return false;
}
$size = @getimagesize($avatar);
if (!$size) {
$this->clientError(sprintf(_("Can't read avatar URL '%s'"), $avatar));
return false;
}
if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
$this->clientError(sprintf(_("Wrong size image at '%s'"), $avatar));
return false;
}
if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
IMAGETYPE_PNG))) {
$this->clientError(sprintf(_("Wrong image type for '%s'"), $avatar));
return false;
}
}
$orig_profile = clone($profile);
/* Use values even if they are an empty string. Parsing an empty string in
updateProfile is the specified way of clearing a parameter in OMB. */
if (!is_null($nickname)) {
$profile->nickname = $nickname;
}
if (!is_null($profile_url)) {
$profile->profileurl = $profile_url;
}
if (!is_null($fullname)) {
$profile->fullname = $fullname;
}
if (!is_null($homepage)) {
$profile->homepage = $homepage;
}
if (!is_null($bio)) {
$profile->bio = $bio;
}
if (!is_null($location)) {
$profile->location = $location;
}
if (!$profile->update($orig_profile)) {
$this->serverError(_('Could not save new profile info'), 500);
return false;
} else {
if ($avatar) {
$temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
copy($avatar, $temp_filename);
$imagefile = new ImageFile($profile->id, $temp_filename);
$filename = Avatar::filename($profile->id,
image_type_to_extension($imagefile->type),
null,
common_timestamp());
rename($temp_filename, Avatar::path($filename));
if (!$profile->setOriginal($filename)) {
$this->serverError(_('Could not save avatar info'), 500);
return false;
}
}
return true;
}
function handle($args)
{
parent::handle($args);
try {
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server());
$srv->handleUpdateProfile();
} catch (Exception $e) {
$this->serverError($e->getMessage());
return;
}
}
}

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Let the user authorize a remote subscription request
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Robin Millette <millette@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
@ -17,9 +28,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/omb.php');
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
require_once INSTALLDIR.'/extlib/libomb/profile.php';
define('TIMESTAMP_THRESHOLD', 300);
class UserauthorizationAction extends Action
@ -32,35 +47,55 @@ class UserauthorizationAction extends Action
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
# CSRF protection
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$params = $this->getStoredParams();
$this->showForm($params, _('There was a problem with your session token. '.
'Try again, please.'));
$srv = $this->getStoredParams();
$this->showForm($srv->getRemoteUser(), _('There was a problem ' .
'with your session token. Try again, ' .
'please.'));
return;
}
# We've shown the form, now post user's choice
/* We've shown the form, now post user's choice. */
$this->sendAuthorization();
} else {
if (!common_logged_in()) {
# Go log in, and then come back
/* Go log in, and then come back. */
common_set_returnto($_SERVER['REQUEST_URI']);
if (!common_config('site', 'openidonly')) {
common_redirect(common_local_url('login'));
} else {
common_redirect(common_local_url('openidlogin'));
}
return;
}
$user = common_current_user();
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
$this->serverError(_('User without matching profile'));
return;
}
/* TODO: If no token is passed the user should get a prompt to enter
it according to OAuth Core 1.0. */
try {
$this->validateRequest();
$this->storeParams($_GET);
$this->showForm($_GET);
} catch (OAuthException $e) {
$this->validateOmb();
$srv = new OMB_Service_Provider(
profile_to_omb_profile($user->uri, $profile),
omb_oauth_datastore());
$remote_user = $srv->handleUserAuth();
} catch (Exception $e) {
$this->clearParams();
$this->clientError($e->getMessage());
return;
}
$this->storeParams($srv);
$this->showForm($remote_user);
}
}
@ -79,23 +114,24 @@ class UserauthorizationAction extends Action
function showPageNotice()
{
$this->element('p', null, _('Please check these details to make sure '.
'that you want to subscribe to this user\'s notices. '.
'If you didn\'t just ask to subscribe to someone\'s notices, '.
'click "Reject".'));
'that you want to subscribe to this ' .
'users notices. If you didnt just ask ' .
'to subscribe to someones notices, '.
'click “Reject”.'));
}
function showContent()
{
$params = $this->params;
$nickname = $params['omb_listenee_nickname'];
$profile = $params['omb_listenee_profile'];
$license = $params['omb_listenee_license'];
$fullname = $params['omb_listenee_fullname'];
$homepage = $params['omb_listenee_homepage'];
$bio = $params['omb_listenee_bio'];
$location = $params['omb_listenee_location'];
$avatar = $params['omb_listenee_avatar'];
$nickname = $params->getNickname();
$profile = $params->getProfileURL();
$license = $params->getLicenseURL();
$fullname = $params->getFullname();
$homepage = $params->getHomepage();
$bio = $params->getBio();
$location = $params->getLocation();
$avatar = $params->getAvatarURL();
$this->elementStart('div', array('class' => 'profile'));
$this->elementStart('div', 'entity_profile vcard');
@ -172,11 +208,14 @@ class UserauthorizationAction extends Action
'id' => 'userauthorization',
'class' => 'form_user_authorization',
'name' => 'userauthorization',
'action' => common_local_url('userauthorization')));
'action' => common_local_url(
'userauthorization')));
$this->hidden('token', common_session_token());
$this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user'));
$this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription'));
$this->submit('accept', _('Accept'), 'submit accept', null,
_('Subscribe to this user'));
$this->submit('reject', _('Reject'), 'submit reject', null,
_('Reject this subscription'));
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');
@ -186,218 +225,56 @@ class UserauthorizationAction extends Action
function sendAuthorization()
{
$params = $this->getStoredParams();
$srv = $this->getStoredParams();
if (!$params) {
if (is_null($srv)) {
$this->clientError(_('No authorization request!'));
return;
}
$callback = $params['oauth_callback'];
if ($this->arg('accept')) {
if (!$this->authorizeToken($params)) {
$this->clientError(_('Error authorizing token'));
}
if (!$this->saveRemoteProfile($params)) {
$this->clientError(_('Error saving remote profile'));
}
if (!$callback) {
$this->showAcceptMessage($params['oauth_token']);
} else {
$newparams = array();
$newparams['oauth_token'] = $params['oauth_token'];
$newparams['omb_version'] = OMB_VERSION_01;
$user = User::staticGet('uri', $params['omb_listener']);
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
$this->serverError(_('User without matching profile'));
$accepted = $this->arg('accept');
try {
list($val, $token) = $srv->continueUserAuth($accepted);
} catch (Exception $e) {
$this->clientError($e->getMessage());
return;
}
$newparams['omb_listener_nickname'] = $user->nickname;
$newparams['omb_listener_profile'] = common_local_url('showstream',
array('nickname' => $user->nickname));
if (!is_null($profile->fullname)) {
$newparams['omb_listener_fullname'] = $profile->fullname;
}
if (!is_null($profile->homepage)) {
$newparams['omb_listener_homepage'] = $profile->homepage;
}
if (!is_null($profile->bio)) {
$newparams['omb_listener_bio'] = $profile->bio;
}
if (!is_null($profile->location)) {
$newparams['omb_listener_location'] = $profile->location;
}
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if ($avatar) {
$newparams['omb_listener_avatar'] = $avatar->url;
}
$parts = array();
foreach ($newparams as $k => $v) {
$parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v);
}
$query_string = implode('&', $parts);
$parsed = parse_url($callback);
$url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
common_redirect($url, 303);
}
if ($val !== false) {
common_redirect($val, 303);
} elseif ($accepted) {
$this->showAcceptMessage($token);
} else {
if (!$callback) {
$this->showRejectMessage();
} else {
# XXX: not 100% sure how to signal failure... just redirect without token?
common_redirect($callback, 303);
}
}
}
function authorizeToken(&$params)
{
$token_field = $params['oauth_token'];
$rt = new Token();
$rt->tok = $token_field;
$rt->type = 0;
$rt->state = 0;
if ($rt->find(true)) {
$orig_rt = clone($rt);
$rt->state = 1; # Authorized but not used
if ($rt->update($orig_rt)) {
return true;
}
}
return false;
}
# XXX: refactor with similar code in finishremotesubscribe.php
function saveRemoteProfile(&$params)
{
# FIXME: we should really do this when the consumer comes
# back for an access token. If they never do, we've got stuff in a
# weird state.
$nickname = $params['omb_listenee_nickname'];
$fullname = $params['omb_listenee_fullname'];
$profile_url = $params['omb_listenee_profile'];
$homepage = $params['omb_listenee_homepage'];
$bio = $params['omb_listenee_bio'];
$location = $params['omb_listenee_location'];
$avatar_url = $params['omb_listenee_avatar'];
$listenee = $params['omb_listenee'];
$remote = Remote_profile::staticGet('uri', $listenee);
if ($remote) {
$exists = true;
$profile = Profile::staticGet($remote->id);
$orig_remote = clone($remote);
$orig_profile = clone($profile);
} else {
$exists = false;
$remote = new Remote_profile();
$remote->uri = $listenee;
$profile = new Profile();
}
$profile->nickname = $nickname;
$profile->profileurl = $profile_url;
if (!is_null($fullname)) {
$profile->fullname = $fullname;
}
if (!is_null($homepage)) {
$profile->homepage = $homepage;
}
if (!is_null($bio)) {
$profile->bio = $bio;
}
if (!is_null($location)) {
$profile->location = $location;
}
if ($exists) {
$profile->update($orig_profile);
} else {
$profile->created = DB_DataObject_Cast::dateTime(); # current time
$id = $profile->insert();
if (!$id) {
return false;
}
$remote->id = $id;
}
if ($exists) {
if (!$remote->update($orig_remote)) {
return false;
}
} else {
$remote->created = DB_DataObject_Cast::dateTime(); # current time
if (!$remote->insert()) {
return false;
}
}
if ($avatar_url) {
if (!$this->addAvatar($profile, $avatar_url)) {
return false;
}
}
$user = common_current_user();
$sub = new Subscription();
$sub->subscriber = $user->id;
$sub->subscribed = $remote->id;
$sub->token = $params['oauth_token']; # NOTE: request token, not valid for use!
$sub->created = DB_DataObject_Cast::dateTime(); # current time
if (!$sub->insert()) {
return false;
}
return true;
}
function addAvatar($profile, $url)
{
$temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
copy($url, $temp_filename);
$imagefile = new ImageFile($profile->id, $temp_filename);
$filename = Avatar::filename($profile->id,
image_type_to_extension($imagefile->type),
null,
common_timestamp());
rename($temp_filename, Avatar::path($filename));
return $profile->setOriginal($filename);
}
function showAcceptMessage($tok)
{
common_show_header(_('Subscription authorized'));
$this->element('p', null,
_('The subscription has been authorized, but no '.
'callback URL was passed. Check with the site\'s instructions for '.
'details on how to authorize the subscription. Your subscription token is:'));
'callback URL was passed. Check with the sites ' .
'instructions for details on how to authorize the ' .
'subscription. Your subscription token is:'));
$this->element('blockquote', 'token', $tok);
common_show_footer();
}
function showRejectMessage($tok)
function showRejectMessage()
{
common_show_header(_('Subscription rejected'));
$this->element('p', null,
_('The subscription has been rejected, but no '.
'callback URL was passed. Check with the site\'s instructions for '.
'details on how to fully reject the subscription.'));
'callback URL was passed. Check with the sites ' .
'instructions for details on how to fully reject ' .
'the subscription.'));
common_show_footer();
}
function storeParams($params)
{
common_ensure_session();
$_SESSION['userauthorizationparams'] = $params;
$_SESSION['userauthorizationparams'] = serialize($params);
}
function clearParams()
@ -409,64 +286,32 @@ class UserauthorizationAction extends Action
function getStoredParams()
{
common_ensure_session();
$params = $_SESSION['userauthorizationparams'];
$params = unserialize($_SESSION['userauthorizationparams']);
return $params;
}
# Throws an OAuthException if anything goes wrong
function validateRequest()
{
/* Find token.
TODO: If no token is passed the user should get a prompt to enter it
according to OAuth Core 1.0 */
$t = new Token();
$t->tok = $_GET['oauth_token'];
$t->type = 0;
if (!$t->find(true)) {
throw new OAuthException("Invalid request token: " . $_GET['oauth_token']);
}
$this->validateOmb();
return true;
}
function validateOmb()
{
foreach (array('omb_version', 'omb_listener', 'omb_listenee',
'omb_listenee_profile', 'omb_listenee_nickname',
'omb_listenee_license') as $param)
{
if (!isset($_GET[$param]) || is_null($_GET[$param])) {
throw new OAuthException("Required parameter '$param' not found");
}
}
# Now, OMB stuff
$version = $_GET['omb_version'];
if ($version != OMB_VERSION_01) {
throw new OAuthException("OpenMicroBlogging version '$version' not supported");
}
$listener = $_GET['omb_listener'];
$listenee = $_GET['omb_listenee'];
$nickname = $_GET['omb_listenee_nickname'];
$profile = $_GET['omb_listenee_profile'];
$user = User::staticGet('uri', $listener);
if (!$user) {
throw new OAuthException("Listener URI '$listener' not found here");
}
$cur = common_current_user();
if ($cur->id != $user->id) {
throw new OAuthException("Can't add for another user!");
}
$listenee = $_GET['omb_listenee'];
if (!Validate::uri($listenee) &&
!common_valid_tag($listenee)) {
throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
throw new Exception(sprintf(_('Listener URI %s not found here'),
$listener));
}
if (strlen($listenee) > 255) {
throw new OAuthException("Listenee URI '$listenee' too long");
throw new Exception(sprintf(_('Listenee URI %s is too long.'),
$listenee));
}
$other = User::staticGet('uri', $listenee);
if ($other) {
throw new OAuthException("Listenee URI '$listenee' is local user");
throw new Exception(sprintf(_('Listenee URI %s is a local user.'),
$listenee));
}
$remote = Remote_profile::staticGet('uri', $listenee);
@ -475,72 +320,40 @@ class UserauthorizationAction extends Action
$sub->subscriber = $user->id;
$sub->subscribed = $remote->id;
if ($sub->find(true)) {
throw new OAuthException("Already subscribed to user!");
throw new Exception('You are already subscribed to this user.');
}
}
$nickname = $_GET['omb_listenee_nickname'];
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
}
$profile = $_GET['omb_listenee_profile'];
if (!common_valid_http_url($profile)) {
throw new OAuthException("Invalid profile URL '$profile'.");
}
if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
throw new OAuthException("Profile URL '$profile' is for a local user.");
if ($profile == common_profile_url($nickname)) {
throw new Exception(sprintf(_('Profile URL %s is for a local user.'),
$profile));
}
$license = $_GET['omb_listenee_license'];
if (!common_valid_http_url($license)) {
throw new OAuthException("Invalid license URL '$license'.");
}
$site_license = common_config('license', 'url');
if (!common_compatible_license($license, $site_license)) {
throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
}
# optional stuff
$fullname = $_GET['omb_listenee_fullname'];
if ($fullname && mb_strlen($fullname) > 255) {
throw new OAuthException("Full name '$fullname' too long.");
}
$homepage = $_GET['omb_listenee_homepage'];
if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
throw new OAuthException("Invalid homepage '$homepage'");
}
$bio = $_GET['omb_listenee_bio'];
if ($bio && mb_strlen($bio) > 140) {
throw new OAuthException("Bio too long '$bio'");
}
$location = $_GET['omb_listenee_location'];
if ($location && mb_strlen($location) > 255) {
throw new OAuthException("Location too long '$location'");
throw new Exception(sprintf(_('Listenee stream license %s is not ' .
'compatible with site license %s.'),
$license, $site_license));
}
$avatar = $_GET['omb_listenee_avatar'];
if ($avatar) {
if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
throw new OAuthException("Invalid avatar URL '$avatar'");
throw new Exception(sprintf(_('Avatar URL %s is not valid.'),
$avatar));
}
$size = @getimagesize($avatar);
if (!$size) {
throw new OAuthException("Can't read avatar URL '$avatar'");
}
if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
throw new OAuthException("Wrong size image at '$avatar'");
throw new Exception(sprintf(_('Cant read avatar URL %s.'),
$avatar));
}
if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
IMAGETYPE_PNG))) {
throw new OAuthException("Wrong image type for '$avatar'");
throw new Exception(sprintf(_('Wrong image type for avatar URL '.
'%s.'), $avatar));
}
}
$callback = $_GET['oauth_callback'];
if ($callback && !common_valid_http_url($callback)) {
throw new OAuthException("Invalid callback URL '$callback'");
}
if ($callback && $callback == common_local_url('finishremotesubscribe')) {
throw new OAuthException("Callback URL '$callback' is for local site.");
}
}
}

View File

@ -88,9 +88,10 @@ class UserrssAction extends Rss10Action
$c = array('url' => common_local_url('userrss',
array('nickname' =>
$user->nickname)),
'title' => $user->nickname,
'title' => sprintf(_('%s timeline'), $user->nickname),
'link' => $profile->profileurl,
'description' => sprintf(_('Microblog by %s'), $user->nickname));
'description' => sprintf(_('Updates from %1$s on %2$s!'),
$user->nickname, common_config('site', 'name')));
return $c;
}

View File

@ -34,6 +34,8 @@ if (!defined('LACONICA')) {
}
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php';
/**
* XRDS for OpenMicroBlogging
@ -52,7 +54,7 @@ class XrdsAction extends Action
*
* @return boolean true
*/
function isReadOnly($args)
function isReadOnly()
{
return true;
}
@ -85,89 +87,31 @@ class XrdsAction extends Action
*/
function showXrds($user)
{
header('Content-Type: application/xrds+xml');
$this->startXML();
$this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
$this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xml:id' => 'oauth',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'));
$this->element('Type', null, 'xri://$xrds*simple');
$this->showService(OAUTH_ENDPOINT_REQUEST,
common_local_url('requesttoken'),
array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
array(OAUTH_HMAC_SHA1),
$user->uri);
$this->showService(OAUTH_ENDPOINT_AUTHORIZE,
common_local_url('userauthorization'),
array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
array(OAUTH_HMAC_SHA1));
$this->showService(OAUTH_ENDPOINT_ACCESS,
common_local_url('accesstoken'),
array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
array(OAUTH_HMAC_SHA1));
$this->showService(OAUTH_ENDPOINT_RESOURCE,
null,
array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
array(OAUTH_HMAC_SHA1));
$this->elementEnd('XRD');
// XXX: decide whether to include user's ID/nickname in postNotice URL
$this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xml:id' => 'omb',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'));
$this->element('Type', null, 'xri://$xrds*simple');
$this->showService(OMB_ENDPOINT_POSTNOTICE,
common_local_url('postnotice'));
$this->showService(OMB_ENDPOINT_UPDATEPROFILE,
common_local_url('updateprofile'));
$this->elementEnd('XRD');
$this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'version' => '2.0'));
$this->element('Type', null, 'xri://$xrds*simple');
$this->showService(OAUTH_DISCOVERY,
'#oauth');
$this->showService(OMB_NAMESPACE,
'#omb');
$this->elementEnd('XRD');
$this->elementEnd('XRDS');
$this->endXML();
}
/**
* Show service.
*
* @param string $type XRDS type
* @param string $uri URI
* @param array $params type parameters, null by default
* @param array $sigs type signatures, null by default
* @param string $localId local ID, null by default
*
* @return void
*/
function showService($type, $uri, $params=null, $sigs=null, $localId=null)
{
$this->elementStart('Service');
if ($uri) {
$this->element('URI', null, $uri);
}
$this->element('Type', null, $type);
if ($params) {
foreach ($params as $param) {
$this->element('Type', null, $param);
}
}
if ($sigs) {
foreach ($sigs as $sig) {
$this->element('Type', null, $sig);
}
}
if ($localId) {
$this->element('LocalID', null, $localId);
}
$this->elementEnd('Service');
$srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri,
$user->getProfile()));
/* Use libombs default XRDS Writer. */
$xrds_writer = null;
$srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer);
}
}
class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper
{
protected $urls;
public function __construct()
{
$this->urls = array(
OAUTH_ENDPOINT_REQUEST => 'requesttoken',
OAUTH_ENDPOINT_AUTHORIZE => 'userauthorization',
OAUTH_ENDPOINT_ACCESS => 'accesstoken',
OMB_ENDPOINT_POSTNOTICE => 'postnotice',
OMB_ENDPOINT_UPDATEPROFILE => 'updateprofile');
}
public function getURL($action)
{
return common_local_url($this->urls[$action]);
}
}
?>

129
classes/Config.php Executable file
View File

@ -0,0 +1,129 @@
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
/**
* Table Definition for config
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Config extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'config'; // table name
public $section; // varchar(32) primary_key not_null
public $setting; // varchar(32) primary_key not_null
public $value; // varchar(255)
/* Static get */
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Config',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
const settingsKey = 'config:settings';
static function loadSettings()
{
$settings = self::_getSettings();
if (!empty($settings)) {
self::_applySettings($settings);
}
}
static function _getSettings()
{
$c = self::memcache();
if (!empty($c)) {
$settings = $c->get(common_cache_key(self::settingsKey));
if (!empty($settings)) {
return $settings;
}
}
$settings = array();
$config = new Config();
$config->find();
while ($config->fetch()) {
$settings[] = array($config->section, $config->setting, $config->value);
}
$config->free();
if (!empty($c)) {
$c->set(common_cache_key(self::settingsKey), $settings);
}
return $settings;
}
static function _applySettings($settings)
{
global $config;
foreach ($settings as $s) {
list($section, $setting, $value) = $s;
$config[$section][$setting] = $value;
}
}
function insert()
{
$result = parent::insert();
if ($result) {
Config::_blowSettingsCache();
}
return $result;
}
function delete()
{
$result = parent::delete();
if ($result) {
Config::_blowSettingsCache();
}
return $result;
}
function update($orig=null)
{
$result = parent::update($orig);
if ($result) {
Config::_blowSettingsCache();
}
return $result;
}
function _blowSettingsCache()
{
$c = self::memcache();
if (!empty($c)) {
$c->delete(common_cache_key(self::settingsKey));
}
}
}

View File

@ -107,7 +107,7 @@ class Design extends Memcached_DataObject
static function toWebColor($color)
{
if (is_null($color)) {
if ($color == null) {
return null;
}
@ -115,7 +115,7 @@ class Design extends Memcached_DataObject
return new WebColor($color);
} catch (WebColorException $e) {
// This shouldn't happen
common_log(LOG_ERR, "Unable to create color for design $id.",
common_log(LOG_ERR, "Unable to create web color for $color",
__FILE__);
return null;
}
@ -204,7 +204,10 @@ class Design extends Memcached_DataObject
'disposition');
foreach ($attrs as $attr) {
$siteDesign->$attr = common_config('design', $attr);
$val = common_config('design', $attr);
if ($val !== false) {
$siteDesign->$attr = $val;
}
}
}

View File

@ -95,7 +95,8 @@ class File extends Memcached_DataObject
if (empty($file_redir)) {
$redir_data = File_redirection::where($given_url);
$redir_url = $redir_data['url'];
if ($redir_url === $given_url) {
// TODO: max field length
if ($redir_url === $given_url || strlen($redir_url) > 255) {
$x = File::saveNew($redir_data, $given_url);
$file_id = $x->id;
} else {

View File

@ -29,34 +29,38 @@ class Foreign_link extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
// XXX: This only returns a 1->1 single obj mapping. Change? Or make
// a getForeignUsers() that returns more than one? --Zach
static function getByUserID($user_id, $service)
{
if (empty($user_id) || empty($service)) {
return null;
}
$flink = new Foreign_link();
$flink->service = $service;
$flink->user_id = $user_id;
$flink->limit(1);
if ($flink->find(true)) {
return $flink;
}
$result = $flink->find(true);
return empty($result) ? null : $flink;
return null;
}
static function getByForeignID($foreign_id, $service)
{
if (empty($foreign_id) || empty($service)) {
return null;
} else {
$flink = new Foreign_link();
$flink->service = $service;
$flink->foreign_id = $foreign_id;
$flink->limit(1);
if ($flink->find(true)) {
return $flink;
}
$result = $flink->find(true);
return null;
return empty($result) ? null : $flink;
}
}
function set_flags($noticesend, $noticerecv, $replysync, $friendsync)

View File

@ -14,7 +14,7 @@ class Message extends Memcached_DataObject
public $uri; // varchar(255) unique_key
public $from_profile; // int(4) not_null
public $to_profile; // int(4) not_null
public $content; // varchar(140)
public $content; // text()
public $rendered; // text()
public $url; // varchar(255)
public $created; // datetime() not_null
@ -22,8 +22,7 @@ class Message extends Memcached_DataObject
public $source; // varchar(32)
/* Static get */
function staticGet($k,$v=null)
{ return Memcached_DataObject::staticGet('Message',$k,$v); }
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Message',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
@ -68,4 +67,20 @@ class Message extends Memcached_DataObject
return $msg;
}
static function maxContent()
{
$desclimit = common_config('message', 'contentlimit');
// null => use global limit (distinct from 0!)
if (is_null($desclimit)) {
$desclimit = common_config('site', 'textlimit');
}
return $desclimit;
}
static function contentTooLong($content)
{
$contentlimit = self::maxContent();
return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
}
}

View File

@ -29,10 +29,6 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
define('NOTICE_CACHE_WINDOW', 61);
define('NOTICE_LOCAL_PUBLIC', 1);
define('NOTICE_REMOTE_OMB', 0);
define('NOTICE_LOCAL_NONPUBLIC', -1);
define('MAX_BOXCARS', 128);
class Notice extends Memcached_DataObject
@ -44,7 +40,7 @@ class Notice extends Memcached_DataObject
public $id; // int(4) primary_key not_null
public $profile_id; // int(4) not_null
public $uri; // varchar(255) unique_key
public $content; // varchar(140)
public $content; // text()
public $rendered; // text()
public $url; // varchar(255)
public $created; // datetime() not_null
@ -55,13 +51,15 @@ class Notice extends Memcached_DataObject
public $conversation; // int(4)
/* Static get */
function staticGet($k,$v=NULL) {
return Memcached_DataObject::staticGet('Notice',$k,$v);
}
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/* Notice types */
const LOCAL_PUBLIC = 1;
const REMOTE_OMB = 0;
const LOCAL_NONPUBLIC = -1;
const GATEWAY = -2;
function getProfile()
@ -148,13 +146,13 @@ class Notice extends Memcached_DataObject
}
static function saveNew($profile_id, $content, $source=null,
$is_local=1, $reply_to=null, $uri=null, $created=null) {
$is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null) {
$profile = Profile::staticGet($profile_id);
$final = common_shorten_links($content);
if (mb_strlen($final) > 140) {
if (Notice::contentTooLong($final)) {
common_log(LOG_INFO, 'Rejecting notice that is too long.');
return _('Problem saving notice. Too long.');
}
@ -191,7 +189,7 @@ class Notice extends Memcached_DataObject
if (($blacklist && in_array($profile_id, $blacklist)) ||
($source && $autosource && in_array($source, $autosource))) {
$notice->is_local = -1;
$notice->is_local = Notice::LOCAL_NONPUBLIC;
} else {
$notice->is_local = $is_local;
}
@ -523,7 +521,7 @@ class Notice extends Memcached_DataObject
function blowPublicCache($blowLast=false)
{
if ($this->is_local == 1) {
if ($this->is_local == Notice::LOCAL_PUBLIC) {
$cache = common_memcache();
if ($cache) {
$cache->delete(common_cache_key('public'));
@ -789,10 +787,11 @@ class Notice extends Memcached_DataObject
}
if (common_config('public', 'localonly')) {
$notice->whereAdd('is_local = 1');
$notice->whereAdd('is_local = ' . Notice::LOCAL_PUBLIC);
} else {
# -1 == blacklisted
$notice->whereAdd('is_local != -1');
# -1 == blacklisted, -2 == gateway (i.e. Twitter)
$notice->whereAdd('is_local !='. Notice::LOCAL_NONPUBLIC);
$notice->whereAdd('is_local !='. Notice::GATEWAY);
}
if ($since_id != 0) {
@ -1354,4 +1353,20 @@ class Notice extends Memcached_DataObject
return $last->id;
}
}
static function maxContent()
{
$contentlimit = common_config('notice', 'contentlimit');
// null => use global limit (distinct from 0!)
if (is_null($contentlimit)) {
$contentlimit = common_config('site', 'textlimit');
}
return $contentlimit;
}
static function contentTooLong($content)
{
$contentlimit = self::maxContent();
return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
}
}

View File

@ -35,14 +35,13 @@ class Profile extends Memcached_DataObject
public $fullname; // varchar(255) multiple_key
public $profileurl; // varchar(255)
public $homepage; // varchar(255) multiple_key
public $bio; // varchar(140) multiple_key
public $bio; // text() multiple_key
public $location; // varchar(255) multiple_key
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=null)
{ return Memcached_DataObject::staticGet('Profile',$k,$v); }
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
@ -461,4 +460,20 @@ class Profile extends Memcached_DataObject
$c->delete(common_cache_key('profile:notice_count:'.$this->id));
}
}
static function maxBio()
{
$biolimit = common_config('profile', 'biolimit');
// null => use global limit (distinct from 0!)
if (is_null($biolimit)) {
$biolimit = common_config('site', 'textlimit');
}
return $biolimit;
}
static function bioTooLong($bio)
{
$biolimit = self::maxBio();
return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
}
}

View File

@ -13,7 +13,7 @@ class User_group extends Memcached_DataObject
public $nickname; // varchar(64) unique_key
public $fullname; // varchar(255)
public $homepage; // varchar(255)
public $description; // varchar(140)
public $description; // text()
public $location; // varchar(255)
public $original_logo; // varchar(255)
public $homepage_logo; // varchar(255)
@ -297,4 +297,61 @@ class User_group extends Memcached_DataObject
return $ids;
}
static function maxDescription()
{
$desclimit = common_config('group', '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::maxDescription();
return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
}
function asAtomEntry($namespace=false, $source=false)
{
$xs = new XMLStringer(true);
if ($namespace) {
$attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
} else {
$attrs = array();
}
$xs->elementStart('entry', $attrs);
if ($source) {
$xs->elementStart('source');
$xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
$xs->element('link', array('href' => $this->permalink()));
}
if ($source) {
$xs->elementEnd('source');
}
$xs->element('title', null, $this->nickname);
$xs->element('summary', null, $this->description);
$xs->element('link', array('rel' => 'alternate',
'href' => $this->permalink()));
$xs->element('id', null, $this->permalink());
$xs->element('published', null, common_date_w3dtf($this->created));
$xs->element('updated', null, common_date_w3dtf($this->modified));
$xs->element('content', array('type' => 'html'), $this->description);
$xs->elementEnd('entry');
return $xs->getString();
}
}

View File

@ -16,6 +16,15 @@ width = K
height = K
url = U
[config]
section = 130
setting = 130
value = 2
[config__keys]
section = K
setting = K
[confirm_address]
code = 130
user_id = 129
@ -239,7 +248,7 @@ id = 129
uri = 2
from_profile = 129
to_profile = 129
content = 2
content = 34
rendered = 34
url = 2
created = 142
@ -266,7 +275,7 @@ ts = K
id = 129
profile_id = 129
uri = 2
content = 2
content = 34
rendered = 34
url = 2
created = 142
@ -314,7 +323,7 @@ nickname = 130
fullname = 2
profileurl = 2
homepage = 2
bio = 2
bio = 34
location = 2
created = 142
modified = 384
@ -486,7 +495,7 @@ id = 129
nickname = 2
fullname = 2
homepage = 2
description = 2
description = 34
location = 2
original_logo = 2
homepage_logo = 2

View File

@ -38,6 +38,8 @@ $config['site']['path'] = 'laconica';
// $config['site']['closed'] = true;
// Only allow registration for people invited by another user
// $config['site']['inviteonly'] = true;
// Only allow registrations and logins through OpenID
// $config['site']['openidonly'] = true;
// Make the site invisible to non-logged-in users
// $config['site']['private'] = true;
@ -97,6 +99,9 @@ $config['sphinx']['port'] = 3312;
// $config['xmpp']['public'][] = 'someindexer@example.net';
// $config['xmpp']['debug'] = false;
// Disable OpenID
// $config['openid']['enabled'] = false;
// Turn off invites
// $config['invite']['enabled'] = false;
@ -164,6 +169,15 @@ $config['sphinx']['port'] = 3312;
// $config['memcached']['server'] = 'localhost';
// $config['memcached']['port'] = 11211;
// Disable post-by-email
// $config['emailpost']['enabled'] = false;
// Disable SMS
// $config['sms']['enabled'] = false;
// Disable Twitter integration
// $config['twitter']['enabled'] = false;
// Twitter integration source attribute. Note: default is Laconica
// $config['integration']['source'] = 'Laconica';
@ -173,6 +187,10 @@ $config['sphinx']['port'] = 3312;
//
// $config['twitterbridge']['enabled'] = true;
// Twitter OAuth settings
// $config['twitter']['consumer_key'] = 'YOURKEY';
// $config['twitter']['consumer_secret'] = 'YOURSECRET';
// Edit throttling. Off by default. If turned on, you can only post 20 notices
// every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
// real users without getting uncontrollable floods from spammers or runaway bots.
@ -243,5 +261,6 @@ $config['sphinx']['port'] = 3312;
// $config['attachments']['user_quota'] = 50000000;
// $config['attachments']['monthly_quota'] = 15000000;
// $config['attachments']['uploads'] = true;
// $config['attachments']['path'] = "/file/";
// $config['oohembed']['endpoint'] = 'http://oohembed.com/oohembed/';

View File

@ -1,2 +1,12 @@
// SQL commands to update an 0.8.x version of Laconica
// to 0.9.x.
alter table notice
modify column content text comment 'update content';
alter table message
modify column content text comment 'message content';
alter table profile
modify column bio text comment 'descriptive biography';
alter table user_group
modify column description text comment 'group description';

2
db/08to09_pg.sql Normal file
View File

@ -0,0 +1,2 @@
// SQL commands to update an 0.8.x version of Laconica
// to 0.9.x.

View File

@ -6,7 +6,7 @@ create table profile (
fullname varchar(255) comment 'display name',
profileurl varchar(255) comment 'URL, cached so we dont regenerate',
homepage varchar(255) comment 'identifying URL',
bio varchar(140) comment 'descriptive biography',
bio text comment 'descriptive biography',
location varchar(255) comment 'physical location',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
@ -110,7 +110,7 @@ create table notice (
id integer auto_increment primary key comment 'unique identifier',
profile_id integer not null comment 'who made the update' references profile (id),
uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
content varchar(140) comment 'update content',
content text comment 'update content',
rendered text comment 'HTML version of the content',
url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
created datetime not null comment 'date this record was created',
@ -331,7 +331,7 @@ create table message (
uri varchar(255) unique key comment 'universally unique identifier',
from_profile integer not null comment 'who the message is from' references profile (id),
to_profile integer not null comment 'who the message is to' references profile (id),
content varchar(140) comment 'message content',
content text comment 'message content',
rendered text comment 'HTML version of the content',
url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
created datetime not null comment 'date this record was created',
@ -380,7 +380,7 @@ create table user_group (
nickname varchar(64) unique key comment 'nickname for addressing',
fullname varchar(255) comment 'display name',
homepage varchar(255) comment 'URL, cached so we dont regenerate',
description varchar(140) comment 'descriptive biography',
description text comment 'group description',
location varchar(255) comment 'related physical location, if any',
original_logo varchar(255) comment 'original size logo',
@ -547,3 +547,13 @@ create table deleted_notice (
index deleted_notice_profile_id_idx (profile_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table config (
section varchar(32) comment 'configuration section',
setting varchar(32) comment 'configuration setting',
value varchar(255) comment 'configuration value',
constraint primary key (section, setting)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -22,6 +22,8 @@ VALUES
('IdentiFox','IdentiFox','http://www.bitbucket.org/uncryptic/identifox/', now()),
('identitwitch','IdentiTwitch','http://richfish.org/identitwitch/', now()),
('LaTwit','LaTwit','http://latwit.mac65.com/', now()),
('LiveTweeter', 'LiveTweeter', 'http://addons.songbirdnest.com/addon/1204', now()),
('livetweeter', 'livetweeter', 'http://addons.songbirdnest.com/addon/1204', now()),
('maisha', 'Maisha', 'http://maisha.grango.org/', now()),
('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()),
('Mobidentica', 'Mobidentica', 'http://www.substanceofcode.com/software/mobidentica/', now()),
@ -34,6 +36,7 @@ VALUES
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
('qwit', 'Qwit', 'http://code.google.com/p/qwit/', now()),
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()),
('rygh.no','rygh.no','http://rygh.no/', now()),

View File

@ -32,4 +32,15 @@ currently-implemented commands:
you subscribe to.
* **off**: Turn off notifications. You'll no longer receive Jabber
notifications.
* **stop**: Same as 'off'
* **quit**: Same as 'off'
* **help**: Show this help. List available Jabber/XMPP commands
* **follow &lt;nickname&gt;**: Subscribe to &lt;nickname&gt;
* **sub &lt;nickname&gt;**: Same as follow
* **leave &lt;nickname&gt;**: Subscribe to &lt;nickname&gt;
* **unsub &lt;nickname&gt;**: Same as leave
* **d &lt;nickname&gt; &lt;text&gt;**: Send direct message to &lt;nickname&gt; with message body &lt;text&gt;
* **get &lt;nickname&gt;**: Get last notice from &lt;nickname&gt;
* **last &lt;nickname&gt;**: Same as 'get'
* **whois &lt;nickname&gt;**: Get Profile info on &lt;nickname&gt;
* **fav &lt;nickname&gt;**: Add user's last notice as a favorite

View File

@ -44,24 +44,24 @@ You can use the following commands with %%site.name%%.
* on - turn on notifications
* off - turn off notifications
* help - show this help
* follow <nickname> - subscribe to user
* leave <nickname> - unsubscribe from user
* d <nickname> <text> - direct message to user
* get <nickname> - get last notice from user
* whois <nickname> - get profile info on user
* fav <nickname> - add user's last notice as a 'fave'
* follow &lt;nickname&gt; - subscribe to user
* leave &lt;nickname&gt; - unsubscribe from user
* d &lt;nickname&gt; &lt;text&gt; - direct message to user
* get &lt;nickname&gt; - get last notice from user
* whois &lt;nickname&gt; - get profile info on user
* fav &lt;nickname&gt; - add user's last notice as a 'fave'
* stats - get your stats
* stop - same as 'off'
* quit - same as 'off'
* sub <nickname> - same as 'follow'
* unsub <nickname> - same as 'leave'
* last <nickname> - same as 'get'
* on <nickname> - not yet implemented.
* off <nickname> - not yet implemented.
* nudge <nickname> - not yet implemented.
* invite <phone number> - not yet implemented.
* track <word> - not yet implemented.
* untrack <word> - not yet implemented.
* sub &lt;nickname&gt; - same as 'follow'
* unsub &lt;nickname&gt; - same as 'leave'
* last &lt;nickname&gt; - same as 'get'
* on &lt;nickname&gt; - not yet implemented.
* off &lt;nickname&gt; - not yet implemented.
* nudge &lt;nickname&gt; - not yet implemented.
* invite &lt;phone number&gt; - not yet implemented.
* track &lt;word&gt; - not yet implemented.
* untrack &lt;word&gt; - not yet implemented.
* track off - not yet implemented.
* untrack all - not yet implemented.
* tracks - not yet implemented.

View File

@ -0,0 +1,51 @@
<?php
require_once 'xrds_mapper.php';
require_once 'constants.php';
/**
* Map XRDS actions to URLs using base URLs.
*
* This interface specifies classes which write the XRDS file announcing
* the OMB server. An instance of an implementing class should be passed to
* OMB_Service_Provider->writeXRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Base_URL_XRDS_Mapper implements OMB_XRDS_Mapper {
protected $urls;
public function __construct($oauth_base, $omb_base) {
$this->urls = array(
OAUTH_ENDPOINT_REQUEST => $oauth_base . 'requesttoken',
OAUTH_ENDPOINT_AUTHORIZE => $oauth_base . 'userauthorization',
OAUTH_ENDPOINT_ACCESS => $oauth_base . 'accesstoken',
OMB_ENDPOINT_POSTNOTICE => $omb_base . 'postnotice',
OMB_ENDPOINT_UPDATEPROFILE => $omb_base . 'updateprofile');
}
public function getURL($action) {
return $this->urls[$action];
}
}
?>

View File

@ -0,0 +1,58 @@
<?php
/**
* Constants for libomb
*
* This file contains constant definitions for libomb. The defined constants
* are service and namespace URIs for OAuth and OMB as used in XRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
/**
* The OMB constants.
**/
define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
/* The OMB version supported by this libomb version. */
define('OMB_VERSION', OMB_VERSION_01);
define('OMB_ENDPOINT_UPDATEPROFILE', OMB_VERSION . '/updateProfile');
define('OMB_ENDPOINT_POSTNOTICE', OMB_VERSION . '/postNotice');
/**
* The OAuth constants.
**/
define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
?>

200
extlib/libomb/datastore.php Executable file
View File

@ -0,0 +1,200 @@
<?php
require_once 'OAuth.php';
/**
* Data access interface
*
* This interface specifies data access methods libomb needs. It should be
* implemented by libomb users. OMB_Datastore is libombs main interface to the
* applications data. Objects corresponding to this interface are used in
* OMB_Service_Provider and OMB_Service_Consumer.
*
* Note that its implemented as a class since OAuthDataStore is as well a
* class, though only declaring methods.
*
* OMB_Datastore extends OAuthDataStore with two OAuth-related methods for token
* revoking and authorizing and all OMB-related methods.
* Refer to OAuth.php for a complete specification of OAuth-related methods.
*
* It is the users duty to signal and handle errors. libomb does not check
* return values nor handle exceptions. It is suggested to use exceptions.
* Note that lookup_token and getProfile return null if the requested object
* is not available. This is NOT an error and should not raise an exception.
* Same applies for lookup_nonce which returns a boolean value. These methods
* may nevertheless throw an exception, for example in case of a storage errors.
*
* Most of the parameters passed to these methods are unescaped and unverified
* user input. Therefore they should be handled with extra care to avoid
* security problems like SQL injections.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Datastore extends OAuthDataStore {
/*********
* OAUTH *
*********/
/**
* Revoke specified OAuth token
*
* Revokes the authorization token specified by $token_key.
* Throws exceptions in case of error.
*
* @param string $token_key The key of the token to be revoked
*
* @access public
**/
public function revoke_token($token_key) {
throw new Exception();
}
/**
* Authorize specified OAuth token
*
* Authorizes the authorization token specified by $token_key.
* Throws exceptions in case of error.
*
* @param string $token_key The key of the token to be authorized
*
* @access public
**/
public function authorize_token($token_key) {
throw new Exception();
}
/*********
* OMB *
*********/
/**
* Get profile by identifying URI
*
* Returns an OMB_Profile object representing the OMB profile identified by
* $identifier_uri.
* Returns null if there is no such OMB profile.
* Throws exceptions in case of other error.
*
* @param string $identifier_uri The OMB identifier URI specifying the
* requested profile
*
* @access public
*
* @return OMB_Profile The corresponding profile
**/
public function getProfile($identifier_uri) {
throw new Exception();
}
/**
* Save passed profile
*
* Stores the OMB profile $profile. Overwrites an existing entry.
* Throws exceptions in case of error.
*
* @param OMB_Profile $profile The OMB profile which should be saved
*
* @access public
**/
public function saveProfile($profile) {
throw new Exception();
}
/**
* Save passed notice
*
* Stores the OMB notice $notice. The datastore may change the passed notice.
* This might by neccessary for URIs depending on a database key. Note that
* it is the users duty to present a mechanism for his OMB_Datastore to
* appropriately change his OMB_Notice. TODO: Ugly.
* Throws exceptions in case of error.
*
* @param OMB_Notice $notice The OMB notice which should be saved
*
* @access public
**/
public function saveNotice(&$notice) {
throw new Exception();
}
/**
* Get subscriptions of a given profile
*
* Returns an array containing subscription informations for the specified
* profile. Every array entry should in turn be an array with keys
* 'uri´: The identifier URI of the subscriber
* 'token´: The subscribe token
* 'secret´: The secret token
* Throws exceptions in case of error.
*
* @param string $subscribed_user_uri The OMB identifier URI specifying the
* subscribed profile
*
* @access public
*
* @return mixed An array containing the subscriptions or 0 if no
* subscription has been found.
**/
public function getSubscriptions($subscribed_user_uri) {
throw new Exception();
}
/**
* Delete a subscription
*
* Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
* Throws exceptions in case of error.
*
* @param string $subscriber_uri The OMB identifier URI specifying the
* subscribing profile
*
* @param string $subscribed_user_uri The OMB identifier URI specifying the
* subscribed profile
*
* @access public
**/
public function deleteSubscription($subscriber_uri, $subscribed_user_uri) {
throw new Exception();
}
/**
* Save a subscription
*
* Saves the subscription from $subscriber_uri to $subscribed_user_uri.
* Throws exceptions in case of error.
*
* @param string $subscriber_uri The OMB identifier URI specifying
* the subscribing profile
*
* @param string $subscribed_user_uri The OMB identifier URI specifying
* the subscribed profile
* @param OAuthToken $token The access token
*
* @access public
**/
public function saveSubscription($subscriber_uri, $subscribed_user_uri,
$token) {
throw new Exception();
}
}
?>

99
extlib/libomb/helper.php Normal file
View File

@ -0,0 +1,99 @@
<?php
require_once 'Validate.php';
/**
* Helper functions for libomb
*
* This file contains helper functions for libomb.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Helper {
/**
* Non-scalar constants
*
* The set of OMB and OAuth Services an OMB Server has to implement.
*/
public static $OMB_SERVICES =
array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
public static $OAUTH_SERVICES =
array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, OAUTH_ENDPOINT_ACCESS);
/**
* Validate URL
*
* Basic URL validation. Currently http, https, ftp and gopher are supported
* schemes.
*
* @param string $url The URL which is to be validated.
*
* @return bool Whether URL is valid.
*
* @access public
*/
public static function validateURL($url) {
return Validate::uri($url, array('allowed_schemes' => array('http', 'https',
'gopher', 'ftp')));
}
/**
* Validate Media type
*
* Basic Media type validation. Checks for valid maintype and correct format.
*
* @param string $mediatype The Media type which is to be validated.
*
* @return bool Whether media type is valid.
*
* @access public
*/
public static function validateMediaType($mediatype) {
if (0 === preg_match('/^(\w+)\/([\w\d-+.]+)$/', $mediatype, $subtypes)) {
return false;
}
if (!in_array(strtolower($subtypes[1]), array('application', 'audio', 'image',
'message', 'model', 'multipart', 'text', 'video'))) {
return false;
}
return true;
}
/**
* Remove escaping from request parameters
*
* Neutralise the evil effects of magic_quotes_gpc in the current request.
* This is used before handing a request off to OAuthRequest::from_request.
* Many thanks to Ciaran Gultnieks for this fix.
*
* @access public
*/
public static function removeMagicQuotesFromRequest() {
if(get_magic_quotes_gpc() == 1) {
$_POST = array_map('stripslashes', $_POST);
$_GET = array_map('stripslashes', $_GET);
}
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
/**
* Exception stating that a passed parameter is invalid
*
* This exception is raised when a parameter does not obey the OMB standard.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_InvalidParameterException extends Exception {
public function __construct($value, $type, $parameter) {
parent::__construct("Invalid value $value for parameter $parameter in $type");
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
/**
* Exception stating that a requested url does not resolve to a valid yadis
*
* This exception is raised when OMB_Service is not able to discover a valid
* yadis location with XRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_InvalidYadisException extends Exception {
}
?>

272
extlib/libomb/notice.php Executable file
View File

@ -0,0 +1,272 @@
<?php
require_once 'invalidparameterexception.php';
require_once 'Validate.php';
require_once 'helper.php';
/**
* OMB Notice representation
*
* This class represents an OMB notice.
*
* Do not call the setters with null values. Instead, if you want to delete a
* field, pass an empty string. The getters will return null for empty fields.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Notice {
protected $author;
protected $uri;
protected $content;
protected $url;
protected $license_url; /* url is an own addition for clarification. */
protected $seealso_url; /* url is an own addition for clarification. */
protected $seealso_disposition;
protected $seealso_mediatype;
protected $seealso_license_url; /* url is an addition for clarification. */
/* The notice as OMB param array. Cached and rebuild on usage.
false while outdated. */
protected $param_array;
/**
* Constructor for OMB_Notice
*
* Initializes the OMB_Notice object with author, uri and content.
* These parameters are mandatory for postNotice.
*
* @param object $author An OMB_Profile object representing the author of the
* notice.
* @param string $uri The notice URI as defined by the OMB. A unique and
* unchanging identifier for a notice.
* @param string $content The content of the notice. 140 chars recommended,
* but there is no limit.
*
* @access public
*/
public function __construct($author, $uri, $content) {
$this->content = $content;
if (is_null($author)) {
throw new OMB_InvalidParameterException('', 'notice', 'omb_listenee');
}
$this->author = $author;
if (!Validate::uri($uri)) {
throw new OMB_InvalidParameterException($uri, 'notice', 'omb_notice');
}
$this->uri = $uri;
$this->param_array = false;
}
/**
* Returns the notice as array
*
* The method returns an array which contains the whole notice as array. The
* array is cached and only rebuilt on changes of the notice.
* Empty optional values are not passed.
*
* @access public
* @returns array The notice as parameter array
*/
public function asParameters() {
if ($this->param_array !== false) {
return $this->param_array;
}
$this->param_array = array(
'omb_notice' => $this->uri,
'omb_notice_content' => $this->content);
if (!is_null($this->url))
$this->param_array['omb_notice_url'] = $this->url;
if (!is_null($this->license_url))
$this->param_array['omb_notice_license'] = $this->license_url;
if (!is_null($this->seealso_url)) {
$this->param_array['omb_seealso'] = $this->seealso_url;
/* This is actually a free interpretation of the OMB standard. We assume
that additional seealso parameters are not of any use if seealso itself
is not set. */
if (!is_null($this->seealso_disposition))
$this->param_array['omb_seealso_disposition'] =
$this->seealso_disposition;
if (!is_null($this->seealso_mediatype))
$this->param_array['omb_seealso_mediatype'] = $this->seealso_mediatype;
if (!is_null($this->seealso_license_url))
$this->param_array['omb_seealso_license'] = $this->seealso_license_url;
}
return $this->param_array;
}
/**
* Builds an OMB_Notice object from array
*
* The method builds an OMB_Notice object from the passed parameters array.
* The array MUST provide a notice URI and content. The array fields HAVE TO
* be named according to the OMB standard, i. e. omb_notice_* and
* omb_seealso_*. Values are handled as not passed if the corresponding array
* fields are not set or the empty string.
*
* @param object $author An OMB_Profile object representing the author of
* the notice.
* @param string $parameters An array containing the notice parameters.
*
* @access public
*
* @returns OMB_Notice The built OMB_Notice.
*/
public static function fromParameters($author, $parameters) {
$notice = new OMB_Notice($author, $parameters['omb_notice'],
$parameters['omb_notice_content']);
if (isset($parameters['omb_notice_url'])) {
$notice->setURL($parameters['omb_notice_url']);
}
if (isset($parameters['omb_notice_license'])) {
$notice->setLicenseURL($parameters['omb_notice_license']);
}
if (isset($parameters['omb_seealso'])) {
$notice->setSeealsoURL($parameters['omb_seealso']);
}
if (isset($parameters['omb_seealso_disposition'])) {
$notice->setSeealsoDisposition($parameters['omb_seealso_disposition']);
}
if (isset($parameters['omb_seealso_mediatype'])) {
$notice->setSeealsoMediatype($parameters['omb_seealso_mediatype']);
}
if (isset($parameters['omb_seealso_license'])) {
$notice->setSeealsoLicenseURL($parameters['omb_seealso_license']);
}
return $notice;
}
public function getAuthor() {
return $this->author;
}
public function getIdentifierURI() {
return $this->uri;
}
public function getContent() {
return $this->content;
}
public function getURL() {
return $this->url;
}
public function getLicenseURL() {
return $this->license_url;
}
public function getSeealsoURL() {
return $this->seealso_url;
}
public function getSeealsoDisposition() {
return $this->seealso_disposition;
}
public function getSeealsoMediatype() {
return $this->seealso_mediatype;
}
public function getSeealsoLicenseURL() {
return $this->seealso_license_url;
}
public function setURL($url) {
if ($url === '') {
$url = null;
} elseif (!OMB_Helper::validateURL($url)) {
throw new OMB_InvalidParameterException($url, 'notice', 'omb_notice_url');
}
$this->url = $url;
$this->param_array = false;
}
public function setLicenseURL($license_url) {
if ($license_url === '') {
$license_url = null;
} elseif (!OMB_Helper::validateURL($license_url)) {
throw new OMB_InvalidParameterException($license_url, 'notice',
'omb_notice_license');
}
$this->license_url = $license_url;
$this->param_array = false;
}
public function setSeealsoURL($seealso_url) {
if ($seealso_url === '') {
$seealso_url = null;
} elseif (!OMB_Helper::validateURL($seealso_url)) {
throw new OMB_InvalidParameterException($seealso_url, 'notice',
'omb_seealso');
}
$this->seealso_url = $seealso_url;
$this->param_array = false;
}
public function setSeealsoDisposition($seealso_disposition) {
if ($seealso_disposition === '') {
$seealso_disposition = null;
} elseif ($seealso_disposition !== 'link' && $seealso_disposition !== 'inline') {
throw new OMB_InvalidParameterException($seealso_disposition, 'notice',
'omb_seealso_disposition');
}
$this->seealso_disposition = $seealso_disposition;
$this->param_array = false;
}
public function setSeealsoMediatype($seealso_mediatype) {
if ($seealso_mediatype === '') {
$seealso_mediatype = null;
} elseif (!OMB_Helper::validateMediaType($seealso_mediatype)) {
throw new OMB_InvalidParameterException($seealso_mediatype, 'notice',
'omb_seealso_mediatype');
}
$this->seealso_mediatype = $seealso_mediatype;
$this->param_array = false;
}
public function setSeealsoLicenseURL($seealso_license_url) {
if ($seealso_license_url === '') {
$seealso_license_url = null;
} elseif (!OMB_Helper::validateURL($seealso_license_url)) {
throw new OMB_InvalidParameterException($seealso_license_url, 'notice',
'omb_seealso_license');
}
$this->seealso_license_url = $seealso_license_url;
$this->param_array = false;
}
}
?>

196
extlib/libomb/omb_yadis_xrds.php Executable file
View File

@ -0,0 +1,196 @@
<?php
require_once 'Auth/Yadis/Yadis.php';
require_once 'unsupportedserviceexception.php';
require_once 'invalidyadisexception.php';
/**
* OMB XRDS representation
*
* This class represents a Yadis XRDS file for OMB. It adds some useful methods to
* Auth_Yadis_XRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Yadis_XRDS extends Auth_Yadis_XRDS {
protected $fetcher;
/**
* Create an instance from URL
*
* Constructs an OMB_Yadis_XRDS object from a given URL. A full Yadis
* discovery is performed on the URL and the XRDS is parsed.
* Throws an OMB_InvalidYadisException when no Yadis is discovered or the
* detected XRDS file is broken.
*
* @param string $url The URL on which Yadis discovery
* should be performed on
* @param Auth_Yadis_HTTPFetcher $fetcher A fetcher used to get HTTP
* resources
*
* @access public
*
* @return OMB_Yadis_XRDS The initialized object representing the given
* resource
**/
public static function fromYadisURL($url, $fetcher) {
/* Perform a Yadis discovery. */
$yadis = Auth_Yadis_Yadis::discover($url, $fetcher);
if ($yadis->failed) {
throw new OMB_InvalidYadisException($url);
}
/* Parse the XRDS file. */
$xrds = OMB_Yadis_XRDS::parseXRDS($yadis->response_text);
if ($xrds === null) {
throw new OMB_InvalidYadisException($url);
}
$xrds->fetcher = $fetcher;
return $xrds;
}
/**
* Get a specific service
*
* Returns the Auth_Yadis_Service object corresponding to the given service
* URI.
* Throws an OMB_UnsupportedServiceException if the service is not available.
*
* @param string $service URI specifier of the requested service
*
* @access public
*
* @return Auth_Yadis_Service The object representing the requested service
**/
public function getService($service) {
$match = $this->services(array( create_function('$s',
"return in_array('$service', \$s->getTypes());")));
if ($match === array()) {
throw new OMB_UnsupportedServiceException($service);
}
return $match[0];
}
/**
* Get a specific XRD
*
* Returns the OMB_Yadis_XRDS object corresponding to the given URI.
* Throws an OMB_UnsupportedServiceException if the XRD is not available.
* Note that getXRD tries to resolve external XRD parts as well.
*
* @param string $uri URI specifier of the requested XRD
*
* @access public
*
* @return OMB_Yadis_XRDS The object representing the requested XRD
**/
public function getXRD($uri) {
$nexthash = strpos($uri, '#');
if ($nexthash !== 0) {
if ($nexthash !== false) {
$cururi = substr($uri, 0, $nexthash);
$nexturi = substr($uri, $nexthash);
}
return
OMB_Yadis_XRDS::fromYadisURL($cururi, $this->fetcher)->getXRD($nexturi);
}
$id = substr($uri, 1);
foreach ($this->allXrdNodes as $node) {
$attrs = $this->parser->attributes($node);
if (array_key_exists('xml:id', $attrs) && $attrs['xml:id'] == $id) {
/* Trick the constructor into thinking this is the only node. */
$bogus_nodes = array($node);
return new OMB_Yadis_XRDS($this->parser, $bogus_nodes);
}
}
throw new OMB_UnsupportedServiceException($uri);
}
/**
* Parse an XML string containing a XRDS document
*
* Parse an XML string (XRDS document) and return either a
* Auth_Yadis_XRDS object or null, depending on whether the
* XRDS XML is valid.
* Copy and paste from parent to select correct constructor.
*
* @param string $xml_string An XRDS XML string.
*
* @access public
*
* @return mixed An instance of OMB_Yadis_XRDS or null,
* depending on the validity of $xml_string
**/
public function &parseXRDS($xml_string, $extra_ns_map = null) {
$_null = null;
if (!$xml_string) {
return $_null;
}
$parser = Auth_Yadis_getXMLParser();
$ns_map = Auth_Yadis_getNSMap();
if ($extra_ns_map && is_array($extra_ns_map)) {
$ns_map = array_merge($ns_map, $extra_ns_map);
}
if (!($parser && $parser->init($xml_string, $ns_map))) {
return $_null;
}
// Try to get root element.
$root = $parser->evalXPath('/xrds:XRDS[1]');
if (!$root) {
return $_null;
}
if (is_array($root)) {
$root = $root[0];
}
$attrs = $parser->attributes($root);
if (array_key_exists('xmlns:xrd', $attrs) &&
$attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
return $_null;
} else if (array_key_exists('xmlns', $attrs) &&
preg_match('/xri/', $attrs['xmlns']) &&
$attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
return $_null;
}
// Get the last XRD node.
$xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
if (!$xrd_nodes) {
return $_null;
}
$xrds = new OMB_Yadis_XRDS($parser, $xrd_nodes);
return $xrds;
}
}

View File

@ -0,0 +1,124 @@
<?php
require_once 'xrds_writer.php';
/**
* Write OMB-specific XRDS using XMLWriter.
*
* This class writes the XRDS file announcing the OMB server. It uses
* OMB_XMLWriter, which is a subclass of XMLWriter. An instance of
* OMB_Plain_XRDS_Writer should be passed to OMB_Service_Provider->writeXRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Plain_XRDS_Writer implements OMB_XRDS_Writer {
public function writeXRDS($user, $mapper) {
header('Content-Type: application/xrds+xml');
$xw = new XMLWriter();
$xw->openURI('php://output');
$xw->setIndent(true);
$xw->startDocument('1.0', 'UTF-8');
$this->writeFullElement($xw, 'XRDS', array('xmlns' => 'xri://$xrds'), array(
array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xml:id' => 'oauth',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'), array(
array('Type', null, 'xri://$xrds*simple'),
array('Service', null, array(
array('Type', null, OAUTH_ENDPOINT_REQUEST),
array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_REQUEST)),
array('Type', null, OAUTH_AUTH_HEADER),
array('Type', null, OAUTH_POST_BODY),
array('Type', null, OAUTH_HMAC_SHA1),
array('LocalID', null, $user->getIdentifierURI())
)),
array('Service', null, array(
array('Type', null, OAUTH_ENDPOINT_AUTHORIZE),
array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_AUTHORIZE)),
array('Type', null, OAUTH_AUTH_HEADER),
array('Type', null, OAUTH_POST_BODY),
array('Type', null, OAUTH_HMAC_SHA1)
)),
array('Service', null, array(
array('Type', null, OAUTH_ENDPOINT_ACCESS),
array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_ACCESS)),
array('Type', null, OAUTH_AUTH_HEADER),
array('Type', null, OAUTH_POST_BODY),
array('Type', null, OAUTH_HMAC_SHA1)
)),
array('Service', null, array(
array('Type', null, OAUTH_ENDPOINT_RESOURCE),
array('Type', null, OAUTH_AUTH_HEADER),
array('Type', null, OAUTH_POST_BODY),
array('Type', null, OAUTH_HMAC_SHA1)
))
)),
array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xml:id' => 'omb',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'), array(
array('Type', null, 'xri://$xrds*simple'),
array('Service', null, array(
array('Type', null, OMB_ENDPOINT_POSTNOTICE),
array('URI', null, $mapper->getURL(OMB_ENDPOINT_POSTNOTICE))
)),
array('Service', null, array(
array('Type', null, OMB_ENDPOINT_UPDATEPROFILE),
array('URI', null, $mapper->getURL(OMB_ENDPOINT_UPDATEPROFILE))
))
)),
array('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'version' => '2.0'), array(
array('Type', null, 'xri://$xrds*simple'),
array('Service', null, array(
array('Type', null, OAUTH_DISCOVERY),
array('URI', null, '#oauth')
)),
array('Service', null, array(
array('Type', null, OMB_VERSION),
array('URI', null, '#omb')
))
))
));
$xw->endDocument();
$xw->flush();
}
public static function writeFullElement($xw, $tag, $attributes, $content) {
$xw->startElement($tag);
if (!is_null($attributes)) {
foreach ($attributes as $name => $value) {
$xw->writeAttribute($name, $value);
}
}
if (is_array($content)) {
foreach ($content as $values) {
OMB_Plain_XRDS_Writer::writeFullElement($xw, $values[0], $values[1], $values[2]);
}
} else {
$xw->text($content);
}
$xw->fullEndElement();
}
}
?>

317
extlib/libomb/profile.php Executable file
View File

@ -0,0 +1,317 @@
<?php
require_once 'invalidparameterexception.php';
require_once 'Validate.php';
require_once 'helper.php';
/**
* OMB profile representation
*
* This class represents an OMB profile.
*
* Do not call the setters with null values. Instead, if you want to delete a
* field, pass an empty string. The getters will return null for empty fields.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Profile {
protected $identifier_uri;
protected $profile_url;
protected $nickname;
protected $license_url;
protected $fullname;
protected $homepage;
protected $bio;
protected $location;
protected $avatar_url;
/* The profile as OMB param array. Cached and rebuild on usage.
false while outdated. */
protected $param_array;
/**
* Constructor for OMB_Profile
*
* Initializes the OMB_Profile object with an identifier uri.
*
* @param string $identifier_uri The profile URI as defined by the OMB. A unique
* and unchanging identifier for a profile.
*
* @access public
*/
public function __construct($identifier_uri) {
if (!Validate::uri($identifier_uri)) {
throw new OMB_InvalidParameterException($identifier_uri, 'profile',
'omb_listenee or omb_listener');
}
$this->identifier_uri = $identifier_uri;
$this->param_array = false;
}
/**
* Returns the profile as array
*
* The method returns an array which contains the whole profile as array. The
* array is cached and only rebuilt on changes of the profile.
*
* @param bool $force_all Specifies whether empty fields should be added to
* the array as well. This is neccessary to clear
* fields via updateProfile.
*
* @param string $prefix The common prefix to the key for all parameters.
*
* @access public
*
* @return array The profile as parameter array
*/
public function asParameters($prefix, $force_all = false) {
if ($this->param_array === false) {
$this->param_array = array('' => $this->identifier_uri);
if ($force_all || !is_null($this->profile_url)) {
$this->param_array['_profile'] = $this->profile_url;
}
if ($force_all || !is_null($this->homepage)) {
$this->param_array['_homepage'] = $this->homepage;
}
if ($force_all || !is_null($this->nickname)) {
$this->param_array['_nickname'] = $this->nickname;
}
if ($force_all || !is_null($this->license_url)) {
$this->param_array['_license'] = $this->license_url;
}
if ($force_all || !is_null($this->fullname)) {
$this->param_array['_fullname'] = $this->fullname;
}
if ($force_all || !is_null($this->bio)) {
$this->param_array['_bio'] = $this->bio;
}
if ($force_all || !is_null($this->location)) {
$this->param_array['_location'] = $this->location;
}
if ($force_all || !is_null($this->avatar_url)) {
$this->param_array['_avatar'] = $this->avatar_url;
}
}
$ret = array();
foreach ($this->param_array as $k => $v) {
$ret[$prefix . $k] = $v;
}
return $ret;
}
/**
* Builds an OMB_Profile object from array
*
* The method builds an OMB_Profile object from the passed parameters array. The
* array MUST provide a profile URI. The array fields HAVE TO be named according
* to the OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
* parameter.
*
* @param string $parameters An array containing the profile parameters.
* @param string $prefix The common prefix of the profile parameter keys.
*
* @access public
*
* @returns OMB_Profile The built OMB_Profile.
*/
public static function fromParameters($parameters, $prefix) {
if (!isset($parameters[$prefix])) {
throw new OMB_InvalidParameterException('', 'profile', $prefix);
}
$profile = new OMB_Profile($parameters[$prefix]);
$profile->updateFromParameters($parameters, $prefix);
return $profile;
}
/**
* Update from array
*
* Updates from the passed parameters array. The array does not have to
* provide a profile URI. The array fields HAVE TO be named according to the
* OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
* parameter.
*
* @param string $parameters An array containing the profile parameters.
* @param string $prefix The common prefix of the profile parameter keys.
*
* @access public
*/
public function updateFromParameters($parameters, $prefix) {
if (isset($parameters[$prefix.'_profile'])) {
$this->setProfileURL($parameters[$prefix.'_profile']);
}
if (isset($parameters[$prefix.'_license'])) {
$this->setLicenseURL($parameters[$prefix.'_license']);
}
if (isset($parameters[$prefix.'_nickname'])) {
$this->setNickname($parameters[$prefix.'_nickname']);
}
if (isset($parameters[$prefix.'_fullname'])) {
$this->setFullname($parameters[$prefix.'_fullname']);
}
if (isset($parameters[$prefix.'_homepage'])) {
$this->setHomepage($parameters[$prefix.'_homepage']);
}
if (isset($parameters[$prefix.'_bio'])) {
$this->setBio($parameters[$prefix.'_bio']);
}
if (isset($parameters[$prefix.'_location'])) {
$this->setLocation($parameters[$prefix.'_location']);
}
if (isset($parameters[$prefix.'_avatar'])) {
$this->setAvatarURL($parameters[$prefix.'_avatar']);
}
}
public function getIdentifierURI() {
return $this->identifier_uri;
}
public function getProfileURL() {
return $this->profile_url;
}
public function getHomepage() {
return $this->homepage;
}
public function getNickname() {
return $this->nickname;
}
public function getLicenseURL() {
return $this->license_url;
}
public function getFullname() {
return $this->fullname;
}
public function getBio() {
return $this->bio;
}
public function getLocation() {
return $this->location;
}
public function getAvatarURL() {
return $this->avatar_url;
}
public function setProfileURL($profile_url) {
if (!OMB_Helper::validateURL($profile_url)) {
throw new OMB_InvalidParameterException($profile_url, 'profile',
'omb_listenee_profile or omb_listener_profile');
}
$this->profile_url = $profile_url;
$this->param_array = false;
}
public function setNickname($nickname) {
if (!Validate::string($nickname,
array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA))) {
throw new OMB_InvalidParameterException($nickname, 'profile', 'nickname');
}
$this->nickname = $nickname;
$this->param_array = false;
}
public function setLicenseURL($license_url) {
if (!OMB_Helper::validateURL($license_url)) {
throw new OMB_InvalidParameterException($license_url, 'profile',
'omb_listenee_license or omb_listener_license');
}
$this->license_url = $license_url;
$this->param_array = false;
}
public function setFullname($fullname) {
if ($fullname === '') {
$fullname = null;
} elseif (!Validate::string($fullname, array('max_length' => 255))) {
throw new OMB_InvalidParameterException($fullname, 'profile', 'fullname');
}
$this->fullname = $fullname;
$this->param_array = false;
}
public function setHomepage($homepage) {
if ($homepage === '') {
$homepage = null;
}
$this->homepage = $homepage;
$this->param_array = false;
}
public function setBio($bio) {
if ($bio === '') {
$bio = null;
} elseif (!Validate::string($bio, array('max_length' => 140))) {
throw new OMB_InvalidParameterException($bio, 'profile', 'fullname');
}
$this->bio = $bio;
$this->param_array = false;
}
public function setLocation($location) {
if ($location === '') {
$location = null;
} elseif (!Validate::string($location, array('max_length' => 255))) {
throw new OMB_InvalidParameterException($location, 'profile', 'fullname');
}
$this->location = $location;
$this->param_array = false;
}
public function setAvatarURL($avatar_url) {
if ($avatar_url === '') {
$avatar_url = null;
} elseif (!OMB_Helper::validateURL($avatar_url)) {
throw new OMB_InvalidParameterException($avatar_url, 'profile',
'omb_listenee_avatar or omb_listener_avatar');
}
$this->avatar_url = $avatar_url;
$this->param_array = false;
}
}
?>

View File

@ -0,0 +1,42 @@
<?php
/**
* Exception stating that the remote service had a failure
*
* This exception is raised when a remote service failed to return a valid
* response to a request or send a valid request.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_RemoteServiceException extends Exception {
public static function fromYadis($request_uri, $result) {
if ($result->status == 200) {
$err = 'Got wrong response ' . $result->body;
} else {
$err = 'Got error code ' . $result->status . ' with response ' . $result->body;
}
return new OMB_RemoteServiceException($request_uri . ': ' . $err);
}
public static function forRequest($action_uri, $failure) {
return new OMB_RemoteServiceException("Handler for $action_uri: " . $failure);
}
}
?>

View File

@ -0,0 +1,430 @@
<?php
require_once 'constants.php';
require_once 'Validate.php';
require_once 'Auth/Yadis/Yadis.php';
require_once 'OAuth.php';
require_once 'unsupportedserviceexception.php';
require_once 'remoteserviceexception.php';
require_once 'omb_yadis_xrds.php';
require_once 'helper.php';
/**
* OMB service representation
*
* This class represents a complete remote OMB service. It provides discovery
* and execution of the services methods.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Service_Consumer {
protected $url; /* The service URL */
protected $services; /* An array of strings mapping service URI to
service URL */
protected $token; /* An OAuthToken */
protected $listener_uri; /* The URI identifying the listener, i. e. the
remote user. */
protected $listenee_uri; /* The URI identifying the listenee, i. e. the
local user during an auth request. */
/**
* According to OAuth Core 1.0, an user authorization request is no full-blown
* OAuth request. nonce, timestamp, consumer_key and signature are not needed
* in this step. See http://laconi.ca/trac/ticket/827 for more informations.
*
* Since Laconica up to version 0.7.2 performs a full OAuth request check, a
* correct request would fail.
**/
public $performLegacyAuthRequest = true;
/* Helper stuff we are going to need. */
protected $fetcher;
protected $oauth_consumer;
protected $datastore;
/**
* Constructor for OMB_Service_Consumer
*
* Initializes an OMB_Service_Consumer object representing the OMB service
* specified by $service_url. Performs a complete service discovery using
* Yadis.
* Throws OMB_UnsupportedServiceException if XRDS file does not specify a
* complete OMB service.
*
* @param string $service_url The URL of the service
* @param string $consumer_url An URL representing the consumer
* @param OMB_Datastore $datastore An instance of a class implementing
* OMB_Datastore
*
* @access public
**/
public function __construct ($service_url, $consumer_url, $datastore) {
$this->url = $service_url;
$this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
$this->datastore = $datastore;
$this->oauth_consumer = new OAuthConsumer($consumer_url, '');
$xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
/* Detect our services. This performs a validation as well, since
getService und getXRD throw exceptions on failure. */
$this->services = array();
foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
OMB_VERSION => OMB_Helper::$OMB_SERVICES)
as $service_root => $targetservices) {
$uris = $xrds->getService($service_root)->getURIs();
$xrd = $xrds->getXRD($uris[0]);
foreach ($targetservices as $targetservice) {
$yadis_service = $xrd->getService($targetservice);
if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
$localid = $yadis_service->getElements('xrd:LocalID');
$this->listener_uri = $yadis_service->parser->content($localid[0]);
}
$uris = $yadis_service->getURIs();
$this->services[$targetservice] = $uris[0];
}
}
}
/**
* Get the handler URI for a service
*
* Returns the URI the remote web service has specified for the given
* service.
*
* @param string $service The URI identifying the service
*
* @access public
*
* @return string The service handler URI
**/
public function getServiceURI($service) {
return $this->services[$service];
}
/**
* Get the remote users URI
*
* Returns the URI of the remote user, i. e. the listener.
*
* @access public
*
* @return string The remote users URI
**/
public function getRemoteUserURI() {
return $this->listener_uri;
}
/**
* Get the listenees URI
*
* Returns the URI of the user being subscribed to, i. e. the local user.
*
* @access public
*
* @return string The local users URI
**/
public function getListeneeURI() {
return $this->listenee_uri;
}
/**
* Request a request token
*
* Performs a token request on the service. Returns an OAuthToken on success.
* Throws an exception if the request fails.
*
* @access public
*
* @return OAuthToken An unauthorized request token
**/
public function requestToken() {
/* Set the token to null just in case the user called setToken. */
$this->token = null;
$result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
array('omb_listener' => $this->listener_uri));
if ($result->status != 200) {
throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
$result);
}
parse_str($result->body, $return);
if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
$result);
}
$this->setToken($return['oauth_token'], $return['oauth_token_secret']);
return $this->token;
}
/**
*
* Request authorization
*
* Returns an URL which equals to an authorization request. The end user
* should be redirected to this location to perform authorization.
* The $finish_url should be a local resource which invokes
* OMB_Consumer::finishAuthorization on request.
*
* @param OMB_Profile $profile An OMB_Profile object representing the
* soon-to-be subscribed (i. e. local) user
* @param string $finish_url Target location after successful
* authorization
*
* @access public
*
* @return string An URL representing an authorization request
**/
public function requestAuthorization($profile, $finish_url) {
if ($this->performLegacyAuthRequest) {
$params = $profile->asParameters('omb_listenee', false);
$params['omb_listener'] = $this->listener_uri;
$params['oauth_callback'] = $finish_url;
$url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
} else {
$params = array(
'oauth_callback' => $finish_url,
'oauth_token' => $this->token->key,
'omb_version' => OMB_VERSION,
'omb_listener' => $this->listener_uri);
$params = array_merge($profile->asParameters('omb_listenee', false). $params);
/* Build result URL. */
$url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
$url .= (strrpos($url, '?') === false ? '?' : '&');
foreach ($params as $k => $v) {
$url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
}
}
$this->listenee_uri = $profile->getIdentifierURI();
return $url;
}
/**
* Finish authorization
*
* Finish the subscription process by converting the received and authorized
* request token into an access token. After that, the subscribers profile
* and the subscription are stored in the database.
* Expects an OAuthRequest in query parameters.
* Throws exceptions on failure.
*
* @access public
**/
public function finishAuthorization() {
OMB_Helper::removeMagicQuotesFromRequest();
$req = OAuthRequest::from_request();
if ($req->get_parameter('oauth_token') !=
$this->token->key) {
/* Thats not the token I wanted to get authorized. */
throw new OAuthException('The authorized token does not equal the ' .
'submitted token.');
}
if ($req->get_parameter('omb_version') != OMB_VERSION) {
throw new OMB_RemoteServiceException('The remote service uses an ' .
'unsupported OMB version');
}
/* Construct the profile to validate it. */
/* Fix OMB bug. Listener URI is not passed. */
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$params = $_POST;
} else {
$params = $_GET;
}
$params['omb_listener'] = $this->listener_uri;
require_once 'profile.php';
$listener = OMB_Profile::fromParameters($params, 'omb_listener');
/* Ask the remote service to convert the authorized request token into an
access token. */
$result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
if ($result->status != 200) {
throw new OAuthException('Could not get access token');
}
parse_str($result->body, $return);
if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
throw new OAuthException('Could not get access token');
}
$this->setToken($return['oauth_token'], $return['oauth_token_secret']);
/* Subscription is finished and valid. Now store the new subscriber and the
subscription in the database. */
$this->datastore->saveProfile($listener);
$this->datastore->saveSubscription($this->listener_uri,
$this->listenee_uri,
$this->token);
}
/**
* Return the URI identifying the listener
*
* Returns the URI for the OMB user who tries to subscribe or already has
* subscribed our user. This method is a workaround for a serious OMB flaw:
* The Listener URI is not passed in the finishauthorization call.
*
* @access public
*
* @return string the listeners URI
**/
public function getListenerURI() {
return $this->listener_uri;
}
/**
* Inform the service about a profile update
*
* Sends an updated profile to the service.
*
* @param OMB_Profile $profile The profile that has changed
*
* @access public
**/
public function updateProfile($profile) {
$params = $profile->asParameters('omb_listenee', true);
$this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
}
/**
* Inform the service about a new notice
*
* Sends a notice to the service.
*
* @param OMB_Notice $notice The notice
*
* @access public
**/
public function postNotice($notice) {
$params = $notice->asParameters();
$params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
$this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
}
/**
* Set the token member variable
*
* Initializes the token based on given token and secret token.
*
* @param string $token The token
* @param string $secret The secret token
*
* @access public
**/
public function setToken($token, $secret) {
$this->token = new OAuthToken($token, $secret);
}
/**
* Prepare an OAuthRequest object
*
* Creates an OAuthRequest object mapping the request specified by the
* parameters.
*
* @param string $action_uri The URI specifying the target service
* @param array $params Additional parameters for the service call
* @param string $method The HTTP method used to call the service
* ('POST' or 'GET', usually)
*
* @access protected
*
* @return OAuthRequest the prepared request
**/
protected function prepareAction($action_uri, $params, $method) {
$url = $this->services[$action_uri];
$url_params = array();
parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
/* Add OMB version. */
$url_params['omb_version'] = OMB_VERSION;
/* Add user-defined parameters. */
$url_params = array_merge($url_params, $params);
$req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
$this->token, $method, $url, $url_params);
/* Sign the request. */
$req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
$this->oauth_consumer, $this->token);
return $req;
}
/**
* Perform a service call
*
* Creates an OAuthRequest object and execute the mapped call as POST request.
*
* @param string $action_uri The URI specifying the target service
* @param array $params Additional parameters for the service call
*
* @access protected
*
* @return Auth_Yadis_HTTPResponse The POST request response
**/
protected function performAction($action_uri, $params) {
$req = $this->prepareAction($action_uri, $params, 'POST');
/* Return result page. */
return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
}
/**
* Perform an OMB action
*
* Executes an OMB action to date, its one of updateProfile or postNotice.
*
* @param string $action_uri The URI specifying the target service
* @param array $params Additional parameters for the service call
* @param string $listenee_uri The URI identifying the local user for whom
* the action is performed
*
* @access protected
**/
protected function performOMBAction($action_uri, $params, $listenee_uri) {
$result = $this->performAction($action_uri, $params);
if ($result->status == 403) {
/* The remote user unsubscribed us. */
$this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
} else if ($result->status != 200 ||
strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
/* The server signaled an error or sent an incorrect response. */
throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
}
}
}

View File

@ -0,0 +1,425 @@
<?php
require_once 'constants.php';
require_once 'remoteserviceexception.php';
require_once 'helper.php';
/**
* OMB service realization
*
* This class realizes a complete, simple OMB service.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_Service_Provider {
protected $user; /* An OMB_Profile representing the user */
protected $datastore; /* AN OMB_Datastore */
protected $remote_user; /* An OMB_Profile representing the remote user during
the authorization process */
protected $oauth_server; /* An OAuthServer; should only be accessed via
getOAuthServer. */
/**
* Initialize an OMB_Service_Provider object
*
* Constructs an OMB_Service_Provider instance that provides OMB services
* referring to a particular user.
*
* @param OMB_Profile $user An OMB_Profile; mandatory for XRDS
* output, user auth handling and OMB
* action performing
* @param OMB_Datastore $datastore An OMB_Datastore; mandatory for
* everything but XRDS output
* @param OAuthServer $oauth_server An OAuthServer; used for token writing
* and OMB action handling; will use
* default value if not set
*
* @access public
**/
public function __construct ($user = null, $datastore = null, $oauth_server = null) {
$this->user = $user;
$this->datastore = $datastore;
$this->oauth_server = $oauth_server;
}
public function getRemoteUser() {
return $this->remote_user;
}
/**
* Write a XRDS document
*
* Writes a XRDS document specifying the OMB service. Optionally uses a
* given object of a class implementing OMB_XRDS_Writer for output. Else
* OMB_Plain_XRDS_Writer is used.
*
* @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs
* @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to
* write the XRDS document
*
* @access public
*
* @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
* returns nothing.
**/
public function writeXRDS($xrds_mapper, $xrds_writer = null) {
if ($xrds_writer == null) {
require_once 'plain_xrds_writer.php';
$xrds_writer = new OMB_Plain_XRDS_Writer();
}
return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
}
/**
* Echo a request token
*
* Outputs an unauthorized request token for the query found in $_GET or
* $_POST.
*
* @access public
**/
public function writeRequestToken() {
OMB_Helper::removeMagicQuotesFromRequest();
echo $this->getOAuthServer()->fetch_request_token(OAuthRequest::from_request());
}
/**
* Handle an user authorization request.
*
* Parses an authorization request. This includes OAuth and OMB verification.
* Throws exceptions on failures. Returns an OMB_Profile object representing
* the remote user.
*
* The OMB_Profile passed to the constructor of OMB_Service_Provider should
* not represent the user specified in the authorization request, but the one
* currently logged in to the service. This condition being satisfied,
* handleUserAuth will check whether the listener specified in the request is
* identical to the logged in user.
*
* @access public
*
* @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote
* user
**/
public function handleUserAuth() {
OMB_Helper::removeMagicQuotesFromRequest();
/* Verify the request token. */
$this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']);
if (is_null($this->token)) {
throw new OAuthException('The given request token has not been issued ' .
'by this service.');
}
/* Verify the OMB part. */
if ($_GET['omb_version'] !== OMB_VERSION) {
throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
'Wrong OMB version ' . $_GET['omb_version']);
}
if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
'Wrong OMB listener ' . $_GET['omb_listener']);
}
foreach (array('omb_listenee', 'omb_listenee_profile',
'omb_listenee_nickname', 'omb_listenee_license') as $param) {
if (!isset($_GET[$param]) || is_null($_GET[$param])) {
throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
"Required parameter '$param' not found");
}
}
/* Store given callback for later use. */
if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
$this->callback = $_GET['oauth_callback'];
if (!OMB_Helper::validateURL($this->callback)) {
throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
'Invalid callback URL specified');
}
}
$this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
return $this->remote_user;
}
/**
* Continue the OAuth dance after user authorization
*
* Performs the appropriate actions after user answered the authorization
* request.
*
* @param bool $accepted Whether the user granted authorization
*
* @access public
*
* @return array A two-component array with the values:
* - callback The callback URL or null if none given
* - token The authorized request token or null if not
* authorized.
**/
public function continueUserAuth($accepted) {
$callback = $this->callback;
if (!$accepted) {
$this->datastore->revoke_token($this->token->key);
$this->token = null;
/* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
laconica works. Moreover I dont know the right way either. */
} else {
$this->datastore->authorize_token($this->token->key);
$this->datastore->saveProfile($this->remote_user);
$this->datastore->saveSubscription($this->user->getIdentifierURI(),
$this->remote_user->getIdentifierURI(), $this->token);
if (!is_null($this->callback)) {
/* Callback wants to get some informations as well. */
$params = $this->user->asParameters('omb_listener', false);
$params['oauth_token'] = $this->token->key;
$params['omb_version'] = OMB_VERSION;
$callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
foreach ($params as $k => $v) {
$callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
OAuthUtil::urlencode_rfc3986($v) . '&';
}
}
}
return array($callback, $this->token);
}
/**
* Echo an access token
*
* Outputs an access token for the query found in $_POST. OMB 0.1 specifies
* that the access token request has to be a POST even if OAuth allows GET as
* well.
*
* @access public
**/
public function writeAccessToken() {
OMB_Helper::removeMagicQuotesFromRequest();
echo $this->getOAuthServer()->fetch_access_token(
OAuthRequest::from_request('POST'));
}
/**
* Handle an updateprofile request
*
* Handles an updateprofile request posted to this service. Updates the
* profile through the OMB_Datastore.
*
* @access public
*
* @return OMB_Profile The updated profile
**/
public function handleUpdateProfile() {
list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
$profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
$this->datastore->saveProfile($profile);
$this->finishOMBRequest();
return $profile;
}
/**
* Handle a postnotice request
*
* Handles a postnotice request posted to this service. Saves the notice
* through the OMB_Datastore.
*
* @access public
*
* @return OMB_Notice The received notice
**/
public function handlePostNotice() {
list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
require_once 'notice.php';
$notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
$this->datastore->saveNotice($notice);
$this->finishOMBRequest();
return $notice;
}
/**
* Handle an OMB request
*
* Performs common OMB request handling.
*
* @param string $uri The URI defining the OMB endpoint being served
*
* @access protected
*
* @return array(OAuthRequest, OMB_Profile)
**/
protected function handleOMBRequest($uri) {
OMB_Helper::removeMagicQuotesFromRequest();
$req = OAuthRequest::from_request('POST');
$listenee = $req->get_parameter('omb_listenee');
try {
list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
} catch (OAuthException $e) {
header('HTTP/1.1 403 Forbidden');
throw OMB_RemoteServiceException::forRequest($uri,
'Revoked accesstoken for ' . $listenee);
}
$version = $req->get_parameter('omb_version');
if ($version !== OMB_VERSION) {
header('HTTP/1.1 400 Bad Request');
throw OMB_RemoteServiceException::forRequest($uri,
'Wrong OMB version ' . $version);
}
$profile = $this->datastore->getProfile($listenee);
if (is_null($profile)) {
header('HTTP/1.1 400 Bad Request');
throw OMB_RemoteServiceException::forRequest($uri,
'Unknown remote profile ' . $listenee);
}
$subscribers = $this->datastore->getSubscriptions($listenee);
if (count($subscribers) === 0) {
header('HTTP/1.1 403 Forbidden');
throw OMB_RemoteServiceException::forRequest($uri,
'No subscriber for ' . $listenee);
}
return array($req, $profile);
}
/**
* Finishes an OMB request handling
*
* Performs common OMB request handling finishing.
*
* @access protected
**/
protected function finishOMBRequest() {
header('HTTP/1.1 200 OK');
header('Content-type: text/plain');
/* There should be no clutter but the version. */
echo "omb_version=" . OMB_VERSION;
}
/**
* Return an OAuthServer
*
* Checks whether the OAuthServer is null. If so, initializes it with a
* default value. Returns the OAuth server.
*
* @access protected
**/
protected function getOAuthServer() {
if (is_null($this->oauth_server)) {
$this->oauth_server = new OAuthServer($this->datastore);
$this->oauth_server->add_signature_method(
new OAuthSignatureMethod_HMAC_SHA1());
}
return $this->oauth_server;
}
/**
* Publish a notice
*
* Posts an OMB notice. This includes storing the notice and posting it to
* subscribed users.
*
* @param OMB_Notice $notice The new notice
*
* @access public
*
* @return array An array mapping subscriber URIs to the exception posting to
* them has raised; Empty array if no exception occured
**/
public function postNotice($notice) {
$uri = $this->user->getIdentifierURI();
/* $notice is passed by reference and may change. */
$this->datastore->saveNotice($notice);
$subscribers = $this->datastore->getSubscriptions($uri);
/* No one to post to. */
if (is_null($subscribers)) {
return array();
}
require_once 'service_consumer.php';
$err = array();
foreach($subscribers as $subscriber) {
try {
$service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
$service->setToken($subscriber['token'], $subscriber['secret']);
$service->postNotice($notice);
} catch (Exception $e) {
$err[$subscriber['uri']] = $e;
continue;
}
}
return $err;
}
/**
* Publish a profile update
*
* Posts the current profile as an OMB profile update. This includes updating
* the stored profile and posting it to subscribed users.
*
* @access public
*
* @return array An array mapping subscriber URIs to the exception posting to
* them has raised; Empty array if no exception occured
**/
public function updateProfile() {
$uri = $this->user->getIdentifierURI();
$this->datastore->saveProfile($this->user);
$subscribers = $this->datastore->getSubscriptions($uri);
/* No one to post to. */
if (is_null($subscribers)) {
return array();
}
require_once 'service_consumer.php';
$err = array();
foreach($subscribers as $subscriber) {
try {
$service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
$service->setToken($subscriber['token'], $subscriber['secret']);
$service->updateProfile($this->user);
} catch (Exception $e) {
$err[$subscriber['uri']] = $e;
continue;
}
}
return $err;
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Exception stating that a requested service is not available
*
* This exception is raised when OMB_Service is asked to call a service the remote
* server does not provide.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
class OMB_UnsupportedServiceException extends Exception {
}
?>

33
extlib/libomb/xrds_mapper.php Executable file
View File

@ -0,0 +1,33 @@
<?php
/**
* Map XRDS actions to URLs
*
* This interface specifies classes which write the XRDS file announcing
* the OMB server. An instance of an implementing class should be passed to
* OMB_Service_Provider->writeXRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
interface OMB_XRDS_Mapper {
public function getURL($action);
}
?>

33
extlib/libomb/xrds_writer.php Executable file
View File

@ -0,0 +1,33 @@
<?php
/**
* Write OMB-specific XRDS
*
* This interface specifies classes which write the XRDS file announcing
* the OMB server. An instance of an implementing class should be passed to
* OMB_Service_Provider->writeXRDS.
*
* PHP version 5
*
* LICENSE: 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/>.
*
* @package OMB
* @author Adrian Lang <mail@adrianlang.de>
* @copyright 2009 Adrian Lang
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
**/
interface OMB_XRDS_Writer {
public function writeXRDS($user, $mapper);
}
?>

View File

@ -73,7 +73,7 @@ function handleError($error)
exit(-1);
}
function checkMirror($action_obj)
function checkMirror($action_obj, $args)
{
global $config;
@ -120,6 +120,25 @@ function isLoginAction($action)
function main()
{
// fake HTTP redirects using lighttpd's 404 redirects
if (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) {
$_lighty_url = $base_url.$_SERVER['REQUEST_URI'];
$_lighty_url = @parse_url($_lighty_url);
if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') {
$_lighty_path = preg_replace('/^'.preg_quote(common_config('site','path')).'\//', '', substr($_lighty_url['path'], 1));
$_SERVER['QUERY_STRING'] = 'p='.$_lighty_path;
if ($_lighty_url['query'])
$_SERVER['QUERY_STRING'] .= '&'.$_lighty_url['query'];
parse_str($_lighty_url['query'], $_lighty_query);
foreach ($_lighty_query as $key => $val) {
$_GET[$key] = $_REQUEST[$key] = $val;
}
$_GET['p'] = $_REQUEST['p'] = $_lighty_path;
}
}
$_SERVER['REDIRECT_URL'] = preg_replace("/\?.+$/", "", $_SERVER['REQUEST_URI']);
// quick check for fancy URL auto-detection support in installer.
if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/","",(dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) {
die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
@ -191,7 +210,7 @@ function main()
} else {
$action_obj = new $action_class();
checkMirror($action_obj);
checkMirror($action_obj, $args);
try {
if ($action_obj->prepare($args)) {

View File

@ -163,7 +163,7 @@ E_O_T;
function updateStatus($status, $error=false)
{
?>
<li <?php echo ($error) ? 'class="error"': ''; ?>><?print $status;?></li>
<li <?php echo ($error) ? 'class="error"': ''; ?>><?php echo $status;?></li>
<?php
}
@ -180,6 +180,9 @@ function handlePost()
$password = $_POST['password'];
$sitename = $_POST['sitename'];
$fancy = !empty($_POST['fancy']);
$server = $_SERVER['HTTP_HOST'];
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
?>
<dl class="system_notice">
<dt>Page notice</dt>
@ -219,20 +222,42 @@ function handlePost()
}
switch($dbtype) {
case 'mysql': mysql_db_installer($host, $database, $username, $password, $sitename, $fancy);
case 'mysql':
$db = mysql_db_installer($host, $database, $username, $password);
break;
case 'pgsql': pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy);
case 'pgsql':
$db = pgsql_db_installer($host, $database, $username, $password);
break;
default:
}
if ($path) $path .= '/';
updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
if (!$db) {
// database connection failed, do not move on to create config file.
return false;
}
updateStatus("Writing config file...");
$res = writeConf($sitename, $server, $path, $fancy, $db);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
/*
TODO https needs to be considered
*/
$link = "http://".$server.'/'.$path;
updateStatus("Laconica has been installed at $link");
updateStatus("You can visit your <a href='$link'>new Laconica site</a>.");
?>
<?php
}
function pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
function pgsql_db_installer($host, $database, $username, $password) {
$connstring = "dbname=$database host=$host user=$username";
//No password would mean trust authentication used.
@ -265,7 +290,7 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
@ -276,29 +301,24 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
return false;
}
}
pg_query($conn, 'COMMIT');
updateStatus("Writing config file...");
if (empty($password)) {
$sqlUrl = "pgsql://$username@$host/$database";
}
else {
$sqlUrl = "pgsql://$username:$password@$host/$database";
}
$res = writeConf($sitename, $sqlUrl, $fancy, 'pgsql');
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
$db = array('type' => 'pgsql', 'database' => $sqlUrl);
return $db;
}
function mysql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
function mysql_db_installer($host, $database, $username, $password) {
updateStatus("Starting installation...");
updateStatus("Checking database...");
@ -306,21 +326,21 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
if (!$conn) {
updateStatus("Can't connect to server '$host' as '$username'.", true);
showForm();
return;
return false;
}
updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
updateStatus("Can't change to database.", true);
showForm();
return;
return false;
}
updateStatus("Running database script...");
$res = runDbScript(INSTALLDIR.'/db/laconica.sql', $conn);
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
@ -331,35 +351,44 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
return false;
}
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
$res = writeConf($sitename, $sqlUrl, $fancy);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
}
function writeConf($sitename, $sqlUrl, $fancy, $type='mysql')
$db = array('type' => 'mysql', 'database' => $sqlUrl);
return $db;
}
function writeConf($sitename, $server, $path, $fancy, $db)
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\n".
// assemble configuration file in a string
$cfg = "<?php\n".
"if (!defined('LACONICA')) { exit(1); }\n\n".
"\$config['site']['name'] = \"$sitename\";\n\n".
// site name
"\$config['site']['name'] = '$sitename';\n\n".
// site location
"\$config['site']['server'] = '$server';\n".
"\$config['site']['path'] = '$path'; \n\n".
// checks if fancy URLs are enabled
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
"\$config['db']['database'] = \"$sqlUrl\";\n\n".
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n" .
"\$config['db']['type'] = \"$type\";\n\n" : '').
"?>");
// database
"\$config['db']['database'] = '{$db['database']}';\n\n".
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
"\$config['db']['type'] = '{$db['type']}';\n\n".
"?>";
// write configuration file out to install directory
$res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
return $res;
}
function runDbScript($filename, $conn, $type='mysql')
function runDbScript($filename, $conn, $type = 'mysql')
{
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
@ -368,10 +397,15 @@ function runDbScript($filename, $conn, $type='mysql')
if (!mb_strlen($stmt)) {
continue;
}
if ($type == 'mysql') {
switch ($type) {
case 'mysql':
$res = mysql_query($stmt, $conn);
} elseif ($type=='pgsql') {
break;
case 'pgsql':
$res = pg_query($conn, $stmt);
break;
default:
updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
}
if ($res === false) {
updateStatus("FAILED SQL: $stmt");

163
js/jcrop/jquery.Jcrop.min.js vendored Normal file
View File

@ -0,0 +1,163 @@
/**
* Jcrop v.0.9.8 (minimized)
* (c) 2008 Kelly Hallman and DeepLiquid.com
* More information: http://deepliquid.com/content/Jcrop.html
* Released under MIT License - this header must remain with code
*/
(function($){$.Jcrop=function(obj,opt)
{var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt))
{opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8')
opt.trackDocument=true;}
if(!('keySupport'in opt))
opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$('<img />').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('<div />').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('<div />').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('<div />').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function()
{var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
{var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos)
{var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset()
{return[ox,oy];};function moveOffset(offset)
{var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundy<y2+oy)oy+=boundy-(y2+oy);if(boundx<x2+ox)ox+=boundx-(x2+ox);x1+=ox;x2+=ox;y1+=oy;y2+=oy;};function getCorner(ord)
{var c=getFixed();switch(ord)
{case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}};function getFixed()
{if(!options.aspectRatio)return getRect();var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,min_y=options.minSize[1]/yscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy;if(max_x==0){max_x=boundx*10}
if(max_y==0){max_y=boundy*10}
if(real_ratio<aspect)
{yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0)
{xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}
else if(xx>boundx)
{xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}
else
{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0)
{yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}
else if(yy>boundy)
{yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x}else if(x1-xx>max_x){xx=x1-max_x;}
if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p)
{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2)
{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1)
{xa=x2;xb=x1;}
if(y2<y1)
{ya=y2;yb=y1;}
return[Math.round(xa),Math.round(ya),Math.round(xb),Math.round(yb)];};function getRect()
{var xsize=x2-x1;var ysize=y2-y1;if(xlimit&&(Math.abs(xsize)>xlimit))
x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit))
y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)<ymin))
y2=(ysize>0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)<xmin))
x2=(xsize>0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;}
if(y1<0){y2-=y1;y1-=y1;}
if(x2<0){x1-=x2;x2-=x2;}
if(y2<0){y1-=y2;y2-=y2;}
if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;}
if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;}
if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;}
if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;}
return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a)
{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function()
{var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};}
if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type)
{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi)
{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord)
{return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord)
{var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord)
{case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;}
return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li)
{for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c)
{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y)
{$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h)
{$sel.width(w).height(h);};function refresh()
{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible()
{if(awake)return update();};function update()
{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show()
{$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release()
{disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles()
{if(seehandles)
{moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles()
{seehandles=true;if(options.allowResize)
{moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles()
{seehandles=false;$hdl_holder.hide();};function animMode(v)
{(animating=v)?disableHandles():enableHandles();};function done()
{animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360})
$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function()
{var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc)
{$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
function toFront()
{$trk.css({zIndex:450});if(trackDoc)
{$(document).mousemove(trackMove).mouseup(trackUp);}}
function toBack()
{$trk.css({zIndex:290});if(trackDoc)
{$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}}
function trackMove(e)
{onMove(mouseAbs(e));};function trackUp(e)
{e.preventDefault();e.stopPropagation();if(btndown)
{btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};}
return false;};function activateHandlers(move,done)
{btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function()
{var $keymgr=$('<input type="radio" />').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
{if(options.keySupport)
{$keymgr.show();$keymgr.focus();}};function onBlur(e)
{$keymgr.hide();};function doNudge(e,x,y)
{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e)
{if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode)
{case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;}
return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj)
{var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e)
{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type)
{if(type!=lastcurs)
{Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos)
{docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move')
return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f)
{return function(pos){if(!options.aspectRatio)switch(mode)
{case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}
else switch(mode)
{case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}
Coords.setCurrent(pos);Selection.update();};};function createMover(pos)
{var lloc=pos;KeyManager.watchKeys();return function(pos)
{Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord)
{switch(ord)
{case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord)
{return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h)
{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0)
{nw=w;nh=(w/$obj.width())*$obj.height();}
if((nh>h)&&h>0)
{nh=h;nw=(h/$obj.height())*$obj.width();}
xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c)
{return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos)
{var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1])
{Selection.enableHandles();Selection.done();}
else
{Selection.release();}
Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e)
{if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos)
{Coords.setCurrent(pos);Selection.update();};function newTracker()
{var trk=$('<div></div>').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a)
{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function()
{return function()
{pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart()
{window.setTimeout(animator,interv);};animateStart();};function setSelect(rect)
{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l)
{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt)
{if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function')
options.onChange=function(){};if(typeof(options.onSelect)!=='function')
options.onSelect=function(){};};function tellSelect()
{return unscale(Coords.getFixed());};function tellScaled()
{return Coords.getFixed();};function setOptionsNew(opt)
{setOptions(opt);interfaceUpdate();};function disableCrop()
{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop()
{options.disabled=false;interfaceUpdate();};function cancelCrop()
{Selection.done();Tracker.activateHandlers(null,null);};function destroy()
{$div.remove();$origimg.show();};function interfaceUpdate(alt)
{options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);}
if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options)
{$img.attr('src',options.outerImage);delete(options.outerImage);}
Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options)
{function attachWhenDone(from)
{var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function()
{if($(this).data('Jcrop'))
{if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
else attachWhenDone(this);});return this;};})(jQuery);

File diff suppressed because one or more lines are too long

View File

@ -27,13 +27,14 @@ $(document).ready(function() {
}
}
/* rgb2hex written by R0bb13 <robertorebollo@gmail.com> */
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
hexDigits = new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
return '#' + dec2hex(rgb[1]) + dec2hex(rgb[2]) + dec2hex(rgb[3]);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
function dec2hex(x) {
hexDigits = new Array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
return isNaN(x) ? '00' : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
}
function UpdateColors(S) {

View File

@ -17,37 +17,72 @@
*/
$(document).ready(function(){
var counterBlackout = false;
// count character on keyup
function counter(event){
var maxLength = 140;
if (maxLength <= 0) {
return;
}
var currentLength = $("#notice_data-text").val().length;
var remaining = maxLength - currentLength;
var counter = $("#notice_text-count");
counter.text(remaining);
if (remaining <= 0) {
if (remaining.toString() != counter.text()) {
if (!counterBlackout || remaining == 0) {
if (counter.text() != String(remaining)) {
counter.text(remaining);
}
if (remaining < 0) {
$("#form_notice").addClass("warning");
} else {
$("#form_notice").removeClass("warning");
}
// Skip updates for the next 500ms.
// On slower hardware, updating on every keypress is unpleasant.
if (!counterBlackout) {
counterBlackout = true;
window.setTimeout(clearCounterBlackout, 500);
}
}
}
}
function clearCounterBlackout() {
// Allow keyup events to poke the counter again
counterBlackout = false;
// Check if the string changed since we last looked
counter(null);
}
function submitonreturn(event) {
if (event.keyCode == 13) {
if (event.keyCode == 13 || event.keyCode == 10) {
// iPhone sends \n not \r for 'return'
$("#form_notice").submit();
event.preventDefault();
event.stopPropagation();
$("#notice_data-text").blur();
$("body").focus();
return false;
}
return true;
}
if ($("#notice_data-text").length) {
$("#notice_data-text").bind("keyup", counter);
$("#notice_data-text").bind("keydown", submitonreturn);
// define maxLength if it wasn't defined already
if (typeof(maxLength) == "undefined") {
maxLength = 140;
}
if ($("#notice_data-text").length) {
if (maxLength > 0) {
$("#notice_data-text").bind("keyup", counter);
// run once in case there's something in there
counter();
}
$("#notice_data-text").bind("keydown", submitonreturn);
if($('body')[0].id != 'conversation') {
$("#notice_data-text").focus();
@ -57,6 +92,10 @@ $(document).ready(function(){
// XXX: refactor this code
var favoptions = { dataType: 'xml',
beforeSubmit: function(data, target, options) {
$(target).addClass('processing');
return true;
},
success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
var dis = new_form.id;
var fav = dis.replace('disfavor', 'favor');
@ -66,6 +105,10 @@ $(document).ready(function(){
};
var disoptions = { dataType: 'xml',
beforeSubmit: function(data, target, options) {
$(target).addClass('processing');
return true;
},
success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
var fav = new_form.id;
var dis = fav.replace('favor', 'disfavor');
@ -185,10 +228,12 @@ $(document).ready(function(){
}
else {
$("#notice_data-text").val("");
if (maxLength > 0) {
counter();
}
}
}
}
},
success: function(xml) { if ($("#error", xml).length > 0) {
var result = document._importNode($("p", xml).get(0), true);
@ -225,7 +270,9 @@ $(document).ready(function(){
$("#notice_data-attach").val("");
$("#notice_in-reply-to").val("");
$('#notice_data-attach_selected').remove();
if (maxLength > 0) {
counter();
}
}
$("#form_notice").removeClass("processing");
$("#notice_action-submit").removeAttr("disabled");
@ -244,7 +291,7 @@ function NoticeReply() {
$('#content .notice').each(function() {
var notice = $(this)[0];
$($('.notice_reply', notice)[0]).click(function() {
var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname');
var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
return false;
});
@ -255,11 +302,16 @@ function NoticeReply() {
function NoticeReplySet(nick,id) {
rgx_username = /^[0-9a-zA-Z\-_.]*$/;
if (nick.match(rgx_username)) {
var text = $("#notice_data-text");
if (text.length) {
replyto = "@" + nick + " ";
if ($("#notice_data-text").length) {
$("#notice_data-text").val(replyto);
text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
$("#form_notice input#notice_in-reply-to").val(id);
$("#notice_data-text").focus();
if (text.get(0).setSelectionRange) {
var len = text.val().length;
text.get(0).setSelectionRange(len,len);
text.get(0).focus();
}
return false;
}
}

View File

@ -196,21 +196,12 @@ class Action extends HTMLOutputter // lawsuit
if (Event::handle('StartShowStyles', array($this))) {
if (Event::handle('StartShowLaconicaStyles', array($this))) {
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
'media' => 'screen, projection, tv'));
$this->cssLink('css/display.css',null,'screen, projection, tv');
if (common_config('site', 'mobile')) {
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
// TODO: "handheld" CSS for other mobile devices
'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
$this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit
}
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION,
'media' => 'print'));
$this->cssLink('css/print.css','base','print');
Event::handle('EndShowLaconicaStyles', array($this));
}
@ -256,26 +247,14 @@ class Action extends HTMLOutputter // lawsuit
{
if (Event::handle('StartShowScripts', array($this))) {
if (Event::handle('StartShowJQueryScripts', array($this))) {
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.min.js')),
' ');
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.form.js')),
' ');
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.joverlay.min.js')),
' ');
$this->script('js/jquery.min.js');
$this->script('js/jquery.form.js');
$this->script('js/jquery.joverlay.min.js');
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/xbImportNode.js')),
' ');
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
' ');
$this->script('js/xbImportNode.js');
$this->script('js/util.js');
// Frame-busting code to avoid clickjacking attacks.
$this->element('script', array('type' => 'text/javascript'),
'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
@ -426,6 +405,14 @@ class Action extends HTMLOutputter // lawsuit
function showPrimaryNav()
{
$user = common_current_user();
$connect = '';
if (common_config('xmpp', 'enabled')) {
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
} else if (common_config('twitter', 'enabled')) {
$connect = 'twittersettings';
}
$this->elementStart('dl', array('id' => 'site_nav_global_primary'));
$this->element('dt', null, _('Primary site navigation'));
@ -437,12 +424,9 @@ class Action extends HTMLOutputter // lawsuit
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$this->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$this->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
} else {
$this->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
if ($connect) {
$this->menuItem(common_local_url($connect),
_('Connect'), _('Connect to services'), false, 'nav_connect');
}
if (common_config('invite', 'enabled')) {
$this->menuItem(common_local_url('invite'),
@ -455,17 +439,24 @@ class Action extends HTMLOutputter // lawsuit
_('Logout'), _('Logout from the site'), false, 'nav_logout');
}
else {
if (!common_config('site', 'openidonly')) {
if (!common_config('site', 'closed')) {
$this->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
}
$this->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
} else {
$this->menuItem(common_local_url('openidlogin'),
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
}
}
$this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
if ($user || !common_config('site', 'private')) {
$this->menuItem(common_local_url('peoplesearch'),
_('Search'), _('Search for people or text'), false, 'nav_search');
}
Event::handle('EndPrimaryNav', array($this));
}
$this->elementEnd('ul');

View File

@ -25,12 +25,14 @@ class ArrayWrapper
{
var $_items = null;
var $_count = 0;
var $N = 0;
var $_i = -1;
function __construct($items)
{
$this->_items = $items;
$this->_count = count($this->_items);
$this->N = $this->_count;
}
function fetch()

View File

@ -211,16 +211,20 @@ class MessageCommand extends Command
function execute($channel)
{
$other = User::staticGet('nickname', common_canonical_nickname($this->other));
$len = mb_strlen($this->text);
if ($len == 0) {
$channel->error($this->user, _('No content!'));
return;
} else if ($len > 140) {
$content = common_shorten_links($content);
if (mb_strlen($content) > 140) {
$channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len));
return;
}
$this->text = common_shorten_links($this->text);
if (Message::contentTooLong($this->text)) {
$channel->error($this->user, sprintf(_('Message too long - maximum is %d characters, you sent %d'),
Message::maxContent(), mb_strlen($this->text)));
return;
}
if (!$other) {

View File

@ -21,6 +21,8 @@ if (!defined('LACONICA')) { exit(1); }
define('LACONICA_VERSION', '0.9.0dev');
// XXX: move these to class variables
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
define('AVATAR_MINI_SIZE', 24);
@ -82,7 +84,7 @@ if (isset($server)) {
if (isset($path)) {
$_path = $path;
} else {
$_path = array_key_exists('SCRIPT_NAME', $_SERVER) ?
$_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
_sn_to_path($_SERVER['SCRIPT_NAME']) :
null;
}
@ -109,11 +111,14 @@ $config =
'broughtbyurl' => null,
'closed' => false,
'inviteonly' => false,
'openidonly' => false,
'private' => false,
'ssl' => 'never',
'sslserver' => null,
'shorturllength' => 30,
'dupelimit' => 60), # default for same person saying the same thing
'dupelimit' => 60, # default for same person saying the same thing
'textlimit' => 140,
),
'syslog' =>
array('appname' => 'laconica', # for syslog
'priority' => 'debug', # XXX: currently ignored
@ -137,7 +142,8 @@ $config =
array('blacklist' => array(),
'featured' => array()),
'profile' =>
array('banned' => array()),
array('banned' => array(),
'biolimit' => null),
'avatar' =>
array('server' => null,
'dir' => INSTALLDIR . '/avatar/',
@ -169,6 +175,8 @@ $config =
'host' => null, # only set if != server
'debug' => false, # print extra debug info
'public' => array()), # JIDs of users who want to receive the public stream
'openid' =>
array('enabled' => true),
'invite' =>
array('enabled' => true),
'sphinx' =>
@ -183,11 +191,20 @@ $config =
array('piddir' => '/var/run',
'user' => false,
'group' => false),
'emailpost' =>
array('enabled' => true),
'sms' =>
array('enabled' => true),
'twitter' =>
array('enabled' => true),
'twitterbridge' =>
array('enabled' => false),
'integration' =>
array('source' => 'Laconica', # source attribute for Twitter
'taguri' => $_server.',2009'), # base for tag URIs
'twitter' =>
array('consumer_key' => null,
'consumer_secret' => null),
'memcached' =>
array('enabled' => false,
'server' => 'localhost',
@ -246,7 +263,8 @@ $config =
'filecommand' => '/usr/bin/file',
),
'group' =>
array('maxaliases' => 3),
array('maxaliases' => 3,
'desclimit' => null),
'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
'search' =>
array('type' => 'fulltext'),
@ -261,6 +279,10 @@ $config =
'linkcolor' => null,
'backgroundimage' => null,
'disposition' => null),
'notice' =>
array('contentlimit' => null),
'message' =>
array('contentlimit' => null),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@ -361,25 +383,11 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
$config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini';
}
// XXX: how many of these could be auto-loaded on use?
// Ignore openidonly if OpenID is disabled
require_once 'Validate.php';
require_once 'markdown.php';
require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/theme.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/Shorturl_api.php';
require_once INSTALLDIR.'/lib/twitter.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
// XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
if (!$config['openid']['enabled']) {
$config['site']['openidonly'] = false;
}
function __autoload($cls)
{
@ -397,6 +405,32 @@ function __autoload($cls)
}
}
// XXX: how many of these could be auto-loaded on use?
// XXX: note that these files should not use config options
// at compile time since DB config options are not yet loaded.
require_once 'Validate.php';
require_once 'markdown.php';
require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/theme.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/Shorturl_api.php';
require_once INSTALLDIR.'/lib/twitter.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
// Load settings from database; note we need autoload for this
Config::loadSettings();
// XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
// Give plugins a chance to initialize in a fully-prepared environment
Event::handle('InitializePlugin');

View File

@ -99,25 +99,27 @@ class ConnectSettingsNav extends Widget
function show()
{
# action => array('prompt', 'title')
$menu =
array('imsettings' =>
$menu = array();
if (common_config('xmpp', 'enabled')) {
$menu['imsettings'] =
array(_('IM'),
_('Updates by instant messenger (IM)')),
'smssettings' =>
_('Updates by instant messenger (IM)'));
}
if (common_config('sms', 'enabled')) {
$menu['smssettings'] =
array(_('SMS'),
_('Updates by SMS')),
'twittersettings' =>
_('Updates by SMS'));
}
if (common_config('twitter', 'enabled')) {
$menu['twittersettings'] =
array(_('Twitter'),
_('Twitter integration options')));
_('Twitter integration options'));
}
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
if ($menuaction == 'imsettings' &&
!common_config('xmpp', 'enabled')) {
continue;
}
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],

View File

@ -311,13 +311,7 @@ class DesignSettingsAction extends AccountSettingsAction
function showStylesheets()
{
parent::showStylesheets();
$farbtasticStyle =
common_path('theme/base/css/farbtastic.css?version='.LACONICA_VERSION);
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => $farbtasticStyle,
'media' => 'screen, projection, tv'));
$this->cssLink('css/farbtastic.css','base','screen, projection, tv');
}
/**
@ -330,13 +324,8 @@ class DesignSettingsAction extends AccountSettingsAction
{
parent::showScripts();
$farbtasticPack = common_path('js/farbtastic/farbtastic.js');
$userDesignGo = common_path('js/userdesign.go.js');
$this->element('script', array('type' => 'text/javascript',
'src' => $farbtasticPack));
$this->element('script', array('type' => 'text/javascript',
'src' => $userDesignGo));
$this->script('js/farbtastic/farbtastic.js');
$this->script('js/farbtastic/farbtastic.go.js');
}
/**

View File

@ -35,7 +35,6 @@ if (!defined('LACONICA'))
require_once INSTALLDIR.'/lib/facebookutil.php';
require_once INSTALLDIR.'/lib/noticeform.php';
class FacebookAction extends Action
{
@ -95,34 +94,13 @@ class FacebookAction extends Action
function showStylesheets()
{
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
$theme = common_config('site', 'theme');
$ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
$ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
$this->cssLink('css/display.css', 'base');
$this->cssLink('css/facebookapp.css', 'base');
}
function showScripts()
{
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
$this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
$this->script('js/facebookapp.js');
}
/**
@ -201,7 +179,6 @@ class FacebookAction extends Action
}
// Make this into a widget later
function showLocalNav()
{
@ -261,7 +238,6 @@ class FacebookAction extends Action
$this->endHTML();
}
function showInstructions()
{
@ -277,8 +253,13 @@ class FacebookAction extends Action
$this->elementStart('dd');
$this->elementStart('p');
$this->text(sprintf($loginmsg_part1, common_config('site', 'name')));
if (!common_config('site', 'openidonly')) {
$this->element('a',
array('href' => common_local_url('register')), _('Register'));
} else {
$this->element('a',
array('href' => common_local_url('openidlogin')), _('Register'));
}
$this->text($loginmsg_part2);
$this->elementEnd('p');
$this->elementEnd('dd');
@ -287,7 +268,6 @@ class FacebookAction extends Action
$this->elementEnd('div');
}
function showLoginForm($msg = null)
{
@ -332,7 +312,6 @@ class FacebookAction extends Action
}
function updateProfileBox($notice)
{
@ -414,7 +393,6 @@ class FacebookAction extends Action
$this->xw->openURI('php://output');
}
/**
* Generate pagination links
*
@ -473,8 +451,9 @@ class FacebookAction extends Action
} else {
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$this->showPage(_('That\'s too long. Max notice size is 140 chars.'));
if (Notice::contentTooLong($content_shortened)) {
$this->showPage(sprintf(_('That\'s too long. Max notice size is %d chars.'),
Notice::maxContent()));
return;
}
}

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