Merge branch '0.8.x' into 0.9.x

This commit is contained in:
Evan Prodromou 2009-07-23 14:46:54 -07:00
commit 8641cb4591
49 changed files with 1008 additions and 311 deletions

13
README
View File

@ -134,7 +134,7 @@ Prerequisites
The following software packages are *required* for this software to
run correctly.
- PHP 5.2.x. It may be possible to run this software on earlier
- PHP 5.2.3+. It may be possible to run this software on earlier
versions of PHP, but many of the functions used are only available
in PHP 5.2 or above.
- MySQL 5.x. The Laconica database is stored, by default, in a MySQL
@ -262,13 +262,16 @@ especially if you've previously installed PHP/MySQL packages.
that user's default group instead. As a last resort, you can create
a new group like "mublog" and add the Web server's user to the group.
4. You should also take this moment to make your avatar subdirectory
writeable by the Web server. An insecure way to do this is:
4. You should also take this moment to make your avatar, background, and
file subdirectories writeable by the Web server. An insecure way to do
this is:
chmod a+w /var/www/mublog/avatar
chmod a+w /var/www/mublog/background
chmod a+w /var/www/mublog/file
You can also make the avatar directory writeable by the Web server
group, as noted above.
You can also make the avatar, background, and file directories
writeable by the Web server group, as noted above.
5. Create a database to hold your microblog data. Something like this
should work:

View File

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

View File

@ -98,6 +98,28 @@ class AttachmentAction extends Action
return $a->title();
}
function extraHead()
{
$this->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.json'),
array('url'=>
common_local_url('attachment',
array('attachment' => $this->attachment->id)))),
'title'=>'oEmbed'),null);
$this->element('link',array('rel'=>'alternate',
'type'=>'text/xml+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.xml'),
array('url'=>
common_local_url('attachment',
array('attachment' => $this->attachment->id)))),
'title'=>'oEmbed'),null);
}
/**
* Handle input
*

View File

@ -83,7 +83,7 @@ class FinishopenidloginAction extends Action
function showContent()
{
if (!empty($this->message_text)) {
$this->element('p', null, $this->message);
$this->element('div', array('class' => 'error'), $this->message_text);
return;
}

View File

@ -194,6 +194,9 @@ class RecoverpasswordAction extends Action
'or your registered email address.'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->element('input', array('name' => 'recover',
'type' => 'hidden',
'value' => _('Recover')));
$this->submit('recover', _('Recover'));
$this->elementEnd('fieldset');
$this->elementEnd('form');

View File

@ -281,6 +281,20 @@ class ShownoticeAction extends OwnerDesignAction
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
$this->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.json'),
array('url'=>$this->notice->uri)),
'title'=>'oEmbed'),null);
$this->element('link',array('rel'=>'alternate',
'type'=>'text/xml+oembed',
'href'=>common_local_url(
'api',
array('apiaction'=>'oembed','method'=>'oembed.xml'),
array('url'=>$this->notice->uri)),
'title'=>'oEmbed'),null);
}
}

View File

@ -171,4 +171,5 @@ class TwitapilaconicaAction extends TwitterapiAction
parent::handle($args);
$this->serverError(_('API method under construction.'), 501);
}
}

173
actions/twitapioembed.php Normal file
View File

@ -0,0 +1,173 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Laconica-only extensions to the Twitter-like API
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Twitter
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008 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);
}
require_once INSTALLDIR.'/lib/twitterapi.php';
/**
* Oembed provider implementation
*
* This class handles all /main/oembed(.xml|.json)/ requests.
*
* @category oEmbed
* @package Laconica
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2008 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class TwitapioembedAction extends TwitterapiAction
{
function oembed($args, $apidata)
{
parent::handle($args);
common_debug("in oembed api action");
$this->auth_user = $apidata['user'];
$url = $args['url'];
if( substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url()) ){
$path = substr($url,strlen(common_root_url()));
$r = Router::get();
$proxy_args = $r->map($path);
if (!$proxy_args) {
$this->serverError(_("$path not found"), 404);
}
$oembed=array();
$oembed['version']='1.0';
$oembed['provider_name']=common_config('site', 'name');
$oembed['provider_url']=common_root_url();
switch($proxy_args['action']){
case 'shownotice':
$oembed['type']='link';
$id = $proxy_args['notice'];
$notice = Notice::staticGet($id);
if(empty($notice)){
$this->serverError(_("notice $id not found"), 404);
}
$profile = $notice->getProfile();
if (empty($profile)) {
$this->serverError(_('Notice has no profile'), 500);
}
if (!empty($profile->fullname)) {
$authorname = $profile->fullname . ' (' . $profile->nickname . ')';
} else {
$authorname = $profile->nickname;
}
$oembed['title'] = sprintf(_('%1$s\'s status on %2$s'),
$authorname,
common_exact_date($notice->created));
$oembed['author_name']=$authorname;
$oembed['author_url']=$profile->profileurl;
$oembed['url']=($notice->url?$notice->url:$notice->uri);
$oembed['html']=$notice->rendered;
break;
case 'attachment':
$id = $proxy_args['attachment'];
$attachment = File::staticGet($id);
if(empty($attachment)){
$this->serverError(_("attachment $id not found"), 404);
}
if(empty($attachment->filename) && $file_oembed = File_oembed::staticGet('file_id', $attachment->id)){
// Proxy the existing oembed information
$oembed['type']=$file_oembed->type;
$oembed['provider']=$file_oembed->provider;
$oembed['provider_url']=$file_oembed->provider_url;
$oembed['width']=$file_oembed->width;
$oembed['height']=$file_oembed->height;
$oembed['html']=$file_oembed->html;
$oembed['title']=$file_oembed->title;
$oembed['author_name']=$file_oembed->author_name;
$oembed['author_url']=$file_oembed->author_url;
$oembed['url']=$file_oembed->url;
}else if(substr($attachment->mimetype,0,strlen('image/'))=='image/'){
$oembed['type']='photo';
//TODO set width and height
//$oembed['width']=
//$oembed['height']=
$oembed['url']=$attachment->url;
}else{
$oembed['type']='link';
$oembed['url']=common_local_url('attachment',
array('attachment' => $attachment->id));
}
if($attachment->title) $oembed['title']=$attachment->title;
break;
default:
$this->serverError(_("$path not supported for oembed requests"), 501);
}
switch($apidata['content-type']){
case 'xml':
$this->init_document('xml');
$this->elementStart('oembed');
$this->element('version',null,$oembed['version']);
$this->element('type',null,$oembed['type']);
if($oembed['provider_name']) $this->element('provider_name',null,$oembed['provider_name']);
if($oembed['provider_url']) $this->element('provider_url',null,$oembed['provider_url']);
if($oembed['title']) $this->element('title',null,$oembed['title']);
if($oembed['author_name']) $this->element('author_name',null,$oembed['author_name']);
if($oembed['author_url']) $this->element('author_url',null,$oembed['author_url']);
if($oembed['url']) $this->element('url',null,$oembed['url']);
if($oembed['html']) $this->element('html',null,$oembed['html']);
if($oembed['width']) $this->element('width',null,$oembed['width']);
if($oembed['height']) $this->element('height',null,$oembed['height']);
if($oembed['cache_age']) $this->element('cache_age',null,$oembed['cache_age']);
if($oembed['thumbnail_url']) $this->element('thumbnail_url',null,$oembed['thumbnail_url']);
if($oembed['thumbnail_width']) $this->element('thumbnail_width',null,$oembed['thumbnail_width']);
if($oembed['thumbnail_height']) $this->element('thumbnail_height',null,$oembed['thumbnail_height']);
$this->elementEnd('oembed');
$this->end_document('xml');
break;
case 'json':
$this->init_document('json');
print(json_encode($oembed));
$this->end_document('json');
break;
default:
$this->serverError(_('content type ' . $apidata['content-type'] . ' not supported'), 501);
}
}else{
$this->serverError(_('Only ' . common_root_url() . ' urls over plain http please'), 404);
}
}
}

View File

@ -59,7 +59,7 @@ class Fave extends Memcached_DataObject
$qry = 'SELECT fave.* FROM fave ';
$qry .= 'INNER JOIN notice ON fave.notice_id = notice.id ';
$qry .= 'WHERE fave.user_id = ' . $user_id . ' ';
$qry .= 'AND notice.is_local != ' . NOTICE_GATEWAY . ' ';
$qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' ';
}
if ($since_id != 0) {
@ -79,7 +79,7 @@ class Fave extends Memcached_DataObject
$qry .= 'ORDER BY modified DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $offset, $limit";
$qry .= "LIMIT $limit OFFSET $offset";
}
$fav->query($qry);

View File

@ -79,9 +79,8 @@ class File extends Memcached_DataObject
if (isset($redir_data['type'])
&& ('text/html' === substr($redir_data['type'], 0, 9))
&& ($oembed_data = File_oembed::_getOembed($given_url))
&& isset($oembed_data['json'])) {
File_oembed::saveNew($oembed_data['json'], $file_id);
&& ($oembed_data = File_oembed::_getOembed($given_url))) {
File_oembed::saveNew($oembed_data, $file_id);
}
return $x;
}
@ -123,6 +122,7 @@ class File extends Memcached_DataObject
}
function isRespectsQuota($user,$fileSize) {
if ($fileSize > common_config('attachments', 'file_quota')) {
return sprintf(_('No file may be larger than %d bytes ' .
'and the file you sent was %d bytes. Try to upload a smaller version.'),
@ -136,8 +136,7 @@ class File extends Memcached_DataObject
if ($total > common_config('attachments', 'user_quota')) {
return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
}
$query .= ' month(modified) = month(now()) and year(modified) = year(now())';
$query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
$this->query($query);
$this->fetch();
$total = $this->total + $fileSize;

View File

@ -56,33 +56,46 @@ class File_oembed extends Memcached_DataObject
return array(false, false, false);
}
function _getOembed($url, $maxwidth = 500, $maxheight = 400, $format = 'json') {
$cmd = common_config('oohembed', 'endpoint') . '?url=' . urlencode($url);
if (is_int($maxwidth)) $cmd .= "&maxwidth=$maxwidth";
if (is_int($maxheight)) $cmd .= "&maxheight=$maxheight";
if (is_string($format)) $cmd .= "&format=$format";
$oe = @file_get_contents($cmd);
if (false === $oe) return false;
return array($format => (('json' === $format) ? json_decode($oe, true) : $oe));
function _getOembed($url, $maxwidth = 500, $maxheight = 400) {
require_once INSTALLDIR.'/extlib/Services/oEmbed.php';
$parameters = array(
'maxwidth'=>$maxwidth,
'maxheight'=>$maxheight,
);
try{
$oEmbed = new Services_oEmbed($url);
$object = $oEmbed->getObject($parameters);
return $object;
}catch(Exception $e){
try{
$oEmbed = new Services_oEmbed($url, array(
Services_oEmbed::OPTION_API => common_config('oohembed', 'endpoint')
));
$object = $oEmbed->getObject($parameters);
return $object;
}catch(Exception $ex){
return false;
}
}
}
function saveNew($data, $file_id) {
$file_oembed = new File_oembed;
$file_oembed->file_id = $file_id;
$file_oembed->version = $data['version'];
$file_oembed->type = $data['type'];
if (!empty($data['provider_name'])) $file_oembed->provider = $data['provider_name'];
if (!isset($file_oembed->provider) && !empty($data['provide'])) $file_oembed->provider = $data['provider'];
if (!empty($data['provide_url'])) $file_oembed->provider_url = $data['provider_url'];
if (!empty($data['width'])) $file_oembed->width = intval($data['width']);
if (!empty($data['height'])) $file_oembed->height = intval($data['height']);
if (!empty($data['html'])) $file_oembed->html = $data['html'];
if (!empty($data['title'])) $file_oembed->title = $data['title'];
if (!empty($data['author_name'])) $file_oembed->author_name = $data['author_name'];
if (!empty($data['author_url'])) $file_oembed->author_url = $data['author_url'];
if (!empty($data['url'])) $file_oembed->url = $data['url'];
$file_oembed->version = $data->version;
$file_oembed->type = $data->type;
if (!empty($data->provider_name)) $file_oembed->provider = $data->provider_name;
if (!empty($data->provider)) $file_oembed->provider = $data->provider;
if (!empty($data->provide_url)) $file_oembed->provider_url = $data->provider_url;
if (!empty($data->width)) $file_oembed->width = intval($data->width);
if (!empty($data->height)) $file_oembed->height = intval($data->height);
if (!empty($data->html)) $file_oembed->html = $data->html;
if (!empty($data->title)) $file_oembed->title = $data->title;
if (!empty($data->author_name)) $file_oembed->author_name = $data->author_name;
if (!empty($data->author_url)) $file_oembed->author_url = $data->author_url;
if (!empty($data->url)) $file_oembed->url = $data->url;
$file_oembed->insert();
if (!empty($data['thumbnail_url'])) {
if (!empty($data->thumbnail_url)) {
File_thumbnail::saveNew($data, $file_id);
}
}

View File

@ -51,9 +51,9 @@ class File_thumbnail extends Memcached_DataObject
function saveNew($data, $file_id) {
$tn = new File_thumbnail;
$tn->file_id = $file_id;
$tn->url = $data['thumbnail_url'];
$tn->width = intval($data['thumbnail_width']);
$tn->height = intval($data['thumbnail_height']);
$tn->url = $data->thumbnail_url;
$tn->width = intval($data->thumbnail_width);
$tn->height = intval($data->thumbnail_height);
$tn->insert();
}
}

View File

@ -32,7 +32,6 @@ define('NOTICE_CACHE_WINDOW', 61);
define('NOTICE_LOCAL_PUBLIC', 1);
define('NOTICE_REMOTE_OMB', 0);
define('NOTICE_LOCAL_NONPUBLIC', -1);
define('NOTICE_GATEWAY', -2);
define('MAX_BOXCARS', 128);
@ -63,6 +62,8 @@ class Notice extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
const GATEWAY = -2;
function getProfile()
{
return Profile::staticGet('id', $this->profile_id);
@ -111,13 +112,21 @@ class Notice extends Memcached_DataObject
function saveTags()
{
/* extract all #hastags */
$count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($this->content), $match);
$count = preg_match_all('/(?:^|\s)#([\pL\pN_\-\.]{1,64})/', strtolower($this->content), $match);
if (!$count) {
return true;
}
//turn each into their canonical tag
//this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
$hashtags = array();
for($i=0; $i<count($match[1]); $i++) {
$hashtags[] = common_canonical_tag($match[1][$i]);
}
/* Add them to the database */
foreach(array_unique($match[1]) as $hashtag) {
foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */
$this->saveTag($hashtag);
}
@ -126,8 +135,6 @@ class Notice extends Memcached_DataObject
function saveTag($hashtag)
{
$hashtag = common_canonical_tag($hashtag);
$tag = new Notice_tag();
$tag->notice_id = $this->id;
$tag->tag = $hashtag;
@ -887,7 +894,7 @@ class Notice extends Memcached_DataObject
if ($cnt > 0) {
$qry .= ', ';
}
$qry .= '('.$id.', '.$this->id.', '.$source.', "'.$this->created.'") ';
$qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
$cnt++;
if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
Notice_inbox::gc($id);
@ -913,10 +920,14 @@ class Notice extends Memcached_DataObject
{
$user = new User();
if(common_config('db','quote_identifiers'))
$user_table = '"user"';
else $user_table = 'user';
$qry =
'SELECT id ' .
'FROM user JOIN subscription '.
'ON user.id = subscription.subscriber ' .
'FROM '. $user_table .' JOIN subscription '.
'ON '. $user_table .'.id = subscription.subscriber ' .
'WHERE subscription.subscribed = %d ';
$user->query(sprintf($qry, $this->profile_id));

View File

@ -199,7 +199,7 @@ class Profile extends Memcached_DataObject
$query .= ' order by id DESC';
if (!is_null($offset)) {
$query .= " limit $offset, $limit";
$query .= " LIMIT $limit OFFSET $offset";
}
$notice->query($query);
@ -360,7 +360,6 @@ class Profile extends Memcached_DataObject
$c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
}
common_debug("subscriptionCount == $cnt");
return $cnt;
}
@ -385,7 +384,6 @@ class Profile extends Memcached_DataObject
$c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
}
common_debug("subscriberCount == $cnt");
return $cnt;
}
@ -407,7 +405,6 @@ class Profile extends Memcached_DataObject
$c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
}
common_debug("faveCount == $cnt");
return $cnt;
}
@ -430,7 +427,6 @@ class Profile extends Memcached_DataObject
$c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
}
common_debug("noticeCount == $cnt");
return $cnt;
}

View File

@ -106,14 +106,11 @@ class Session extends Memcached_DataObject
{
self::logdeb("garbage collection (maxlifetime = $maxlifetime)");
$epoch = time() - $maxlifetime;
$qry = 'DELETE FROM session ' .
'WHERE modified < "'.$epoch.'"';
$epoch = common_sql_date(time() - $maxlifetime);
$session = new Session();
$result = $session->query($qry);
$session->whereAdd('modified < "'.$epoch.'"');
$result = $session->delete(DB_DATAOBJECT_WHEREADD_ONLY);
self::logdeb("garbage collection result = $result");
}

View File

@ -443,7 +443,7 @@ class User extends Memcached_DataObject
'SELECT notice.* ' .
'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
'WHERE subscription.subscriber = %d ' .
'AND notice.is_local != ' . NOTICE_GATEWAY;
'AND notice.is_local != ' . Notice::GATEWAY;
return Notice::getStream(sprintf($qry, $this->id),
'user:notices_with_friends:' . $this->id,
$offset, $limit, $since_id, $before_id,

View File

@ -275,11 +275,14 @@ class User_group extends Memcached_DataObject
// XXX: cache this
$user = new User();
if(common_config('db','quote_identifiers'))
$user_table = '"user"';
else $user_table = 'user';
$qry =
'SELECT id ' .
'FROM user JOIN group_member '.
'ON user.id = group_member.profile_id ' .
'FROM '. $user_table .' JOIN group_member '.
'ON '. $user_table .'.id = group_member.profile_id ' .
'WHERE group_member.group_id = %d ';
$user->query(sprintf($qry, $this->id));

View File

@ -40,6 +40,19 @@ create table sms_carrier (
modified timestamp /* comment 'date this record was modified ' */
);
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)
);
/* local users */
create table "user" (
@ -71,6 +84,8 @@ create table "user" (
autosubscribe integer 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 integer default 0 /* comment 'has an inbox been created for this user?' */,
design_id integer /* comment 'id of a design' */references design(id),
viewdesigns integer default 1 /* comment 'whether to view user-provided designs'*/,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */
@ -184,7 +199,7 @@ create table token (
create table nonce (
consumer_key varchar(255) not null /* comment 'unique identifier, root URL' */,
tok char(32) not null /* comment 'identifying value' */,
tok char(32) /* comment 'buggy old value, ignored' */,
nonce char(32) null /* comment 'buggy old value, ignored */,
ts integer not null /* comment 'timestamp sent' values are epoch, and only used internally */,
@ -390,6 +405,8 @@ create table user_group (
homepage_logo varchar(255) /* comment 'homepage (profile) size logo' */,
stream_logo varchar(255) /* comment 'stream-sized logo' */,
mini_logo varchar(255) /* comment 'mini logo' */,
design_id integer /*comment 'id of a design' */ references design(id),
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
modified timestamp /* comment 'date this record was modified' */
@ -424,7 +441,6 @@ create table group_inbox (
group_id integer not null /* comment 'group receiving the message' references user_group (id) */,
notice_id integer not null /* comment 'notice received' references notice (id) */,
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */,
primary key (group_id, notice_id)
);
create index group_inbox_created_idx on group_inbox using btree(created);
@ -439,13 +455,14 @@ create table file (
size integer,
title varchar(255),
date integer,
protected 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 (
id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
file_id bigint unique,
file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
version varchar(20),
type varchar(20),
provider varchar(50),
@ -461,8 +478,7 @@ create table file_oembed (
create sequence file_redirection_seq;
create table file_redirection (
id bigint default nextval('file_redirection_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique,
url varchar(255) primary key,
file_id bigint,
redirections integer,
httpcode integer
@ -470,8 +486,7 @@ create table file_redirection (
create sequence file_thumbnail_seq;
create table file_thumbnail (
id bigint default nextval('file_thumbnail_seq') primary key /* comment 'unique identifier' */,
file_id bigint unique,
file_id bigint primary key,
url varchar(255) unique,
width integer,
height integer
@ -479,26 +494,32 @@ create table file_thumbnail (
create sequence file_to_post_seq;
create table file_to_post (
id bigint default nextval('file_to_post_seq') primary key /* comment 'unique identifier' */,
file_id bigint,
post_id bigint,
unique(file_id, post_id)
primary key (file_id, post_id)
);
create sequence design_seq;
create table design (
id bigint default nextval('design_seq') /* comment 'design ID'*/,
backgroundcolor integer /* comment 'main background color'*/ ,
contentcolor integer /*comment 'content area background color'*/ ,
sidebarcolor integer /*comment 'sidebar background color'*/ ,
textcolor integer /*comment 'text color'*/ ,
linkcolor integer /*comment 'link color'*/,
backgroundimage varchar(255) /*comment 'background image, if any'*/,
disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/,
primary key (id)
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);
/* Textsearch stuff */
create index textsearch_idx on profile using gist(textsearch);

View File

@ -60,4 +60,5 @@ VALUES
(100112, 'Cincinnati Bell Wireless', '%s@gocbw.com', now()),
(100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()),
(100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()),
(100115, 'E-Plus', '%s@smsmail.eplus.de', now());
(100115, 'E-Plus', '%s@smsmail.eplus.de', now()),
(100116, 'Cellular South', '%s@csouth1.com', now());

View File

@ -162,7 +162,7 @@ class Services_oEmbed
}
if ($this->options[self::OPTION_API] === null) {
$this->options[self::OPTION_API] = $this->discover();
$this->options[self::OPTION_API] = $this->discover($url);
}
}
@ -319,7 +319,7 @@ class Services_oEmbed
}
}
return (isset($ret['json']) ? $ret['json'] : array_pop($ret));
return (isset($ret['application/json']) ? $ret['application/json'] : array_pop($ret));
}
/**

View File

@ -1,12 +1,14 @@
RewriteEngine On
<IfModule mod_rewrite.c>
RewriteEngine On
# NOTE: change this to your actual Laconica path; may be "/".
# NOTE: change this to your actual Laconica path; may be "/".
RewriteBase /mublog/
RewriteBase /mublog/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) index.php?p=$1 [L,QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) index.php?p=$1 [L,QSA]
</IfModule>
<FilesMatch "\.(ini)">
Order allow,deny

View File

@ -165,7 +165,8 @@ function main()
if (!$user && common_config('site', 'private') &&
!in_array($action, array('login', 'openidlogin', 'finishopenidlogin',
'recoverpassword', 'api', 'doc', 'register'))) {
'recoverpassword', 'api', 'doc', 'register')) &&
!preg_match('/rss$/', $action)) {
common_redirect(common_local_url('login'));
return;
}

View File

@ -43,12 +43,12 @@ function checkPrereqs()
$pass = false;
}
if (version_compare(PHP_VERSION, '5.0.0', '<')) {
?><p class="error">Require PHP version 5 or greater.</p><?php
if (version_compare(PHP_VERSION, '5.2.3', '<')) {
?><p class="error">Require PHP version 5.2.3 or greater.</p><?php
$pass = false;
}
$reqs = array('gd', 'mysql', 'curl',
$reqs = array('gd', 'curl',
'xmlwriter', 'mbstring',
'gettext');
@ -58,6 +58,10 @@ function checkPrereqs()
$pass = false;
}
}
if (!checkExtension('pgsql') && !checkExtension('mysql')) {
?><p class="error">Cannot find mysql or pgsql extension. You need one or the other: <code><?php echo $req; ?></code></p><?php
$pass = false;
}
if (!is_writable(INSTALLDIR)) {
?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p>
@ -66,17 +70,16 @@ function checkPrereqs()
$pass = false;
}
if (!is_writable(INSTALLDIR.'/avatar/')) {
?><p class="error">Cannot write avatar directory: <code><?php echo INSTALLDIR; ?>/avatar/</code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/avatar/</code></p>
<?
$pass = false;
}
if (!is_writable(INSTALLDIR.'/background/')) {
?><p class="error">Cannot write background directory: <code><?php echo INSTALLDIR; ?>/background/</code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/background/</code></p>
<?
$pass = false;
// Check the subdirs used for file uploads
$fileSubdirs = array('avatar', 'background', 'file');
foreach ($fileSubdirs as $fileSubdir) {
$fileFullPath = INSTALLDIR."/$fileSubdir/";
if (!is_writable($fileFullPath)) {
?><p class="error">Cannot write <?php echo $fileSubdir; ?> directory: <code><?php echo $fileFullPath; ?></code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo $fileFullPath; ?></code></p>
<?
$pass = false;
}
}
return $pass;
@ -127,7 +130,15 @@ function showForm()
<p class="form_guide">Database hostname</p>
</li>
<li>
<label for="host">Database</label>
<label for="dbtype">Type</label>
<input type="radio" name="dbtype" id="fancy-mysql" value="mysql" checked='checked' /> MySQL<br />
<input type="radio" name="dbtype" id="dbtype-pgsql" value="pgsql" /> PostgreSQL<br />
<p class="form_guide">Database type</p>
</li>
<li>
<label for="database">Name</label>
<input type="text" id="database" name="database" />
<p class="form_guide">Database name</p>
</li>
@ -139,7 +150,7 @@ function showForm()
<li>
<label for="password">Password</label>
<input type="password" id="password" name="password" />
<p class="form_guide">Database password</p>
<p class="form_guide">Database password (optional)</p>
</li>
</ul>
<input type="submit" name="submit" class="submit" value="Submit" />
@ -163,6 +174,7 @@ function handlePost()
<?php
$host = $_POST['host'];
$dbtype = $_POST['dbtype'];
$database = $_POST['database'];
$username = $_POST['username'];
$password = $_POST['password'];
@ -191,64 +203,28 @@ function handlePost()
$fail = true;
}
if (empty($password)) {
updateStatus("No password specified.", true);
$fail = true;
}
// if (empty($password)) {
// updateStatus("No password specified.", true);
// $fail = true;
// }
if (empty($sitename)) {
updateStatus("No sitename specified.", true);
$fail = true;
}
if($fail){
showForm();
return;
}
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = mysql_connect($host, $username, $password);
if (!$conn) {
updateStatus("Can't connect to server '$host' as '$username'.", true);
showForm();
return;
}
updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
updateStatus("Can't change to database.", true);
showForm();
return;
}
updateStatus("Running database script...");
$res = runDbScript(INSTALLDIR.'/db/laconica.sql', $conn);
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
updateStatus(sprintf("Adding %s data to database...", $name));
$res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
if($fail){
showForm();
return;
}
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
$res = writeConf($sitename, $sqlUrl, $fancy);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
switch($dbtype) {
case 'mysql': mysql_db_installer($host, $database, $username, $password, $sitename);
break;
case 'pgsql': pgsql_db_installer($host, $database, $username, $password, $sitename);
break;
default:
}
if ($path) $path .= '/';
updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
?>
@ -256,7 +232,106 @@ function handlePost()
<?php
}
function writeConf($sitename, $sqlUrl, $fancy)
function pgsql_db_installer($host, $database, $username, $password, $sitename) {
$connstring = "dbname=$database host=$host user=$username";
//No password would mean trust authentication used.
if (!empty($password)) {
$connstring .= " password=$password";
}
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = pg_connect($connstring);
updateStatus("Running database script...");
//wrap in transaction;
pg_query($conn, 'BEGIN');
$res = runDbScript(INSTALLDIR.'/db/laconica_pg.sql', $conn, 'pgsql');
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
updateStatus(sprintf("Adding %s data to database...", $name));
$res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
}
}
pg_query($conn, 'COMMIT');
updateStatus("Writing config file...");
if (empty($password)) {
$sqlUrl = "pgsql://$username@$host/$database";
}
else {
$sqlUrl = "pgsql://$username:$password@$host/$database";
}
$res = writeConf($sitename, $sqlUrl, $fancy, 'pgsql');
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
}
function mysql_db_installer($host, $database, $username, $password, $sitename) {
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = mysql_connect($host, $username, $password);
if (!$conn) {
updateStatus("Can't connect to server '$host' as '$username'.", true);
showForm();
return;
}
updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
updateStatus("Can't change to database.", true);
showForm();
return;
}
updateStatus("Running database script...");
$res = runDbScript(INSTALLDIR.'/db/laconica.sql', $conn);
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
updateStatus(sprintf("Adding %s data to database...", $name));
$res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
}
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
$res = writeConf($sitename, $sqlUrl, $fancy);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
}
function writeConf($sitename, $sqlUrl, $fancy, $type='mysql')
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\n".
@ -264,11 +339,13 @@ function writeConf($sitename, $sqlUrl, $fancy)
"\$config['site']['name'] = \"$sitename\";\n\n".
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
"\$config['db']['database'] = \"$sqlUrl\";\n\n".
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n" .
"\$config['db']['type'] = \"$type\";\n\n" : '').
"?>");
return $res;
}
function runDbScript($filename, $conn)
function runDbScript($filename, $conn, $type='mysql')
{
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
@ -277,8 +354,13 @@ function runDbScript($filename, $conn)
if (!mb_strlen($stmt)) {
continue;
}
$res = mysql_query($stmt, $conn);
if ($type == 'mysql') {
$res = mysql_query($stmt, $conn);
} elseif ($type=='pgsql') {
$res = pg_query($conn, $stmt);
}
if ($res === false) {
updateStatus("FAILED SQL: $stmt");
return $res;
}
}

View File

@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
define('LACONICA_VERSION', '0.9.0dev');
define('LACONICA_VERSION', '0.8.1dev');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);

View File

@ -24,6 +24,7 @@ if (!defined('LACONICA')) {
class Daemon
{
var $daemonize = true;
var $_id = 'generic';
function __construct($daemonize = true)
{
@ -35,6 +36,16 @@ class Daemon
return null;
}
function get_id()
{
return $this->_id;
}
function set_id($id)
{
$this->_id = $id;
}
function background()
{
$pid = pcntl_fork();

View File

@ -55,7 +55,8 @@ class DBQueueManager extends QueueManager
{
while (true) {
$this->_log(LOG_DEBUG, 'Checking for notices...');
$notice = $this->_nextItem($queue, null);
$timeout = $handler->timeout();
$notice = $this->_nextItem($queue, $timeout);
if (empty($notice)) {
$this->_log(LOG_DEBUG, 'No notices waiting; idling.');
// Nothing in the queue. Do you
@ -87,7 +88,9 @@ class DBQueueManager extends QueueManager
do {
$qi = Queue_item::top($queue);
if (!empty($qi)) {
if (empty($qi)) {
sleep(1);
} else {
$notice = Notice::staticGet('id', $qi->notice_id);
if (!empty($notice)) {
$result = $notice;

View File

@ -48,7 +48,7 @@ class GroupsByMembersSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_member '.
'ON user_group.id = group_member.group_id ' .
'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified,user_group.design_id ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;

View File

@ -48,7 +48,7 @@ class GroupsByPostsSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_inbox '.
'ON user_group.id = group_inbox.group_id ' .
'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified,user_group.design_id ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;

View File

@ -73,7 +73,7 @@ class GroupTagCloudSection extends TagCloudSection
$quoted = array();
foreach ($names as $name) {
$quoted[] = "\"$name\"";
$quoted[] = "'$name'";
}
$namestring = implode(',', $quoted);

View File

@ -53,7 +53,7 @@ function client_prefered_language($httplang)
if (!empty($httplang[2][$i])) {
// if no q default to 1.0
$client_langs[$httplang[2][$i]] =
($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
($httplang[6][$i]? (float) $httplang[6][$i] : 1.0 - ($i*0.01));
}
if (!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
// if a catchall default 0.01 lower

View File

@ -121,7 +121,7 @@ function mail_notify_from()
$domain = mail_domain();
$notifyfrom = common_config('site', 'name') .' <noreply@'.$domain.'>';
$notifyfrom = '"'.common_config('site', 'name') .'" <noreply@'.$domain.'>';
}
return $notifyfrom;

View File

@ -140,6 +140,12 @@ class MessageForm extends Form
'rows' => 4,
'name' => 'content'),
($this->content) ? $this->content : '');
$this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters'));
$this->out->element('dd', array('id' => 'notice_text-count'),
'140');
$this->out->elementEnd('dl');
}
/**

View File

@ -74,11 +74,7 @@ class PopularNoticeSection extends NoticeSection
$offset = 0;
$limit = NOTICES_PER_SECTION + 1;
if (common_config('db', 'type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
$notice = Memcached_DataObject::cachedQuery('Notice',
sprintf($qry, common_config('popular', 'dropoff')),

View File

@ -29,7 +29,6 @@ define('QUEUE_HANDLER_HIT_IDLE', 0);
class QueueHandler extends Daemon
{
var $_id = 'generic';
function __construct($id=null, $daemonize=true)
{
@ -55,16 +54,6 @@ class QueueHandler extends Daemon
return strtolower($this->class_name().'.'.$this->get_id());
}
function get_id()
{
return $this->_id;
}
function set_id($id)
{
$this->_id = $id;
}
function transport()
{
return null;

View File

@ -129,6 +129,11 @@ class Router
$m->connect('index.php?action=' . $action, array('action' => $action));
}
$m->connect('main/:method',
array('action' => 'api',
'method' => 'oembed(.xml|.json)?',
'apiaction' => 'oembed'));
// settings
foreach (array('profile', 'avatar', 'password', 'openid', 'im',
@ -206,7 +211,7 @@ class Router
array('tag' => '[a-zA-Z0-9]+'));
$m->connect('tag/:tag',
array('action' => 'tag'),
array('tag' => '[a-zA-Z0-9]+'));
array('tag' => '[\pL\pN_\-\.]{1,64}'));
$m->connect('peopletag/:tag',
array('action' => 'peopletag'),
@ -390,6 +395,10 @@ class Router
// laconica
$m->connect('api/laconica/:method',
array('action' => 'api',
'apiaction' => 'laconica'));
$m->connect('api/laconica/:method',
array('action' => 'api',
'apiaction' => 'laconica'));

View File

@ -39,6 +39,7 @@ class Rss10Action extends Action
var $creators = array();
var $limit = DEFAULT_RSS_LIMIT;
var $notices = null;
var $tags_already_output = array();
/**
* Constructor
@ -96,15 +97,48 @@ class Rss10Action extends Action
{
// Parent handling, including cache check
parent::handle($args);
// Get the list of notices
if (empty($this->tag)) {
$this->notices = $this->getNotices($this->limit);
} else {
$this->notices = $this->getTaggedNotices($this->tag, $this->limit);
if (common_config('site', 'private')) {
if (!isset($_SERVER['PHP_AUTH_USER'])) {
# This header makes basic auth go
header('WWW-Authenticate: Basic realm="Laconica RSS"');
# If the user hits cancel -- bam!
$this->show_basic_auth_error();
return;
} else {
$nickname = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
if (!common_check_user($nickname, $password)) {
# basic authentication failed
list($proxy, $ip) = common_client_ip();
common_log(LOG_WARNING, "Failed RSS auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
$this->show_basic_auth_error();
return;
}
}
}
// Get the list of notices
$this->notices = $this->getNotices($this->limit);
$this->showRss();
}
function show_basic_auth_error()
{
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: application/xml; charset=utf-8');
$this->startXML();
$this->elementStart('hash');
$this->element('error', null, 'Could not authenticate you.');
$this->element('request', null, $_SERVER['REQUEST_URI']);
$this->elementEnd('hash');
$this->endXML();
}
/**
* Get the notices to output in this stream
*
@ -192,24 +226,6 @@ class Rss10Action extends Action
}
}
// XXX: Surely there should be a common function to do this?
function extract_tags ($string)
{
$count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($string), $match);
if (!count)
{
return array();
}
$rv = array();
foreach ($match[1] as $tag)
{
$rv[] = common_canonical_tag($tag);
}
return array_unique($rv);
}
function showItem($notice)
{
$profile = Profile::staticGet($notice->profile_id);
@ -234,6 +250,11 @@ class Rss10Action extends Action
$replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
$this->element('sioc:reply_of', array('rdf:resource' => $replyurl));
}
if (!empty($notice->conversation)) {
$conversationurl = common_local_url('conversation',
array('id' => $notice->conversation));
$this->element('sioc:has_discussion', array('rdf:resource' => $conversationurl));
}
$attachments = $notice->attachments();
if($attachments){
foreach($attachments as $attachment){
@ -263,18 +284,28 @@ class Rss10Action extends Action
$this->element('sioc:links_to', array('rdf:resource'=>$attachment->url));
}
}
$tags = $this->extract_tags($notice->content);
if (!empty($tags)) {
foreach ($tags as $tag)
{
$tagpage = common_local_url('tag', array('tag' => $tag));
$tagrss = common_local_url('tagrss', array('tag' => $tag));
$tag = new Notice_tag();
$tag->notice_id = $notice->id;
if ($tag->find()) {
$entry['tags']=array();
while ($tag->fetch()) {
$tagpage = common_local_url('tag', array('tag' => $tag->tag));
if ( in_array($tag, $this->tags_already_output) ) {
$this->element('ctag:tagged', array('rdf:resource'=>$tagpage.'#concept'));
continue;
}
$tagrss = common_local_url('tagrss', array('tag' => $tag->tag));
$this->elementStart('ctag:tagged');
$this->elementStart('ctag:Tag', array('rdf:about'=>$tagpage.'#concept', 'ctag:label'=>$tag));
$this->elementStart('ctag:Tag', array('rdf:about'=>$tagpage.'#concept', 'ctag:label'=>$tag->tag));
$this->element('foaf:page', array('rdf:resource'=>$tagpage));
$this->element('rdfs:seeAlso', array('rdf:resource'=>$tagrss));
$this->elementEnd('ctag:Tag');
$this->elementEnd('ctag:tagged');
$this->tags_already_output[] = $tag->tag;
}
}
$this->elementEnd('item');
@ -320,6 +351,8 @@ class Rss10Action extends Action
'http://rdfs.org/sioc/ns#',
'xmlns:sioct' =>
'http://rdfs.org/sioc/types#',
'xmlns:rdfs' =>
'http://www.w3.org/2000/01/rdf-schema#',
'xmlns:laconica' =>
'http://laconi.ca/ont/',
'xmlns' => 'http://purl.org/rss/1.0/'));

View File

@ -186,21 +186,24 @@ class TwitterapiAction extends Action
$twitter_status['favorited'] = false;
}
# Enclosures
// Enclosures
$attachments = $notice->attachments();
$twitter_status['attachments']=array();
if($attachments){
foreach($attachments as $attachment){
if ($attachment->isEnclosure()) {
$enclosure=array();
$enclosure['url']=$attachment->url;
$enclosure['mimetype']=$attachment->mimetype;
$enclosure['size']=$attachment->size;
$twitter_status['attachments'][]=$enclosure;
}
$enclosures = array();
foreach ($attachments as $attachment) {
if ($attachment->isEnclosure()) {
$enclosure = array();
$enclosure['url'] = $attachment->url;
$enclosure['mimetype'] = $attachment->mimetype;
$enclosure['size'] = $attachment->size;
$enclosures[] = $enclosure;
}
}
if (!empty($enclosures)) {
$twitter_status['attachments'] = $enclosures;
}
if ($include_user) {
# Don't get notice (recursive!)
$twitter_user = $this->twitter_user_array($profile, false);
@ -215,7 +218,7 @@ class TwitterapiAction extends Action
$profile = $notice->getProfile();
$entry = array();
# We trim() to avoid extraneous whitespace in the output
// We trim() to avoid extraneous whitespace in the output
$entry['content'] = common_xml_safe_str(trim($notice->rendered));
$entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
@ -228,7 +231,26 @@ class TwitterapiAction extends Action
$entry['updated'] = $entry['published'];
$entry['author'] = $profile->getBestName();
# Enclosure
// Enclosures
$attachments = $notice->attachments();
$enclosures = array();
foreach ($attachments as $attachment) {
if ($attachment->isEnclosure()) {
$enclosure = array();
$enclosure['url'] = $attachment->url;
$enclosure['mimetype'] = $attachment->mimetype;
$enclosure['size'] = $attachment->size;
$enclosures[] = $enclosure;
}
}
if (!empty($enclosures)) {
$entry['enclosures'] = $enclosures;
}
/*
// Enclosure
$attachments = $notice->attachments();
if($attachments){
$entry['enclosures']=array();
@ -242,8 +264,20 @@ class TwitterapiAction extends Action
}
}
}
*/
# RSS Item specific
// Tags/Categories
$tag = new Notice_tag();
$tag->notice_id = $notice->id;
if ($tag->find()) {
$entry['tags']=array();
while ($tag->fetch()) {
$entry['tags'][]=$tag->tag;
}
}
$tag->free();
// RSS Item specific
$entry['description'] = $entry['content'];
$entry['pubDate'] = common_date_rfc2822($notice->created);
$entry['guid'] = $entry['link'];
@ -369,6 +403,9 @@ class TwitterapiAction extends Action
case 'text':
$this->element($element, null, common_xml_safe_str($value));
break;
case 'attachments':
$this->show_xml_attachments($twitter_status['attachments']);
break;
default:
$this->element($element, null, $value);
}
@ -389,6 +426,20 @@ class TwitterapiAction extends Action
$this->elementEnd($role);
}
function show_xml_attachments($attachments) {
if (!empty($attachments)) {
$this->elementStart('attachments', array('type' => 'array'));
foreach ($attachments as $attachment) {
$attrs = array();
$attrs['url'] = $attachment['url'];
$attrs['mimetype'] = $attachment['mimetype'];
$attrs['size'] = $attachment['size'];
$this->element('enclosure', $attrs, '');
}
$this->elementEnd('attachments');
}
}
function show_twitter_rss_item($entry)
{
$this->elementStart('item');
@ -403,6 +454,12 @@ class TwitterapiAction extends Action
$enclosure = $entry['enclosures'][0];
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
}
if($entry['tags']){
foreach($entry['tags'] as $tag){
$this->element('category', null,$tag);
}
}
$this->elementEnd('item');
}

View File

@ -404,7 +404,7 @@ function common_render_text($text)
$r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
$r = common_replace_urls_callback($r, 'common_linkify');
$r = preg_replace('/(^|\(|\[|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
$r = preg_replace('/(^|\(|\[|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
// XXX: machine tags
return $r;
}
@ -414,9 +414,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
$regex = '#'.
'(?:'.
'(?:'.
'(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://'.
'(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'.
'|'.
'(?:mailto|aim|tel):'.
'(?:mailto|aim|tel|xmpp):'.
')'.
'[^.\s]+\.[^\s]+'.
'|'.

View File

@ -87,18 +87,25 @@ class PiwikAnalyticsPlugin extends Plugin
function onEndShowScripts($action)
{
$js1 = 'var pkBaseURL = (("https:" == document.location.protocol) ? "https://'.
$this->piwikroot.'" : "http://'.$this->piwikroot.
'"); document.write(unescape("%3Cscript src=\'" + pkBaseURL + "piwik.js\''.
' type=\'text/javascript\'%3E%3C/script%3E"));';
$js2 = 'piwik_action_name = ""; piwik_idsite = '.$this->piwikid.
'; piwik_url = pkBaseURL + "piwik.php"; piwik_log(piwik_action_name, piwik_idsite, piwik_url);';
$action->elementStart('script', array('type' => 'text/javascript'));
$action->raw($js1);
$action->elementEnd('script');
$action->elementStart('script', array('type' => 'text/javascript'));
$action->raw($js2);
$action->elementEnd('script');
$piwikCode = <<<ENDOFPIWIK
<!-- Piwik -->
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://{$this->piwikroot}" : "http://{$this->piwikroot}");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4);
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
</script>
<!-- End Piwik Tag -->
ENDOFPIWIK;
$action->raw($piwikCode);
return true;
}
}

View File

@ -28,7 +28,8 @@
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$helptext = <<<ENDOFHELP
getvaliddaemons.php - print out the currently configured PID directory
getvaliddaemons.php - print out a list of valid daemons that should be started
by the startdaemons script
ENDOFHELP;

View File

@ -299,25 +299,40 @@ class MailerDaemon
$attachments = array();
$this->extract_part($parsed,$msg,$attachments);
return array($from, $to, $msg, $attachments);
}
function extract_part($parsed,&$msg,&$attachments){
if ($parsed->ctype_primary == 'multipart') {
foreach ($parsed->parts as $part) {
if ($part->ctype_primary == 'text' &&
$part->ctype_secondary == 'plain') {
$msg = $part->body;
}else{
if ($part->body) {
$attachment = tmpfile();
fwrite($attachment, $part->body);
$attachments[] = $attachment;
}
if($parsed->ctype_secondary == 'alternative'){
$altmsg = $this->extract_msg_from_multipart_alternative_part($parsed);
if(!empty($altmsg)) $msg = $altmsg;
}else{
foreach($parsed->parts as $part){
$this->extract_part($part,$msg,$attachments);
}
}
} else if ($type == 'text/plain') {
} else if ($parsed->ctype_primary == 'text'
&& $parsed->ctype_secondary=='plain') {
$msg = $parsed->body;
} else {
$this->unsupported_type($type);
}else if(!empty($parsed->body)){
if(common_config('attachments', 'uploads')){
//only save attachments if uploads are enabled
$attachment = tmpfile();
fwrite($attachment, $parsed->body);
$attachments[] = $attachment;
}
}
return array($from, $to, $msg, $attachments);
}
function extract_msg_from_multipart_alternative_part($parsed){
foreach ($parsed->parts as $part) {
$this->extract_part($part,$msg,$attachments);
}
//we don't want any attachments that are a result of this parsing
return $msg;
}
function unsupported_type($type)

View File

@ -25,19 +25,18 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('MAXCHILDREN', 2);
define('POLL_INTERVAL', 60); // in seconds
$shortoptions = 'i::';
$longoptions = array('id::');
$shortoptions = 'di::';
$longoptions = array('id::', 'debug');
$helptext = <<<END_OF_TRIM_HELP
Batch script for retrieving Twitter messages from foreign service.
-i --id Identity (default 'generic')
-i --id Identity (default 'generic')
-d --debug Debug (lots of log output)
END_OF_TRIM_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/common.php';
require_once INSTALLDIR .'/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/daemon.php';
/**
@ -61,6 +60,15 @@ class TwitterStatusFetcher extends Daemon
{
private $_children = array();
function __construct($id=null, $daemonize=true)
{
parent::__construct($daemonize);
if ($id) {
$this->set_id($id);
}
}
/**
* Name of this daemon
*
@ -80,6 +88,11 @@ class TwitterStatusFetcher extends Daemon
function run()
{
if (defined('SCRIPT_DEBUG')) {
common_debug($this->name() .
': debugging log output enabled.');
}
do {
$flinks = $this->refreshFlinks();
@ -317,7 +330,7 @@ class TwitterStatusFetcher extends Daemon
$notice->rendered = common_render_content($notice->content, $notice);
$notice->source = 'twitter';
$notice->reply_to = null; // XXX lookup reply
$notice->is_local = NOTICE_GATEWAY;
$notice->is_local = Notice::GATEWAY;
if (Event::handle('StartNoticeSave', array(&$notice))) {
$id = $notice->insert();
@ -640,6 +653,10 @@ if (have_option('i')) {
$id = null;
}
if (have_option('d') || have_option('debug')) {
define('SCRIPT_DEBUG', true);
}
$fetcher = new TwitterStatusFetcher($id);
$fetcher->runOnce();

View File

@ -175,6 +175,10 @@ class XMPPDaemon extends Daemon
$user = $this->get_user($from);
// For common_current_user to work
global $_cur;
$_cur = $user;
if (!$user) {
$this->from_site($from, 'Unknown user; go to ' .
common_local_url('imsettings') .
@ -211,6 +215,7 @@ class XMPPDaemon extends Daemon
$user->free();
unset($user);
unset($_cur);
unset($pl['xml']);
$pl['xml'] = null;

View File

@ -482,7 +482,7 @@ height:16px;
}
#form_notice .form_note {
position:absolute;
top:99px;
bottom:2px;
right:98px;
z-index:9;
}
@ -863,7 +863,7 @@ clear:left;
float:left;
font-size:0.95em;
margin-left:59px;
width:60%;
width:50%;
}
#showstream .notice div.entry-content,
#shownotice .notice div.entry-content {

View File

@ -12,7 +12,7 @@ margin:0 auto;
}
#content {
width:70%;
width:69%;
}
#aside_primary {
padding:5%;

View File

@ -12,12 +12,12 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
html { font-size: 100%; background-color:#fff; }
html { font-size: 87.5%; }
body {
background-color:#fff;
color:#000;
font-family:sans-serif;
font-size:0.75em;
font-size:1em;
line-height:normal;
position:relative;
height:100%;
@ -471,6 +471,24 @@ margin-bottom:7px;
#form_notice #notice_submit label {
display:none;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:87px;
cursor:pointer;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
left:82%;
width:16px;
height:16px;
}
#form_notice #notice_data-attach {
left:34.6%;
padding:0;
height:16px;
}
#form_notice .form_note {
position:absolute;
top:-10px;
@ -488,6 +506,7 @@ font-weight:bold;
line-height:1.15;
padding:1px 2px;
}
#form_notice #notice_action-submit {
width:14%;
height:35px;
@ -505,6 +524,26 @@ margin-bottom:7px;
margin-left:18px;
float:left;
}
#form_notice .error,
#form_notice .success {
float:left;
clear:both;
width:81.5%;
margin-bottom:0;
line-height:1.618;
}
#form_notice #notice_data-attach_selected code {
float:left;
width:90%;
display:block;
font-size:1.1em;
line-height:1.8;
overflow:auto;
}
#form_notice #notice_data-attach_selected button {
float:right;
font-size:0.8em;
}
/* entity_profile */
@ -746,17 +785,18 @@ float:left;
width:100%;
border-top-width:1px;
border-top-style:dotted;
font-size:1.2em;
}
.notices li {
list-style-type:none;
line-height:1.1;
width:94%;
padding-right:5%;
padding-left:1%;
min-height:47px;
}
.notices .notices {
margin-top:7px;
margin-left:2%;
width:98%;
float:left;
}
/* NOTICES */
#notices_primary {
@ -805,13 +845,17 @@ text-decoration:underline;
.notice .entry-title {
float:none;
display:inline;
width:100%;
overflow:hidden;
display:inline;
}
#shownotice .notice .entry-title {
font-size:2.2em;
}
#conversation .notice .entry-title {
display:block;
width:90%;
}
.notice p.entry-content {
display:inline;
@ -887,13 +931,12 @@ outline:none;
.notice-options {
padding-left:2%;
float:left;
width:50%;
font-size:0.95em;
width:12.5%;
width:16px;
float:right;
display:none;
}
.notices li.hover div.notice-options {
.notices li:hover div.notice-options {
display:block;
}
@ -917,7 +960,7 @@ top:30px;
right:7px;
}
.notice-options .notice_delete {
bottom:7px;
top:47px;
right:7px;
}
.notice-options .notice_reply dt {
@ -958,6 +1001,97 @@ border:0;
padding:0;
}
.notice .attachment {
position:relative;
padding-left:16px;
}
#attachments .attachment {
padding-left:0;
}
.notice .attachment img {
position:absolute;
top:18px;
left:0;
z-index:99;
}
#shownotice .notice .attachment img {
position:static;
}
#attachments {
clear:both;
float:left;
width:100%;
margin-top:18px;
}
#attachments dt {
font-weight:bold;
font-size:1.3em;
margin-bottom:4px;
}
#attachments ol li {
margin-bottom:18px;
list-style-type:decimal;
float:left;
clear:both;
}
#jOverlayContent,
#jOverlayContent #content,
#jOverlayContent #content_inner {
width: auto !important;
margin-bottom:0;
}
#jOverlayContent #content {
padding:11px;
min-height:auto;
}
#jOverlayContent .external span {
display:block;
margin-bottom:11px;
}
#jOverlayContent button {
position:absolute;
top:0;
right:0;
width:29px;
height:29px;
text-align:center;
font-weight:bold;
padding:0;
}
#jOverlayContent h1 {
max-width:425px;
}
#jOverlayContent #content {
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
#jOverlayLoading {
top:5%;
left:40%;
}
#attachment_view img {
max-width:480px;
max-height:480px;
}
#attachment_view #oembed_info {
margin-top:11px;
}
#attachment_view #oembed_info dt,
#attachment_view #oembed_info dd {
float:left;
}
#attachment_view #oembed_info dt {
clear:left;
margin-right:11px;
font-weight:bold;
}
#attachment_view #oembed_info dt:after {
content: ":";
}
#usergroups #new_group {
float: left;
@ -1218,7 +1352,8 @@ clear:both;
#outbox.user_in #content,
#subscriptions.user_in #content,
#subscribers.user_in #content,
#showgroup.user_in #content {
#showgroup.user_in #content,
#conversation.user_in #content {
padding-top:160px;
}
@ -1243,6 +1378,14 @@ padding-top:160px;
display:none;
}
#jOverlayContent #core #content {
padding-top:11px;
}
#jOverlayContent #core {
background:none;
padding-top:0;
}
html,
body,
@ -1311,24 +1454,23 @@ border-top-color:#87B4C8;
}
#content .notice p.entry-content a:visited {
background-color:#fcfcfc;
}
#content .notice p.entry-content .vcard a {
background-color:#fcfffc;
}
#aside_primary {
background-color:#DDFFCC;
}
#notice_text-count {
color:#333;
color:#000000;
}
#form_notice.warning #notice_text-count {
color:#000;
color:#000000;
}
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
@ -1426,8 +1568,11 @@ background-image:url(../images/icons/twotone/green/shield.gif);
/* NOTICES */
.notices li.over {
background-color:#fcfcfc;
.notice .attachment {
background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
}
#attachments .attachment {
background:none;
}
.notice-options .notice_reply a,
@ -1451,10 +1596,24 @@ background:transparent url(../images/icons/icon_trash.gif) no-repeat 0 45%;
.notices div.notice-options {
opacity:0.4;
}
.notices li.hover div.entry-content,
.notices li.hover div.notice-options {
.notices li:hover div.entry-content,
.notices li:hover div.notice-options {
opacity:1;
}
.notices .notices {
background-color:rgba(200, 200, 200, 0.01);
}
.notices .notices .notices {
background-color:rgba(200, 200, 200, 0.02);
}
.notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.03);
}
.notices .notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.04);
}
div.entry-content {
color:#333;
}
@ -1462,8 +1621,11 @@ div.notice-options a,
div.notice-options input {
font-family:sans-serif;
}
.notices li.hover {
background-color:#fcfcfc;
#content .notices li:hover {
background-color:rgba(240, 240, 240, 0.2);
}
#conversation .notices li:hover {
background-color:transparent;
}
/*END: NOTICES */

View File

@ -8,6 +8,16 @@ color:#fff;
background-color:#ddffcc;
}
#form_notice .form_note + label {
position:absolute;
top:25px;
left:83%;
text-indent:-9999px;
height:16px;
width:16px;
display:block;
}
#aside_primary {
width:181px;
}
@ -32,3 +42,13 @@ top:158px;
#subscribers #content {
padding-top:138px;
}
.notice {
z-index:1;
}
.notice:hover {
z-index:9999;
}
.notice .thumbnail img {
z-index:9999;
}

View File

@ -383,7 +383,7 @@ margin-bottom:1em;
}
#content {
width:49.009%;
width:50%;
min-height:259px;
float:left;
padding:0 18px;
@ -402,7 +402,7 @@ float:left;
width:45.917%;
min-height:259px;
float:left;
margin-left:1.385%;
margin-left:0.25%;
padding-bottom:47px;
}
@ -736,11 +736,10 @@ margin-right:11px;
.notice,
.profile {
position:relative;
padding-top:11px;
padding-bottom:11px;
padding:11px 2%;
clear:both;
float:left;
width:96.41%;
width:95.7%;
border-width:1px;
border-style:solid;
margin-bottom:11px;
@ -993,13 +992,36 @@ font-weight:bold;
padding:0;
}
#jOverlayContent h1 {
max-width:475px;
max-width:425px;
}
#jOverlayContent #content {
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
#jOverlayLoading {
top:5%;
left:40%;
}
#attachment_view img {
max-width:480px;
max-height:480px;
}
#attachment_view #oembed_info {
margin-top:11px;
}
#attachment_view #oembed_info dt,
#attachment_view #oembed_info dd {
float:left;
}
#attachment_view #oembed_info dt {
clear:left;
margin-right:11px;
font-weight:bold;
}
#attachment_view #oembed_info dt:after {
content: ":";
}
#usergroups #new_group {
float: left;
@ -1058,8 +1080,6 @@ top:3px;
left:3px;
}
.pagination {
float:left;
clear:both;
@ -1105,7 +1125,6 @@ padding-right:30px;
}
/* END: NOTICE */
.hentry .entry-content p {
margin-bottom:18px;
}
@ -1122,7 +1141,6 @@ margin-bottom:18px;
margin-left:18px;
}
/* TOP_POSTERS */
.section tbody td {
padding-right:11px;
@ -1150,7 +1168,6 @@ margin-right:0;
display:none;
}
/* tagcloud */
.tag-cloud {
list-style-type:none;
@ -1233,6 +1250,11 @@ clear:both;
margin-bottom:0;
}
#form_settings_design #settings_design_background-image img {
max-width:480px;
max-height:480px;
}
#form_settings_design #settings_design_color .form_data,
#form_settings_design #color-picker {
float:left;

View File

@ -14,7 +14,8 @@ background:url(../images/illustrations/illu_pigeons-01.png) no-repeat 0 100%;
}
body,
a:active {
a:active,
#content {
background-color:#AEA187;
}
body {