Merge branch '0.8.x' of git://gitorious.org/laconica/dev into dev/0.8.x

This commit is contained in:
Evan Prodromou 2009-06-10 04:46:20 +00:00
commit 4110838cb4
85 changed files with 4268 additions and 1770 deletions

6
.gitignore vendored
View File

@ -1,5 +1,6 @@
avatar/*
files/*
file/*
_darcs/*
logs/*
config.php
@ -16,3 +17,8 @@ dataobject.ini
.buildpath
.project
.settings
TODO.rym
config-*.php
good-config.php
lac08.log
php.log

91
README
View File

@ -694,6 +694,13 @@ to users on a remote site. (Or not... it's not well tested.) The
Upgrading
=========
IMPORTANT NOTE: Laconica 0.7.4 introduced a fix for some
incorrectly-stored international characters ("UTF-8"). For new
installations, it will now store non-ASCII characters correctly.
However, older installations will have the incorrect storage, and will
consequently show up "wrong" in browsers. See below for how to deal
with this situation.
If you've been using Laconica 0.6, 0.5 or lower, or if you've been
tracking the "git" version of the software, you will probably want
to upgrade and keep your existing data. There is no automated upgrade
@ -783,6 +790,29 @@ problem.
3. When fixup_inboxes is finished, you can set the enabled flag to
'true'.
UTF-8 Database
--------------
Laconica 0.7.4 introduced a fix for some incorrectly-stored
international characters ("UTF-8"). This fix is not
backwards-compatible; installations from before 0.7.4 will show
non-ASCII characters of old notices incorrectly. This section explains
what to do.
0. You can disable the new behaviour by setting the 'db''utf8' config
option to "false". You should only do this until you're ready to
convert your DB to the new format.
1. When you're ready to convert, you can run the fixup_utf8.php script
in the scripts/ subdirectory. If you've had the "new behaviour"
enabled (probably a good idea), you can give the ID of the first
"new" notice as a parameter, and only notices before that one will
be converted. Notices are converted in reverse chronological order,
so the most recent (and visible) ones will be converted first. The
script should work whether or not you have the 'db''utf8' config
option enabled.
2. When you're ready, set $config['db']['utf8'] to true, so that
new notices will be stored correctly.
Configuration options
=====================
@ -910,6 +940,10 @@ mirror: you can set this to an array of DSNs, like the above
and adding the slaves to this array. Note that if you want some
requests to go to the 'database' (master) server, you'll need
to include it in this array, too.
utf8: whether to talk to the database in UTF-8 mode. This is the default
with new installations, but older sites may want to turn it off
until they get their databases fixed up. See "UTF-8 database"
above for details.
syslog
------
@ -1136,6 +1170,63 @@ welcome: nickname of a user account that sends welcome messages to new
If either of these special user accounts are specified, the users should
be created before the configuration is updated.
snapshot
--------
The software will, by default, send statistical snapshots about the
local installation to a stats server on the laconi.ca Web site. This
data is used by the developers to prioritize development decisions. No
identifying data about users or organizations is collected. The data
is available to the public for review. Participating in this survey
helps Laconica developers take your needs into account when updating
the software.
run: string indicating when to run the statistics. Values can be 'web'
(run occasionally at Web time), 'cron' (run from a cron script),
or 'never' (don't ever run). If you set it to 'cron', remember to
schedule the script to run on a regular basis.
frequency: if run value is 'web', how often to report statistics.
Measured in Web hits; depends on how active your site is.
Default is 10000 -- that is, one report every 10000 Web hits,
on average.
reporturl: URL to post statistics to. Defaults to Laconica developers'
report system, but if they go evil or disappear you may
need to update this to another value. Note: if you
don't want to report stats, it's much better to
set 'run' to 'never' than to set this value to something
nonsensical.
attachments
-----------
The software lets users upload files with their notices. You can configure
the types of accepted files by mime types and a trio of quota options:
per file, per user (total), per user per month.
We suggest the use of the pecl file_info extension to handle mime type
detection.
supported: an array of mime types you accept to store and distribute,
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
setup your server to properly reckognize the types you want to
support.
For quotas, be sure you've set the upload_max_filesize and post_max_size
in php.ini to be large enough to handle your upload. In httpd.conf
(if you're using apache), check that the LimitRequestBody directive isn't
set too low (it's optional, so it may not be there at all).
file_quota: maximum size for a single file upload in bytes. A user can send
any amount of notices with attachments as long as each attachment
is smaller than file_quota.
user_quota: total size in bytes a user can store on this server. Each user
can store any number of files as long as their total size does
not exceed the user_quota.
monthly_quota: total size permitted in the current month. This is the total
size in bytes that a user can upload each month.
Troubleshooting
===============

View File

@ -67,6 +67,7 @@ class ApiAction extends Action
$this->process_command();
} else {
# basic authentication failed
common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname.");
$this->show_basic_auth_error();
}
}

View File

@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
exit(1);
}
//require_once INSTALLDIR.'/lib/personalgroupnav.php';
//require_once INSTALLDIR.'/lib/feedlist.php';
require_once INSTALLDIR.'/lib/attachmentlist.php';
/**
@ -67,11 +65,11 @@ class AttachmentAction extends Action
{
parent::prepare($args);
$id = $this->arg('attachment');
if ($id = $this->trimmed('attachment')) {
$this->attachment = File::staticGet($id);
}
$this->attachment = File::staticGet($id);
if (!$this->attachment) {
if (empty($this->attachment)) {
$this->clientError(_('No such attachment.'), 404);
return false;
}
@ -178,10 +176,8 @@ class AttachmentAction extends Action
function showContent()
{
$this->elementStart('ul', array('class' => 'attachments'));
$ali = new Attachment($this->attachment, $this);
$cnt = $ali->show();
$this->elementEnd('ul');
}
/**

View File

@ -45,26 +45,6 @@ require_once INSTALLDIR.'/actions/attachment.php';
class Attachment_ajaxAction extends AttachmentAction
{
/**
* Load attributes based on database arguments
*
* Loads all the DB stuff
*
* @param array $args $_REQUEST array
*
* @return success flag
*/
function prepare($args)
{
parent::prepare($args);
if (!$this->attachment) {
$this->clientError(_('No such attachment.'), 404);
return false;
}
return true;
}
/**
* Show page, a template method.
*
@ -95,8 +75,6 @@ class Attachment_ajaxAction extends AttachmentAction
$this->elementEnd('div');
}
/**
* Last-modified date for page
*

View File

@ -31,9 +31,7 @@ if (!defined('LACONICA')) {
exit(1);
}
//require_once INSTALLDIR.'/lib/personalgroupnav.php';
//require_once INSTALLDIR.'/lib/feedlist.php';
require_once INSTALLDIR.'/actions/attachments.php';
require_once INSTALLDIR.'/actions/attachment.php';
/**
* Show notice attachments
@ -45,39 +43,8 @@ require_once INSTALLDIR.'/actions/attachments.php';
* @link http://laconi.ca/
*/
class Attachments_ajaxAction extends AttachmentsAction
class Attachment_thumbnailAction extends AttachmentAction
{
function showContent()
{
}
/**
* Fill the content area of the page
*
* Shows a single notice list item.
*
* @return void
*/
function showContentBlock()
{
$al = new AttachmentList($this->notice, $this);
$cnt = $al->show();
}
/**
* Extra <head> content
*
* We show the microid(s) for the author, if any.
*
* @return void
*/
function extraHead()
{
}
/**
* Show page, a template method.
*
@ -100,16 +67,52 @@ class Attachments_ajaxAction extends AttachmentsAction
*/
function showCore()
{
$this->elementStart('div', array('id' => 'core'));
if (Event::handle('StartShowContentBlock', array($this))) {
$this->showContentBlock();
Event::handle('EndShowContentBlock', array($this));
$file_thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
if (empty($file_thumbnail->url)) {
return;
}
$this->elementEnd('div');
$this->element('img', array('src' => $file_thumbnail->url, 'alt' => 'Thumbnail'));
}
/**
* Last-modified date for page
*
* When was the content of this page last modified? Based on notice,
* profile, avatar.
*
* @return int last-modified date as unix timestamp
*/
/*
function lastModified()
{
return max(strtotime($this->notice->created),
strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0);
}
*/
/**
* An entity tag for this page
*
* Shows the ETag for the page, based on the notice ID and timestamps
* for the notice, profile, and avatar. It's weak, since we change
* the date text "one hour ago", etc.
*
* @return string etag
*/
/*
function etag()
{
$avtime = ($this->avatar) ?
strtotime($this->avatar->modified) : 0;
return 'W/"' . implode(':', array($this->arg('action'),
common_language(),
$this->notice->id,
strtotime($this->notice->created),
strtotime($this->profile->modified),
$avtime)) . '"';
}
*/
}

View File

@ -1,292 +0,0 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Show notice attachments
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Personal
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008-2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
//require_once INSTALLDIR.'/lib/personalgroupnav.php';
//require_once INSTALLDIR.'/lib/feedlist.php';
require_once INSTALLDIR.'/lib/attachmentlist.php';
/**
* Show notice attachments
*
* @category Personal
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class AttachmentsAction extends Action
{
/**
* Notice object to show
*/
var $notice = null;
/**
* Profile of the notice object
*/
var $profile = null;
/**
* Avatar of the profile of the notice object
*/
var $avatar = null;
/**
* Is this action read-only?
*
* @return boolean true
*/
function isReadOnly($args)
{
return true;
}
/**
* Last-modified date for page
*
* When was the content of this page last modified? Based on notice,
* profile, avatar.
*
* @return int last-modified date as unix timestamp
*/
function lastModified()
{
return max(strtotime($this->notice->created),
strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0);
}
/**
* An entity tag for this page
*
* Shows the ETag for the page, based on the notice ID and timestamps
* for the notice, profile, and avatar. It's weak, since we change
* the date text "one hour ago", etc.
*
* @return string etag
*/
function etag()
{
$avtime = ($this->avatar) ?
strtotime($this->avatar->modified) : 0;
return 'W/"' . implode(':', array($this->arg('action'),
common_language(),
$this->notice->id,
strtotime($this->notice->created),
strtotime($this->profile->modified),
$avtime)) . '"';
}
/**
* Title of the page
*
* @return string title of the page
*/
function title()
{
return sprintf(_('%1$s\'s status on %2$s'),
$this->profile->nickname,
common_exact_date($this->notice->created));
}
/**
* Load attributes based on database arguments
*
* Loads all the DB stuff
*
* @param array $args $_REQUEST array
*
* @return success flag
*/
function prepare($args)
{
parent::prepare($args);
$id = $this->arg('notice');
$this->notice = Notice::staticGet($id);
if (!$this->notice) {
$this->clientError(_('No such notice.'), 404);
return false;
}
/*
// STOP if there are no attachments
// maybe even redirect if there's a single one
// RYM FIXME TODO
$this->clientError(_('No such attachment.'), 404);
return false;
*/
$this->profile = $this->notice->getProfile();
if (!$this->profile) {
$this->serverError(_('Notice has no profile'), 500);
return false;
}
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
return true;
}
/**
* Handle input
*
* Only handles get, so just show the page.
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($this->notice->is_local == 0) {
if (!empty($this->notice->url)) {
common_redirect($this->notice->url, 301);
} else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) {
common_redirect($this->notice->uri, 301);
}
} else {
$f2p = new File_to_post;
$f2p->post_id = $this->notice->id;
$file = new File;
$file->joinAdd($f2p);
$file->selectAdd();
$file->selectAdd('file.id as id');
$count = $file->find(true);
if (!$count) return;
if (1 === $count) {
common_redirect(common_local_url('attachment', array('attachment' => $file->id)), 301);
} else {
$this->showPage();
}
}
}
/**
* Don't show local navigation
*
* @return void
*/
function showLocalNavBlock()
{
}
/**
* Fill the content area of the page
*
* Shows a single notice list item.
*
* @return void
*/
function showContent()
{
$al = new AttachmentList($this->notice, $this);
$cnt = $al->show();
}
/**
* Don't show page notice
*
* @return void
*/
function showPageNoticeBlock()
{
}
/**
* Don't show aside
*
* @return void
*/
function showAside() {
}
/**
* Extra <head> content
*
* We show the microid(s) for the author, if any.
*
* @return void
*/
function extraHead()
{
$user = User::staticGet($this->profile->id);
if (!$user) {
return;
}
if ($user->emailmicroid && $user->email && $this->notice->uri) {
$id = new Microid('mailto:'. $user->email,
$this->notice->uri);
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
if ($user->jabbermicroid && $user->jabber && $this->notice->uri) {
$id = new Microid('xmpp:', $user->jabber,
$this->notice->uri);
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
}
}

View File

@ -11,7 +11,7 @@
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
* Copyright (C) 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -31,7 +31,7 @@ if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/noticelist.php');
require_once INSTALLDIR.'/lib/noticelist.php';
/**
* Conversation tree in the browser
@ -42,9 +42,10 @@ require_once(INSTALLDIR.'/lib/noticelist.php');
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class ConversationAction extends Action
{
var $id = null;
var $id = null;
var $page = null;
/**
@ -69,24 +70,47 @@ class ConversationAction extends Action
return true;
}
/**
* Handle the action
*
* @param array $args Web and URL arguments
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$this->showPage();
}
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _("Conversation");
}
/**
* Show content.
*
* Display a hierarchical unordered list in the content area.
* Uses ConversationTree to do most of the heavy lifting.
*
* @return void
*/
function showContent()
{
// FIXME this needs to be a tree, not a list
$qry = 'SELECT * FROM notice WHERE conversation = %s ';
$offset = ($this->page-1)*NOTICES_PER_PAGE;
$offset = ($this->page-1) * NOTICES_PER_PAGE;
$limit = NOTICES_PER_PAGE + 1;
$txt = sprintf($qry, $this->id);
@ -95,9 +119,9 @@ class ConversationAction extends Action
'notice:conversation:'.$this->id,
$offset, $limit);
$nl = new NoticeList($notices, $this);
$ct = new ConversationTree($notices, $this);
$cnt = $nl->show();
$cnt = $ct->show();
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'conversation', array('id' => $this->id));
@ -105,3 +129,170 @@ class ConversationAction extends Action
}
/**
* Conversation tree
*
* The widget class for displaying a hierarchical list of notices.
*
* @category Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class ConversationTree extends NoticeList
{
var $tree = null;
var $table = null;
/**
* Show the tree of notices
*
* @return void
*/
function show()
{
$cnt = 0;
$this->tree = array();
$this->table = array();
while ($this->notice->fetch()) {
$cnt++;
$id = $this->notice->id;
$notice = clone($this->notice);
$this->table[$id] = $notice;
if (is_null($notice->reply_to)) {
$this->tree['root'] = array($notice->id);
} else if (array_key_exists($notice->reply_to, $this->tree)) {
$this->tree[$notice->reply_to][] = $notice->id;
} else {
$this->tree[$notice->reply_to] = array($notice->id);
}
}
$this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
if (array_key_exists('root', $this->tree)) {
$rootid = $this->tree['root'][0];
$this->showNoticePlus($rootid);
}
$this->out->elementEnd('ol');
$this->out->elementEnd('div');
return $cnt;
}
/**
* Shows a notice plus its list of children.
*
* @param integer $id ID of the notice to show
*
* @return void
*/
function showNoticePlus($id)
{
$notice = $this->table[$id];
// We take responsibility for doing the li
$this->out->elementStart('li', array('class' => 'hentry notice',
'id' => 'notice-' . $this->notice->id));
$item = $this->newListItem($notice);
$item->show();
if (array_key_exists($id, $this->tree)) {
$children = $this->tree[$id];
$this->out->elementStart('ol', array('class' => 'notices'));
foreach ($children as $child) {
$this->showNoticePlus($child);
}
$this->out->elementEnd('ol');
}
$this->out->elementEnd('li');
}
/**
* Override parent class to return our preferred item.
*
* @param Notice $notice Notice to display
*
* @return NoticeListItem a list item to show
*/
function newListItem($notice)
{
return new ConversationTreeItem($notice, $this->out);
}
}
/**
* Conversation tree list item
*
* Special class of NoticeListItem for use inside conversation trees.
*
* @category Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class ConversationTreeItem extends NoticeListItem
{
/**
* start a single notice.
*
* The default creates the <li>; we skip, since the ConversationTree
* takes care of that.
*
* @return void
*/
function showStart()
{
return;
}
/**
* finish the notice
*
* The default closes the <li>; we skip, since the ConversationTree
* takes care of that.
*
* @return void
*/
function showEnd()
{
return;
}
/**
* show link to notice conversation page
*
* Since we're only used on the conversation page, we skip this
*
* @return void
*/
function showContext()
{
return;
}
}

View File

@ -112,8 +112,8 @@ class DeletenoticeAction extends DeleteAction
$this->hidden('token', common_session_token());
$this->hidden('notice', $this->trimmed('notice'));
$this->element('p', null, _('Are you sure you want to delete this notice?'));
$this->submit('form_action-yes', _('Yes'), 'submit form_action-primary', 'yes');
$this->submit('form_action-no', _('No'), 'submit form_action-secondary', 'no');
$this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not delete this notice"));
$this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Delete this notice'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}

View File

@ -70,7 +70,7 @@ class DesignsettingsAction extends AccountSettingsAction
function showContent()
{
$user = common_current_user();
$this->elementStart('form', array('method' => 'POST',
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_design',
'class' => 'form_settings',
'action' =>
@ -82,7 +82,7 @@ class DesignsettingsAction extends AccountSettingsAction
$this->element('legend', null, _('Change background image'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->element('label', array('for' => 'design_ background-image_file'),
$this->element('label', array('for' => 'design_background-image_file'),
_('Upload file'));
$this->element('input', array('name' => 'design_background-image_file',
'type' => 'file',
@ -140,10 +140,12 @@ class DesignsettingsAction extends AccountSettingsAction
$this->elementEnd('ul');
$this->elementEnd('fieldset');
$this->submit('save', _('Save'));
$this->element('input', array('type' => 'reset',
$this->element('input', array('id' => 'settings_design_reset',
'type' => 'reset',
'value' => 'Reset',
'class' => 'form_action-secondary'));
'class' => 'submit form_action-primary',
'title' => _('Reset back to default')));
$this->submit('save', _('Save'), 'submit form_action-secondary', 'save', _('Save design'));
/*TODO: Check submitted form values:
json_encode(form values)

View File

@ -115,7 +115,7 @@ class FacebookhomeAction extends FacebookAction
$flink->foreign_id = $this->fbuid;
$flink->service = FACEBOOK_SERVICE;
$flink->created = common_sql_now();
$flink->set_flags(true, false, false);
$flink->set_flags(true, false, false, false);
$flink_id = $flink->insert();
@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction
function setDefaults()
{
// A default prefix string for notices
$this->facebook->api_client->data_setUserPreference(
FACEBOOK_NOTICE_PREFIX, 'dented: ');
$this->facebook->api_client->data_setUserPreference(
FACEBOOK_PROMPTED_UPDATE_PREF, 'false');
}

View File

@ -17,7 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/facebookaction.php');
@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction
function showFormContent()
{
// Get a list of users who are already using the app for exclusion
$exclude_ids = $this->facebook->api_client->friends_getAppUsers();
$exclude_ids_csv = null;
// fbml needs these as a csv string, not an array
if ($exclude_ids) {
$exclude_ids_csv = implode(',', $exclude_ids);
}
$content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) .
htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
@ -112,30 +104,37 @@ class FacebookinviteAction extends FacebookAction
$multi_params = array('showborder' => 'false');
$multi_params['actiontext'] = $actiontext;
$multi_params['bypass'] = 'cancel';
if ($exclude_ids_csv) {
// Get a list of users who are already using the app for exclusion
$exclude_ids = $this->facebook->api_client->friends_getAppUsers();
$exclude_ids_csv = null;
// fbml needs these as a csv string, not an array
if ($exclude_ids) {
$exclude_ids_csv = implode(',', $exclude_ids);
$multi_params['exclude_ids'] = $exclude_ids_csv;
}
$multi_params['bypass'] = 'cancel';
$this->element('fb:multi-friend-selector', $multi_params);
$this->elementEnd('fb:request-form');
$this->element('h2', null, sprintf(_('Friends already using %s:'),
common_config('site', 'name')));
$this->elementStart('ul', array('id' => 'facebook-friends'));
if ($exclude_ids) {
foreach ($exclude_ids as $friend) {
$this->elementStart('li');
$this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
$this->element('fb:name', array('uid' => $friend,
'capitalize' => 'true'));
$this->elementEnd('li');
$this->element('h2', null, sprintf(_('Friends already using %s:'),
common_config('site', 'name')));
$this->elementStart('ul', array('id' => 'facebook-friends'));
foreach ($exclude_ids as $friend) {
$this->elementStart('li');
$this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
$this->element('fb:name', array('uid' => $friend,
'capitalize' => 'true'));
$this->elementEnd('li');
}
$this->elementEnd("ul");
}
$this->elementEnd("ul");
}
function title()

View File

@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction
$prefix = $this->trimmed('prefix');
$original = clone($this->flink);
$this->flink->set_flags($noticesync, $replysync, false);
$this->flink->set_flags($noticesync, $replysync, false, false);
$result = $this->flink->update($original);
$this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX,

40
actions/file.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/actions/shownotice.php');
class FileAction extends ShowNoticeAction
{
function showPage() {
$source_url = common_local_url('file', array('notice' => $this->notice->id));
$query = "select file_redirection.url as url from file join file_redirection on file.id = file_redirection.file_id where file.url = '$source_url'";
$file = new File_redirection;
$file->query($query);
$file->fetch();
if (empty($file->url)) {
die('nothing attached here');
} else {
header("Location: {$file->url}");
die();
}
}
}

View File

@ -84,20 +84,24 @@ class NewnoticeAction extends Action
function handle($args)
{
parent::handle($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// check for this before token since all POST and FILES data
// is losts when size is exceeded
if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
$this->clientError(sprintf(_('The server was unable to handle ' .
'that much POST data (%s bytes) due to its current configuration.'),
$_SERVER['CONTENT_LENGTH']));
}
parent::handle($args);
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
try {
$this->saveNewNotice();
} catch (Exception $e) {
@ -109,6 +113,30 @@ class NewnoticeAction extends Action
}
}
function getUploadedFileType() {
require_once 'MIME/Type.php';
$filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
if (in_array($filetype, common_config('attachments', 'supported'))) {
return $filetype;
}
$media = MIME_Type::getMedia($filetype);
if ('application' !== $media) {
$hint = sprintf(_(' Try using another %s format.'), $media);
} else {
$hint = '';
}
$this->clientError(sprintf(
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
}
function isRespectsQuota($user) {
$file = new File;
$ret = $file->isRespectsQuota($user);
if (true === $ret) return true;
$this->clientError($ret);
}
/**
* Save a new notice, based on arguments
*
@ -131,7 +159,6 @@ class NewnoticeAction extends Action
$this->clientError(_('No content!'));
} else {
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$this->clientError(_('That\'s too long. '.
'Max notice size is 140 chars.'));
@ -158,17 +185,53 @@ class NewnoticeAction extends Action
$replyto = 'false';
}
// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
if (isset($_FILES['attach']['error'])) {
switch ($_FILES['attach']['error']) {
case UPLOAD_ERR_NO_FILE:
// no file uploaded, nothing to do
break;
case UPLOAD_ERR_OK:
$mimetype = $this->getUploadedFileType();
if (!$this->isRespectsQuota($user)) {
die('clientError() should trigger an exception before reaching here.');
}
break;
case UPLOAD_ERR_INI_SIZE:
$this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
case UPLOAD_ERR_FORM_SIZE:
$this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
case UPLOAD_ERR_PARTIAL:
$this->clientError(_('The uploaded file was only partially uploaded.'));
case UPLOAD_ERR_NO_TMP_DIR:
$this->clientError(_('Missing a temporary folder.'));
case UPLOAD_ERR_CANT_WRITE:
$this->clientError(_('Failed to write file to disk.'));
case UPLOAD_ERR_EXTENSION:
$this->clientError(_('File upload stopped by extension.'));
default:
die('Should never reach here.');
}
}
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto);
if (is_string($notice)) {
$this->clientError($notice);
return;
}
if (isset($mimetype)) {
$this->storeFile($notice, $mimetype);
}
$this->saveUrls($notice);
common_broadcast_notice($notice);
if ($this->boolean('ajax')) {
@ -194,6 +257,33 @@ class NewnoticeAction extends Action
}
}
function storeFile($notice, $mimetype) {
$filename = basename($_FILES['attach']['name']);
$destination = "file/{$notice->id}-$filename";
if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) {
$file = new File;
$file->url = common_local_url('file', array('notice' => $notice->id));
$file->size = filesize(INSTALLDIR . "/$destination");
$file->date = time();
$file->mimetype = $mimetype;
if ($file_id = $file->insert()) {
$file_redir = new File_redirection;
$file_redir->url = common_path($destination);
$file_redir->file_id = $file_id;
$file_redir->insert();
$f2p = new File_to_post;
$f2p->file_id = $file_id;
$f2p->post_id = $notice->id;
$f2p->insert();
} else {
$this->clientError(_('There was a database error while saving your file. Please try again.'));
}
} else {
$this->clientError(_('File could not be moved to destination directory.'));
}
}
/** save all urls in the notice to the db
*
* follow redirects and save all available file information
@ -203,7 +293,7 @@ class NewnoticeAction extends Action
*
* @return void
*/
function saveUrls($notice) {
function saveUrls($notice, $uploaded = null) {
common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
}
@ -316,3 +406,4 @@ class NewnoticeAction extends Action
$nli->show();
}
}

View File

@ -208,10 +208,10 @@ class ShownoticeAction extends Action
function showContent()
{
$this->elementStart('ul', array('class' => 'notices'));
$this->elementStart('ol', array('class' => 'notices xoxo'));
$nli = new NoticeListItem($this->notice, $this);
$nli->show();
$this->elementEnd('ul');
$this->elementEnd('ol');
}
/**

View File

@ -49,8 +49,6 @@ class TagAction extends Action
{
$pop = new PopularNoticeSection($this);
$pop->show();
$freqatt = new FrequentAttachmentSection($this);
$freqatt->show();
}
function title()

View File

@ -120,4 +120,30 @@ class File extends Memcached_DataObject
File_to_post::processNew($file_id, $notice_id);
return $x;
}
function isRespectsQuota($user) {
if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) {
return sprintf(_('No file may be larger than %d bytes ' .
'and the file you sent was %d bytes. Try to upload a smaller version.'),
common_config('attachments', 'file_quota'), $_FILES['attach']['size']);
}
$query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
$this->query($query);
$this->fetch();
$total = $this->total + $_FILES['attach']['size'];
if ($total > common_config('attachments', 'user_quota')) {
return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
}
$query .= ' month(modified) = month(now()) and year(modified) = year(now())';
$this->query($query);
$this->fetch();
$total = $this->total + $_FILES['attach']['size'];
if ($total > common_config('attachments', 'monthly_quota')) {
return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
}
return true;
}
}

View File

@ -133,7 +133,7 @@ class File_redirection extends Memcached_DataObject
$file->limit(1);
$file->orderBy('len');
$file->find(true);
if (!empty($file->id)) {
if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) {
return $file->url;
}

View File

@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject
public $__table = 'foreign_link'; // table name
public $user_id; // int(4) primary_key not_null
public $foreign_id; // int(4) primary_key not_null
public $foreign_id; // bigint(8) primary_key not_null unsigned
public $service; // int(4) primary_key not_null
public $credentials; // varchar(255)
public $noticesync; // tinyint(1) not_null default_1

0
classes/Group_inbox.php Executable file → Normal file
View File

0
classes/Group_member.php Executable file → Normal file
View File

View File

@ -227,4 +227,22 @@ class Memcached_DataObject extends DB_DataObject
$c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry);
return new ArrayWrapper($cached);
}
// We overload so that 'SET NAMES "utf8"' is called for
// each connection
function _connect()
{
global $_DB_DATAOBJECT;
$exists = !empty($this->_database_dsn_md5) &&
isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]);
$result = parent::_connect();
if (!$exists) {
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
if (common_config('db', 'utf8')) {
$DB->query('SET NAMES "utf8"');
}
}
return $result;
}
}

View File

@ -277,6 +277,18 @@ class Notice extends Memcached_DataObject
return true;
}
function getUploadedAttachment() {
$post = clone $this;
$query = 'select file.url as uploaded from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"';
$post->query($query);
$post->fetch();
$ret = $post->uploaded;
// var_dump($post);
$post->free();
// die();
return $ret;
}
function hasAttachments() {
$post = clone $this;
$query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id);

0
classes/Related_group.php Executable file → Normal file
View File

0
classes/Status_network.php Executable file → Normal file
View File

0
classes/User_group.php Executable file → Normal file
View File

View File

@ -46,6 +46,64 @@ modified = 384
notice_id = K
user_id = K
[file]
id = 129
url = 2
mimetype = 2
size = 1
title = 2
date = 1
protected = 1
[file__keys]
id = N
[file_oembed]
id = 129
file_id = 1
version = 2
type = 2
provider = 2
provider_url = 2
width = 1
height = 1
html = 34
title = 2
author_name = 2
author_url = 2
url = 2
[file_oembed__keys]
id = N
[file_redirection]
id = 129
url = 2
file_id = 1
redirections = 1
httpcode = 1
[file_redirection__keys]
id = N
[file_thumbnail]
id = 129
file_id = 1
url = 2
width = 1
height = 1
[file_thumbnail__keys]
id = N
[file_to_post]
id = 129
file_id = 1
post_id = 1
[file_to_post__keys]
id = N
[foreign_link]
user_id = 129
foreign_id = 129
@ -392,63 +450,3 @@ modified = 384
[user_openid__keys]
canonical = K
display = U
[file]
id = 129
url = 2
mimetype = 2
size = 1
title = 2
date = 1
protected = 1
[file__keys]
id = N
[file_oembed]
id = 129
file_id = 129
version = 2
type = 2
provider = 2
provider_url = 2
width = 1
height = 1
html = 34
title = 2
author_name = 2
author_url = 2
url = 2
[file_oembed__keys]
id = N
[file_redirection]
id = 129
url = 2
file_id = 129
redirections = 1
httpcode = 1
[file_redirection__keys]
id = N
[file_thumbnail]
id = 129
file_id = 129
url = 2
width = 1
height = 1
[file_thumbnail__keys]
id = N
[file_to_post]
id = 129
file_id = 129
post_id = 129
[file_to_post__keys]
id = N

View File

@ -3,219 +3,223 @@
if (!defined('LACONICA')) { exit(1); }
#If you have downloaded libraries in random little places, you
#can add the paths here
// If you have downloaded libraries in random little places, you
// can add the paths here
#$extra_path = array("/opt/php-openid-2.0.1", "/usr/local/share/php");
#set_include_path(implode(PATH_SEPARATOR, $extra_path) . PATH_SEPARATOR . get_include_path());
// $extra_path = array("/opt/php-openid-2.0.1", "/usr/local/share/php");
// set_include_path(implode(PATH_SEPARATOR, $extra_path) . PATH_SEPARATOR . get_include_path());
# We get called by common.php, $config is a tree with lots of config
# options
# These are for configuring your URLs
// We get called by common.php, $config is a tree with lots of config
// options
// These are for configuring your URLs
$config['site']['name'] = 'Just another Laconica microblog';
$config['site']['server'] = 'localhost';
$config['site']['path'] = 'laconica';
#$config['site']['fancy'] = false;
#$config['site']['theme'] = 'default';
#To enable the built-in mobile style sheet, defaults to false.
#$config['site']['mobile'] = true;
#For contact email, defaults to $_SERVER["SERVER_ADMIN"]
#$config['site']['email'] = 'admin@example.net';
#Brought by...
#$config['site']['broughtby'] = 'Individual or Company';
#$config['site']['broughtbyurl'] = 'http://example.net/';
#If you don't want to let users register (say, for a one-person install)
#Crude but effective -- register everybody, then lock down
#$config['site']['closed'] = true;
#Only allow registration for people invited by another user
#$config['site']['inviteonly'] = true;
#Make the site invisible to non-logged-in users
#$config['site']['private'] = true;
// $config['site']['fancy'] = false;
// $config['site']['theme'] = 'default';
// To enable the built-in mobile style sheet, defaults to false.
// $config['site']['mobile'] = true;
// For contact email, defaults to $_SERVER["SERVER_ADMIN"]
// $config['site']['email'] = 'admin@example.net';
// Brought by...
// $config['site']['broughtby'] = 'Individual or Company';
// $config['site']['broughtbyurl'] = 'http://example.net/';
// If you don't want to let users register (say, for a one-person install)
// Crude but effective -- register everybody, then lock down
// $config['site']['closed'] = true;
// Only allow registration for people invited by another user
// $config['site']['inviteonly'] = true;
// Make the site invisible to non-logged-in users
// $config['site']['private'] = true;
# 'direct' for direct notice links in sections
# 'attachment' for notice attachment links in sections
# left undefined, no link is showed
#$config['site']['notice_link'] = 'attachment';
#$config['site']['notice_link'] = 'direct';
// If you want logging sent to a file instead of syslog
// $config['site']['logfile'] = '/tmp/laconica.log';
# If you want logging sent to a file instead of syslog
#$config['site']['logfile'] = '/tmp/laconica.log';
// Enables extra log information, for example full details of PEAR DB errors
// $config['site']['logdebug'] = true;
# Enables extra log information, for example full details of PEAR DB errors
#$config['site']['logdebug'] = true;
// To set your own logo, overriding the one in the theme
// $config['site']['logo'] = '/mylogo.png';
#To set your own logo, overriding the one in the theme
#$config['site']['logo'] = '/mylogo.png';
# This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php
# Set it to match your actual database
// This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php
// Set it to match your actual database
$config['db']['database'] = 'mysql://laconica:microblog@localhost/laconica';
#$config['db']['ini_your_db_name'] = $config['db']['schema_location'].'/laconica.ini';
# *** WARNING *** WARNING *** WARNING *** WARNING ***
# Setting debug to a non-zero value will expose your DATABASE PASSWORD to Web users.
# !!!!!! DO NOT SET THIS ON PRODUCTION SERVERS !!!!!! DB_DataObject's bug, btw, not
# ours.
# *** WARNING *** WARNING *** WARNING *** WARNING ***
#$config['db']['debug'] = 0;
#$config['db']['db_driver'] = 'MDB2';
// $config['db']['ini_your_db_name'] = $config['db']['schema_location'].'/laconica.ini';
// *** WARNING *** WARNING *** WARNING *** WARNING ***
// Setting debug to a non-zero value will expose your DATABASE PASSWORD to Web users.
// !!!!!! DO NOT SET THIS ON PRODUCTION SERVERS !!!!!! DB_DataObject's bug, btw, not
// ours.
// *** WARNING *** WARNING *** WARNING *** WARNING ***
// $config['db']['debug'] = 0;
// $config['db']['db_driver'] = 'MDB2';
#Database type. For mysql, these defaults are fine. For postgresql, set
#'quote_identifiers' to true and 'type' to 'pgsql':
#$config['db']['quote_identifiers'] = false;
#$config['db']['type'] = 'mysql';
// Database type. For mysql, these defaults are fine. For postgresql, set
// 'quote_identifiers' to true and 'type' to 'pgsql':
// $config['db']['quote_identifiers'] = false;
// $config['db']['type'] = 'mysql';
#session_set_cookie_params(0, '/'. $config['site']['path'] .'/');
// session_set_cookie_params(0, '/'. $config['site']['path'] .'/');
#Standard fancy-url clashes prevented by not allowing nicknames on a blacklist
#Add your own here. Note: empty array by default
#$config['nickname']['blacklist'][] = 'scobleizer';
// Standard fancy-url clashes prevented by not allowing nicknames on a blacklist
// Add your own here. Note: empty array by default
// $config['nickname']['blacklist'][] = 'scobleizer';
# sphinx search
// sphinx search
$config['sphinx']['enabled'] = false;
$config['sphinx']['server'] = 'localhost';
$config['sphinx']['port'] = 3312;
# Users to populate the 'Featured' tab
#$config['nickname']['featured'][] = 'scobleizer';
// Users to populate the 'Featured' tab
// $config['nickname']['featured'][] = 'scobleizer';
# xmpp
#$config['xmpp']['enabled'] = false;
#$config['xmpp']['server'] = 'server.example.net';
#$config['xmpp']['host'] = NULL; # Only set if different from server
#$config['xmpp']['port'] = 5222;
#$config['xmpp']['user'] = 'update';
#$config['xmpp']['encryption'] = false;
#$config['xmpp']['resource'] = 'uniquename';
#$config['xmpp']['password'] = 'blahblahblah';
#$config['xmpp']['public'][] = 'someindexer@example.net';
#$config['xmpp']['debug'] = false;
// xmpp
// $config['xmpp']['enabled'] = false;
// $config['xmpp']['server'] = 'server.example.net';
// $config['xmpp']['host'] = NULL; // Only set if different from server
// $config['xmpp']['port'] = 5222;
// $config['xmpp']['user'] = 'update';
// $config['xmpp']['encryption'] = false;
// $config['xmpp']['resource'] = 'uniquename';
// $config['xmpp']['password'] = 'blahblahblah';
// $config['xmpp']['public'][] = 'someindexer@example.net';
// $config['xmpp']['debug'] = false;
#Default locale info
#$config['site']['timezone'] = 'Pacific/Auckland';
#$config['site']['language'] = 'en_NZ';
// Default locale info
// $config['site']['timezone'] = 'Pacific/Auckland';
// $config['site']['language'] = 'en_NZ';
#Email info, used for all outbound email
#$config['mail']['notifyfrom'] = 'microblog@example.net';
#$config['mail']['domain'] = 'microblog.example.net';
# See http://pear.php.net/manual/en/package.mail.mail.factory.php for options
#$config['mail']['backend'] = 'smtp';
#$config['mail']['params'] = array(
# 'host' => 'localhost',
# 'port' => 25,
# );
#For incoming email, if enabled. Defaults to site server name.
#$config['mail']['domain'] = 'incoming.example.net';
// Email info, used for all outbound email
// $config['mail']['notifyfrom'] = 'microblog@example.net';
// $config['mail']['domain'] = 'microblog.example.net';
// See http://pear.php.net/manual/en/package.mail.mail.factory.php for options
// $config['mail']['backend'] = 'smtp';
// $config['mail']['params'] = array(
// 'host' => 'localhost',
// 'port' => 25,
// );
// For incoming email, if enabled. Defaults to site server name.
// $config['mail']['domain'] = 'incoming.example.net';
#exponential decay factor for tags, default 10 days
#raise this if traffic is slow, lower it if it's fast
#$config['tag']['dropoff'] = 86400.0 * 10;
// exponential decay factor for tags, default 10 days
// raise this if traffic is slow, lower it if it's fast
// $config['tag']['dropoff'] = 86400.0 * 10;
#exponential decay factor for popular (most favorited notices)
#default 10 days -- similar to tag dropoff
#$config['popular']['dropoff'] = 86400.0 * 10;
// exponential decay factor for popular (most favorited notices)
// default 10 days -- similar to tag dropoff
// $config['popular']['dropoff'] = 86400.0 * 10;
#optionally show non-local messages in public timeline
#$config['public']['localonly'] = false;
// optionally show non-local messages in public timeline
// $config['public']['localonly'] = false;
#hide certain users from public pages, by ID
#$config['public']['blacklist'][] = 123;
#$config['public']['blacklist'][] = 2307;
// hide certain users from public pages, by ID
// $config['public']['blacklist'][] = 123;
// $config['public']['blacklist'][] = 2307;
#Mark certain notice sources as automatic and thus not
#appropriate for public feed
#$config['public]['autosource'][] = 'twitterfeed';
#$config['public]['autosource'][] = 'rssdent';
#$config['public]['autosource'][] = 'Ping.Fm';
#$config['public]['autosource'][] = 'HelloTxt';
#$config['public]['autosource'][] = 'Updating.Me';
// Mark certain notice sources as automatic and thus not
// appropriate for public feed
// $config['public]['autosource'][] = 'twitterfeed';
// $config['public]['autosource'][] = 'rssdent';
// $config['public]['autosource'][] = 'Ping.Fm';
// $config['public]['autosource'][] = 'HelloTxt';
// $config['public]['autosource'][] = 'Updating.Me';
#Do notice broadcasts offline
#If you use this, you must run the six offline daemons in the
#background. See the README for details.
#$config['queue']['enabled'] = true;
// Do notice broadcasts offline
// If you use this, you must run the six offline daemons in the
// background. See the README for details.
// $config['queue']['enabled'] = true;
#Queue subsystem
#subsystems: internal (default) or stomp
#using stomp requires an external message queue server
#$config['queue']['subsystem'] = 'stomp';
#$config['queue']['stomp_server'] = 'tcp://localhost:61613';
#use different queue_basename for each laconica instance managed by the server
#$config['queue']['queue_basename'] = 'laconica';
// Queue subsystem
// subsystems: internal (default) or stomp
// using stomp requires an external message queue server
// $config['queue']['subsystem'] = 'stomp';
// $config['queue']['stomp_server'] = 'tcp://localhost:61613';
// use different queue_basename for each laconica instance managed by the server
// $config['queue']['queue_basename'] = 'laconica';
#The following customise the behaviour of the various daemons:
#$config['daemon']['piddir'] = '/var/run';
#$config['daemon']['user'] = false;
#$config['daemon']['group'] = false;
// The following customise the behaviour of the various daemons:
// $config['daemon']['piddir'] = '/var/run';
// $config['daemon']['user'] = false;
// $config['daemon']['group'] = false;
#For installations with high traffic, laconica can use MemCached to cache
#frequently requested information. Only enable the following if you have
#MemCached up and running:
#$config['memcached']['enabled'] = false;
#$config['memcached']['server'] = 'localhost';
#$config['memcached']['port'] = 11211;
// For installations with high traffic, laconica can use MemCached to cache
// frequently requested information. Only enable the following if you have
// MemCached up and running:
// $config['memcached']['enabled'] = false;
// $config['memcached']['server'] = 'localhost';
// $config['memcached']['port'] = 11211;
# Enable bidirectional Twitter bridge
#$config['twitterbridge']['enabled'] = true;
// Twitter integration source attribute. Note: default is Laconica
// $config['integration']['source'] = 'Laconica';
#Twitter integration source attribute. Note: default is Laconica
#$config['integration']['source'] = 'Laconica';
// Edit throttling. Off by default. If turned on, you can only post 20 notices
// every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
// real users without getting uncontrollable floods from spammers or runaway bots.
# Edit throttling. Off by default. If turned on, you can only post 20 notices
# every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
# real users without getting uncontrollable floods from spammers or runaway bots.
// $config['throttle']['enabled'] = true;
// $config['throttle']['count'] = 100;
// $config['throttle']['timespan'] = 3600;
#$config['throttle']['enabled'] = true;
#$config['throttle']['count'] = 100;
#$config['throttle']['timespan'] = 3600;
// List of users banned from posting (nicknames and/or IDs)
// $config['profile']['banned'][] = 'hacker';
// $config['profile']['banned'][] = 12345;
# List of users banned from posting (nicknames and/or IDs)
#$config['profile']['banned'][] = 'hacker';
#$config['profile']['banned'][] = 12345;
// Config section for the built-in Facebook application
// $config['facebook']['apikey'] = 'APIKEY';
// $config['facebook']['secret'] = 'SECRET';
# Config section for the built-in Facebook application
#$config['facebook']['apikey'] = 'APIKEY';
#$config['facebook']['secret'] = 'SECRET';
// Add Google Analytics
// require_once('plugins/GoogleAnalyticsPlugin.php');
// $ga = new GoogleAnalyticsPlugin('your secret code');
# Facebook Connect plugin (Needs valid APIKEY above)
#require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
#$fbc = new FBConnectPlugin();
// Use Templating (template: /tpl/index.php)
// require_once('plugins/TemplatePlugin.php');
// $tpl = new TemplatePlugin();
# Add Google Analytics
# require_once('plugins/GoogleAnalyticsPlugin.php');
# $ga = new GoogleAnalyticsPlugin('your secret code');
// Don't allow saying the same thing more than once per hour
// $config['site']['dupelimit'] = 3600;
// Don't enforce the dupe limit
// $config['site']['dupelimit'] = -1;
# Use Templating (template: /tpl/index.php)
# require_once('plugins/TemplatePlugin.php');
# $tpl = new TemplatePlugin();
// Base string for minting Tag URIs in Atom feeds. Defaults to
// "yourserver,2009". This needs to be configured properly for your Atom
// feeds to validate. See: http://www.faqs.org/rfcs/rfc4151.html and
// http://taguri.org/ Examples:
// $config['integration']['taguri'] = 'example.net,2008';
// $config['integration']['taguri'] = 'admin@example.net,2009-03-09'
#Don't allow saying the same thing more than once per hour
#$config['site']['dupelimit'] = 3600;
#Don't enforce the dupe limit
#$config['site']['dupelimit'] = -1;
// Don't use SSL
// $config['site']['ssl'] = 'never';
// Use SSL only for sensitive pages (like login, password change)
// $config['site']['ssl'] = 'sometimes';
// Use SSL for all pages
// $config['site']['ssl'] = 'always';
#Base string for minting Tag URIs in Atom feeds. Defaults to
#"yourserver,2009". This needs to be configured properly for your Atom
#feeds to validate. See: http://www.faqs.org/rfcs/rfc4151.html and
#http://taguri.org/ Examples:
#$config['integration']['taguri'] = 'example.net,2008';
#$config['integration']['taguri'] = 'admin@example.net,2009-03-09'
// Use a different hostname for SSL-encrypted pages
// $config['site']['sslserver'] = 'secure.example.org';
#Don't use SSL
#$config['site']['ssl'] = 'never';
#Use SSL only for sensitive pages (like login, password change)
#$config['site']['ssl'] = 'sometimes';
#Use SSL for all pages
#$config['site']['ssl'] = 'always';
// If you have a lot of status networks on the same server, you can
// store the site data in a database and switch as follows
// Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet');
// if (!Status_network::setupSite($_server, $_path)) {
// print "Error\n";
// exit(1);
// }
#Use a different hostname for SSL-encrypted pages
#$config['site']['sslserver'] = 'secure.example.org';
// How often to send snapshots; in # of web hits. Ideally,
// try to do this once per month (that is, make this equal to number
// of hits per month)
// $config['snapshot']['frequency'] = 10000;
// If you don't want to report statistics to the central server, uncomment.
// $config['snapshot']['run'] = 'never';
// If you want to report statistics in a cron job instead.
// $config['snapshot']['run'] = 'cron';
// Support for file uploads (attachments),
// select supported mimetypes and quotas (in bytes)
// $config['attachments']['supported'] = array('image/png', 'application/ogg');
// $config['attachments']['file_quota'] = 5000000;
// $config['attachments']['user_quota'] = 50000000;
// $config['attachments']['monthly_quota'] = 15000000;
#If you have a lot of status networks on the same server, you can
#store the site data in a database and switch as follows
#Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet');
#if (!Status_network::setupSite($_server, $_path)) {
# print "Error\n";
# exit(1);
#}

View File

@ -2,4 +2,5 @@ insert into foreign_service
(id, name, description, created)
values
('1','Twitter', 'Twitter Micro-blogging service', now()),
('2','Facebook', 'Facebook', now());
('2','Facebook', 'Facebook', now()),
('3','FacebookConnect', 'Facebook Connect', now());

View File

@ -285,7 +285,7 @@ create table foreign_user (
create table foreign_link (
user_id int comment 'link to user on this system, if exists' references user (id),
foreign_id int comment 'link ' references foreign_user(id),
foreign_id bigint unsigned comment 'link to user on foreign service, if exists' references foreign_user(id),
service int not null comment 'foreign key to service' references foreign_service(id),
credentials varchar(255) comment 'authc credentials, typically a password',
noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies',
@ -482,5 +482,3 @@ create table file_to_post (
unique(file_id, post_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -2,13 +2,16 @@ INSERT INTO notice_source
(code, name, url, created)
VALUES
('adium', 'Adium', 'http://www.adiumx.com/', now()),
('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()),
('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()),
('bti','bti','http://gregkh.github.com/bti/', now()),
('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()),
('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()),
('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
('Facebook','Facebook','http://apps.facebook.com/identica/', now()),
('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()),
('gravity', 'Gravity', 'http://mobileways.de/gravity', now()),
('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
('identicatools','Laconica Tools','http://bitbucketlabs.net/laconica-tools/', now()),
@ -27,6 +30,7 @@ VALUES
('pingvine','PingVine','http://pingvine.com/', now()),
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()),
('rygh.no','rygh.no','http://rygh.no/', now()),

523
extlib/MIME/Type.php Normal file
View File

@ -0,0 +1,523 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002, 2008 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Ian Eure <ieure@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $
require_once 'PEAR.php';
$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
$_fileCmd = 'file';
/**
* Class for working with MIME types
*
* @category MIME
* @package MIME_Type
* @license PHP License 3.0
* @version 1.2.0
* @link http://pear.php.net/package/MIME_Type
* @author Ian Eure <ieure@php.net>
*/
class MIME_Type
{
/**
* The MIME media type
*
* @var string
*/
var $media = '';
/**
* The MIME media sub-type
*
* @var string
*/
var $subType = '';
/**
* Optional MIME parameters
*
* @var array
*/
var $parameters = array();
/**
* List of valid media types.
* A media type is the string in front of the slash.
* The media type of "text/xml" would be "text".
*
* @var array
*/
var $validMediaTypes = array(
'text',
'image',
'audio',
'video',
'application',
'multipart',
'message'
);
/**
* Constructor.
*
* If $type is set, if will be parsed and the appropriate class vars set.
* If not, you get an empty class.
* This is useful, but not quite as useful as parsing a type.
*
* @param string $type MIME type
*
* @return void
*/
function MIME_Type($type = false)
{
if ($type) {
$this->parse($type);
}
}
/**
* Parse a mime-type and set the class variables.
*
* @param string $type MIME type to parse
*
* @return void
*/
function parse($type)
{
$this->media = $this->getMedia($type);
$this->subType = $this->getSubType($type);
$this->parameters = array();
if (MIME_Type::hasParameters($type)) {
require_once 'MIME/Type/Parameter.php';
foreach (MIME_Type::getParameters($type) as $param) {
$param = new MIME_Type_Parameter($param);
$this->parameters[$param->name] = $param;
}
}
}
/**
* Does this type have any parameters?
*
* @param string $type MIME type to check
*
* @return boolean true if $type has parameters, false otherwise
* @static
*/
function hasParameters($type)
{
if (strstr($type, ';')) {
return true;
}
return false;
}
/**
* Get a MIME type's parameters
*
* @param string $type MIME type to get parameters of
*
* @return array $type's parameters
* @static
*/
function getParameters($type)
{
$params = array();
$tmp = explode(';', $type);
for ($i = 1; $i < count($tmp); $i++) {
$params[] = trim($tmp[$i]);
}
return $params;
}
/**
* Strip parameters from a MIME type string.
*
* @param string $type MIME type string
*
* @return string MIME type with parameters removed
* @static
*/
function stripParameters($type)
{
if (strstr($type, ';')) {
return substr($type, 0, strpos($type, ';'));
}
return $type;
}
/**
* Removes comments from a media type, subtype or parameter.
*
* @param string $string String to strip comments from
* @param string &$comment Comment is stored in there.
*
* @return string String without comments
* @static
*/
function stripComments($string, &$comment)
{
if (strpos($string, '(') === false) {
return $string;
}
$inquote = false;
$quoting = false;
$incomment = 0;
$newstring = '';
for ($n = 0; $n < strlen($string); $n++) {
if ($quoting) {
if ($incomment == 0) {
$newstring .= $string[$n];
} else if ($comment !== null) {
$comment .= $string[$n];
}
$quoting = false;
} else if ($string[$n] == '\\') {
$quoting = true;
} else if (!$inquote && $incomment > 0 && $string[$n] == ')') {
$incomment--;
if ($incomment == 0 && $comment !== null) {
$comment .= ' ';
}
} else if (!$inquote && $string[$n] == '(') {
$incomment++;
} else if ($string[$n] == '"') {
if ($inquote) {
$inquote = false;
} else {
$inquote = true;
}
} else if ($incomment == 0) {
$newstring .= $string[$n];
} else if ($comment !== null) {
$comment .= $string[$n];
}
}
if ($comment !== null) {
$comment = trim($comment);
}
return $newstring;
}
/**
* Get a MIME type's media
*
* @note 'media' refers to the portion before the first slash
*
* @param string $type MIME type to get media of
*
* @return string $type's media
* @static
*/
function getMedia($type)
{
$tmp = explode('/', $type);
return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
}
/**
* Get a MIME type's subtype
*
* @param string $type MIME type to get subtype of
*
* @return string $type's subtype, null if invalid mime type
* @static
*/
function getSubType($type)
{
$tmp = explode('/', $type);
if (!isset($tmp[1])) {
return null;
}
$tmp = explode(';', $tmp[1]);
return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
}
/**
* Create a textual MIME type from object values
*
* This function performs the opposite function of parse().
*
* @return string MIME type string
*/
function get()
{
$type = strtolower($this->media . '/' . $this->subType);
if (count($this->parameters)) {
foreach ($this->parameters as $key => $null) {
$type .= '; ' . $this->parameters[$key]->get();
}
}
return $type;
}
/**
* Is this type experimental?
*
* @note Experimental types are denoted by a leading 'x-' in the media or
* subtype, e.g. text/x-vcard or x-world/x-vrml.
*
* @param string $type MIME type to check
*
* @return boolean true if $type is experimental, false otherwise
* @static
*/
function isExperimental($type)
{
if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' ||
substr(MIME_Type::getSubType($type), 0, 2) == 'x-') {
return true;
}
return false;
}
/**
* Is this a vendor MIME type?
*
* @note Vendor types are denoted with a leading 'vnd. in the subtype.
*
* @param string $type MIME type to check
*
* @return boolean true if $type is a vendor type, false otherwise
* @static
*/
function isVendor($type)
{
if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') {
return true;
}
return false;
}
/**
* Is this a wildcard type?
*
* @param string $type MIME type to check
*
* @return boolean true if $type is a wildcard, false otherwise
* @static
*/
function isWildcard($type)
{
if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') {
return true;
}
return false;
}
/**
* Perform a wildcard match on a MIME type
*
* Example:
* MIME_Type::wildcardMatch('image/*', 'image/png')
*
* @param string $card Wildcard to check against
* @param string $type MIME type to check
*
* @return boolean true if there was a match, false otherwise
* @static
*/
function wildcardMatch($card, $type)
{
if (!MIME_Type::isWildcard($card)) {
return false;
}
if ($card == '*/*') {
return true;
}
if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) {
return true;
}
return false;
}
/**
* Add a parameter to this type
*
* @param string $name Attribute name
* @param string $value Attribute value
* @param string $comment Comment for this parameter
*
* @return void
*/
function addParameter($name, $value, $comment = false)
{
$tmp = new MIME_Type_Parameter();
$tmp->name = $name;
$tmp->value = $value;
$tmp->comment = $comment;
$this->parameters[$name] = $tmp;
}
/**
* Remove a parameter from this type
*
* @param string $name Parameter name
*
* @return void
*/
function removeParameter($name)
{
unset($this->parameters[$name]);
}
/**
* Autodetect a file's MIME-type
*
* This function may be called staticly.
*
* @internal Tries to use fileinfo extension at first. If that
* does not work, mime_magic is used. If this is also not available
* or does not succeed, "file" command is tried to be executed with
* System_Command. When that fails, too, then we use our in-built
* extension-to-mimetype-mapping list.
*
* @param string $file Path to the file to get the type of
* @param bool $params Append MIME parameters if true
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*
* @since 1.0.0beta1
* @static
*/
function autoDetect($file, $params = false)
{
// Sanity checks
if (!file_exists($file)) {
return PEAR::raiseError("File \"$file\" doesn't exist");
}
if (!is_readable($file)) {
return PEAR::raiseError("File \"$file\" is not readable");
}
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
$type = finfo_file($finfo, $file);
finfo_close($finfo);
if ($type !== false && $type !== '') {
return MIME_Type::_handleDetection($type, $params);
}
}
if (function_exists('mime_content_type')) {
$type = mime_content_type($file);
if ($type !== false && $type !== '') {
return MIME_Type::_handleDetection($type, $params);
}
}
@include_once 'System/Command.php';
if (class_exists('System_Command')) {
return MIME_Type::_handleDetection(
MIME_Type::_fileAutoDetect($file),
$params
);
}
require_once 'MIME/Type/Extension.php';
$mte = new MIME_Type_Extension();
return $mte->getMIMEType($file);
}
/**
* Handles a detected MIME type and modifies it if necessary.
*
* @param string $type MIME Type of a file
* @param bool $params Append MIME parameters if true
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*/
function _handleDetection($type, $params)
{
// _fileAutoDetect() may have returned an error.
if (PEAR::isError($type)) {
return $type;
}
// Don't return an empty string
if (!$type || !strlen($type)) {
return PEAR::raiseError("Sorry, couldn't determine file type.");
}
// Strip parameters if present & requested
if (MIME_Type::hasParameters($type) && !$params) {
$type = MIME_Type::stripParameters($type);
}
return $type;
}
/**
* Autodetect a file's MIME-type with 'file' and System_Command
*
* This function may be called staticly.
*
* @param string $file Path to the file to get the type of
*
* @return string $file's MIME-type
*
* @since 1.0.0beta1
* @static
*/
function _fileAutoDetect($file)
{
$cmd = new System_Command();
// Make sure we have the 'file' command.
$fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd');
if (!$cmd->which($fileCmd)) {
unset($cmd);
return PEAR::raiseError("Can't find file command \"{$fileCmd}\"");
}
$cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file));
$res = $cmd->execute();
unset($cmd);
return $res;
}
}

View File

@ -0,0 +1,298 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP versions 4 and 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2009 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Christian Schmidt <schmidt@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $
require_once 'PEAR.php';
/**
* Class for mapping file extensions to MIME types.
*
* @category MIME
* @package MIME_Type
* @author Christian Schmidt <schmidt@php.net>
* @license PHP License 3.0
* @version 1.2.0
* @link http://pear.php.net/package/MIME_Type
*/
class MIME_Type_Extension
{
/**
* Mapping between file extension and MIME type.
*
* @internal The array is sorted alphabetically by value and with primary
* extension first. Be careful about not adding duplicate keys - PHP
* silently ignores duplicates. The following command can be used for
* checking for duplicates:
* grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d
* application/octet-stream is generally used as fallback when no other
* MIME-type can be found, but the array does not contain a lot of such
* unknown extension. One entry exists, though, to allow detection of
* file extension for this MIME-type.
*
* @var array
*/
var $extensionToType = array (
'ez' => 'application/andrew-inset',
'atom' => 'application/atom+xml',
'jar' => 'application/java-archive',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'mathml' => 'application/mathml+xml',
'doc' => 'application/msword',
'dat' => 'application/octet-stream',
'oda' => 'application/oda',
'ogg' => 'application/ogg',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'rdf' => 'application/rdf+xml',
'rss' => 'application/rss+xml',
'smi' => 'application/smil',
'smil' => 'application/smil',
'gram' => 'application/srgs',
'grxml' => 'application/srgs+xml',
'kml' => 'application/vnd.google-earth.kml+xml',
'kmz' => 'application/vnd.google-earth.kmz',
'mif' => 'application/vnd.mif',
'xul' => 'application/vnd.mozilla.xul+xml',
'xls' => 'application/vnd.ms-excel',
'xlb' => 'application/vnd.ms-excel',
'xlt' => 'application/vnd.ms-excel',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
'ppt' => 'application/vnd.ms-powerpoint',
'pps' => 'application/vnd.ms-powerpoint',
'odc' => 'application/vnd.oasis.opendocument.chart',
'odb' => 'application/vnd.oasis.opendocument.database',
'odf' => 'application/vnd.oasis.opendocument.formula',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'vsd' => 'application/vnd.visio',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'vxml' => 'application/voicexml+xml',
'bcpio' => 'application/x-bcpio',
'vcd' => 'application/x-cdlink',
'pgn' => 'application/x-chess-pgn',
'cpio' => 'application/x-cpio',
'csh' => 'application/x-csh',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'spl' => 'application/x-futuresplash',
'tgz' => 'application/x-gtar',
'gtar' => 'application/x-gtar',
'hdf' => 'application/x-hdf',
'js' => 'application/x-javascript',
'skp' => 'application/x-koan',
'skd' => 'application/x-koan',
'skt' => 'application/x-koan',
'skm' => 'application/x-koan',
'latex' => 'application/x-latex',
'nc' => 'application/x-netcdf',
'cdf' => 'application/x-netcdf',
'sh' => 'application/x-sh',
'shar' => 'application/x-shar',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'sv4cpio' => 'application/x-sv4cpio',
'sv4crc' => 'application/x-sv4crc',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'tex' => 'application/x-tex',
'texinfo' => 'application/x-texinfo',
'texi' => 'application/x-texinfo',
't' => 'application/x-troff',
'tr' => 'application/x-troff',
'roff' => 'application/x-troff',
'man' => 'application/x-troff-man',
'me' => 'application/x-troff-me',
'ms' => 'application/x-troff-ms',
'ustar' => 'application/x-ustar',
'src' => 'application/x-wais-source',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'xslt' => 'application/xslt+xml',
'xml' => 'application/xml',
'xsl' => 'application/xml',
'dtd' => 'application/xml-dtd',
'zip' => 'application/zip',
'au' => 'audio/basic',
'snd' => 'audio/basic',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'kar' => 'audio/midi',
'mpga' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'm3u' => 'audio/x-mpegurl',
'wma' => 'audio/x-ms-wma',
'wax' => 'audio/x-ms-wax',
'ram' => 'audio/x-pn-realaudio',
'ra' => 'audio/x-pn-realaudio',
'rm' => 'application/vnd.rn-realmedia',
'wav' => 'audio/x-wav',
'pdb' => 'chemical/x-pdb',
'xyz' => 'chemical/x-xyz',
'bmp' => 'image/bmp',
'cgm' => 'image/cgm',
'gif' => 'image/gif',
'ief' => 'image/ief',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'djvu' => 'image/vnd.djvu',
'djv' => 'image/vnd.djvu',
'wbmp' => 'image/vnd.wap.wbmp',
'ras' => 'image/x-cmu-raster',
'ico' => 'image/x-icon',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'rgb' => 'image/x-rgb',
'xbm' => 'image/x-xbitmap',
'psd' => 'image/x-photoshop',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-xwindowdump',
'eml' => 'message/rfc822',
'igs' => 'model/iges',
'iges' => 'model/iges',
'msh' => 'model/mesh',
'mesh' => 'model/mesh',
'silo' => 'model/mesh',
'wrl' => 'model/vrml',
'vrml' => 'model/vrml',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'css' => 'text/css',
'csv' => 'text/csv',
'html' => 'text/html',
'htm' => 'text/html',
'txt' => 'text/plain',
'asc' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'sgml' => 'text/sgml',
'sgm' => 'text/sgml',
'tsv' => 'text/tab-separated-values',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
'etx' => 'text/x-setext',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'mxu' => 'video/vnd.mpegurl',
'm4u' => 'video/vnd.mpegurl',
'flv' => 'video/x-flv',
'asf' => 'video/x-ms-asf',
'asx' => 'video/x-ms-asf',
'wmv' => 'video/x-ms-wmv',
'wm' => 'video/x-ms-wm',
'wmx' => 'video/x-ms-wmx',
'avi' => 'video/x-msvideo',
'ogv' => 'video/ogg',
'movie' => 'video/x-sgi-movie',
'ice' => 'x-conference/x-cooltalk',
);
/**
* Autodetect a file's MIME-type.
*
* @param string $file Path to the file to get the type of
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*/
function getMIMEType($file)
{
$extension = substr(strrchr($file, '.'), 1);
if ($extension === false) {
return PEAR::raiseError("File has no extension.");
}
if (!isset($this->extensionToType[$extension])) {
return PEAR::raiseError("Sorry, couldn't determine file type.");
}
return $this->extensionToType[$extension];
}
/**
* Return default MIME-type for the specified extension.
*
* @param string $type MIME-type
*
* @return string A file extension without leading period.
*/
function getExtension($type)
{
require_once 'MIME/Type.php';
// Strip parameters and comments.
$type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type);
$extension = array_search($type, $this->extensionToType);
if ($extension === false) {
return PEAR::raiseError("Sorry, couldn't determine extension.");
}
return $extension;
}
}
?>

View File

@ -0,0 +1,163 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Ian Eure <ieure@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $
/**
* Class for working with MIME type parameters
*
* @version 1.2.0
* @package MIME_Type
* @author Ian Eure <ieure@php.net>
*/
class MIME_Type_Parameter {
/**
* Parameter name
*
* @var string
*/
var $name;
/**
* Parameter value
*
* @var string
*/
var $value;
/**
* Parameter comment
*
* @var string
*/
var $comment;
/**
* Constructor.
*
* @param string $param MIME parameter to parse, if set.
* @return void
*/
function MIME_Type_Parameter($param = false)
{
if ($param) {
$this->parse($param);
}
}
/**
* Parse a MIME type parameter and set object fields
*
* @param string $param MIME type parameter to parse
* @return void
*/
function parse($param)
{
$comment = '';
$param = MIME_Type::stripComments($param, $comment);
$this->name = $this->getAttribute($param);
$this->value = $this->getValue($param);
$this->comment = $comment;
}
/**
* Get a parameter attribute (e.g. name)
*
* @param string MIME type parameter
* @return string Attribute name
* @static
*/
function getAttribute($param)
{
$tmp = explode('=', $param);
return trim($tmp[0]);
}
/**
* Get a parameter value
*
* @param string $param MIME type parameter
* @return string Value
* @static
*/
function getValue($param)
{
$tmp = explode('=', $param, 2);
$value = $tmp[1];
$value = trim($value);
if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
$value = substr($value, 1, -1);
}
$value = str_replace('\\"', '"', $value);
return $value;
}
/**
* Get a parameter comment
*
* @param string $param MIME type parameter
* @return string Parameter comment
* @see getComment()
* @static
*/
function getComment($param)
{
$cs = strpos($param, '(');
$comment = substr($param, $cs);
return trim($comment, '() ');
}
/**
* Does this parameter have a comment?
*
* @param string $param MIME type parameter
* @return boolean true if $param has a comment, false otherwise
* @static
*/
function hasComment($param)
{
if (strstr($param, '(')) {
return true;
}
return false;
}
/**
* Get a string representation of this parameter
*
* This function performs the oppsite of parse()
*
* @return string String representation of parameter
*/
function get()
{
$val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"';
if ($this->comment) {
$val .= ' (' . $this->comment . ')';
}
return $val;
}
}
?>

View File

@ -64,11 +64,13 @@ function handleError($error)
function main()
{
// quick check for fancy URL auto-detection support in installer.
if (isset($_SERVER['REDIRECT_URL']) && ('/check-fancy' === $_SERVER['REDIRECT_URL'])) {
if (isset($_SERVER['REDIRECT_URL']) && ((dirname($_SERVER['REQUEST_URI']) . '/check-fancy') === $_SERVER['REDIRECT_URL'])) {
die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
}
global $user, $action, $config;
Snapshot::check();
if (!_have_config()) {
$msg = sprintf(_("No configuration file found. Try running ".
"the installation program first."));

View File

@ -35,15 +35,17 @@ function main()
function checkPrereqs()
{
$pass = true;
if (file_exists(INSTALLDIR.'/config.php')) {
?><p class="error">Config file &quot;config.php&quot; already exists.</p>
<?php
return false;
$pass = false;
}
if (version_compare(PHP_VERSION, '5.0.0', '<')) {
?><p class="error">Require PHP version 5 or greater.</p><?php
return false;
$pass = false;
}
$reqs = array('gd', 'mysql', 'curl',
@ -53,7 +55,7 @@ function checkPrereqs()
foreach ($reqs as $req) {
if (!checkExtension($req)) {
?><p class="error">Cannot load required extension: <code><?php echo $req; ?></code></p><?php
return false;
$pass = false;
}
}
@ -61,17 +63,17 @@ function checkPrereqs()
?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?></code>
<?php
return false;
$pass = false;
}
if (!is_writable(INSTALLDIR.'/avatar/')) {
?><p class="error">Cannot write avatar directory: <code><?php echo INSTALLDIR; ?>/avatar/</code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/avatar/</code></p>
<?
return false;
$pass = false;
}
return true;
return $pass;
}
function checkExtension($name)
@ -114,16 +116,16 @@ function showForm()
<input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
<p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
</li>
<li>
<label for="host">Hostname</label>
<input type="text" id="host" name="host" />
<p class="form_guide">Database hostname</p>
</li>
<li>
<label for="host">Site path</label>
<input type="text" id="path" name="path" value="$config_path" />
<p class="form_guide">Site path, following the "/" after the domain name in the URL. Empty is fine. Field should be filled automatically.</p>
</li>
<li>
<label for="host">Hostname</label>
<input type="text" id="host" name="host" />
<p class="form_guide">Database hostname</p>
</li>
<li>
<label for="host">Database</label>
<input type="text" id="database" name="database" />
@ -173,36 +175,38 @@ function handlePost()
<dd>
<ul>
<?php
$fail = false;
if (empty($host)) {
updateStatus("No hostname specified.", true);
showForm();
return;
$fail = true;
}
if (empty($database)) {
updateStatus("No database specified.", true);
showForm();
return;
$fail = true;
}
if (empty($username)) {
updateStatus("No username specified.", true);
showForm();
return;
$fail = true;
}
if (empty($password)) {
updateStatus("No password specified.", true);
showForm();
return;
$fail = true;
}
if (empty($sitename)) {
updateStatus("No sitename specified.", true);
showForm();
return;
$fail = true;
}
if($fail){
showForm();
return;
}
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = mysql_connect($host, $username, $password);
@ -247,7 +251,7 @@ function handlePost()
}
updateStatus("Done!");
if ($path) $path .= '/';
updateStatus("You can visit your <a href='/$path'>new Laconica site</a).");
updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
?>
<?php
@ -257,16 +261,17 @@ function writeConf($sitename, $sqlUrl, $fancy, $path)
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\n".
"if (!defined('LACONICA')) { exit(1); }\n\n".
"\$config['site']['name'] = \"$sitename\";\n\n".
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
"\$config['site']['path'] = \"$path\";\n\n".
"\$config['db']['database'] = \"$sqlUrl\";\n\n");
"\$config['db']['database'] = \"$sqlUrl\";\n\n".
"?>");
return $res;
}
function runDbScript($filename, $conn)
{
return true;
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
foreach ($stmts as $stmt) {
@ -290,13 +295,13 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
<head>
<title>Install Laconica</title>
<link rel="stylesheet" type="text/css" href="theme/base/css/display.css?version=0.8" media="screen, projection, tv"/>
<link rel="shortcut icon" href="favicon.ico"/>
<link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
<!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
<!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
<!--[if IE]><link rel="stylesheet" type="text/css" href="theme/earthy/css/ie.css?version=0.8" /><![endif]-->
<script src='js/jquery.min.js'></script>
<script src='js/install.js'></script>
<!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
<script src="js/jquery.min.js"></script>
<script src="js/install.js"></script>
</head>
<body id="install">
<div id="wrap">

View File

@ -1,29 +1,30 @@
/** Init for Farbtastic library and page setup
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
$(document).ready(function() {
function UpdateColors(e) {
var S = f.linked;
var C = f.color;
if (S && S.value && S.value != C) {
UpdateSwatch(S);
switch (parseInt(f.linked.id.slice(-1))) {
case 0: default:
$('body').css({'background-color':C});
break;
case 1:
$('#content').css({'background-color':C});
break;
case 2:
$('#aside_primary').css({'background-color':C});
break;
case 3:
$('body').css({'color':C});
break;
case 4:
$('a').css({'color':C});
break;
}
S.value = C;
function UpdateColors(S) {
C = $(S).val();
switch (parseInt(S.id.slice(-1))) {
case 0: default:
$('body').css({'background-color':C});
break;
case 1:
$('#content').css({'background-color':C});
break;
case 2:
$('#aside_primary').css({'background-color':C});
break;
case 3:
$('body').css({'color':C});
break;
case 4:
$('a').css({'color':C});
break;
}
}
@ -33,35 +34,52 @@ $(document).ready(function() {
}
function UpdateSwatch(e) {
$(e).css({
"background-color": e.value,
"color": f.hsl[2] > 0.5 ? "#000": "#fff"
});
$(e).css({"background-color": e.value,
"color": f.hsl[2] > 0.5 ? "#000": "#fff"});
}
$('#settings_design_color').append('<div id="color-picker"></div>');
$('#color-picker').hide();
function SynchColors(e) {
var S = f.linked;
var C = f.color;
var f = $.farbtastic('#color-picker', UpdateColors);
var swatches = $('#settings_design_color .swatch');
if (S && S.value && S.value != C) {
S.value = C;
UpdateSwatch(S);
UpdateColors(S);
}
}
swatches
.each(UpdateColors)
function Init() {
$('#settings_design_color').append('<div id="color-picker"></div>');
$('#color-picker').hide();
.blur(function() {
$(this).val($(this).val().toUpperCase());
})
f = $.farbtastic('#color-picker', SynchColors);
swatches = $('#settings_design_color .swatch');
.focus(function() {
$('#color-picker').show();
UpdateFarbtastic(this);
})
.change(function() {
UpdateFarbtastic(this);
UpdateSwatch(this);
}).change()
;
swatches
.each(SynchColors)
.blur(function() {
$(this).val($(this).val().toUpperCase());
})
.focus(function() {
$('#color-picker').show();
UpdateFarbtastic(this);
})
.change(function() {
UpdateFarbtastic(this);
UpdateSwatch(this);
UpdateColors(this);
}).change();
}
var f, swatches;
Init();
$('#form_settings_design').bind('reset', function(){
setTimeout(function(){
swatches.each(function(){UpdateColors(this);});
$('#color-picker').remove();
swatches.unbind();
Init();
},10);
});
});

View File

@ -1,39 +1,48 @@
$(function(){
var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0;
var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0;
var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width");
var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height");
/** Init for Jcrop library and page setup
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
jQuery("#avatar_original img").Jcrop({
onChange: showPreview,
setSelect: [ x, y, w, h ],
onSelect: updateCoords,
aspectRatio: 1,
boxWidth: 480,
boxHeight: 480,
bgColor: '#000',
bgOpacity: .4
});
});
$(function(){
var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0;
var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0;
var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width");
var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height");
function showPreview(coords) {
var rx = 96 / coords.w;
var ry = 96 / coords.h;
jQuery("#avatar_original img").Jcrop({
onChange: showPreview,
setSelect: [ x, y, w, h ],
onSelect: updateCoords,
aspectRatio: 1,
boxWidth: 480,
boxHeight: 480,
bgColor: '#000',
bgOpacity: .4
});
});
var img_width = $("#avatar_original img").attr("width");
var img_height = $("#avatar_original img").attr("height");
function showPreview(coords) {
var rx = 96 / coords.w;
var ry = 96 / coords.h;
$('#avatar_preview img').css({
width: Math.round(rx *img_width) + 'px',
height: Math.round(ry * img_height) + 'px',
marginLeft: '-' + Math.round(rx * coords.x) + 'px',
marginTop: '-' + Math.round(ry * coords.y) + 'px'
});
};
var img_width = $("#avatar_original img").attr("width");
var img_height = $("#avatar_original img").attr("height");
function updateCoords(c) {
$('#avatar_crop_x').val(c.x);
$('#avatar_crop_y').val(c.y);
$('#avatar_crop_w').val(c.w);
$('#avatar_crop_h').val(c.h);
};
$('#avatar_preview img').css({
width: Math.round(rx *img_width) + 'px',
height: Math.round(ry * img_height) + 'px',
marginLeft: '-' + Math.round(rx * coords.x) + 'px',
marginTop: '-' + Math.round(ry * coords.y) + 'px'
});
};
function updateCoords(c) {
$('#avatar_crop_x').val(c.x);
$('#avatar_crop_y').val(c.y);
$('#avatar_crop_w').val(c.w);
$('#avatar_crop_h').val(c.h);
};

View File

@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) {
function fileUpload() {
var form = $form[0];
if ($(':input[@name=submit]', form).length) {
if ($(':input[name=submit]', form).length) {
alert('Error: Form elements must not be named "submit".');
return;
}
@ -570,7 +570,7 @@ $.fn.clearForm = function() {
$.fn.clearFields = $.fn.clearInputs = function() {
return this.each(function() {
var t = this.type, tag = this.tagName.toLowerCase();
if (t == 'text' || t == 'password' || tag == 'textarea')
if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')
this.value = '';
else if (t == 'checkbox' || t == 'radio')
this.checked = false;

View File

@ -17,10 +17,6 @@
*/
$(document).ready(function(){
$('.attachments').click(function() {$().jOverlay({zIndex:999, success:function(html) {$('.attachment').click(function() {$().jOverlay({url:$(this).attr('href') + '/ajax'}); return false; });
}, url:$(this).attr('href') + '/ajax'}); return false; });
$('.attachment').click(function() {$().jOverlay({url:$(this).attr('href') + '/ajax'}); return false; });
// count character on keyup
function counter(event){
var maxLength = 140;
@ -203,11 +199,11 @@ $(document).ready(function(){
$("#notices_primary .notices").prepend(document._importNode(li, true));
$("#notices_primary .notice:first").css({display:"none"});
$("#notices_primary .notice:first").fadeIn(2500);
NoticeHover();
NoticeReply();
}
}
$("#notice_data-text").val("");
$("#notice_data-attach").val("");
counter();
}
$("#form_notice").removeClass("processing");
@ -219,26 +215,26 @@ $(document).ready(function(){
$("#form_notice").each(addAjaxHidden);
NoticeHover();
NoticeReply();
NoticeAttachments();
});
function NoticeHover() {
$("#content .notice").hover(
function () {
$(this).addClass('hover');
},
function () {
$(this).removeClass('hover');
}
);
function mouseHandler(e) {
$(e.target).closest('li.hentry')[(e.type === 'mouseover') ? 'addClass' : 'removeClass']('hover');
};
$('#content .notices').mouseover(mouseHandler);
$('#content .notices').mouseout(mouseHandler);
}
function NoticeReply() {
if ($('#notice_data-text').length > 0) {
$('#content .notice').each(function() {
var notice = $(this);
$('.notice_reply', $(this)).click(function() {
var nickname = ($('.author .nickname', notice).length > 0) ? $('.author .nickname', notice) : $('.author .nickname');
NoticeReplySet(nickname.text(), $('.notice_id', notice).text());
var notice = $(this)[0];
$($('.notice_reply', notice)[0]).click(function() {
var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname');
NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
return false;
});
});
@ -258,3 +254,53 @@ function NoticeReplySet(nick,id) {
}
return true;
}
function NoticeAttachments() {
$.fn.jOverlay.options = {
method : 'GET',
data : '',
url : '',
color : '#000',
opacity : '0.6',
zIndex : 99,
center : true,
imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
bgClickToClose : true,
success : function() {
$('#jOverlayContent').append('<button>&#215;</button>');
$('#jOverlayContent button').click($.closeOverlay);
},
timeout : 0
};
$('a.attachment').click(function() {
$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
return false;
});
var t;
$("body:not(#shownotice) a.thumbnail").hover(
function() {
var anchor = $(this);
$("a.thumbnail").children('img').hide();
anchor.closest(".entry-title").addClass('ov');
if (anchor.children('img').length == 0) {
t = setTimeout(function() {
$.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
anchor.append(data);
});
}, 500);
}
else {
anchor.children('img').show();
}
},
function() {
clearTimeout(t);
$("a.thumbnail").children('img').hide();
$(this).closest(".entry-title").removeClass('ov');
}
);
}

View File

@ -80,20 +80,22 @@ class AttachmentList extends Widget
function show()
{
// $this->out->elementStart('div', array('id' =>'attachments_primary'));
$this->out->elementStart('div', array('id' =>'content'));
$this->out->element('h2', null, _('Attachments'));
$this->out->elementStart('ul', array('class' => 'attachments'));
$atts = new File;
$att = $atts->getAttachments($this->notice->id);
if (empty($att)) return 0;
$this->out->elementStart('dl', array('id' =>'attachments'));
$this->out->element('dt', null, _('Attachments'));
$this->out->elementStart('dd');
$this->out->elementStart('ol', array('class' => 'attachments'));
foreach ($att as $n=>$attachment) {
$item = $this->newListItem($attachment);
$item->show();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('div');
$this->out->elementEnd('dd');
$this->out->elementEnd('ol');
$this->out->elementEnd('dl');
return count($att);
}
@ -171,7 +173,7 @@ class AttachmentListItem extends Widget
}
function linkTitle() {
return 'Our page for ' . $this->title();
return $this->title();
}
/**
@ -191,34 +193,25 @@ class AttachmentListItem extends Widget
}
function linkAttr() {
return array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $this->attachment->id)));
return array('class' => 'attachment', 'href' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id);
}
function showLink() {
$attr = $this->linkAttr();
$text = $this->linkTitle();
$this->out->elementStart('h4');
$this->out->element('a', $attr, $text);
if ($this->attachment->url !== $this->title())
$this->out->element('span', null, " ({$this->attachment->url})");
$this->out->elementEnd('h4');
$this->out->elementStart('a', $this->linkAttr());
$this->out->element('span', null, $this->linkTitle());
$this->showRepresentation();
$this->out->elementEnd('a');
}
function showNoticeAttachment()
{
$this->showLink();
$this->showRepresentation();
}
function showRepresentation() {
$thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
if (!empty($thumbnail)) {
$this->out->elementStart('a', $this->linkAttr()/*'href' => $this->linkTo()*/);
$this->out->element('img', array('alt' => 'nothing to say', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height));
$this->out->elementEnd('a');
}
}
@ -260,7 +253,7 @@ class Attachment extends AttachmentListItem
}
function linkTitle() {
return 'Direct link to ' . $this->title();
return $this->attachment->url;
}
function showRepresentation() {
@ -275,6 +268,23 @@ class Attachment extends AttachmentListItem
case 'image/jpeg':
$this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
break;
case 'application/ogg':
case 'audio/x-speex':
case 'video/mpeg':
case 'audio/mpeg':
case 'video/mp4':
case 'video/quicktime':
$arr = array('type' => $this->attachment->mimetype,
'data' => $this->attachment->url,
'width' => 320,
'height' => 240
);
$this->out->elementStart('object', $arr);
$this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url));
$this->out->element('param', array('name' => 'autoStart', 'value' => 1));
$this->out->elementEnd('object');
break;
}
}
} else {

View File

@ -1,80 +0,0 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Base class for sections showing lists of attachments
*
* 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 Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
define('ATTACHMENTS_PER_SECTION', 6);
/**
* Base class for sections showing lists of attachments
*
* These are the widgets that show interesting data about a person
* group, or site.
*
* @category Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class AttachmentSection extends Section
{
function showContent()
{
$attachments = $this->getAttachments();
$cnt = 0;
$this->out->elementStart('ul', 'attachments');
while ($attachments->fetch() && ++$cnt <= ATTACHMENTS_PER_SECTION) {
$this->showAttachment($attachments);
}
$this->out->elementEnd('ul');
return ($cnt > ATTACHMENTS_PER_SECTION);
}
function getAttachments()
{
return null;
}
function showAttachment($attachment)
{
$this->out->elementStart('li');
$this->out->element('a', array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $attachment->file_id))), "Attachment tagged {$attachment->c} times");
$this->out->elementEnd('li');
}
}

View File

@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
define('LACONICA_VERSION', '0.7.3');
define('LACONICA_VERSION', '0.8.0dev');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
@ -159,6 +159,46 @@ $config =
'newuser' =>
array('subscribe' => null,
'welcome' => null),
'snapshot' =>
array('run' => 'web',
'frequency' => 10000,
'reporturl' => 'http://laconi.ca/stats/report'),
'attachments' =>
array('supported' => array('image/png',
'image/jpeg',
'image/gif',
'image/svg+xml',
'audio/mpeg',
'audio/x-speex',
'application/ogg',
'application/pdf',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.text-template',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.graphics-template',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.presentation-template',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.spreadsheet-template',
'application/vnd.oasis.opendocument.chart',
'application/vnd.oasis.opendocument.chart-template',
'application/vnd.oasis.opendocument.image',
'application/vnd.oasis.opendocument.image-template',
'application/vnd.oasis.opendocument.formula',
'application/vnd.oasis.opendocument.formula-template',
'application/vnd.oasis.opendocument.text-master',
'application/vnd.oasis.opendocument.text-web',
'application/x-zip',
'application/zip',
'text/plain',
'video/mpeg',
'video/mp4',
'video/quicktime',
'video/mpeg'),
'file_quota' => 5000000,
'user_quota' => 50000000,
'monthly_quota' => 15000000,
),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@ -170,6 +210,7 @@ $config['db'] =
'require_prefix' => 'classes/',
'class_prefix' => '',
'mirror' => null,
'utf8' => true,
'db_driver' => 'DB', # XXX: JanRain libs only work with DB
'quote_identifiers' => false,
'type' => 'mysql' );
@ -219,19 +260,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
// XXX: how many of these could be auto-loaded on use?
require_once('Validate.php');
require_once('markdown.php');
require_once 'Validate.php';
require_once 'markdown.php';
require_once(INSTALLDIR.'/lib/util.php');
require_once(INSTALLDIR.'/lib/action.php');
require_once(INSTALLDIR.'/lib/theme.php');
require_once(INSTALLDIR.'/lib/mail.php');
require_once(INSTALLDIR.'/lib/subs.php');
require_once(INSTALLDIR.'/lib/Shorturl_api.php');
require_once(INSTALLDIR.'/lib/twitter.php');
require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/theme.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/Shorturl_api.php';
require_once INSTALLDIR.'/lib/twitter.php';
require_once(INSTALLDIR.'/lib/clientexception.php');
require_once(INSTALLDIR.'/lib/serverexception.php');
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
// XXX: other formats here

View File

@ -98,9 +98,9 @@ class FacebookAction extends Action
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
$theme = common_config('site', 'theme');
@ -278,7 +278,7 @@ class FacebookAction extends Action
$this->element('a',
array('href' => common_local_url('register')), _('Register'));
$this->text($loginmsg_part2);
$this->elementEnd('p');
$this->elementEnd('p');
$this->elementEnd('dd');
$this->elementEnd('dl');
@ -317,7 +317,7 @@ class FacebookAction extends Action
$this->elementEnd('ul');
$this->submit('submit', _('Login'));
$this->elementEnd('fieldset');
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementStart('p');
@ -336,65 +336,65 @@ class FacebookAction extends Action
// Need to include inline CSS for styling the Profile box
$app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
$icon_url = $app_props['icon_url'];
$app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
$icon_url = $app_props['icon_url'];
$style = '<style>
.entry-title *,
.entry-content * {
font-size:14px;
font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
}
.entry-title a,
.entry-content a {
color:#002E6E;
}
.entry-title *,
.entry-content * {
font-size:14px;
font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
}
.entry-title a,
.entry-content a {
color:#002E6E;
}
.entry-title .vcard .photo {
float:left;
display:inline;
margin-right:11px;
margin-bottom:11px
margin-right:11px;
margin-bottom:11px
}
.entry-title {
margin-bottom:11px;
}
.entry-title {
margin-bottom:11px;
}
.entry-title p.entry-content {
display:inline;
margin-left:5px;
margin-left:5px;
}
div.entry-content {
clear:both;
}
div.entry-content {
clear:both;
}
div.entry-content dl,
div.entry-content dt,
div.entry-content dd {
display:inline;
text-transform:lowercase;
text-transform:lowercase;
}
div.entry-content dd,
div.entry-content .device dt {
margin-left:0;
margin-right:5px;
div.entry-content .device dt {
margin-left:0;
margin-right:5px;
}
div.entry-content dl.timestamp dt,
div.entry-content dl.response dt {
div.entry-content dl.response dt {
display:none;
}
div.entry-content dd a {
display:inline-block;
}
#facebook_laconica_app {
text-indent:-9999px;
height:16px;
width:16px;
display:block;
background:url('.$icon_url.') no-repeat 0 0;
float:right;
}
#facebook_laconica_app {
text-indent:-9999px;
height:16px;
width:16px;
display:block;
background:url('.$icon_url.') no-repeat 0 0;
float:right;
}
</style>';
$this->xw->openMemory();
@ -646,48 +646,16 @@ class FacebookNoticeListItem extends NoticeListItem
function show()
{
$this->showStart();
$this->showNotice();
$this->showNoticeInfo();
$this->out->elementStart('div', 'entry-title');
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
$this->showReplyTo();
$this->out->elementEnd('div');
// XXX: Need to update to show attachements and controls
$this->showEnd();
}
function showNoticeLink()
{
$noticeurl = common_local_url('shownotice',
array('notice' => $this->notice->id));
// XXX: we need to figure this out better. Is this right?
if (strcmp($this->notice->uri, $noticeurl) != 0 &&
preg_match('/^http/', $this->notice->uri)) {
$noticeurl = $this->notice->uri;
}
$this->out->elementStart('dl', 'timestamp');
$this->out->element('dt', null, _('Published'));
$this->out->elementStart('dd', null);
$this->out->elementStart('a', array('rel' => 'bookmark',
'href' => $noticeurl));
$dt = common_date_iso8601($this->notice->created);
$this->out->element('abbr', array('class' => 'published',
'title' => $dt),
common_date_string($this->notice->created));
$this->out->elementEnd('a');
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}
class FacebookProfileBoxNotice extends FacebookNoticeListItem
{
@ -706,28 +674,16 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
/**
* Recipe function for displaying a single notice in the
* Facebook App's Profile
* Facebook App profile notice box
*
* @return void
*/
function show()
{
$this->out->elementStart('div', 'entry-title');
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
$this->showReplyTo();
$this->out->elementEnd('div');
$this->showNotice();
$this->showNoticeInfo();
$this->showAppLink();
}
function showAppLink()

View File

@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php';
class Form extends Widget
{
var $enctype = null;
/**
* Show the form
*
@ -63,11 +65,15 @@ class Form extends Widget
function show()
{
$this->out->elementStart('form',
array('id' => $this->id(),
'class' => $this->formClass(),
'method' => 'post',
'action' => $this->action()));
$attributes = array('id' => $this->id(),
'class' => $this->formClass(),
'method' => 'post',
'action' => $this->action());
if (!empty($this->enctype)) {
$attributes['enctype'] = $this->enctype;
}
$this->out->elementStart('form', $attributes);
$this->out->elementStart('fieldset');
$this->formLegend();
$this->sessionToken();

View File

@ -1,66 +0,0 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* FIXME
*
* 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 Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* FIXME
*
* These are the widgets that show interesting data about a person
* group, or site.
*
* @category Widget
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class FrequentAttachmentSection extends AttachmentSection
{
function getAttachments() {
$notice_tag = new Notice_tag;
$query = 'select file_id, count(file_id) as c from notice_tag join file_to_post on post_id = notice_id where tag="' . $notice_tag->escape($this->out->tag) . '" group by file_id order by c desc';
$notice_tag->query($query);
return $notice_tag;
}
function title()
{
return sprintf(_('Attachments frequently tagged with %s'), $this->out->tag);
}
function divId()
{
return 'frequent_attachments';
}
}

View File

@ -90,6 +90,7 @@ class NoticeForm extends Form
$this->user = common_current_user();
}
$this->enctype = 'multipart/form-data';
}
/**
@ -142,17 +143,21 @@ class NoticeForm extends Form
'rows' => 4,
'name' => 'status_textarea'),
($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');
$this->out->element('label', array('for' => 'notice_data-attach'), _('Attach'));
$this->out->element('input', array('id' => 'notice_data-attach',
'type' => 'file',
'name' => 'attach',
'title' => _('Attach a file')));
if ($this->action) {
$this->out->hidden('notice_return-to', $this->action, 'returnto');
}
$this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto');
$this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
}
/**

View File

@ -34,6 +34,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/favorform.php';
require_once INSTALLDIR.'/lib/disfavorform.php';
require_once INSTALLDIR.'/lib/attachmentlist.php';
/**
* widget for displaying a list of notices
@ -85,7 +86,7 @@ class NoticeList extends Widget
{
$this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ul', array('class' => 'notices'));
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
$cnt = 0;
@ -100,7 +101,7 @@ class NoticeList extends Widget
$item->show();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('ol');
$this->out->elementEnd('div');
return $cnt;
@ -180,86 +181,49 @@ class NoticeListItem extends Widget
$this->showStart();
$this->showNotice();
$this->showNoticeAttachments();
$this->showNoticeOptions();
$this->showNoticeInfo();
$this->showNoticeOptions();
$this->showEnd();
}
function showNotice()
{
if(0)
$this->out->elementStart('entry-title');
else
if ('shownotice' === $this->out->args['action']) {
$width = '85%';
} else {
$width = '90%';
}
$this->out->elementStart('div', array('class' => 'entry-title', 'style' => "float: left; width: $width;"));
$this->out->elementStart('div', 'entry-title');
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
}
function showNoticeAttachments()
{
$f2p = new File_to_post;
$f2p->post_id = $this->notice->id;
$file = new File;
$file->joinAdd($f2p);
$file->selectAdd();
$file->selectAdd('file.id as id');
$count = $file->find(true);
if (!$count) return;
if (1 === $count) {
$href = common_local_url('attachment', array('attachment' => $file->id));
$att_class = 'attachment';
} else {
$href = common_local_url('attachments', array('notice' => $this->notice->id));
$att_class = 'attachments';
function showNoticeAttachments() {
if ($this->isUsedInList()) {
return;
}
$al = new AttachmentList($this->notice, $this->out);
$al->show();
}
$clip = theme_path('images/icons/clip', 'base');
if ('shownotice' === $this->out->args['action']) {
$height = '96px';
$width = '83%';
$width_att = '15%';
$clip .= '-big.png';
$top = '70px';
} else {
$height = '48px';
$width = '90%';
$width_att = '8%';
$clip .= '.png';
$top = '20px';
}
if(0)
$this->out->elementStart('div', 'entry-attachments');
else
$this->out->elementStart('p', array('class' => 'entry-attachments', 'style' => "float: right; width: $width_att; background: url($clip) no-repeat; text-align: right; height: $height;"));
$this->out->element('a', array('class' => $att_class, 'style' => "text-decoration: none; padding-top: $top; display: block; height: $height;", 'href' => $href, 'title' => "# of attachments: $count"), $count === 1 ? '' : $count);
function isUsedInList() {
return 'shownotice' !== $this->out->args['action'];
}
/*
function attachmentCount($discriminant = true) {
$file_oembed = new File_oembed;
$query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id;
$file_oembed->query($query);
$file_oembed->fetch();
return intval($file_oembed->c);
}
*/
$this->out->elementEnd('p');
function showWithAttachment() {
}
function showNoticeInfo()
{
if(0)
$this->out->elementStart('div', 'entry-content');
else
if ('shownotice' === $this->out->args['action']) {
$width = '85%';
} else {
$width = '90%';
}
$this->out->elementStart('div', array('class' => 'entry-content', 'style' => "float: left; width: $width;"));
$this->showNoticeLink();
// $this->showWithAttachment();
$this->showNoticeSource();
$this->showContext();
$this->out->elementEnd('div');
@ -269,10 +233,7 @@ else
{
$user = common_current_user();
if ($user) {
if(0)
$this->out->elementStart('div', 'notice-options');
else
$this->out->elementStart('div', array('class' => 'notice-options', 'style' => 'float: right; width: 16%;'));
$this->showFaveForm();
$this->showReplyLink();
$this->showDeleteLink();
@ -403,6 +364,10 @@ else
// versions (>> 0.4.x)
$this->out->raw(common_render_content($this->notice->content, $this->notice));
}
$uploaded = $this->notice->getUploadedAttachment();
if ($uploaded) {
$this->out->element('a', array('href' => $uploaded, 'class' => 'attachment'), $uploaded);
}
$this->out->elementEnd('p');
}
@ -433,6 +398,7 @@ else
$this->out->element('abbr', array('class' => 'published',
'title' => $dt),
common_date_string($this->notice->created));
$this->out->elementEnd('a');
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');

View File

@ -52,12 +52,12 @@ class NoticeSection extends Section
{
$notices = $this->getNotices();
$cnt = 0;
$this->out->elementStart('ul', 'notices');
$this->out->elementStart('ol', 'notices xoxo');
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices);
}
$this->out->elementEnd('ul');
$this->out->elementEnd('ol');
return ($cnt > NOTICES_PER_SECTION);
}

View File

@ -153,24 +153,21 @@ class Router
$m->connect('attachment/:attachment/ajax',
array('action' => 'attachment_ajax'),
array('notice' => '[0-9]+'));
array('attachment' => '[0-9]+'));
$m->connect('attachment/:attachment',
array('action' => 'attachment'),
array('notice' => '[0-9]+'));
// notice
$m->connect('attachment/:attachment/thumbnail',
array('action' => 'attachment_thumbnail'),
array('attachment' => '[0-9]+'));
$m->connect('notice/new', array('action' => 'newnotice'));
$m->connect('notice/new?replyto=:replyto',
array('action' => 'newnotice'),
array('replyto' => '[A-Za-z0-9_-]+'));
$m->connect('notice/:notice/attachments/ajax',
array('action' => 'attachments_ajax'),
array('notice' => '[0-9]+'));
$m->connect('notice/:notice/attachments',
array('action' => 'attachments'),
array('notice' => '[0-9]+'));
$m->connect('notice/:notice/file',
array('action' => 'file'),
array('notice' => '[0-9]+'));
$m->connect('notice/:notice',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));

View File

@ -51,7 +51,7 @@ class SearchAction extends Action
*
* @return boolean true
*/
function isReadOnly()
function isReadOnly($args)
{
return true;
}

227
lib/snapshot.php Normal file
View File

@ -0,0 +1,227 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* A snapshot of site stats that can report itself to headquarters
*
* 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 Stats
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* A snapshot of site stats that can report itself to headquarters
*
* This class will collect statistics on the site and report them to
* a statistics server of the admin's choice. (Default is the big one
* at laconi.ca.)
*
* It can either be called from a cron job, or run occasionally by the
* Web site.
*
* @category Stats
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
*/
class Snapshot
{
var $stats = null;
/**
* Constructor for a snapshot
*/
function __construct()
{
}
/**
* Static function for reporting statistics
*
* This function checks whether it should report statistics, based on
* the current configuation settings. If it should, it creates a new
* Snapshot object, takes a snapshot, and reports it to headquarters.
*
* @return void
*/
static function check()
{
switch (common_config('snapshot', 'run')) {
case 'web':
// skip if we're not running on the Web.
if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
break;
}
// Run once every frequency hits
// XXX: do frequency by time (once a week, etc.) rather than
// hits
if (rand() % common_config('snapshot', 'frequency') == 0) {
$snapshot = new Snapshot();
$snapshot->take();
$snapshot->report();
}
break;
case 'cron':
// skip if we're running on the Web
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
break;
}
common_log(LOG_INFO, 'Running snapshot from cron job');
// We're running from the command line; assume
$snapshot = new Snapshot();
$snapshot->take();
common_log(LOG_INFO, count($snapshot->stats) . " statistics being uploaded.");
$snapshot->report();
break;
case 'never':
break;
default:
common_log(LOG_WARNING, "Unrecognized value for snapshot run config.");
}
}
/**
* Take a snapshot of the server
*
* Builds an array of statistical and configuration data based
* on the local database and config files. We avoid grabbing any
* information that could be personal or private.
*
* @return void
*/
function take()
{
$this->stats = array();
// Some basic identification stuff
$this->stats['version'] = LACONICA_VERSION;
$this->stats['phpversion'] = phpversion();
$this->stats['name'] = common_config('site', 'name');
$this->stats['root'] = common_root_url();
// non-identifying stats on various tables. Primary
// interest is size and rate of activity of service.
$tables = array('user',
'notice',
'subscription',
'remote_profile',
'user_group');
foreach ($tables as $table) {
$this->tableStats($table);
}
// stats on some important config options
$this->stats['theme'] = common_config('site', 'theme');
$this->stats['dbtype'] = common_config('db', 'type');
$this->stats['xmpp'] = common_config('xmpp', 'enabled');
$this->stats['inboxes'] = common_config('inboxes', 'enabled');
$this->stats['queue'] = common_config('queue', 'enabled');
$this->stats['license'] = common_config('license', 'url');
$this->stats['fancy'] = common_config('site', 'fancy');
$this->stats['private'] = common_config('site', 'private');
$this->stats['closed'] = common_config('site', 'closed');
$this->stats['memcached'] = common_config('memcached', 'enabled');
$this->stats['language'] = common_config('site', 'language');
$this->stats['timezone'] = common_config('site', 'timezone');
}
/**
* Reports statistics to headquarters
*
* Posts statistics to a reporting server.
*
* @return void
*/
function report()
{
// XXX: Use OICU2 and OAuth to make authorized requests
$postdata = http_build_query($this->stats);
$opts =
array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: '.
'application/x-www-form-urlencoded',
'content' => $postdata,
'user_agent' => 'Laconica/'.LACONICA_VERSION
)
);
$context = stream_context_create($opts);
$reporturl = common_config('snapshot', 'reporturl');
$result = @file_get_contents($reporturl, false, $context);
return $result;
}
/**
* Updates statistics for a single table
*
* Determines the size of a table and its oldest and newest rows.
* Goal here is to see how active a site is. Note that it
* fills up the instance stats variable.
*
* @param string $table name of table to check
*
* @return void
*/
function tableStats($table)
{
$inst = DB_DataObject::factory($table);
$inst->selectAdd();
$inst->selectAdd('count(*) as cnt, '.
'min(created) as first, '.
'max(created) as last');
if ($inst->find(true)) {
$this->stats[$table.'count'] = $inst->cnt;
$this->stats[$table.'first'] = $inst->first;
$this->stats[$table.'last'] = $inst->last;
}
$inst->free();
unset($inst);
}
}

View File

@ -496,6 +496,32 @@ function common_linkify($url) {
}
$attrs = array('href' => $longurl, 'rel' => 'external');
// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
// where ID is the id of the attachment for the given URL.
//
// we need a better test telling what can be shown as an attachment
// we're currently picking up oembeds only.
// I think the best option is another file_view table in the db
// and associated dbobject.
$query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
$file = new File;
$file->query($query);
$file->fetch();
if (!empty($file->file_id)) {
$query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'";
$file2 = new File;
$file2->query($query);
$file2->fetch();
if (empty($file2->file_id)) {
$attrs['class'] = 'attachment';
} else {
$attrs['class'] = 'attachment thumbnail';
}
$attrs['id'] = "attachment-{$file->file_id}";
}
return XMLStringer::estring('a', $attrs, $display);
}

View File

@ -0,0 +1,110 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* 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 Menu
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* Menu for login group of actions
*
* @category Output
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Widget
*/
class FBCLoginGroupNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
// action => array('prompt', 'title')
$menu = array();
$menu['login'] = array(_('Login'),
_('Login with a username and password'));
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
$menu['register'] = array(_('Register'),
_('Sign up for a new account'));
}
$menu['openidlogin'] = array(_('OpenID'),
_('Login or register with OpenID'));
$menu['FBConnectLogin'] = array(_('Facebook'),
_('Login or register using Facebook'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* 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 Menu
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* A widget for showing the connect group local nav menu
*
* @category Output
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Widget
*/
class FBCSettingsNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
# action => array('prompt', 'title')
$menu =
array('imsettings' =>
array(_('IM'),
_('Updates by instant messenger (IM)')),
'smssettings' =>
array(_('SMS'),
_('Updates by SMS')),
'twittersettings' =>
array(_('Twitter'),
_('Twitter integration options')),
'FBConnectSettings' =>
array(_('Facebook'),
_('Facebook Connect settings')));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
if ($menuaction == 'imsettings' &&
!common_config('xmpp', 'enabled')) {
continue;
}
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@ -0,0 +1,73 @@
<?php
if (!defined('LACONICA')) {
exit(1);
}
/*
* Generates the cross domain communication channel file
* (xd_receiver.html). By generating it we can add some caching
* instructions.
*
* See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
*/
class FBC_XDReceiverAction extends Action
{
/**
* Do we need to write to the database?
*
* @return boolean true
*/
function isReadonly()
{
return true;
}
/**
* Handle a request
*
* @param array $args Arguments from $_REQUEST
*
* @return void
*/
function handle($args)
{
// Parent handling, including cache check
parent::handle($args);
$this->showPage();
}
function showPage()
{
// cache the xd_receiver
header('Cache-Control: max-age=225065900');
header('Expires:');
header('Pragma:');
$this->startXML('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$language = $this->getLanguage();
$this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xml:lang' => $language,
'lang' => $language));
$this->elementStart('head');
$this->element('title', null, 'cross domain receiver page');
$this->element('script',
array('src' =>
'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js',
'type' => 'text/javascript'), '');
$this->elementEnd('head');
$this->elementStart('body');
$this->elementEnd('body');
$this->elementEnd('html');
}
}

View File

@ -0,0 +1,454 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
class FBConnectauthAction extends Action
{
var $fbuid = null;
var $fb_fields = null;
function prepare($args) {
parent::prepare($args);
try {
$this->fbuid = getFacebook()->get_loggedin_user();
if ($this->fbuid > 0) {
$this->fb_fields = $this->getFacebookFields($this->fbuid,
array('first_name', 'last_name', 'name'));
} else {
common_debug("No Facebook User found.");
}
} catch (Exception $e) {
common_log(LOG_WARNING, 'Problem getting Facebook uid: ' .
$e->getMessage());
}
return true;
}
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
// User is already logged in. Does she already have a linked Facebook acct?
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if ($flink) {
// User already has a linked Facebook account and shouldn't be here
common_debug('There is already a local user (' . $flink->user_id .
') linked with this Facebook (' . $this->fbuid . ').');
// We don't want these cookies
getFacebook()->clear_cookie_state();
$this->clientError(_('There is already a local user linked with this Facebook.'));
} else {
// User came from the Facebook connect settings tab, and
// probably just wants to link/relink their Facebook account
$this->connectUser();
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. Try again, please.'));
return;
}
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->showForm(_('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname'));
return;
}
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectNewUser();
} else {
common_debug(print_r($this->args, true), __FILE__);
$this->showForm(_('Something weird happened.'),
$this->trimmed('newname'));
}
} else {
$this->tryLogin();
}
}
function showPageNotice()
{
if ($this->error) {
$this->element('div', array('class' => 'error'), $this->error);
} else {
$this->element('div', 'instructions',
sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
}
}
function title()
{
return _('Facebook Account Setup');
}
function showForm($error=null, $username=null)
{
$this->error = $error;
$this->username = $username;
$this->showPage();
}
function showPage()
{
parent::showPage();
}
function showContent()
{
if (!empty($this->message_text)) {
$this->element('p', null, $this->message);
return;
}
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook_connect',
'class' => 'form_settings',
'action' => common_local_url('FBConnectAuth')));
$this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
$this->element('legend', null, _('Connection options'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->element('input', array('type' => 'checkbox',
'id' => 'license',
'class' => 'checkbox',
'name' => 'license',
'value' => 'true'));
$this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
$this->text(_('My text and files are available under '));
$this->element('a', array('href' => common_config('license', 'url')),
common_config('license', 'title'));
$this->text(_(' except this private data: password, email address, IM address, phone number.'));
$this->elementEnd('label');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->element('legend', null,
_('Create new account'));
$this->element('p', null,
_('Create a new user with this nickname.'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('newname', _('New nickname'),
($this->username) ? $this->username : '',
_('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('create', _('Create'));
$this->elementEnd('fieldset');
$this->elementStart('fieldset');
$this->element('legend', null,
_('Connect existing account'));
$this->element('p', null,
_('If you already have an account, login with your username and password to connect it to your Facebook.'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('nickname', _('Existing nickname'));
$this->elementEnd('li');
$this->elementStart('li');
$this->password('password', _('Password'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('connect', _('Connect'));
$this->elementEnd('fieldset');
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function message($msg)
{
$this->message_text = $msg;
$this->showPage();
}
function createNewUser()
{
if (common_config('site', 'closed')) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = null;
if (common_config('site', 'inviteonly')) {
$code = $_SESSION['invitecode'];
if (empty($code)) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = Invitation::staticGet($code);
if (empty($invite)) {
$this->clientError(_('Not a valid invitation code.'));
return;
}
}
$nickname = $this->trimmed('newname');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
}
if (!User::allowed_nickname($nickname)) {
$this->showForm(_('Nickname not allowed.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->showForm(_('Nickname already in use. Try another one.'));
return;
}
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
$args = array('nickname' => $nickname, 'fullname' => $fullname);
if (!empty($invite)) {
$args['code'] = $invite->code;
}
$user = User::register($args);
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_set_user($user);
common_real_login(true);
common_debug("Registered new user $user->id from Facebook user $this->fbuid");
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
303);
}
function connectNewUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->showForm(_('Invalid username or password.'));
return;
}
$user = User::staticGet('nickname', $nickname);
if ($user) {
common_debug("Legit user to connect to Facebook: $nickname");
}
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
function connectUser()
{
$user = common_current_user();
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
// Return to Facebook connection settings tab
common_redirect(common_local_url('FBConnectSettings'), 303);
}
function tryLogin()
{
common_debug("Trying Facebook Login...");
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if ($flink) {
$user = $flink->getUser();
if ($user) {
common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
} else {
common_debug("No flink found for fbuid: $this->fbuid");
$this->showForm(null, $this->bestNewNickname());
}
}
function goHome($nickname)
{
$url = common_get_returnto();
if ($url) {
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('all',
array('nickname' =>
$nickname));
}
common_redirect($url, 303);
}
function flinkUser($user_id, $fbuid)
{
$flink = new Foreign_link();
$flink->user_id = $user_id;
$flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_CONNECT_SERVICE;
$flink->created = common_sql_now();
$flink_id = $flink->insert();
return $flink_id;
}
function bestNewNickname()
{
if (!empty($this->fb_fields['name'])) {
$nickname = $this->nicknamize($this->fb_fields['name']);
if ($this->isNewNickname($nickname)) {
return $nickname;
}
}
// Try the full name
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
if (!empty($fullname)) {
$fullname = $this->nicknamize($fullname);
if ($this->isNewNickname($fullname)) {
return $fullname;
}
}
return null;
}
// Given a string, try to make it work as a nickname
function nicknamize($str)
{
$str = preg_replace('/\W/', '', $str);
return strtolower($str);
}
function isNewNickname($str)
{
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
return false;
}
if (!User::allowed_nickname($str)) {
return false;
}
if (User::staticGet('nickname', $str)) {
return false;
}
return true;
}
// XXX: Consider moving this to lib/facebookutil.php
function getFacebookFields($fb_uid, $fields) {
try {
$facebook = getFacebook();
$infos = $facebook->api_client->users_getInfo($fb_uid, $fields);
if (empty($infos)) {
return null;
}
return reset($infos);
} catch (Exception $e) {
common_log(LOG_WARNING, "Facebook client failure when requesting " .
join(",", $fields) . " on uid " . $fb_uid .
" : ". $e->getMessage());
return null;
}
}
}

View File

@ -1,12 +1,9 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* 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.
@ -18,354 +15,53 @@
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/lib/facebookutil.php';
if (!defined('LACONICA')) {
exit(1);
}
class FBConnectloginAction extends Action
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
class FBConnectLoginAction extends Action
{
var $fbuid = null;
var $fb_fields = null;
function prepare($args) {
parent::prepare($args);
$this->fbuid = getFacebook()->get_loggedin_user();
$this->fb_fields = $this->getFacebookFields($this->fbuid,
array('first_name', 'last_name', 'name'));
return true;
}
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. Try again, please.'));
return;
}
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->showForm(_('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname'));
return;
}
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectUser();
} else {
common_debug(print_r($this->args, true), __FILE__);
$this->showForm(_('Something weird happened.'),
$this->trimmed('newname'));
}
} else {
$this->tryLogin();
}
$this->showPage();
}
function getInstructions()
{
return _('Login with your Facebook Account');
}
function showPageNotice()
{
if ($this->error) {
$this->element('div', array('class' => 'error'), $this->error);
} else {
$this->element('div', 'instructions',
sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
}
$instr = $this->getInstructions();
$output = common_markup_to_html($instr);
$this->elementStart('div', 'instructions');
$this->raw($output);
$this->elementEnd('div');
}
function title()
{
return _('Facebook Account Setup');
return _('Facebook Login');
}
function showForm($error=null, $username=null)
{
$this->error = $error;
$this->username = $username;
function showContent() {
$this->showPage();
}
$this->elementStart('fieldset');
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
function showPage()
{
parent::showPage();
}
function showContent()
{
if (!empty($this->message_text)) {
$this->element('p', null, $this->message);
return;
}
$this->elementStart('form', array('method' => 'post',
'id' => 'account_connect',
'action' => common_local_url('fbconnectlogin')));
$this->hidden('token', common_session_token());
$this->element('h2', null,
_('Create new account'));
$this->element('p', null,
_('Create a new user with this nickname.'));
$this->input('newname', _('New nickname'),
($this->username) ? $this->username : '',
_('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->elementStart('p');
$this->element('input', array('type' => 'checkbox',
'id' => 'license',
'name' => 'license',
'value' => 'true'));
$this->text(_('My text and files are available under '));
$this->element('a', array('href' => common_config('license', 'url')),
common_config('license', 'title'));
$this->text(_(' except this private data: password, email address, IM address, phone number.'));
$this->elementEnd('p');
$this->submit('create', _('Create'));
$this->element('h2', null,
_('Connect existing account'));
$this->element('p', null,
_('If you already have an account, login with your username and password to connect it to your Facebook.'));
$this->input('nickname', _('Existing nickname'));
$this->password('password', _('Password'));
$this->submit('connect', _('Connect'));
$this->elementEnd('form');
}
function message($msg)
{
$this->message_text = $msg;
$this->showPage();
}
function createNewUser()
{
if (common_config('site', 'closed')) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = null;
if (common_config('site', 'inviteonly')) {
$code = $_SESSION['invitecode'];
if (empty($code)) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = Invitation::staticGet($code);
if (empty($invite)) {
$this->clientError(_('Not a valid invitation code.'));
return;
}
}
$nickname = $this->trimmed('newname');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
}
if (!User::allowed_nickname($nickname)) {
$this->showForm(_('Nickname not allowed.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->showForm(_('Nickname already in use. Try another one.'));
return;
}
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
$args = array('nickname' => $nickname, 'fullname' => $fullname);
if (!empty($invite)) {
$args['code'] = $invite->code;
}
$user = User::register($args);
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_set_user($user);
common_real_login(true);
common_debug("Registered new user $user->id from Facebook user $this->fbuid");
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
303);
}
function connectUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->showForm(_('Invalid username or password.'));
return;
}
$user = User::staticGet('nickname', $nickname);
if ($user) {
common_debug("Legit user to connect to Facebook: $nickname");
}
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
function tryLogin()
{
common_debug("Trying Facebook Login...");
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if ($flink) {
$user = $flink->getUser();
if ($user) {
common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
} else {
$this->showForm(null, $this->bestNewNickname());
}
}
function goHome($nickname)
{
$url = common_get_returnto();
if ($url) {
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('all',
array('nickname' =>
$nickname));
}
common_redirect($url, 303);
}
function flinkUser($user_id, $fbuid)
{
$flink = new Foreign_link();
$flink->user_id = $user_id;
$flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_SERVICE;
$flink->created = common_sql_now();
$flink_id = $flink->insert();
return $flink_id;
}
function bestNewNickname()
{
if (!empty($this->fb_fields['name'])) {
$nickname = $this->nicknamize($this->fb_fields['name']);
if ($this->isNewNickname($nickname)) {
return $nickname;
}
}
// Try the full name
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
if (!empty($fullname)) {
$fullname = $this->nicknamize($fullname);
if ($this->isNewNickname($fullname)) {
return $fullname;
}
}
return null;
}
// Given a string, try to make it work as a nickname
function nicknamize($str)
{
$str = preg_replace('/\W/', '', $str);
return strtolower($str);
}
function isNewNickname($str)
{
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
return false;
}
if (!User::allowed_nickname($str)) {
return false;
}
if (User::staticGet('nickname', $str)) {
return false;
}
return true;
}
// XXX: Consider moving this to lib/facebookutil.php
function getFacebookFields($fb_uid, $fields) {
try {
$infos = getFacebook()->api_client->users_getInfo($fb_uid, $fields);
if (empty($infos)) {
return null;
}
return reset($infos);
} catch (Exception $e) {
error_log("Failure in the api when requesting " . join(",", $fields)
." on uid " . $fb_uid . " : ". $e->getMessage());
return null;
}
$this->elementEnd('fieldset');
}
}

View File

@ -0,0 +1,37 @@
/** Styles for Facebook logo and Facebook user profile avatar.
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
#site_nav_global_primary #nav_fb {
position:relative;
margin-left:18px;
margin-right:-7px;
}
#nav_fb .fb_profile_pic_rendered img {
position:relative;
top:3px;
left:0;
display:inline;
border:1px solid #3B5998;
padding:1px;
}
#nav_fb img {
position:absolute;
top:-13px;
left:-11px;
display:inline;
}
#settings_facebook_connect_options legend {
display:none;
}
#form_settings_facebook_connect fieldset fieldset legend {
display:block;
}

View File

@ -31,8 +31,15 @@ if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
define("FACEBOOK_CONNECT_SERVICE", 3);
require_once INSTALLDIR . '/lib/facebookutil.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
/**
* Plugin to enable Facebook Connect
@ -46,7 +53,6 @@ require_once INSTALLDIR . '/lib/facebookutil.php';
class FBConnectPlugin extends Plugin
{
function __construct()
{
parent::__construct();
@ -54,91 +60,91 @@ class FBConnectPlugin extends Plugin
// Hook in new actions
function onRouterInitialized(&$m) {
$m->connect('main/facebookconnect', array('action' => 'fbconnectlogin'));
$m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
$m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
}
// Add in xmlns:fb
function onStartShowHTML($action)
{
// XXX: This is probably a bad place to do general processing
// so maybe I need to make some new events? Maybe in
// Action::prepare?
$name = get_class($action);
// Avoid a redirect loop
if (!in_array($name, array('FBConnectloginAction', 'ClientErrorAction'))) {
$this->checkFacebookUser($action);
}
$httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
$_SERVER['HTTP_ACCEPT'] : null;
// XXX: allow content negotiation for RDF, RSS, or XRDS
$cp = common_accept_to_prefs($httpaccept);
$sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
$type = common_negotiate_type($cp, $sp);
if (!$type) {
throw new ClientException(_('This page is not available in a '.
'media type you accept'), 406);
}
header('Content-Type: '.$type);
// XXX: Horrible hack to make Safari, FF2, and Chrome work with
// Facebook Connect. These browser cannot use Facebook's
// DOM parsing routines unless the mime type of the page is
// text/html even though Facebook Connect uses XHTML. This is
// A bug in Facebook Connect, and this is a temporary solution
// until they fix their JavaScript libs.
header('Content-Type: text/html');
$action->extraHeaders();
$action->startXML('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$language = $action->getLanguage();
$action->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
'xml:lang' => $language,
'lang' => $language));
$action->elementStart('html',
array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
'xml:lang' => $language,
'lang' => $language));
return false;
}
function onEndShowLaconicaScripts($action)
// Note: this script needs to appear in the <body>
function onStartShowHeader($action)
{
$apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect');
$login_url = common_local_url('FBConnectAuth');
$logout_url = common_local_url('logout');
// XXX: Facebook says we don't need this FB_RequireFeatures(),
// but we actually do, for IE and Safari. Gar.
$html = sprintf('<script type="text/javascript">
window.onload = function () {
FB_RequireFeatures(
["XFBML"],
function() {
FB.Facebook.init("%s", "../xd_receiver.html");
}
); }
function goto_login() {
window.location = "%s";
}
function goto_logout() {
window.location = "%s";
}
</script>', $apikey,
$login_url, $logout_url);
$action->raw($html);
}
// Note: this script needs to appear as close as possible to </body>
function onEndShowFooter($action)
{
$action->element('script',
array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
' ');
'');
}
$apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect');
$url = common_get_returnto();
if ($url) {
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('public');
}
$html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm");
function refresh_page() {
window.location = "%s";
}
</script>', $apikey, $plugin_path, $url);
$action->raw($html);
function onEndShowLaconicaStyles($action)
{
$action->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => common_path('plugins/FBConnect/FBConnectPlugin.css')));
}
function onStartPrimaryNav($action)
@ -146,114 +152,148 @@ class FBConnectPlugin extends Plugin
$user = common_current_user();
if ($user) {
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
$flink = Foreign_link::getByUserId($user->id,
FACEBOOK_CONNECT_SERVICE);
$fbuid = 0;
if ($flink) {
try {
$facebook = getFacebook();
$fbuid = getFacebook()->get_loggedin_user();
} catch (Exception $e) {
common_log(LOG_WARNING,
'Problem getting Facebook client: ' .
$e->getMessage());
}
// Display Facebook Logged in indicator w/Facebook favicon
if ($fbuid > 0) {
$action->elementStart('li', array('id' => 'nav_fb'));
$action->elementStart('fb:profile-pic', array('uid' => $flink->foreign_id,
'linked' => 'false',
'width' => 16,
'height' => 16));
$action->elementEnd('fb:profile-pic');
$iconurl = common_path('/plugins/FBConnect/fbfavicon.ico');
$action->element('img', array('src' => $iconurl));
$action->elementEnd('li');
}
}
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
} else {
$action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$action->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
// Need to override the Logout link to make it do FB stuff
if ($flink && $fbuid > 0) {
$logout_url = common_local_url('logout');
$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);
$action->raw($html);
} else {
$action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
$action->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout');
}
$action->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
// Need to override the Logout link to make it do FB stuff
$logout_url = common_local_url('logout');
$title = _('Logout from the site');
$text = _('Logout');
$html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' .
'onclick="FB.Connect.logoutAndRedirect(\'%s\')">%s</a></li>',
$logout_url, $title, $logout_url, $text);
$action->raw($html);
}
else {
if (!common_config('site', 'closed')) {
$action->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
_('Register'), _('Create an account'), false, 'nav_register');
}
$action->menuItem(common_local_url('openidlogin'),
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
$action->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
_('Login'), _('Login to the site'), false, 'nav_login');
}
$action->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
_('Help'), _('Help me!'), false, 'nav_help');
$action->menuItem(common_local_url('peoplesearch'),
_('Search'), _('Search for people or text'), false, 'nav_search');
// Tack on "Connect with Facebook" button
// XXX: Maybe this looks bad and should not go here. Where should it go?
if (!$user) {
$action->elementStart('li');
$action->element('fb:login-button', array('onlogin' => 'refresh_page()',
'length' => 'long'));
$action->elementEnd('li');
}
_('Search'), _('Search for people or text'), false, 'nav_search');
return false;
}
function checkFacebookUser() {
function onStartShowLocalNavBlock($action)
{
$action_name = get_class($action);
$login_actions = array('LoginAction', 'RegisterAction',
'OpenidloginAction', 'FBConnectLoginAction');
if (in_array($action_name, $login_actions)) {
$nav = new FBCLoginGroupNav($action);
$nav->show();
return false;
}
$connect_actions = array('SmssettingsAction',
'TwittersettingsAction', 'FBConnectSettingsAction');
if (in_array($action_name, $connect_actions)) {
$nav = new FBCSettingsNav($action);
$nav->show();
return false;
}
return true;
}
function onStartLogout($action)
{
$user = common_current_user();
if ($user) {
return;
}
$flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
try {
$action->logout();
if ($flink) {
$facebook = getFacebook();
$fbuid = $facebook->get_loggedin_user();
// If you're a Facebook user and you're logged in do nothing
try {
$fbuid = $facebook->get_loggedin_user();
// If you're a Facebook user and you're not logged in
// redirect to Facebook connect login page because that means you have clicked
// the 'connect with Facebook' button and have cookies
if ($fbuid > 0) {
if ($facebook->api_client->users_isAppUser($fbuid) ||
$facebook->api_client->added) {
// user should be connected...
common_debug("Facebook user found: $fbuid");
if ($user) {
common_debug("Facebook user is logged in.");
return;
} else {
common_debug("Facebook user is NOT logged in.");
common_redirect(common_local_url('fbconnectlogin'), 303);
}
} else {
common_debug("No Facebook connect user found.");
if ($fbuid > 0) {
$facebook->logout(common_local_url('public'));
}
}
} catch (Exception $e) {
common_debug('Expired FB session.');
} catch (Exception $e) {
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
$e->getMessage());
}
}
return true;
}
}

View File

@ -0,0 +1,203 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Facebook Connect settings
*
* 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 Settings
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/connectsettingsaction.php';
/**
* Facebook Connect settings action
*
* @category Settings
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class FBConnectSettingsAction extends ConnectSettingsAction
{
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
return _('Facebook Connect Settings');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _('Manage how your account connects to Facebook');
}
/**
* Content area of the page
*
* Shows a form for uploading an avatar.
*
* @return void
*/
function showContent()
{
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook',
'class' => 'form_settings',
'action' =>
common_local_url('FBConnectSettings')));
if (!$flink) {
$this->element('p', 'instructions',
_('There is no Facebook user connected to this account.'));
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
} else {
$this->element('p', 'form_note',
_('Connected Facebook user'));
$this->elementStart('p', array('class' => 'facebook-user-display'));
$this->elementStart('fb:profile-pic',
array('uid' => $flink->foreign_id,
'size' => 'small',
'linked' => 'true',
'facebook-logo' => 'true'));
$this->elementEnd('fb:profile-pic');
$this->elementStart('fb:name', array('uid' => $flink->foreign_id,
'useyou' => 'false'));
$this->elementEnd('fb:name');
$this->elementEnd('p');
$this->hidden('token', common_session_token());
$this->elementStart('fieldset');
$this->element('legend', null, _('Disconnect my account from Facebook'));
if (!$user->password) {
$this->elementStart('p', array('class' => 'form_guide'));
$this->text(_('Disconnecting your Faceboook ' .
'would make it impossible to log in! Please '));
$this->element('a',
array('href' => common_local_url('passwordsettings')),
_('set a password'));
$this->text(_(' first.'));
$this->elementEnd('p');
} else {
$note = 'Keep your %s account but disconnect from Facebook. ' .
'You\'ll use your %s password to log in.';
$site = common_config('site', 'name');
$this->element('p', 'instructions',
sprintf($note, $site, $site));
$this->submit('disconnect', _('Disconnect'));
}
$this->elementEnd('fieldset');
}
$this->elementEnd('form');
}
/**
* Handle post
*
* Disconnects the current Facebook user from the current user's account
*
* @return void
*/
function handlePost()
{
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
if ($this->arg('disconnect')) {
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
$result = $flink->delete();
if ($result === false) {
common_log_db_error($user, 'DELETE', __FILE__);
$this->serverError(_('Couldn\'t delete link to Facebook.'));
return;
}
try {
// Clear FB Connect cookies out
$facebook = getFacebook();
$facebook->clear_cookie_state();
} catch (Exception $e) {
common_log(LOG_WARNING,
'Couldn\'t clear Facebook cookies: ' .
$e->getMessage());
}
$this->showForm(_('You have disconnected from Facebook.'), true);
} else {
$this->showForm(_('Not sure what you\'re trying to do.'));
return;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,10 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>cross domain receiver page</title>
</head>
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script>
</body>
</html>

View File

@ -0,0 +1,109 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to show WikiHashtags content in the sidebar
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author 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);
}
define('WIKIHASHTAGSPLUGIN_VERSION', '0.1');
/**
* Plugin to use WikiHashtags
*
* @category Plugin
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see Event
*/
class WikiHashtagsPlugin extends Plugin
{
function __construct($code=null)
{
parent::__construct();
}
function onStartShowSections($action)
{
$name = $action->trimmed('action');
if ($name == 'tag') {
$taginput = $action->trimmed('tag');
$tag = common_canonical_tag($taginput);
if (!empty($tag)) {
$url = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=render',
urlencode($tag));
$editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit',
urlencode($tag));
$context = stream_context_create(array('http' => array('method' => "GET",
'header' =>
"User-Agent: " . $this->userAgent())));
$html = @file_get_contents($url, false, $context);
$action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section'));
if (!empty($html)) {
$action->element('style', null,
"span.editsection { display: none }\n".
"table.toc { display: none }");
$action->raw($html);
$action->elementStart('p');
$action->element('a', array('href' => $editurl,
'title' => sprintf(_('Edit the article for #%s on WikiHashtags'), $tag)),
_('Edit'));
$action->element('a', array('href' => 'http://www.gnu.org/copyleft/fdl.html',
'title' => _('Shared under the terms of the GNU Free Documentation License'),
'rel' => 'license'),
'GNU FDL');
$action->elementEnd('p');
} else {
$action->element('a', array('href' => $editurl),
sprintf(_('Start the article for #%s on WikiHashtags'), $tag));
}
$action->elementEnd('div');
}
}
return true;
}
function userAgent()
{
return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION .
' Laconica/' . LACONICA_VERSION;
}
}

141
scripts/fixup_utf8.php Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
# Abort if called from a web server
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit(1);
}
ini_set("max_execution_time", "0");
ini_set("max_input_time", "0");
set_time_limit(0);
mb_internal_encoding('UTF-8');
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('LACONICA', true);
require_once(INSTALLDIR . '/lib/common.php');
require_once('DB.php');
function fixup_utf8($id) {
$dbl = doConnect('latin1');
if (empty($dbl)) {
return;
}
$dbu = doConnect('utf8');
if (empty($dbu)) {
return;
}
// Do a separate DB connection
$sth = $dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
if (PEAR::isError($sth)) {
echo "ERROR: " . $sth->getMessage() . "\n";
return;
}
$sql = 'SELECT id, content, rendered FROM notice ' .
'WHERE LENGTH(content) != CHAR_LENGTH(content)';
if (!empty($id)) {
$sql .= ' AND id < ' . $id;
}
$sql .= ' ORDER BY id DESC';
$rn = $dbl->query($sql);
if (PEAR::isError($rn)) {
echo "ERROR: " . $rn->getMessage() . "\n";
return;
}
echo "Number of rows: " . $rn->numRows() . "\n";
$notice = array();
while (DB_OK == $rn->fetchInto($notice)) {
$id = ($notice[0])+0;
$content = bin2hex($notice[1]);
$rendered = bin2hex($notice[2]);
echo "$id...";
$result =& $dbu->execute($sth, array($content, $rendered, $id));
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
continue;
}
$cnt = $dbu->affectedRows();
if ($cnt != 1) {
echo "ERROR: 0 rows affected\n";
continue;
}
$notice = Notice::staticGet('id', $id);
$notice->decache();
echo "OK\n";
}
}
function doConnect($charset)
{
$db = DB::connect(common_config('db', 'database'),
array('persistent' => false));
if (PEAR::isError($db)) {
echo "ERROR: " . $db->getMessage() . "\n";
return NULL;
}
$result = $db->query("SET NAMES $charset");
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
$db->disconnect();
return NULL;
}
$result = $db->autoCommit(true);
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
$db->disconnect();
return NULL;
}
return $db;
}
$id = ($argc > 1) ? $argv[1] : null;
fixup_utf8($id);

View File

@ -0,0 +1,37 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
# Abort if called from a web server
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit(1);
}
ini_set("max_execution_time", "0");
ini_set("max_input_time", "0");
set_time_limit(0);
mb_internal_encoding('UTF-8');
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('LACONICA', true);
require_once(INSTALLDIR . '/lib/common.php');
Snapshot::check();

View File

@ -214,7 +214,8 @@ class TwitterStatusFetcher extends Daemon
return;
}
foreach ($timeline as $status) {
// Reverse to preserve order
foreach (array_reverse($timeline) as $status) {
// Hacktastic: filter out stuff coming from this Laconica
$source = mb_strtolower(common_config('integration', 'source'));

View File

@ -198,9 +198,11 @@ padding:0 7px;
}
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
padding:0;
}
#form_search .submit {
@ -450,6 +452,21 @@ float:left;
font-size:1.3em;
margin-bottom:7px;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:25px;
right:49px;
width:16px;
height:16px;
cursor:pointer;
}
#form_notice #notice_data-attach {
text-indent:-279px;
}
#form_notice #notice_submit label {
display:none;
}
@ -740,12 +757,14 @@ border-top-style:dotted;
.notices li {
list-style-type:none;
}
.notices li.hover {
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
.notices .notices {
margin-top:7px;
margin-left:5%;
width:95%;
float:left;
}
/* NOTICES */
#notices_primary {
float:left;
@ -794,6 +813,9 @@ float:left;
width:100%;
overflow:hidden;
}
.notice .entry-title.ov {
overflow:visible;
}
#shownotice .notice .entry-title {
font-size:2.2em;
}
@ -818,7 +840,7 @@ clear:left;
float:left;
font-size:0.95em;
margin-left:59px;
width:65%;
width:60%;
}
#showstream .notice div.entry-content,
#shownotice .notice div.entry-content {
@ -848,15 +870,12 @@ display:inline-block;
text-transform:lowercase;
}
.notice-options {
padding-left:2%;
float:left;
width:50%;
position:relative;
font-size:0.95em;
width:12.5%;
width:90px;
float:right;
margin-right:11px;
}
.notice-options a {
@ -918,6 +937,75 @@ padding:0;
}
.notice .attachment {
position:relative;
padding-left:16px;
}
#attachments .attachment {
padding-left:0;
}
.notice .attachment img {
position:absolute;
top:18px;
left:0;
z-index:99;
}
#shownotice .notice .attachment img {
position:static;
}
#attachments {
clear:both;
float:left;
width:100%;
margin-top:18px;
}
#attachments dt {
font-weight:bold;
font-size:1.3em;
margin-bottom:4px;
}
#attachments ol li {
margin-bottom:18px;
list-style-type:decimal;
float:left;
clear:both;
}
#jOverlayContent,
#jOverlayContent #content,
#jOverlayContent #content_inner {
width: auto !important;
margin-bottom:0;
}
#jOverlayContent #content {
padding:11px;
min-height:auto;
}
#jOverlayContent .external span {
display:block;
margin-bottom:11px;
}
#jOverlayContent button {
position:absolute;
top:0;
right:0;
width:29px;
height:29px;
text-align:center;
font-weight:bold;
padding:0;
}
#jOverlayContent h1 {
max-width:475px;
}
#jOverlayContent #content {
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
#usergroups #new_group {
float: left;
margin-right: 2em;
@ -1040,8 +1128,6 @@ margin-left:18px;
}
/* TOP_POSTERS */
.section tbody td {
padding-right:18px;
@ -1166,6 +1252,7 @@ width:33%;
}
#settings_design_color .form_data label {
float:none;
display:block;
}
#settings_design_color .form_data .swatch {
padding:11px;

View File

@ -1,6 +1,3 @@
@import url("display.css");
@import url("../../identica/css/display.css");
* {
font-size:14px;
font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;

View File

@ -30,3 +30,12 @@ margin-right:4px;
.entity_profile {
width:64%;
}
.notice {
z-index:1;
}
.notice:hover {
z-index:9999;
}
.notice .thumbnail img {
z-index:9999;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -198,10 +198,13 @@ padding:0 7px;
}
.form_settings input.form_action-secondary {
margin-left:29px;
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
}
#form_search .submit {
margin-left:11px;
@ -443,6 +446,27 @@ float:left;
font-size:1.3em;
margin-bottom:7px;
}
#form_notice label {
display:block;
float:left;
font-size:1.3em;
margin-bottom:7px;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:25px;
right:49px;
width:16px;
height:16px;
cursor:pointer;
}
#form_notice #notice_data-attach {
text-indent:-279px;
}
#form_notice #notice_submit label {
display:none;
}

View File

@ -36,7 +36,7 @@ border-color:#aaa;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -65,7 +65,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#002E6E;
}
@ -102,6 +102,13 @@ color:#333;
#form_notice.warning #notice_text-count {
color:#000;
}
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;

View File

@ -199,9 +199,11 @@ padding:0 7px;
}
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
padding:0;
}
#form_search .submit {
@ -1267,7 +1269,7 @@ border-color:#aaa;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -1296,7 +1298,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#0084B4;
}

View File

@ -33,7 +33,7 @@ border-color:#aaa;
border-color:#C3D6DF;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -60,7 +60,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#002E6E;
}
@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count {
color:#000;
}
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
@ -175,10 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
}
/* NOTICES */
.notices li.over {
background-color:#fcfcfc;
.notice .attachment {
background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
}
#attachments .attachment {
background:none;
}
.notice-options .notice_reply a,
.notice-options form input.submit {
background-color:transparent;
@ -197,7 +206,9 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r
}
.notices div.entry-content,
.notices div.notice-options {
.notices div.notice-options,
.notices li.hover .notices div.entry-content,
.notices li.hover .notices div.notice-options {
opacity:0.4;
}
.notices li.hover div.entry-content,
@ -214,6 +225,19 @@ font-family:sans-serif;
.notices li.hover {
background-color:#fcfcfc;
}
.notices .notices {
background-color:rgba(200, 200, 200, 0.050);
}
.notices .notices .notices {
background-color:rgba(200, 200, 200, 0.100);
}
.notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.150);
}
.notices .notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.300);
}
/*END: NOTICES */
#new_group a {

View File

@ -189,9 +189,11 @@ padding:0 7px;
}
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
padding:0;
}
#form_search .submit {

View File

@ -38,7 +38,7 @@ color:#ccc;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -65,7 +65,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#0f0;
}

View File

@ -33,7 +33,7 @@ border-color:#aaa;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -60,7 +60,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#002E6E;
}
@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count {
color:#000;
}
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
@ -175,10 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
}
/* NOTICES */
.notices li.over {
background-color:#fcfcfc;
.notice .attachment {
background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
}
#attachments .attachment {
background:none;
}
.notice-options .notice_reply a,
.notice-options form input.submit {
background-color:transparent;
@ -197,7 +206,9 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r
}
.notices div.entry-content,
.notices div.notice-options {
.notices div.notice-options,
.notices li.hover .notices div.entry-content,
.notices li.hover .notices div.notice-options {
opacity:0.4;
}
.notices li.hover div.entry-content,
@ -214,6 +225,19 @@ font-family:sans-serif;
.notices li.hover {
background-color:#fcfcfc;
}
.notices .notices {
background-color:rgba(200, 200, 200, 0.050);
}
.notices .notices .notices {
background-color:rgba(200, 200, 200, 0.100);
}
.notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.150);
}
.notices .notices .notices .notices .notices {
background-color:rgba(200, 200, 200, 0.300);
}
/*END: NOTICES */
#new_group a {

View File

@ -198,9 +198,11 @@ padding:0 7px;
}
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
padding:0;
}
#form_search .submit {

View File

@ -37,7 +37,7 @@ border-color:#aaa;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -64,7 +64,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#8F0000;
}

View File

@ -199,9 +199,11 @@ padding:0 7px;
}
.form_settings input.form_action-primary {
padding:0;
}
.form_settings input.form_action-secondary {
margin-left:29px;
padding:0;
}
#form_search .submit {

View File

@ -36,7 +36,7 @@ border-color:#aaa;
border-color:#ddd;
}
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
background:none;
}
@ -63,7 +63,7 @@ div.notice-options input,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
.form_settings input.form_action-primary {
color:#000;
}