git.gnu.io has moved to IP address 209.51.188.249 -- please double check where you are logging in.

Commit ff877320 authored by Evan Prodromou's avatar Evan Prodromou

Merge branch '0.8.x' into testing

Conflicts:
	actions/twitterauthorization.php
	lib/oauthclient.php
	lib/twitter.php
	lib/twitterapi.php
	lib/twitteroauthclient.php
	scripts/twitterstatusfetcher.php
parents 27aeba01 f3cdc7f2
......@@ -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
--------------
* OAuth
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:
$config['twitter']['consumer_key'] = 'YOURKEY';
$config['twitter']['consumer_secret'] = 'YOURSECRET';
When registering your application with Twitter set the type to "Browser"
and your Callback URL to:
http://example.org/mublog/twitter/authorization
The default access type should be, "Read & Write".
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.
* Importing statuses from Twitter
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.
To allow your users to import their friends' Twitter statuses, you will
need to enable the bidirectional Twitter bridge in config.php:
Sample cron job:
$config['twitterbridge']['enabled'] = true;
# Update Twitter friends subscriptions every half hour
0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
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
......@@ -1167,6 +1197,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 +1266,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
-----------
......
......@@ -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();
......
......@@ -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;
}
......
......@@ -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";
......
......@@ -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);
......
......@@ -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');
}
}
}
......@@ -67,7 +67,11 @@ class ConfirmaddressAction extends Action
parent::handle($args);
if (!common_logged_in()) {
common_set_returnto($this->selfUrl());
common_redirect(common_local_url('login'));
if (!common_config('site', 'openidonly')) {
common_redirect(common_local_url('login'));
} else {
common_redirect(common_local_url('openidlogin'));
}
return;
}
$code = $this->trimmed('code');
......
......@@ -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');
$this->elementStart('li');
$this->checkbox('emailpost',
_('I want to post notices by email.'),
$user->emailpost);
$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.'),
......
......@@ -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');
......
......@@ -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;
}
......
......@@ -30,7 +30,9 @@ class FinishopenidloginAction extends Action
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
if (!common_config('openid', 'enabled')) {
common_redirect(common_local_url('login'));
} else if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
......@@ -217,7 +219,7 @@ class FinishopenidloginAction extends Action
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;
}
......@@ -389,7 +391,7 @@ class FinishopenidloginAction extends Action
{
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
'format' => NICKNAME_FMT))) {
return false;
}
if (!User::allowed_nickname($str)) {
......
......@@ -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');
}
}
......
......@@ -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;
}
......
......@@ -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));
......
......@@ -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',
......
......@@ -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);
}
......
......@@ -65,6 +65,8 @@ class LoginAction extends Action
*
* Switches on request method; either shows the form or handles its input.
*
* Checks if only OpenID is allowed and redirects to openidlogin if so.
*
* @param array $args $_REQUEST data
*
* @return void
......@@ -73,7 +75,9 @@ class LoginAction extends Action
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
if (common_config('site', 'openidonly')) {
common_redirect(common_local_url('openidlogin'));
} else if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->checkLogin();
......@@ -247,11 +251,15 @@ 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, or ' .
'try [OpenID](%%action.openidlogin%%). ');
} else {
return _('Login with your username and password. ' .
'Don\'t have a username yet? ' .
'[Register](%%action.register%%) a new account.');
}
}
......
......@@ -91,8 +91,8 @@ class NewnoticeAction extends Action
// is losts when size is exceeded
if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
$this->clientError(sprintf(_('The server was unable to handle ' .
'that much POST data (%s bytes) due to its current configuration.'),
$_SERVER['CONTENT_LENGTH']));
'that much POST data (%s bytes) due to its current configuration.'),
$_SERVER['CONTENT_LENGTH']));
}
parent::handle($args);
......@@ -130,7 +130,7 @@ class NewnoticeAction extends Action
$hint = '';
}
$this->clientError(sprintf(
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
}
function isRespectsQuota($user) {
......@@ -190,37 +190,37 @@ class NewnoticeAction extends Action
if (isset($_FILES['attach']['error'])) {
switch ($_FILES['attach']['error']) {
case UPLOAD_ERR_NO_FILE:
// no file uploaded, nothing to do
break;
case UPLOAD_ERR_NO_FILE:
// no file uploaded, nothing to do
break;
case UPLOAD_ERR_OK:
$mimetype = $this->getUploadedFileType();
if (!$this->isRespectsQuota($user)) {
die('clientError() should trigger an exception before reaching here.');
}
break;
case UPLOAD_ERR_OK:
$mimetype = $this->getUploadedFileType();
if (!$this->isRespectsQuota($user)) {
die('clientError() should trigger an exception before reaching here.');
}
break;
case UPLOAD_ERR_INI_SIZE:
$this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
case UPLOAD_ERR_INI_SIZE:
$this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
case UPLOAD_ERR_FORM_SIZE:
$this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
case UPLOAD_ERR_FORM_SIZE:
$this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
case UPLOAD_ERR_PARTIAL:
$this->clientError(_('The uploaded file was only partially uploaded.'));
case UPLOAD_ERR_PARTIAL:
$this->clientError(_('The uploaded file was only partially uploaded.'));
case UPLOAD_ERR_NO_TMP_DIR:
$this->clientError(_('Missing a temporary folder.'));
case UPLOAD_ERR_NO_TMP_DIR:
$this->clientError(_('Missing a temporary folder.'));
case UPLOAD_ERR_CANT_WRITE:
$this->clientError(_('Failed to write file to disk.'));
case UPLOAD_ERR_CANT_WRITE:
$this->clientError(_('Failed to write file to disk.'));
case UPLOAD_ERR_EXTENSION:
$this->clientError(_('File upload stopped by extension.'));
case UPLOAD_ERR_EXTENSION:
$this->clientError(_('File upload stopped by extension.'));
default:
die('Should never reach here.');
default:
die('Should never reach here.');
}
}
......@@ -233,7 +233,7 @@ class NewnoticeAction extends Action
$fileRecord = $this->storeFile($filename, $mimetype);
$fileurl = common_local_url('attachment',
array('attachment' => $fileRecord->id));
array('attachment' => $fileRecord->id));
// not sure this is necessary -- Zach
$this->maybeAddRedir($fileRecord->id, $fileurl);
......@@ -367,7 +367,7 @@ class NewnoticeAction extends Action
File_to_post::processNew($filerec->id, $notice->id);
$this->maybeAddRedir($filerec->id,
common_local_url('file', array('notice' => $notice->id)));
common_local_url('file', array('notice' => $notice->id)));
}
/**
......
......@@ -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');
......
......@@ -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;
}
......
......@@ -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');
......@@ -151,12 +144,11 @@ class TwitapioembedAction extends TwitterapiAction
if($oembed['thumbnail_url']) $this->element('thumbnail_url',null,$oembed['thumbnail_url']);
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;