Merge branch '0.8.x' into twitter-oauth

This commit is contained in:
Zach Copley 2009-08-03 22:47:57 +00:00
commit dccd4aa68d
31 changed files with 1005 additions and 213 deletions

2
.gitignore vendored
View File

@ -23,4 +23,4 @@ config-*.php
good-config.php good-config.php
lac08.log lac08.log
php.log php.log
config.php.*

17
README
View File

@ -964,9 +964,6 @@ sslserver: use an alternate server name for SSL URLs, like
shorturllength: Length of URL at which URLs in a message exceeding 140 shorturllength: Length of URL at which URLs in a message exceeding 140
characters will be sent to the user's chosen characters will be sent to the user's chosen
shortening service. shortening service.
design: a default design (colors and background) for the site.
Sub-items are: backgroundcolor, contentcolor, sidebarcolor,
textcolor, linkcolor, backgroundimage, disposition.
dupelimit: minimum time allowed for one person to say the same thing dupelimit: minimum time allowed for one person to say the same thing
twice. Default 60s. Anything lower is considered a user twice. Default 60s. Anything lower is considered a user
or UI error. or UI error.
@ -1432,6 +1429,20 @@ notify third-party servers of updates.
notify: an array of URLs for ping endpoints. Default is the empty notify: an array of URLs for ping endpoints. Default is the empty
array (no notification). array (no notification).
design
------
Default design (colors and background) for the site. Actual appearance
depends on the theme. Null values mean to use the theme defaults.
backgroundcolor: Hex color of the site background.
contentcolor: Hex color of the content area background.
sidebarcolor: Hex color of the sidebar background.
textcolor: Hex color of all non-link text.
linkcolor: Hex color of all links.
backgroundimage: Image to use for the background.
disposition: Flags for whether or not to tile the background image.
Plugins Plugins
======= =======

View File

@ -130,6 +130,7 @@ class ApiAction extends Action
'laconica/wadl', 'laconica/wadl',
'tags/timeline', 'tags/timeline',
'oembed/oembed', 'oembed/oembed',
'groups/show',
'groups/timeline'); 'groups/timeline');
static $bareauth = array('statuses/user_timeline', static $bareauth = array('statuses/user_timeline',

View File

@ -167,6 +167,8 @@ class ConversationTree extends NoticeList
function _buildTree() function _buildTree()
{ {
$cnt = 0;
$this->tree = array(); $this->tree = array();
$this->table = array(); $this->table = array();

View File

@ -229,7 +229,7 @@ class PublicAction extends Action
// $top->show(); // $top->show();
$pop = new PopularNoticeSection($this); $pop = new PopularNoticeSection($this);
$pop->show(); $pop->show();
$gbp = new GroupsByPostsSection($this); $gbp = new GroupsByMembersSection($this);
$gbp->show(); $gbp->show();
$feat = new FeaturedUsersSection($this); $feat = new FeaturedUsersSection($this);
$feat->show(); $feat->show();

View File

@ -207,32 +207,10 @@ class TwitapifavoritesAction extends TwitterapiAction
$other = User::staticGet('id', $notice->profile_id); $other = User::staticGet('id', $notice->profile_id);
if ($other && $other->id != $user->id) { if ($other && $other->id != $user->id) {
if ($other->email && $other->emailnotifyfav) { if ($other->email && $other->emailnotifyfav) {
$this->notify_mail($other, $user, $notice); mail_notify_fave($other, $user, $notice);
} }
# XXX: notify by IM # XXX: notify by IM
# XXX: notify by SMS # XXX: notify by SMS
} }
} }
}
function notify_mail($other, $user, $notice)
{
$profile = $user->getProfile();
$bestname = $profile->getBestName();
$subject = sprintf(_('%s added your notice as a favorite'), $bestname);
$body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" .
"In case you forgot, you can see the text of your notice here:\n\n" .
"%3\$s\n\n" .
"You can see the list of %1\$s's favorites here:\n\n" .
"%4\$s\n\n" .
"Faithfully yours,\n" .
"%5\$s\n"),
$bestname,
common_exact_date($notice->created),
common_local_url('shownotice', array('notice' => $notice->id)),
common_local_url('showfavorites', array('nickname' => $user->nickname)),
common_config('site', 'name'));
mail_to_user($other, $subject, $body);
}
}

View File

@ -51,6 +51,32 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
class TwitapigroupsAction extends TwitterapiAction class TwitapigroupsAction extends TwitterapiAction
{ {
function show($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$this->auth_user = $apidata['user'];
$group = $this->get_group($apidata['api_arg'], $apidata);
if (empty($group)) {
$this->clientError('Not Found', 404, $apidata['content-type']);
return;
}
switch($apidata['content-type']) {
case 'xml':
$this->show_single_xml_group($group);
break;
case 'json':
$this->show_single_json_group($group);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
}
}
function timeline($args, $apidata) function timeline($args, $apidata)
{ {
parent::handle($args); parent::handle($args);
@ -88,8 +114,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
$this->show_xml_timeline($notice); $this->show_xml_timeline($notice);
break; break;
case 'rss': case 'rss':
$this->show_rss_timeline($notice, $title, $link, $this->show_rss_timeline($notice, $title, $link, $subtitle);
$subtitle, $suplink);
break; break;
case 'atom': case 'atom':
if (isset($apidata['api_arg'])) { if (isset($apidata['api_arg'])) {
@ -101,7 +126,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
'api/laconica/groups/timeline.atom'; 'api/laconica/groups/timeline.atom';
} }
$this->show_atom_timeline($notice, $title, $id, $link, $this->show_atom_timeline($notice, $title, $id, $link,
$subtitle, $suplink, $selfuri); $subtitle, null, $selfuri);
break; break;
case 'json': case 'json':
$this->show_json_timeline($notice); $this->show_json_timeline($notice);

View File

@ -88,8 +88,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
$this->show_xml_timeline($notice); $this->show_xml_timeline($notice);
break; break;
case 'rss': case 'rss':
$this->show_rss_timeline($notice, $title, $link, $this->show_rss_timeline($notice, $title, $link, $subtitle);
$subtitle, $suplink);
break; break;
case 'atom': case 'atom':
if (isset($apidata['api_arg'])) { if (isset($apidata['api_arg'])) {
@ -101,7 +100,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
'api/laconica/tags/timeline.atom'; 'api/laconica/tags/timeline.atom';
} }
$this->show_atom_timeline($notice, $title, $id, $link, $this->show_atom_timeline($notice, $title, $id, $link,
$subtitle, $suplink, $selfuri); $subtitle, null, $selfuri);
break; break;
case 'json': case 'json':
$this->show_json_timeline($notice); $this->show_json_timeline($notice);

View File

@ -55,26 +55,38 @@ class Design extends Memcached_DataObject
function showCSS($out) function showCSS($out)
{ {
try { $css = '';
$bgcolor = new WebColor($this->backgroundcolor); $bgcolor = Design::toWebColor($this->backgroundcolor);
$ccolor = new WebColor($this->contentcolor);
$sbcolor = new WebColor($this->sidebarcolor);
$tcolor = new WebColor($this->textcolor);
$lcolor = new WebColor($this->linkcolor);
} catch (WebColorException $e) { if (!empty($bgcolor)) {
// This shouldn't happen $css .= 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n";
common_log(LOG_ERR, "Unable to create color for design $id.",
__FILE__);
} }
$css = 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n"; $ccolor = Design::toWebColor($this->contentcolor);
$css .= '#content, #site_nav_local_views .current a { background-color: #';
$css .= $ccolor->hexValue() . '} '."\n"; if (!empty($ccolor)) {
$css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n"; $css .= '#content, #site_nav_local_views .current a { background-color: #';
$css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n"; $css .= $ccolor->hexValue() . '} '."\n";
$css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n"; }
$sbcolor = Design::toWebColor($this->sidebarcolor);
if (!empty($sbcolor)) {
$css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n";
}
$tcolor = Design::toWebColor($this->textcolor);
if (!empty($tcolor)) {
$css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n";
}
$lcolor = Design::toWebColor($this->linkcolor);
if (!empty($lcolor)) {
$css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n";
}
if (!empty($this->backgroundimage) && if (!empty($this->backgroundimage) &&
$this->disposition & BACKGROUND_ON) { $this->disposition & BACKGROUND_ON) {
@ -88,8 +100,25 @@ class Design extends Memcached_DataObject
'); ' . $repeat . ' background-attachment:fixed; }' . "\n"; '); ' . $repeat . ' background-attachment:fixed; }' . "\n";
} }
$out->element('style', array('type' => 'text/css'), $css); if (0 != mb_strlen($css)) {
$out->element('style', array('type' => 'text/css'), $css);
}
}
static function toWebColor($color)
{
if (is_null($color)) {
return null;
}
try {
return new WebColor($color);
} catch (WebColorException $e) {
// This shouldn't happen
common_log(LOG_ERR, "Unable to create color for design $id.",
__FILE__);
return null;
}
} }
static function filename($id, $extension, $extra=null) static function filename($id, $extension, $extra=null)
@ -152,4 +181,33 @@ class Design extends Memcached_DataObject
} }
} }
/**
* Return a design object based on the configured site design.
*
* @return Design a singleton design object for the site.
*/
static function siteDesign()
{
static $siteDesign = null;
if (empty($siteDesign)) {
$siteDesign = new Design();
$attrs = array('backgroundcolor',
'contentcolor',
'sidebarcolor',
'textcolor',
'linkcolor',
'backgroundimage',
'disposition');
foreach ($attrs as $attr) {
$siteDesign->$attr = common_config('design', $attr);
}
}
return $siteDesign;
}
} }

View File

@ -93,7 +93,6 @@ class File extends Memcached_DataObject
if (empty($file)) { if (empty($file)) {
$file_redir = File_redirection::staticGet('url', $given_url); $file_redir = File_redirection::staticGet('url', $given_url);
if (empty($file_redir)) { if (empty($file_redir)) {
common_debug("processNew() '$given_url' not a known redirect.\n");
$redir_data = File_redirection::where($given_url); $redir_data = File_redirection::where($given_url);
$redir_url = $redir_data['url']; $redir_url = $redir_data['url'];
if ($redir_url === $given_url) { if ($redir_url === $given_url) {
@ -114,7 +113,9 @@ class File extends Memcached_DataObject
if (empty($x)) { if (empty($x)) {
$x = File::staticGet($file_id); $x = File::staticGet($file_id);
if (empty($x)) die('Impossible!'); if (empty($x)) {
throw new ServerException("Robin thinks something is impossible.");
}
} }
File_to_post::processNew($file_id, $notice_id); File_to_post::processNew($file_id, $notice_id);

View File

@ -102,15 +102,14 @@ class Notice extends Memcached_DataObject
if (!$count) { if (!$count) {
return true; return true;
} }
//turn each into their canonical tag //turn each into their canonical tag
//this is needed to remove dupes before saving e.g. #hash.tag = #hashtag //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
$hashtags = array(); $hashtags = array();
for($i=0; $i<count($match[1]); $i++) { for($i=0; $i<count($match[1]); $i++) {
$hashtags[] = common_canonical_tag($match[1][$i]); $hashtags[] = common_canonical_tag($match[1][$i]);
} }
/* Add them to the database */ /* Add them to the database */
foreach(array_unique($hashtags) as $hashtag) { foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */ /* elide characters we don't want in the tag */
@ -183,29 +182,30 @@ class Notice extends Memcached_DataObject
$notice->is_local = $is_local; $notice->is_local = $is_local;
} }
$notice->query('BEGIN');
$notice->reply_to = $reply_to;
if (!empty($created)) { if (!empty($created)) {
$notice->created = $created; $notice->created = $created;
} else { } else {
$notice->created = common_sql_now(); $notice->created = common_sql_now();
} }
$notice->content = $final; $notice->content = $final;
$notice->rendered = common_render_content($final, $notice); $notice->rendered = common_render_content($final, $notice);
$notice->source = $source; $notice->source = $source;
$notice->uri = $uri; $notice->uri = $uri;
if (!empty($reply_to)) { $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
$reply_notice = Notice::staticGet('id', $reply_to);
if (!empty($reply_notice)) { if (!empty($notice->reply_to)) {
$notice->reply_to = $reply_to; $reply = Notice::staticGet('id', $notice->reply_to);
$notice->conversation = $reply_notice->conversation; $notice->conversation = $reply->conversation;
}
} }
if (Event::handle('StartNoticeSave', array(&$notice))) { if (Event::handle('StartNoticeSave', array(&$notice))) {
// XXX: some of these functions write to the DB
$notice->query('BEGIN');
$id = $notice->insert(); $id = $notice->insert();
if (!$id) { if (!$id) {
@ -213,18 +213,33 @@ class Notice extends Memcached_DataObject
return _('Problem saving notice.'); return _('Problem saving notice.');
} }
# Update the URI after the notice is in the database // Update ID-dependent columns: URI, conversation
if (!$uri) {
$orig = clone($notice);
$notice->uri = common_notice_uri($notice);
$orig = clone($notice);
$changed = false;
if (empty($uri)) {
$notice->uri = common_notice_uri($notice);
$changed = true;
}
// If it's not part of a conversation, it's
// the beginning of a new conversation.
if (empty($notice->conversation)) {
$notice->conversation = $notice->id;
$changed = true;
}
if ($changed) {
if (!$notice->update($orig)) { if (!$notice->update($orig)) {
common_log_db_error($notice, 'UPDATE', __FILE__); common_log_db_error($notice, 'UPDATE', __FILE__);
return _('Problem saving notice.'); return _('Problem saving notice.');
} }
} }
# XXX: do we need to change this for remote users? // XXX: do we need to change this for remote users?
$notice->saveReplies(); $notice->saveReplies();
$notice->saveTags(); $notice->saveTags();
@ -232,8 +247,13 @@ class Notice extends Memcached_DataObject
$notice->addToInboxes(); $notice->addToInboxes();
$notice->saveUrls(); $notice->saveUrls();
// FIXME: why do we have to re-render the content?
// Remove this if it's not necessary.
$orig2 = clone($notice); $orig2 = clone($notice);
$notice->rendered = common_render_content($final, $notice);
$notice->rendered = common_render_content($final, $notice);
if (!$notice->update($orig2)) { if (!$notice->update($orig2)) {
common_log_db_error($notice, 'UPDATE', __FILE__); common_log_db_error($notice, 'UPDATE', __FILE__);
return _('Problem saving notice.'); return _('Problem saving notice.');
@ -290,9 +310,9 @@ class Notice extends Memcached_DataObject
$notice->profile_id = $profile_id; $notice->profile_id = $profile_id;
$notice->content = $content; $notice->content = $content;
if (common_config('db','type') == 'pgsql') if (common_config('db','type') == 'pgsql')
$notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit')); $notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit'));
else else
$notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit')); $notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit'));
$cnt = $notice->count(); $cnt = $notice->count();
return ($cnt == 0); return ($cnt == 0);
@ -906,14 +926,14 @@ class Notice extends Memcached_DataObject
{ {
$user = new User(); $user = new User();
if(common_config('db','quote_identifiers')) if(common_config('db','quote_identifiers'))
$user_table = '"user"'; $user_table = '"user"';
else $user_table = 'user'; else $user_table = 'user';
$qry = $qry =
'SELECT id ' . 'SELECT id ' .
'FROM '. $user_table .' JOIN subscription '. 'FROM '. $user_table .' JOIN subscription '.
'ON '. $user_table .'.id = subscription.subscriber ' . 'ON '. $user_table .'.id = subscription.subscriber ' .
'WHERE subscription.subscribed = %d '; 'WHERE subscription.subscribed = %d ';
$user->query(sprintf($qry, $this->profile_id)); $user->query(sprintf($qry, $this->profile_id));
@ -1031,16 +1051,6 @@ class Notice extends Memcached_DataObject
if (!$recipient) { if (!$recipient) {
continue; continue;
} }
if ($i == 0 && ($recipient->id != $sender->id) && !$this->reply_to) { // Don't save reply to self
$reply_for = $recipient;
$recipient_notice = $reply_for->getCurrentNotice();
if ($recipient_notice) {
$orig = clone($this);
$this->reply_to = $recipient_notice->id;
$this->conversation = $recipient_notice->conversation;
$this->update($orig);
}
}
// Don't save replies from blocked profile to local user // Don't save replies from blocked profile to local user
$recipient_user = User::staticGet('id', $recipient->id); $recipient_user = User::staticGet('id', $recipient->id);
if ($recipient_user && $recipient_user->hasBlocked($sender)) { if ($recipient_user && $recipient_user->hasBlocked($sender)) {
@ -1087,14 +1097,6 @@ class Notice extends Memcached_DataObject
} }
} }
// If it's not a reply, make it the root of a new conversation
if (empty($this->conversation)) {
$orig = clone($this);
$this->conversation = $this->id;
$this->update($orig);
}
foreach (array_keys($replied) as $recipient) { foreach (array_keys($replied) as $recipient) {
$user = User::staticGet('id', $recipient); $user = User::staticGet('id', $recipient);
if ($user) { if ($user) {
@ -1266,4 +1268,76 @@ class Notice extends Memcached_DataObject
return $ids; return $ids;
} }
/**
* Determine which notice, if any, a new notice is in reply to.
*
* For conversation tracking, we try to see where this notice fits
* in the tree. Rough algorithm is:
*
* if (reply_to is set and valid) {
* return reply_to;
* } else if ((source not API or Web) and (content starts with "T NAME" or "@name ")) {
* return ID of last notice by initial @name in content;
* }
*
* Note that all @nickname instances will still be used to save "reply" records,
* so the notice shows up in the mentioned users' "replies" tab.
*
* @param integer $reply_to ID passed in by Web or API
* @param integer $profile_id ID of author
* @param string $source Source tag, like 'web' or 'gwibber'
* @param string $content Final notice content
*
* @return integer ID of replied-to notice, or null for not a reply.
*/
static function getReplyTo($reply_to, $profile_id, $source, $content)
{
static $lb = array('xmpp', 'mail', 'sms', 'omb');
// If $reply_to is specified, we check that it exists, and then
// return it if it does
if (!empty($reply_to)) {
$reply_notice = Notice::staticGet('id', $reply_to);
if (!empty($reply_notice)) {
return $reply_to;
}
}
// If it's not a "low bandwidth" source (one where you can't set
// a reply_to argument), we return. This is mostly web and API
// clients.
if (!in_array($source, $lb)) {
return null;
}
// Is there an initial @ or T?
if (preg_match('/^T ([A-Z0-9]{1,64}) /', $content, $match) ||
preg_match('/^@([a-z0-9]{1,64})\s+/', $content, $match)) {
$nickname = common_canonical_nickname($match[1]);
} else {
return null;
}
// Figure out who that is.
$sender = Profile::staticGet('id', $profile_id);
$recipient = common_relative_profile($sender, $nickname, common_sql_now());
if (empty($recipient)) {
return null;
}
// Get their last notice
$last = $recipient->getCurrentNotice();
if (!empty($last)) {
return $last->id;
}
}
} }

View File

@ -108,11 +108,24 @@ class Session extends Memcached_DataObject
$epoch = common_sql_date(time() - $maxlifetime); $epoch = common_sql_date(time() - $maxlifetime);
$ids = array();
$session = new Session(); $session = new Session();
$session->whereAdd('modified < "'.$epoch.'"'); $session->whereAdd('modified < "'.$epoch.'"');
$result = $session->delete(DB_DATAOBJECT_WHEREADD_ONLY); $session->selectAdd();
$session->selectAdd('id');
self::logdeb("garbage collection result = $result"); $session->find();
while ($session->fetch()) {
$ids[] = $session->id;
}
$session->free();
foreach ($ids as $id) {
self::destroy($id);
}
} }
static function setSaveHandler() static function setSaveHandler()

View File

@ -18,14 +18,14 @@ $config['site']['server'] = 'localhost';
$config['site']['path'] = 'laconica'; $config['site']['path'] = 'laconica';
// $config['site']['fancy'] = false; // $config['site']['fancy'] = false;
// $config['site']['theme'] = 'default'; // $config['site']['theme'] = 'default';
// Sets the site's default design values (match it with the values in the theme) // Sets the site's default design values
// $config['site']['design']['backgroundcolor'] = '#F0F2F5'; // $config['design']['backgroundcolor'] = '#F0F2F5';
// $config['site']['design']['contentcolor'] = '#FFFFFF'; // $config['design']['contentcolor'] = '#FFFFFF';
// $config['site']['design']['sidebarcolor'] = '#CEE1E9'; // $config['design']['sidebarcolor'] = '#CEE1E9';
// $config['site']['design']['textcolor'] = '#000000'; // $config['design']['textcolor'] = '#000000';
// $config['site']['design']['linkcolor'] = '#002E6E'; // $config['design']['linkcolor'] = '#002E6E';
// $config['site']['design']['backgroundimage'] = null; // $config['design']['backgroundimage'] = null;
// $config['site']['design']['disposition'] = 1; // $config['design']['disposition'] = 1;
// To enable the built-in mobile style sheet, defaults to false. // To enable the built-in mobile style sheet, defaults to false.
// $config['site']['mobile'] = true; // $config['site']['mobile'] = true;
// For contact email, defaults to $_SERVER["SERVER_ADMIN"] // For contact email, defaults to $_SERVER["SERVER_ADMIN"]

108
db/074to080_pg.sql Normal file
View File

@ -0,0 +1,108 @@
BEGIN;
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)
);
alter table "user"
add column design_id integer references design(id);
alter table "user"
add column viewdesigns integer default 1;
alter table notice add column
conversation integer references notice (id);
create index notice_conversation_idx on notice(conversation);
alter table foreign_user
alter column id TYPE bigint;
alter table foreign_user alter column id set not null;
alter table foreign_link
alter column foreign_id TYPE bigint;
alter table user_group
add column design_id integer;
/*attachments and URLs stuff */
create sequence file_seq;
create table file (
id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique,
mimetype varchar(50),
size integer,
title varchar(255),
date integer,
protected integer,
filename text /* comment 'if a local file, name of the file' */,
modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/
);
create sequence file_oembed_seq;
create table file_oembed (
file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
version varchar(20),
type varchar(20),
provider varchar(50),
provider_url varchar(255),
width integer,
height integer,
html text,
title varchar(255),
author_name varchar(50),
author_url varchar(255),
url varchar(255)
);
create sequence file_redirection_seq;
create table file_redirection (
url varchar(255) primary key,
file_id bigint,
redirections integer,
httpcode integer
);
create sequence file_thumbnail_seq;
create table file_thumbnail (
file_id bigint primary key,
url varchar(255) unique,
width integer,
height integer
);
create sequence file_to_post_seq;
create table file_to_post (
file_id bigint,
post_id bigint,
primary key (file_id, post_id)
);
create table group_block (
group_id integer not null /* comment 'group profile is blocked from' */ references user_group (id),
blocked integer not null /* comment 'profile that is blocked' */references profile (id),
blocker integer not null /* comment 'user making the block'*/ references "user" (id),
modified timestamp /* comment 'date of blocking'*/ ,
primary key (group_id, blocked)
);
create table group_alias (
alias varchar(64) /* comment 'additional nickname for the group'*/ ,
group_id integer not null /* comment 'group profile is blocked from'*/ references user_group (id),
modified timestamp /* comment 'date alias was created'*/,
primary key (alias)
);
create index group_alias_group_id_idx on group_alias (group_id);
COMMIT;

View File

@ -108,7 +108,7 @@ function checkMirror($action_obj)
function main() function main()
{ {
// quick check for fancy URL auto-detection support in installer. // quick check for fancy URL auto-detection support in installer.
if (isset($_SERVER['REDIRECT_URL']) && ((dirname($_SERVER['REQUEST_URI']) . '/check-fancy') === $_SERVER['REDIRECT_URL'])) { if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/","",(dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) {
die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs."); die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
} }
global $user, $action; global $user, $action;

View File

@ -7,6 +7,35 @@
* @link http://laconi.ca/ * @link http://laconi.ca/
*/ */
$(document).ready(function() { $(document).ready(function() {
function InitColors(i, E) {
switch (parseInt(E.id.slice(-1))) {
case 1: default:
$(E).val(rgb2hex($('body').css('background-color')));
break;
case 2:
$(E).val(rgb2hex($('#content').css('background-color')));
break;
case 3:
$(E).val(rgb2hex($('#aside_primary').css('background-color')));
break;
case 4:
$(E).val(rgb2hex($('html body').css('color')));
break;
case 5:
$(E).val(rgb2hex($('a').css('color')));
break;
}
}
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
hexDigits = new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
function UpdateColors(S) { function UpdateColors(S) {
C = $(S).val(); C = $(S).val();
switch (parseInt(S.id.slice(-1))) { switch (parseInt(S.id.slice(-1))) {
@ -55,7 +84,7 @@ $(document).ready(function() {
f = $.farbtastic('#color-picker', SynchColors); f = $.farbtastic('#color-picker', SynchColors);
swatches = $('#settings_design_color .swatch'); swatches = $('#settings_design_color .swatch');
swatches.each(InitColors);
swatches swatches
.each(SynchColors) .each(SynchColors)
.blur(function() { .blur(function() {

View File

@ -191,6 +191,7 @@ class Action extends HTMLOutputter // lawsuit
function showStylesheets() function showStylesheets()
{ {
if (Event::handle('StartShowStyles', array($this))) { if (Event::handle('StartShowStyles', array($this))) {
if (Event::handle('StartShowLaconicaStyles', array($this))) { if (Event::handle('StartShowLaconicaStyles', array($this))) {
$this->element('link', array('rel' => 'stylesheet', $this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css', 'type' => 'text/css',
@ -209,6 +210,7 @@ class Action extends HTMLOutputter // lawsuit
'media' => 'print')); 'media' => 'print'));
Event::handle('EndShowLaconicaStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this));
} }
if (Event::handle('StartShowUAStyles', array($this))) { if (Event::handle('StartShowUAStyles', array($this))) {
$this->comment('[if IE]><link rel="stylesheet" type="text/css" '. $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]'); 'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
@ -223,6 +225,21 @@ class Action extends HTMLOutputter // lawsuit
'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]'); 'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
Event::handle('EndShowUAStyles', array($this)); Event::handle('EndShowUAStyles', array($this));
} }
if (Event::handle('StartShowDesign', array($this))) {
$user = common_current_user();
if (empty($user) || $user->viewdesigns) {
$design = $this->getDesign();
if (!empty($design)) {
$design->showCSS($this);
}
}
Event::handle('EndShowDesign', array($this));
}
Event::handle('EndShowStyles', array($this)); Event::handle('EndShowStyles', array($this));
} }
} }
@ -1074,4 +1091,15 @@ class Action extends HTMLOutputter // lawsuit
{ {
return null; return null;
} }
/**
* A design for this action
*
* @return Design a design object to use
*/
function getDesign()
{
return Design::siteDesign();
}
} }

View File

@ -94,14 +94,6 @@ $config =
array('name' => 'Just another Laconica microblog', array('name' => 'Just another Laconica microblog',
'server' => $_server, 'server' => $_server,
'theme' => 'default', 'theme' => 'default',
'design' =>
array('backgroundcolor' => '#CEE1E9',
'contentcolor' => '#FFFFFF',
'sidebarcolor' => '#C8D1D5',
'textcolor' => '#000000',
'linkcolor' => '#002E6E',
'backgroundimage' => null,
'disposition' => 1),
'path' => $_path, 'path' => $_path,
'logfile' => null, 'logfile' => null,
'logo' => null, 'logo' => null,
@ -264,6 +256,14 @@ $config =
'sessions' => 'sessions' =>
array('handle' => false, // whether to handle sessions ourselves array('handle' => false, // whether to handle sessions ourselves
'debug' => false), // debugging output for sessions 'debug' => false), // debugging output for sessions
'design' =>
array('backgroundcolor' => null, // null -> 'use theme default'
'contentcolor' => null,
'sidebarcolor' => null,
'textcolor' => null,
'linkcolor' => null,
'backgroundimage' => null,
'disposition' => null),
); );
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@ -280,6 +280,10 @@ $config['db'] =
'quote_identifiers' => false, 'quote_identifiers' => false,
'type' => 'mysql' ); 'type' => 'mysql' );
// Backward compatibility
$config['site']['design'] =& $config['design'];
if (function_exists('date_default_timezone_set')) { if (function_exists('date_default_timezone_set')) {
/* Work internally in UTC */ /* Work internally in UTC */
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');

View File

@ -47,33 +47,10 @@ if (!defined('LACONICA')) {
class CurrentUserDesignAction extends Action class CurrentUserDesignAction extends Action
{ {
/**
* Show the user's design stylesheet
*
* @return nothing
*/
function showStylesheets()
{
parent::showStylesheets();
$user = common_current_user();
if (empty($user) || $user->viewdesigns) {
$design = $this->getDesign();
if (!empty($design)) {
$design->showCSS($this);
}
}
}
/** /**
* A design for this action * A design for this action
* *
* if the user attribute has been set, returns that user's * Returns the design preferences for the current user.
* design.
* *
* @return Design a design object to use * @return Design a design object to use
*/ */
@ -82,11 +59,15 @@ class CurrentUserDesignAction extends Action
{ {
$cur = common_current_user(); $cur = common_current_user();
if (empty($cur)) { if (!empty($cur)) {
return null;
$design = $cur->getDesign();
if (!empty($design)) {
return $design;
}
} }
return $cur->getDesign(); return parent::getDesign();
} }
} }

View File

@ -182,7 +182,7 @@ class DesignSettingsAction extends AccountSettingsAction
'class' => 'swatch', 'class' => 'swatch',
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $bgcolor->hexValue())); 'value' => ''));
$this->elementEnd('li'); $this->elementEnd('li');
$ccolor = new WebColor($design->contentcolor); $ccolor = new WebColor($design->contentcolor);
@ -195,7 +195,7 @@ class DesignSettingsAction extends AccountSettingsAction
'class' => 'swatch', 'class' => 'swatch',
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $ccolor->hexValue())); 'value' => ''));
$this->elementEnd('li'); $this->elementEnd('li');
$sbcolor = new WebColor($design->sidebarcolor); $sbcolor = new WebColor($design->sidebarcolor);
@ -208,7 +208,7 @@ class DesignSettingsAction extends AccountSettingsAction
'class' => 'swatch', 'class' => 'swatch',
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $sbcolor->hexValue())); 'value' => ''));
$this->elementEnd('li'); $this->elementEnd('li');
$tcolor = new WebColor($design->textcolor); $tcolor = new WebColor($design->textcolor);
@ -221,7 +221,7 @@ class DesignSettingsAction extends AccountSettingsAction
'class' => 'swatch', 'class' => 'swatch',
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $tcolor->hexValue())); 'value' => ''));
$this->elementEnd('li'); $this->elementEnd('li');
$lcolor = new WebColor($design->linkcolor); $lcolor = new WebColor($design->linkcolor);
@ -234,7 +234,7 @@ class DesignSettingsAction extends AccountSettingsAction
'class' => 'swatch', 'class' => 'swatch',
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $lcolor->hexValue())); 'value' => ''));
$this->elementEnd('li'); $this->elementEnd('li');
} catch (WebColorException $e) { } catch (WebColorException $e) {

View File

@ -49,26 +49,6 @@ class GroupDesignAction extends Action {
/** The group in question */ /** The group in question */
var $group = null; var $group = null;
/**
* Show the groups's design stylesheet
*
* @return nothing
*/
function showStylesheets()
{
parent::showStylesheets();
$user = common_current_user();
if (empty($user) || $user->viewdesigns) {
$design = $this->getDesign();
if (!empty($design)) {
$design->showCSS($this);
}
}
}
/** /**
* A design for this action * A design for this action
* *
@ -80,10 +60,12 @@ class GroupDesignAction extends Action {
function getDesign() function getDesign()
{ {
if (empty($this->group)) { if (!empty($this->group)) {
return null; $design = $this->group->getDesign();
if (!empty($design)) {
return $design;
}
} }
return parent::getDesign();
return $this->group->getDesign();
} }
} }

View File

@ -52,26 +52,6 @@ class OwnerDesignAction extends Action {
var $user = null; var $user = null;
/**
* Show the owner's design stylesheet
*
* @return nothing
*/
function showStylesheets()
{
parent::showStylesheets();
$user = common_current_user();
if (empty($user) || $user->viewdesigns) {
$design = $this->getDesign();
if (!empty($design)) {
$design->showCSS($this);
}
}
}
/** /**
* A design for this action * A design for this action
* *
@ -83,10 +63,15 @@ class OwnerDesignAction extends Action {
function getDesign() function getDesign()
{ {
if (empty($this->user)) { if (!empty($this->user)) {
return null;
$design = $this->user->getDesign();
if (!empty($design)) {
return $design;
}
} }
return $this->user->getDesign(); return parent::getDesign();
} }
} }

View File

@ -117,6 +117,16 @@ class Router
$m->connect('main/tagother/:id', array('action' => 'tagother')); $m->connect('main/tagother/:id', array('action' => 'tagother'));
$m->connect('main/oembed.xml',
array('action' => 'api',
'method' => 'oembed.xml',
'apiaction' => 'oembed'));
$m->connect('main/oembed.json',
array('action' => 'api',
'method' => 'oembed.json',
'apiaction' => 'oembed'));
// these take a code // these take a code
foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) { foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
@ -479,11 +489,6 @@ class Router
Event::handle('RouterInitialized', array($m)); Event::handle('RouterInitialized', array($m));
$m->connect('main/:method',
array('action' => 'api',
'method' => 'oembed(.xml|.json)?',
'apiaction' => 'oembed'));
return $m; return $m;
} }

View File

@ -213,6 +213,26 @@ class TwitterapiAction extends Action
return $twitter_status; return $twitter_status;
} }
function twitter_group_array($group)
{
$twitter_group=array();
$twitter_group['id']=$group->id;
$twitter_group['url']=$group->permalink();
$twitter_group['nickname']=$group->nickname;
$twitter_group['fullname']=$group->fullname;
$twitter_group['homepage_url']=$group->homepage_url;
$twitter_group['original_logo']=$group->original_logo;
$twitter_group['homepage_logo']=$group->homepage_logo;
$twitter_group['stream_logo']=$group->stream_logo;
$twitter_group['mini_logo']=$group->mini_logo;
$twitter_group['homepage']=$group->homepage;
$twitter_group['description']=$group->description;
$twitter_group['location']=$group->location;
$twitter_group['created']=$this->date_twitter($group->created);
$twitter_group['modified']=$this->date_twitter($group->modified);
return $twitter_group;
}
function twitter_rss_entry_array($notice) function twitter_rss_entry_array($notice)
{ {
$profile = $notice->getProfile(); $profile = $notice->getProfile();
@ -413,6 +433,15 @@ class TwitterapiAction extends Action
$this->elementEnd('status'); $this->elementEnd('status');
} }
function show_twitter_xml_group($twitter_group)
{
$this->elementStart('group');
foreach($twitter_group as $element => $value) {
$this->element($element, null, $value);
}
$this->elementEnd('group');
}
function show_twitter_xml_user($twitter_user, $role='user') function show_twitter_xml_user($twitter_user, $role='user')
{ {
$this->elementStart($role); $this->elementStart($role);
@ -450,12 +479,12 @@ class TwitterapiAction extends Action
$this->element('link', null, $entry['link']); $this->element('link', null, $entry['link']);
# RSS only supports 1 enclosure per item # RSS only supports 1 enclosure per item
if($entry['enclosures']){ if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
$enclosure = $entry['enclosures'][0]; $enclosure = $entry['enclosures'][0];
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
} }
if($entry['tags']){ if(array_key_exists('tags', $entry)){
foreach($entry['tags'] as $tag){ foreach($entry['tags'] as $tag){
$this->element('category', null,$tag); $this->element('category', null,$tag);
} }
@ -639,6 +668,22 @@ class TwitterapiAction extends Action
$this->end_document('json'); $this->end_document('json');
} }
function show_single_json_group($group)
{
$this->init_document('json');
$twitter_group = $this->twitter_group_array($group);
$this->show_json_objects($twitter_group);
$this->end_document('json');
}
function show_single_xml_group($group)
{
$this->init_document('xml');
$twitter_group = $this->twitter_group_array($group);
$this->show_twitter_xml_group($twitter_group);
$this->end_document('xml');
}
// Anyone know what date format this is? // Anyone know what date format this is?
// Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
function date_twitter($dt) function date_twitter($dt)

View File

@ -140,7 +140,7 @@ function common_have_session()
function common_ensure_session() function common_ensure_session()
{ {
$c = null; $c = null;
if (array_key_exists(session_name, $_COOKIE)) { if (array_key_exists(session_name(), $_COOKIE)) {
$c = $_COOKIE[session_name()]; $c = $_COOKIE[session_name()];
} }
if (!common_have_session()) { if (!common_have_session()) {
@ -1410,20 +1410,21 @@ function common_client_ip()
return null; return null;
} }
if ($_SERVER['HTTP_X_FORWARDED_FOR']) { if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
if ($_SERVER['HTTP_CLIENT_IP']) { if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
$proxy = $_SERVER['HTTP_CLIENT_IP']; $proxy = $_SERVER['HTTP_CLIENT_IP'];
} else { } else {
$proxy = $_SERVER['REMOTE_ADDR']; $proxy = $_SERVER['REMOTE_ADDR'];
} }
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else { } else {
if ($_SERVER['HTTP_CLIENT_IP']) { $proxy = null;
if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
$ip = $_SERVER['HTTP_CLIENT_IP']; $ip = $_SERVER['HTTP_CLIENT_IP'];
} else { } else {
$ip = $_SERVER['REMOTE_ADDR']; $ip = $_SERVER['REMOTE_ADDR'];
} }
} }
return array($ip, $proxy); return array($proxy, $ip);
} }

View File

@ -122,9 +122,7 @@ class FBConnectPlugin extends Plugin
FB_RequireFeatures( FB_RequireFeatures(
["XFBML"], ["XFBML"],
function() { function() {
FB.init("%s", "../xd_receiver.html", FB.init("%s", "../xd_receiver.html");
{"doNotUseCachedConnectState":true });
} }
); } ); }
@ -222,7 +220,7 @@ class FBConnectPlugin extends Plugin
try { try {
$facebook = getFacebook(); $facebook = getFacebook();
$fbuid = $facebook->api_client->users_getLoggedInUser(); $fbuid = $facebook->get_loggedin_user();
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_WARNING, common_log(LOG_WARNING,

22
plugins/recaptcha/LICENSE Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
AUTHORS:
Mike Crawford
Ben Maurer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

23
plugins/recaptcha/README Normal file
View File

@ -0,0 +1,23 @@
Laconica reCAPTCHA plugin 0.2 8/3/09
====================================
Adds a captcha to your registration page to reduce automated spam bots registering.
Use:
1. Get an API key from http://recaptcha.net
2. In config.php add:
include_once('plugins/recaptcha.php');
$captcha = new recaptcha(publickey, privatekey, showErrors);
Changelog
=========
0.1 initial release
0.2 Work around for webkit browsers
reCAPTCHA README
================
The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation
for this library can be found at
http://recaptcha.net/plugins/php

View File

@ -0,0 +1,106 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to show reCaptcha when a user registers
*
* 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 Plugin
* @package Laconica
* @author Eric Helgeson <erichelgeson@gmail.com>
* @copyright 2009
* @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);
}
define('RECAPTCHA', '0.2');
class recaptcha extends Plugin
{
var $private_key;
var $public_key;
var $display_errors;
var $failed;
var $ssl;
function __construct($public_key, $private_key, $display_errors=false)
{
parent::__construct();
require_once(INSTALLDIR.'/plugins/recaptcha/recaptchalib.php');
$this->public_key = $public_key;
$this->private_key = $private_key;
$this->display_errors = $display_errors;
}
function checkssl(){
if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') {
return true;
}
return false;
}
function onStartShowHTML($action)
{
//XXX: Horrible hack to make Safari, FF2, and Chrome work with
//reChapcha. reChapcha beaks xhtml strict
header('Content-Type: text/html');
$action->extraHeaders();
$action->startXML('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$action->raw('<style type="text/css">#recaptcha_area{float:left;}</style>');
return false;
}
function onEndRegistrationFormData($action)
{
$action->elementStart('li');
$action->raw('<label for="recaptcha_area">Captcha</label>');
if($this->checkssl() === true){
$action->raw(recaptcha_get_html($this->public_key), null, true);
} else {
$action->raw(recaptcha_get_html($this->public_key));
}
$action->elementEnd('li');
return true;
}
function onStartRegistrationTry($action)
{
$resp = recaptcha_check_answer ($this->private_key,
$_SERVER["REMOTE_ADDR"],
$action->trimmed('recaptcha_challenge_field'),
$action->trimmed('recaptcha_response_field'));
if (!$resp->is_valid)
{
if($this->display_errors)
{
$action->showForm ("(reCAPTCHA said: " . $resp->error . ")");
}
$action->showForm("Captcha does not match!");
return false;
}
}
}

View File

@ -0,0 +1,277 @@
<?php
/*
* This is a PHP library that handles calling reCAPTCHA.
* - Documentation and latest version
* http://recaptcha.net/plugins/php/
* - Get a reCAPTCHA API Key
* http://recaptcha.net/api/getkey
* - Discussion group
* http://groups.google.com/group/recaptcha
*
* Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
* AUTHORS:
* Mike Crawford
* Ben Maurer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* The reCAPTCHA server URL's
*/
define("RECAPTCHA_API_SERVER", "http://api.recaptcha.net");
define("RECAPTCHA_API_SECURE_SERVER", "https://api-secure.recaptcha.net");
define("RECAPTCHA_VERIFY_SERVER", "api-verify.recaptcha.net");
/**
* Encodes the given data into a query string format
* @param $data - array of string elements to be encoded
* @return string - encoded request
*/
function _recaptcha_qsencode ($data) {
$req = "";
foreach ( $data as $key => $value )
$req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
// Cut the last '&'
$req=substr($req,0,strlen($req)-1);
return $req;
}
/**
* Submits an HTTP POST to a reCAPTCHA server
* @param string $host
* @param string $path
* @param array $data
* @param int port
* @return array response
*/
function _recaptcha_http_post($host, $path, $data, $port = 80) {
$req = _recaptcha_qsencode ($data);
$http_request = "POST $path HTTP/1.0\r\n";
$http_request .= "Host: $host\r\n";
$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
$http_request .= "Content-Length: " . strlen($req) . "\r\n";
$http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
$http_request .= "\r\n";
$http_request .= $req;
$response = '';
if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
die ('Could not open socket');
}
fwrite($fs, $http_request);
while ( !feof($fs) )
$response .= fgets($fs, 1160); // One TCP-IP packet
fclose($fs);
$response = explode("\r\n\r\n", $response, 2);
return $response;
}
/**
* Gets the challenge HTML (javascript and non-javascript version).
* This is called from the browser, and the resulting reCAPTCHA HTML widget
* is embedded within the HTML form it was called from.
* @param string $pubkey A public key for reCAPTCHA
* @param string $error The error given by reCAPTCHA (optional, default is null)
* @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
* @return string - The HTML to be embedded in the user's form.
*/
function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
{
if ($pubkey == null || $pubkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($use_ssl) {
$server = RECAPTCHA_API_SECURE_SERVER;
} else {
$server = RECAPTCHA_API_SERVER;
}
$errorpart = "";
if ($error) {
$errorpart = "&amp;error=" . $error;
}
return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
<noscript>
<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
</noscript>';
}
/**
* A ReCaptchaResponse is returned from recaptcha_check_answer()
*/
class ReCaptchaResponse {
var $is_valid;
var $error;
}
/**
* Calls an HTTP POST function to verify if the user's guess was correct
* @param string $privkey
* @param string $remoteip
* @param string $challenge
* @param string $response
* @param array $extra_params an array of extra variables to post to the server
* @return ReCaptchaResponse
*/
function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
{
if ($privkey == null || $privkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($remoteip == null || $remoteip == '') {
die ("For security reasons, you must pass the remote ip to reCAPTCHA");
}
//discard spam submissions
if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
$recaptcha_response = new ReCaptchaResponse();
$recaptcha_response->is_valid = false;
$recaptcha_response->error = 'incorrect-captcha-sol';
return $recaptcha_response;
}
$response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify",
array (
'privatekey' => $privkey,
'remoteip' => $remoteip,
'challenge' => $challenge,
'response' => $response
) + $extra_params
);
$answers = explode ("\n", $response [1]);
$recaptcha_response = new ReCaptchaResponse();
if (trim ($answers [0]) == 'true') {
$recaptcha_response->is_valid = true;
}
else {
$recaptcha_response->is_valid = false;
$recaptcha_response->error = $answers [1];
}
return $recaptcha_response;
}
/**
* gets a URL where the user can sign up for reCAPTCHA. If your application
* has a configuration page where you enter a key, you should provide a link
* using this function.
* @param string $domain The domain where the page is hosted
* @param string $appname The name of your application
*/
function recaptcha_get_signup_url ($domain = null, $appname = null) {
return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname));
}
function _recaptcha_aes_pad($val) {
$block_size = 16;
$numpad = $block_size - (strlen ($val) % $block_size);
return str_pad($val, strlen ($val) + $numpad, chr($numpad));
}
/* Mailhide related code */
function _recaptcha_aes_encrypt($val,$ky) {
if (! function_exists ("mcrypt_encrypt")) {
die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
}
$mode=MCRYPT_MODE_CBC;
$enc=MCRYPT_RIJNDAEL_128;
$val=_recaptcha_aes_pad($val);
return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
function _recaptcha_mailhide_urlbase64 ($x) {
return strtr(base64_encode ($x), '+/', '-_');
}
/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
function recaptcha_mailhide_url($pubkey, $privkey, $email) {
if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
"you can do so at <a href='http://mailhide.recaptcha.net/apikey'>http://mailhide.recaptcha.net/apikey</a>");
}
$ky = pack('H*', $privkey);
$cryptmail = _recaptcha_aes_encrypt ($email, $ky);
return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
}
/**
* gets the parts of the email to expose to the user.
* eg, given johndoe@example,com return ["john", "example.com"].
* the email is then displayed as john...@example.com
*/
function _recaptcha_mailhide_email_parts ($email) {
$arr = preg_split("/@/", $email );
if (strlen ($arr[0]) <= 4) {
$arr[0] = substr ($arr[0], 0, 1);
} else if (strlen ($arr[0]) <= 6) {
$arr[0] = substr ($arr[0], 0, 3);
} else {
$arr[0] = substr ($arr[0], 0, 4);
}
return $arr;
}
/**
* Gets html to display an email address given a public an private key.
* to get a key, go to:
*
* http://mailhide.recaptcha.net/apikey
*/
function recaptcha_mailhide_html($pubkey, $privkey, $email) {
$emailparts = _recaptcha_mailhide_email_parts ($email);
$url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
}
?>

36
scripts/sessiongc.php Normal file
View File

@ -0,0 +1,36 @@
#!/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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$helptext = <<<END_OF_GC_HELP
sessiongc.php
Delete old sessions from the server
END_OF_GC_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
$maxlifetime = ini_get('session.gc_maxlifetime');
print "Deleting sessions older than $maxlifetime seconds.\n";
Session::gc($maxlifetime);