README
------
-Laconica 0.7.3 ("You Are The Everything")
-7 April 2009
+Laconica 0.7.4 ("Can't Get There From Here")
+29 May 2009
This is the README file for Laconica, the Open Source microblogging
platform. It includes installation instructions, descriptions of
New this version
================
-This is a minor bug-fix and feature release since version 0.7.2.1,
-released Mar 11 2009. Notable changes this version:
-
-- A plugin to allow a templating language for customization
-- A plugin for Piwik Analytics engine
-- A bookmarklet for posting a notice about a Web page you're reading
-- A welcome notice ('welcomebot') and default subscription for new users
-- Support for SSL for some or all pages on the site
-- Better handling of empty notice lists on many pages
-- Major improvements to the Twitter friend-sync offline processing
-- subscribers, subscriptions, groups are listed on the Personal page.
-- "Invite" link restored to main menu
-- Better memory handling in FOAF output
-- Fix for SUP support (FriendFeed)
-- Correct and intelligent redirect HTTP status codes
-- Fix DB collations for search and sort
-- Better H1s and Titles using user full names
-- Fixes to make the linkback plugin operational
-- Better indication that a notice is being published by Ajax (spinner)
-- Better and unified Atom output
-- Hiding "register" and "join now" messages when site is closed
-- ping, twitter and facebook queuehandlers working better
-- Updated RPM spec
+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.
Prerequisites
=============
1. Unpack the tarball you downloaded on your Web server. Usually a
command like this will work:
- tar zxf laconica-0.7.3.tar.gz
+ tar zxf laconica-0.7.4.tar.gz
- ...which will make a laconica-0.7.3 subdirectory in your current
+ ...which will make a laconica-0.7.4 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.)
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.3 /var/www/mublog
+ mv laconica-0.7.4 /var/www/mublog
This will make your Laconica instance available in the mublog path of
your server, like "http://example.net/mublog". "microblog" or
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.3. Try these step-by-step instructions; read
+procedure in Laconica 0.7.4. 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
T_STRING") in the browser, check to see that you don't have any
conflicts in your code.
-If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes"
+If you upgraded to Laconica 0.7.4 without reading the "Notice inboxes"
section above, and all your users' 'Personal' tabs are empty, read the
"Notice inboxes" section above.
* Ken Sedgwick
* Brian Hendrickson
* Tobias Diekershoff
+* Dan Moore
+* Fil
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,
}
if (in_array($fullname, $bareauth)) {
- # bareauth: only needs auth if without an argument
- if ($this->api_arg) {
+ # 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')) {
return false;
} else {
return true;
require_once INSTALLDIR.'/lib/accountsettingsaction.php';
-
-
class DesignsettingsAction extends AccountSettingsAction
{
/**
$this->element('legend', null, _('Change colours'));
$this->elementStart('ul', 'form_data');
- //This is a JSON object in the DB field. Here for testing. Remove later.
- $userSwatch = '{"body":{"background-color":"#F0F2F5"},
- "#content":{"background-color":"#FFFFFF"},
- "#aside_primary":{"background-color":"#CEE1E9"},
- "html body":{"color":"#000000"},
- "a":{"color":"#002E6E"}}';
+ $design = $user->getDesign();
- //Default theme swatch -- Where should this be stored?
- $defaultSwatch = array('body' => array('background-color' => '#F0F2F5'),
- '#content' => array('background-color' => '#FFFFFF'),
- '#aside_primary' => array('background-color' => '#CEE1E9'),
- 'html body' => array('color' => '#000000'),
- 'a' => array('color' => '#002E6E'));
-
- $userSwatch = ($userSwatch) ? json_decode($userSwatch, true) : $defaultSwatch;
+ if (empty($design)) {
+ $design = $this->defaultDesign();
+ }
- $s = 0;
$labelSwatch = array('Background',
'Content',
'Sidebar',
'Text',
'Links');
+
foreach($userSwatch as $propertyvalue => $value) {
$foo = array_values($value);
$this->elementStart('li');
'title' => _('Reset back to default')));
$this->submit('save', _('Save'), 'submit form_action-secondary', 'save', _('Save design'));
-/*TODO: Check submitted form values:
-json_encode(form values)
-if submitted Swatch == DefaultSwatch, don't store in DB.
-else store in BD
-*/
+ /*TODO: Check submitted form values:
+ json_encode(form values)
+ if submitted Swatch == DefaultSwatch, don't store in DB.
+ else store in BD
+ */
+
$this->elementEnd('fieldset');
$this->elementEnd('form');
-
}
/**
function handlePost()
{
- /*
- // CSRF protection
-
- $token = $this->trimmed('token');
- if (!$token || $token != common_session_token()) {
- $this->showForm(_('There was a problem with your session token. '.
- 'Try again, please.'));
- return;
- }
-
- $user = common_current_user();
- assert(!is_null($user)); // should already be checked
-
- // FIXME: scrub input
-
- $newpassword = $this->arg('newpassword');
- $confirm = $this->arg('confirm');
-
- # Some validation
-
- if (strlen($newpassword) < 6) {
- $this->showForm(_('Password must be 6 or more characters.'));
- return;
- } else if (0 != strcmp($newpassword, $confirm)) {
- $this->showForm(_('Passwords don\'t match.'));
- return;
- }
-
- if ($user->password) {
- $oldpassword = $this->arg('oldpassword');
-
- if (!common_check_user($user->nickname, $oldpassword)) {
- $this->showForm(_('Incorrect old password'));
- return;
- }
- }
-
- $original = clone($user);
-
- $user->password = common_munge_password($newpassword, $user->id);
-
- $val = $user->validate();
- if ($val !== true) {
- $this->showForm(_('Error saving user; invalid.'));
- return;
- }
-
- if (!$user->update($original)) {
- $this->serverError(_('Can\'t save new password.'));
- return;
- }
-
- $this->showForm(_('Password saved.'), true);
- */
+ // TODO: implement this
+ return;
}
-
/**
* Add the Farbtastic stylesheet
*
if (!defined('LACONICA')) { exit(1); }
-class InviteAction extends Action
+class InviteAction extends CurrentUserDesignAction
{
var $mode = null;
var $error = null;
* @link http://laconi.ca/
*/
-class RepliesAction extends Action
+class RepliesAction extends OwnerDesignAction
{
- var $user = null;
var $page = null;
/**
* @link http://laconi.ca/
*/
-class ShowfavoritesAction extends Action
+class ShowfavoritesAction extends OwnerDesignAction
{
- /** User we're getting the faves of */
- var $user = null;
/** Page of the faves we're on */
var $page = null;
$this->page, 'showfavorites', array('nickname' => $this->user->nickname));
}
-
/**
* show the personal group nav
*
function lastModified()
{
- return max(strtotime($this->notice->created),
+ return max(strtotime($this->notice->modified),
strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0);
}
$this->serverError(_('API method under construction.'), $code=501);
}
+ // We don't have a rate limit, but some clients check this method.
+ // It always returns the same thing: 100 hit left.
function rate_limit_status($args, $apidata)
{
parent::handle($args);
- $this->serverError(_('API method under construction.'), $code=501);
+
+ $type = $apidata['content-type'];
+ $this->init_document($type);
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->elementStart('hash');
+ $this->element('remaining-hits', array('type' => 'integer'), 100);
+ $this->element('hourly-limit', array('type' => 'integer'), 100);
+ $this->element('reset-time', array('type' => 'datetime'), null);
+ $this->element('reset_time_in_seconds', array('type' => 'integer'), 0);
+ $this->elementEnd('hash');
+ } elseif ($apidata['content-type'] == 'json') {
+
+ $out = array('reset_time_in_seconds' => 0,
+ 'remaining_hits' => 100,
+ 'hourly_limit' => 100,
+ 'reset_time' => '');
+ print json_encode($out);
+ }
+
+ $this->end_document($type);
}
}
$count = $this->arg('count');
$since = $this->arg('since');
$since_id = $this->arg('since_id');
- $before_id = $this->arg('before_id');
+ $max_id = $this->arg('max_id');
$page = $this->arg('page');
$link = $server . $user->nickname . '/outbox';
}
- if ($before_id) {
- $message->whereAdd("id < $before_id");
+ if ($max_id) {
+ $message->whereAdd("id <= $max_id");
}
if ($since_id) {
$page = $this->arg('page');
$since_id = $this->arg('since_id');
- $before_id = $this->arg('before_id');
+ $max_id = $this->arg('max_id');
- // NOTE: page, since_id, and before_id are extensions to Twitter API -- TB
if (!$page) {
$page = 1;
}
if (!$since_id) {
$since_id = 0;
}
- if (!$before_id) {
- $before_id = 0;
+ if (!$max_id) {
+ $max_id = 0;
}
$since = strtotime($this->arg('since'));
- $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since);
+ $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $max_id, $since);
if ($notice) {
$since_id = $this->arg('since_id');
$count = $this->arg('count');
$page = $this->arg('page');
- $before_id = $this->arg('before_id');
+ $max_id = $this->arg('max_id');
if (!$page) {
$page = 1;
$since_id = 0;
}
- // NOTE: before_id is an extension to Twitter API -- TB
- if (!$before_id) {
- $before_id = 0;
+ if (!$max_id) {
+ $max_id = 0;
}
$since = strtotime($this->arg('since'));
$link = common_local_url('all', array('nickname' => $user->nickname));
$subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename);
- $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $before_id, $since);
+ $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $max_id, $since);
switch($apidata['content-type']) {
case 'xml':
$since = $this->arg('since');
$since_id = $this->arg('since_id');
$page = $this->arg('page');
- $before_id = $this->arg('before_id');
+ $max_id = $this->arg('max_id');
if (!$page) {
$page = 1;
$since_id = 0;
}
- // NOTE: before_id is an extensions to Twitter API -- TB
- if (!$before_id) {
- $before_id = 0;
+ if (!$max_id) {
+ $max_id = 0;
}
$since = strtotime($this->arg('since'));
# XXX: since
- $notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since);
+ $notice = $user->getNotices((($page-1)*20), $count, $since_id, $max_id, $since);
switch($apidata['content-type']) {
case 'xml':
$count = $this->arg('count');
$page = $this->arg('page');
$since_id = $this->arg('since_id');
- $before_id = $this->arg('before_id');
+ $max_id = $this->arg('max_id');
$user = $this->get_user($apidata['api_arg'], $apidata);
$this->auth_user = $apidata['user'];
$since_id = 0;
}
- // NOTE: before_id is an extension to Twitter API -- TB
- if (!$before_id) {
- $before_id = 0;
+ if (!$max_id) {
+ $max_id = 0;
}
$since = strtotime($this->arg('since'));
$notice = $user->getReplies((($page-1)*20),
- $count, $since_id, $before_id, $since);
+ $count, $since_id, $max_id, $since);
$notices = array();
while ($notice->fetch()) {
{
function show($args, $apidata)
- {
+ {
parent::handle($args);
- if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
$this->clientError(_('API method not found!'), $code = 404);
return;
}
-
- $user = null;
- $email = $this->arg('email');
- $user_id = $this->arg('user_id');
-
- 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'];
- }
-
- if (!$user) {
- // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
- $this->client_error(_('Not found.'), 404, $apidata['content-type']);
- return;
- }
-
- $profile = $user->getProfile();
-
- if (!$profile) {
- common_server_error(_('User has no profile.'));
- return;
- }
-
- $twitter_user = $this->twitter_user_array($profile, true);
-
- // Add in extended user fields offered up by this method
- $twitter_user['created_at'] = $this->date_twitter($profile->created);
-
- $subbed = DB_DataObject::factory('subscription');
- $subbed->subscriber = $profile->id;
- $subbed_count = (int) $subbed->count() - 1;
-
- $notices = DB_DataObject::factory('notice');
- $notices->profile_id = $profile->id;
- $notice_count = (int) $notices->count();
-
- $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
- $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
-
- // Other fields Twitter sends...
- $twitter_user['profile_background_color'] = '';
- $twitter_user['profile_background_image_url'] = '';
- $twitter_user['profile_text_color'] = '';
- $twitter_user['profile_link_color'] = '';
- $twitter_user['profile_sidebar_fill_color'] = '';
- $twitter_user['profile_sidebar_border_color'] = '';
- $twitter_user['profile_background_tile'] = false;
-
- $faves = DB_DataObject::factory('fave');
- $faves->user_id = $user->id;
- $faves_count = (int) $faves->count();
- $twitter_user['favourites_count'] = $faves_count;
-
- $timezone = 'UTC';
-
- if ($user->timezone) {
- $timezone = $user->timezone;
- }
-
- $t = new DateTime;
- $t->setTimezone(new DateTimeZone($timezone));
- $twitter_user['utc_offset'] = $t->format('Z');
- $twitter_user['time_zone'] = $timezone;
-
- if (isset($apidata['user'])) {
-
- $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
-
- // Notifications on?
- $sub = Subscription::pkeyGet(array('subscriber' =>
- $apidata['user']->id, 'subscribed' => $profile->id));
-
- if ($sub) {
- $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
- }
- }
-
- if ($apidata['content-type'] == 'xml') {
- $this->init_document('xml');
- $this->show_twitter_xml_user($twitter_user);
- $this->end_document('xml');
- } elseif ($apidata['content-type'] == 'json') {
- $this->init_document('json');
- $this->show_json_objects($twitter_user);
- $this->end_document('json');
- } else {
-
- // This is in case 'show' was called via /account/verify_credentials
- // without a format (xml or json).
+
+ $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'];
+ }
+
+ if (!$user) {
+ $this->client_error(_('Not found.'), 404, $apidata['content-type']);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $twitter_user = $this->twitter_user_array($profile, true);
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->init_document('xml');
+ $this->show_twitter_xml_user($twitter_user);
+ $this->end_document('xml');
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->init_document('json');
+ $this->show_json_objects($twitter_user);
+ $this->end_document('json');
+ } else {
+
+ // This is in case 'show' was called via /account/verify_credentials
+ // without a format (xml or json).
header('Content-Type: text/html; charset=utf-8');
print 'Authorized';
}
- }
+ }
}
* @link http://laconi.ca/
*/
-class UsergroupsAction extends Action
+class UsergroupsAction extends OwnerDesignAction
{
- var $user = null;
var $page = null;
var $profile = null;
--- /dev/null
+<?php
+/*
+ * Laconica - the distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for design
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Design extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'design'; // table name
+ public $id; // int(4) primary_key not_null
+ public $backgroundcolor; // int(4)
+ public $contentcolor; // int(4)
+ public $sidebarcolor; // int(4)
+ public $textcolor; // int(4)
+ public $linkcolor; // int(4)
+ public $backgroundimage; // varchar(255)
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Design',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function showCSS($out)
+ {
+ $out->element('stylesheet', array('type' => 'text/css'),
+ 'body { background-color: #' . dechex($this->backgroundcolor) . '} '."\n".
+ '#content { background-color #' . dechex($this->contentcolor) . '} '."\n".
+ '#aside_primary { background-color #'. dechex($this->sidebarcolor) .'} '."\n".
+ 'html body { color: #'. dechex($this->textcolor) .'} '."\n".
+ 'a { color: #' . dechex($this->linkcolor) . '} '."\n");
+ }
+}
return $ids;
}
- function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since)
+ function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
{
$fav = new Fave();
$fav->whereAdd('notice_id > ' . $since_id);
}
- if ($before_id != 0) {
- $fav->whereAdd('notice_id < ' . $before_id);
+ if ($max_id != 0) {
+ $fav->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {
$result = parent::_connect();
if (!$exists) {
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
- if (common_config('db', 'utf8')) {
- $DB->query('SET NAMES "utf8"');
+ if (common_config('db', 'type') == 'mysql' &&
+ common_config('db', 'utf8')) {
+ $conn = $DB->connection;
+ if ($DB instanceof DB_mysqli) {
+ mysqli_set_charset($conn, 'utf8');
+ } else if ($DB instanceof DB_mysql) {
+ mysql_set_charset('utf8', $conn);
+ }
}
}
return $result;
$profile = Profile::staticGet($profile_id);
+ $final = common_shorten_links($content);
+
if (!$profile) {
common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
return _('Problem saving notice. Unknown user.');
return _('Too many notices too fast; take a breather and post again in a few minutes.');
}
- if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $content)) {
+ if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
}
$notice->reply_to = $reply_to;
$notice->created = common_sql_now();
- $notice->content = $content;
- $notice->rendered = common_render_content($content, $notice);
+ $notice->content = $final;
+ $notice->rendered = common_render_content($final, $notice);
$notice->source = $source;
$notice->uri = $uri;
$notice->saveReplies();
$notice->saveTags();
- $notice->saveGroups();
- if (common_config('queue', 'enabled')) {
- $notice->addToAuthorInbox();
- } else {
- $notice->addToInboxes();
- }
+ $notice->addToInboxes();
+ $notice->saveGroups();
$notice->query('COMMIT');
# Clear the cache for subscribed users, so they'll update at next request
# XXX: someone clever could prepend instead of clearing the cache
- if (common_config('memcached', 'enabled')) {
- if (common_config('queue', 'enabled')) {
- $notice->blowAuthorCaches();
- } else {
- $notice->blowCaches();
- }
- }
+ $notice->blowCaches();
return $notice;
}
$this->blowGroupCache($blowLast);
}
- function blowAuthorCaches($blowLast=false)
- {
- // Clear the user's cache
- $cache = common_memcache();
- if (!empty($cache)) {
- $cache->delete(common_cache_key('notice_inbox:by_user:'.$this->profile_id));
- }
- $this->blowNoticeCache($blowLast);
- $this->blowPublicCache($blowLast);
- }
-
function blowGroupCache($blowLast=false)
{
$cache = common_memcache();
# XXX: too many args; we need to move to named params or even a separate
# class for notice streams
- static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) {
+ static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
if (common_config('memcached', 'enabled')) {
- # Skip the cache if this is a since, since_id or before_id qry
- if ($since_id > 0 || $before_id > 0 || $since) {
- return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+ # Skip the cache if this is a since, since_id or max_id qry
+ if ($since_id > 0 || $max_id > 0 || $since) {
+ return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} else {
return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
}
}
- return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+ return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
}
- static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) {
+ static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
$needAnd = false;
$needWhere = true;
$qry .= ' notice.id > ' . $since_id;
}
- if ($before_id > 0) {
+ if ($max_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
$qry .= ' AND ';
}
- $qry .= ' notice.id < ' . $before_id;
+ $qry .= ' notice.id <= ' . $max_id;
}
if ($since) {
}
}
- function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+ function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{
$ids = Notice::stream(array('Notice', '_publicStreamDirect'),
array(),
'public',
- $offset, $limit, $since_id, $before_id, $since);
+ $offset, $limit, $since_id, $max_id, $since);
return Notice::getStreamByIds($ids);
}
- function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+ function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{
$notice = new Notice();
$notice->whereAdd('id > ' . $since_id);
}
- if ($before_id != 0) {
- $notice->whereAdd('id < ' . $before_id);
+ if ($max_id != 0) {
+ $notice->whereAdd('id <= ' . $max_id);
}
if (!is_null($since)) {
return;
}
- function addToAuthorInbox()
- {
- $enabled = common_config('inboxes', 'enabled');
-
- if ($enabled === true || $enabled === 'transitional') {
- $user = User::staticGet('id', $this->profile_id);
- if (empty($user)) {
- return;
- }
- $inbox = new Notice_inbox();
- $UT = common_config('db','type')=='pgsql'?'"user"':'user';
- $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
- "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " .
- "FROM $UT " .
- "WHERE $UT.id = " . $this->profile_id . ' ' .
- 'AND NOT EXISTS (SELECT user_id, notice_id ' .
- 'FROM notice_inbox ' .
- "WHERE user_id = " . $this->profile_id . ' '.
- 'AND notice_id = ' . $this->id . ' )';
- if ($enabled === 'transitional') {
- $qry .= " AND $UT.inboxed = 1";
- }
- $inbox->query($qry);
- }
- return;
- }
-
function saveGroups()
{
$enabled = common_config('inboxes', 'enabled');
}
}
- function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null, $tag=null)
+ function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{
$cache = common_memcache();
if (empty($cache) ||
- $since_id != 0 || $before_id != 0 || !is_null($since) ||
+ $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
- $before_id, $since, $tag)));
+ $max_id, $since)));
}
$idkey = common_cache_key($cachekey);
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function stream($user_id, $offset, $limit, $since_id, $before_id, $since)
+ function stream($user_id, $offset, $limit, $since_id, $max_id, $since)
{
return Notice::stream(array('Notice_inbox', '_streamDirect'),
array($user_id),
'notice_inbox:by_user:'.$user_id,
- $offset, $limit, $since_id, $before_id, $since);
+ $offset, $limit, $since_id, $max_id, $since);
}
- function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since)
+ function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
{
$inbox = new Notice_inbox();
$inbox->whereAdd('notice_id > ' . $since_id);
}
- if ($before_id != 0) {
- $inbox->whereAdd('notice_id < ' . $before_id);
+ if ($max_id != 0) {
+ $inbox->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {
return Notice::getStreamByIds($ids);
}
- function _streamDirect($tag, $offset, $limit, $since_id, $before_id, $since)
+ function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since)
{
$nt = new Notice_tag();
$nt->whereAdd('notice_id > ' . $since_id);
}
- if ($before_id != 0) {
- $nt->whereAdd('notice_id < ' . $before_id);
+ if ($max_id != 0) {
+ $nt->whereAdd('notice_id < ' . $max_id);
}
if (!is_null($since)) {
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
- $offset, $limit, $since_id, $before_id, $since);
+ $offset, $limit, $since_id, $max_id, $since);
return Notice::getStreamByIds($ids);
}
$notice->whereAdd('id > ' . $since_id);
}
- if ($before_id != 0) {
- $notice->whereAdd('id < ' . $before_id);
+ if ($max_id != 0) {
+ $notice->whereAdd('id <= ' . $max_id);
}
if (!is_null($since)) {
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+ function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
{
$ids = Notice::stream(array('Reply', '_streamDirect'),
array($user_id),
'reply:stream:' . $user_id,
- $offset, $limit, $since_id, $before_id, $since);
+ $offset, $limit, $since_id, $max_id, $since);
return $ids;
}
- function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+ function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
{
$reply = new Reply();
$reply->profile_id = $user_id;
$reply->whereAdd('notice_id > ' . $since_id);
}
- if ($before_id != 0) {
- $reply->whereAdd('notice_id < ' . $before_id);
+ if ($max_id != 0) {
+ $reply->whereAdd('notice_id < ' . $max_id);
}
if (!is_null($since)) {
public $autosubscribe; // tinyint(1)
public $urlshorteningservice; // varchar(50) default_ur1.ca
public $inboxed; // tinyint(1)
+ public $design_id; // int(4)
+ public $viewdesigns; // tinyint(1) default_1
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
- function staticGet($k,$v=NULL)
- {
- return Memcached_DataObject::staticGet('User',$k,$v);
- }
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
- common_debug("Ids = " . implode(',', $ids));
return Notice::getStreamByIds($ids);
}
return ($cnt > 0);
}
+
+ function getDesign()
+ {
+ return Design::staticGet('id', $this->design_id);
+ }
}
return Notice::getStreamByIds($ids);
}
- function _streamDirect($offset, $limit, $since_id, $before_id, $since)
+ function _streamDirect($offset, $limit, $since_id, $max_id, $since)
{
$inbox = new Group_inbox();
$inbox->whereAdd('notice_id > ' . $since_id);
}
- if ($before_id != 0) {
- $inbox->whereAdd('notice_id < ' . $before_id);
+ if ($max_id != 0) {
+ $inbox->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {
+
[avatar]
profile_id = 129
original = 17
[consumer__keys]
consumer_key = K
+[design]
+id = 129
+backgroundcolor = 1
+contentcolor = 1
+sidebarcolor = 1
+textcolor = 1
+linkcolor = 1
+backgroundimage = 2
+
+[design__keys]
+id = N
+
[fave]
notice_id = 129
user_id = 129
autosubscribe = 17
urlshorteningservice = 2
inboxed = 17
+design_id = 1
+viewdesigns = 17
created = 142
modified = 384
/* local users */
create table user (
+
id integer primary key comment 'foreign key to profile table' references profile (id),
nickname varchar(64) unique key comment 'nickname or username, duped in profile',
password varchar(255) comment 'salted password, can be null for OpenID users',
autosubscribe tinyint default 0 comment 'automatically subscribe to users who subscribe to us',
urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs',
inboxed tinyint default 0 comment 'has an inbox been created for this user?',
+ design_id integer comment 'id of a design' references design(id),
+ viewdesigns tinyint default 1 comment 'whether to view user-provided designs',
+
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
unique(file_id, post_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table design (
+ id integer primary key auto_increment 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'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
(code, name, url, created)
VALUES
('adium', 'Adium', 'http://www.adiumx.com/', now()),
+ ('Afficheur', 'Afficheur', 'http://afficheur.sourceforge.jp/', now()),
('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()),
+ ('anyio', 'Any.IO', 'http://any.io/', now()),
('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()),
('bti','bti','http://gregkh.github.com/bti/', now()),
('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
('Facebook','Facebook','http://apps.facebook.com/identica/', now()),
('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()),
+ ('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()),
('gravity', 'Gravity', 'http://mobileways.de/gravity', now()),
('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
Event::handle('EndShowUAStyles', array($this));
}
+ if (Event::handle('StartShowDesign', array($this))) {
+ $design = $this->getDesign();
+ if (!empty($design)) {
+ $cur = common_current_user();
+ if (empty($cur) || $cur->viewdesigns) {
+ $design->showCSS($this);
+ }
+ }
+ Event::handle('EndShowDesign', array($this, $design));
+ }
Event::handle('EndShowStyles', array($this));
}
}
'src' => common_path('js/jquery.joverlay.min.js')),
' ');
-
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
'title' => _('Previous')));
}
}
+
+ /**
+ * A design for this action
+ *
+ * A design (colors and background) for the current page. May be
+ * the user's design, or a group's design, or a site design.
+ *
+ * @return Design a design object to use
+ */
+
+ function getDesign()
+ {
+ // XXX: return site design by default
+ return null;
+ }
}
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
* @see Notice
- * @see StreamAction
* @see NoticeListItem
* @see ProfileNoticeList
*/
--- /dev/null
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for actions that use the current user's design
+ *
+ * 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 Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Base class for actions that use the current user's design
+ *
+ * Some pages (settings in particular) use the current user's chosen
+ * design. This superclass returns that design.
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+
+class CurrentUserDesignAction extends Action
+{
+ /**
+ * A design for this action
+ *
+ * if the user attribute has been set, returns that user's
+ * design.
+ *
+ * @return Design a design object to use
+ */
+
+ function getDesign()
+ {
+ $cur = common_current_user();
+
+ if (empty($cur)) {
+ return null;
+ }
+
+ return $cur->getDesign();
+ }
+}
define('AVATARS_PER_PAGE', 80);
-class GalleryAction extends Action
+class GalleryAction extends OwnerDesignAction
{
var $profile = null;
- var $user = null;
var $page = null;
var $tag = null;
"FROM $UT JOIN subscription " .
"ON $UT.id = subscription.subscriber " .
'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+ 'AND subscription.subscribed != subscription.subscriber ' .
"AND $UT.smsemail IS NOT null " .
"AND $UT.smsnotify = 1 " .
'AND subscription.sms = 1 ');
* @see OutboxAction
*/
-class MailboxAction extends PersonalAction
+class MailboxAction extends CurrentUserDesignAction
{
var $page = null;
- function prepare($args)
+ function prepare($args)
{
parent::prepare($args);
* Returns either the name (and link) of the API client that posted the notice,
* or one of other other channels.
*
- * @param string $source the source of the message
+ * @param string $source the source of the message
*
* @return void
*/
- function showSource($source)
+ function showSource($source)
{
$source_name = _($source);
switch ($source) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
* @see Notice
- * @see StreamAction
* @see NoticeListItem
* @see ProfileNoticeList
*/
function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
{
-
- common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
-
$user = User::staticGet('id', $notice->profile_id);
if (!$user) {
- common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
return false;
}
$req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION));
- common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
-
if ($result->status == 403) { # not authorized, don't send again
common_debug('403 result, deleting subscription', __FILE__);
# FIXME: figure out how to delete this
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
- common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
- common_debug('postdata = '.$req->to_postdata(), __FILE__);
$result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION));
- common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
-
if (empty($result) || !$result) {
common_debug("Unable to contact " . $req->get_normalized_http_url());
} else if ($result->status == 403) { # not authorized, don't send again
--- /dev/null
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for actions that use the page owner's design
+ *
+ * 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 Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Base class for actions that use the page owner's design
+ *
+ * Some pages have a clear "owner" -- like the profile page, subscriptions
+ * pages, etc. This superclass uses that owner's chosen design for the page
+ * design.
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+
+class OwnerDesignAction extends Action {
+
+ /** The user for this page. */
+
+ var $user = null;
+
+ /**
+ * A design for this action
+ *
+ * if the user attribute has been set, returns that user's
+ * design.
+ *
+ * @return Design a design object to use
+ */
+
+ function getDesign()
+ {
+ if (empty($this->user)) {
+ return null;
+ }
+
+ return $this->user->getDesign();
+ }
+}
+++ /dev/null
-<?php
-/**
- * Laconica, the distributed open-source microblogging tool
- *
- * User profile page
- *
- * 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 Personal
- * @package Laconica
- * @author Evan Prodromou <evan@controlyourself.ca>
- * @author Sarven Capadisli <csarven@controlyourself.ca>
- * @copyright 2008-2009 Control Yourself, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Base class for user profile page
- *
- * @category Personal
- * @package Laconica
- * @author Evan Prodromou <evan@controlyourself.ca>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://laconi.ca/
- */
-
-class PersonalAction extends Action
-{
-
- var $user = null;
-
- function isReadOnly($args)
- {
- return true;
- }
-
- function handle($args)
- {
- parent::handle($args);
- }
-
-}
* @link http://laconi.ca/
*/
-class ProfileAction extends Action
+class ProfileAction extends OwnerDesignAction
{
- var $user = null;
var $page = null;
var $profile = null;
var $tag = null;
* @see Widget
*/
-class SettingsAction extends Action
+class SettingsAction extends CurrentUserDesignAction
{
/**
* A message for the user.
+++ /dev/null
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, 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/personal.php');
-require_once(INSTALLDIR.'/lib/noticelist.php');
-
-class StreamAction extends PersonalAction
-{
- function show_notice_list($notice)
- {
- $nl = new NoticeList($notice);
- return $nl->show();
- }
-}
/**
* Overrides XMLOutputter::element to write booleans as strings (true|false).
* See that method's documentation for more info.
- *
+ *
* @param string $tag Element type or tagname
* @param array $attrs Array of element attributes, as
* key-value pairs
return parent::element($tag, $attrs, $content);
}
-
+
function twitter_user_array($profile, $get_notice=false)
{
-
$twitter_user = array();
+ $twitter_user['id'] = intval($profile->id);
$twitter_user['name'] = $profile->getBestName();
- $twitter_user['followers_count'] = $this->count_subscriptions($profile);
$twitter_user['screen_name'] = $profile->nickname;
- $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
$twitter_user['location'] = ($profile->location) ? $profile->location : null;
- $twitter_user['id'] = intval($profile->id);
+ $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+ $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
+ Avatar::defaultImage(AVATAR_STREAM_SIZE);
- $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
- $twitter_user['protected'] = false; # not supported by Laconica yet
$twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
+ $twitter_user['protected'] = false; # not supported by Laconica yet
+ $twitter_user['followers_count'] = $this->count_subscriptions($profile);
+
+ // To be supported soon...
+ $twitter_user['profile_background_color'] = '';
+ $twitter_user['profile_text_color'] = '';
+ $twitter_user['profile_link_color'] = '';
+ $twitter_user['profile_sidebar_fill_color'] = '';
+ $twitter_user['profile_sidebar_border_color'] = '';
+
+ $subbed = DB_DataObject::factory('subscription');
+ $subbed->subscriber = $profile->id;
+ $subbed_count = (int) $subbed->count() - 1;
+ $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
+
+ $twitter_user['created_at'] = $this->date_twitter($profile->created);
+
+ $faves = DB_DataObject::factory('fave');
+ $faves->user_id = $user->id;
+ $faves_count = (int) $faves->count();
+ $twitter_user['favourites_count'] = $faves_count; // British spelling!
+
+ // Need to pull up the user for some of this
+ $user = User::staticGet($profile->id);
+
+ $timezone = 'UTC';
+
+ if ($user->timezone) {
+ $timezone = $user->timezone;
+ }
+
+ $t = new DateTime;
+ $t->setTimezone(new DateTimeZone($timezone));
+
+ $twitter_user['utc_offset'] = $t->format('Z');
+ $twitter_user['time_zone'] = $timezone;
+
+ // To be supported some day, perhaps
+ $twitter_user['profile_background_image_url'] = '';
+ $twitter_user['profile_background_tile'] = false;
+
+ $notices = DB_DataObject::factory('notice');
+ $notices->profile_id = $profile->id;
+ $notice_count = (int) $notices->count();
+
+ $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
+
+ // Is the requesting user following this user?
+ $twitter_user['following'] = false;
+ $twitter_user['notifications'] = false;
+
+ if (isset($apidata['user'])) {
+
+ $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
+
+ // Notifications on?
+ $sub = Subscription::pkeyGet(array('subscriber' =>
+ $apidata['user']->id, 'subscribed' => $profile->id));
+
+ if ($sub) {
+ $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
+ }
+ }
if ($get_notice) {
$notice = $profile->getCurrentNotice();
function get_user($id, $apidata=null)
{
if (!$id) {
- return $apidata['user'];
+
+ // Twitter supports these other ways of passing the user ID
+ if (is_numeric($this->arg('id'))) {
+ return User::staticGet($this->arg('id'));
+ } else if ($this->arg('id')) {
+ $nickname = common_canonical_nickname($this->arg('id'));
+ return User::staticGet('nickname', $nickname);
+ } else if ($this->arg('user_id')) {
+ // This is to ensure that a non-numeric user_id still
+ // overrides screen_name even if it doesn't get used
+ if (is_numeric($this->arg('user_id'))) {
+ return User::staticGet('id', $this->arg('user_id'));
+ }
+ } else if ($this->arg('screen_name')) {
+ $nickname = common_canonical_nickname($this->arg('screen_name'));
+ return User::staticGet('nickname', $nickname);
+ } else {
+ // Fall back to trying the currently authenticated user
+ return $apidata['user'];
+ }
+
} else if (is_numeric($id)) {
return User::staticGet($id);
} else {
return $result;
}
+function common_post_inbox_transports()
+{
+ $transports = array('omb', 'sms');
+
+ if (common_config('xmpp', 'enabled')) {
+ $transports = array_merge($transports, array('jabber', 'public'));
+ }
+
+ return $transports;
+}
+
+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);
+>>>>>>> 0.7.x:lib/util.php
+ }
+ common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
+ return true;
+}
+
function common_real_broadcast($notice, $remote=false)
{
$success = true;
require_once(INSTALLDIR . '/lib/common.php');
require_once('DB.php');
-function fixup_utf8($id) {
+class UTF8FixerUpper
+{
+ var $dbl = null;
+ var $dbu = null;
+ var $args = array();
+
+ function __construct($args)
+ {
+ $this->args = $args;
+
+ if (array_key_exists('max_date', $args)) {
+ $this->max_date = strftime('%Y-%m-%d %H:%M:%S', strtotime($args['max_date']));
+ } else {
+ $this->max_date = strftime('%Y-%m-%d %H:%M:%S', time());
+ }
- $dbl = doConnect('latin1');
+ $this->dbl = $this->doConnect('latin1');
- if (empty($dbl)) {
- return;
- }
+ if (empty($this->dbl)) {
+ return;
+ }
- $dbu = doConnect('utf8');
+ $this->dbu = $this->doConnect('utf8');
- if (empty($dbu)) {
- return;
+ if (empty($this->dbu)) {
+ return;
+ }
}
- // Do a separate DB connection
+ function doConnect($charset)
+ {
+ $db = DB::connect(common_config('db', 'database'),
+ array('persistent' => false));
- $sth = $dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
+ if (PEAR::isError($db)) {
+ echo "ERROR: " . $db->getMessage() . "\n";
+ return NULL;
+ }
- if (PEAR::isError($sth)) {
- echo "ERROR: " . $sth->getMessage() . "\n";
- return;
- }
+ $conn = $db->connection;
- $sql = 'SELECT id, content, rendered FROM notice ' .
- 'WHERE LENGTH(content) != CHAR_LENGTH(content)';
+ $succ = mysqli_set_charset($conn, $charset);
- if (!empty($id)) {
- $sql .= ' AND id < ' . $id;
- }
+ if (!$succ) {
+ echo "ERROR: couldn't set charset\n";
+ $db->disconnect();
+ return NULL;
+ }
- $sql .= ' ORDER BY id DESC';
+ $result = $db->autoCommit(true);
- $rn = $dbl->query($sql);
+ if (PEAR::isError($result)) {
+ echo "ERROR: " . $result->getMessage() . "\n";
+ $db->disconnect();
+ return NULL;
+ }
- if (PEAR::isError($rn)) {
- echo "ERROR: " . $rn->getMessage() . "\n";
- return;
+ return $db;
}
- echo "Number of rows: " . $rn->numRows() . "\n";
+ function fixup()
+ {
+ $this->fixupNotices($this->args['max_notice'],
+ $this->args['min_notice']);
+ $this->fixupProfiles();
+ $this->fixupGroups();
+ $this->fixupMessages();
+ }
- $notice = array();
+ function fixupNotices($max_id, $min_id) {
- while (DB_OK == $rn->fetchInto($notice)) {
+ // Do a separate DB connection
- $id = ($notice[0])+0;
- $content = bin2hex($notice[1]);
- $rendered = bin2hex($notice[2]);
+ $sth = $this->dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
- echo "$id...";
+ if (PEAR::isError($sth)) {
+ echo "ERROR: " . $sth->getMessage() . "\n";
+ return;
+ }
- $result =& $dbu->execute($sth, array($content, $rendered, $id));
+ $sql = 'SELECT id, content, rendered FROM notice ' .
+ 'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
+ 'AND modified < "'.$this->max_date.'" ';
- if (PEAR::isError($result)) {
- echo "ERROR: " . $result->getMessage() . "\n";
- continue;
+ if (!empty($max_id)) {
+ $sql .= ' AND id <= ' . $max_id;
+ }
+
+ if (!empty($min_id)) {
+ $sql .= ' AND id >= ' . $min_id;
}
- $cnt = $dbu->affectedRows();
+ $sql .= ' ORDER BY id DESC';
- if ($cnt != 1) {
- echo "ERROR: 0 rows affected\n";
- continue;
+ $rn = $this->dbl->query($sql);
+
+ if (PEAR::isError($rn)) {
+ echo "ERROR: " . $rn->getMessage() . "\n";
+ return;
}
- $notice = Notice::staticGet('id', $id);
- $notice->decache();
+ echo "Number of rows: " . $rn->numRows() . "\n";
- echo "OK\n";
- }
-}
+ $notice = array();
-function doConnect($charset)
-{
- $db = DB::connect(common_config('db', 'database'),
- array('persistent' => false));
+ while (DB_OK == $rn->fetchInto($notice)) {
+
+ $id = ($notice[0])+0;
+ $content = bin2hex($notice[1]);
+ $rendered = bin2hex($notice[2]);
- if (PEAR::isError($db)) {
- echo "ERROR: " . $db->getMessage() . "\n";
- return NULL;
+ echo "$id...";
+
+ $result =& $this->dbu->execute($sth, array($content, $rendered, $id));
+
+ if (PEAR::isError($result)) {
+ echo "ERROR: " . $result->getMessage() . "\n";
+ continue;
+ }
+
+ $cnt = $this->dbu->affectedRows();
+
+ if ($cnt != 1) {
+ echo "ERROR: 0 rows affected\n";
+ continue;
+ }
+
+ $notice = Notice::staticGet('id', $id);
+ $notice->decache();
+ $notice->free();
+
+ echo "OK\n";
+ }
}
- $result = $db->query("SET NAMES $charset");
+ function fixupProfiles()
+ {
+ // Do a separate DB connection
+
+ $sth = $this->dbu->prepare("UPDATE profile SET ".
+ "fullname = UNHEX(?),".
+ "location = UNHEX(?), ".
+ "bio = UNHEX(?) ".
+ "WHERE id = ?");
+
+ if (PEAR::isError($sth)) {
+ echo "ERROR: " . $sth->getMessage() . "\n";
+ return;
+ }
+
+ $sql = 'SELECT id, fullname, location, bio FROM profile ' .
+ 'WHERE (LENGTH(fullname) != CHAR_LENGTH(fullname) '.
+ 'OR LENGTH(location) != CHAR_LENGTH(location) '.
+ 'OR LENGTH(bio) != CHAR_LENGTH(bio)) '.
+ 'AND modified < "'.$this->max_date.'" '.
+ ' ORDER BY modified DESC';
+
+ $rn = $this->dbl->query($sql);
+
+ if (PEAR::isError($rn)) {
+ echo "ERROR: " . $rn->getMessage() . "\n";
+ return;
+ }
+
+ echo "Number of rows: " . $rn->numRows() . "\n";
+
+ $profile = array();
+
+ while (DB_OK == $rn->fetchInto($profile)) {
+
+ $id = ($profile[0])+0;
+ $fullname = bin2hex($profile[1]);
+ $location = bin2hex($profile[2]);
+ $bio = bin2hex($profile[3]);
+
+ echo "$id...";
+
+ $result =& $this->dbu->execute($sth, array($fullname, $location, $bio, $id));
+
+ if (PEAR::isError($result)) {
+ echo "ERROR: " . $result->getMessage() . "\n";
+ continue;
+ }
+
+ $cnt = $this->dbu->affectedRows();
- if (PEAR::isError($result)) {
- echo "ERROR: " . $result->getMessage() . "\n";
- $db->disconnect();
- return NULL;
+ if ($cnt != 1) {
+ echo "ERROR: 0 rows affected\n";
+ continue;
+ }
+
+ $profile = Profile::staticGet('id', $id);
+ $profile->decache();
+ $profile->free();
+
+ echo "OK\n";
+ }
}
- $result = $db->autoCommit(true);
+ function fixupGroups()
+ {
+ // Do a separate DB connection
+
+ $sth = $this->dbu->prepare("UPDATE user_group SET ".
+ "fullname = UNHEX(?),".
+ "location = UNHEX(?), ".
+ "description = UNHEX(?) ".
+ "WHERE id = ?");
+
+ if (PEAR::isError($sth)) {
+ echo "ERROR: " . $sth->getMessage() . "\n";
+ return;
+ }
+
+ $sql = 'SELECT id, fullname, location, description FROM user_group ' .
+ 'WHERE LENGTH(fullname) != CHAR_LENGTH(fullname) '.
+ 'OR LENGTH(location) != CHAR_LENGTH(location) '.
+ 'OR LENGTH(description) != CHAR_LENGTH(description) ';
+ 'AND modified < "'.$this->max_date.'" '.
+ 'ORDER BY modified DESC';
+
+ $rn = $this->dbl->query($sql);
+
+ if (PEAR::isError($rn)) {
+ echo "ERROR: " . $rn->getMessage() . "\n";
+ return;
+ }
+
+ echo "Number of rows: " . $rn->numRows() . "\n";
- if (PEAR::isError($result)) {
- echo "ERROR: " . $result->getMessage() . "\n";
- $db->disconnect();
- return NULL;
+ $user_group = array();
+
+ while (DB_OK == $rn->fetchInto($user_group)) {
+
+ $id = ($user_group[0])+0;
+ $fullname = bin2hex($user_group[1]);
+ $location = bin2hex($user_group[2]);
+ $description = bin2hex($user_group[3]);
+
+ echo "$id...";
+
+ $result =& $this->dbu->execute($sth, array($fullname, $location, $description, $id));
+
+ if (PEAR::isError($result)) {
+ echo "ERROR: " . $result->getMessage() . "\n";
+ continue;
+ }
+
+ $cnt = $this->dbu->affectedRows();
+
+ if ($cnt != 1) {
+ echo "ERROR: 0 rows affected\n";
+ continue;
+ }
+
+ $user_group = User_group::staticGet('id', $id);
+ $user_group->decache();
+ $user_group->free();
+
+ echo "OK\n";
+ }
}
- return $db;
+ function fixupMessages() {
+
+ // Do a separate DB connection
+
+ $sth = $this->dbu->prepare("UPDATE message SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
+
+ if (PEAR::isError($sth)) {
+ echo "ERROR: " . $sth->getMessage() . "\n";
+ return;
+ }
+
+ $sql = 'SELECT id, content, rendered FROM message ' .
+ 'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
+ 'AND modified < "'.$this->max_date.'" '.
+ 'ORDER BY id DESC';
+
+ $rn = $this->dbl->query($sql);
+
+ if (PEAR::isError($rn)) {
+ echo "ERROR: " . $rn->getMessage() . "\n";
+ return;
+ }
+
+ echo "Number of rows: " . $rn->numRows() . "\n";
+
+ $message = array();
+
+ while (DB_OK == $rn->fetchInto($message)) {
+
+ $id = ($message[0])+0;
+ $content = bin2hex($message[1]);
+ $rendered = bin2hex($message[2]);
+
+ echo "$id...";
+
+ $result =& $this->dbu->execute($sth, array($content, $rendered, $id));
+
+ if (PEAR::isError($result)) {
+ echo "ERROR: " . $result->getMessage() . "\n";
+ continue;
+ }
+
+ $cnt = $this->dbu->affectedRows();
+
+ if ($cnt != 1) {
+ echo "ERROR: 0 rows affected\n";
+ continue;
+ }
+
+ $message = Message::staticGet('id', $id);
+ $message->decache();
+ $message->free();
+
+ echo "OK\n";
+ }
+ }
}
-$id = ($argc > 1) ? $argv[1] : null;
+$max_date = ($argc > 1) ? $argv[1] : null;
+$max_id = ($argc > 2) ? $argv[2] : null;
+$min_id = ($argc > 3) ? $argv[3] : null;
+
+$fixer = new UTF8FixerUpper(array('max_date' => $max_date,
+ 'max_notice' => $max_id,
+ 'min_notice' => $min_id));
+
+$fixer->fixup();
-fixup_utf8($id);
* daemon names.
*/
-
# 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";
echo "twitterqueuehandler.php ";
echo "facebookqueuehandler.php ";
echo "pingqueuehandler.php ";
-echo "inboxqueuehandler.php ";
echo "smsqueuehandler.php ";
+++ /dev/null
-#!/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');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
-
-set_error_handler('common_error_handler');
-
-class InboxQueueHandler extends QueueHandler
-{
- function transport()
- {
- return 'inbox';
- }
-
- function start() {
- $this->log(LOG_INFO, "INITIALIZE");
- return true;
- }
-
- function handle_notice($notice)
- {
- $this->log(LOG_INFO, "Distributing notice to inboxes for $notice->id");
- $notice->addToInboxes();
- $notice->blowSubsCache();
- return true;
- }
-
- function finish() {
- }
-}
-
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
-
-$id = ($argc > 1) ? $argv[1] : null;
-
-$handler = new InboxQueueHandler($id);
-
-$handler->runOnce();
+++ /dev/null
-#!/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');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
-
-set_error_handler('common_error_handler');
-
-class MemcachedQueueHandler extends QueueHandler
-{
- function transport()
- {
- return 'memcache';
- }
-
- function start() {
- $this->log(LOG_INFO, "INITIALIZE");
- return true;
- }
-
- function handle_notice($notice)
- {
- // XXX: fork here
- $this->log(LOG_INFO, "Blowing memcached for $notice->id");
- $notice->blowCaches();
- return true;
- }
-
- function finish() {
- }
-
-}
-
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
-
-$id = ($argc > 1) ? $argv[1] : null;
-
-$handler = new MemcachedQueueHandler($id);
-
-$handler->runOnce();
DIR=`php $SDIR/getpiddir.php`
for f in jabberhandler ombhandler publichandler smshandler pinghandler \
- xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
- memcachehandler inboxhandler twitterstatusfetcher; do
+ xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do
FILES="$DIR/$f.*.pid"
for ff in "$FILES" ; do