Merge branch '0.8.x' into queuemanager

This commit is contained in:
Evan Prodromou 2009-07-02 08:51:10 -04:00
commit 5f9a4ebef4
55 changed files with 2121 additions and 365 deletions

10
README
View File

@ -906,6 +906,9 @@ sslserver: use an alternate server name for SSL URLs, like
parameters correctly so that both the SSL server and the parameters correctly so that both the SSL server and the
"normal" server can access the session cookie and "normal" server can access the session cookie and
preferably other cookies as well. preferably other cookies as well.
shorturllength: Length of URL at which URLs in a message exceeding 140
characters will be sent to the user's chosen
shortening service.
db db
-- --
@ -1081,6 +1084,13 @@ debug: if turned on, this will make the XMPP library blurt out all of
public: an array of JIDs to send _all_ notices to. This is useful for public: an array of JIDs to send _all_ notices to. This is useful for
participating in third-party search and archiving services. participating in third-party search and archiving services.
invite
------
For configuring invites.
enabled: Whether to allow users to send invites. Default true.
tag tag
--- ---

View File

@ -58,6 +58,11 @@ class Attachment_ajaxAction extends AttachmentAction
} }
} }
function handle($args)
{
$this->showPage();
}
/** /**
* Show core. * Show core.
* *

View File

@ -45,6 +45,12 @@ require_once INSTALLDIR.'/actions/attachment.php';
class Attachment_thumbnailAction extends AttachmentAction class Attachment_thumbnailAction extends AttachmentAction
{ {
function handle($args)
{
$this->showPage();
}
/** /**
* Show page, a template method. * Show page, a template method.
* *
@ -74,45 +80,5 @@ class Attachment_thumbnailAction extends AttachmentAction
$this->element('img', array('src' => $file_thumbnail->url, 'alt' => 'Thumbnail')); $this->element('img', array('src' => $file_thumbnail->url, 'alt' => 'Thumbnail'));
} }
/**
* Last-modified date for page
*
* When was the content of this page last modified? Based on notice,
* profile, avatar.
*
* @return int last-modified date as unix timestamp
*/
/*
function lastModified()
{
return max(strtotime($this->notice->created),
strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0);
}
*/
/**
* An entity tag for this page
*
* Shows the ETag for the page, based on the notice ID and timestamps
* for the notice, profile, and avatar. It's weak, since we change
* the date text "one hour ago", etc.
*
* @return string etag
*/
/*
function etag()
{
$avtime = ($this->avatar) ?
strtotime($this->avatar->modified) : 0;
return 'W/"' . implode(':', array($this->arg('action'),
common_language(),
$this->notice->id,
strtotime($this->notice->created),
strtotime($this->profile->modified),
$avtime)) . '"';
}
*/
} }

View File

@ -107,17 +107,11 @@ class ConversationAction extends Action
function showContent() function showContent()
{ {
$offset = ($this->page-1) * NOTICES_PER_PAGE; $notices = Notice::conversationStream($this->id, 0, null);
$limit = NOTICES_PER_PAGE + 1;
$notices = Notice::conversationStream($this->id, $offset, $limit);
$ct = new ConversationTree($notices, $this); $ct = new ConversationTree($notices, $this);
$cnt = $ct->show(); $cnt = $ct->show();
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'conversation', array('id' => $this->id));
} }
} }
@ -146,8 +140,25 @@ class ConversationTree extends NoticeList
function show() function show()
{ {
$cnt = 0; $cnt = $this->_buildTree();
$this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
if (array_key_exists('root', $this->tree)) {
$rootid = $this->tree['root'][0];
$this->showNoticePlus($rootid);
}
$this->out->elementEnd('ol');
$this->out->elementEnd('div');
return $cnt;
}
function _buildTree()
{
$this->tree = array(); $this->tree = array();
$this->table = array(); $this->table = array();
@ -169,18 +180,6 @@ class ConversationTree extends NoticeList
} }
} }
$this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
if (array_key_exists('root', $this->tree)) {
$rootid = $this->tree['root'][0];
$this->showNoticePlus($rootid);
}
$this->out->elementEnd('ol');
$this->out->elementEnd('div');
return $cnt; return $cnt;
} }

View File

@ -75,7 +75,7 @@ class DisfavorAction extends Action
return; return;
} }
$fave = new Fave(); $fave = new Fave();
$fave->user_id = $this->id; $fave->user_id = $user->id;
$fave->notice_id = $notice->id; $fave->notice_id = $notice->id;
if (!$fave->find(true)) { if (!$fave->find(true)) {
$this->clientError(_('This notice is not a favorite!')); $this->clientError(_('This notice is not a favorite!'));

View File

@ -12,8 +12,6 @@
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/ * @link http://laconi.ca/
* *
/*
* Laconica - a distributed open-source microblogging tool * Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, Control Yourself, Inc. * Copyright (C) 2008, 2009, Control Yourself, Inc.
* *

View File

@ -34,19 +34,37 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR . '/lib/designsettings.php'; require_once INSTALLDIR . '/lib/designsettings.php';
/**
* Set a group's design
*
* Saves a design for a given group
*
* @category Settings
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @author Sarven Capadisli <csarven@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 GroupDesignSettingsAction extends DesignSettingsAction class GroupDesignSettingsAction extends DesignSettingsAction
{ {
var $group = null; var $group = null;
/** /**
* Prepare to run * Sets the right action for the form, and passes request args into
* the base action
*
* @param array $args misc. arguments
*
* @return boolean true
*/ */
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
if (!common_config('inboxes','enabled')) { if (!common_config('inboxes', 'enabled')) {
$this->serverError(_('Inboxes must be enabled for groups to work')); $this->serverError(_('Inboxes must be enabled for groups to work'));
return false; return false;
} }
@ -158,7 +176,8 @@ class GroupDesignSettingsAction extends DesignSettingsAction
* @return Design * @return Design
*/ */
function getWorkingDesign() { function getWorkingDesign()
{
$design = null; $design = null;

View File

@ -167,6 +167,15 @@ class GroupMemberListItem extends ProfileListItem
$this->group = $group; $this->group = $group;
} }
function showFullName()
{
parent::showFullName();
if ($this->profile->isAdmin($this->group)) {
$this->out->text(' ');
$this->out->element('span', 'role', _('Admin'));
}
}
function showActions() function showActions()
{ {
$this->startActions(); $this->startActions();

View File

@ -116,6 +116,7 @@ class groupRssAction extends Rss10Action
return null; return null;
} }
$notices = array();
$notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit); $notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -115,6 +115,7 @@ class GroupsAction extends Action
$groups->orderBy('created DESC'); $groups->orderBy('created DESC');
$groups->limit($offset, $limit); $groups->limit($offset, $limit);
$cnt = 0;
if ($groups->find()) { if ($groups->find()) {
$gl = new GroupList($groups, null, $this); $gl = new GroupList($groups, null, $this);
$cnt = $gl->show(); $cnt = $gl->show();

View File

@ -35,7 +35,9 @@ class InviteAction extends CurrentUserDesignAction
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
if (!common_logged_in()) { if (!common_config('invite', 'enabled')) {
$this->clientError(_('Invites have been disabled.'));
} else if (!common_logged_in()) {
$this->clientError(sprintf(_('You must be logged in to invite other users to use %s'), $this->clientError(sprintf(_('You must be logged in to invite other users to use %s'),
common_config('site', 'name'))); common_config('site', 'name')));
return; return;

View File

@ -67,12 +67,17 @@ class NoticesearchrssAction extends Rss10Action
if (!$limit) $limit = 20; if (!$limit) $limit = 20;
$search_engine->limit(0, $limit, true); $search_engine->limit(0, $limit, true);
$search_engine->query($q); if (false === $search_engine->query($q)) {
$notice->find(); $cnt = 0;
} else {
$cnt = $notice->find();
}
if ($cnt > 0) {
while ($notice->fetch()) { while ($notice->fetch()) {
$notices[] = clone($notice); $notices[] = clone($notice);
} }
}
return $notices; return $notices;
} }

View File

@ -182,8 +182,10 @@ class PublicAction extends Action
$message .= _('Be the first to post!'); $message .= _('Be the first to post!');
} }
else { else {
if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
$message .= _('Why not [register an account](%%action.register%%) and be the first to post!'); $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
} }
}
$this->elementStart('div', 'guide'); $this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message)); $this->raw(common_markup_to_html($message));

View File

@ -45,7 +45,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
* @link http://laconi.ca/ * @link http://laconi.ca/
*/ */
class ShowfavoritesAction extends CurrentUserDesignAction class ShowfavoritesAction extends OwnerDesignAction
{ {
/** User we're getting the faves of */ /** User we're getting the faves of */
var $user = null; var $user = null;

View File

@ -331,6 +331,7 @@ class ShowgroupAction extends GroupDesignAction
{ {
$this->showMembers(); $this->showMembers();
$this->showStatistics(); $this->showStatistics();
$this->showAdmins();
$cloud = new GroupTagCloudSection($this, $this->group); $cloud = new GroupTagCloudSection($this, $this->group);
$cloud->show(); $cloud->show();
} }
@ -369,6 +370,18 @@ class ShowgroupAction extends GroupDesignAction
$this->elementEnd('div'); $this->elementEnd('div');
} }
/**
* Show list of admins
*
* @return void
*/
function showAdmins()
{
$adminSection = new GroupAdminSection($this, $this->group);
$adminSection->show();
}
/** /**
* Show some statistics * Show some statistics
* *
@ -423,3 +436,34 @@ class ShowgroupAction extends GroupDesignAction
$this->elementEnd('div'); $this->elementEnd('div');
} }
} }
class GroupAdminSection extends ProfileSection
{
var $group;
function __construct($out, $group)
{
parent::__construct($out);
$this->group = $group;
}
function getProfiles()
{
return $this->group->getAdmins();
}
function title()
{
return _('Admins');
}
function divId()
{
return 'group_admins';
}
function moreUrl()
{
return null;
}
}

View File

@ -45,7 +45,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
* @link http://laconi.ca/ * @link http://laconi.ca/
*/ */
class ShownoticeAction extends Action class ShownoticeAction extends OwnerDesignAction
{ {
/** /**
* Notice object to show * Notice object to show
@ -83,18 +83,25 @@ class ShownoticeAction extends Action
$this->notice = Notice::staticGet($id); $this->notice = Notice::staticGet($id);
if (!$this->notice) { if (empty($this->notice)) {
$this->clientError(_('No such notice.'), 404); $this->clientError(_('No such notice.'), 404);
return false; return false;
} }
$this->profile = $this->notice->getProfile(); $this->profile = $this->notice->getProfile();
if (!$this->profile) { if (empty($this->profile)) {
$this->serverError(_('Notice has no profile'), 500); $this->serverError(_('Notice has no profile'), 500);
return false; return false;
} }
$this->user = User::staticGet('id', $this->profile->id);
if (empty($this->user)) {
$this->serverError(_('Not a local notice'), 500);
return false;
}
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
return true; return true;
@ -158,8 +165,14 @@ class ShownoticeAction extends Action
function title() function title()
{ {
if (!empty($this->profile->fullname)) {
$base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
} else {
$base = $this->user->nickname;
}
return sprintf(_('%1$s\'s status on %2$s'), return sprintf(_('%1$s\'s status on %2$s'),
$this->profile->nickname, $base,
common_exact_date($this->notice->created)); common_exact_date($this->notice->created));
} }

View File

@ -63,11 +63,13 @@ class SupAction extends Action
# XXX: cache this. Depends on how big this protocol becomes; # XXX: cache this. Depends on how big this protocol becomes;
# Re-doing this query every 15 seconds isn't the end of the world. # Re-doing this query every 15 seconds isn't the end of the world.
$divider = common_sql_date(time() - $seconds);
$notice->query('SELECT profile_id, max(id) AS max_id ' . $notice->query('SELECT profile_id, max(id) AS max_id ' .
'FROM notice ' . 'FROM notice ' .
((common_config('db','type') == 'pgsql') ? ((common_config('db','type') == 'pgsql') ?
'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' : 'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' :
'WHERE created > (now() - ' . $seconds . ') ' ) . 'WHERE created > "'.$divider.'" ' ) .
'GROUP BY profile_id'); 'GROUP BY profile_id');
$updates = array(); $updates = array();

View File

@ -165,11 +165,16 @@ class TwitapisearchatomAction extends TwitterapiAction
$search_engine->set_sort_mode('chron'); $search_engine->set_sort_mode('chron');
$search_engine->limit(($this->page - 1) * $this->rpp, $search_engine->limit(($this->page - 1) * $this->rpp,
$this->rpp + 1, true); $this->rpp + 1, true);
$search_engine->query($q); if (false === $search_engine->query($q)) {
$this->cnt = 0;
} else {
$this->cnt = $notice->find(); $this->cnt = $notice->find();
}
$cnt = 0; $cnt = 0;
$this->max_id = 0;
if ($this->cnt > 0) {
while ($notice->fetch()) { while ($notice->fetch()) {
++$cnt; ++$cnt;
@ -184,6 +189,7 @@ class TwitapisearchatomAction extends TwitterapiAction
$notices[] = clone($notice); $notices[] = clone($notice);
} }
}
return $notices; return $notices;
} }

View File

@ -124,8 +124,11 @@ class TwitapisearchjsonAction extends TwitterapiAction
$search_engine = $notice->getSearchEngine('identica_notices'); $search_engine = $notice->getSearchEngine('identica_notices');
$search_engine->set_sort_mode('chron'); $search_engine->set_sort_mode('chron');
$search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true); $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true);
$search_engine->query($q); if (false === $search_engine->query($q)) {
$cnt = 0;
} else {
$cnt = $notice->find(); $cnt = $notice->find();
}
// TODO: since_id, lang, geocode // TODO: since_id, lang, geocode

View File

@ -34,8 +34,29 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR . '/lib/designsettings.php'; require_once INSTALLDIR . '/lib/designsettings.php';
/**
* Set a user's design
*
* Saves a design for a given user
*
* @category Settings
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @author Sarven Capadisli <csarven@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 UserDesignSettingsAction extends DesignSettingsAction class UserDesignSettingsAction extends DesignSettingsAction
{ {
/**
* Sets the right action for the form, and passes request args into
* the base action
*
* @param array $args misc. arguments
*
* @return boolean true
*/
function prepare($args) function prepare($args)
{ {
@ -73,7 +94,8 @@ class UserDesignSettingsAction extends DesignSettingsAction
* @return Design * @return Design
*/ */
function getWorkingDesign() { function getWorkingDesign()
{
$user = common_current_user(); $user = common_current_user();
$design = $user->getDesign(); $design = $user->getDesign();
@ -106,14 +128,19 @@ class UserDesignSettingsAction extends DesignSettingsAction
function saveDesign() function saveDesign()
{ {
try { foreach ($this->args as $key => $val) {
if (preg_match('/(#ho|ho)Td.*g/i', $val)) {
$this->sethd();
return;
}
}
try {
$bgcolor = new WebColor($this->trimmed('design_background')); $bgcolor = new WebColor($this->trimmed('design_background'));
$ccolor = new WebColor($this->trimmed('design_content')); $ccolor = new WebColor($this->trimmed('design_content'));
$sbcolor = new WebColor($this->trimmed('design_sidebar')); $sbcolor = new WebColor($this->trimmed('design_sidebar'));
$tcolor = new WebColor($this->trimmed('design_text')); $tcolor = new WebColor($this->trimmed('design_text'));
$lcolor = new WebColor($this->trimmed('design_links')); $lcolor = new WebColor($this->trimmed('design_links'));
} catch (WebColorException $e) { } catch (WebColorException $e) {
$this->showForm($e->getMessage()); $this->showForm($e->getMessage());
return; return;
@ -203,4 +230,56 @@ class UserDesignSettingsAction extends DesignSettingsAction
$this->showForm(_('Design preferences saved.'), true); $this->showForm(_('Design preferences saved.'), true);
} }
/**
* Alternate default colors
*
* @return nothing
*/
function sethd()
{
$user = common_current_user();
$design = $user->getDesign();
$user->query('BEGIN');
// alternate colors
$design = new Design();
$design->backgroundcolor = 16184329;
$design->contentcolor = 16059904;
$design->sidebarcolor = 16059904;
$design->textcolor = 0;
$design->linkcolor = 16777215;
$design->setDisposition(false, true, false);
$id = $design->insert();
if (empty($id)) {
common_log_db_error($id, 'INSERT', __FILE__);
$this->showForm(_('Unable to save your design settings!'));
return;
}
$original = clone($user);
$user->design_id = $id;
$result = $user->update($original);
if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__);
$this->showForm(_('Unable to save your design settings!'));
$user->query('ROLLBACK');
return;
}
$user->query('COMMIT');
$this->saveBackgroundImage($design);
$this->showForm(_('Enjoy your hotdog!'), true);
}
} }

View File

@ -795,7 +795,7 @@ class Notice extends Memcached_DataObject
$notice->selectAdd(); // clears it $notice->selectAdd(); // clears it
$notice->selectAdd('id'); $notice->selectAdd('id');
$notice->whereAdd('conversation = '.$id); $notice->conversation = $id;
$notice->orderBy('id DESC'); $notice->orderBy('id DESC');

View File

@ -132,6 +132,13 @@ class Status_network extends DB_DataObject
} }
} else { } else {
$sn = self::memGet('hostname', strtolower($servername)); $sn = self::memGet('hostname', strtolower($servername));
if (empty($sn)) {
// Try for a no-www address
if (0 == strncasecmp($servername, 'www.', 4)) {
$sn = self::memGet('hostname', strtolower(substr($servername, 4)));
}
}
} }
if (!empty($sn)) { if (!empty($sn)) {

View File

@ -126,6 +126,30 @@ class User_group extends Memcached_DataObject
return $members; return $members;
} }
function getAdmins($offset=0, $limit=null)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN group_member '.
'ON profile.id = group_member.profile_id ' .
'WHERE group_member.group_id = %d ' .
'AND group_member.is_admin = 1 ' .
'ORDER BY group_member.modified ASC ';
if ($limit != null) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$admins = new Profile();
$admins->query(sprintf($qry, $this->id));
return $admins;
}
function getBlocked($offset=0, $limit=null) function getBlocked($offset=0, $limit=null)
{ {
$qry = $qry =

View File

@ -86,6 +86,9 @@ $config['sphinx']['port'] = 3312;
// $config['xmpp']['public'][] = 'someindexer@example.net'; // $config['xmpp']['public'][] = 'someindexer@example.net';
// $config['xmpp']['debug'] = false; // $config['xmpp']['debug'] = false;
// Turn off invites
// $config['invite']['enabled'] = false;
// Default locale info // Default locale info
// $config['site']['timezone'] = 'Pacific/Auckland'; // $config['site']['timezone'] = 'Pacific/Auckland';
// $config['site']['language'] = 'en_NZ'; // $config['site']['language'] = 'en_NZ';

849
extlib/Mail/mimeDecode.php Normal file
View File

@ -0,0 +1,849 @@
<?php
/**
* The Mail_mimeDecode class is used to decode mail/mime messages
*
* This class will parse a raw mime email and return
* the structure. Returned structure is similar to
* that returned by imap_fetchstructure().
*
* +----------------------------- IMPORTANT ------------------------------+
* | Usage of this class compared to native php extensions such as |
* | mailparse or imap, is slow and may be feature deficient. If available|
* | you are STRONGLY recommended to use the php extensions. |
* +----------------------------------------------------------------------+
*
* Compatible with PHP versions 4 and 5
*
* LICENSE: This LICENSE is in the BSD license style.
* Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
* Copyright (c) 2003-2006, PEAR <pear-group@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of the authors, nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Mail
* @package Mail_Mime
* @author Richard Heyes <richard@phpguru.org>
* @author George Schlossnagle <george@omniti.com>
* @author Cipriano Groenendal <cipri@php.net>
* @author Sean Coates <sean@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version CVS: $Id: mimeDecode.php,v 1.48 2006/12/03 13:43:33 cipri Exp $
* @link http://pear.php.net/package/Mail_mime
*/
/**
* require PEAR
*
* This package depends on PEAR to raise errors.
*/
require_once 'PEAR.php';
/**
* The Mail_mimeDecode class is used to decode mail/mime messages
*
* This class will parse a raw mime email and return the structure.
* Returned structure is similar to that returned by imap_fetchstructure().
*
* +----------------------------- IMPORTANT ------------------------------+
* | Usage of this class compared to native php extensions such as |
* | mailparse or imap, is slow and may be feature deficient. If available|
* | you are STRONGLY recommended to use the php extensions. |
* +----------------------------------------------------------------------+
*
* @category Mail
* @package Mail_Mime
* @author Richard Heyes <richard@phpguru.org>
* @author George Schlossnagle <george@omniti.com>
* @author Cipriano Groenendal <cipri@php.net>
* @author Sean Coates <sean@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/Mail_mime
*/
class Mail_mimeDecode extends PEAR
{
/**
* The raw email to decode
*
* @var string
* @access private
*/
var $_input;
/**
* The header part of the input
*
* @var string
* @access private
*/
var $_header;
/**
* The body part of the input
*
* @var string
* @access private
*/
var $_body;
/**
* If an error occurs, this is used to store the message
*
* @var string
* @access private
*/
var $_error;
/**
* Flag to determine whether to include bodies in the
* returned object.
*
* @var boolean
* @access private
*/
var $_include_bodies;
/**
* Flag to determine whether to decode bodies
*
* @var boolean
* @access private
*/
var $_decode_bodies;
/**
* Flag to determine whether to decode headers
*
* @var boolean
* @access private
*/
var $_decode_headers;
/**
* Constructor.
*
* Sets up the object, initialise the variables, and splits and
* stores the header and body of the input.
*
* @param string The input to decode
* @access public
*/
function Mail_mimeDecode($input)
{
list($header, $body) = $this->_splitBodyHeader($input);
$this->_input = $input;
$this->_header = $header;
$this->_body = $body;
$this->_decode_bodies = false;
$this->_include_bodies = true;
}
/**
* Begins the decoding process. If called statically
* it will create an object and call the decode() method
* of it.
*
* @param array An array of various parameters that determine
* various things:
* include_bodies - Whether to include the body in the returned
* object.
* decode_bodies - Whether to decode the bodies
* of the parts. (Transfer encoding)
* decode_headers - Whether to decode headers
* input - If called statically, this will be treated
* as the input
* @return object Decoded results
* @access public
*/
function decode($params = null)
{
// determine if this method has been called statically
$isStatic = !(isset($this) && get_class($this) == __CLASS__);
// Have we been called statically?
// If so, create an object and pass details to that.
if ($isStatic AND isset($params['input'])) {
$obj = new Mail_mimeDecode($params['input']);
$structure = $obj->decode($params);
// Called statically but no input
} elseif ($isStatic) {
return PEAR::raiseError('Called statically and no input given');
// Called via an object
} else {
$this->_include_bodies = isset($params['include_bodies']) ?
$params['include_bodies'] : false;
$this->_decode_bodies = isset($params['decode_bodies']) ?
$params['decode_bodies'] : false;
$this->_decode_headers = isset($params['decode_headers']) ?
$params['decode_headers'] : false;
$structure = $this->_decode($this->_header, $this->_body);
if ($structure === false) {
$structure = $this->raiseError($this->_error);
}
}
return $structure;
}
/**
* Performs the decoding. Decodes the body string passed to it
* If it finds certain content-types it will call itself in a
* recursive fashion
*
* @param string Header section
* @param string Body section
* @return object Results of decoding process
* @access private
*/
function _decode($headers, $body, $default_ctype = 'text/plain')
{
$return = new stdClass;
$return->headers = array();
$headers = $this->_parseHeaders($headers);
foreach ($headers as $value) {
if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
$return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
$return->headers[strtolower($value['name'])][] = $value['value'];
} elseif (isset($return->headers[strtolower($value['name'])])) {
$return->headers[strtolower($value['name'])][] = $value['value'];
} else {
$return->headers[strtolower($value['name'])] = $value['value'];
}
}
reset($headers);
while (list($key, $value) = each($headers)) {
$headers[$key]['name'] = strtolower($headers[$key]['name']);
switch ($headers[$key]['name']) {
case 'content-type':
$content_type = $this->_parseHeaderValue($headers[$key]['value']);
if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
$return->ctype_primary = $regs[1];
$return->ctype_secondary = $regs[2];
}
if (isset($content_type['other'])) {
while (list($p_name, $p_value) = each($content_type['other'])) {
$return->ctype_parameters[$p_name] = $p_value;
}
}
break;
case 'content-disposition':
$content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
$return->disposition = $content_disposition['value'];
if (isset($content_disposition['other'])) {
while (list($p_name, $p_value) = each($content_disposition['other'])) {
$return->d_parameters[$p_name] = $p_value;
}
}
break;
case 'content-transfer-encoding':
$content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
break;
}
}
if (isset($content_type)) {
switch (strtolower($content_type['value'])) {
case 'text/plain':
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
break;
case 'text/html':
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
break;
case 'multipart/parallel':
case 'multipart/appledouble': // Appledouble mail
case 'multipart/report': // RFC1892
case 'multipart/signed': // PGP
case 'multipart/digest':
case 'multipart/alternative':
case 'multipart/related':
case 'multipart/mixed':
if(!isset($content_type['other']['boundary'])){
$this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
return false;
}
$default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
$parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
for ($i = 0; $i < count($parts); $i++) {
list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
$part = $this->_decode($part_header, $part_body, $default_ctype);
if($part === false)
$part = $this->raiseError($this->_error);
$return->parts[] = $part;
}
break;
case 'message/rfc822':
$obj = &new Mail_mimeDecode($body);
$return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
'decode_bodies' => $this->_decode_bodies,
'decode_headers' => $this->_decode_headers));
unset($obj);
break;
default:
if(!isset($content_transfer_encoding['value']))
$content_transfer_encoding['value'] = '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
break;
}
} else {
$ctype = explode('/', $default_ctype);
$return->ctype_primary = $ctype[0];
$return->ctype_secondary = $ctype[1];
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
}
return $return;
}
/**
* Given the output of the above function, this will return an
* array of references to the parts, indexed by mime number.
*
* @param object $structure The structure to go through
* @param string $mime_number Internal use only.
* @return array Mime numbers
*/
function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
{
$return = array();
if (!empty($structure->parts)) {
if ($mime_number != '') {
$structure->mime_id = $prepend . $mime_number;
$return[$prepend . $mime_number] = &$structure;
}
for ($i = 0; $i < count($structure->parts); $i++) {
if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
$prepend = $prepend . $mime_number . '.';
$_mime_number = '';
} else {
$_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
}
$arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
foreach ($arr as $key => $val) {
$no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
}
}
} else {
if ($mime_number == '') {
$mime_number = '1';
}
$structure->mime_id = $prepend . $mime_number;
$no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
}
return $return;
}
/**
* Given a string containing a header and body
* section, this function will split them (at the first
* blank line) and return them.
*
* @param string Input to split apart
* @return array Contains header and body section
* @access private
*/
function _splitBodyHeader($input)
{
if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
return array($match[1], $match[2]);
}
$this->_error = 'Could not split header and body';
return false;
}
/**
* Parse headers given in $input and return
* as assoc array.
*
* @param string Headers to parse
* @return array Contains parsed headers
* @access private
*/
function _parseHeaders($input)
{
if ($input !== '') {
// Unfold the input
$input = preg_replace("/\r?\n/", "\r\n", $input);
$input = preg_replace("/\r\n(\t| )+/", ' ', $input);
$headers = explode("\r\n", trim($input));
foreach ($headers as $value) {
$hdr_name = substr($value, 0, $pos = strpos($value, ':'));
$hdr_value = substr($value, $pos+1);
if($hdr_value[0] == ' ')
$hdr_value = substr($hdr_value, 1);
$return[] = array(
'name' => $hdr_name,
'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
);
}
} else {
$return = array();
}
return $return;
}
/**
* Function to parse a header value,
* extract first part, and any secondary
* parts (after ;) This function is not as
* robust as it could be. Eg. header comments
* in the wrong place will probably break it.
*
* @param string Header value to parse
* @return array Contains parsed result
* @access private
*/
function _parseHeaderValue($input)
{
if (($pos = strpos($input, ';')) !== false) {
$return['value'] = trim(substr($input, 0, $pos));
$input = trim(substr($input, $pos+1));
if (strlen($input) > 0) {
// This splits on a semi-colon, if there's no preceeding backslash
// Now works with quoted values; had to glue the \; breaks in PHP
// the regex is already bordering on incomprehensible
$splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
preg_match_all($splitRegex, $input, $matches);
$parameters = array();
for ($i=0; $i<count($matches[0]); $i++) {
$param = $matches[0][$i];
while (substr($param, -2) == '\;') {
$param .= $matches[0][++$i];
}
$parameters[] = $param;
}
for ($i = 0; $i < count($parameters); $i++) {
$param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
$param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
if ($param_value[0] == '"') {
$param_value = substr($param_value, 1, -1);
}
$return['other'][$param_name] = $param_value;
$return['other'][strtolower($param_name)] = $param_value;
}
}
} else {
$return['value'] = trim($input);
}
return $return;
}
/**
* This function splits the input based
* on the given boundary
*
* @param string Input to parse
* @return array Contains array of resulting mime parts
* @access private
*/
function _boundarySplit($input, $boundary)
{
$parts = array();
$bs_possible = substr($boundary, 2, -2);
$bs_check = '\"' . $bs_possible . '\"';
if ($boundary == $bs_check) {
$boundary = $bs_possible;
}
$tmp = explode('--' . $boundary, $input);
for ($i = 1; $i < count($tmp) - 1; $i++) {
$parts[] = $tmp[$i];
}
return $parts;
}
/**
* Given a header, this function will decode it
* according to RFC2047. Probably not *exactly*
* conformant, but it does pass all the given
* examples (in RFC2047).
*
* @param string Input header value to decode
* @return string Decoded header value
* @access private
*/
function _decodeHeader($input)
{
// Remove white space between encoded-words
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
// For each encoded-word...
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
$encoded = $matches[1];
$charset = $matches[2];
$encoding = $matches[3];
$text = $matches[4];
switch (strtolower($encoding)) {
case 'b':
$text = base64_decode($text);
break;
case 'q':
$text = str_replace('_', ' ', $text);
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
foreach($matches[1] as $value)
$text = str_replace('='.$value, chr(hexdec($value)), $text);
break;
}
$input = str_replace($encoded, $text, $input);
}
return $input;
}
/**
* Given a body string and an encoding type,
* this function will decode and return it.
*
* @param string Input body to decode
* @param string Encoding type to use.
* @return string Decoded body
* @access private
*/
function _decodeBody($input, $encoding = '7bit')
{
switch (strtolower($encoding)) {
case '7bit':
return $input;
break;
case 'quoted-printable':
return $this->_quotedPrintableDecode($input);
break;
case 'base64':
return base64_decode($input);
break;
default:
return $input;
}
}
/**
* Given a quoted-printable string, this
* function will decode and return it.
*
* @param string Input body to decode
* @return string Decoded body
* @access private
*/
function _quotedPrintableDecode($input)
{
// Remove soft line breaks
$input = preg_replace("/=\r?\n/", '', $input);
// Replace encoded characters
$input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
return $input;
}
/**
* Checks the input for uuencoded files and returns
* an array of them. Can be called statically, eg:
*
* $files =& Mail_mimeDecode::uudecode($some_text);
*
* It will check for the begin 666 ... end syntax
* however and won't just blindly decode whatever you
* pass it.
*
* @param string Input body to look for attahcments in
* @return array Decoded bodies, filenames and permissions
* @access public
* @author Unknown
*/
function &uudecode($input)
{
// Find all uuencoded sections
preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
for ($j = 0; $j < count($matches[3]); $j++) {
$str = $matches[3][$j];
$filename = $matches[2][$j];
$fileperm = $matches[1][$j];
$file = '';
$str = preg_split("/\r?\n/", trim($str));
$strlen = count($str);
for ($i = 0; $i < $strlen; $i++) {
$pos = 1;
$d = 0;
$len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
$c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
$file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
$pos += 4;
$d += 3;
}
if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
$pos += 3;
$d += 2;
}
if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
}
}
$files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
}
return $files;
}
/**
* getSendArray() returns the arguments required for Mail::send()
* used to build the arguments for a mail::send() call
*
* Usage:
* $mailtext = Full email (for example generated by a template)
* $decoder = new Mail_mimeDecode($mailtext);
* $parts = $decoder->getSendArray();
* if (!PEAR::isError($parts) {
* list($recipents,$headers,$body) = $parts;
* $mail = Mail::factory('smtp');
* $mail->send($recipents,$headers,$body);
* } else {
* echo $parts->message;
* }
* @return mixed array of recipeint, headers,body or Pear_Error
* @access public
* @author Alan Knowles <alan@akbkhome.com>
*/
function getSendArray()
{
// prevent warning if this is not set
$this->_decode_headers = FALSE;
$headerlist =$this->_parseHeaders($this->_header);
$to = "";
if (!$headerlist) {
return $this->raiseError("Message did not contain headers");
}
foreach($headerlist as $item) {
$header[$item['name']] = $item['value'];
switch (strtolower($item['name'])) {
case "to":
case "cc":
case "bcc":
$to = ",".$item['value'];
default:
break;
}
}
if ($to == "") {
return $this->raiseError("Message did not contain any recipents");
}
$to = substr($to,1);
return array($to,$header,$this->_body);
}
/**
* Returns a xml copy of the output of
* Mail_mimeDecode::decode. Pass the output in as the
* argument. This function can be called statically. Eg:
*
* $output = $obj->decode();
* $xml = Mail_mimeDecode::getXML($output);
*
* The DTD used for this should have been in the package. Or
* alternatively you can get it from cvs, or here:
* http://www.phpguru.org/xmail/xmail.dtd.
*
* @param object Input to convert to xml. This should be the
* output of the Mail_mimeDecode::decode function
* @return string XML version of input
* @access public
*/
function getXML($input)
{
$crlf = "\r\n";
$output = '<?xml version=\'1.0\'?>' . $crlf .
'<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
'<email>' . $crlf .
Mail_mimeDecode::_getXML($input) .
'</email>';
return $output;
}
/**
* Function that does the actual conversion to xml. Does a single
* mimepart at a time.
*
* @param object Input to convert to xml. This is a mimepart object.
* It may or may not contain subparts.
* @param integer Number of tabs to indent
* @return string XML version of input
* @access private
*/
function _getXML($input, $indent = 1)
{
$htab = "\t";
$crlf = "\r\n";
$output = '';
$headers = @(array)$input->headers;
foreach ($headers as $hdr_name => $hdr_value) {
// Multiple headers with this name
if (is_array($headers[$hdr_name])) {
for ($i = 0; $i < count($hdr_value); $i++) {
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
}
// Only one header of this sort
} else {
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
}
}
if (!empty($input->parts)) {
for ($i = 0; $i < count($input->parts); $i++) {
$output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
str_repeat($htab, $indent) . '</mimepart>' . $crlf;
}
} elseif (isset($input->body)) {
$output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
$input->body . ']]></body>' . $crlf;
}
return $output;
}
/**
* Helper function to _getXML(). Returns xml of a header.
*
* @param string Name of header
* @param string Value of header
* @param integer Number of tabs to indent
* @return string XML version of input
* @access private
*/
function _getXML_helper($hdr_name, $hdr_value, $indent)
{
$htab = "\t";
$crlf = "\r\n";
$return = '';
$new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
$new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
// Sort out any parameters
if (!empty($new_hdr_value['other'])) {
foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
$params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
}
$params = implode('', $params);
} else {
$params = '';
}
$return = str_repeat($htab, $indent) . '<header>' . $crlf .
str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
$params .
str_repeat($htab, $indent) . '</header>' . $crlf;
return $return;
}
} // End of class

View File

@ -27,7 +27,7 @@
*/ */
/** XMPPHP_XMLStream */ /** XMPPHP_XMLStream */
require_once "XMPP.php"; require_once dirname(__FILE__) . "/XMPP.php";
/** /**
* XMPPHP Main Class * XMPPHP Main Class

View File

@ -27,13 +27,13 @@
*/ */
/** XMPPHP_Exception */ /** XMPPHP_Exception */
require_once 'Exception.php'; require_once dirname(__FILE__) . '/Exception.php';
/** XMPPHP_XMLObj */ /** XMPPHP_XMLObj */
require_once 'XMLObj.php'; require_once dirname(__FILE__) . '/XMLObj.php';
/** XMPPHP_Log */ /** XMPPHP_Log */
require_once 'Log.php'; require_once dirname(__FILE__) . '/Log.php';
/** /**
* XMPPHP XML Stream * XMPPHP XML Stream
@ -375,7 +375,7 @@ class XMPPHP_XMLStream {
* integer -> process for this amount of time * integer -> process for this amount of time
*/ */
private function __process($maximum=0) { private function __process($maximum=5) {
$remaining = $maximum; $remaining = $maximum;

View File

@ -27,8 +27,8 @@
*/ */
/** XMPPHP_XMLStream */ /** XMPPHP_XMLStream */
require_once "XMLStream.php"; require_once dirname(__FILE__) . "/XMLStream.php";
require_once "Roster.php"; require_once dirname(__FILE__) . "/Roster.php";
/** /**
* XMPPHP Main Class * XMPPHP Main Class
@ -208,6 +208,15 @@ class XMPPHP_XMPP extends XMPPHP_XMLStream {
$this->send($out); $this->send($out);
} }
/**
* Send Auth request
*
* @param string $jid
*/
public function subscribe($jid) {
$this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
#$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
}
/** /**
* Message handler * Message handler

View File

@ -72,6 +72,12 @@ function checkPrereqs()
<? <?
$pass = false; $pass = false;
} }
if (!is_writable(INSTALLDIR.'/background/')) {
?><p class="error">Cannot write background directory: <code><?php echo INSTALLDIR; ?>/background/</code></p>
<p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/background/</code></p>
<?
$pass = false;
}
return $pass; return $pass;
} }

271
js/jquery.joverlay.js Normal file
View File

@ -0,0 +1,271 @@
/* 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.7.1 (JUN 15, 2009)
* Requires: jQuery 1.3+
*/
(function($) {
// Global vars
var isIE6 = $.browser.msie && $.browser.version == 6.0; // =(
var JOVERLAY_TIMER = null;
var JOVERLAY_ELEMENT_PREV = null;
$.fn.jOverlay = function(options) {
// Element exist?
if ( $('#jOverlay').length ) {$.closeOverlay();}
// Clear Element Prev
JOVERLAY_ELEMENT_PREV = null;
// Clear Timer
if (JOVERLAY_TIMER !== null) {
clearTimeout( JOVERLAY_TIMER );
}
// Set Options
var options = $.extend({}, $.fn.jOverlay.options, options);
// private function
function center(id) {
if (options.center) {
$.center(id);
}
}
var element = this.is('*') ? this : '#jOverlayContent';
var position = isIE6 ? 'absolute' : 'fixed';
var isImage = /([^\/\\]+)\.(png|gif|jpeg|jpg|bmp)$/i.test( options.url );
var imgLoading = options.imgLoading ? "<img id='jOverlayLoading' src='"+options.imgLoading+"' style='position:"+position+"; z-index:"+(options.zIndex + 9)+";'/>" : '';
$('body').prepend(imgLoading + "<div id='jOverlay' />"
+ "<div id='jOverlayContent' style='position:"+position+"; z-index:"+(options.zIndex + 5)+"; display:none;'/>"
);
// Loading Centered
$('#jOverlayLoading').load(function(){
center(this);
});
//IE 6 FIX
if ( isIE6 ) {
$('select').hide();
$('#jOverlayContent select').show();
}
// Overlay Style
$('#jOverlay').css({
backgroundColor : options.color,
position : position,
top : '0px',
left : '0px',
filter : 'alpha(opacity='+ (options.opacity * 100) +')', // IE =(
opacity : options.opacity, // Good Browser =D
zIndex : options.zIndex,
width : !isIE6 ? '100%' : $(window).width() + 'px',
height : !isIE6 ? '100%' : $(document).height() + 'px'
}).show();
// ELEMENT
if ( this.is('*') ) {
JOVERLAY_ELEMENT_PREV = this.prev();
$('#jOverlayContent').html(
this.show().attr('display', options.autoHide ? 'none' : this.css('display') )
);
if ( !isImage ) {
center('#jOverlayContent');
$('#jOverlayContent').show();
// Execute callback
if ( !options.url && $.isFunction( options.success ) ) {
options.success( this );
}
}
}
// IMAGE
if ( isImage ) {
$('<img/>').load(function(){
var resize = $.resize(this.width, this.height);
$(this).css({
width : resize.width,
height : resize.height
});
$( element ).html(this);
center('#jOverlayContent');
$('#jOverlayLoading').fadeOut(500);
$('#jOverlayContent').show();
// Execute callback
if ( $.isFunction( options.success ) ) {
options.success( this );
}
}).error(function(){
alert('Image ('+options.url+') not found.');
$.closeOverlay();
}).attr({'src' : options.url, 'alt' : options.url});
}
// AJAX
if ( options.url && !isImage ) {
$.ajax({
type: options.method,
data: options.data,
url: options.url,
success: function(responseText) {
$('#jOverlayLoading').fadeOut(500);
$( element ).html(responseText).show();
center('#jOverlayContent');
// Execute callback
if ($.isFunction( options.success )) {
options.success(responseText);
}
},
error : function() {
alert('URL ('+options.url+') not found.');
$.closeOverlay();
}
});
}
// :(
if ( isIE6 ) {
// Window scroll
$(window).scroll(function(){
center('#jOverlayContent');
});
// Window resize
$(window).resize(function(){
$('#jOverlay').css({
width: $(window).width() + 'px',
height: $(document).height() + 'px'
});
center('#jOverlayContent');
});
}
// Press ESC to close
$(document).keydown(function(event){
if (event.keyCode == 27) {
$.closeOverlay();
}
});
// Click to close
if ( options.bgClickToClose ) {
$('#jOverlay').click($.closeOverlay);
}
// Timeout (auto-close)
// time in millis to wait before auto-close
// set to 0 to disable
if ( Number(options.timeout) > 0 ) {
jOverlayTimer = setTimeout( $.closeOverlay, Number(options.timeout) );
}
// ADD CSS
$('#jOverlayContent').css(options.css || {});
};
// Resizing large images - orginal by Christian Montoya.
// Edited by - Cody Lindley (http://www.codylindley.com) (Thickbox 3.1)
$.resize = function(imageWidth, imageHeight) {
var x = $(window).width() - 150;
var y = $(window).height() - 150;
if (imageWidth > x) {
imageHeight = imageHeight * (x / imageWidth);
imageWidth = x;
if (imageHeight > y) {
imageWidth = imageWidth * (y / imageHeight);
imageHeight = y;
}
} else if (imageHeight > y) {
imageWidth = imageWidth * (y / imageHeight);
imageHeight = y;
if (imageWidth > x) {
imageHeight = imageHeight * (x / imageWidth);
imageWidth = x;
}
}
return {width:imageWidth, height:imageHeight};
};
// Centered Element
$.center = function(element) {
var element = $(element);
var elemWidth = element.width();
element.css({
width : elemWidth + 'px',
marginLeft : '-' + (elemWidth / 2) + 'px',
marginTop : '-' + element.height() / 2 + 'px',
height : 'auto',
top : !isIE6 ? '50%' : $(window).scrollTop() + ($(window).height() / 2) + 'px',
left : '50%'
});
};
// Options default
$.fn.jOverlay.options = {
method : 'GET',
data : '',
url : '',
color : '#000',
opacity : '0.6',
zIndex : 9999,
center : true,
imgLoading : '',
bgClickToClose : true,
success : null,
timeout : 0,
autoHide : true,
css : {}
};
// Close
$.closeOverlay = function() {
if (isIE6) { $("select").show(); }
if ( JOVERLAY_ELEMENT_PREV !== null ) {
if ( JOVERLAY_ELEMENT_PREV !== null ) {
var element = $('#jOverlayContent').children();
JOVERLAY_ELEMENT_PREV.after( element.css('display', element.attr('display') ) );
element.removeAttr('display');
}
}
$('#jOverlayLoading, #jOverlayContent, #jOverlay').remove();
};
})(jQuery);

View File

@ -1,6 +1,7 @@
/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html /* 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) * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* Version: 0.6 (Abr 23, 2009) * Version: 0.7.1 (JUN 15, 2009
* Requires: jQuery 1.3+ * Requires: jQuery 1.3+
* Packer from http://dean.edwards.name/packer/
*/ */
(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); (function($){var g=$.browser.msie&&$.browser.version==6.0;var h=null;var i=null;$.fn.jOverlay=function(b){if($('#jOverlay').length){$.closeOverlay()}i=null;if(h!==null){clearTimeout(h)}var b=$.extend({},$.fn.jOverlay.options,b);function center(a){if(b.center){$.center(a)}}var c=this.is('*')?this:'#jOverlayContent';var d=g?'absolute':'fixed';var e=/([^\/\\]+)\.(png|gif|jpeg|jpg|bmp)$/i.test(b.url);var f=b.imgLoading?"<img id='jOverlayLoading' src='"+b.imgLoading+"' style='position:"+d+"; z-index:"+(b.zIndex+9)+";'/>":'';$('body').prepend(f+"<div id='jOverlay' />"+"<div id='jOverlayContent' style='position:"+d+"; z-index:"+(b.zIndex+5)+"; display:none;'/>");$('#jOverlayLoading').load(function(){center(this)});if(g){$('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:!g?'100%':$(window).width()+'px',height:!g?'100%':$(document).height()+'px'}).show();if(this.is('*')){i=this.prev();$('#jOverlayContent').html(this.show().attr('display',b.autoHide?'none':this.css('display')));if(!e){center('#jOverlayContent');$('#jOverlayContent').show();if(!b.url&&$.isFunction(b.success)){b.success(this)}}}if(e){$('<img/>').load(function(){var a=$.resize(this.width,this.height);$(this).css({width:a.width,height:a.height});$(c).html(this);center('#jOverlayContent');$('#jOverlayLoading').fadeOut(500);$('#jOverlayContent').show();if($.isFunction(b.success)){b.success(this)}}).error(function(){alert('Image ('+b.url+') not found.');$.closeOverlay()}).attr({'src':b.url,'alt':b.url})}if(b.url&&!e){$.ajax({type:b.method,data:b.data,url:b.url,success:function(a){$('#jOverlayLoading').fadeOut(500);$(c).html(a).show();center('#jOverlayContent');if($.isFunction(b.success)){b.success(a)}},error:function(){alert('URL ('+b.url+') not found.');$.closeOverlay()}})}if(g){$(window).scroll(function(){center('#jOverlayContent')});$(window).resize(function(){$('#jOverlay').css({width:$(window).width()+'px',height:$(document).height()+'px'});center('#jOverlayContent')})}$(document).keydown(function(a){if(a.keyCode==27){$.closeOverlay()}});if(b.bgClickToClose){$('#jOverlay').click($.closeOverlay)}if(Number(b.timeout)>0){jOverlayTimer=setTimeout($.closeOverlay,Number(b.timeout))}$('#jOverlayContent').css(b.css||{})};$.resize=function(a,b){var x=$(window).width()-150;var y=$(window).height()-150;if(a>x){b=b*(x/a);a=x;if(b>y){a=a*(y/b);b=y}}else if(b>y){a=a*(y/b);b=y;if(a>x){b=b*(x/a);a=x}}return{width:a,height:b}};$.center=function(a){var a=$(a);var b=a.width();a.css({width:b+'px',marginLeft:'-'+(b/2)+'px',marginTop:'-'+a.height()/2+'px',height:'auto',top:!g?'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,autoHide:true,css:{}};$.closeOverlay=function(){if(g){$("select").show()}if(i!==null){if(i!==null){var a=$('#jOverlayContent').children();i.after(a.css('display',a.attr('display')));a.removeAttr('display')}}$('#jOverlayLoading, #jOverlayContent, #jOverlay').remove()}})(jQuery);

View File

@ -272,18 +272,20 @@ function NoticeAttachments() {
color : '#000', color : '#000',
opacity : '0.6', opacity : '0.6',
zIndex : 99, zIndex : 99,
center : true, center : false,
imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
bgClickToClose : true, bgClickToClose : true,
success : function() { success : function() {
$('#jOverlayContent').append('<button>&#215;</button>'); $('#jOverlayContent').append('<button>&#215;</button>');
$('#jOverlayContent button').click($.closeOverlay); $('#jOverlayContent button').click($.closeOverlay);
}, },
timeout : 0 timeout : 0,
autoHide : true,
css : {'max-width':'502px', 'top':'22.5%', 'left':'32.5%'}
}; };
$('#content .notice a.attachment').click(function() { $('#content .notice a.attachment').click(function() {
$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
return false; return false;
}); });
@ -296,7 +298,7 @@ function NoticeAttachments() {
if (anchor.children('img').length == 0) { if (anchor.children('img').length == 0) {
t = setTimeout(function() { t = setTimeout(function() {
$.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
anchor.append(data); anchor.append(data);
}); });
}, 500); }, 500);

View File

@ -40,7 +40,7 @@ class ShortUrlApi
} }
private function is_long($url) { private function is_long($url) {
return strlen($url) >= $this->long_limit; return strlen($url) >= common_config('site', 'shorturllength');
} }
protected function http_post($data) { protected function http_post($data) {

View File

@ -247,7 +247,6 @@ class Action extends HTMLOutputter // lawsuit
'src' => common_path('js/jquery.joverlay.min.js')), 'src' => common_path('js/jquery.joverlay.min.js')),
' '); ' ');
Event::handle('EndShowJQueryScripts', array($this)); Event::handle('EndShowJQueryScripts', array($this));
} }
if (Event::handle('StartShowLaconicaScripts', array($this))) { if (Event::handle('StartShowLaconicaScripts', array($this))) {
@ -422,11 +421,13 @@ class Action extends HTMLOutputter // lawsuit
$this->menuItem(common_local_url('smssettings'), $this->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
} }
if (common_config('invite', 'enabled')) {
$this->menuItem(common_local_url('invite'), $this->menuItem(common_local_url('invite'),
_('Invite'), _('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'), sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')), common_config('site', 'name')),
false, 'nav_invitecontact'); false, 'nav_invitecontact');
}
$this->menuItem(common_local_url('logout'), $this->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout'); _('Logout'), _('Logout from the site'), false, 'nav_logout');
} }
@ -964,12 +965,16 @@ class Action extends HTMLOutputter // lawsuit
$action = $this->trimmed('action'); $action = $this->trimmed('action');
$args = $this->args; $args = $this->args;
unset($args['action']); unset($args['action']);
if (common_config('site', 'fancy')) {
unset($args['p']);
}
if (array_key_exists('submit', $args)) { if (array_key_exists('submit', $args)) {
unset($args['submit']); unset($args['submit']);
} }
foreach (array_keys($_COOKIE) as $cookie) { foreach (array_keys($_COOKIE) as $cookie) {
unset($args[$cookie]); unset($args[$cookie]);
} }
return common_local_url($action, $args); return common_local_url($action, $args);
} }

View File

@ -120,6 +120,7 @@ $config =
'private' => false, 'private' => false,
'ssl' => 'never', 'ssl' => 'never',
'sslserver' => null, 'sslserver' => null,
'shorturllength' => 30,
'dupelimit' => 60), # default for same person saying the same thing 'dupelimit' => 60), # default for same person saying the same thing
'syslog' => 'syslog' =>
array('appname' => 'laconica', # for syslog array('appname' => 'laconica', # for syslog
@ -175,6 +176,8 @@ $config =
'host' => null, # only set if != server 'host' => null, # only set if != server
'debug' => false, # print extra debug info 'debug' => false, # print extra debug info
'public' => array()), # JIDs of users who want to receive the public stream 'public' => array()), # JIDs of users who want to receive the public stream
'invite' =>
array('enabled' => true),
'sphinx' => 'sphinx' =>
array('enabled' => false, array('enabled' => false,
'server' => 'localhost', 'server' => 'localhost',

View File

@ -23,6 +23,13 @@ if (!defined('LACONICA')) {
class Daemon class Daemon
{ {
var $daemonize = true;
function __construct($daemonize = true)
{
$this->daemonize = $daemonize;
}
function name() function name()
{ {
return null; return null;
@ -129,13 +136,17 @@ class Daemon
common_log(LOG_INFO, $this->name() . ' already running. Exiting.'); common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
exit(0); exit(0);
} }
if ($this->background()) {
if ($this->daemonize) {
common_log(LOG_INFO, 'Backgrounding daemon "'.$this->name().'"');
$this->background();
}
$this->writePidFile(); $this->writePidFile();
$this->changeUser(); $this->changeUser();
$this->run(); $this->run();
$this->clearPidFile(); $this->clearPidFile();
} }
}
function run() function run()
{ {

View File

@ -35,6 +35,20 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR . '/lib/accountsettingsaction.php'; require_once INSTALLDIR . '/lib/accountsettingsaction.php';
require_once INSTALLDIR . '/lib/webcolor.php'; require_once INSTALLDIR . '/lib/webcolor.php';
/**
* Base class for setting a user or group design
*
* Shows the design setting form and also handles some things like saving
* background images, and fetching a default design
*
* @category Settings
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @author Sarven Capadisli <csarven@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 DesignSettingsAction extends AccountSettingsAction class DesignSettingsAction extends AccountSettingsAction
{ {
@ -63,6 +77,14 @@ class DesignSettingsAction extends AccountSettingsAction
'with a background image and a colour palette of your choice.'); 'with a background image and a colour palette of your choice.');
} }
/**
* Shows the design settings form
*
* @param Design $design a working design to show
*
* @return nothing
*/
function showDesignForm($design) function showDesignForm($design)
{ {
@ -94,7 +116,8 @@ class DesignSettingsAction extends AccountSettingsAction
if (!empty($design->backgroundimage)) { if (!empty($design->backgroundimage)) {
$this->elementStart('li', array('id' => 'design_background-image_onoff')); $this->elementStart('li', array('id' =>
'design_background-image_onoff'));
$this->element('img', array('src' => $this->element('img', array('src' =>
Design::url($design->backgroundimage))); Design::url($design->backgroundimage)));
@ -136,7 +159,7 @@ class DesignSettingsAction extends AccountSettingsAction
$this->elementStart('li'); $this->elementStart('li');
$this->checkbox('design_background-image_repeat', $this->checkbox('design_background-image_repeat',
_('Tile background image'), _('Tile background image'),
($design->disposition & BACKGROUND_TILE) ? true : false ); ($design->disposition & BACKGROUND_TILE) ? true : false);
$this->elementEnd('li'); $this->elementEnd('li');
} }
@ -212,17 +235,18 @@ class DesignSettingsAction extends AccountSettingsAction
'maxlength' => '7', 'maxlength' => '7',
'size' => '7', 'size' => '7',
'value' => '#' . $lcolor->hexValue())); 'value' => '#' . $lcolor->hexValue()));
$this->elementEnd('li'); $this->elementEnd('li');
} catch (WebColorException $e) { } catch (WebColorException $e) {
common_log(LOG_ERR, 'Bad color values in design ID: ' . common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id);
$design->id);
} }
$this->elementEnd('ul'); $this->elementEnd('ul');
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->submit('defaults', _('Use defaults'), 'submit form_action-default',
'defaults', _('Restore default designs'));
$this->element('input', array('id' => 'settings_design_reset', $this->element('input', array('id' => 'settings_design_reset',
'type' => 'reset', 'type' => 'reset',
'value' => 'Reset', 'value' => 'Reset',
@ -271,8 +295,8 @@ class DesignSettingsAction extends AccountSettingsAction
if ($this->arg('save')) { if ($this->arg('save')) {
$this->saveDesign(); $this->saveDesign();
} else if ($this->arg('reset')) { } else if ($this->arg('defaults')) {
$this->resetDesign(); $this->restoreDefaults();
} else { } else {
$this->showForm(_('Unexpected form submission.')); $this->showForm(_('Unexpected form submission.'));
} }
@ -316,7 +340,7 @@ class DesignSettingsAction extends AccountSettingsAction
} }
/** /**
* Get a default user design * Get a default design
* *
* @return Design design * @return Design design
*/ */
@ -358,7 +382,16 @@ class DesignSettingsAction extends AccountSettingsAction
return $design; return $design;
} }
function saveBackgroundImage($design) { /**
* Save the background image, if any, and set its disposition
*
* @param Design $design a working design to attach the img to
*
* @return nothing
*/
function saveBackgroundImage($design)
{
// Now that we have a Design ID we can add a file to the design. // Now that we have a Design ID we can add a file to the design.
// XXX: This is an additional DB hit, but figured having the image // XXX: This is an additional DB hit, but figured having the image
@ -386,7 +419,14 @@ class DesignSettingsAction extends AccountSettingsAction
move_uploaded_file($imagefile->filepath, $filepath); move_uploaded_file($imagefile->filepath, $filepath);
// delete any old backround img laying around
if (isset($design->backgroundimage)) {
@unlink(Design::path($design->backgroundimage));
}
$original = clone($design); $original = clone($design);
$design->backgroundimage = $filename; $design->backgroundimage = $filename;
// default to on, no tile // default to on, no tile
@ -403,4 +443,35 @@ class DesignSettingsAction extends AccountSettingsAction
} }
} }
/**
* Restore the user or group design to system defaults
*
* @return nothing
*/
function restoreDefaults()
{
$design = $this->getWorkingDesign();
$default = $this->defaultDesign();
$original = clone($design);
$design->backgroundcolor = $default->backgroundcolor;
$design->contentcolor = $default->contentcolor;
$design->sidebarcolor = $default->sidebarcolor;
$design->textcolor = $default->textcolor;
$design->linkcolor = $default->linkcolor;
$design->setDisposition(false, true, false);
$result = $design->update($original);
if ($result === false) {
common_log_db_error($design, 'UPDATE', __FILE__);
$this->showForm(_('Couldn\'t update your design.'));
return;
}
$this->showForm(_('Design defaults restored.'), true);
}
} }

View File

@ -213,12 +213,14 @@ class FacebookAction extends Action
array('href' => 'index.php', 'title' => _('Home')), _('Home')); array('href' => 'index.php', 'title' => _('Home')), _('Home'));
$this->elementEnd('li'); $this->elementEnd('li');
if (common_config('invite', 'enabled')) {
$this->elementStart('li', $this->elementStart('li',
array('class' => array('class' =>
($this->action == 'facebookinvite') ? 'current' : 'facebook_invite')); ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite'));
$this->element('a', $this->element('a',
array('href' => 'invite.php', 'title' => _('Invite')), _('Invite')); array('href' => 'invite.php', 'title' => _('Invite')), _('Invite'));
$this->elementEnd('li'); $this->elementEnd('li');
}
$this->elementStart('li', $this->elementStart('li',
array('class' => array('class' =>

View File

@ -130,30 +130,46 @@ class GroupEditForm extends Form
function formData() function formData()
{ {
if ($this->group) {
$id = $this->group->id;
$nickname = $this->group->nickname;
$fullname = $this->group->fullname;
$homepage = $this->group->homepage;
$description = $this->group->description;
$location = $this->group->location;
} else {
$id = '';
$nickname = '';
$fullname = '';
$homepage = '';
$description = '';
$location = '';
}
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->hidden('groupid', $this->group->id); $this->out->hidden('groupid', $id);
$this->out->input('nickname', _('Nickname'), $this->out->input('nickname', _('Nickname'),
($this->out->arg('nickname')) ? $this->out->arg('nickname') : $this->group->nickname, ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
_('1-64 lowercase letters or numbers, no punctuation or spaces')); _('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->input('fullname', _('Full name'), $this->out->input('fullname', _('Full name'),
($this->out->arg('fullname')) ? $this->out->arg('fullname') : $this->group->fullname); ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->input('homepage', _('Homepage'), $this->out->input('homepage', _('Homepage'),
($this->out->arg('homepage')) ? $this->out->arg('homepage') : $this->group->homepage, ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
_('URL of the homepage or blog of the group or topic')); _('URL of the homepage or blog of the group or topic'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->textarea('description', _('Description'), $this->out->textarea('description', _('Description'),
($this->out->arg('description')) ? $this->out->arg('description') : $this->group->description, ($this->out->arg('description')) ? $this->out->arg('description') : $description,
_('Describe the group or topic in 140 chars')); _('Describe the group or topic in 140 chars'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->input('location', _('Location'), $this->out->input('location', _('Location'),
($this->out->arg('location')) ? $this->out->arg('location') : $this->group->location, ($this->out->arg('location')) ? $this->out->arg('location') : $location,
_('Location for the group, if any, like "City, State (or Region), Country"')); _('Location for the group, if any, like "City, State (or Region), Country"'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
if (common_config('group', 'maxaliases') > 0) { if (common_config('group', 'maxaliases') > 0) {

View File

@ -89,6 +89,7 @@ class JSONSearchResultsList
function show() function show()
{ {
$cnt = 0; $cnt = 0;
$this->max_id = 0;
$time_start = microtime(true); $time_start = microtime(true);

View File

@ -108,7 +108,7 @@ function get_all_languages() {
'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'), 'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'),
'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'), 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'), 'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'), 'en' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
'es' => array('q' => 1, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'), 'es' => array('q' => 1, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'),
'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'), 'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
'fr-fr' => array('q' => 1, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'), 'fr-fr' => array('q' => 1, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),

View File

@ -108,7 +108,9 @@ class ProfileAction extends OwnerDesignAction
$this->element('h2', null, _('Subscriptions')); $this->element('h2', null, _('Subscriptions'));
if ($profile) { $cnt = 0;
if (!empty($profile)) {
$pml = new ProfileMiniList($profile, $this); $pml = new ProfileMiniList($profile, $this);
$cnt = $pml->show(); $cnt = $pml->show();
if ($cnt == 0) { if ($cnt == 0) {
@ -137,7 +139,9 @@ class ProfileAction extends OwnerDesignAction
$this->element('h2', null, _('Subscribers')); $this->element('h2', null, _('Subscribers'));
if ($profile) { $cnt = 0;
if (!empty($profile)) {
$pml = new ProfileMiniList($profile, $this); $pml = new ProfileMiniList($profile, $this);
$cnt = $pml->show(); $cnt = $pml->show();
if ($cnt == 0) { if ($cnt == 0) {

View File

@ -47,6 +47,7 @@ define('PROFILES_PER_MINILIST', 27);
class ProfileMiniList extends ProfileList class ProfileMiniList extends ProfileList
{ {
function startList() function startList()
{ {
$this->out->elementStart('ul', 'entities users xoxo'); $this->out->elementStart('ul', 'entities users xoxo');
@ -56,6 +57,23 @@ class ProfileMiniList extends ProfileList
{ {
return new ProfileMiniListItem($profile, $this->action); return new ProfileMiniListItem($profile, $this->action);
} }
function showProfiles()
{
$cnt = 0;
while ($this->profile->fetch()) {
$cnt++;
if ($cnt > PROFILES_PER_MINILIST) {
break;
}
$pli = $this->newListItem($this->profile);
$pli->show();
}
return $cnt;
}
} }
class ProfileMiniListItem extends ProfileListItem class ProfileMiniListItem extends ProfileListItem

View File

@ -31,8 +31,10 @@ class QueueHandler extends Daemon
{ {
var $_id = 'generic'; var $_id = 'generic';
function QueueHandler($id=null) function __construct($id=null, $daemonize=true)
{ {
parent::__construct($daemonize);
if ($id) { if ($id) {
$this->set_id($id); $this->set_id($id);
} }

View File

@ -100,7 +100,7 @@ class SubGroupNav extends Widget
$this->user->nickname), $this->user->nickname),
$action == 'usergroups', $action == 'usergroups',
'nav_usergroups'); 'nav_usergroups');
if (!is_null($cur) && $this->user->id === $cur->id) { if (common_config('invite', 'enabled') && !is_null($cur) && $this->user->id === $cur->id) {
$this->out->menuItem(common_local_url('invite'), $this->out->menuItem(common_local_url('invite'),
_('Invite'), _('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'), sprintf(_('Invite friends and colleagues to join you on %s'),

View File

@ -114,7 +114,7 @@ function common_check_user($nickname, $password)
return false; return false;
} }
$user = User::staticGet('nickname', $nickname); $user = User::staticGet('nickname', $nickname);
if (is_null($user)) { if (is_null($user) || $user === false) {
return false; return false;
} else { } else {
if (0 == strcmp(common_munge_password($password, $user->id), if (0 == strcmp(common_munge_password($password, $user->id),
@ -145,7 +145,6 @@ function common_ensure_session()
} }
if (!common_have_session()) { if (!common_have_session()) {
if (common_config('sessions', 'handle')) { if (common_config('sessions', 'handle')) {
common_log(LOG_INFO, "Using our own session handler");
Session::setSaveHandler(); Session::setSaveHandler();
} }
@session_start(); @session_start();
@ -500,17 +499,19 @@ function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying // It comes in special'd, so we unspecial it before passing to the stringifying
// functions // functions
$url = htmlspecialchars_decode($url); $url = htmlspecialchars_decode($url);
$display = File_redirection::_canonUrl($url);
$canon = File_redirection::_canonUrl($url);
$longurl_data = File_redirection::where($url); $longurl_data = File_redirection::where($url);
if (is_array($longurl_data)) { if (is_array($longurl_data)) {
$longurl = $longurl_data['url']; $longurl = $longurl_data['url'];
} elseif (is_string($longurl_data)) { } elseif (is_string($longurl_data)) {
$longurl = $longurl_data; $longurl = $longurl_data;
} else { } else {
die('impossible to linkify'); throw new ServerException("Can't linkify url '$url'");
} }
$attrs = array('href' => $longurl, 'rel' => 'external'); $attrs = array('href' => $canon, 'rel' => 'external');
$is_attachment = false; $is_attachment = false;
$attachment_id = null; $attachment_id = null;
@ -528,13 +529,13 @@ function common_linkify($url) {
} }
} }
// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID' // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
// where ID is the id of the attachment for the given URL. // where ID is the id of the attachment for the given URL.
// //
// we need a better test telling what can be shown as an attachment // we need a better test telling what can be shown as an attachment
// we're currently picking up oembeds only. // we're currently picking up oembeds only.
// I think the best option is another file_view table in the db // I think the best option is another file_view table in the db
// and associated dbobject. // and associated dbobject.
$query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'"; $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
$file = new File; $file = new File;
@ -564,7 +565,7 @@ function common_linkify($url) {
$attrs['id'] = "attachment-{$attachment_id}"; $attrs['id'] = "attachment-{$attachment_id}";
} }
return XMLStringer::estring('a', $attrs, $display); return XMLStringer::estring('a', $attrs, $url);
} }
function common_shorten_links($text) function common_shorten_links($text)
@ -982,15 +983,20 @@ function common_ensure_syslog()
} }
} }
function common_log_line($priority, $msg)
{
static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
return date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
}
function common_log($priority, $msg, $filename=null) function common_log($priority, $msg, $filename=null)
{ {
$logfile = common_config('site', 'logfile'); $logfile = common_config('site', 'logfile');
if ($logfile) { if ($logfile) {
$log = fopen($logfile, "a"); $log = fopen($logfile, "a");
if ($log) { if ($log) {
static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR', $output = common_log_line($priority, $msg);
'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
$output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
fwrite($log, $output); fwrite($log, $output);
fclose($log); fclose($log);
} }
@ -1221,18 +1227,39 @@ function common_canonical_sms($sms)
function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext) function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
{ {
switch ($errno) { switch ($errno) {
case E_ERROR:
case E_COMPILE_ERROR:
case E_CORE_ERROR:
case E_USER_ERROR: case E_USER_ERROR:
common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)"); case E_PARSE:
exit(1); case E_RECOVERABLE_ERROR:
common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline) [ABORT]");
die();
break; break;
case E_WARNING:
case E_COMPILE_WARNING:
case E_CORE_WARNING:
case E_USER_WARNING: case E_USER_WARNING:
common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)"); common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
break; break;
case E_NOTICE:
case E_USER_NOTICE: case E_USER_NOTICE:
common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)"); common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
break; break;
case E_STRICT:
case E_DEPRECATED:
case E_USER_DEPRECATED:
// XXX: config variable to log this stuff, too
break;
default:
common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline) [UNKNOWN LEVEL, die()'ing]");
die();
break;
} }
// FIXME: show error page if we're on the Web // FIXME: show error page if we're on the Web

View File

@ -66,7 +66,7 @@ class FBConnectauthAction extends Action
// User is already logged in. Does she already have a linked Facebook acct? // User is already logged in. Does she already have a linked Facebook acct?
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if ($flink) { if (!empty($flink)) {
// User already has a linked Facebook account and shouldn't be here // User already has a linked Facebook account and shouldn't be here
common_debug('There is already a local user (' . $flink->user_id . common_debug('There is already a local user (' . $flink->user_id .
@ -337,7 +337,7 @@ class FBConnectauthAction extends Action
if ($flink) { if ($flink) {
$user = $flink->getUser(); $user = $flink->getUser();
if ($user) { if (!empty($user)) {
common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)"); common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");

View File

@ -10,22 +10,21 @@
#site_nav_global_primary #nav_fb { #site_nav_global_primary #nav_fb {
position:relative; position:relative;
margin-left:18px; margin-left:18px;
margin-right:-7px;
} }
#nav_fb .fb_profile_pic_rendered img { #nav_fb #fbc_profile-pic {
position:relative; position:absolute;
top:3px; top:-3px;
left:0; left:-18px;
display:inline; display:inline;
border:1px solid #3B5998; border:1px solid #3B5998;
padding:1px; padding:1px;
} }
#nav_fb img { #nav_fb #fb_favicon {
position:absolute; position:absolute;
top:-13px; top:-13px;
left:-11px; left:-25px;
display:inline; display:inline;
} }

View File

@ -69,6 +69,9 @@ class FBConnectPlugin extends Plugin
// Add in xmlns:fb // Add in xmlns:fb
function onStartShowHTML($action) function onStartShowHTML($action)
{ {
if ($this->reqFbScripts($action)) {
// XXX: Horrible hack to make Safari, FF2, and Chrome work with // XXX: Horrible hack to make Safari, FF2, and Chrome work with
// Facebook Connect. These browser cannot use Facebook's // Facebook Connect. These browser cannot use Facebook's
// DOM parsing routines unless the mime type of the page is // DOM parsing routines unless the mime type of the page is
@ -92,12 +95,19 @@ class FBConnectPlugin extends Plugin
'lang' => $language)); 'lang' => $language));
return false; return false;
} else {
return true;
}
} }
// Note: this script needs to appear in the <body> // Note: this script needs to appear in the <body>
function onStartShowHeader($action) function onStartShowHeader($action)
{ {
if ($this->reqFbScripts($action)) {
$apikey = common_config('facebook', 'apikey'); $apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect'); $plugin_path = common_path('plugins/FBConnect');
@ -129,35 +139,83 @@ class FBConnectPlugin extends Plugin
$action->raw($html); $action->raw($html);
} }
}
// Note: this script needs to appear as close as possible to </body> // Note: this script needs to appear as close as possible to </body>
function onEndShowFooter($action) function onEndShowFooter($action)
{ {
if ($this->reqFbScripts($action)) {
$action->element('script', $action->element('script',
array('type' => 'text/javascript', array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'), 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
''); '');
} }
}
function onEndShowLaconicaStyles($action) function onEndShowLaconicaStyles($action)
{ {
if ($this->reqFbScripts($action)) {
$action->element('link', array('rel' => 'stylesheet', $action->element('link', array('rel' => 'stylesheet',
'type' => 'text/css', 'type' => 'text/css',
'href' => common_path('plugins/FBConnect/FBConnectPlugin.css'))); 'href' => common_path('plugins/FBConnect/FBConnectPlugin.css')));
} }
}
function onStartPrimaryNav($action) /**
* Does the Action we're plugged into require the FB Scripts? We only
* want to output FB namespace, scripts, CSS, etc. on the pages that
* really need them.
*
* @param Action the action in question
*
* @return boolean true
*/
function reqFbScripts($action) {
// If you're logged in w/FB Connect, you always need the FB stuff
$fbuid = $this->loggedIn();
if (!empty($fbuid)) {
return true;
}
// List of actions that require FB stuff
$needy = array('FBConnectLoginAction',
'FBConnectauthAction',
'FBConnectSettingsAction');
if (in_array(get_class($action), $needy)) {
return true;
}
return false;
}
/**
* Is the user currently logged in with FB Connect?
*
* @return mixed $fbuid the Facebook ID of the logged in user, or null
*/
function loggedIn()
{ {
$user = common_current_user(); $user = common_current_user();
if ($user) { if (!empty($user)) {
$flink = Foreign_link::getByUserId($user->id, $flink = Foreign_link::getByUserId($user->id,
FACEBOOK_CONNECT_SERVICE); FACEBOOK_CONNECT_SERVICE);
$fbuid = 0; $fbuid = 0;
if ($flink) { if (!empty($flink)) {
try { try {
@ -170,24 +228,48 @@ class FBConnectPlugin extends Plugin
$e->getMessage()); $e->getMessage());
} }
// Display Facebook Logged in indicator w/Facebook favicon
if ($fbuid > 0) { if ($fbuid > 0) {
return $fbuid;
}
}
}
return null;
}
function onStartPrimaryNav($action)
{
$user = common_current_user();
if (!empty($user)) {
$fbuid = $this->loggedIn();
if (!empty($fbuid)) {
/* Default FB silhouette pic for FB users who haven't
uploaded a profile pic yet. */
$silhouetteUrl =
'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
$url = $this->getProfilePicURL($fbuid);
$action->elementStart('li', array('id' => 'nav_fb')); $action->elementStart('li', array('id' => 'nav_fb'));
$action->elementStart('fb:profile-pic', array('uid' => $flink->foreign_id,
'linked' => 'false',
'width' => 16,
'height' => 16));
$action->elementEnd('fb:profile-pic');
$iconurl = common_path('/plugins/FBConnect/fbfavicon.ico'); $action->element('img', array('id' => 'fbc_profile-pic',
$action->element('img', array('src' => $iconurl)); 'src' => (!empty($url)) ? $url : $silhouetteUrl,
'alt' => 'Facebook Connect User',
'width' => '16'), '');
$iconurl = common_path('plugins/FBConnect/fbfavicon.ico');
$action->element('img', array('id' => 'fb_favicon',
'src' => $iconurl));
$action->elementEnd('li'); $action->elementEnd('li');
} }
}
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
@ -200,14 +282,16 @@ class FBConnectPlugin extends Plugin
$action->menuItem(common_local_url('smssettings'), $action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
} }
if (common_config('invite', 'enabled')) {
$action->menuItem(common_local_url('invite'), $action->menuItem(common_local_url('invite'),
_('Invite'), _('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'), sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')), common_config('site', 'name')),
false, 'nav_invitecontact'); false, 'nav_invitecontact');
}
// Need to override the Logout link to make it do FB stuff // Need to override the Logout link to make it do FB stuff
if ($flink && $fbuid > 0) { if (!empty($fbuid)) {
$logout_url = common_local_url('logout'); $logout_url = common_local_url('logout');
$title = _('Logout from the site'); $title = _('Logout from the site');
@ -256,7 +340,7 @@ class FBConnectPlugin extends Plugin
return false; return false;
} }
$connect_actions = array('SmssettingsAction', $connect_actions = array('SmssettingsAction', 'ImsettingsAction',
'TwittersettingsAction', 'FBConnectSettingsAction'); 'TwittersettingsAction', 'FBConnectSettingsAction');
if (in_array($action_name, $connect_actions)) { if (in_array($action_name, $connect_actions)) {
@ -270,23 +354,13 @@ class FBConnectPlugin extends Plugin
function onStartLogout($action) function onStartLogout($action)
{ {
$user = common_current_user();
$flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
$action->logout(); $action->logout();
$fbuid = $this->loggedIn();
if ($flink) { if (!empty($fbuid)) {
$facebook = getFacebook();
try { try {
$fbuid = $facebook->get_loggedin_user(); $facebook = getFacebook();
$facebook->expire_session();
if ($fbuid > 0) {
$facebook->logout(common_local_url('public'));
}
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' . common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
$e->getMessage()); $e->getMessage());
@ -296,4 +370,28 @@ class FBConnectPlugin extends Plugin
return true; return true;
} }
function getProfilePicURL($fbuid)
{
$facebook = getFacebook();
$url = null;
try {
$fqry = 'SELECT pic_square FROM user WHERE uid = %s';
$result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
if (!empty($result)) {
$url = $result[0]['pic_square'];
}
} catch (Exception $e) {
common_log(LOG_WARNING, "Facebook client failure requesting profile pic!");
}
return $url;
}
} }

View File

@ -63,14 +63,21 @@ if (isset($longoptions)) {
$parser = new Console_Getopt(); $parser = new Console_Getopt();
list($options, $args) = $parser->getopt($argv, $shortoptions, $longoptions); $result = $parser->getopt($argv, $shortoptions, $longoptions);
if (PEAR::isError($result)) {
print $result->getMessage()."\n";
exit(1);
} else {
list($options, $args) = $result;
}
function show_help() function show_help()
{ {
global $helptext; global $helptext;
$_default_help_text = <<<END_OF_DEFAULT $_default_help_text = <<<END_OF_DEFAULT
General options: General options:
-q --quiet Quiet (little output) -q --quiet Quiet (little output)
-v --verbose Verbose (lots of output) -v --verbose Verbose (lots of output)
@ -115,24 +122,53 @@ require_once INSTALLDIR . '/lib/common.php';
set_error_handler('common_error_handler'); set_error_handler('common_error_handler');
function have_option($str) function _make_matches($opt, $alt)
{
$matches = array();
if (strlen($opt) > 1 && 0 != strncmp($opt, '--', 2)) {
$matches[] = '--'.$opt;
} else {
$matches[] = $opt;
}
if (!empty($alt)) {
if (strlen($alt) > 1 && 0 != strncmp($alt, '--', 2)) {
$matches[] = '--'.$alt;
} else {
$matches[] = $alt;
}
}
return $matches;
}
function have_option($opt, $alt=null)
{ {
global $options; global $options;
$matches = _make_matches($opt, $alt);
foreach ($options as $option) { foreach ($options as $option) {
if ($option[0] == $str) { if (in_array($option[0], $matches)) {
return true; return true;
} }
} }
return false; return false;
} }
function get_option_value($str) function get_option_value($opt, $alt=null)
{ {
global $options; global $options;
$matches = _make_matches($opt, $alt);
foreach ($options as $option) { foreach ($options as $option) {
if ($option[0] == $str) { if (in_array($option[0], $matches)) {
return $option[1]; return $option[1];
} }
} }
return null; return null;
} }

View File

@ -24,22 +24,17 @@ require_once INSTALLDIR.'/scripts/commandline.inc';
common_log(LOG_INFO, 'Fixing up conversations.'); common_log(LOG_INFO, 'Fixing up conversations.');
$notice = new Notice(); $nid = new Notice();
$notice->whereAdd('conversation is null'); $nid->query('select id, reply_to from notice where conversation is null');
$notice->orderBy('id');
$cnt = $notice->find(); while ($nid->fetch()) {
print "Found $cnt notices.\n"; $cid = null;
while ($notice->fetch()) { $notice = new Notice();
print "$notice->id =>"; if (empty($nid->reply_to)) {
$cid = $nid->id;
$orig = clone($notice);
if (empty($notice->reply_to)) {
$notice->conversation = $notice->id;
} else { } else {
$reply = Notice::staticGet('id', $notice->reply_to); $reply = Notice::staticGet('id', $notice->reply_to);
@ -52,6 +47,9 @@ while ($notice->fetch()) {
} else { } else {
$notice->conversation = $reply->conversation; $notice->conversation = $reply->conversation;
} }
unset($reply);
$reply = null;
} }
print "$notice->conversation"; print "$notice->conversation";
@ -63,5 +61,10 @@ while ($notice->fetch()) {
continue; continue;
} }
$notice = null;
$orig = null;
unset($notice);
unset($orig);
print ".\n"; print ".\n";
} }

71
scripts/showcache.php Normal file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = "t:c:v:k:";
$helptext = <<<ENDOFHELP
USAGE: showcache.php <args>
shows the cached object based on the args
-t table Table to look up
-c column Column to look up, default "id"
-v value Value to look up
-k key Key to look up; other args are ignored
ENDOFHELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
$karg = get_option_value('k');
if (!empty($karg)) {
$k = common_cache_key($karg);
} else {
$table = get_option_value('t');
if (empty($table)) {
die("No table or key specified\n");
}
$column = get_option_value('c');
if (empty($column)) {
$column = 'id';
}
$value = get_option_value('v');
$k = Memcached_DataObject::cacheKey($table, $column, $value);
}
print "Checking key '$k'...\n";
$c = common_memcache();
if (empty($c)) {
die("Can't initialize cache object!\n");
}
$obj = $c->get($k);
if (empty($obj)) {
print "Empty.\n";
} else {
var_dump($obj);
print "\n";
}

View File

@ -25,9 +25,14 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('MAXCHILDREN', 2); define('MAXCHILDREN', 2);
define('POLL_INTERVAL', 60); // in seconds define('POLL_INTERVAL', 60); // in seconds
$shortoptions = 'i::';
$longoptions = array('id::');
$helptext = <<<END_OF_TRIM_HELP $helptext = <<<END_OF_TRIM_HELP
Batch script for retrieving Twitter messages from foreign service. Batch script for retrieving Twitter messages from foreign service.
-i --id Identity (default 'generic')
END_OF_TRIM_HELP; END_OF_TRIM_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc'; require_once INSTALLDIR.'/scripts/commandline.inc';
@ -64,7 +69,7 @@ class TwitterStatusFetcher extends Daemon
function name() function name()
{ {
return ('twitterstatusfetcher.generic'); return ('twitterstatusfetcher.'.$this->_id);
} }
/** /**
@ -625,6 +630,16 @@ class TwitterStatusFetcher extends Daemon
declare(ticks = 1); declare(ticks = 1);
$fetcher = new TwitterStatusFetcher(); if (have_option('i')) {
$id = get_option_value('i');
} else if (have_option('--id')) {
$id = get_option_value('--id');
} else if (count($args) > 0) {
$id = $args[0];
} else {
$id = null;
}
$fetcher = new TwitterStatusFetcher($id);
$fetcher->runOnce(); $fetcher->runOnce();

View File

@ -20,13 +20,14 @@
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i::'; $shortoptions = 'fi::';
$longoptions = array('id::'); $longoptions = array('id::', 'foreground');
$helptext = <<<END_OF_XMPP_HELP $helptext = <<<END_OF_XMPP_HELP
Daemon script for receiving new notices from Jabber users. Daemon script for receiving new notices from Jabber users.
-i --id Identity (default none) -i --id Identity (default none)
-f --foreground Stay in the foreground (default background)
END_OF_XMPP_HELP; END_OF_XMPP_HELP;
@ -42,8 +43,10 @@ require_once INSTALLDIR . '/lib/daemon.php';
class XMPPDaemon extends Daemon class XMPPDaemon extends Daemon
{ {
function XMPPDaemon($resource=null) function __construct($resource=null, $daemonize=true)
{ {
parent::__construct($daemonize);
static $attrs = array('server', 'port', 'user', 'password', 'host'); static $attrs = array('server', 'port', 'user', 'password', 'host');
foreach ($attrs as $attr) foreach ($attrs as $attr)
@ -62,7 +65,6 @@ class XMPPDaemon extends Daemon
function connect() function connect()
{ {
$connect_to = ($this->host) ? $this->host : $this->server; $connect_to = ($this->host) ? $this->host : $this->server;
$this->log(LOG_INFO, "Connecting to $connect_to on port $this->port"); $this->log(LOG_INFO, "Connecting to $connect_to on port $this->port");
@ -73,10 +75,17 @@ class XMPPDaemon extends Daemon
return false; return false;
} }
$this->log(LOG_INFO, "Connected");
$this->conn->setReconnectTimeout(600); $this->conn->setReconnectTimeout(600);
$this->log(LOG_INFO, "Sending initial presence.");
jabber_send_presence("Send me a message to post a notice", 'available', jabber_send_presence("Send me a message to post a notice", 'available',
null, 'available', 100); null, 'available', 100);
$this->log(LOG_INFO, "Done connecting.");
return !$this->conn->isDisconnected(); return !$this->conn->isDisconnected();
} }
@ -89,17 +98,23 @@ class XMPPDaemon extends Daemon
{ {
if ($this->connect()) { if ($this->connect()) {
$this->log(LOG_DEBUG, "Initializing stanza handlers.");
$this->conn->addEventHandler('message', 'handle_message', $this); $this->conn->addEventHandler('message', 'handle_message', $this);
$this->conn->addEventHandler('presence', 'handle_presence', $this); $this->conn->addEventHandler('presence', 'handle_presence', $this);
$this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
$this->log(LOG_DEBUG, "Beginning processing loop.");
$this->conn->process(); $this->conn->process();
} }
} }
function handle_reconnect(&$pl) function handle_reconnect(&$pl)
{ {
$this->log(LOG_DEBUG, "Got reconnection callback.");
$this->conn->processUntil('session_start'); $this->conn->processUntil('session_start');
$this->log(LOG_DEBUG, "Sending reconnection presence.");
$this->conn->presence('Send me a message to post a notice', 'available', null, 'available', 100); $this->conn->presence('Send me a message to post a notice', 'available', null, 'available', 100);
} }
@ -111,21 +126,27 @@ class XMPPDaemon extends Daemon
function handle_message(&$pl) function handle_message(&$pl)
{ {
$from = jabber_normalize_jid($pl['from']);
if ($pl['type'] != 'chat') { if ($pl['type'] != 'chat') {
return; $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from.");
}
if (mb_strlen($pl['body']) == 0) {
return; return;
} }
$from = jabber_normalize_jid($pl['from']); if (mb_strlen($pl['body']) == 0) {
$this->log(LOG_WARNING, "Ignoring message with empty body from $from.");
return;
}
# Forwarded from another daemon (probably a broadcaster) for # Forwarded from another daemon (probably a broadcaster) for
# us to handle # us to handle
if ($this->is_self($from)) { if ($this->is_self($from)) {
$this->log(LOG_INFO, "Got forwarded notice from self ($from).");
$from = $this->get_ofrom($pl); $from = $this->get_ofrom($pl);
$this->log(LOG_INFO, "Originally sent by $from.");
if (is_null($from) || $this->is_self($from)) { if (is_null($from) || $this->is_self($from)) {
$this->log(LOG_INFO, "Ignoring notice originally sent by $from.");
return; return;
} }
} }
@ -140,6 +161,7 @@ class XMPPDaemon extends Daemon
return; return;
} }
if ($this->handle_command($user, $pl['body'])) { if ($this->handle_command($user, $pl['body'])) {
$this->log(LOG_INFO, "Command messag by $from handled.");
return; return;
} else if ($this->is_autoreply($pl['body'])) { } else if ($this->is_autoreply($pl['body'])) {
$this->log(LOG_INFO, 'Ignoring auto reply from ' . $from); $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from);
@ -148,12 +170,20 @@ class XMPPDaemon extends Daemon
$this->log(LOG_INFO, 'Ignoring OTR from ' . $from); $this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
return; return;
} else if ($this->is_direct($pl['body'])) { } else if ($this->is_direct($pl['body'])) {
$this->log(LOG_INFO, 'Got a direct message ' . $from);
preg_match_all('/d[\ ]*([a-z0-9]{1,64})/', $pl['body'], $to); preg_match_all('/d[\ ]*([a-z0-9]{1,64})/', $pl['body'], $to);
$to = preg_replace('/^d([\ ])*/', '', $to[0][0]); $to = preg_replace('/^d([\ ])*/', '', $to[0][0]);
$body = preg_replace('/d[\ ]*('. $to .')[\ ]*/', '', $pl['body']); $body = preg_replace('/d[\ ]*('. $to .')[\ ]*/', '', $pl['body']);
$this->log(LOG_INFO, 'Direct message from '. $user->nickname . ' to ' . $to);
$this->add_direct($user, $body, $to, $from); $this->add_direct($user, $body, $to, $from);
} else { } else {
$this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
$this->add_notice($user, $pl); $this->add_notice($user, $pl);
} }
@ -261,6 +291,7 @@ class XMPPDaemon extends Daemon
$notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
if (is_string($notice)) { if (is_string($notice)) {
$this->log(LOG_ERR, $notice); $this->log(LOG_ERR, $notice);
$this->from_site($user->jabber, $notice);
return; return;
} }
common_broadcast_notice($notice); common_broadcast_notice($notice);
@ -307,7 +338,14 @@ class XMPPDaemon extends Daemon
function log($level, $msg) function log($level, $msg)
{ {
common_log($level, 'XMPPDaemon('.$this->resource.'): '.$msg); $text = 'XMPPDaemon('.$this->resource.'): '.$msg;
common_log($level, $text);
if (!$this->daemonize)
{
$line = common_log_line($level, $text);
echo $line;
echo "\n";
}
} }
function subscribed($to) function subscribed($to)
@ -323,16 +361,16 @@ if (common_config('xmpp','enabled')==false) {
exit(); exit();
} }
if (have_option('i')) { if (have_option('i', 'id')) {
$id = get_option_value('i'); $id = get_option_value('i', 'id');
} else if (have_option('--id')) {
$id = get_option_value('--id');
} else if (count($args) > 0) { } else if (count($args) > 0) {
$id = $args[0]; $id = $args[0];
} else { } else {
$id = null; $id = null;
} }
$daemon = new XMPPDaemon($id); $foreground = have_option('f', 'foreground');
$daemon = new XMPPDaemon($id, !$foreground);
$daemon->runOnce(); $daemon->runOnce();

View File

@ -206,7 +206,10 @@ border-radius:4px;
padding:0 7px; padding:0 7px;
} }
.form_settings input.form_action-default {
margin-right:11px;
}
.form_settings input.form_action-default,
.form_settings input.form_action-primary { .form_settings input.form_action-primary {
padding:0; padding:0;
} }
@ -270,7 +273,6 @@ clear:both;
margin-bottom:18px; margin-bottom:18px;
} }
#anon_notice { #anon_notice {
float:left; float:left;
width:43.2%; width:43.2%;
@ -285,7 +287,6 @@ font-size:1.1em;
font-weight:bold; font-weight:bold;
} }
#footer { #footer {
float:left; float:left;
width:64%; width:64%;
@ -588,16 +589,16 @@ font-weight:normal;
content: ")"; content: ")";
font-weight:normal; font-weight:normal;
} }
.entity_profile dt,
.entity_profile dt {
display:none;
}
.entity_profile h2 { .entity_profile h2 {
display:none; display:none;
} }
.entity_profile .role {
margin-left:11px;
font-style:italic;
}
/* entity_profile */ /* entity_profile */
/*entity_actions*/ /*entity_actions*/
.entity_actions { .entity_actions {
float:right; float:right;
@ -726,7 +727,6 @@ margin-bottom:0;
min-height:60px; min-height:60px;
} }
.profile .form_group_join legend, .profile .form_group_join legend,
.profile .form_group_leave legend, .profile .form_group_leave legend,
.profile .form_user_subscribe legend, .profile .form_user_subscribe legend,
@ -761,13 +761,11 @@ display:inline;
margin-right:11px; margin-right:11px;
} }
.profile .entity_profile .form_subscription_edit label { .profile .entity_profile .form_subscription_edit label {
font-weight:normal; font-weight:normal;
margin-right:11px; margin-right:11px;
} }
/* NOTICE */ /* NOTICE */
.notice, .notice,
.profile { .profile {
@ -790,7 +788,6 @@ width:95%;
float:left; float:left;
} }
/* NOTICES */ /* NOTICES */
#notices_primary { #notices_primary {
float:left; float:left;
@ -962,7 +959,6 @@ border:0;
padding:0; padding:0;
} }
.notice .attachment { .notice .attachment {
position:relative; position:relative;
padding-left:16px; padding-left:16px;
@ -1031,7 +1027,13 @@ border-radius:7px;
-moz-border-radius:7px; -moz-border-radius:7px;
-webkit-border-radius:7px; -webkit-border-radius:7px;
} }
#jOverlayContent #content img {
max-width:480px;
}
#jOverlayLoading {
top:22.5%;
left:40%;
}
#attachment_view #oembed_info { #attachment_view #oembed_info {
margin-top:11px; margin-top:11px;
} }
@ -1059,7 +1061,6 @@ margin-bottom:18px;
padding-left:20px; padding-left:20px;
} }
#filter_tags { #filter_tags {
margin-bottom:11px; margin-bottom:11px;
float:left; float:left;
@ -1105,8 +1106,6 @@ top:3px;
left:3px; left:3px;
} }
.pagination { .pagination {
float:left; float:left;
clear:both; clear:both;
@ -1152,7 +1151,6 @@ padding-right:30px;
} }
/* END: NOTICE */ /* END: NOTICE */
.hentry .entry-content p { .hentry .entry-content p {
margin-bottom:18px; margin-bottom:18px;
} }
@ -1169,7 +1167,6 @@ margin-bottom:18px;
margin-left:18px; margin-left:18px;
} }
/* TOP_POSTERS */ /* TOP_POSTERS */
.section tbody td { .section tbody td {
padding-right:18px; padding-right:18px;
@ -1197,7 +1194,6 @@ margin-right:0;
display:none; display:none;
} }
/* tagcloud */ /* tagcloud */
.tag-cloud { .tag-cloud {
list-style-type:none; list-style-type:none;
@ -1280,6 +1276,10 @@ clear:both;
margin-bottom:0; margin-bottom:0;
} }
#form_settings_design #settings_design_background-image img {
max-width:480px;
}
#form_settings_design #settings_design_color .form_data, #form_settings_design #settings_design_color .form_data,
#form_settings_design #color-picker { #form_settings_design #color-picker {
float:left; float:left;