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

Conflicts:
	config.php.sample
This commit is contained in:
Evan Prodromou 2009-05-19 14:42:19 -04:00
commit 8cc8f9fd0c
77 changed files with 4508 additions and 543 deletions

View File

@ -100,6 +100,20 @@ StartPublicGroupNav: Showing the public group nav menu
EndPublicGroupNav: At the end of the public group nav menu
- $action: the current action
StartSubGroupNav: Showing the subscriptions group nav menu
- $action: the current action
EndSubGroupNav: At the end of the subscriptions group nav menu
- $action: the current action
RouterInitialized: After the router instance has been initialized
- $m: the Net_URL_Mapper that has just been set up
StartLogout: Before logging out
- $action: the logout action
EndLogout: After logging out
- $action: the logout action
ArgsInitialized: After the argument array has been initialized
- $args: associative array of arguments, can be modified

209
actions/attachment.php Normal file
View File

@ -0,0 +1,209 @@
<?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 AttachmentAction extends Action
{
/**
* Attachment object to show
*/
var $attachment = null;
/**
* 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('attachment');
$this->attachment = File::staticGet($id);
if (!$this->attachment) {
$this->clientError(_('No such attachment.'), 404);
return false;
}
return true;
}
/**
* Is this action read-only?
*
* @return boolean true
*/
function isReadOnly($args)
{
return true;
}
/**
* Title of the page
*
* @return string title of the page
*/
function title()
{
$a = new Attachment($this->attachment);
return $a->title();
}
/**
* 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)) . '"';
}
*/
/**
* Handle input
*
* Only handles get, so just show the page.
*
* @param array $args $_REQUEST data (unused)
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$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()
{
$this->elementStart('ul', array('class' => 'attachments'));
$ali = new Attachment($this->attachment, $this);
$cnt = $ali->show();
$this->elementEnd('ul');
}
/**
* Don't show page notice
*
* @return void
*/
function showPageNoticeBlock()
{
}
/**
* Show aside: this attachments appears in what notices
*
* @return void
*/
function showSections() {
$ns = new AttachmentNoticeSection($this);
$ns->show();
$atcs = new AttachmentTagCloudSection($this);
$atcs->show();
}
}

141
actions/attachment_ajax.php Normal file
View File

@ -0,0 +1,141 @@
<?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.'/actions/attachment.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 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.
*
* @return nothing
*/
function showPage()
{
if (Event::handle('StartShowBody', array($this))) {
$this->showCore();
Event::handle('EndShowBody', array($this));
}
}
/**
* Show core.
*
* Shows local navigation, content block and aside.
*
* @return nothing
*/
function showCore()
{
$this->elementStart('div', array('id' => 'core'));
if (Event::handle('StartShowContentBlock', array($this))) {
$this->showContentBlock();
Event::handle('EndShowContentBlock', array($this));
}
$this->elementEnd('div');
}
/**
* 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)) . '"';
}
*/
}

292
actions/attachments.php Normal file
View File

@ -0,0 +1,292 @@
<?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

@ -0,0 +1,115 @@
<?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.'/actions/attachments.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 Attachments_ajaxAction extends AttachmentsAction
{
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.
*
* @return nothing
*/
function showPage()
{
if (Event::handle('StartShowBody', array($this))) {
$this->showCore();
Event::handle('EndShowBody', array($this));
}
}
/**
* Show core.
*
* Shows local navigation, content block and aside.
*
* @return nothing
*/
function showCore()
{
$this->elementStart('div', array('id' => 'core'));
if (Event::handle('StartShowContentBlock', array($this))) {
$this->showContentBlock();
Event::handle('EndShowContentBlock', array($this));
}
$this->elementEnd('div');
}
}

View File

@ -76,14 +76,22 @@ class DesignsettingsAction extends AccountSettingsAction
'action' =>
common_local_url('designsettings')));
$this->elementStart('fieldset');
// $this->element('legend', null, _('Design settings'));
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', array('id' => 'settings_design_background-image'));
$this->element('legend', null, _('Change background image'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->element('p', null, _('Upload background image'));
$this->element('label', array('for' => 'design_ background-image_file'),
_('Upload file'));
$this->element('input', array('name' => 'design_background-image_file',
'type' => 'file',
'id' => 'design_background-image_file'));
$this->element('p', 'form_guide', _('You can upload your personal background image. The maximum file size is 2Mb.'));
$this->element('input', array('name' => 'MAX_FILE_SIZE',
'type' => 'hidden',
'id' => 'MAX_FILE_SIZE',
'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('fieldset');
@ -91,28 +99,57 @@ class DesignsettingsAction extends AccountSettingsAction
$this->elementStart('fieldset', array('id' => 'settings_design_color'));
$this->element('legend', null, _('Change colours'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('color-1', _('Background color'), '#F0F2F5', null);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('color-2', _('Content background color'), '#FFFFFF', null);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('color-3', _('Sidebar background color'), '#CEE1E9', null);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('color-4', _('Text color'), '#000000', null);
$this->elementEnd('li');
$this->elementStart('li');
$this->input('color-5', _('Link color'), '#002E6E', null);
$this->elementEnd('li');
//This is a JSON object in the DB field. Here for testing. Remove later.
$userSwatch = '{"body":{"background-color":"#F0F2F5"},
"#content":{"background-color":"#FFFFFF"},
"#aside_primary":{"background-color":"#CEE1E9"},
"html body":{"color":"#000000"},
"a":{"color":"#002E6E"}}';
//Default theme swatch -- Where should this be stored?
$defaultSwatch = array('body' => array('background-color' => '#F0F2F5'),
'#content' => array('background-color' => '#FFFFFF'),
'#aside_primary' => array('background-color' => '#CEE1E9'),
'html body' => array('color' => '#000000'),
'a' => array('color' => '#002E6E'));
$userSwatch = ($userSwatch) ? json_decode($userSwatch, true) : $defaultSwatch;
$s = 0;
$labelSwatch = array('Background',
'Content',
'Sidebar',
'Text',
'Links');
foreach($userSwatch as $propertyvalue => $value) {
$foo = array_values($value);
$this->elementStart('li');
$this->element('label', array('for' => 'swatch-'.$s), _($labelSwatch[$s]));
$this->element('input', array('name' => 'swatch-'.$s, //prefer swatch[$s] ?
'type' => 'text',
'id' => 'swatch-'.$s,
'class' => 'swatch',
'maxlength' => '7',
'size' => '7',
'value' => $foo[0]));
$this->elementEnd('li');
$s++;
}
$this->elementEnd('ul');
$this->element('div', array('id' => 'color-picker'));
$this->elementEnd('fieldset');
$this->submit('save', _('Save'));
$this->element('input', array('type' => 'reset',
'value' => 'Reset',
'class' => 'form_action-secondary'));
/*TODO: Check submitted form values:
json_encode(form values)
if submitted Swatch == DefaultSwatch, don't store in DB.
else store in BD
*/
$this->elementEnd('fieldset');
$this->elementEnd('form');
@ -187,7 +224,7 @@ class DesignsettingsAction extends AccountSettingsAction
/**
* Add the jCrop stylesheet
* Add the Farbtastic stylesheet
*
* @return void
*/
@ -205,7 +242,7 @@ class DesignsettingsAction extends AccountSettingsAction
}
/**
* Add the jCrop scripts
* Add the Farbtastic scripts
*
* @return void
*/
@ -214,14 +251,12 @@ class DesignsettingsAction extends AccountSettingsAction
{
parent::showScripts();
// if ($this->mode == 'crop') {
$farbtasticPack = common_path('js/farbtastic/farbtastic.js');
$farbtasticGo = common_path('js/farbtastic/farbtastic.go.js');
$farbtasticPack = common_path('js/farbtastic/farbtastic.js');
$farbtasticGo = common_path('js/farbtastic/farbtastic.go.js');
$this->element('script', array('type' => 'text/javascript',
'src' => $farbtasticPack));
$this->element('script', array('type' => 'text/javascript',
'src' => $farbtasticGo));
// }
$this->element('script', array('type' => 'text/javascript',
'src' => $farbtasticPack));
$this->element('script', array('type' => 'text/javascript',
'src' => $farbtasticGo));
}
}

View File

@ -70,10 +70,20 @@ class LogoutAction extends Action
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
} else {
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
if (Event::handle('StartLogout', array($this))) {
$this->logout();
}
Event::handle('EndLogout', array($this));
common_redirect(common_local_url('public'), 303);
}
}
function logout()
{
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
}
}

View File

@ -158,7 +158,8 @@ class NewnoticeAction extends Action
$replyto = 'false';
}
$notice = Notice::saveNew($user->id, $content, 'web', 1,
// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto);
if (is_string($notice)) {
@ -166,6 +167,8 @@ class NewnoticeAction extends Action
return;
}
$this->saveUrls($notice);
common_broadcast_notice($notice);
if ($this->boolean('ajax')) {
@ -191,6 +194,24 @@ class NewnoticeAction extends Action
}
}
/** save all urls in the notice to the db
*
* follow redirects and save all available file information
* (mimetype, date, size, oembed, etc.)
*
* @param class $notice Notice to pull URLs from
*
* @return void
*/
function saveUrls($notice) {
common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
}
function saveUrl($data) {
list($url, $notice_id) = $data;
$zzz = File::processNew($url, $notice_id);
}
/**
* Show an Ajax-y error message
*

View File

@ -68,6 +68,9 @@ class ShowstreamAction extends ProfileAction
} else {
$base = $this->user->nickname;
}
if (!empty($this->tag)) {
$base .= sprintf(_(' tagged %s'), $this->tag);
}
if ($this->page == 1) {
return $base;
@ -110,6 +113,15 @@ class ShowstreamAction extends ProfileAction
function getFeeds()
{
if (!empty($this->tag)) {
return array(new Feed(Feed::RSS1,
common_local_url('userrss',
array('nickname' => $this->user->nickname,
'tag' => $this->tag)),
sprintf(_('Notice feed for %s tagged %s (RSS 1.0)'),
$this->user->nickname, $this->tag)));
}
return array(new Feed(Feed::RSS1,
common_local_url('userrss',
array('nickname' => $this->user->nickname)),
@ -363,7 +375,9 @@ class ShowstreamAction extends ProfileAction
function showNotices()
{
$notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
$notice = empty($this->tag)
? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1)
: $this->user->getTaggedNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null, $this->tag);
$pnl = new ProfileNoticeList($notice, $this);
$cnt = $pnl->show();

View File

@ -49,9 +49,10 @@ class TagAction extends Action
{
$pop = new PopularNoticeSection($this);
$pop->show();
$freqatt = new FrequentAttachmentSection($this);
$freqatt->show();
}
function title()
{
if ($this->page == 1) {

View File

@ -25,14 +25,15 @@ require_once(INSTALLDIR.'/lib/rssaction.php');
class UserrssAction extends Rss10Action
{
var $user = null;
var $tag = null;
function prepare($args)
{
parent::prepare($args);
$nickname = $this->trimmed('nickname');
$nickname = $this->trimmed('nickname');
$this->user = User::staticGet('nickname', $nickname);
$this->tag = $this->trimmed('tag');
if (!$this->user) {
$this->clientError(_('No such user.'));
@ -42,6 +43,25 @@ class UserrssAction extends Rss10Action
}
}
function getTaggedNotices($tag = null, $limit=0)
{
$user = $this->user;
if (is_null($user)) {
return null;
}
$notice = $user->getTaggedNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit, 0, 0, null, $tag);
$notices = array();
while ($notice->fetch()) {
$notices[] = clone($notice);
}
return $notices;
}
function getNotices($limit=0)
{

123
classes/File.php Normal file
View File

@ -0,0 +1,123 @@
<?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.'/classes/Memcached_DataObject.php';
require_once INSTALLDIR.'/classes/File_redirection.php';
require_once INSTALLDIR.'/classes/File_oembed.php';
require_once INSTALLDIR.'/classes/File_thumbnail.php';
require_once INSTALLDIR.'/classes/File_to_post.php';
//require_once INSTALLDIR.'/classes/File_redirection.php';
/**
* Table Definition for file
*/
class File extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file'; // table name
public $id; // int(11) not_null primary_key group_by
public $url; // varchar(255) unique_key
public $mimetype; // varchar(50)
public $size; // int(11) group_by
public $title; // varchar(255)
public $date; // int(11) group_by
public $protected; // int(1) group_by
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function isProtected($url) {
return 'http://www.facebook.com/login.php' === $url;
}
function getAttachments($post_id) {
$query = "select file.* from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $this->escape($post_id);
$this->query($query);
$att = array();
while ($this->fetch()) {
$att[] = clone($this);
}
$this->free();
return $att;
}
function saveNew($redir_data, $given_url) {
$x = new File;
$x->url = $given_url;
if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected'];
if (!empty($redir_data['title'])) $x->title = $redir_data['title'];
if (!empty($redir_data['type'])) $x->mimetype = $redir_data['type'];
if (!empty($redir_data['size'])) $x->size = intval($redir_data['size']);
if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']);
$file_id = $x->insert();
if (isset($redir_data['type'])
&& ('text/html' === substr($redir_data['type'], 0, 9))
&& ($oembed_data = File_oembed::_getOembed($given_url))
&& isset($oembed_data['json'])) {
File_oembed::saveNew($oembed_data['json'], $file_id);
}
return $x;
}
function processNew($given_url, $notice_id) {
if (empty($given_url)) return -1; // error, no url to process
$given_url = File_redirection::_canonUrl($given_url);
if (empty($given_url)) return -1; // error, no url to process
$file = File::staticGet('url', $given_url);
if (empty($file->id)) {
$file_redir = File_redirection::staticGet('url', $given_url);
if (empty($file_redir->id)) {
$redir_data = File_redirection::where($given_url);
$redir_url = $redir_data['url'];
if ($redir_url === $given_url) {
$x = File::saveNew($redir_data, $given_url);
$file_id = $x->id;
} else {
$x = File::processNew($redir_url, $notice_id);
$file_id = $x->id;
File_redirection::saveNew($redir_data, $file_id, $given_url);
}
} else {
$file_id = $file_redir->file_id;
}
} else {
$file_id = $file->id;
$x = $file;
}
if (empty($x)) {
$x = File::staticGet($file_id);
if (empty($x)) die('Impossible!');
}
File_to_post::processNew($file_id, $notice_id);
return $x;
}
}

87
classes/File_oembed.php Normal file
View File

@ -0,0 +1,87 @@
<?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.'/classes/Memcached_DataObject.php';
/**
* Table Definition for file_oembed
*/
class File_oembed extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file_oembed'; // table name
public $id; // int(11) not_null primary_key group_by
public $file_id; // int(11) unique_key group_by
public $version; // varchar(20)
public $type; // varchar(20)
public $provider; // varchar(50)
public $provider_url; // varchar(255)
public $width; // int(11) group_by
public $height; // int(11) group_by
public $html; // blob(65535) blob
public $title; // varchar(255)
public $author_name; // varchar(50)
public $author_url; // varchar(255)
public $url; // varchar(255)
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_oembed',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function _getOembed($url, $maxwidth = 500, $maxheight = 400, $format = 'json') {
$cmd = 'http://oohembed.com/oohembed/?url=' . urlencode($url);
if (is_int($maxwidth)) $cmd .= "&maxwidth=$maxwidth";
if (is_int($maxheight)) $cmd .= "&maxheight=$maxheight";
if (is_string($format)) $cmd .= "&format=$format";
$oe = @file_get_contents($cmd);
if (false === $oe) return false;
return array($format => (('json' === $format) ? json_decode($oe, true) : $oe));
}
function saveNew($data, $file_id) {
$file_oembed = new File_oembed;
$file_oembed->file_id = $file_id;
$file_oembed->version = $data['version'];
$file_oembed->type = $data['type'];
if (!empty($data['provider_name'])) $file_oembed->provider = $data['provider_name'];
if (!isset($file_oembed->provider) && !empty($data['provide'])) $file_oembed->provider = $data['provider'];
if (!empty($data['provide_url'])) $file_oembed->provider_url = $data['provider_url'];
if (!empty($data['width'])) $file_oembed->width = intval($data['width']);
if (!empty($data['height'])) $file_oembed->height = intval($data['height']);
if (!empty($data['html'])) $file_oembed->html = $data['html'];
if (!empty($data['title'])) $file_oembed->title = $data['title'];
if (!empty($data['author_name'])) $file_oembed->author_name = $data['author_name'];
if (!empty($data['author_url'])) $file_oembed->author_url = $data['author_url'];
if (!empty($data['url'])) $file_oembed->url = $data['url'];
$file_oembed->insert();
if (!empty($data['thumbnail_url'])) {
File_thumbnail::saveNew($data, $file_id);
}
}
}

View File

@ -0,0 +1,274 @@
<?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.'/classes/Memcached_DataObject.php';
require_once INSTALLDIR.'/classes/File.php';
require_once INSTALLDIR.'/classes/File_oembed.php';
define('USER_AGENT', 'Laconica user agent / file probe');
/**
* Table Definition for file_redirection
*/
class File_redirection extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file_redirection'; // table name
public $id; // int(11) not_null primary_key group_by
public $url; // varchar(255) unique_key
public $file_id; // int(11) group_by
public $redirections; // int(11) group_by
public $httpcode; // int(11) group_by
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_redirection',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function _commonCurl($url, $redirs) {
$curlh = curl_init();
curl_setopt($curlh, CURLOPT_URL, $url);
curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects
curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait
curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow
curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT);
curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlh, CURLOPT_FILETIME, true);
curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output
return $curlh;
}
function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
if ($redirs < 0) return false;
// let's see if we know this...
$a = File::staticGet('url', $short_url);
if (empty($a->id)) {
$b = File_redirection::staticGet('url', $short_url);
if (empty($b->id)) {
// we'll have to figure it out
} else {
// this is a redirect to $b->file_id
$a = File::staticGet($b->file_id);
$url = $a->url;
}
} else {
// this is a direct link to $a->url
$url = $a->url;
}
if (isset($url)) {
return $url;
}
$curlh = File_redirection::_commonCurl($short_url, $redirs);
// Don't include body in output
curl_setopt($curlh, CURLOPT_NOBODY, true);
curl_exec($curlh);
$info = curl_getinfo($curlh);
curl_close($curlh);
if (405 == $info['http_code']) {
$curlh = File_redirection::_commonCurl($short_url, $redirs);
curl_exec($curlh);
$info = curl_getinfo($curlh);
curl_close($curlh);
}
if (!empty($info['redirect_count']) && File::isProtected($info['url'])) {
return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true);
}
$ret = array('code' => $info['http_code']
, 'redirects' => $info['redirect_count']
, 'url' => $info['url']);
if (!empty($info['content_type'])) $ret['type'] = $info['content_type'];
if ($protected) $ret['protected'] = true;
if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length'];
if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime'];
return $ret;
}
function where($in_url) {
$ret = File_redirection::_redirectWhere_imp($in_url);
return $ret;
}
function makeShort($long_url) {
$long_url = File_redirection::_canonUrl($long_url);
// do we already know this long_url and have a short redirection for it?
$file = new File;
$file_redir = new File_redirection;
$file->url = $long_url;
$file->joinAdd($file_redir);
$file->selectAdd('length(file_redirection.url) as len');
$file->limit(1);
$file->orderBy('len');
$file->find(true);
if (!empty($file->id)) {
return $file->url;
}
// if yet unknown, we must find a short url according to user settings
$short_url = File_redirection::_userMakeShort($long_url, common_current_user());
return $short_url;
}
function _userMakeShort($long_url, $user) {
if (empty($user)) {
// common current user does not find a user when called from the XMPP daemon
// therefore we'll set one here fix, so that XMPP given URLs may be shortened
$user->urlshorteningservice = 'ur1.ca';
}
$curlh = curl_init();
curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
switch($user->urlshorteningservice) {
case 'ur1.ca':
require_once INSTALLDIR.'/lib/Shorturl_api.php';
$short_url_service = new LilUrl;
$short_url = $short_url_service->shorten($long_url);
break;
case '2tu.us':
$short_url_service = new TightUrl;
require_once INSTALLDIR.'/lib/Shorturl_api.php';
$short_url = $short_url_service->shorten($long_url);
break;
case 'ptiturl.com':
require_once INSTALLDIR.'/lib/Shorturl_api.php';
$short_url_service = new PtitUrl;
$short_url = $short_url_service->shorten($long_url);
break;
case 'bit.ly':
curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url));
$short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
break;
case 'is.gd':
curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url));
$short_url = curl_exec($curlh);
break;
case 'snipr.com':
curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
$short_url = curl_exec($curlh);
break;
case 'metamark.net':
curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
$short_url = curl_exec($curlh);
break;
case 'tinyurl.com':
curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
$short_url = curl_exec($curlh);
break;
default:
$short_url = false;
}
curl_close($curlh);
if ($short_url) {
$short_url = (string)$short_url;
// store it
$file = File::staticGet('url', $long_url);
if (empty($file)) {
$redir_data = File_redirection::where($long_url);
$file = File::saveNew($redir_data, $long_url);
$file_id = $file->id;
if (!empty($redir_data['oembed']['json'])) {
File_oembed::saveNew($redir_data['oembed']['json'], $file_id);
}
} else {
$file_id = $file->id;
}
$file_redir = File_redirection::staticGet('url', $short_url);
if (empty($file_redir)) {
$file_redir = new File_redirection;
$file_redir->url = $short_url;
$file_redir->file_id = $file_id;
$file_redir->insert();
}
return $short_url;
}
return $long_url;
}
function _canonUrl($in_url, $default_scheme = 'http://') {
if (empty($in_url)) return false;
$out_url = $in_url;
$p = parse_url($out_url);
if (empty($p['host']) || empty($p['scheme'])) {
list($scheme) = explode(':', $in_url, 2);
switch ($scheme) {
case 'fax':
case 'tel':
$out_url = str_replace('.-()', '', $out_url);
break;
case 'mailto':
case 'aim':
case 'jabber':
case 'xmpp':
// don't touch anything
break;
default:
$out_url = $default_scheme . ltrim($out_url, '/');
$p = parse_url($out_url);
if (empty($p['scheme'])) return false;
break;
}
}
if (('ftp' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) {
if (empty($p['host'])) return false;
if (empty($p['path'])) {
$out_url .= '/';
}
}
return $out_url;
}
function saveNew($data, $file_id, $url) {
$file_redir = new File_redirection;
$file_redir->url = $url;
$file_redir->file_id = $file_id;
$file_redir->redirections = intval($data['redirects']);
$file_redir->httpcode = intval($data['code']);
$file_redir->insert();
}
}

View File

@ -0,0 +1,55 @@
<?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.'/classes/Memcached_DataObject.php';
/**
* Table Definition for file_thumbnail
*/
class File_thumbnail extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file_thumbnail'; // table name
public $id; // int(11) not_null primary_key group_by
public $file_id; // int(11) unique_key group_by
public $url; // varchar(255) unique_key
public $width; // int(11) group_by
public $height; // int(11) group_by
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_thumbnail',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function saveNew($data, $file_id) {
$tn = new File_thumbnail;
$tn->file_id = $file_id;
$tn->url = $data['thumbnail_url'];
$tn->width = intval($data['thumbnail_width']);
$tn->height = intval($data['thumbnail_height']);
$tn->insert();
}
}

60
classes/File_to_post.php Normal file
View File

@ -0,0 +1,60 @@
<?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.'/classes/Memcached_DataObject.php';
/**
* Table Definition for file_to_post
*/
class File_to_post extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'file_to_post'; // table name
public $id; // int(11) not_null primary_key group_by
public $file_id; // int(11) multiple_key group_by
public $post_id; // int(11) group_by
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_to_post',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function processNew($file_id, $notice_id) {
static $seen = array();
if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) {
$f2p = new File_to_post;
$f2p->file_id = $file_id;
$f2p->post_id = $notice_id;
$f2p->insert();
if (empty($seen[$notice_id])) {
$seen[$notice_id] = array($file_id);
} else {
$seen[$notice_id][] = $file_id;
}
}
}
}

View File

@ -124,8 +124,6 @@ class Notice extends Memcached_DataObject
$profile = Profile::staticGet($profile_id);
$final = common_shorten_links($content);
if (!$profile) {
common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
return _('Problem saving notice. Unknown user.');
@ -136,7 +134,7 @@ class Notice extends Memcached_DataObject
return _('Too many notices too fast; take a breather and post again in a few minutes.');
}
if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $content)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
}
@ -167,8 +165,8 @@ class Notice extends Memcached_DataObject
$notice->reply_to = $reply_to;
$notice->created = common_sql_now();
$notice->content = $final;
$notice->rendered = common_render_content($final, $notice);
$notice->content = $content;
$notice->rendered = common_render_content($content, $notice);
$notice->source = $source;
$notice->uri = $uri;
@ -279,6 +277,16 @@ class Notice extends Memcached_DataObject
return true;
}
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);
$post->query($query);
$post->fetch();
$n_attachments = intval($post->n_attachments);
$post->free();
return $n_attachments;
}
function blowCaches($blowLast=false)
{
$this->blowSubsCache($blowLast);
@ -1016,7 +1024,7 @@ class Notice extends Memcached_DataObject
}
}
function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null, $tag=null)
{
$cache = common_memcache();
@ -1024,7 +1032,7 @@ class Notice extends Memcached_DataObject
$since_id != 0 || $before_id != 0 || !is_null($since) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
$before_id, $since)));
$before_id, $since, $tag)));
}
$idkey = common_cache_key($cachekey);
@ -1044,7 +1052,7 @@ class Notice extends Memcached_DataObject
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
$last_id, 0, null)));
$last_id, 0, null, $tag)));
$new_window = array_merge($new_ids, $window);
@ -1059,7 +1067,7 @@ class Notice extends Memcached_DataObject
}
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
0, 0, null)));
0, 0, null, $tag)));
$windowstr = implode(',', $window);

View File

@ -153,18 +153,66 @@ class Profile extends Memcached_DataObject
return null;
}
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
function getTaggedNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null, $tag=null)
{
// XXX: I'm not sure this is going to be any faster. It probably isn't.
$ids = Notice::stream(array($this, '_streamTaggedDirect'),
array(),
'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $before_id, $since, $tag);
common_debug(print_r($ids, true));
return Notice::getStreamByIds($ids);
}
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
// XXX: I'm not sure this is going to be any faster. It probably isn't.
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $before_id);
$offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids);
}
function _streamDirect($offset, $limit, $since_id, $before_id, $since)
function _streamTaggedDirect($offset, $limit, $since_id, $before_id, $since=null, $tag=null)
{
common_debug('_streamTaggedDirect()');
$notice = new Notice();
$notice->profile_id = $this->id;
$query = "select id from notice join notice_tag on id=notice_id where tag='" . $notice->escape($tag) . "' and profile_id=" . $notice->escape($notice->profile_id);
if ($since_id != 0) {
$query .= " and id > $since_id";
}
if ($before_id != 0) {
$query .= " and id < $before_id";
}
if (!is_null($since)) {
$query .= " and created > '" . date('Y-m-d H:i:s', $since) . "'";
}
$query .= ' order by id DESC';
if (!is_null($offset)) {
$query .= " limit $offset, $limit";
}
$notice->query($query);
$ids = array();
while ($notice->fetch()) {
common_debug(print_r($notice, true));
$ids[] = $notice->id;
}
return $ids;
}
function _streamDirect($offset, $limit, $since_id, $before_id, $since = null)
{
$notice = new Notice();

View File

@ -407,13 +407,22 @@ class User extends Memcached_DataObject
return Notice::getStreamByIds($ids);
}
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) {
$profile = $this->getProfile();
if (!$profile) {
return null;
} else {
return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id, $since);
}
}
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$profile = $this->getProfile();
if (!$profile) {
return null;
} else {
return $profile->getNotices($offset, $limit, $since_id, $before_id);
return $profile->getNotices($offset, $limit, $since_id, $before_id, $since);
}
}

61
classes/laconica.ini Executable file → Normal file
View File

@ -1,4 +1,3 @@
[avatar]
profile_id = 129
original = 17
@ -393,3 +392,63 @@ 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

@ -41,3 +41,17 @@ subscribed = profile:id
[fave]
notice_id = notice:id
user_id = user:id
[file_oembed]
file_id = file:id
[file_redirection]
file_id = file:id
[file_thumbnail]
file_id = file:id
[file_to_post]
file_id = file:id
post_id = notice:id

View File

@ -425,3 +425,62 @@ create table group_inbox (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table file (
id integer primary key auto_increment,
url varchar(255), mimetype varchar(50),
size integer,
title varchar(255),
date integer(11),
protected integer(1),
unique(url)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table file_oembed (
id integer primary key auto_increment,
file_id integer,
version varchar(20),
type varchar(20),
provider varchar(50),
provider_url varchar(255),
width integer,
height integer,
html text,
title varchar(255),
author_name varchar(50),
author_url varchar(255),
url varchar(255),
unique(file_id)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table file_redirection (
id integer primary key auto_increment,
url varchar(255),
file_id integer,
redirections integer,
httpcode integer,
unique(url)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table file_thumbnail (
id integer primary key auto_increment,
file_id integer,
url varchar(255),
width integer,
height integer,
unique(file_id),
unique(url)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table file_to_post (
id integer primary key auto_increment,
file_id integer,
post_id integer,
unique(file_id, post_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -427,6 +427,64 @@ create table group_inbox (
);
create index group_inbox_created_idx on group_inbox using btree(created);
/*attachments and URLs stuff */
create sequence file_seq;
create table file (
id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique,
mimetype varchar(50),
size integer,
title varchar(255),
date integer(11),
protected integer(1)
);
create sequence file_oembed_seq;
create table file_oembed (
id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
file_id bigint unique,
version varchar(20),
type varchar(20),
provider varchar(50),
provider_url varchar(255),
width integer,
height integer,
html text,
title varchar(255),
author_name varchar(50),
author_url varchar(255),
url varchar(255),
);
create sequence file_redirection_seq;
create table file_redirection (
id bigint default nextval('file_redirection_seq') primary key /* comment 'unique identifier' */,
url varchar(255) unique,
file_id bigint,
redirections integer,
httpcode integer
);
create sequence file_thumbnail_seq;
create table file_thumbnail (
id bigint default nextval('file_thumbnail_seq') primary key /* comment 'unique identifier' */,
file_id bigint unique,
url varchar(255) unique,
width integer,
height integer
);
create sequence file_to_post_seq;
create table file_to_post (
id bigint default nextval('file_to_post_seq') primary key /* comment 'unique identifier' */,
file_id bigint,
post_id bigint,
unique(file_id, post_id)
);
/* Textsearch stuff */
create index textsearch_idx on profile using gist(textsearch);

View File

@ -1,5 +1,5 @@
<?php
// Copyright 2004-2008 Facebook. All Rights Reserved.
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
@ -30,13 +30,12 @@
// +---------------------------------------------------------------------------+
// | For help with this library, contact developers-help@facebook.com |
// +---------------------------------------------------------------------------+
//
include_once 'facebookapi_php5_restlib.php';
define('FACEBOOK_API_VALIDATION_ERROR', 1);
class Facebook {
public $api_client;
public $api_key;
public $secret;
public $generate_session_secret;
@ -213,28 +212,55 @@ class Facebook {
}
}
// Invalidate the session currently being used, and clear any state associated with it
// Invalidate the session currently being used, and clear any state associated
// with it. Note that the user will still remain logged into Facebook.
public function expire_session() {
if ($this->api_client->auth_expireSession()) {
if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
$cookies = array('user', 'session_key', 'expires', 'ss');
foreach ($cookies as $name) {
setcookie($this->api_key . '_' . $name, false, time() - 3600);
unset($_COOKIE[$this->api_key . '_' . $name]);
}
setcookie($this->api_key, false, time() - 3600);
unset($_COOKIE[$this->api_key]);
}
// now, clear the rest of the stored state
$this->user = 0;
$this->api_client->session_key = 0;
$this->clear_cookie_state();
return true;
} else {
return false;
}
}
/** Logs the user out of all temporary application sessions as well as their
* Facebook session. Note this will only work if the user has a valid current
* session with the application.
*
* @param string $next URL to redirect to upon logging out
*
*/
public function logout($next) {
$logout_url = $this->get_logout_url($next);
// Clear any stored state
$this->clear_cookie_state();
$this->redirect($logout_url);
}
/**
* Clears any persistent state stored about the user, including
* cookies and information related to the current session in the
* client.
*
*/
public function clear_cookie_state() {
if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
$cookies = array('user', 'session_key', 'expires', 'ss');
foreach ($cookies as $name) {
setcookie($this->api_key . '_' . $name, false, time() - 3600);
unset($_COOKIE[$this->api_key . '_' . $name]);
}
setcookie($this->api_key, false, time() - 3600);
unset($_COOKIE[$this->api_key]);
}
// now, clear the rest of the stored state
$this->user = 0;
$this->api_client->session_key = 0;
}
public function redirect($url) {
if ($this->in_fb_canvas()) {
echo '<fb:redirect url="' . $url . '"/>';
@ -249,7 +275,8 @@ class Facebook {
}
public function in_frame() {
return isset($this->fb_params['in_canvas']) || isset($this->fb_params['in_iframe']);
return isset($this->fb_params['in_canvas'])
|| isset($this->fb_params['in_iframe']);
}
public function in_fb_canvas() {
return isset($this->fb_params['in_canvas']);
@ -296,14 +323,42 @@ class Facebook {
}
public function get_add_url($next=null) {
return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
($next ? '&next=' . urlencode($next) : '');
$page = self::get_facebook_url().'/add.php';
$params = array('api_key' => $this->api_key);
if ($next) {
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function get_login_url($next, $canvas) {
return self::get_facebook_url().'/login.php?v=1.0&api_key=' . $this->api_key .
($next ? '&next=' . urlencode($next) : '') .
($canvas ? '&canvas' : '');
$page = self::get_facebook_url().'/login.php';
$params = array('api_key' => $this->api_key,
'v' => '1.0');
if ($next) {
$params['next'] = $next;
}
if ($canvas) {
$params['canvas'] = '1';
}
return $page . '?' . http_build_query($params);
}
public function get_logout_url($next) {
$page = self::get_facebook_url().'/logout.php';
$params = array('app_key' => $this->api_key,
'session_key' => $this->api_client->session_key);
if ($next) {
$params['connect_next'] = 1;
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function set_user($user, $session_key, $expires=null, $session_secret=null) {
@ -410,7 +465,20 @@ class Facebook {
return $fb_params;
}
/*
/**
* Validates the account that a user was trying to set up an
* independent account through Facebook Connect.
*
* @param user The user attempting to set up an independent account.
* @param hash The hash passed to the reclamation URL used.
* @return bool True if the user is the one that selected the
* reclamation link.
*/
public function verify_account_reclamation($user, $hash) {
return $hash == md5($user . $this->secret);
}
/**
* Validates that a given set of parameters match their signature.
* Parameters all match a given input prefix, such as "fb_sig".
*
@ -422,6 +490,37 @@ class Facebook {
return self::generate_sig($fb_params, $this->secret) == $expected_sig;
}
/**
* Validate the given signed public session data structure with
* public key of the app that
* the session proof belongs to.
*
* @param $signed_data the session info that is passed by another app
* @param string $public_key Optional public key of the app. If this
* is not passed, function will make an API call to get it.
* return true if the session proof passed verification.
*/
public function verify_signed_public_session_data($signed_data,
$public_key = null) {
// If public key is not already provided, we need to get it through API
if (!$public_key) {
$public_key = $this->api_client->auth_getAppPublicKey(
$signed_data['api_key']);
}
// Create data to verify
$data_to_serialize = $signed_data;
unset($data_to_serialize['sig']);
$serialized_data = implode('_', $data_to_serialize);
// Decode signature
$signature = base64_decode($signed_data['sig']);
$result = openssl_verify($serialized_data, $signature, $public_key,
OPENSSL_ALGO_SHA1);
return $result == 1;
}
/*
* Generate a signature using the application secret key.
*

View File

@ -1,5 +1,5 @@
<?php
// Copyright 2004-2008 Facebook. All Rights Reserved.
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |

1026
extlib/facebook/facebookapi_php5_restlib.php Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,10 @@ 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'])) {
die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
}
global $user, $action, $config;
if (!_have_config()) {
@ -101,6 +105,8 @@ function main()
$args = array_merge($args, $_REQUEST);
Event::handle('ArgsInitialize', array(&$args));
$action = $args['action'];
if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) {

View File

@ -86,7 +86,8 @@ function checkExtension($name)
function showForm()
{
?>
$config_path = htmlentities(trim(dirname($_SERVER['REQUEST_URI']), '/'));
echo<<<E_O_T
</ul>
</dd>
</dl>
@ -108,11 +109,21 @@ function showForm()
<p class="form_guide">The name of your site</p>
</li>
<li>
<label for="fancy-enable">Fancy URLs</label>
<input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
<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">Database</label>
<input type="text" id="database" name="database" />
@ -132,7 +143,8 @@ function showForm()
<input type="submit" name="submit" class="submit" value="Submit" />
</fieldset>
</form>
<?php
E_O_T;
}
function updateStatus($status, $error=false)
@ -148,11 +160,13 @@ function handlePost()
?>
<?php
$host = $_POST['host'];
$host = $_POST['host'];
$database = $_POST['database'];
$username = $_POST['username'];
$password = $_POST['password'];
$sitename = $_POST['sitename'];
$path = $_POST['path'];
$fancy = !empty($_POST['fancy']);
?>
<dl class="system_notice">
<dt>Page notice</dt>
@ -225,29 +239,34 @@ function handlePost()
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
$res = writeConf($sitename, $sqlUrl);
$res = writeConf($sitename, $sqlUrl, $fancy, $path);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
if ($path) $path .= '/';
updateStatus("You can visit your <a href='/$path'>new Laconica site</a).");
?>
<?php
}
function writeConf($sitename, $sqlUrl)
function writeConf($sitename, $sqlUrl, $fancy, $path)
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\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");
return $res;
}
function runDbScript($filename, $conn)
{
return true;
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
foreach ($stmts as $stmt) {
@ -276,6 +295,8 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<!--[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>
</head>
<body id="install">
<div id="wrap">

View File

@ -1,10 +1,67 @@
$(document).ready(function() {
var f = $.farbtastic('#color-picker');
var colors = $('#settings_design_color input');
function UpdateColors(e) {
var S = f.linked;
var C = f.color;
colors
.each(function () { f.linkTo(this); })
.focus(function() {
f.linkTo(this);
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 UpdateFarbtastic(e) {
f.linked = e;
f.setColor(e.value);
}
function UpdateSwatch(e) {
$(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();
var f = $.farbtastic('#color-picker', UpdateColors);
var swatches = $('#settings_design_color .swatch');
swatches
.each(UpdateColors)
.blur(function() {
$(this).val($(this).val().toUpperCase());
})
.focus(function() {
$('#color-picker').show();
UpdateFarbtastic(this);
})
.change(function() {
UpdateFarbtastic(this);
UpdateSwatch(this);
}).change()
;
});

18
js/install.js Normal file
View File

@ -0,0 +1,18 @@
$(document).ready(function(){
$.ajax({url:'check-fancy',
type:'GET',
success:function(data, textStatus) {
$('#fancy-enable').attr('checked', true);
$('#fancy-disable').attr('checked', false);
$('#fancy-form_guide').text(data);
},
error:function(XMLHttpRequest, textStatus, errorThrown) {
$('#fancy-enable').attr('checked', false);
$('#fancy-disable').attr('checked', true);
$('#fancy-enable').attr('disabled', true);
$('#fancy-disable').attr('disabled', true);
$('#fancy-form_guide').text("Fancy URL support detection failed, disabling this option. Make sure you renamed htaccess.sample to .htaccess.");
}
});
});

6
js/jquery.joverlay.min.js vendored Normal file
View File

@ -0,0 +1,6 @@
/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* Version: 0.6 (Abr 23, 2009)
* Requires: jQuery 1.3+
*/
(function($){var f=$.browser.msie&&$.browser.version==6.0;var g=null;$.fn.jOverlay=function(b){var b=$.extend({},$.fn.jOverlay.options,b);if(g!=null){clearTimeout(g)}var c=this.is('*')?this:'#jOverlayContent';var d=f?'absolute':'fixed';var e=b.imgLoading?"<img id='jOverlayLoading' src='"+b.imgLoading+"' style='position:"+d+"; z-index:"+(b.zIndex+9)+";'/>":'';$('body').prepend(e+"<div id='jOverlay' />"+"<div id='jOverlayContent' style='position:"+d+"; z-index:"+(b.zIndex+5)+"; display:none;'/>");$('#jOverlayLoading').load(function(){if(b.center){$.center(this)}});if(f){$("select").hide();$("#jOverlayContent select").show()}$('#jOverlay').css({backgroundColor:b.color,position:d,top:'0px',left:'0px',filter:'alpha(opacity='+(b.opacity*100)+')',opacity:b.opacity,zIndex:b.zIndex,width:!f?'100%':$(window).width()+'px',height:!f?'100%':$(document).height()+'px'}).show();if(this.is('*')){$('#jOverlayContent').html(this.addClass('jOverlayChildren').show()).show();if(b.center){$.center('#jOverlayContent')}if(!b.url&&$.isFunction(b.success)){b.success(this.html())}}if(b.url){$.ajax({type:b.method,data:b.data,url:b.url,success:function(a){$('#jOverlayLoading').fadeOut(600);$(c).html(a).show();if(b.center){$.center('#jOverlayContent')}if($.isFunction(b.success)){b.success(a)}}})}if(f){$(window).scroll(function(){if(b.center){$.center('#jOverlayContent')}});$(window).resize(function(){$('#jOverlay').css({width:$(window).width()+'px',height:$(document).height()+'px'});if(b.center){$.center('#jOverlayContent')}})}$(document).keydown(function(a){if(a.keyCode==27){$.closeOverlay()}});if(b.bgClickToClose){$('#jOverlay').click($.closeOverlay)}if(Number(b.timeout)>0){g=setTimeout($.closeOverlay,Number(b.timeout))}};$.center=function(a){var a=$(a);var b=a.height();var c=a.width();a.css({width:c+'px',marginLeft:'-'+(c/2)+'px',marginTop:'-'+b/2+'px',height:'auto',top:!f?'50%':$(window).scrollTop()+($(window).height()/2)+"px",left:'50%'})};$.fn.jOverlay.options={method:'GET',data:'',url:'',color:'#000',opacity:'0.6',zIndex:9999,center:true,imgLoading:'',bgClickToClose:true,success:null,timeout:0};$.closeOverlay=function(){if(f){$("select").show()}$('#jOverlayContent .jOverlayChildren').hide().prependTo($('body'));$('#jOverlayLoading, #jOverlayContent, #jOverlay').remove()}})(jQuery);

View File

@ -17,6 +17,10 @@
*/
$(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;

View File

@ -22,6 +22,7 @@ if (!defined('LACONICA')) { exit(1); }
class ShortUrlApi
{
protected $service_url;
protected $long_limit = 27;
function __construct($service_url)
{
@ -39,7 +40,7 @@ class ShortUrlApi
}
private function is_long($url) {
return strlen($url) >= 30;
return strlen($url) >= $this->long_limit;
}
protected function http_post($data) {

View File

@ -98,15 +98,15 @@ class Action extends HTMLOutputter // lawsuit
Event::handle('EndShowHTML', array($this));
}
if (Event::handle('StartShowHead', array($this))) {
$this->showHead();
$this->showHead();
Event::handle('EndShowHead', array($this));
}
if (Event::handle('StartShowBody', array($this))) {
$this->showBody();
$this->showBody();
Event::handle('EndShowBody', array($this));
}
if (Event::handle('StartEndHTML', array($this))) {
$this->endHTML();
$this->endHTML();
Event::handle('EndEndHTML', array($this));
}
}
@ -243,6 +243,12 @@ class Action extends HTMLOutputter // lawsuit
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.form.js')),
' ');
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.joverlay.min.js')),
' ');
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
@ -347,7 +353,7 @@ class Action extends HTMLOutputter // lawsuit
{
$this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
'class' => 'user_in')
: array('id' => $this->trimmed('action')));
: array('id' => $this->trimmed('action')));
$this->elementStart('div', array('id' => 'wrap'));
if (Event::handle('StartShowHeader', array($this))) {
$this->showHeader();
@ -431,10 +437,10 @@ class Action extends HTMLOutputter // lawsuit
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$this->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
$this->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout');
}
@ -591,7 +597,10 @@ class Action extends HTMLOutputter // lawsuit
'class' => 'system_notice'));
$this->element('dt', null, _('Page notice'));
$this->elementStart('dd');
$this->showPageNotice();
if (Event::handle('StartShowPageNotice', array($this))) {
$this->showPageNotice();
Event::handle('EndShowPageNotice', array($this));
}
$this->elementEnd('dd');
$this->elementEnd('dl');
}
@ -629,7 +638,7 @@ class Action extends HTMLOutputter // lawsuit
$this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside'));
if (Event::handle('StartShowExportData', array($this))) {
$this->showExportData();
$this->showExportData();
Event::handle('EndShowExportData', array($this));
}
if (Event::handle('StartShowSections', array($this))) {

300
lib/attachmentlist.php Normal file
View File

@ -0,0 +1,300 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* widget for displaying a list of 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 UI
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Sarven Capadisli <csarven@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);
}
/**
* widget for displaying a list of notice attachments
*
* There are a number of actions that display a list of notices, in
* reverse chronological order. This widget abstracts out most of the
* code for UI for notice lists. It's overridden to hide some
* data for e.g. the profile page.
*
* @category UI
* @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 Notice
* @see StreamAction
* @see NoticeListItem
* @see ProfileNoticeList
*/
class AttachmentList extends Widget
{
/** the current stream of notices being displayed. */
var $notice = null;
/**
* constructor
*
* @param Notice $notice stream of notices from DB_DataObject
*/
function __construct($notice, $out=null)
{
parent::__construct($out);
$this->notice = $notice;
}
/**
* show the list of notices
*
* "Uses up" the stream by looping through it. So, probably can't
* be called twice on the same list.
*
* @return int count of notices listed.
*/
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);
foreach ($att as $n=>$attachment) {
$item = $this->newListItem($attachment);
$item->show();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('div');
return count($att);
}
/**
* returns a new list item for the current notice
*
* Recipe (factory?) method; overridden by sub-classes to give
* a different list item class.
*
* @param Notice $notice the current notice
*
* @return NoticeListItem a list item for displaying the notice
*/
function newListItem($attachment)
{
return new AttachmentListItem($attachment, $this->out);
}
}
/**
* widget for displaying a single notice
*
* This widget has the core smarts for showing a single notice: what to display,
* where, and under which circumstances. Its key method is show(); this is a recipe
* that calls all the other show*() methods to build up a single notice. The
* ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
* author info (since that's implicit by the data in the page).
*
* @category UI
* @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 NoticeList
* @see ProfileNoticeListItem
*/
class AttachmentListItem extends Widget
{
/** The attachment this item will show. */
var $attachment = null;
var $oembed = null;
/**
* constructor
*
* Also initializes the profile attribute.
*
* @param Notice $notice The notice we'll display
*/
function __construct($attachment, $out=null)
{
parent::__construct($out);
$this->attachment = $attachment;
$this->oembed = File_oembed::staticGet('file_id', $this->attachment->id);
}
function title() {
if (empty($this->attachment->title)) {
if (empty($this->oembed->title)) {
$title = $this->attachment->url;
} else {
$title = $this->oembed->title;
}
} else {
$title = $this->attachment->title;
}
return $title;
}
function linkTitle() {
return 'Our page for ' . $this->title();
}
/**
* recipe function for displaying a single notice.
*
* This uses all the other methods to correctly display a notice. Override
* it or one of the others to fine-tune the output.
*
* @return void
*/
function show()
{
$this->showStart();
$this->showNoticeAttachment();
$this->showEnd();
}
function linkAttr() {
return array('class' => 'attachment', 'href' => common_local_url('attachment', array('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');
}
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');
}
}
/**
* start a single notice.
*
* @return void
*/
function showStart()
{
// XXX: RDFa
// TODO: add notice_type class e.g., notice_video, notice_image
$this->out->elementStart('li');
}
/**
* finish the notice
*
* Close the last elements in the notice list item
*
* @return void
*/
function showEnd()
{
$this->out->elementEnd('li');
}
}
class Attachment extends AttachmentListItem
{
function show() {
$this->showNoticeAttachment();
}
function linkAttr() {
return array('class' => 'external', 'href' => $this->attachment->url);
}
function linkTitle() {
return 'Direct link to ' . $this->title();
}
function showRepresentation() {
if (empty($this->oembed->type)) {
if (empty($this->attachment->mimetype)) {
$this->out->element('pre', null, 'oh well... not sure how to handle the following: ' . print_r($this->attachment, true));
} else {
switch ($this->attachment->mimetype) {
case 'image/gif':
case 'image/png':
case 'image/jpg':
case 'image/jpeg':
$this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
break;
}
}
} else {
switch ($this->oembed->type) {
case 'rich':
case 'video':
case 'link':
if (!empty($this->oembed->html)) {
$this->out->raw($this->oembed->html);
}
break;
case 'photo':
$this->out->element('img', array('src' => $this->oembed->url, 'width' => $this->oembed->width, 'height' => $this->oembed->height, 'alt' => 'alt'));
break;
default:
$this->out->element('pre', null, 'oh well... not sure how to handle the following oembed: ' . print_r($this->oembed, true));
}
}
}
}

View File

@ -0,0 +1,75 @@
<?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 AttachmentNoticeSection extends NoticeSection
{
function showContent() {
parent::showContent();
return false;
}
function getNotices()
{
$notice = new Notice;
$f2p = new File_to_post;
$f2p->file_id = $this->out->attachment->id;
$notice->joinAdd($f2p);
$notice->orderBy('created desc');
$notice->selectAdd('post_id as id');
$notice->find();
return $notice;
}
function title()
{
return _('Notices where this attachment appears');
}
function divId()
{
return 'popular_notices';
}
}

80
lib/attachmentsection.php Normal file
View File

@ -0,0 +1,80 @@
<?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

@ -0,0 +1,83 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Attachment tag cloud section
*
* 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);
}
/**
* Attachment tag cloud section
*
* @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 AttachmentTagCloudSection extends TagCloudSection
{
function title()
{
return _('Tags for this attachment');
}
function showTag($tag, $weight, $relative)
{
if ($relative > 0.5) {
$rel = 'tag-cloud-7';
} else if ($relative > 0.4) {
$rel = 'tag-cloud-6';
} else if ($relative > 0.3) {
$rel = 'tag-cloud-5';
} else if ($relative > 0.2) {
$rel = 'tag-cloud-4';
} else if ($relative > 0.1) {
$rel = 'tag-cloud-3';
} else if ($relative > 0.05) {
$rel = 'tag-cloud-2';
} else {
$rel = 'tag-cloud-1';
}
$this->out->elementStart('li', $rel);
$this->out->element('a', array('href' => $this->tagUrl($tag)),
$tag);
$this->out->elementEnd('li');
}
function getTags()
{
$notice_tag = new Notice_tag;
$query = 'select tag,count(tag) as weight from notice_tag join file_to_post on (notice_tag.notice_id=post_id) join notice on notice_id = notice.id where file_id=' . $notice_tag->escape($this->out->attachment->id) . ' group by tag order by weight desc';
$notice_tag->query($query);
return $notice_tag;
}
}

View File

@ -27,9 +27,21 @@ define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
function getFacebook()
{
static $facebook = null;
$apikey = common_config('facebook', 'apikey');
$secret = common_config('facebook', 'secret');
return new Facebook($apikey, $secret);
if ($facebook === null) {
$facebook = new Facebook($apikey, $secret);
}
if (!$facebook) {
common_log(LOG_ERR, 'Could not make new Facebook client obj!',
__FILE__);
}
return $facebook;
}
function updateProfileBox($facebook, $flink, $notice) {
@ -92,7 +104,6 @@ function isFacebookBound($notice, $flink) {
}
function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();

View File

@ -0,0 +1,66 @@
<?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

@ -179,22 +179,86 @@ class NoticeListItem extends Widget
{
$this->showStart();
$this->showNotice();
$this->showNoticeInfo();
$this->showNoticeAttachments();
$this->showNoticeOptions();
$this->showNoticeInfo();
$this->showEnd();
}
function showNotice()
{
$this->out->elementStart('div', 'entry-title');
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->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';
}
$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);
$this->out->elementEnd('p');
}
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->showNoticeSource();
$this->showContext();
@ -205,7 +269,10 @@ class NoticeListItem extends Widget
{
$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();

View File

@ -51,17 +51,13 @@ class NoticeSection extends Section
function showContent()
{
$notices = $this->getNotices();
$cnt = 0;
$this->out->elementStart('ul', 'notices');
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices);
}
$this->out->elementEnd('ul');
return ($cnt > NOTICES_PER_SECTION);
}
@ -100,6 +96,37 @@ class NoticeSection extends Section
$this->out->elementStart('p', 'entry-content');
$this->out->raw($notice->rendered);
$notice_link_cfg = common_config('site', 'notice_link');
if ('direct' === $notice_link_cfg) {
$this->out->text(' (');
$this->out->element('a', array('href' => $notice->uri), 'see');
$this->out->text(')');
} elseif ('attachment' === $notice_link_cfg) {
if ($count = $notice->hasAttachments()) {
// link to attachment(s) pages
if (1 === $count) {
$f2p = File_to_post::staticGet('post_id', $notice->id);
$href = common_local_url('attachment', array('attachment' => $f2p->file_id));
$att_class = 'attachment';
} else {
$href = common_local_url('attachments', array('notice' => $notice->id));
$att_class = 'attachments';
}
$clip = theme_path('images/icons/clip.png', 'base');
$this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: $count"));
$this->out->raw(" ($count&nbsp");
$this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt'));
$this->out->text(')');
$this->out->elementEnd('a');
} else {
$this->out->text(' (');
$this->out->element('a', array('href' => $notice->uri), 'see');
$this->out->text(')');
}
}
$this->out->elementEnd('p');
if (!empty($notice->value)) {
$this->out->elementStart('p');

View File

@ -51,7 +51,7 @@ class PopularNoticeSection extends NoticeSection
if (common_config('db', 'type') == 'pgsql') {
$weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
if (!empty($this->out->tag)) {
$tag = pg_escape_string($this->tag);
$tag = pg_escape_string($this->out->tag);
}
} else {
$weightexpr='sum(exp(-(now() - fave.modified) / %s))';

View File

@ -49,16 +49,17 @@ require_once INSTALLDIR.'/lib/groupminilist.php';
class ProfileAction extends Action
{
var $user = null;
var $page = null;
var $user = null;
var $page = null;
var $profile = null;
var $tag = null;
function prepare($args)
{
parent::prepare($args);
$nickname_arg = $this->arg('nickname');
$nickname = common_canonical_nickname($nickname_arg);
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
@ -85,10 +86,9 @@ class ProfileAction extends Action
return false;
}
$this->tag = $this->trimmed('tag');
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
common_set_returnto($this->selfUrl());
return true;
}
@ -244,4 +244,5 @@ class ProfileAction extends Action
$this->elementEnd('div');
}
}
}

View File

@ -151,12 +151,26 @@ class Router
$m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
array('q' => '.+'));
$m->connect('attachment/:attachment/ajax',
array('action' => 'attachment_ajax'),
array('notice' => '[0-9]+'));
$m->connect('attachment/:attachment',
array('action' => 'attachment'),
array('notice' => '[0-9]+'));
// notice
$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',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));
@ -412,6 +426,16 @@ class Router
array('size' => '(original|96|48|24)',
'nickname' => '[a-zA-Z0-9]{1,64}'));
$m->connect(':nickname/tag/:tag/rss',
array('action' => 'userrss'),
array('nickname' => '[a-zA-Z0-9]{1,64}'),
array('tag' => '[a-zA-Z0-9]+'));
$m->connect(':nickname/tag/:tag',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'),
array('tag' => '[a-zA-Z0-9]+'));
$m->connect(':nickname',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));

View File

@ -97,7 +97,11 @@ class Rss10Action extends Action
// Parent handling, including cache check
parent::handle($args);
// Get the list of notices
$this->notices = $this->getNotices($this->limit);
if (empty($this->tag)) {
$this->notices = $this->getNotices($this->limit);
} else {
$this->notices = $this->getTaggedNotices($this->tag, $this->limit);
}
$this->showRss();
}

View File

@ -74,38 +74,44 @@ class SubGroupNav extends Widget
$this->out->elementStart('ul', array('class' => 'nav'));
$this->out->menuItem(common_local_url('subscriptions',
array('nickname' =>
$this->user->nickname)),
_('Subscriptions'),
sprintf(_('People %s subscribes to'),
$this->user->nickname),
$action == 'subscriptions',
'nav_subscriptions');
$this->out->menuItem(common_local_url('subscribers',
array('nickname' =>
$this->user->nickname)),
_('Subscribers'),
sprintf(_('People subscribed to %s'),
$this->user->nickname),
$action == 'subscribers',
'nav_subscribers');
$this->out->menuItem(common_local_url('usergroups',
array('nickname' =>
$this->user->nickname)),
_('Groups'),
sprintf(_('Groups %s is a member of'),
$this->user->nickname),
$action == 'usergroups',
'nav_usergroups');
if (!is_null($cur) && $this->user->id === $cur->id) {
$this->out->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
$action == 'invite',
'nav_invite');
if (Event::handle('StartSubGroupNav', array($this))) {
$this->out->menuItem(common_local_url('subscriptions',
array('nickname' =>
$this->user->nickname)),
_('Subscriptions'),
sprintf(_('People %s subscribes to'),
$this->user->nickname),
$action == 'subscriptions',
'nav_subscriptions');
$this->out->menuItem(common_local_url('subscribers',
array('nickname' =>
$this->user->nickname)),
_('Subscribers'),
sprintf(_('People subscribed to %s'),
$this->user->nickname),
$action == 'subscribers',
'nav_subscribers');
$this->out->menuItem(common_local_url('usergroups',
array('nickname' =>
$this->user->nickname)),
_('Groups'),
sprintf(_('Groups %s is a member of'),
$this->user->nickname),
$action == 'usergroups',
'nav_usergroups');
if (!is_null($cur) && $this->user->id === $cur->id) {
$this->out->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
$action == 'invite',
'nav_invite');
}
Event::handle('EndSubGroupNav', array($this));
}
$this->out->elementEnd('ul');
}
}

View File

@ -114,7 +114,11 @@ class TagCloudSection extends Section
function tagUrl($tag)
{
return common_local_url('tag', array('tag' => $tag));
if ('showstream' === $this->out->trimmed('action')) {
return common_local_url('showstream', array('nickname' => $this->out->profile->nickname, 'tag' => $tag));
} else {
return common_local_url('tag', array('tag' => $tag));
}
}
function divId()

View File

@ -395,7 +395,7 @@ function common_render_text($text)
return $r;
}
function common_replace_urls_callback($text, $callback) {
function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
'(?:'.
@ -466,7 +466,11 @@ function common_replace_urls_callback($text, $callback) {
$url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
// Call user specified func
$modified_url = call_user_func($callback, $url);
if (empty($notice_id)) {
$modified_url = call_user_func($callback, $url);
} else {
$modified_url = call_user_func($callback, array($url, $notice_id));
}
// Replace it!
$start = mb_strpos($text, $url, $offset);
@ -481,107 +485,24 @@ function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying
// functions
$url = htmlspecialchars_decode($url);
$display = $url;
$url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
$attrs = array('href' => $url, 'rel' => 'external');
if ($longurl = common_longurl($url)) {
$attrs['title'] = $longurl;
$display = File_redirection::_canonUrl($url);
$longurl_data = File_redirection::where($url);
if (is_array($longurl_data)) {
$longurl = $longurl_data['url'];
} elseif (is_string($longurl_data)) {
$longurl = $longurl_data;
} else {
die('impossible to linkify');
}
$attrs = array('href' => $longurl, 'rel' => 'external');
return XMLStringer::estring('a', $attrs, $display);
}
function common_longurl($short_url)
{
$long_url = common_shorten_link($short_url, true);
if ($long_url === $short_url) return false;
return $long_url;
}
function common_longurl2($uri)
{
$uri_e = urlencode($uri);
$longurl = unserialize(file_get_contents("http://api.longurl.org/v1/expand?format=php&url=$uri_e"));
if (empty($longurl['long_url']) || $uri === $longurl['long_url']) return false;
return stripslashes($longurl['long_url']);
}
function common_shorten_links($text)
{
if (mb_strlen($text) <= 140) return $text;
static $cache = array();
if (isset($cache[$text])) return $cache[$text];
// \s = not a horizontal whitespace character (since PHP 5.2.4)
return $cache[$text] = common_replace_urls_callback($text, 'common_shorten_link');;
}
function common_shorten_link($url, $reverse = false)
{
static $url_cache = array();
if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
$user = common_current_user();
if (!isset($user)) {
// common current user does not find a user when called from the XMPP daemon
// therefore we'll set one here fix, so that XMPP given URLs may be shortened
$user->urlshorteningservice = 'ur1.ca';
}
$curlh = curl_init();
curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
switch($user->urlshorteningservice) {
case 'ur1.ca':
$short_url_service = new LilUrl;
$short_url = $short_url_service->shorten($url);
break;
case '2tu.us':
$short_url_service = new TightUrl;
$short_url = $short_url_service->shorten($url);
break;
case 'ptiturl.com':
$short_url_service = new PtitUrl;
$short_url = $short_url_service->shorten($url);
break;
case 'bit.ly':
curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($url));
$short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
break;
case 'is.gd':
curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($url));
$short_url = curl_exec($curlh);
break;
case 'snipr.com':
curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($url));
$short_url = curl_exec($curlh);
break;
case 'metamark.net':
curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($url));
$short_url = curl_exec($curlh);
break;
case 'tinyurl.com':
curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($url));
$short_url = curl_exec($curlh);
break;
default:
$short_url = false;
}
curl_close($curlh);
if ($short_url) {
$url_cache[(string)$short_url] = $url;
return (string)$short_url;
}
return $url;
return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
}
function common_xml_safe_str($str)

View File

@ -0,0 +1,371 @@
<?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/FBConnectLogin.php';
require_once INSTALLDIR . '/lib/facebookutil.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();
}
}
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' => '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;
}
}
}

View File

@ -0,0 +1,259 @@
<?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/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/lib/facebookutil.php';
/**
* Plugin to enable Facebook Connect
*
* @category Plugin
* @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 FBConnectPlugin extends Plugin
{
function __construct()
{
parent::__construct();
}
// Hook in new actions
function onRouterInitialized(&$m) {
$m->connect('main/facebookconnect', array('action' => 'fbconnectlogin'));
}
// 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);
$action->extraHeaders();
$action->startXML('html',
'-//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));
return false;
}
function onEndShowLaconicaScripts($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 onStartPrimaryNav($action)
{
$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');
} 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
$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');
}
$action->menuItem(common_local_url('openidlogin'),
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
$action->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
}
$action->menuItem(common_local_url('doc', array('title' => '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');
}
return false;
}
function checkFacebookUser() {
$user = common_current_user();
if ($user) {
return;
}
try {
$facebook = getFacebook();
$fbuid = $facebook->get_loggedin_user();
// If you're a Facebook user and you're logged in do nothing
// 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.");
}
}
} catch (Exception $e) {
common_debug('Expired FB session.');
}
}
}

View File

@ -0,0 +1,10 @@
<!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

@ -495,7 +495,7 @@ line-height:1.618;
/* entity_profile */
.entity_profile {
position:relative;
width:67.702%;
width:74.702%;
min-height:123px;
float:left;
margin-bottom:18px;
@ -531,12 +531,15 @@ margin-bottom:4px;
.entity_profile .entity_nickname {
margin-left:11px;
display:inline;
font-weight:bold;
}
.entity_profile .entity_nickname {
margin-left:0;
}
.entity_profile .fn,
.entity_profile .nickname {
font-size:1.1em;
font-weight:bold;
}
.entity_profile .entity_fn dd:before {
content: "(";
font-weight:normal;
@ -558,7 +561,7 @@ display:none;
/*entity_actions*/
.entity_actions {
float:right;
margin-left:4.35%;
margin-left:2.35%;
max-width:25%;
}
.entity_actions h2 {
@ -636,6 +639,7 @@ margin-bottom:29px;
clear:both;
float:left;
width:100%;
list-style-position:inside;
}
.aside .section h2 {
text-transform:uppercase;
@ -659,6 +663,7 @@ list-style-type:none;
float:left;
margin-right:7px;
margin-bottom:7px;
display:inline;
}
.section .entities li .photo {
margin-right:0;
@ -1039,7 +1044,7 @@ margin-left:18px;
/* TOP_POSTERS */
.section tbody td {
padding-right:11px;
padding-right:18px;
padding-bottom:11px;
}
.section .vcard .photo {
@ -1156,6 +1161,17 @@ width:400px;
margin-right:28px;
}
#settings_design_color .form_data li {
width:33%;
}
#settings_design_color .form_data label {
float:none;
}
#settings_design_color .form_data .swatch {
padding:11px;
margin-left:0;
}
.instructions ul {
list-style-position:inside;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
html { font-size: 100%; background-color:#fff; height:100%; }
html { font-size: 100%; background-color:#fff; }
body {
background-color:#fff;
color:#000;
@ -126,7 +126,7 @@ margin-left:0;
.form_settings label {
margin-top:2px;
width:145px;
width:143px;
}
.form_actions label {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -72,13 +72,6 @@ border-top-color:#D1D9E4;
border-top-color:#C3D6DF;
}
#content .notice p.entry-content a:visited {
background-color:#fcfcfc;
}
#content .notice p.entry-content .vcard a {
background-color:#fcfffc;
}
#aside_primary {
background-color:#CEE1E9;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,4 +1,4 @@
/** theme: earthy base
/** theme: h4ck3r base
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
html { background-color:#fff; height:100%; }
html { font-size: 100%; background-color:#fff; height:100%; }
body {
background-color:#fff;
color:#000;
@ -28,7 +28,6 @@ overflow:hidden;
h1 {
font-size:1.4em;
margin-bottom:18px;
text-align:right;
}
#showstream h1 { display:none; }
h2 { font-size:1.3em; }
@ -52,9 +51,6 @@ font-size:1em;
input, textarea, select {
border-width:2px;
border-style: solid;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
}
input.submit {
@ -87,10 +83,7 @@ border:0;
.error,
.success {
padding:4px 7px;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
padding:4px 1.55%;
margin-bottom:18px;
}
form label.submit {
@ -192,9 +185,6 @@ margin-left:0;
}
.form_settings .form_note {
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
padding:0 7px;
}
@ -249,11 +239,11 @@ display:none;
}
#site_notice {
position:absolute;
top:65px;
right:18px;
width:250px;
width:24%;
float:left;
clear:right;
margin-top:7px;
margin-right:18px;
width:31%;
}
#page_notice {
clear:both;
@ -262,17 +252,17 @@ margin-bottom:18px;
#anon_notice {
float:left;
width:43.2%;
float:right;
clear:right;
width:41.2%;
padding:1.1%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
border-width:2px;
border-style:solid;
border-style:dashed;
line-height:1.5;
font-size:1.1em;
font-weight:bold;
-moz-transform:skewX(-30deg) scale(0.85);
-webkit-transform:skewX(-30deg) scale(0.85);
}
@ -283,6 +273,7 @@ padding:18px;
}
#site_nav_local_views {
width:100%;
float:right;
}
#site_nav_local_views dt {
@ -297,12 +288,8 @@ list-style-type:none;
float:left;
text-decoration:none;
padding:4px 11px;
-moz-border-radius-topleft:4px;
-moz-border-radius-topright:4px;
-webkit-border-top-left-radius:4px;
-webkit-border-top-right-radius:4px;
border-width:1px;
border-style:solid;
border-style:dashed;
border-bottom:0;
text-shadow: 2px 2px 2px #ddd;
font-weight:bold;
@ -310,8 +297,6 @@ font-weight:bold;
#site_nav_local_views .nav {
float:left;
width:100%;
border-bottom-width:1px;
border-bottom-style:solid;
}
#site_nav_global_primary dt,
@ -387,15 +372,15 @@ margin-bottom:1em;
}
#content {
width:63.009%;
width:60.009%;
min-height:259px;
padding-top:1.795%;
padding-bottom:1.795%;
padding:1.795%;
float:right;
clear:both;
border-radius:7px;
border-style:solid;
border-width:0;
border-style:dashed;
border-width:1px;
}
#shownotice #content {
min-height:0;
}
#content_inner {
@ -409,33 +394,27 @@ width:27.917%;
min-height:259px;
float:right;
margin-right:4.385%;
margin-top:73px;
padding:1.795%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
border-width:1px;
border-style:solid;
border-style:dashed;
}
#form_notice {
width:45.664%;
float:left;
width:43.664%;
float:right;
position:relative;
line-height:1;
}
#form_notice fieldset {
border:0;
padding:0;
position:relative;
}
#form_notice legend {
display:none;
}
#form_notice textarea {
float:left;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
width:80.789%;
height:67px;
line-height:1.5;
@ -481,7 +460,13 @@ margin-bottom:7px;
margin-left:18px;
float:left;
}
#form_notice .error {
float:left;
clear:both;
width:96.9%;
margin-bottom:0;
line-height:1.618;
}
/* entity_profile */
.entity_profile {
@ -715,32 +700,18 @@ margin-right:11px;
.notice,
.profile {
position:relative;
padding-top:11px;
padding-bottom:11px;
clear:both;
float:left;
width:100%;
border-width:1px;
border-style:solid;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
border-top-width:1px;
border-top-style:dashed;
}
#content .notice,
#content .profile {
padding:1.795%;
margin-bottom:44px;
}
#content .notice {
width:96.25%;
}
.notices li {
list-style-type:none;
}
.notices li.hover {
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
}
/* NOTICES */
#notices_primary {
@ -770,16 +741,14 @@ overflow:hidden;
font-weight:bold;
}
.notice .author .photo {
margin-bottom:0;
}
.vcard .photo {
display:inline;
margin-right:11px;
margin-bottom:11px;
float:left;
}
#shownotice .vcard .photo {
margin-bottom:4px;
}
.vcard .url {
text-decoration:none;
}
@ -788,7 +757,7 @@ text-decoration:underline;
}
.notice .entry-title {
float:left;
display:inline;
width:100%;
overflow:hidden;
}
@ -812,14 +781,9 @@ border-radius:4px;
}
.notice div.entry-content {
clear:left;
float:left;
font-size:0.95em;
margin-left:59px;
width:70%;
}
#showstream .notice div.entry-content {
margin-left:0;
width:65%;
}
.notice .notice-options a,
@ -846,23 +810,6 @@ text-transform:lowercase;
}
.notice-data {
position:absolute;
top:18px;
right:0;
min-height:50px;
margin-bottom:4px;
}
.notice .entry-content .notice-data dt {
display:none;
}
.notice-data a {
display:block;
outline:none;
}
.notice-options {
padding-left:2%;
float:left;
@ -1040,6 +987,8 @@ padding-right:30px;
.hentry .entry-content p {
margin-bottom:18px;
}
.system_notice ul,
.instructions ul,
.hentry entry-content ol,
.hentry .entry-content ul {
list-style-position:inside;

View File

@ -1,4 +1,4 @@
/** theme: earthy
/** theme: h4ck3r
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
@ -12,26 +12,27 @@
html,
body,
a:active {
background-color:#665500;
background-color:#000;
}
body {
font-family: Verdana, sans-serif;
background-image:url(../images/illustrations/illu_h4x0r1ng.gif);
font-family: monospace;
font-size:1em;
color:#647819;
}
address {
margin-right:7.18%;
}
h1 {
color:#fff;
}
input, textarea, select, option {
font-family: Verdana, sans-serif;
font-family: monospace;
}
input, textarea, select,
.entity_remote_subscribe {
border-color:#aaa;
background-color:#000;
color:#ccc;
}
#filter_tags ul li {
border-color:#ddd;
@ -45,7 +46,7 @@ input.submit,
#form_notice.warning #notice_text-count,
.form_settings .form_note,
.entity_remote_subscribe {
background-color:#9BB43E;
background-color:rgba(0, 255, 0, 0.5);
}
input:focus, textarea:focus, select:focus,
@ -54,7 +55,7 @@ border-color:#9BB43E;
}
input.submit,
.entity_remote_subscribe {
color:#dddd33;
color:#fff;
}
a,
@ -65,57 +66,45 @@ div.notice-options input,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
color:#ee4400;
color:#0f0;
}
.notice,
.profile {
border-color:#DDAA00;
border-top-color:#333;
}
.section .profile {
border-top-color:#aaaa66;
}
#content .notice p.entry-content a:visited {
background-color:#fcfcfc;
}
#content .notice p.entry-content .vcard a {
background-color:#fcfffc;
border-top-color:#87B4C8;
}
#aside_primary {
background-color:#DDAA00;
background-color:rgba(0,128,0,0.3);
}
#notice_text-count {
color:#333;
color:#0f0;
}
#form_notice.warning #notice_text-count {
color:#000;
}
#form_notice.processing #notice_action-submit {
background:#dddd33 url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
background:#ccc url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
text-indent:-9999px;
}
#content,
#site_nav_local_views .nav,
#site_nav_local_views a,
#aside_primary {
border-color:#dddd33;
border-color:#50964D;
}
#content .notice,
#content .profile,
#content,
#site_nav_local_views .current a {
background-color:#dddd33;
}
#site_nav_local_views .current a {
color:#EE4400;
background-color:rgba(0, 0, 0, 0.698);
}
#site_nav_local_views a {
background-color:rgba(255, 255, 255, 0.2);
color:#fff;
background-color:rgba(0, 200, 0, 0.3);
}
#site_nav_local_views a:hover {
background-color:rgba(255, 255, 255, 0.4);
@ -129,13 +118,11 @@ background-color:#EFF3DC;
}
#anon_notice {
background-color:#aaaa66;
color:#dddd33;
border-color:#dddd33;
color:#ccc;
border-color:#50964D;
}
#showstream #anon_notice {
background-color:#9BB43E;
}
#export_data li a {
@ -167,12 +154,12 @@ background-color:transparent;
.form_user_subscribe input.submit,
.form_user_unsubscribe input.submit {
background-color:#9BB43E;
color:#dddd33;
color:#ccc;
}
.form_user_unsubscribe input.submit,
.form_group_leave input.submit,
.form_user_authorization input.reject {
background-color:#aaaa66;
background-color:#87B4C8;
}
.entity_edit a {
@ -221,15 +208,13 @@ opacity:0.4;
opacity:1;
}
div.entry-content {
color:#333;
color:#ccc;
}
div.notice-options a,
div.notice-options input {
font-family:sans-serif;
}
.notices li.hover {
/*background-color:#fcfcfc;*/
}
/*END: NOTICES */
#new_group a {
@ -239,7 +224,7 @@ background:transparent url(../../base/images/icons/twotone/green/news.gif) no-re
.pagination .nav_prev a,
.pagination .nav_next a {
background-repeat:no-repeat;
border-color:#DDAA00;
border-color:#000;
}
.pagination .nav_prev a {
background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);

View File

Before

Width:  |  Height:  |  Size: 646 B

After

Width:  |  Height:  |  Size: 646 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

BIN
theme/h4ck3r/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -72,13 +72,6 @@ border-top-color:#CEE1E9;
border-top-color:#87B4C8;
}
#content .notice p.entry-content a:visited {
background-color:#fcfcfc;
}
#content .notice p.entry-content .vcard a {
background-color:#fcfffc;
}
#aside_primary {
background-color:#CEE1E9;
}

View File

@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
html { font-size: 87.5%; background-color:#fff; height:100%; }
html { font-size: 87.5%; background-color:#fff; }
body {
background-color:#fff;
color:#000;
@ -386,12 +386,12 @@ margin-bottom:1em;
}
#content {
width:100%;
width:67.9%;
min-height:259px;
padding-top:1.795%;
padding-bottom:1.795%;
float:left;
clear:left;
border-radius:7px;
-moz-border-radius:7px;
-moz-border-radius-topleft:0;
@ -409,11 +409,11 @@ float:left;
}
#aside_primary {
width:96.3%;
width:27.917%;
min-height:259px;
float:left;
clear:both;
padding:1.795%;
margin-left:0.385%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
@ -730,7 +730,7 @@ list-style-type:none;
}
#content .notice {
width:25%;
width:37%;
margin-left:17px;
margin-bottom:47px;
clear:none;
@ -743,6 +743,10 @@ min-height:235px;
margin-bottom:18px;
}
#shownotice #content .notice {
width:96%;
}
/* NOTICES */
#notices_primary {

View File

@ -15,7 +15,6 @@ html {
html,
body,
a:active {
/*background-color:#F0F2F5;*/
}
body {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -23,14 +23,16 @@ Only alter this file if you want to change the layout of the site. Please note t
./default/css/display.css contains only the background images and colour rules:
This file is a good basis for creating your own theme.
Let's create a theme:
1. Copy over the default theme to start off (replace 'mytheme'):
cp -r ./default ./mytheme
1. To start off, copy over the default theme:
cp -r default mytheme
2. Edit your mytheme stylesheet:
nano ./mytheme/css/display.css
nano mytheme/css/display.css
3. Search and replace a colour or a path to the background image of your choice.
a) Search and replace your colours and background images, or
b) Create your own layout either importing a separate stylesheet (e.g., change to @import url(base.css);) or simply place it before the rest of the rules.
4. Set /config.php to load 'mytheme':
$config['site']['theme'] = 'mytheme';