Merge commit 'origin/0.8.x' into 0.9.x

This commit is contained in:
Eric Helgeson 2009-07-21 09:20:47 -05:00
commit db19d61e68
40 changed files with 1072 additions and 308 deletions

2
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

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

@ -275,6 +275,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

@ -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;
}

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

@ -873,8 +873,11 @@ 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);
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
$inbox->query($qry);
@ -896,10 +899,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

@ -24,6 +24,10 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
// We keep 5 pages of inbox notices in memcache, +1 for pagination check
define('INBOX_CACHE_WINDOW', 101);
define('NOTICE_INBOX_GC_BOXCAR', 128);
define('NOTICE_INBOX_GC_MAX', 12800);
define('NOTICE_INBOX_LIMIT', 1000);
define('NOTICE_INBOX_SOFT_LIMIT', 1000);
define('NOTICE_INBOX_SOURCE_SUB', 1);
define('NOTICE_INBOX_SOURCE_GROUP', 2);
@ -100,4 +104,41 @@ class Notice_inbox extends Memcached_DataObject
{
return Memcached_DataObject::pkeyGet('Notice_inbox', $kv);
}
static function gc($user_id)
{
$entry = new Notice_inbox();
$entry->user_id = $user_id;
$entry->orderBy('created DESC');
$entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX);
$total = $entry->find();
if ($total > 0) {
$notices = array();
$cnt = 0;
while ($entry->fetch()) {
$notices[] = $entry->notice_id;
$cnt++;
if ($cnt >= NOTICE_INBOX_GC_BOXCAR) {
self::deleteMatching($user_id, $notices);
$notices = array();
$cnt = 0;
}
}
if ($cnt > 0) {
self::deleteMatching($user_id, $notices);
$notices = array();
}
}
}
static function deleteMatching($user_id, $notices)
{
$entry = new Notice_inbox();
return $entry->query('DELETE FROM notice_inbox '.
'WHERE user_id = ' . $user_id . ' ' .
'AND notice_id in ('.implode(',', $notices).')');
}
}

View File

@ -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

@ -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

@ -1,3 +1,4 @@
/* local and remote users have profiles */
create sequence profile_seq;
@ -184,7 +185,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 */,
@ -375,6 +376,20 @@ create table profile_block (
);
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 sequence user_group_seq;
create table user_group (
@ -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' */
@ -486,19 +503,26 @@ create table file_to_post (
unique(file_id, post_id)
);
create sequence design_seq;
create table design (
id bigint default nextval('design_seq') /* comment 'design ID'*/,
backgroundcolor integer /* comment 'main background color'*/ ,
contentcolor integer /*comment 'content area background color'*/ ,
sidebarcolor integer /*comment 'sidebar background color'*/ ,
textcolor integer /*comment 'text color'*/ ,
linkcolor integer /*comment 'link color'*/,
backgroundimage varchar(255) /*comment 'background image, if any'*/,
disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/,
primary key (id)
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

@ -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

@ -107,13 +107,13 @@ class Facebook {
* @param bool resolve_auth_token convert an auth token into a session
*/
public function validate_fb_params($resolve_auth_token=true) {
$this->fb_params = $this->get_valid_fb_params($_POST, 48*3600, 'fb_sig');
$this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
// note that with preload FQL, it's possible to receive POST params in
// addition to GET, so use a different prefix to differentiate them
if (!$this->fb_params) {
$fb_params = $this->get_valid_fb_params($_GET, 48*3600, 'fb_sig');
$fb_post_params = $this->get_valid_fb_params($_POST, 48*3600, 'fb_post_sig');
$fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
$fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
$this->fb_params = array_merge($fb_params, $fb_post_params);
}

View File

@ -55,6 +55,7 @@ class FacebookRestClient {
private $pending_batch;
private $call_as_apikey;
private $use_curl_if_available;
private $format = null;
const BATCH_MODE_DEFAULT = 0;
const BATCH_MODE_SERVER_PARALLEL = 0;
@ -178,39 +179,32 @@ function toggleDisplay(id, type) {
private function execute_server_side_batch() {
$item_count = count($this->batch_queue);
$method_feed = array();
foreach($this->batch_queue as $batch_item) {
foreach ($this->batch_queue as $batch_item) {
$method = $batch_item['m'];
$params = $batch_item['p'];
$this->finalize_params($method, $params);
$method_feed[] = $this->create_post_string($method, $params);
list($get, $post) = $this->finalize_params($method, $params);
$method_feed[] = $this->create_url_string(array_merge($post, $get));
}
$method_feed_json = json_encode($method_feed);
$serial_only =
($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
$params = array('method_feed' => $method_feed_json,
'serial_only' => $serial_only);
if ($this->call_as_apikey) {
$params['call_as_apikey'] = $this->call_as_apikey;
}
$xml = $this->post_request('batch.run', $params);
$result = $this->convert_xml_to_result($xml, 'batch.run', $params);
$params = array('method_feed' => json_encode($method_feed),
'serial_only' => $serial_only,
'format' => $this->format);
$result = $this->call_method('facebook.batch.run', $params);
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookRestClientException($result['error_msg'],
$result['error_code']);
}
for($i = 0; $i < $item_count; $i++) {
for ($i = 0; $i < $item_count; $i++) {
$batch_item = $this->batch_queue[$i];
$batch_item_result_xml = $result[$i];
$batch_item_result = $this->convert_xml_to_result($batch_item_result_xml,
$batch_item['m'],
$batch_item['p']);
$batch_item['p']['format'] = $this->format;
$batch_item_result = $this->convert_result($result[$i],
$batch_item['m'],
$batch_item['p']);
if (is_array($batch_item_result) &&
isset($batch_item_result['error_code'])) {
@ -516,12 +510,20 @@ function toggleDisplay(id, type) {
* behalf of app. Successful creation guarantees app will be admin.
*
* @param assoc array $event_info json encoded event information
* @param string $file (Optional) filename of picture to set
*
* @return int event id
*/
public function &events_create($event_info) {
return $this->call_method('facebook.events.create',
public function events_create($event_info, $file = null) {
if ($file) {
return $this->call_upload_method('facebook.events.create',
array('event_info' => $event_info),
$file,
Facebook::get_facebook_url('api-photo') . '/restserver.php');
} else {
return $this->call_method('facebook.events.create',
array('event_info' => $event_info));
}
}
/**
@ -529,13 +531,21 @@ function toggleDisplay(id, type) {
*
* @param int $eid event id
* @param assoc array $event_info json encoded event information
* @param string $file (Optional) filename of new picture to set
*
* @return bool true if successful
*/
public function &events_edit($eid, $event_info) {
return $this->call_method('facebook.events.edit',
public function events_edit($eid, $event_info, $file = null) {
if ($file) {
return $this->call_upload_method('facebook.events.edit',
array('eid' => $eid, 'event_info' => $event_info),
$file,
Facebook::get_facebook_url('api-photo') . '/restserver.php');
} else {
return $this->call_method('facebook.events.edit',
array('eid' => $eid,
'event_info' => $event_info));
'event_info' => $event_info));
}
}
/**
@ -935,7 +945,7 @@ function toggleDisplay(id, type) {
/**
* Makes an FQL query. This is a generalized way of accessing all the data
* in the API, as an alternative to most of the other method calls. More
* info at http://developers.facebook.com/documentation.php?v=1.0&doc=fql
* info at http://wiki.developers.facebook.com/index.php/FQL
*
* @param string $query the query to evaluate
*
@ -946,6 +956,21 @@ function toggleDisplay(id, type) {
array('query' => $query));
}
/**
* Makes a set of FQL queries in parallel. This method takes a dictionary
* of FQL queries where the keys are names for the queries. Results from
* one query can be used within another query to fetch additional data. More
* info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL
*
* @param string $queries JSON-encoded dictionary of queries to evaluate
*
* @return array generalized array representing the results
*/
public function &fql_multiquery($queries) {
return $this->call_method('facebook.fql.multiquery',
array('queries' => $queries));
}
/**
* Returns whether or not pairs of users are friends.
* Note that the Facebook friend relationship is symmetric.
@ -994,6 +1019,23 @@ function toggleDisplay(id, type) {
}
/**
* Returns the mutual friends between the target uid and a source uid or
* the current session user.
*
* @param int $target_uid Target uid for which mutual friends will be found.
* @param int $source_uid (optional) Source uid for which mutual friends will
* be found. If no source_uid is specified,
* source_id will default to the session
* user.
* @return array An array of friend uids
*/
public function &friends_getMutualFriends($target_uid, $source_uid = null) {
return $this->call_method('facebook.friends.getMutualFriends',
array("target_uid" => $target_uid,
"source_uid" => $source_uid));
}
/**
* Returns the set of friend lists for the current session user.
*
@ -1168,6 +1210,44 @@ function toggleDisplay(id, type) {
array('permissions_apikey' => $permissions_apikey));
}
/**
* Payments Order API
*/
/**
* Set Payments properties for an app.
*
* @param properties a map from property names to values
* @return true on success
*/
public function payments_setProperties($properties) {
return $this->call_method ('facebook.payments.setProperties',
array('properties' => json_encode($properties)));
}
public function payments_getOrderDetails($order_id) {
return json_decode($this->call_method(
'facebook.payments.getOrderDetails',
array('order_id' => $order_id)), true);
}
public function payments_updateOrder($order_id, $status,
$params) {
return $this->call_method('facebook.payments.updateOrder',
array('order_id' => $order_id,
'status' => $status,
'params' => json_encode($params)));
}
public function payments_getOrders($status, $start_time,
$end_time, $test_mode=false) {
return json_decode($this->call_method('facebook.payments.getOrders',
array('status' => $status,
'start_time' => $start_time,
'end_time' => $end_time,
'test_mode' => $test_mode)), true);
}
/**
* Creates a note with the specified title and content.
*
@ -1233,7 +1313,6 @@ function toggleDisplay(id, type) {
* notes.
*/
public function &notes_get($uid, $note_ids = null) {
return $this->call_method('notes.get',
array('uid' => $uid,
'note_ids' => $note_ids));
@ -1631,6 +1710,63 @@ function toggleDisplay(id, type) {
return $this->call_method('facebook.users.setStatus', $args);
}
/**
* Gets the comments for a particular xid. This is essentially a wrapper
* around the comment FQL table.
*
* @param string $xid external id associated with the comments
*
* @return array of comment objects
*/
public function &comments_get($xid) {
$args = array('xid' => $xid);
return $this->call_method('facebook.comments.get', $args);
}
/**
* Add a comment to a particular xid on behalf of a user. If called
* without an app_secret (with session secret), this will only work
* for the session user.
*
* @param string $xid external id associated with the comments
* @param string $text text of the comment
* @param int $uid user adding the comment (def: session user)
* @param string $title optional title for the stream story
* @param string $url optional url for the stream story
* @param bool $publish_to_stream publish a feed story about this comment?
* a link will be generated to title/url in the story
*
* @return string comment_id associated with the comment
*/
public function &comments_add($xid, $text, $uid=0, $title='', $url='',
$publish_to_stream=false) {
$args = array(
'xid' => $xid,
'uid' => $this->get_uid($uid),
'text' => $text,
'title' => $title,
'url' => $url,
'publish_to_stream' => $publish_to_stream);
return $this->call_method('facebook.comments.add', $args);
}
/**
* Remove a particular comment.
*
* @param string $xid the external id associated with the comments
* @param string $comment_id id of the comment to remove (returned by
* comments.add and comments.get)
*
* @return boolean
*/
public function &comments_remove($xid, $comment_id) {
$args = array(
'xid' => $xid,
'comment_id' => $comment_id);
return $this->call_method('facebook.comments.remove', $args);
}
/**
* Gets the stream on behalf of a user using a set of users. This
* call will return the latest $limit queries between $start_time
@ -1642,11 +1778,16 @@ function toggleDisplay(id, type) {
* @param int $end_time end time to look for stories (def: now)
* @param int $limit number of stories to attempt to fetch (def: 30)
* @param string $filter_key key returned by stream.getFilters to fetch
* @param array $metadata metadata to include with the return, allows
* requested metadata to be returned, such as
* profiles, albums, photo_tags
*
* @return array(
* 'posts' => array of posts,
* 'profiles' => array of profile metadata of users/pages in posts
* 'albums' => array of album metadata in posts
* 'posts' => array of posts,
* // if requested, the following data may be returned
* 'profiles' => array of profile metadata of users/pages in posts
* 'albums' => array of album metadata in posts
* 'photo_tags' => array of photo_tags for photos in posts
* )
*/
public function &stream_get($viewer_id = null,
@ -2849,6 +2990,7 @@ function toggleDisplay(id, type) {
array('uids' => $uids ? json_encode($uids) : null));
}
/* UTILITY FUNCTIONS */
/**
@ -2862,18 +3004,15 @@ function toggleDisplay(id, type) {
* See: http://wiki.developers.facebook.com/index.php/Using_batching_API
*/
public function &call_method($method, $params = array()) {
if ($this->format) {
$params['format'] = $this->format;
}
if (!$this->pending_batch()) {
if ($this->call_as_apikey) {
$params['call_as_apikey'] = $this->call_as_apikey;
}
$data = $this->post_request($method, $params);
if (empty($params['format']) || strtolower($params['format']) != 'json') {
$result = $this->convert_xml_to_result($data, $method, $params);
}
else {
$result = json_decode($data, true);
}
$result = $this->convert_result($data, $method, $params);
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookRestClientException($result['error_msg'],
$result['error_code']);
@ -2888,6 +3027,32 @@ function toggleDisplay(id, type) {
return $result;
}
protected function convert_result($data, $method, $params) {
$is_xml = (empty($params['format']) ||
strtolower($params['format']) != 'json');
return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params)
: json_decode($data, true);
}
/**
* Change the response format
*
* @param string $format The response format (json, xml)
*/
public function setFormat($format) {
$this->format = $format;
return $this;
}
/**
* get the current response serialization format
*
* @return string 'xml', 'json', or null (which means 'xml')
*/
public function getFormat() {
return $this->format;
}
/**
* Calls the specified file-upload POST method with the specified parameters
*
@ -2906,8 +3071,14 @@ function toggleDisplay(id, type) {
throw new FacebookRestClientException($description, $code);
}
$xml = $this->post_upload_request($method, $params, $file, $server_addr);
$result = $this->convert_xml_to_result($xml, $method, $params);
if ($this->format) {
$params['format'] = $this->format;
}
$data = $this->post_upload_request($method,
$params,
$file,
$server_addr);
$result = $this->convert_result($data, $method, $params);
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookRestClientException($result['error_msg'],
@ -2946,11 +3117,13 @@ function toggleDisplay(id, type) {
return $result;
}
private function finalize_params($method, &$params) {
$this->add_standard_params($method, $params);
protected function finalize_params($method, $params) {
list($get, $post) = $this->add_standard_params($method, $params);
// we need to do this before signing the params
$this->convert_array_values_to_json($params);
$params['sig'] = Facebook::generate_sig($params, $this->secret);
$this->convert_array_values_to_json($post);
$post['sig'] = Facebook::generate_sig(array_merge($get, $post),
$this->secret);
return array($get, $post);
}
private function convert_array_values_to_json(&$params) {
@ -2961,28 +3134,38 @@ function toggleDisplay(id, type) {
}
}
private function add_standard_params($method, &$params) {
/**
* Add the generally required params to our request.
* Params method, api_key, and v should be sent over as get.
*/
private function add_standard_params($method, $params) {
$post = $params;
$get = array();
if ($this->call_as_apikey) {
$params['call_as_apikey'] = $this->call_as_apikey;
$get['call_as_apikey'] = $this->call_as_apikey;
}
$params['method'] = $method;
$params['session_key'] = $this->session_key;
$params['api_key'] = $this->api_key;
$params['call_id'] = microtime(true);
if ($params['call_id'] <= $this->last_call_id) {
$params['call_id'] = $this->last_call_id + 0.001;
$get['method'] = $method;
$get['session_key'] = $this->session_key;
$get['api_key'] = $this->api_key;
$post['call_id'] = microtime(true);
if ($post['call_id'] <= $this->last_call_id) {
$post['call_id'] = $this->last_call_id + 0.001;
}
$this->last_call_id = $params['call_id'];
if (!isset($params['v'])) {
$params['v'] = '1.0';
$this->last_call_id = $post['call_id'];
if (isset($post['v'])) {
$get['v'] = $post['v'];
unset($post['v']);
} else {
$get['v'] = '1.0';
}
if (isset($this->use_ssl_resources) &&
$this->use_ssl_resources) {
$params['return_ssl_resources'] = true;
$post['return_ssl_resources'] = true;
}
return array($get, $post);
}
private function create_post_string($method, $params) {
private function create_url_string($params) {
$post_params = array();
foreach ($params as $key => &$val) {
$post_params[] = $key.'='.urlencode($val);
@ -3022,48 +3205,64 @@ function toggleDisplay(id, type) {
}
public function post_request($method, $params) {
$this->finalize_params($method, $params);
$post_string = $this->create_post_string($method, $params);
list($get, $post) = $this->finalize_params($method, $params);
$post_string = $this->create_url_string($post);
$get_string = $this->create_url_string($get);
$url_with_get = $this->server_addr . '?' . $get_string;
if ($this->use_curl_if_available && function_exists('curl_init')) {
$useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->server_addr);
curl_setopt($ch, CURLOPT_URL, $url_with_get);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$result = curl_exec($ch);
$result = $this->curl_exec($ch);
curl_close($ch);
} else {
$content_type = 'application/x-www-form-urlencoded';
$content = $post_string;
$result = $this->run_http_post_transaction($content_type,
$content,
$this->server_addr);
$url_with_get);
}
return $result;
}
/**
* execute a curl transaction -- this exists mostly so subclasses can add
* extra options and/or process the response, if they wish.
*
* @param resource $ch a curl handle
*/
protected function curl_exec($ch) {
$result = curl_exec($ch);
return $result;
}
private function post_upload_request($method, $params, $file, $server_addr = null) {
$server_addr = $server_addr ? $server_addr : $this->server_addr;
$this->finalize_params($method, $params);
list($get, $post) = $this->finalize_params($method, $params);
$get_string = $this->create_url_string($get);
$url_with_get = $server_addr . '?' . $get_string;
if ($this->use_curl_if_available && function_exists('curl_init')) {
// prepending '@' causes cURL to upload the file; the key is ignored.
$params['_file'] = '@' . $file;
$post['_file'] = '@' . $file;
$useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $server_addr);
curl_setopt($ch, CURLOPT_URL, $url_with_get);
// this has to come before the POSTFIELDS set!
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POST, 1);
// passing an array gets curl to use the multipart/form-data content type
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
$result = curl_exec($ch);
$result = $this->curl_exec($ch);
curl_close($ch);
} else {
$result = $this->run_multipart_http_transaction($method, $params, $file, $server_addr);
$result = $this->run_multipart_http_transaction($method, $post,
$file, $url_with_get);
}
return $result;
}
@ -3110,7 +3309,7 @@ function toggleDisplay(id, type) {
}
}
private function get_uid($uid) {
protected function get_uid($uid) {
return $uid ? $uid : $this->user;
}
}
@ -3145,6 +3344,7 @@ class FacebookAPIErrorCodes {
const API_EC_DEPRECATED = 11;
const API_EC_VERSION = 12;
const API_EC_INTERNAL_FQL_ERROR = 13;
const API_EC_HOST_PUP = 14;
/*
* PARAMETER ERRORS
@ -3179,6 +3379,7 @@ class FacebookAPIErrorCodes {
const API_EC_PERMISSION = 200;
const API_EC_PERMISSION_USER = 210;
const API_EC_PERMISSION_NO_DEVELOPERS = 211;
const API_EC_PERMISSION_OFFLINE_ACCESS = 212;
const API_EC_PERMISSION_ALBUM = 220;
const API_EC_PERMISSION_PHOTO = 221;
const API_EC_PERMISSION_MESSAGE = 230;
@ -3267,6 +3468,7 @@ class FacebookAPIErrorCodes {
const FQL_EC_DEPRECATED_TABLE = 611;
const FQL_EC_EXTENDED_PERMISSION = 612;
const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
const FQL_EC_UNRESOLVED_DEPENDENCY = 614;
const API_EC_REF_SET_FAILED = 700;
@ -3318,6 +3520,21 @@ class FacebookAPIErrorCodes {
const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
/*
* PAYMENTS API ERRORS
*/
const API_EC_PAYMENTS_UNKNOWN = 1150;
const API_EC_PAYMENTS_APP_INVALID = 1151;
const API_EC_PAYMENTS_DATABASE = 1152;
const API_EC_PAYMENTS_PERMISSION_DENIED = 1153;
const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154;
const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155;
const API_EC_PAYMENTS_INVALID_ORDER = 1156;
const API_EC_PAYMENTS_INVALID_PARAM = 1157;
const API_EC_PAYMENTS_INVALID_OPERATION = 1158;
const API_EC_PAYMENTS_PAYMENT_FAILED = 1159;
const API_EC_PAYMENTS_DISABLED = 1160;
/*
* CONNECT SESSION ERRORS
*/
@ -3347,6 +3564,7 @@ class FacebookAPIErrorCodes {
const API_EC_COMMENTS_INVALID_XID = 1703;
const API_EC_COMMENTS_INVALID_UID = 1704;
const API_EC_COMMENTS_INVALID_POST = 1705;
const API_EC_COMMENTS_INVALID_REMOVE = 1706;
/**
* This array is no longer maintained; to view the description of an error

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>
@ -127,7 +131,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 +151,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 +175,7 @@ function handlePost()
<?php
$host = $_POST['host'];
$dbtype = $_POST['dbtype'];
$database = $_POST['database'];
$username = $_POST['username'];
$password = $_POST['password'];
@ -191,64 +204,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 +233,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 +340,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 +355,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

@ -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

@ -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

@ -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',
@ -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
@ -234,6 +235,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){
@ -268,6 +274,12 @@ class Rss10Action extends Action
foreach ($tags as $tag)
{
$tagpage = common_local_url('tag', array('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));
$this->elementStart('ctag:tagged');
$this->elementStart('ctag:Tag', array('rdf:about'=>$tagpage.'#concept', 'ctag:label'=>$tag));
@ -275,6 +287,8 @@ class Rss10Action extends Action
$this->element('rdfs:seeAlso', array('rdf:resource'=>$tagrss));
$this->elementEnd('ctag:Tag');
$this->elementEnd('ctag:tagged');
$this->tags_already_output[] = $tag;
}
}
$this->elementEnd('item');
@ -320,6 +334,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,8 @@ class TwitterapiAction extends Action
}
}
}
# RSS Item specific
*/
// RSS Item specific
$entry['description'] = $entry['content'];
$entry['pubDate'] = common_date_rfc2822($notice->created);
$entry['guid'] = $entry['link'];
@ -369,6 +391,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 +414,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');

View File

@ -122,7 +122,9 @@ class FBConnectPlugin extends Plugin
FB_RequireFeatures(
["XFBML"],
function() {
FB.Facebook.init("%s", "../xd_receiver.html");
FB.init("%s", "../xd_receiver.html",
{"doNotUseCachedConnectState":true });
}
); }
@ -220,11 +222,11 @@ class FBConnectPlugin extends Plugin
try {
$facebook = getFacebook();
$fbuid = getFacebook()->get_loggedin_user();
$fbuid = $facebook->api_client->users_getLoggedInUser();
} catch (Exception $e) {
common_log(LOG_WARNING,
'Problem getting Facebook client: ' .
'Problem getting Facebook user: ' .
$e->getMessage());
}
@ -297,9 +299,9 @@ class FBConnectPlugin extends Plugin
$title = _('Logout from the site');
$text = _('Logout');
$html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' .
'onclick="FB.Connect.logout(function() { goto_logout() })">%s</a></li>',
$logout_url, $title, $text);
$html = sprintf('<li id="nav_logout"><a href="#" title="%s" ' .
'onclick="FB.Connect.logoutAndRedirect(\'%s\');">%s</a></li>',
$title, $logout_url, $text);
$action->raw($html);

77
plugins/FBConnect/README Normal file
View File

@ -0,0 +1,77 @@
This plugin allows you to utilize Facebook Connect with Laconica.
Supported Facebook Connect features:
- Authenticate (register/login/logout -- works similar to OpenID)
- Associate an existing Laconica account with a Facebook account
- Disconnect a Facebook account from a Laconica account
Future planned functionality:
- Invite Facebook friends to use your Laconica installation
- Auto-subscribe Facebook friends already using Laconica
- Share Laconica favorite notices to your Facebook stream
To use the plugin you will need to configure a Facebook application
to point to your Laconica installation (see the Installation section
below).
Installation
============
If you don't already have the built-in Facebook application configured,
you'll need to log into Facebook and create/configure a new application.
Please follow the instructions in the section titled, "Setting Up Your
Application and Getting an API Key," on the following page of the
Facebook developer wiki:
http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site
If you already are using the build-in Laconica Facebook application,
you can modify your existing application's configuration using the
Facebook Developer Application on Facebook. Use it to edit your
application settings, and under the 'Connect' tab, change the 'Connect
URL' to be the main URL for your Laconica site. E.g.:
http://SITE/PATH_TO_LACONICA/
After you application is created and configured, you'll need to add its
API key and secret to your Laconica config.php file:
$config['facebook']['apikey'] = 'APIKEY';
$config['facebook']['secret'] = 'SECRET';
Finally, to enable the plugin, add the following stanza to your
config.php:
require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
$fbc = new FBConnectPlugin();
To try out the plugin, fire up your browser and connect to:
http://SITE/PATH_TO_LACONICA/main/facebooklogin
or, if you do not have fancy URLs turned on:
http://SITE/PATH_TO_LACONICA/index.php/main/facebooklogin
You should see a page with a blue button that says: "Connect with
Facebook".
Connect/Disconnect existing account
===================================
If the Facebook Connect plugin is enabled, there will be a new Facebook
Connect Settings tab under each user's Connect menu. Users can connect
and disconnect to their Facebook accounts from it. Note: Before a user
can disconnect from Facebook, she must set a normal Laconica password.
Otherwise, she might not be able to login in to her account in the
future. This is usually only required for users who have used Facebook
Connect to register their Laconica account, and therefore haven't
already set a local password.
Helpful links
=============
Facebook Connect Homepage:
http://developers.facebook.com/connect.php

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

@ -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

@ -52,43 +52,5 @@ if (!empty($id)) {
$cnt = $user->find();
while ($user->fetch()) {
$inbox_entry = new Notice_inbox();
$inbox_entry->user_id = $user->id;
$inbox_entry->orderBy('created DESC');
$inbox_entry->limit(1000, 1);
$id = null;
if ($inbox_entry->find(true)) {
$id = $inbox_entry->notice_id;
}
$inbox_entry->free();
unset($inbox_entry);
if (is_null($id)) {
continue;
}
$start = microtime(true);
$old_inbox = new Notice_inbox();
$cnt = $old_inbox->query('DELETE from notice_inbox WHERE user_id = ' . $user->id . ' AND notice_id < ' . $id);
$old_inbox->free();
unset($old_inbox);
print "Deleted $cnt notices for $user->nickname ($user->id).\n";
$finish = microtime(true);
$delay = 3.0 * ($finish - $start);
print "Delaying $delay seconds...";
// Wait to let slaves catch up
usleep($delay * 1000000);
print "DONE.\n";
Notice_inbox::gc($user->id);
}

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();
@ -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

@ -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

@ -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 {