Merge commit 'origin/0.8.x' into 0.9.x
This commit is contained in:
commit
db19d61e68
2
README
2
README
@ -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
|
||||
|
@ -129,6 +129,7 @@ class ApiAction extends Action
|
||||
'laconica/config',
|
||||
'laconica/wadl',
|
||||
'tags/timeline',
|
||||
'oembed/oembed',
|
||||
'groups/timeline');
|
||||
|
||||
static $bareauth = array('statuses/user_timeline',
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,4 +171,5 @@ class TwitapilaconicaAction extends TwitterapiAction
|
||||
parent::handle($args);
|
||||
$this->serverError(_('API method under construction.'), 501);
|
||||
}
|
||||
|
||||
}
|
||||
|
173
actions/twitapioembed.php
Normal file
173
actions/twitapioembed.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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).')');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 ¬es_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
|
||||
|
197
install.php
197
install.php
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -73,7 +73,7 @@ class GroupTagCloudSection extends TagCloudSection
|
||||
$quoted = array();
|
||||
|
||||
foreach ($names as $name) {
|
||||
$quoted[] = "\"$name\"";
|
||||
$quoted[] = "'$name'";
|
||||
}
|
||||
|
||||
$namestring = implode(',', $quoted);
|
||||
|
@ -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');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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')),
|
||||
|
@ -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;
|
||||
|
@ -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'));
|
||||
|
@ -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/'));
|
||||
|
@ -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');
|
||||
|
@ -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
77
plugins/FBConnect/README
Normal 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -12,7 +12,7 @@ margin:0 auto;
|
||||
}
|
||||
|
||||
#content {
|
||||
width:70%;
|
||||
width:69%;
|
||||
}
|
||||
#aside_primary {
|
||||
padding:5%;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user