summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README119
-rw-r--r--actions/api.php63
-rw-r--r--actions/conversation.php5
-rw-r--r--actions/favorited.php2
-rw-r--r--actions/groupdesignsettings.php32
-rw-r--r--actions/othersettings.php11
-rw-r--r--actions/peoplesearch.php44
-rw-r--r--actions/subscriptions.php5
-rw-r--r--actions/twitapifriendships.php83
-rw-r--r--actions/twitapistatuses.php15
-rw-r--r--actions/twitapiusers.php13
-rw-r--r--classes/Fave.php2
-rw-r--r--classes/Notice.php5
-rw-r--r--classes/Queue_item.php11
-rw-r--r--classes/User.php2
-rw-r--r--config.php.sample3
-rw-r--r--db/laconica_pg.sql26
-rw-r--r--doc-src/tos300
-rw-r--r--js/util.js8
-rw-r--r--lib/action.php7
-rw-r--r--lib/common.php3
-rw-r--r--lib/currentuserdesignaction.php12
-rw-r--r--lib/dbqueuemanager.php166
-rw-r--r--lib/facebookutil.php81
-rw-r--r--lib/groupdesignaction.php16
-rw-r--r--lib/jabber.php10
-rw-r--r--lib/mail.php72
-rw-r--r--lib/ownerdesignaction.php12
-rw-r--r--lib/peoplesearchresults.php80
-rw-r--r--lib/ping.php2
-rw-r--r--lib/popularnoticesection.php2
-rw-r--r--lib/profilelist.php9
-rw-r--r--lib/queuehandler.php133
-rw-r--r--lib/queuemanager.php74
-rw-r--r--lib/router.php4
-rw-r--r--lib/stompqueuemanager.php174
-rw-r--r--lib/twitter.php99
-rw-r--r--lib/twitterapi.php61
-rw-r--r--lib/unqueuemanager.php85
-rw-r--r--lib/util.php164
-rw-r--r--lib/xmppqueuehandler.php39
-rw-r--r--plugins/FBConnect/FBConnectPlugin.php2
-rwxr-xr-xscripts/update_translations.php145
-rwxr-xr-xscripts/xmppdaemon.php37
-rw-r--r--theme/base/css/display.css26
-rw-r--r--theme/base/css/ie.css10
-rw-r--r--theme/default/css/display.css6
-rw-r--r--theme/identica/css/display.css6
48 files changed, 1698 insertions, 588 deletions
diff --git a/README b/README
index 0f1b5a43b..97432e566 100644
--- a/README
+++ b/README
@@ -2,8 +2,8 @@
README
------
-Laconica 0.7.4 ("Can't Get There From Here")
-29 May 2009
+Laconica 0.8.0 ("Shiny Happy People")
+8 July 2009
This is the README file for Laconica, the Open Source microblogging
platform. It includes installation instructions, descriptions of
@@ -71,29 +71,52 @@ for additional terms.
New this version
================
-This is a minor bug-fix and feature release since version 0.7.3,
-released Apr 4 2009. Notable changes this version:
-
-- Improved handling of UTF-8 characters. The new code is *not* backwards
- compatible by default; see "Upgrading" below for instructions on
- converting existing databases to the correct character set.
-- Unroll joins for large queries. This greatly enhanced database
- performance -- up to 50x for some queries -- at the expense of making
- an extra DB hit for some queries.
-- Added an optional plugin to use WikiHashtags
- (http://hashtags.wikia.com/) for the sidebar on hashtag pages.
-- Optimized Twitter friend synchronization.
-- Better error handling for Ajax posting of notices, including
- HTTP errors and timeouts.
-- Experimental Comet plugin -- supports the cometd and the Bayeux
- protocol. Using this plugin, you can show "real time" updates on the
- public and tag pages. However, server configuration is complex.
-- If queues are enabled, update inboxes and memcached off-line. Speeds
- up posting considerably.
-- Correctly shorten links posted through XMPP.
-- <link> elements for pagination, supported by some browsers like Opera.
-- Corrected date format in search API.
-- Made the public XRDS file work correctly.
+This is a major feature release since version 0.7.4, released May 31
+2009. Notable changes this version:
+
+- Support for a hosted service (status network). Multiple sites can
+ share the same codebase but use different databases.
+- OEmbed. Links to pages that support OEmbed (http://www.oembed.com/)
+ become popup links, and the media are shown in a special lightbox.
+- File attachments. Users can attach files of the size and type approved
+ by an administrator, and a shortened link will be included in the
+ notice.
+- Related notices are organized into conversations, with each reply a
+ branch in a tree. Conversations have pages and are linked to from each
+ notice in the conversation.
+- User designs. Users can specify colours and backgrounds
+ for their profile pages and other "personal" pages.
+- Group designs. Group administrators can specify similar designs for
+ group profiles and related pages.
+- Site designs. Site authors can specify a design (background and
+ colors) for the site.
+- New themes. Five new themes are added to the base release; these show
+ off the flexibility of Laconica's theming system.
+- Statistics. Public sites will periodically send usage statistics,
+ configuration options, and dependency information to Laconica dev site.
+ This will help us understand how the software is used and plan future
+ versions of the software.
+- Additional hooks. The hooks and plugins system introduced in 0.7.x was
+ expanded with additional points of access.
+- Facebook Connect. A new plugin allows logging in with Facebook Connect
+ (http://developers.facebook.com/connect.php).
+- A session handler. A new optional session handler class to manage PHP
+ sessions reliably and quickly for large sites.
+- STOMP queuing. Queue management for offline daemons has been
+ abstracted with three concrete instances. A new interface that should
+ work with STOMP servers like ActiveMQ and RabbitMQ is available, which
+ should make things scale better.
+- Group block. Group admins can block users from joining or posting to
+ a group.
+- Group aliases. Groups can be referred to with aliases, additional
+ names. For example, "!yul" and "!montreal" can be the same group.
+- Bidirectional Twitter bridge. Users can read the tweets their Twitter
+ friends post on Twitter.
+- Adaptation of WordPress.com Terms of Service (http://en.wordpress.com/tos/)
+ as default TOS for Laconica sites.
+- Better command-line handling for scripts, including standard options
+ and ability to set hostname and path from the command line.
+- Many, many bug fixes.
Prerequisites
=============
@@ -198,9 +221,9 @@ especially if you've previously installed PHP/MySQL packages.
1. Unpack the tarball you downloaded on your Web server. Usually a
command like this will work:
- tar zxf laconica-0.7.4.tar.gz
+ tar zxf laconica-0.8.0.tar.gz
- ...which will make a laconica-0.7.4 subdirectory in your current
+ ...which will make a laconica-0.8.0 subdirectory in your current
directory. (If you don't have shell access on your Web server, you
may have to unpack the tarball on your local computer and FTP the
files to the server.)
@@ -208,7 +231,7 @@ especially if you've previously installed PHP/MySQL packages.
2. Move the tarball to a directory of your choosing in your Web root
directory. Usually something like this will work:
- mv laconica-0.7.4 /var/www/mublog
+ mv laconica-0.8.0 /var/www/mublog
This will make your Laconica instance available in the mublog path of
your server, like "http://example.net/mublog". "microblog" or
@@ -702,11 +725,11 @@ However, older installations will have the incorrect storage, and will
consequently show up "wrong" in browsers. See below for how to deal
with this situation.
-If you've been using Laconica 0.6, 0.5 or lower, or if you've been
-tracking the "git" version of the software, you will probably want
-to upgrade and keep your existing data. There is no automated upgrade
-procedure in Laconica 0.7.4. Try these step-by-step instructions; read
-to the end first before trying them.
+If you've been using Laconica 0.7, 0.6, 0.5 or lower, or if you've
+been tracking the "git" version of the software, you will probably
+want to upgrade and keep your existing data. There is no automated
+upgrade procedure in Laconica 0.8.0. Try these step-by-step
+instructions; read to the end first before trying them.
0. Download Laconica and set up all the prerequisites as if you were
doing a new install.
@@ -726,20 +749,31 @@ to the end first before trying them.
5. Once all writing processes to your site are turned off, make a
final backup of the Web directory and database.
6. Move your Laconica directory to a backup spot, like "mublog.bak".
-7. Unpack your Laconica 0.6 tarball and move it to "mublog" or
+7. Unpack your Laconica 0.8.0 tarball and move it to "mublog" or
wherever your code used to be.
8. Copy the config.php file and avatar directory from your old
directory to your new directory.
9. Copy htaccess.sample to .htaccess in the new directory. Change the
RewriteBase to use the correct path.
-10. Rebuild the database. For MySQL, go to your Laconica directory and
- run the rebuilddb.sh script like this:
+10. Rebuild the database. NOTE: this step is destructive and cannot be
+ reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't
+ do it without a known-good backup!
+
+ If your database is at version 0.7.4, you can run a special upgrade
+ script:
+
+ mysql -u<rootuser> -p<rootpassword> <database> db/074to080.sql
+
+ Otherwise, go to your Laconica directory and AFTER YOU MAKE A
+ BACKUP run the rebuilddb.sh script like this:
./scripts/rebuilddb.sh rootuser rootpassword database db/laconica.sql
Here, rootuser and rootpassword are the username and password for a
user who can drop and create databases as well as tables; typically
- that's _not_ the user Laconica runs as.
+ that's _not_ the user Laconica runs as. Note that rebuilddb.sh drops
+ your database and rebuilds it; if there is an error you have no
+ database. Make sure you have a backup.
For PostgreSQL databases there is an equivalent, rebuilddb_psql.sh,
which operates slightly differently. Read the documentation in that
script before running it.
@@ -791,6 +825,9 @@ problem.
3. When fixup_inboxes is finished, you can set the enabled flag to
'true'.
+NOTE: we will drop support for non-inboxed sites in the 0.9.x version
+of Laconica. It's time to switch now!
+
UTF-8 Database
--------------
@@ -817,7 +854,7 @@ what to do.
Configuration options
=====================
-The sole configuration file for Laconica (excepting configurations for
+The main configuration file for Laconica (excepting configurations for
dependency software) is config.php in your Laconica directory. If you
edit any other file in the directory, like lib/common.php (where most
of the defaults are defined), you will lose your configuration options
@@ -1396,7 +1433,7 @@ if anyone's been overlooked in error.
* Ori Avtalion
* Meitar Moscovitz
* Ken Sheppardson (Trac server, man-about-town)
-* Tiago 'gouki' Faria (i18n managerx)
+* Tiago 'gouki' Faria (i18n manager)
* Sean Murphy
* Leslie Michael Orchard
* Eric Helgeson
@@ -1405,6 +1442,10 @@ if anyone's been overlooked in error.
* Tobias Diekershoff
* Dan Moore
* Fil
+* Jeff Mitchell
+* Brenda Wallace
+* Jeffery To
+* Federico Marani
Thanks also to the developers of our upstream library code and to the
thousands of people who have tried out Identi.ca, installed Laconi.ca,
diff --git a/actions/api.php b/actions/api.php
index 08f5fadad..18c3b68d4 100644
--- a/actions/api.php
+++ b/actions/api.php
@@ -75,14 +75,14 @@ class ApiAction extends Action
}
} else {
- # Caller might give us a username even if not required
- if (isset($_SERVER['PHP_AUTH_USER'])) {
- $user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
- if ($user) {
- $this->user = $user;
- }
- # Twitter doesn't throw an error if the user isn't found
- }
+ // Caller might give us a username even if not required
+ if (isset($_SERVER['PHP_AUTH_USER'])) {
+ $user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
+ if ($user) {
+ $this->user = $user;
+ }
+ # Twitter doesn't throw an error if the user isn't found
+ }
$this->process_command();
}
@@ -117,7 +117,7 @@ class ApiAction extends Action
}
}
- # Whitelist of API methods that don't need authentication
+ // Whitelist of API methods that don't need authentication
function requires_auth()
{
static $noauth = array( 'statuses/public_timeline',
@@ -135,28 +135,61 @@ class ApiAction extends Action
'statuses/replies',
'statuses/mentions',
'statuses/followers',
- 'favorites/favorites');
+ 'favorites/favorites',
+ 'friendships/show');
$fullname = "$this->api_action/$this->api_method";
// If the site is "private", all API methods except laconica/config
// need authentication
+
if (common_config('site', 'private')) {
return $fullname != 'laconica/config' || false;
}
+ // bareauth: only needs auth if without an argument or query param specifying user
+
if (in_array($fullname, $bareauth)) {
- # bareauth: only needs auth if without an argument or query param specifying user
- if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) {
+
+ // Special case: friendships/show only needs auth if source_id or
+ // source_screen_name is not specified as a param
+
+ if ($fullname == 'friendships/show') {
+
+ $source_id = $this->arg('source_id');
+ $source_screen_name = $this->arg('source_screen_name');
+
+ if (empty($source_id) && empty($source_screen_name)) {
+ return true;
+ }
+
return false;
- } else {
+ }
+
+ // if all of these are empty, auth is required
+
+ $id = $this->arg('id');
+ $user_id = $this->arg('user_id');
+ $screen_name = $this->arg('screen_name');
+
+ if (empty($this->api_arg) &&
+ empty($id) &&
+ empty($user_id) &&
+ empty($screen_name)) {
return true;
+ } else {
+ return false;
}
+
} else if (in_array($fullname, $noauth)) {
- # noauth: never needs auth
+
+ // noauth: never needs auth
+
return false;
} else {
- # everybody else needs auth
+
+ // everybody else needs auth
+
return true;
}
}
diff --git a/actions/conversation.php b/actions/conversation.php
index cd6f26329..0eb0d86d6 100644
--- a/actions/conversation.php
+++ b/actions/conversation.php
@@ -31,6 +31,9 @@ if (!defined('LACONICA')) {
exit(1);
}
+// XXX: not sure how to do paging yet,
+// so set a 60-notice limit
+
require_once INSTALLDIR.'/lib/noticelist.php';
/**
@@ -107,7 +110,7 @@ class ConversationAction extends Action
function showContent()
{
- $notices = Notice::conversationStream($this->id, 0, null);
+ $notices = Notice::conversationStream($this->id, null, null);
$ct = new ConversationTree($notices, $this);
diff --git a/actions/favorited.php b/actions/favorited.php
index c902d80f5..156c7a700 100644
--- a/actions/favorited.php
+++ b/actions/favorited.php
@@ -194,7 +194,7 @@ class FavoritedAction extends Action
$qry = 'SELECT notice.*, '.
$weightexpr . ' as weight ' .
'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
- 'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source ' .
+ 'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source,notice.conversation ' .
'ORDER BY weight DESC';
$offset = ($this->page - 1) * NOTICES_PER_PAGE;
diff --git a/actions/groupdesignsettings.php b/actions/groupdesignsettings.php
index 6c1c052cb..bb01243c6 100644
--- a/actions/groupdesignsettings.php
+++ b/actions/groupdesignsettings.php
@@ -312,36 +312,4 @@ class GroupDesignSettingsAction extends DesignSettingsAction
$this->showForm(_('Design preferences saved.'), true);
}
- /**
- * Handle input and output a page (overrided)
- *
- * @param array $args $_REQUEST arguments
- *
- * @return void
- */
-
- function handle($args)
- {
- parent::handle($args);
- if (!common_logged_in()) {
- $this->clientError(_('Not logged in.'));
- return;
- } else if (!common_is_real_login()) {
- // Cookie theft means that automatic logins can't
- // change important settings or see private info, and
- // _all_ our settings are important
- common_set_returnto($this->selfUrl());
- $user = common_current_user();
- if ($user->hasOpenID()) {
- common_redirect(common_local_url('openidlogin'), 303);
- } else {
- common_redirect(common_local_url('login'), 303);
- }
- } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- $this->handlePost();
- } else {
- $this->showForm();
- }
- }
-
}
diff --git a/actions/othersettings.php b/actions/othersettings.php
index b542233ca..1277f8052 100644
--- a/actions/othersettings.php
+++ b/actions/othersettings.php
@@ -83,14 +83,12 @@ class OthersettingsAction extends AccountSettingsAction
{
$user = common_current_user();
-
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_other',
'class' => 'form_settings',
'action' =>
common_local_url('othersettings')));
$this->elementStart('fieldset');
- $this->element('legend', null, _('URL Auto-shortening'));
$this->hidden('token', common_session_token());
// I18N
@@ -109,10 +107,14 @@ class OthersettingsAction extends AccountSettingsAction
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
- $this->dropdown('urlshorteningservice', _('Service'),
+ $this->dropdown('urlshorteningservice', _('Shorten URLs with'),
$services, _('Automatic shortening service to use.'),
false, $user->urlshorteningservice);
$this->elementEnd('li');
+ $this->elementStart('li');
+ $this->checkbox('viewdesigns', _('View profile designs'),
+ $user->viewdesigns, _('Show or hide profile designs.'));
+ $this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('save', _('Save'));
$this->elementEnd('fieldset');
@@ -145,6 +147,8 @@ class OthersettingsAction extends AccountSettingsAction
return;
}
+ $viewdesigns = $this->boolean('viewdesigns');
+
$user = common_current_user();
assert(!is_null($user)); // should already be checked
@@ -154,6 +158,7 @@ class OthersettingsAction extends AccountSettingsAction
$original = clone($user);
$user->urlshorteningservice = $urlshorteningservice;
+ $user->viewdesigns = $viewdesigns;
$result = $user->update($original);
diff --git a/actions/peoplesearch.php b/actions/peoplesearch.php
index c61e0e273..60ddb6a82 100644
--- a/actions/peoplesearch.php
+++ b/actions/peoplesearch.php
@@ -87,3 +87,47 @@ class PeoplesearchAction extends SearchAction
}
}
+/**
+ * People search results class
+ *
+ * Derivative of ProfileList with specialization for highlighting search terms.
+ *
+ * @category Widget
+ * @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/
+ *
+ * @see PeoplesearchAction
+ */
+
+class PeopleSearchResults extends ProfileList
+{
+ var $terms = null;
+ var $pattern = null;
+
+ function __construct($profile, $terms, $action)
+ {
+ parent::__construct($profile, $action);
+
+ $this->terms = array_map('preg_quote',
+ array_map('htmlspecialchars', $terms));
+
+ $this->pattern = '/('.implode('|',$terms).')/i';
+ }
+
+ function newProfileItem($profile)
+ {
+ return new PeopleSearchResultItem($profile, $this->action);
+ }
+}
+
+class PeopleSearchResultItem extends ProfileListItem
+{
+ function highlight($text)
+ {
+ return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
+ }
+}
+
diff --git a/actions/subscriptions.php b/actions/subscriptions.php
index 4124abea4..42bdae10f 100644
--- a/actions/subscriptions.php
+++ b/actions/subscriptions.php
@@ -159,7 +159,10 @@ class SubscriptionsListItem extends SubscriptionListItem
$this->showBio();
$this->showTags();
// Relevant portion!
- $this->showOwnerControls();
+ $cur = common_current_user();
+ if (!empty($cur) && $cur->id == $this->owner->id) {
+ $this->showOwnerControls();
+ }
$this->endProfile();
}
diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php
index 29eb4cc0f..5fb55e9ff 100644
--- a/actions/twitapifriendships.php
+++ b/actions/twitapifriendships.php
@@ -160,4 +160,85 @@ class TwitapifriendshipsAction extends TwitterapiAction
}
-} \ No newline at end of file
+ function show($args, $apidata)
+ {
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ $this->clientError(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ $source_id = (int)$this->trimmed('source_id');
+ $source_screen_name = $this->trimmed('source_screen_name');
+
+ // If the source is not specified for an unauthenticated request,
+ // the method will return an HTTP 403.
+
+ if (empty($source_id) && empty($source_screen_name)) {
+ if (empty($apidata['user'])) {
+ $this->clientError(_('Could not determine source user.'),
+ $code = 403);
+ return;
+ }
+ }
+
+ $source = null;
+
+ if (!empty($source_id)) {
+ $source = User::staticGet($source_id);
+ } elseif (!empty($source_screen_name)) {
+ $source = User::staticGet('nickname', $source_screen_name);
+ } else {
+ $source = $apidata['user'];
+ }
+
+ // If a source or target is specified but does not exist,
+ // the method will return an HTTP 404.
+
+ if (empty($source)) {
+ $this->clientError(_('Could not determine source user.'),
+ $code = 404);
+ return;
+ }
+
+ $target_id = (int)$this->trimmed('target_id');
+ $target_screen_name = $this->trimmed('target_screen_name');
+
+ $target = null;
+
+ if (!empty($target_id)) {
+ $target = User::staticGet($target_id);
+ } elseif (!empty($target_screen_name)) {
+ $target = User::staticGet('nickname', $target_screen_name);
+ } else {
+ $this->clientError(_('Target user not specified.'),
+ $code = 403);
+ return;
+ }
+
+ if (empty($target)) {
+ $this->clientError(_('Could not find target user.'),
+ $code = 404);
+ return;
+ }
+
+ $result = $this->twitter_relationship_array($source, $target);
+
+ switch ($apidata['content-type']) {
+ case 'xml':
+ $this->init_document('xml');
+ $this->show_twitter_xml_relationship($result[relationship]);
+ $this->end_document('xml');
+ break;
+ case 'json':
+ $this->init_document('json');
+ print json_encode($result);
+ $this->end_document('json');
+ break;
+ default:
+ break;
+ }
+ }
+
+}
diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php
index 555c746cb..c9943698d 100644
--- a/actions/twitapistatuses.php
+++ b/actions/twitapistatuses.php
@@ -373,9 +373,19 @@ class TwitapistatusesAction extends TwitterapiAction
return;
}
+ // 'id' is an undocumented parameter in Twitter's API. Several
+ // clients make use of it, so we support it too.
+
+ // show.json?id=12345 takes precedence over /show/12345.json
+
$this->auth_user = $apidata['user'];
- $notice_id = $apidata['api_arg'];
- $notice = Notice::staticGet($notice_id);
+ $notice_id = $this->trimmed('id');
+
+ if (empty($notice_id)) {
+ $notice_id = $apidata['api_arg'];
+ }
+
+ $notice = Notice::staticGet((int)$notice_id);
if ($notice) {
if ($apidata['content-type'] == 'xml') {
@@ -389,7 +399,6 @@ class TwitapistatusesAction extends TwitterapiAction
$this->clientError(_('No status with that ID found.'),
404, $apidata['content-type']);
}
-
}
function destroy($args, $apidata)
diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php
index 4057b63e7..de8326e3a 100644
--- a/actions/twitapiusers.php
+++ b/actions/twitapiusers.php
@@ -37,24 +37,17 @@ class TwitapiusersAction extends TwitterapiAction
$user = null;
$email = $this->arg('email');
- $user_id = $this->arg('user_id');
// XXX: email field deprecated in Twitter's API
- // XXX: Also: need to add screen_name param
-
if ($email) {
$user = User::staticGet('email', $email);
- } elseif ($user_id) {
- $user = $this->get_user($user_id);
- } elseif (isset($apidata['api_arg'])) {
- $user = $this->get_user($apidata['api_arg']);
- } elseif (isset($apidata['user'])) {
- $user = $apidata['user'];
+ } else {
+ $user = $this->get_user($apidata['api_arg'], $apidata);
}
if (empty($user)) {
- $this->client_error(_('Not found.'), 404, $apidata['content-type']);
+ $this->clientError(_('Not found.'), 404, $apidata['content-type']);
return;
}
diff --git a/classes/Fave.php b/classes/Fave.php
index f4cf6256f..c3ec62dcf 100644
--- a/classes/Fave.php
+++ b/classes/Fave.php
@@ -42,7 +42,7 @@ class Fave extends Memcached_DataObject
$ids = Notice::stream(array('Fave', '_streamDirect'),
array($user_id, $own),
($own) ? 'fave:ids_by_user_own:'.$user_id :
- 'fave:by_user:'.$user_id,
+ 'fave:ids_by_user:'.$user_id,
$offset, $limit);
return $ids;
}
diff --git a/classes/Notice.php b/classes/Notice.php
index 2ba2f31b1..5ec0692d9 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -1187,6 +1187,7 @@ class Notice extends Memcached_DataObject
if (empty($cache) ||
$since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
+ is_null($limit) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
$max_id, $since)));
@@ -1209,7 +1210,7 @@ class Notice extends Memcached_DataObject
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
- $last_id, 0, null, $tag)));
+ $last_id, 0, null)));
$new_window = array_merge($new_ids, $window);
@@ -1224,7 +1225,7 @@ class Notice extends Memcached_DataObject
}
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
- 0, 0, null, $tag)));
+ 0, 0, null)));
$windowstr = implode(',', $window);
diff --git a/classes/Queue_item.php b/classes/Queue_item.php
index 9b909ec22..295c321b5 100644
--- a/classes/Queue_item.php
+++ b/classes/Queue_item.php
@@ -4,7 +4,7 @@
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-class Queue_item extends Memcached_DataObject
+class Queue_item extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
@@ -13,7 +13,7 @@ class Queue_item extends Memcached_DataObject
public $notice_id; // int(4) primary_key not_null
public $transport; // varchar(8) primary_key not_null
public $created; // datetime() not_null
- public $claimed; // datetime()
+ public $claimed; // datetime()
/* Static get */
function staticGet($k,$v=null)
@@ -24,7 +24,7 @@ class Queue_item extends Memcached_DataObject
function sequenceKey()
{ return array(false, false); }
-
+
static function top($transport) {
$qi = new Queue_item();
@@ -54,4 +54,9 @@ class Queue_item extends Memcached_DataObject
$qi = null;
return null;
}
+
+ function &pkeyGet($kv)
+ {
+ return Memcached_DataObject::pkeyGet('Queue_item', $kv);
+ }
}
diff --git a/classes/User.php b/classes/User.php
index 62a3f8a66..04b38a0d2 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -491,6 +491,8 @@ class User extends Memcached_DataObject
// ;last cache, too
$cache->delete(common_cache_key('fave:ids_by_user:'.$this->id));
$cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last'));
+ $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
+ $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
}
}
diff --git a/config.php.sample b/config.php.sample
index a23b41b31..57aa6a6c8 100644
--- a/config.php.sample
+++ b/config.php.sample
@@ -36,6 +36,9 @@ $config['site']['path'] = 'laconica';
// If you want logging sent to a file instead of syslog
// $config['site']['logfile'] = '/tmp/laconica.log';
+// Change the syslog facility that Laconica logs to (default is LOG_USER)
+// $config['syslog']['facility'] = LOG_LOCAL7;
+
// Enables extra log information, for example full details of PEAR DB errors
// $config['site']['logdebug'] = true;
diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql
index b213bbd50..dae8b8faf 100644
--- a/db/laconica_pg.sql
+++ b/db/laconica_pg.sql
@@ -116,7 +116,9 @@ create table notice (
modified timestamp /* comment 'date this record was modified' */,
reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) ,
is_local integer default 0 /* comment 'notice was generated by a user' */,
- source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
+ source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */,
+ conversation integer /*id of root notice in this conversation' */ references notice (id)
+
/* FULLTEXT(content) */
);
@@ -172,7 +174,7 @@ create table token (
tok char(32) not null /* comment 'identifying value' */,
secret char(32) not null /* comment 'secret value' */,
type integer not null default 0 /* comment 'request or access' */,
- state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */,
+ state integer default 0 /* comment 'for requests 0 = initial, 1 = authorized, 2 = used' */,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */,
@@ -346,7 +348,7 @@ create table notice_inbox (
user_id integer not null /* comment 'user receiving the message' */ references "user" (id),
notice_id integer not null /* comment 'notice received' */ references notice (id),
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */,
- source integer default 1 /* comment 'reason it is in the inbox; 1=subscription' */,
+ source integer default 1 /* comment 'reason it is in the inbox: 1=subscription' */,
primary key (user_id, notice_id)
);
@@ -436,8 +438,8 @@ create table file (
mimetype varchar(50),
size integer,
title varchar(255),
- date integer(11),
- protected integer(1)
+ date integer,
+ protected integer
);
create sequence file_oembed_seq;
@@ -454,7 +456,7 @@ create table file_oembed (
title varchar(255),
author_name varchar(50),
author_url varchar(255),
- url varchar(255),
+ url varchar(255)
);
create sequence file_redirection_seq;
@@ -484,6 +486,18 @@ create table file_to_post (
unique(file_id, post_id)
);
+create sequence design_seq;
+create table design (
+ id bigint default nextval('design_seq') /* comment 'design ID'*/,
+ backgroundcolor integer /* comment 'main background color'*/ ,
+ contentcolor integer /*comment 'content area background color'*/ ,
+ sidebarcolor integer /*comment 'sidebar background color'*/ ,
+ textcolor integer /*comment 'text color'*/ ,
+ linkcolor integer /*comment 'link color'*/,
+ backgroundimage varchar(255) /*comment 'background image, if any'*/,
+ disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/,
+ primary key (id)
+);
/* Textsearch stuff */
diff --git a/doc-src/tos b/doc-src/tos
new file mode 100644
index 000000000..bcfc31981
--- /dev/null
+++ b/doc-src/tos
@@ -0,0 +1,300 @@
+The gist
+--------
+
+We (the folks at [%%site.broughtby%%](%%site.broughtbyurl%%)) run a
+service called %%site.name%% and would love for you to use it. Our
+service is designed to give you as much control and ownership over
+what goes in your notice stream as possible and encourage you to
+express yourself freely. However, be responsible in what you post. In
+particular, make sure that none of the prohibited items listed below
+appear in your notice stream or get linked to from your notice stream (things
+like spam, viruses, or hate content).
+
+You can review our [Public Stream](%%action.public%%) to get a sense
+of the types of notices that are welcome on our service (or not!). If
+you find a %%site.name%% account that you believe violates our terms
+of service, please check our [Contact](%%doc.contact%%) documentation.
+
+(Note: Automattic, Inc., original creators of the below Terms of
+Service, decided to make them available under a Creative Commons
+Sharealike license, which means you’re more than welcome to steal it
+and repurpose it for your own use. Just make sure to replace
+references to us with ones to you. They’d appreciate a link to
+[WordPress.com](http://www.wordpress.com/) somewhere on your site.
+They spent a lot of money and time on the below, and other people
+shouldn’t need to do the same. (We didn't!))
+
+Terms of Service
+----------------
+
+The following terms and conditions govern all use of the %%site.name%%
+website and all content, services and products available at or through
+the website (taken together, the Website). The Website is owned and
+operated by %%site.broughtby%% (“Operator”). The Website is offered
+subject to your acceptance without modification of all of the terms
+and conditions contained herein and all other operating rules,
+policies (including, without limitation, Operator’s [Privacy
+Policy](%%doc.privacy%%))
+and procedures that may be published from time to time on this Site by
+Operator (collectively, the “Agreement”).
+
+Please read this Agreement carefully before accessing or using the
+Website. By accessing or using any part of the web site, you agree to
+become bound by the terms and conditions of this agreement. If you do
+not agree to all the terms and conditions of this agreement, then you
+may not access the Website or use any services. If these terms and
+conditions are considered an offer by Operator, acceptance is
+expressly limited to these terms. The Website is available only to
+individuals who are at least 13 years old.
+
+<ol>
+
+<li><strong>Your %%site.name%% Account and Site.</strong> If you
+create a notice stream on the Website, you are responsible for
+maintaining the security of your account and notice stream, and you
+are fully responsible for all activities that occur under the account
+and any other actions taken in connection with the notice stream. You
+must not describe or assign keywords to your notice stream in a
+misleading or unlawful manner, including in a manner intended to trade
+on the name or reputation of others, and Operator may change or remove
+any description or keyword that it considers inappropriate or
+unlawful, or otherwise likely to cause Operator liability. You must
+immediately notify Operator of any unauthorized uses of your notice
+stream, your account or any other breaches of security. Operator will
+not be liable for any acts or omissions by You, including any damages
+of any kind incurred as a result of such acts or omissions.</li>
+
+<li><strong>Responsibility of Contributors.</strong> If you operate a
+notice stream, comment on a notice stream, post material to the
+Website, post links on the Website, or otherwise make (or allow any
+third party to make) material available by means of the Website (any
+such material, “Content”), You are entirely responsible for the
+content of, and any harm resulting from, that Content. That is the
+case regardless of whether the Content in question constitutes text,
+graphics, an audio file, or computer software. By making Content
+available, you represent and warrant that:
+
+<ul>
+
+<li>the downloading, copying and use of the Content will not infringe
+the proprietary rights, including but not limited to the copyright,
+patent, trademark or trade secret rights, of any third party;</li>
+
+<li>if your employer has rights to intellectual property you create,
+you have either (i) received permission from your employer to post or
+make available the Content, including but not limited to any software,
+or (ii) secured from your employer a waiver as to all rights in or to
+the Content;</li>
+
+<li>you have fully complied with any third-party licenses
+relating to the Content, and have done all things necessary to
+successfully pass through to end users any required terms;</li>
+
+<li>the Content does not contain or install any viruses, worms, malware,
+Trojan horses or other harmful or destructive content;</li>
+
+<li>the Content is not spam, and does not contain unethical or
+unwanted commercial content designed to drive traffic to third party
+sites or boost the search engine rankings of third party sites, or to
+further unlawful acts (such as phishing) or mislead recipients as to
+the source of the material (such as spoofing);</li>
+
+<li>if the Content is machine- or randomly-generated, it is for
+purposes of direct entertainment, information and/or utility for you
+or other users, and not for spam,</li>
+
+<li>the Content is not libelous or defamatory (more info on
+what that means), does not contain threats or incite violence towards
+individuals or entities, and does not violate the privacy or publicity
+rights of any third party;</li>
+
+<li>your notice stream is not getting advertised via unwanted electronic
+messages such as spam links on newsgroups, email lists, other notice streams
+and web sites, and similar unsolicited promotional methods;</li>
+
+<li>your notice stream is not named in a manner that misleads your
+readers into thinking that you are another person or company. For
+example, your notice stream’s URL or name is not the name of a person other
+than yourself or company other than your own; and</li>
+
+<li>you have, in the case of Content that includes computer code,
+accurately categorized and/or described the type, nature, uses and
+effects of the materials, whether requested to do so by Operator or
+otherwise.</li>
+
+</ul>
+
+<p>By submitting Content to Operator for inclusion on your Website, you
+grant Operator a world-wide, royalty-free, and non-exclusive license
+to reproduce, modify, adapt and publish the Content solely for the
+purpose of displaying, distributing and promoting your notice
+stream.</p>
+
+<p>By submitting Content to Operator for inclusion on your Website,
+you grant all readers the right to use, re-use, modify and/or
+re-distribute the Content under the terms of the <a
+href="%%license.url%%">%%license.title%%</a>.</p>
+
+<p>If you delete Content, Operator will use reasonable efforts to remove it from
+the Website, but you acknowledge that caching or references to the
+Content may not be made immediately unavailable.</p>
+
+<p>Without limiting any of those representations or warranties, Operator
+has the right (though not the obligation) to, in Operator’s sole
+discretion (i) refuse or remove any content that, in Operator’s
+reasonable opinion, violates any Operator policy or is in any way
+harmful or objectionable, or (ii) terminate or deny access to and use
+of the Website to any individual or entity for any reason, in
+Operator’s sole discretion.</p>
+</li>
+
+<li><strong>Responsibility of Website Visitors.</strong> Operator has not reviewed,
+and cannot review, all of the material, including computer software,
+posted to the Website, and cannot therefore be responsible for that
+material’s content, use or effects. By operating the Website,
+Operator does not represent or imply that it endorses the material
+there posted, or that it believes such material to be accurate, useful
+or non-harmful. You are responsible for taking precautions as
+necessary to protect yourself and your computer systems from viruses,
+worms, Trojan horses, and other harmful or destructive content. The
+Website may contain content that is offensive, indecent, or otherwise
+objectionable, as well as content containing technical inaccuracies,
+typographical mistakes, and other errors. The Website may also contain
+material that violates the privacy or publicity rights, or infringes
+the intellectual property and other proprietary rights, of third
+parties, or the downloading, copying or use of which is subject to
+additional terms and conditions, stated or unstated. Operator
+disclaims any responsibility for any harm resulting from the use by
+visitors of the Website, or from any downloading by those visitors of
+content there posted.</li>
+
+<li><strong>Content Posted on Other Websites.</strong> We have not reviewed, and
+cannot review, all of the material, including computer software, made
+available through the websites and webpages to which %%site.name%%
+links, and that link to %%site.name%%. Operator does not have any
+control over those external websites and webpages, and is not
+responsible for their contents or their use. By linking to a
+external website or webpage, Operator does not represent or
+imply that it endorses such website or webpage. You are responsible
+for taking precautions as necessary to protect yourself and your
+computer systems from viruses, worms, Trojan horses, and other harmful
+or destructive content. Operator disclaims any responsibility for
+any harm resulting from your use of external websites and
+webpages.</li>
+
+<li><strong>Copyright Infringement and DMCA Policy.</strong> As Operator asks
+others to respect its intellectual property rights, it respects the
+intellectual property rights of others. If you believe that material
+located on or linked to by %%site.name%% violates your copyright, you
+are encouraged to notify Operator in accordance with Operator’s
+Digital Millennium Copyright Act (”DMCA”) Policy. Operator will
+respond to all such notices, including as required or appropriate by
+removing the infringing material or disabling all links to the
+infringing material. In the case of a visitor who may infringe or
+repeatedly infringes the copyrights or other intellectual property
+rights of Operator or others, Operator may, in its discretion,
+terminate or deny access to and use of the Website. In the case of
+such termination, Operator will have no obligation to provide a
+refund of any amounts previously paid to Operator.</li>
+
+<li><strong>Intellectual Property.</strong> This Agreement does not
+transfer from Operator to you any Operator or third party intellectual
+property, and all right, title and interest in and to such property
+will remain (as between the parties) solely with Operator.
+%%site.name%%, the %%site.name%% logo, and all other trademarks,
+service marks, graphics and logos used in connection with
+%%site.name%%, or the Website are trademarks or registered trademarks
+of Operator or Operator’s licensors. Other trademarks, service marks,
+graphics and logos used in connection with the Website may be the
+trademarks of other third parties. Your use of the Website grants you
+no right or license to reproduce or otherwise use any Operator or
+third-party trademarks.</li>
+
+<li><strong>Changes.</strong> Operator reserves the right, at its sole
+discretion, to modify or replace any part of this Agreement. It is
+your responsibility to check this Agreement periodically for changes.
+Your continued use of or access to the Website following the posting
+of any changes to this Agreement constitutes acceptance of those
+changes. Operator may also, in the future, offer new services and/or
+features through the Website (including, the release of new tools and
+resources). Such new features and/or services shall be subject to the
+terms and conditions of this Agreement.</li>
+
+<li><strong>Termination.</strong> Operator may terminate your access
+to all or any part of the Website at any time, with or without cause,
+with or without notice, effective immediately. If you wish to
+terminate this Agreement or your %%site.name%% account (if you have
+one), you may simply discontinue using the Website. All provisions of
+this Agreement which by their nature should survive termination shall
+survive termination, including, without limitation, ownership
+provisions, warranty disclaimers, indemnity and limitations of
+liability.</li>
+
+<li><strong>Disclaimer of Warranties.</strong> The Website is provided
+“as is”. Operator and its suppliers and licensors hereby disclaim all
+warranties of any kind, express or implied, including, without
+limitation, the warranties of merchantability, fitness for a
+particular purpose and non-infringement. Neither Operator nor its
+suppliers and licensors, makes any warranty that the Website will be
+error free or that access thereto will be continuous or uninterrupted.
+If you’re actually reading this, here’s a treat. You understand that
+you download from, or otherwise obtain content or services through,
+the Website at your own discretion and risk.</li>
+
+<li><strong>Limitation of Liability.</strong> In no event will
+Operator, or its suppliers or licensors, be liable with respect to any
+subject matter of this agreement under any contract, negligence,
+strict liability or other legal or equitable theory for: (i) any
+special, incidental or consequential damages; (ii) the cost of
+procurement or substitute products or services; (iii) for interruption
+of use or loss or corruption of data; or (iv) for any amounts that
+exceed the fees paid by you to Operator under this agreement during
+the twelve (12) month period prior to the cause of action. Operator
+shall have no liability for any failure or delay due to matters beyond
+their reasonable control. The foregoing shall not apply to the extent
+prohibited by applicable law.</li>
+
+<li><strong>General Representation and Warranty.</strong> You
+represent and warrant that (i) your use of the Website will be in
+strict accordance with the Operator Privacy Policy, with this
+Agreement and with all applicable laws and regulations (including
+without limitation any local laws or regulations in your country,
+state, city, or other governmental area, regarding online conduct and
+acceptable content, and including all applicable laws regarding the
+transmission of technical data exported from the United States or the
+country in which you reside) and (ii) your use of the Website will not
+infringe or misappropriate the intellectual property rights of any
+third party.</li>
+
+<li><strong>Indemnification.</strong> You agree to indemnify and hold
+harmless Operator, its contractors, and its licensors, and their
+respective directors, officers, employees and agents from and against
+any and all claims and expenses, including attorneys’ fees, arising
+out of your use of the Website, including but not limited to out of
+your violation this Agreement.</li>
+
+<li><strong>Miscellaneous.</strong> This Agreement constitutes the
+entire agreement between Operator and you concerning the subject
+matter hereof, and they may only be modified by a written amendment
+signed by an authorized executive of Operator, or by the posting by
+Operator of a revised version. If any part of this Agreement is held
+invalid or unenforceable, that part will be construed to reflect the
+parties’ original intent, and the remaining portions will remain in
+full force and effect. A waiver by either party of any term or
+condition of this Agreement or any breach thereof, in any one
+instance, will not waive such term or condition or any subsequent
+breach thereof. You may assign your rights under this Agreement to any
+party that consents to, and agrees to be bound by, its terms and
+conditions; Operator may assign its rights under this Agreement
+without condition. This Agreement will be binding upon and will inure
+to the benefit of the parties, their successors and permitted
+assigns.</li> </ol>
+
+*Originally published by Automattic, Inc. as the [WordPress.com Terms
+of Service](http://en.wordpress.com/tos/) and made available by them
+under the [Creative Commons Attribution-ShareAlike 3.0
+License](http://creativecommons.org/licenses/by-sa/3.0/).
+Modifications to remove reference to "VIP services", rename "blog" to
+"notice stream", remove the choice-of-venue clause, and add variables
+specific to instances of this software made by Control Yourself, Inc.
+and made available under the terms of the same license.* \ No newline at end of file
diff --git a/js/util.js b/js/util.js
index 638104c1c..bbcbc0bbb 100644
--- a/js/util.js
+++ b/js/util.js
@@ -49,8 +49,9 @@ $(document).ready(function(){
// run once in case there's something in there
counter();
- // set the focus
- $("#notice_data-text").focus();
+ if($('body')[0].id != 'conversation') {
+ $("#notice_data-text").focus();
+ }
}
// XXX: refactor this code
@@ -222,6 +223,7 @@ $(document).ready(function(){
}
$("#notice_data-text").val("");
$("#notice_data-attach").val("");
+ $("#notice_in-reply-to").val("");
$('#notice_data-attach_selected').remove();
counter();
}
@@ -281,7 +283,7 @@ function NoticeAttachments() {
},
timeout : 0,
autoHide : true,
- css : {'max-width':'502px', 'top':'22.5%', 'left':'32.5%'}
+ css : {'max-width':'542px', 'top':'22.5%', 'left':'32.5%'}
};
$('#content .notice a.attachment').click(function() {
diff --git a/lib/action.php b/lib/action.php
index c89fe180a..da5b48858 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -439,8 +439,6 @@ class Action extends HTMLOutputter // lawsuit
$this->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
}
- $this->menuItem(common_local_url('openidlogin'),
- _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
$this->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
}
@@ -708,6 +706,11 @@ class Action extends HTMLOutputter // lawsuit
_('About'));
$this->menuItem(common_local_url('doc', array('title' => 'faq')),
_('FAQ'));
+ $bb = common_config('site', 'broughtby');
+ if (!empty($bb)) {
+ $this->menuItem(common_local_url('doc', array('title' => 'tos')),
+ _('TOS'));
+ }
$this->menuItem(common_local_url('doc', array('title' => 'privacy')),
_('Privacy'));
$this->menuItem(common_local_url('doc', array('title' => 'source')),
diff --git a/lib/common.php b/lib/common.php
index 5d451463b..14be747bc 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -124,7 +124,8 @@ $config =
'dupelimit' => 60), # default for same person saying the same thing
'syslog' =>
array('appname' => 'laconica', # for syslog
- 'priority' => 'debug'), # XXX: currently ignored
+ 'priority' => 'debug', # XXX: currently ignored
+ 'facility' => LOG_USER),
'queue' =>
array('enabled' => false,
'subsystem' => 'db', # default to database, or 'stomp'
diff --git a/lib/currentuserdesignaction.php b/lib/currentuserdesignaction.php
index 7c2520cf6..4c7e15a8b 100644
--- a/lib/currentuserdesignaction.php
+++ b/lib/currentuserdesignaction.php
@@ -53,14 +53,19 @@ class CurrentUserDesignAction extends Action
*
* @return nothing
*/
+
function showStylesheets()
{
parent::showStylesheets();
- $design = $this->getDesign();
+ $user = common_current_user();
+
+ if (empty($user) || $user->viewdesigns) {
+ $design = $this->getDesign();
- if (!empty($design)) {
- $design->showCSS($this);
+ if (!empty($design)) {
+ $design->showCSS($this);
+ }
}
}
@@ -84,5 +89,4 @@ class CurrentUserDesignAction extends Action
return $cur->getDesign();
}
-
}
diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php
new file mode 100644
index 000000000..6e7172de0
--- /dev/null
+++ b/lib/dbqueuemanager.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Simple-minded queue manager for storing items in the database
+ *
+ * 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 QueueManager
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@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/
+ */
+
+class DBQueueManager extends QueueManager
+{
+ var $qis = array();
+
+ function enqueue($object, $queue)
+ {
+ $notice = $object;
+
+ $qi = new Queue_item();
+
+ $qi->notice_id = $notice->id;
+ $qi->transport = $queue;
+ $qi->created = $notice->created;
+ $result = $qi->insert();
+
+ if (!$result) {
+ common_log_db_error($qi, 'INSERT', __FILE__);
+ throw new ServerException('DB error inserting queue item');
+ }
+
+ return true;
+ }
+
+ function service($queue, $handler)
+ {
+ while (true) {
+ $this->_log(LOG_DEBUG, 'Checking for notices...');
+ $notice = $this->_nextItem($queue, null);
+ if (empty($notice)) {
+ $this->_log(LOG_DEBUG, 'No notices waiting; idling.');
+ // Nothing in the queue. Do you
+ // have other tasks, like servicing your
+ // XMPP connection, to do?
+ $handler->idle(QUEUE_HANDLER_MISS_IDLE);
+ } else {
+ $this->_log(LOG_INFO, 'Got notice '. $notice->id);
+ // Yay! Got one!
+ if ($handler->handle_notice($notice)) {
+ $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id);
+ $this->_done($notice, $queue);
+ } else {
+ $this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id);
+ $this->_fail($notice, $queue);
+ }
+ // Chance to e.g. service your XMPP connection
+ $this->_log(LOG_DEBUG, 'Idling after success.');
+ $handler->idle(QUEUE_HANDLER_HIT_IDLE);
+ }
+ // XXX: when do we give up?
+ }
+ }
+
+ function _nextItem($queue, $timeout=null)
+ {
+ $start = time();
+ $result = null;
+
+ do {
+ $qi = Queue_item::top($queue);
+ if (!empty($qi)) {
+ $notice = Notice::staticGet('id', $qi->notice_id);
+ if (!empty($notice)) {
+ $result = $notice;
+ } else {
+ $this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id);
+ $qi->delete();
+ $qi->free();
+ $qi = null;
+ }
+ }
+ } while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout));
+
+ return $result;
+ }
+
+ function _done($object, $queue)
+ {
+ // XXX: right now, we only handle notices
+
+ $notice = $object;
+
+ $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
+ 'transport' => $queue));
+
+ if (empty($qi)) {
+ $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
+ } else {
+ if (empty($qi->claimed)) {
+ $this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '.
+ 'for '.$notice->id.', queue '.$queue);
+ }
+ $qi->delete();
+ $qi->free();
+ $qi = null;
+ }
+
+ $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
+
+ $notice->free();
+ $notice = null;
+ }
+
+ function _fail($object, $queue)
+ {
+ // XXX: right now, we only handle notices
+
+ $notice = $object;
+
+ $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
+ 'transport' => $queue));
+
+ if (empty($qi)) {
+ $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
+ } else {
+ if (empty($qi->claimed)) {
+ $this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '.
+ 'for '.$notice->id.', queue '.$queue);
+ } else {
+ $orig = clone($qi);
+ $qi->claimed = null;
+ $qi->update($orig);
+ $qi = null;
+ }
+ }
+
+ $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
+
+ $notice->free();
+ $notice = null;
+ }
+
+ function _log($level, $msg)
+ {
+ common_log($level, 'DBQueueManager: '.$msg);
+ }
+}
diff --git a/lib/facebookutil.php b/lib/facebookutil.php
index 4d0df797b..632ec4bad 100644
--- a/lib/facebookutil.php
+++ b/lib/facebookutil.php
@@ -51,6 +51,10 @@ function updateProfileBox($facebook, $flink, $notice) {
function isFacebookBound($notice, $flink) {
+ if (empty($flink)) {
+ return false;
+ }
+
// If the user does not want to broadcast to Facebook, move along
if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
common_log(LOG_INFO, "Skipping notice $notice->id " .
@@ -86,10 +90,10 @@ function isFacebookBound($notice, $flink) {
if ($result != 1) {
$user = $flink->getUser();
- $msg = "Can't send notice $notice->id to Facebook " .
+ $msg = "Not sending notice $notice->id to Facebook " .
"because user $user->nickname hasn't given the " .
'Facebook app \'status_update\' permission.';
- common_log(LOG_INFO, $msg);
+ common_debug($msg);
$success = false;
}
@@ -108,13 +112,16 @@ function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();
$flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
- $fbuid = $flink->foreign_id;
if (isFacebookBound($notice, $flink)) {
$status = null;
+ $fbuid = $flink->foreign_id;
+
+ $user = $flink->getUser();
// Get the status 'verb' (prefix) the user has set
+
try {
$prefix = $facebook->api_client->
data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
@@ -122,23 +129,79 @@ function facebookBroadcastNotice($notice)
$status = "$prefix $notice->content";
} catch(FacebookRestClientException $e) {
- common_log(LOG_ERR, $e->getMessage());
- return false;
+ common_log(LOG_WARNING, $e->getMessage());
+ common_log(LOG_WARNING,
+ 'Unable to get the status verb setting from Facebook ' .
+ "for $user->nickname (user id: $user->id).");
}
- // Okay, we're good to go!
+ // Okay, we're good to go, update the FB status
try {
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
- updateProfileBox($facebook, $flink, $notice);
} catch(FacebookRestClientException $e) {
common_log(LOG_ERR, $e->getMessage());
- return false;
+ common_log(LOG_ERR,
+ 'Unable to update Facebook status for ' .
+ "$user->nickname (user id: $user->id)!");
+
+ $code = $e->getCode();
- // Should we remove flink if this fails?
+ if ($code >= 200) {
+
+ // 200 The application does not have permission to operate on the passed in uid parameter.
+ // 250 Updating status requires the extended permission status_update.
+ // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML
+
+ remove_facebook_app($flink);
+ }
+
+ }
+
+ // Now try to update the profile box
+
+ try {
+ updateProfileBox($facebook, $flink, $notice);
+ } catch(FacebookRestClientException $e) {
+ common_log(LOG_WARNING, $e->getMessage());
+ common_log(LOG_WARNING,
+ 'Unable to update Facebook profile box for ' .
+ "$user->nickname (user id: $user->id).");
}
}
return true;
}
+
+function remove_facebook_app($flink)
+{
+
+ $user = $flink->getUser();
+
+ common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' .
+ "user $user->nickname (user id: $user->id).");
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log(LOG_ERR, 'Could not remove Facebook App ' .
+ "Foreign_link for $user->nickname (user id: $user->id)!");
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that we are removing their FB app access
+
+ $result = mail_facebook_app_removed($user);
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify ' .
+ "$user->nickname (user id: $user->id) " .
+ 'that their Facebook app link was ' .
+ 'removed!';
+
+ common_log(LOG_WARNING, $msg);
+ }
+
+}
diff --git a/lib/groupdesignaction.php b/lib/groupdesignaction.php
index bc95921f1..58777c283 100644
--- a/lib/groupdesignaction.php
+++ b/lib/groupdesignaction.php
@@ -34,7 +34,7 @@ if (!defined('LACONICA')) {
/**
* Base class for actions that use a group's design
*
- * Pages related to groups can be themed with a design.
+ * Pages related to groups can be themed with a design.
* This superclass returns that design.
*
* @category Action
@@ -48,7 +48,7 @@ class GroupDesignAction extends Action {
/** The group in question */
var $group = null;
-
+
/**
* Show the groups's design stylesheet
*
@@ -58,10 +58,14 @@ class GroupDesignAction extends Action {
{
parent::showStylesheets();
- $design = $this->getDesign();
+ $user = common_current_user();
+
+ if (empty($user) || $user->viewdesigns) {
+ $design = $this->getDesign();
- if (!empty($design)) {
- $design->showCSS($this);
+ if (!empty($design)) {
+ $design->showCSS($this);
+ }
}
}
@@ -76,12 +80,10 @@ class GroupDesignAction extends Action {
function getDesign()
{
-
if (empty($this->group)) {
return null;
}
return $this->group->getDesign();
}
-
}
diff --git a/lib/jabber.php b/lib/jabber.php
index 7d584ad01..e15076160 100644
--- a/lib/jabber.php
+++ b/lib/jabber.php
@@ -77,6 +77,14 @@ function jabber_daemon_address()
return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
}
+class Sharing_XMPP extends XMPPHP_XMPP
+{
+ function getSocket()
+ {
+ return $this->socket;
+ }
+}
+
/**
* connect the configured Jabber account to the configured server
*
@@ -89,7 +97,7 @@ function jabber_connect($resource=null)
{
static $conn = null;
if (!$conn) {
- $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
+ $conn = new Sharing_XMPP(common_config('xmpp', 'host') ?
common_config('xmpp', 'host') :
common_config('xmpp', 'server'),
common_config('xmpp', 'port'),
diff --git a/lib/mail.php b/lib/mail.php
index 4e1f1dbb1..90ee3c992 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -625,3 +625,75 @@ function mail_notify_attn($user, $notice)
common_init_locale();
mail_to_user($user, $subject, $body);
}
+
+/**
+ * Send a mail message to notify a user that her Twitter bridge link
+ * has stopped working, and therefore has been removed. This can
+ * happen when the user changes her Twitter password, or otherwise
+ * revokes access.
+ *
+ * @param User $user user whose Twitter bridge link has been removed
+ *
+ * @return boolean success flag
+ */
+
+function mail_twitter_bridge_removed($user)
+{
+ common_init_locale($user->language);
+
+ $profile = $user->getProfile();
+
+ $subject = sprintf(_('Your Twitter bridge has been disabled.'));
+
+ $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " .
+ 'link to Twitter has been disabled. Your Twitter credentials ' .
+ 'have either changed (did you recently change your Twitter ' .
+ 'password?) or you have otherwise revoked our access to your ' .
+ "Twitter account.\n\n" .
+ 'You can re-enable your Twitter bridge by visiting your ' .
+ "Twitter settings page:\n\n\t%2\$s\n\n" .
+ "Regards,\n%3\$s\n"),
+ $profile->getBestName(),
+ common_local_url('twittersettings'),
+ common_config('site', 'name'));
+
+ common_init_locale();
+ return mail_to_user($user, $subject, $body);
+}
+
+/**
+ * Send a mail message to notify a user that her Facebook Application
+ * access has been removed.
+ *
+ * @param User $user user whose Facebook app link has been removed
+ *
+ * @return boolean success flag
+ */
+
+function mail_facebook_app_removed($user)
+{
+ common_init_locale($user->language);
+
+ $profile = $user->getProfile();
+
+ $site_name = common_config('site', 'name');
+
+ $subject = sprintf(
+ _('Your %s Facebook application access has been disabled.',
+ $site_name));
+
+ $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that we are " .
+ 'unable to update your Facebook status from %s, and have disabled ' .
+ 'the Facebook application for your account. This may be because ' .
+ 'you have removed the Facebook application\'s authorization, or ' .
+ 'have deleted your Facebook account. You can re-enable the ' .
+ 'Facebook application and automatic status updating by ' .
+ "re-installing the %1\$s Facebook application.\n\nRegards,\n\n%1\$s"),
+ $site_name);
+
+ common_init_locale();
+ return mail_to_user($user, $subject, $body);
+
+}
+
+
diff --git a/lib/ownerdesignaction.php b/lib/ownerdesignaction.php
index 424474f42..785b8a93d 100644
--- a/lib/ownerdesignaction.php
+++ b/lib/ownerdesignaction.php
@@ -61,11 +61,15 @@ class OwnerDesignAction extends Action {
{
parent::showStylesheets();
- $design = $this->getDesign();
+ $user = common_current_user();
- if (!empty($design)) {
- $design->showCSS($this);
- }
+ if (empty($user) || $user->viewdesigns) {
+ $design = $this->getDesign();
+
+ if (!empty($design)) {
+ $design->showCSS($this);
+ }
+ }
}
/**
diff --git a/lib/peoplesearchresults.php b/lib/peoplesearchresults.php
deleted file mode 100644
index 9f6696b5f..000000000
--- a/lib/peoplesearchresults.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * People search results class
- *
- * PHP version 5
- *
- * @category Widget
- * @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.
- *
- * 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);
-}
-
-require_once INSTALLDIR.'/lib/profilelist.php';
-
-/**
- * People search results class
- *
- * Derivative of ProfileList with specialization for highlighting search terms.
- *
- * @category Widget
- * @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/
- *
- * @see PeoplesearchAction
- */
-
-class PeopleSearchResults extends ProfileList
-{
- var $terms = null;
- var $pattern = null;
-
- function __construct($profile, $terms, $action)
- {
- parent::__construct($profile, $action);
-
- $this->terms = array_map('preg_quote',
- array_map('htmlspecialchars', $terms));
-
- $this->pattern = '/('.implode('|',$terms).')/i';
- }
-
- function newProfileItem($profile)
- {
- return new PeopleSearchResultItem($profile, $this->action);
- }
-}
-
-class PeopleSearchResultItem extends ProfileListItem
-{
- function highlight($text)
- {
- return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
- }
-}
-
diff --git a/lib/ping.php b/lib/ping.php
index 3de541e9a..d26c73417 100644
--- a/lib/ping.php
+++ b/lib/ping.php
@@ -59,7 +59,7 @@ function ping_broadcast_notice($notice) {
$response = xmlrpc_decode($file);
- if (xmlrpc_is_fault($response)) {
+ if (is_array($response) && xmlrpc_is_fault($response)) {
common_log(LOG_WARNING,
"XML-RPC error for ping ($notify_url, $notice->id) ".
"$response[faultString] ($response[faultCode])");
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
index 375d5538b..e47c9b385 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -68,7 +68,7 @@ class PopularNoticeSection extends NoticeSection
}
$qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
'notice.rendered,notice.url,notice.created,notice.modified,' .
- 'notice.reply_to,notice.is_local,notice.source ' .
+ 'notice.reply_to,notice.is_local,notice.source,notice.conversation ' .
'ORDER BY weight DESC';
$offset = 0;
diff --git a/lib/profilelist.php b/lib/profilelist.php
index a604230f8..774538a4b 100644
--- a/lib/profilelist.php
+++ b/lib/profilelist.php
@@ -248,8 +248,13 @@ class ProfileListItem extends Widget
$usf = new UnsubscribeForm($this->out, $this->profile);
$usf->show();
} else {
- $sf = new SubscribeForm($this->out, $this->profile);
- $sf->show();
+ // Is it a local user? can't remote sub from a list
+ // XXX: make that possible!
+ $other = User::staticGet('id', $this->profile->id);
+ if (!empty($other)) {
+ $sf = new SubscribeForm($this->out, $this->profile);
+ $sf->show();
+ }
}
$this->out->elementEnd('li');
}
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index c1c4f3309..c2ff10f32 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -17,14 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-define('CLAIM_TIMEOUT', 1200);
-
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/daemon.php');
require_once(INSTALLDIR.'/classes/Queue_item.php');
require_once(INSTALLDIR.'/classes/Notice.php');
+define('CLAIM_TIMEOUT', 1200);
+define('QUEUE_HANDLER_MISS_IDLE', 10);
+define('QUEUE_HANDLER_HIT_IDLE', 0);
+
class QueueHandler extends Daemon
{
var $_id = 'generic';
@@ -38,6 +40,11 @@ class QueueHandler extends Daemon
}
}
+ function timeout()
+ {
+ return 60;
+ }
+
function class_name()
{
return ucfirst($this->transport()) . 'Handler';
@@ -76,110 +83,21 @@ class QueueHandler extends Daemon
return true;
}
- function db_dispatch() {
- do {
- $qi = Queue_item::top($this->transport());
- if ($qi) {
- $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
- $notice = Notice::staticGet($qi->notice_id);
- if ($notice) {
- $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
- # XXX: what to do if broadcast fails?
- $result = $this->handle_notice($notice);
- if (!$result) {
- $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
- $orig = $qi;
- $qi->claimed = null;
- $qi->update($orig);
- $this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id);
- continue;
- }
- $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
- $notice->free();
- unset($notice);
- $notice = null;
- } else {
- $this->log(LOG_WARNING, 'queue item for notice that does not exist');
- }
- $qi->delete();
- $qi->free();
- unset($qi);
- $this->idle(0);
- } else {
- $this->clear_old_claims();
- $this->idle(5);
- }
- } while (true);
- }
-
- function stomp_dispatch() {
-
- // use an external message queue system via STOMP
- require_once("Stomp.php");
-
- $server = common_config('queue','stomp_server');
- $username = common_config('queue', 'stomp_username');
- $password = common_config('queue', 'stomp_password');
-
- $con = new Stomp($server);
-
- if (!$con->connect($username, $password)) {
- $this->log(LOG_ERR, 'Failed to connect to queue server');
- return false;
- }
-
- $queue_basename = common_config('queue','queue_basename');
- // subscribe to the relevant queue (format: basename-transport)
- $con->subscribe('/queue/'.$queue_basename.'-'.$this->transport());
-
- do {
- $frame = $con->readFrame();
- if ($frame) {
- $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($frame->headers['created']));
-
- // XXX: Now the queue handler receives only the ID of the
- // notice, and it has to get it from the DB
- // A massive improvement would be avoid DB query by transmitting
- // all the notice details via queue server...
- $notice = Notice::staticGet($frame->body);
-
- if ($notice) {
- $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
- $result = $this->handle_notice($notice);
- if ($result) {
- // if the msg has been handled positively, ack it
- // and the queue server will remove it from the queue
- $con->ack($frame);
- $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
- }
- else {
- // no ack
- $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
- }
- $notice->free();
- unset($notice);
- $notice = null;
- } else {
- $this->log(LOG_WARNING, 'queue item for notice that does not exist');
- }
- }
- } while (true);
-
- $con->disconnect();
- }
-
function run()
{
if (!$this->start()) {
return false;
}
+
$this->log(LOG_INFO, 'checking for queued notices');
- if (common_config('queue','subsystem') == 'stomp') {
- $this->stomp_dispatch();
- }
- else {
- $this->db_dispatch();
- }
+
+ $queue = $this->transport();
+ $timeout = $this->timeout();
+
+ $qm = QueueManager::get();
+
+ $qm->service($queue, $this);
+
if (!$this->finish()) {
return false;
}
@@ -188,24 +106,19 @@ class QueueHandler extends Daemon
function idle($timeout=0)
{
- if ($timeout>0) {
+ if ($timeout > 0) {
sleep($timeout);
}
}
- function clear_old_claims()
+ function log($level, $msg)
{
- $qi = new Queue_item();
- $qi->transport = $this->transport();
- $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
- $qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
- $qi->free();
- unset($qi);
+ common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
}
- function log($level, $msg)
+ function getSockets()
{
- common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
+ return array();
}
}
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
new file mode 100644
index 000000000..582c24790
--- /dev/null
+++ b/lib/queuemanager.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Abstract class for queue managers
+ *
+ * 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 QueueManager
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@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/
+ */
+
+class QueueManager
+{
+ static $qm = null;
+
+ static function get()
+ {
+ if (empty(self::$qm)) {
+
+ if (Event::handle('StartNewQueueManager', array(&self::$qm))) {
+
+ $enabled = common_config('queue', 'enabled');
+ $type = common_config('queue', 'subsystem');
+
+ if (!$enabled) {
+ // does everything immediately
+ self::$qm = new UnQueueManager();
+ } else {
+ switch ($type) {
+ case 'db':
+ self::$qm = new DBQueueManager();
+ break;
+ case 'stomp':
+ self::$qm = new StompQueueManager();
+ break;
+ default:
+ throw new ServerException("No queue manager class for type '$type'");
+ }
+ }
+ }
+ }
+
+ return self::$qm;
+ }
+
+ function enqueue($object, $queue)
+ {
+ throw ServerException("Unimplemented function 'enqueue' called");
+ }
+
+ function service($queue, $handler)
+ {
+ throw ServerException("Unimplemented function 'service' called");
+ }
+}
diff --git a/lib/router.php b/lib/router.php
index 784ea9882..75e72f932 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -261,7 +261,7 @@ class Router
$m->connect('api/statuses/:method',
array('action' => 'api',
'apiaction' => 'statuses'),
- array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|friends|followers|featured)(\.(atom|rss|xml|json))?'));
+ array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
$m->connect('api/statuses/:method/:argument',
array('action' => 'api',
@@ -317,7 +317,7 @@ class Router
$m->connect('api/friendships/:method',
array('action' => 'api',
'apiaction' => 'friendships'),
- array('method' => 'exists(\.(xml|json))'));
+ array('method' => '(show|exists)(\.(xml|json))'));
// Social graph
diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php
new file mode 100644
index 000000000..d13af3fa5
--- /dev/null
+++ b/lib/stompqueuemanager.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Abstract class for queue managers
+ *
+ * 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 QueueManager
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@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/
+ */
+
+require_once 'Stomp.php';
+
+class LiberalStomp extends Stomp
+{
+ function getSocket()
+ {
+ return $this->_socket;
+ }
+}
+
+class StompQueueManager
+{
+ var $server = null;
+ var $username = null;
+ var $password = null;
+ var $base = null;
+ var $con = null;
+
+ function __construct()
+ {
+ $this->server = common_config('queue', 'stomp_server');
+ $this->username = common_config('queue', 'stomp_username');
+ $this->password = common_config('queue', 'stomp_password');
+ $this->base = common_config('queue', 'queue_basename');
+ }
+
+ function _connect()
+ {
+ if (empty($this->con)) {
+ $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
+ $this->con = new LiberalStomp($this->server);
+
+ if ($this->con->connect($this->username, $this->password)) {
+ $this->_log(LOG_INFO, "Connected.");
+ } else {
+ $this->_log(LOG_ERR, 'Failed to connect to queue server');
+ throw new ServerException('Failed to connect to queue server');
+ }
+ }
+ }
+
+ function enqueue($object, $queue)
+ {
+ $notice = $object;
+
+ $this->_connect();
+
+ // XXX: serialize and send entire notice
+
+ $result = $this->con->send($this->_queueName($queue),
+ $notice->id, // BODY of the message
+ array ('created' => $notice->created));
+
+ if (!$result) {
+ common_log(LOG_ERR, 'Error sending to '.$queue.' queue');
+ return false;
+ }
+
+ common_log(LOG_DEBUG, 'complete remote queueing notice ID = '
+ . $notice->id . ' for ' . $queue);
+ }
+
+ function service($queue, $handler)
+ {
+ $result = null;
+
+ $this->_connect();
+
+ $this->con->setReadTimeout($handler->timeout());
+
+ $this->con->subscribe($this->_queueName($queue));
+
+ while (true) {
+
+ // Wait for something on one of our sockets
+
+ $stompsock = $this->con->getSocket();
+
+ $handsocks = $handler->getSockets();
+
+ $socks = array_merge(array($stompsock), $handsocks);
+
+ $read = $socks;
+ $write = array();
+ $except = array();
+
+ $ready = stream_select($read, $write, $except, $handler->timeout(), 0);
+
+ if ($ready === false) {
+ $this->_log(LOG_ERR, "Error selecting on sockets");
+ } else if ($ready > 0) {
+ if (in_array($stompsock, $read)) {
+ $this->_handleNotice($queue, $handler);
+ }
+ foreach ($handsocks as $sock) {
+ if (in_array($sock, $read)) {
+ $handler->idle(QUEUE_HANDLER_HIT_IDLE);
+ break;
+ }
+ }
+ }
+ }
+
+ $this->con->unsubscribe($this->_queueName($queue));
+ }
+
+ function _handleNotice($queue, $handler)
+ {
+ $frame = $this->con->readFrame();
+
+ if (!empty($frame)) {
+ $notice = Notice::staticGet('id', $frame->body);
+
+ if (empty($notice)) {
+ $this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue);
+ $this->con->ack($frame);
+ } else {
+ if ($handler->handle_notice($notice)) {
+ $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
+ $this->con->ack($frame);
+ } else {
+ $this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
+ // FIXME we probably shouldn't have to do
+ // this kind of queue management ourselves
+ $this->con->ack($frame);
+ $this->enqueue($notice, $queue);
+ }
+ unset($notice);
+ }
+
+ unset($frame);
+ }
+ }
+
+ function _queueName($queue)
+ {
+ return common_config('queue', 'queue_basename') . $queue;
+ }
+
+ function _log($level, $msg)
+ {
+ common_log($level, 'StompQueueManager: '.$msg);
+ }
+}
diff --git a/lib/twitter.php b/lib/twitter.php
index 3ec082686..47af32e61 100644
--- a/lib/twitter.php
+++ b/lib/twitter.php
@@ -360,14 +360,11 @@ function is_twitter_bound($notice, $flink) {
function broadcast_twitter($notice)
{
- $success = true;
$flink = Foreign_link::getByUserID($notice->profile_id,
TWITTER_SERVICE);
- // XXX: Not sure WHERE to check whether a notice should go to
- // Twitter. Should we even put in the queue if it shouldn't? --Zach
- if (!is_null($flink) && is_twitter_bound($notice, $flink)) {
+ if (is_twitter_bound($notice, $flink)) {
$fuser = $flink->getForeignUser();
$twitter_user = $fuser->nickname;
@@ -401,33 +398,99 @@ function broadcast_twitter($notice)
curl_setopt_array($ch, $options);
$data = curl_exec($ch);
$errmsg = curl_error($ch);
+ $errno = curl_errno($ch);
- if ($errmsg) {
- common_debug("cURL error: $errmsg - " .
+ if (!empty($errmsg)) {
+ common_debug("cURL error ($errno): $errmsg - " .
"trying to send notice for $twitter_user.",
__FILE__);
- $success = false;
+
+ $user = $flink->getUser();
+
+ if ($errmsg == 'The requested URL returned error: 401') {
+ common_debug(sprintf('User %s (user id: %s) ' .
+ 'has bad Twitter credentials!',
+ $user->nickname, $user->id));
+
+ // Bad credentials we need to delete the foreign_link
+ // to Twitter and inform the user.
+
+ remove_twitter_link($flink);
+
+ return true;
+
+ } else {
+
+ // Some other error happened, so we should try to
+ // send again later
+
+ return false;
+ }
+
}
curl_close($ch);
- if (!$data) {
+ if (empty($data)) {
common_debug("No data returned by Twitter's " .
"API trying to send update for $twitter_user",
__FILE__);
- $success = false;
- }
- // Twitter should return a status
- $status = json_decode($data);
+ // XXX: Not sure this represents a failure to send, but it
+ // probably does
- if (!$status->id) {
- common_debug("Unexpected data returned by Twitter " .
- " API trying to send update for $twitter_user",
- __FILE__);
- $success = false;
+ return false;
+
+ } else {
+
+ // Twitter should return a status
+ $status = json_decode($data);
+
+ if (empty($status)) {
+ common_debug("Unexpected data returned by Twitter " .
+ " API trying to send update for $twitter_user",
+ __FILE__);
+
+ // XXX: Again, this could represent a failure posting
+ // or the Twitter API might just be behaving flakey.
+ // We're treating it as a failure to post.
+
+ return false;
+ }
}
}
- return $success;
+ return true;
+}
+
+function remove_twitter_link($flink)
+{
+ $user = $flink->getUser();
+
+ common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
+ "user $user->nickname (user id: $user->id).");
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
+ "Foreign_link for $user->nickname (user id: $user->id)!");
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that her Twitter bridge is down
+
+ $result = mail_twitter_bridge_removed($user);
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify ' .
+ "$user->nickname (user id: $user->id) " .
+ 'that their Twitter bridge link was ' .
+ 'removed!';
+
+ common_log(LOG_WARNING, $msg);
+ }
+
}
+
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
index f538a0298..40e5b5067 100644
--- a/lib/twitterapi.php
+++ b/lib/twitterapi.php
@@ -278,6 +278,67 @@ class TwitterapiAction extends Action
return $twitter_dm;
}
+ function twitter_relationship_array($source, $target)
+ {
+ $relationship = array();
+
+ $relationship['source'] =
+ $this->relationship_details_array($source, $target);
+ $relationship['target'] =
+ $this->relationship_details_array($target, $source);
+
+ return array('relationship' => $relationship);
+ }
+
+ function relationship_details_array($source, $target)
+ {
+ $details = array();
+
+ $details['screen_name'] = $source->nickname;
+ $details['followed_by'] = $target->isSubscribed($source);
+ $details['following'] = $source->isSubscribed($target);
+
+ $notifications = false;
+
+ if ($source->isSubscribed($target)) {
+
+ $sub = Subscription::pkeyGet(array('subscriber' =>
+ $source->id, 'subscribed' => $target->id));
+
+ if (!empty($sub)) {
+ $notifications = ($sub->jabber || $sub->sms);
+ }
+ }
+
+ $details['notifications_enabled'] = $notifications;
+ $details['blocking'] = $source->hasBlocked($target);
+ $details['id'] = $source->id;
+
+ return $details;
+ }
+
+ function show_twitter_xml_relationship($relationship)
+ {
+ $this->elementStart('relationship');
+
+ foreach($relationship as $element => $value) {
+ if ($element == 'source' || $element == 'target') {
+ $this->elementStart($element);
+ $this->show_xml_relationship_details($value);
+ $this->elementEnd($element);
+ }
+ }
+
+ $this->elementEnd('relationship');
+ }
+
+ function show_xml_relationship_details($details)
+ {
+ foreach($details as $element => $value) {
+ $this->element($element, null, $value);
+ }
+ }
+
function show_twitter_xml_status($twitter_status)
{
$this->elementStart('status');
diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php
new file mode 100644
index 000000000..515461072
--- /dev/null
+++ b/lib/unqueuemanager.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * A queue manager interface for just doing things immediately
+ *
+ * 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 QueueManager
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@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/
+ */
+
+class UnQueueManager
+{
+ function enqueue($object, $queue)
+ {
+ $notice = $object;
+
+ switch ($queue)
+ {
+ case 'omb':
+ if ($this->_isLocal($notice)) {
+ require_once(INSTALLDIR.'/lib/omb.php');
+ omb_broadcast_remote_subscribers($notice);
+ }
+ break;
+ case 'public':
+ if ($this->_isLocal($notice)) {
+ require_once(INSTALLDIR.'/lib/jabber.php');
+ jabber_public_notice($notice);
+ }
+ break;
+ case 'twitter':
+ if ($this->_isLocal($notice)) {
+ broadcast_twitter($notice);
+ }
+ break;
+ case 'facebook':
+ if ($this->_isLocal($notice)) {
+ require_once INSTALLDIR . '/lib/facebookutil.php';
+ return facebookBroadcastNotice($notice);
+ }
+ break;
+ case 'ping':
+ if ($this->_isLocal($notice)) {
+ require_once INSTALLDIR . '/lib/ping.php';
+ return ping_broadcast_notice($notice);
+ }
+ case 'sms':
+ require_once(INSTALLDIR.'/lib/mail.php');
+ mail_broadcast_notice_sms($notice);
+ break;
+ case 'jabber':
+ require_once(INSTALLDIR.'/lib/jabber.php');
+ jabber_broadcast_notice($notice);
+ break;
+ default:
+ throw ServerException("UnQueueManager: Unknown queue: $type");
+ }
+ }
+
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
+ $notice->is_local == NOTICE_LOCAL_NONPUBLIC);
+ }
+} \ No newline at end of file
diff --git a/lib/util.php b/lib/util.php
index 461ca15c1..9e8ec41d2 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -862,165 +862,45 @@ function common_redirect($url, $code=307)
function common_broadcast_notice($notice, $remote=false)
{
- if (common_config('queue', 'enabled')) {
- // Do it later!
- return common_enqueue_notice($notice);
- } else {
- return common_real_broadcast($notice, $remote);
- }
+ return common_enqueue_notice($notice);
}
// Stick the notice on the queue
function common_enqueue_notice($notice)
{
- $transports = array('omb', 'sms', 'public', 'twitter', 'facebook', 'ping');
-
- if (common_config('xmpp', 'enabled'))
- {
- $transports[] = 'jabber';
- }
-
- if (common_config('queue','subsystem') == 'stomp') {
- common_enqueue_notice_stomp($notice, $transports);
- }
- else {
- common_enqueue_notice_db($notice, $transports);
- }
- return $result;
-}
-
-function common_enqueue_notice_stomp($notice, $transports)
-{
- // use an external message queue system via STOMP
- require_once("Stomp.php");
+ static $localTransports = array('omb',
+ 'twitter',
+ 'facebook',
+ 'ping');
+ static $allTransports = array('sms');
- $server = common_config('queue','stomp_server');
- $username = common_config('queue', 'stomp_username');
- $password = common_config('queue', 'stomp_password');
+ $transports = $allTransports;
- $con = new Stomp($server);
+ $xmpp = common_config('xmpp', 'enabled');
- if (!$con->connect($username, $password)) {
- common_log(LOG_ERR, 'Failed to connect to queue server');
- return false;
+ if ($xmpp) {
+ $transports[] = 'jabber';
}
- $queue_basename = common_config('queue','queue_basename');
-
- foreach ($transports as $transport) {
- $result = $con->send('/queue/'.$queue_basename.'-'.$transport, // QUEUE
- $notice->id, // BODY of the message
- array ('created' => $notice->created));
- if (!$result) {
- common_log(LOG_ERR, 'Error sending to '.$transport.' queue');
- return false;
- }
- common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' . $notice->id . ' for ' . $transport);
- }
-
- //send tags as headers, so they can be used as JMS selectors
- common_log(LOG_DEBUG, 'searching for tags ' . $notice->id);
- $tags = array();
- $tag = new Notice_tag();
- $tag->notice_id = $notice->id;
- if ($tag->find()) {
- while ($tag->fetch()) {
- common_log(LOG_DEBUG, 'tag found = ' . $tag->tag);
- array_push($tags,$tag->tag);
+ if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
+ $notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
+ $transports = array_merge($transports, $localTransports);
+ if ($xmpp) {
+ $transports[] = 'public';
}
}
- $tag->free();
- $con->send('/topic/laconica.'.$notice->profile_id,
- $notice->content,
- array(
- 'profile_id' => $notice->profile_id,
- 'created' => $notice->created,
- 'tags' => implode($tags,' - ')
- )
- );
- common_log(LOG_DEBUG, 'sent to personal topic ' . $notice->id);
- $con->send('/topic/laconica.allusers',
- $notice->content,
- array(
- 'profile_id' => $notice->profile_id,
- 'created' => $notice->created,
- 'tags' => implode($tags,' - ')
- )
- );
- common_log(LOG_DEBUG, 'sent to catch-all topic ' . $notice->id);
- $result = true;
-}
+ $qm = QueueManager::get();
-function common_enqueue_notice_db($notice, $transports)
-{
- // in any other case, 'internal'
- foreach ($transports as $transport) {
- common_enqueue_notice_transport($notice, $transport);
+ foreach ($transports as $transport)
+ {
+ $qm->enqueue($notice, $transport);
}
-}
-function common_enqueue_notice_transport($notice, $transport)
-{
- $qi = new Queue_item();
- $qi->notice_id = $notice->id;
- $qi->transport = $transport;
- $qi->created = $notice->created;
- $result = $qi->insert();
- if (!$result) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
- throw new ServerException('DB error inserting queue item: ' . $last_error->message);
- }
- common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
return true;
}
-function common_real_broadcast($notice, $remote=false)
-{
- $success = true;
- if (!$remote) {
- // Make sure we have the OMB stuff
- require_once(INSTALLDIR.'/lib/omb.php');
- $success = omb_broadcast_remote_subscribers($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- require_once(INSTALLDIR.'/lib/jabber.php');
- $success = jabber_broadcast_notice($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- require_once(INSTALLDIR.'/lib/mail.php');
- $success = mail_broadcast_notice_sms($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- $success = jabber_public_notice($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- $success = broadcast_twitter($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id);
- }
- }
-
- // XXX: Do a real-time FB broadcast here?
-
- // XXX: broadcast notices to other IM
- return $success;
-}
-
function common_broadcast_profile($profile)
{
// XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
@@ -1098,7 +978,8 @@ function common_ensure_syslog()
{
static $initialized = false;
if (!$initialized) {
- openlog(common_config('syslog', 'appname'), 0, LOG_USER);
+ openlog(common_config('syslog', 'appname'), 0,
+ common_config('syslog', 'facility'));
$initialized = true;
}
}
@@ -1147,6 +1028,9 @@ function common_log_objstring(&$object)
if (is_null($object)) {
return "null";
}
+ if (!($object instanceof DB_DataObject)) {
+ return "(unknown)";
+ }
$arr = $object->toArray();
$fields = array();
foreach ($arr as $k => $v) {
diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php
index 986e09c25..77d476c30 100644
--- a/lib/xmppqueuehandler.php
+++ b/lib/xmppqueuehandler.php
@@ -21,6 +21,8 @@ if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/queuehandler.php');
+define('PING_INTERVAL', 120);
+
/**
* Common superclass for all XMPP-using queue handlers. They all need to
* service their message queues on idle, and forward any incoming messages
@@ -30,6 +32,9 @@ require_once(INSTALLDIR.'/lib/queuehandler.php');
class XmppQueueHandler extends QueueHandler
{
+ var $pingid = 0;
+ var $lastping = null;
+
function start()
{
# Low priority; we don't want to receive messages
@@ -44,6 +49,11 @@ class XmppQueueHandler extends QueueHandler
return !is_null($this->conn);
}
+ function timeout()
+ {
+ return 10;
+ }
+
function handle_reconnect(&$pl)
{
$this->conn->processUntil('session_start');
@@ -55,7 +65,13 @@ class XmppQueueHandler extends QueueHandler
# Process the queue for as long as needed
try {
if ($this->conn) {
+ $this->log(LOG_DEBUG, "Servicing the XMPP queue.");
$this->conn->processTime($timeout);
+ $now = time();
+ if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) {
+ $this->sendPing();
+ $this->lastping = $now;
+ }
}
} catch (XMPPHP_Exception $e) {
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
@@ -63,6 +79,22 @@ class XmppQueueHandler extends QueueHandler
}
}
+ function sendPing()
+ {
+ $jid = jabber_daemon_address().'/'.$this->_id.$this->transport();
+ $server = common_config('xmpp', 'server');
+
+ if (!isset($this->pingid)) {
+ $this->pingid = 0;
+ } else {
+ $this->pingid++;
+ }
+
+ $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
+
+ $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
+ }
+
function forward_message(&$pl)
{
if ($pl['type'] != 'chat') {
@@ -91,7 +123,12 @@ class XmppQueueHandler extends QueueHandler
if (common_config('xmpp', 'listener')) {
return common_config('xmpp', 'listener');
} else {
- return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
+ return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon';
}
}
+
+ function getSockets()
+ {
+ return array($this->conn->getSocket());
+ }
}
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
index d8af1a4e8..65870a187 100644
--- a/plugins/FBConnect/FBConnectPlugin.php
+++ b/plugins/FBConnect/FBConnectPlugin.php
@@ -313,8 +313,6 @@ class FBConnectPlugin extends Plugin
$action->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
}
- $action->menuItem(common_local_url('openidlogin'),
- _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
$action->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
}
diff --git a/scripts/update_translations.php b/scripts/update_translations.php
index 4d7adafea..2f4ca8720 100755
--- a/scripts/update_translations.php
+++ b/scripts/update_translations.php
@@ -1,66 +1,97 @@
+#!/usr/bin/env php
<?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/>.
+ */
+
+// Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+// Master Laconica .pot file location (created by update_pot.sh)
+$laconica_pot = INSTALLDIR . '/locale/laconica.po';
set_time_limit(60);
-chdir(dirname(__FILE__) . '/..');
/* Languages to pull */
-$languages = array(
- 'da_DK' => 'http://laconi.ca/translate/download.php?file_id=93',
- 'nl_NL' => 'http://laconi.ca/translate/download.php?file_id=97',
- 'en_NZ' => 'http://laconi.ca/translate/download.php?file_id=87',
- 'eo' => 'http://laconi.ca/translate/download.php?file_id=88',
- 'fr_FR' => 'http://laconi.ca/translate/download.php?file_id=99',
- 'de_DE' => 'http://laconi.ca/translate/download.php?file_id=100',
- 'it_IT' => 'http://laconi.ca/translate/download.php?file_id=101',
- 'ko' => 'http://laconi.ca/translate/download.php?file_id=102',
- 'no_NB' => 'http://laconi.ca/translate/download.php?file_id=104',
- 'pt' => 'http://laconi.ca/translate/download.php?file_id=106',
- 'pt_BR' => 'http://laconi.ca/translate/download.php?file_id=107',
- 'ru_RU' => 'http://laconi.ca/translate/download.php?file_id=109',
- 'es' => 'http://laconi.ca/translate/download.php?file_id=110',
- 'tr_TR' => 'http://laconi.ca/translate/download.php?file_id=114',
- 'uk_UA' => 'http://laconi.ca/translate/download.php?file_id=115',
- 'he_IL' => 'http://laconi.ca/translate/download.php?file_id=116',
- 'mk_MK' => 'http://laconi.ca/translate/download.php?file_id=103',
- 'ja_JP' => 'http://laconi.ca/translate/download.php?file_id=117',
- 'cs_CZ' => 'http://laconi.ca/translate/download.php?file_id=96',
- 'ca_ES' => 'http://laconi.ca/translate/download.php?file_id=95',
- 'pl_PL' => 'http://laconi.ca/translate/download.php?file_id=105',
- 'sv_SE' => 'http://laconi.ca/translate/download.php?file_id=128'
-);
+$languages = get_all_languages();
/* Update the languages */
-foreach ($languages as $code => $file) {
-
- $lcdir='locale/'.$code;
- $msgdir=$lcdir.'/LC_MESSAGES';
- $pofile=$msgdir.'/laconica.po';
- $mofile=$msgdir.'/laconica.mo';
-
- /* Check for an existing */
- if (!is_dir($msgdir)) {
- mkdir($lcdir);
- mkdir($msgdir);
- $existingSHA1 = '';
- } else {
- $existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
- }
-
- /* Get the remote one */
- $newFile = file_get_contents($file);
-
- // Update if the local .po file is different to the one downloaded, or
- // if the .mo file is not present.
- if(sha1($newFile)!=$existingSHA1 || !file_exists($mofile)) {
- echo "Updating ".$code."\n";
- file_put_contents($pofile, $newFile);
- $prevdir = getcwd();
- chdir($msgdir);
- system('msgmerge -U laconica.po ../../laconica.pot');
- system('msgfmt -f -o laconica.mo laconica.po');
- chdir($prevdir);
- } else {
- echo "Unchanged - ".$code."\n";
- }
+
+foreach ($languages as $language) {
+
+ $code = $language['lang'];
+ $file_url = 'http://laconi.ca/pootle/' . $code .
+ '/laconica/LC_MESSAGES/laconica.po';
+ $lcdir = INSTALLDIR . '/locale/' . $code;
+ $msgdir = "$lcdir/LC_MESSAGES";
+ $pofile = "$msgdir/laconica.po";
+ $mofile = "$msgdir/laconica.mo";
+
+ /* Check for an existing */
+ if (!is_dir($msgdir)) {
+ mkdir($lcdir);
+ mkdir($msgdir);
+ $existingSHA1 = '';
+ } else {
+ $existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
+ }
+
+ /* Get the remote one */
+ $new_file = curl_get_file($file_url);
+
+ if ($new_file === FALSE) {
+ echo "Couldn't retrieve .po file for $code: $file_url\n";
+ continue;
+ }
+
+ // Update if the local .po file is different to the one downloaded, or
+ // if the .mo file is not present.
+ if (sha1($new_file) != $existingSHA1 || !file_exists($mofile)) {
+ echo "Updating ".$code."\n";
+ file_put_contents($pofile, $new_file);
+ system(sprintf('msgmerge -U %s %s', $pofile, $laconica_pot));
+ system(sprintf('msgfmt -f -o %s %s', $mofile, $pofile));
+ } else {
+ echo "Unchanged - ".$code."\n";
+ }
}
+
echo "Finished\n";
+
+
+function curl_get_file($url)
+{
+ $c = curl_init();
+ curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($c, CURLOPT_URL, $url);
+ $contents = curl_exec($c);
+ curl_close($c);
+
+ if (!empty($contents)) {
+ return $contents;
+ }
+
+ return FALSE;
+}
diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php
index ca6218120..488b4b514 100755
--- a/scripts/xmppdaemon.php
+++ b/scripts/xmppdaemon.php
@@ -60,7 +60,9 @@ class XMPPDaemon extends Daemon
$this->resource = common_config('xmpp', 'resource') . 'daemon';
}
- $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->user}@{$this->server}/{$this->resource}");
+ $this->jid = $this->user.'@'.$this->server.'/'.$this->resource;
+
+ $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->jid}");
}
function connect()
@@ -106,16 +108,36 @@ class XMPPDaemon extends Daemon
$this->log(LOG_DEBUG, "Beginning processing loop.");
- $this->conn->process();
+ while ($this->conn->processTime(60)) {
+ $this->sendPing();
+ }
}
}
+ function sendPing()
+ {
+ if (!isset($this->pingid)) {
+ $this->pingid = 0;
+ } else {
+ $this->pingid++;
+ }
+
+ $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
+
+ $this->conn->send("<iq from='{$this->jid}' to='{$this->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
+ }
+
function handle_reconnect(&$pl)
{
$this->log(LOG_DEBUG, "Got reconnection callback.");
$this->conn->processUntil('session_start');
$this->log(LOG_DEBUG, "Sending reconnection presence.");
$this->conn->presence('Send me a message to post a notice', 'available', null, 'available', 100);
+ unset($pl['xml']);
+ $pl['xml'] = null;
+
+ $pl = null;
+ unset($pl);
}
function get_user($from)
@@ -189,6 +211,12 @@ class XMPPDaemon extends Daemon
$user->free();
unset($user);
+
+ unset($pl['xml']);
+ $pl['xml'] = null;
+
+ $pl = null;
+ unset($pl);
}
function is_self($from)
@@ -334,6 +362,11 @@ class XMPPDaemon extends Daemon
}
break;
}
+ unset($pl['xml']);
+ $pl['xml'] = null;
+
+ $pl = null;
+ unset($pl);
}
function log($level, $msg)
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index f2b200376..3426e71c0 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -275,7 +275,7 @@ margin-bottom:18px;
#anon_notice {
float:left;
-width:43.2%;
+width:42.4%;
padding:1.1%;
border-radius:7px;
-moz-border-radius:7px;
@@ -396,7 +396,7 @@ margin-bottom:1em;
}
#content {
-width:64.009%;
+width:63.311%;
min-height:259px;
padding:1.795%;
float:left;
@@ -422,7 +422,7 @@ float:left;
width:27.917%;
min-height:259px;
float:left;
-margin-left:0.5%;
+margin-left:0.699%;
padding:1.795%;
border-radius:7px;
-moz-border-radius:7px;
@@ -432,7 +432,7 @@ border-style:solid;
}
#form_notice {
-width:45.664%;
+width:45%;
float:left;
position:relative;
line-height:1;
@@ -471,12 +471,12 @@ cursor:pointer;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
-left:394px;
+left:86%;
width:16px;
height:16px;
}
#form_notice #notice_data-attach {
-left:183px;
+left:40.6%;
padding:0;
height:16px;
}
@@ -783,8 +783,8 @@ list-style-type:none;
}
.notices .notices {
margin-top:7px;
-margin-left:5%;
-width:95%;
+margin-left:2%;
+width:98%;
float:left;
}
@@ -1020,20 +1020,21 @@ font-weight:bold;
padding:0;
}
#jOverlayContent h1 {
-max-width:475px;
+max-width:425px;
}
#jOverlayContent #content {
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
-#jOverlayContent #content img {
-max-width:480px;
-}
#jOverlayLoading {
top:22.5%;
left:40%;
}
+#attachment_view img {
+max-width:480px;
+max-height:480px;
+}
#attachment_view #oembed_info {
margin-top:11px;
}
@@ -1278,6 +1279,7 @@ margin-bottom:0;
#form_settings_design #settings_design_background-image img {
max-width:480px;
+max-height:480px;
}
#form_settings_design #settings_design_color .form_data,
diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css
index 43fb01492..3e128b84e 100644
--- a/theme/base/css/ie.css
+++ b/theme/base/css/ie.css
@@ -9,7 +9,7 @@ width:78%;
#form_notice .form_note + label {
position:absolute;
top:25px;
-left:380px;
+left:83%;
text-indent:-9999px;
height:16px;
width:16px;
@@ -25,10 +25,6 @@ width:78.5%;
#form_notice #notice_data-attach_selected button {
padding:0 4px;
}
-#anon_notice {
-max-width:39%;
-}
-
.notice-options input.submit {
font-size:0;
margin-top:3px;
@@ -49,6 +45,6 @@ z-index:1;
.notice:hover {
z-index:9999;
}
-.notice .thumbnail img {
+.notice .thumbnail img {
z-index:9999;
-} \ No newline at end of file
+}
diff --git a/theme/default/css/display.css b/theme/default/css/display.css
index 89197bddb..251d6706b 100644
--- a/theme/default/css/display.css
+++ b/theme/default/css/display.css
@@ -115,12 +115,14 @@ border-color:transparent;
background-color:#FFFFFF;
}
-#site_nav_local_views a {
-background-color:rgba(194, 194, 194, 0.5);
+#site_nav_local_views li {
box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
}
+#site_nav_local_views a {
+background-color:rgba(194, 194, 194, 0.5);
+}
#site_nav_local_views a:hover {
background-color:rgba(255, 255, 255, 0.7);
}
diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css
index 025debf34..42a4573a7 100644
--- a/theme/identica/css/display.css
+++ b/theme/identica/css/display.css
@@ -115,12 +115,14 @@ border-color:transparent;
background-color:#FFFFFF;
}
-#site_nav_local_views a {
-background-color:rgba(194, 194, 194, 0.5);
+#site_nav_local_views li {
box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
}
+#site_nav_local_views a {
+background-color:rgba(194, 194, 194, 0.5);
+}
#site_nav_local_views a:hover {
background-color:rgba(255, 255, 255, 0.7);
}