summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README12
-rw-r--r--actions/api.php4
-rw-r--r--actions/attachment.php11
-rw-r--r--actions/conversation.php41
-rw-r--r--actions/file.php12
-rw-r--r--actions/groupdesignsettings.php31
-rw-r--r--actions/groupmembers.php9
-rw-r--r--actions/grouprss.php1
-rw-r--r--actions/newnotice.php26
-rw-r--r--actions/noticesearchrss.php13
-rw-r--r--actions/public.php6
-rw-r--r--actions/showgroup.php44
-rw-r--r--actions/sup.php4
-rw-r--r--actions/twitapisearchatom.php27
-rw-r--r--actions/twitapisearchjson.php9
-rw-r--r--actions/userdesignsettings.php107
-rw-r--r--classes/Design.php2
-rw-r--r--classes/Notice.php86
-rw-r--r--classes/Session.php129
-rw-r--r--classes/Status_network.php7
-rw-r--r--classes/User_group.php24
-rw-r--r--[-rwxr-xr-x]classes/laconica.ini9
-rw-r--r--db/laconica.sql11
-rw-r--r--extlib/Mail/mimeDecode.php849
-rw-r--r--extlib/XMPPHP/BOSH.php2
-rw-r--r--extlib/XMPPHP/XMLStream.php8
-rw-r--r--extlib/XMPPHP/XMPP.php13
-rw-r--r--index.php48
-rw-r--r--install.php6
-rw-r--r--js/jquery.joverlay.min.js5
-rw-r--r--js/userdesign.go.js3
-rw-r--r--js/util.js4
-rw-r--r--lib/common.php3
-rw-r--r--lib/daemon.php21
-rw-r--r--lib/designsettings.php111
-rw-r--r--lib/language.php2
-rw-r--r--lib/queuehandler.php5
-rw-r--r--lib/router.php4
-rw-r--r--lib/util.php108
-rw-r--r--plugins/FBConnect/FBConnectPlugin.php233
-rw-r--r--scripts/commandline.inc84
-rwxr-xr-xscripts/fixup_conversations.php31
-rw-r--r--scripts/showcache.php71
-rwxr-xr-xscripts/twitterstatusfetcher.php19
-rwxr-xr-xscripts/xmppdaemon.php62
-rw-r--r--theme/base/css/display.css36
46 files changed, 2021 insertions, 332 deletions
diff --git a/README b/README
index 2545465ce..e02fa02d3 100644
--- a/README
+++ b/README
@@ -1285,6 +1285,18 @@ type: type of search. Ignored if PostgreSQL or Sphinx are enabled. Can either
systems. We'll probably add another type sometime in the future,
with our own indexing system (maybe like MediaWiki's).
+sessions
+--------
+
+Session handling.
+
+handle: boolean. Whether we should register our own PHP session-handling
+ code (using the database and memcache if enabled). Defaults to false.
+ Setting this to true makes some sense on large or multi-server
+ sites, but it probably won't hurt for smaller ones, either.
+debug: whether to output debugging info for session storage. Can help
+ with weird session bugs, sometimes. Default false.
+
Troubleshooting
===============
diff --git a/actions/api.php b/actions/api.php
index 1fe5875ad..08f5fadad 100644
--- a/actions/api.php
+++ b/actions/api.php
@@ -67,7 +67,9 @@ class ApiAction extends Action
$this->process_command();
} else {
# basic authentication failed
- common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname.");
+ list($proxy, $ip) = common_client_ip();
+
+ common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
$this->show_basic_auth_error();
}
}
diff --git a/actions/attachment.php b/actions/attachment.php
index e4dc0e054..ee4cd9640 100644
--- a/actions/attachment.php
+++ b/actions/attachment.php
@@ -111,7 +111,16 @@ class AttachmentAction extends Action
function handle($args)
{
parent::handle($args);
- $this->showPage();
+
+ if (empty($this->attachment->filename)) {
+
+ // if it's not a local file, gtfo
+
+ common_redirect($this->attachment->url, 303);
+
+ } else {
+ $this->showPage();
+ }
}
/**
diff --git a/actions/conversation.php b/actions/conversation.php
index 654a670f5..79197da2d 100644
--- a/actions/conversation.php
+++ b/actions/conversation.php
@@ -107,17 +107,11 @@ class ConversationAction extends Action
function showContent()
{
- $offset = ($this->page-1) * NOTICES_PER_PAGE;
- $limit = NOTICES_PER_PAGE + 1;
-
- $notices = Notice::conversationStream($this->id, $offset, $limit);
+ $notices = Notice::conversationStream($this->id);
$ct = new ConversationTree($notices, $this);
$cnt = $ct->show();
-
- $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
- $this->page, 'conversation', array('id' => $this->id));
}
}
@@ -148,6 +142,25 @@ class ConversationTree extends NoticeList
{
$cnt = 0;
+ $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->table = array();
@@ -168,20 +181,6 @@ class ConversationTree extends NoticeList
$this->tree[$notice->reply_to] = array($notice->id);
}
}
-
- $this->out->elementStart('div', array('id' =>'notices_primary'));
- $this->out->element('h2', null, _('Notices'));
- $this->out->elementStart('ol', array('class' => 'notices xoxo'));
-
- if (array_key_exists('root', $this->tree)) {
- $rootid = $this->tree['root'][0];
- $this->showNoticePlus($rootid);
- }
-
- $this->out->elementEnd('ol');
- $this->out->elementEnd('div');
-
- return $cnt;
}
/**
diff --git a/actions/file.php b/actions/file.php
index 271f57ab9..8310e48df 100644
--- a/actions/file.php
+++ b/actions/file.php
@@ -56,5 +56,17 @@ class FileAction extends Action
function handle() {
common_redirect($this->filerec->url);
}
+
+ /**
+ * Is this action read-only?
+ *
+ * @return boolean true
+ */
+
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
}
diff --git a/actions/groupdesignsettings.php b/actions/groupdesignsettings.php
index 79c192ac4..6c1c052cb 100644
--- a/actions/groupdesignsettings.php
+++ b/actions/groupdesignsettings.php
@@ -34,19 +34,37 @@ if (!defined('LACONICA')) {
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
{
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)
{
parent::prepare($args);
- if (!common_config('inboxes','enabled')) {
+ if (!common_config('inboxes', 'enabled')) {
$this->serverError(_('Inboxes must be enabled for groups to work'));
return false;
}
@@ -57,7 +75,7 @@ class GroupDesignSettingsAction extends DesignSettingsAction
}
$nickname_arg = $this->trimmed('nickname');
- $nickname = common_canonical_nickname($nickname_arg);
+ $nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
@@ -158,7 +176,8 @@ class GroupDesignSettingsAction extends DesignSettingsAction
* @return Design
*/
- function getWorkingDesign() {
+ function getWorkingDesign()
+ {
$design = null;
@@ -273,9 +292,9 @@ class GroupDesignSettingsAction extends DesignSettingsAction
return;
}
- $original = clone($this->group);
+ $original = clone($this->group);
$this->group->design_id = $id;
- $result = $this->group->update($original);
+ $result = $this->group->update($original);
if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__);
diff --git a/actions/groupmembers.php b/actions/groupmembers.php
index d132cdf96..14256526a 100644
--- a/actions/groupmembers.php
+++ b/actions/groupmembers.php
@@ -167,6 +167,15 @@ class GroupMemberListItem extends ProfileListItem
$this->group = $group;
}
+ function showFullName()
+ {
+ parent::showFullName();
+ if ($this->profile->isAdmin($this->group)) {
+ $this->out->text(' ');
+ $this->out->element('span', 'role', _('Admin'));
+ }
+ }
+
function showActions()
{
$this->startActions();
diff --git a/actions/grouprss.php b/actions/grouprss.php
index 0b7280a11..2bdcaafb2 100644
--- a/actions/grouprss.php
+++ b/actions/grouprss.php
@@ -116,6 +116,7 @@ class groupRssAction extends Rss10Action
return null;
}
+ $notices = array();
$notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
while ($notice->fetch()) {
diff --git a/actions/newnotice.php b/actions/newnotice.php
index 15caff6ea..5f44a32a9 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -229,14 +229,25 @@ class NewnoticeAction extends Action
if (empty($filename)) {
$this->clientError(_('Couldn\'t save file.'));
}
- $fileurl = File::url($filename);
+
+ $fileRecord = $this->storeFile($filename, $mimetype);
+
+ $fileurl = common_local_url('attachment',
+ array('attachment' => $fileRecord->id));
+
+ // not sure this is necessary -- Zach
+ $this->maybeAddRedir($fileRecord->id, $fileurl);
+
$short_fileurl = common_shorten_url($fileurl);
$content_shortened .= ' ' . $short_fileurl;
+
if (mb_strlen($content_shortened) > 140) {
$this->deleteFile($filename);
$this->clientError(_('Max notice size is 140 chars, including attachment URL.'));
}
- $fileRecord = $this->rememberFile($filename, $mimetype, $short_fileurl);
+
+ // Also, not sure this is necessary -- Zach
+ $this->maybeAddRedir($fileRecord->id, $short_fileurl);
}
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
@@ -305,8 +316,8 @@ class NewnoticeAction extends Action
@unlink($filepath);
}
- function rememberFile($filename, $mimetype, $short)
- {
+ function storeFile($filename, $mimetype) {
+
$file = new File;
$file->filename = $filename;
@@ -325,11 +336,14 @@ class NewnoticeAction extends Action
$this->clientError(_('There was a database error while saving your file. Please try again.'));
}
- $this->maybeAddRedir($file_id, $short);
-
return $file;
}
+ function rememberFile($file, $short)
+ {
+ $this->maybeAddRedir($file->id, $short);
+ }
+
function maybeAddRedir($file_id, $url)
{
$file_redir = File_redirection::staticGet('url', $url);
diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php
index c1bf3bf5f..2a4b2060d 100644
--- a/actions/noticesearchrss.php
+++ b/actions/noticesearchrss.php
@@ -67,11 +67,16 @@ class NoticesearchrssAction extends Rss10Action
if (!$limit) $limit = 20;
$search_engine->limit(0, $limit, true);
- $search_engine->query($q);
- $notice->find();
+ if (false === $search_engine->query($q)) {
+ $cnt = 0;
+ } else {
+ $cnt = $notice->find();
+ }
- while ($notice->fetch()) {
- $notices[] = clone($notice);
+ if ($cnt > 0) {
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
}
return $notices;
diff --git a/actions/public.php b/actions/public.php
index 9851285c4..ef9ef0d1a 100644
--- a/actions/public.php
+++ b/actions/public.php
@@ -182,8 +182,10 @@ class PublicAction extends Action
$message .= _('Be the first to post!');
}
else {
- $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
- }
+ if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+ $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
+ }
+ }
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
diff --git a/actions/showgroup.php b/actions/showgroup.php
index b6a0f4844..ce11d574e 100644
--- a/actions/showgroup.php
+++ b/actions/showgroup.php
@@ -331,6 +331,7 @@ class ShowgroupAction extends GroupDesignAction
{
$this->showMembers();
$this->showStatistics();
+ $this->showAdmins();
$cloud = new GroupTagCloudSection($this, $this->group);
$cloud->show();
}
@@ -370,6 +371,18 @@ class ShowgroupAction extends GroupDesignAction
}
/**
+ * Show list of admins
+ *
+ * @return void
+ */
+
+ function showAdmins()
+ {
+ $adminSection = new GroupAdminSection($this, $this->group);
+ $adminSection->show();
+ }
+
+ /**
* Show some statistics
*
* @return void
@@ -423,3 +436,34 @@ class ShowgroupAction extends GroupDesignAction
$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;
+ }
+} \ No newline at end of file
diff --git a/actions/sup.php b/actions/sup.php
index e446a7b0d..a5b665562 100644
--- a/actions/sup.php
+++ b/actions/sup.php
@@ -63,11 +63,13 @@ class SupAction extends Action
# XXX: cache this. Depends on how big this protocol becomes;
# 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 ' .
'FROM notice ' .
((common_config('db','type') == 'pgsql') ?
'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' :
- 'WHERE created > (now() - ' . $seconds . ') ' ) .
+ 'WHERE created > "'.$divider.'" ' ) .
'GROUP BY profile_id');
$updates = array();
diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php
index eb9ab5d8e..708b1494d 100644
--- a/actions/twitapisearchatom.php
+++ b/actions/twitapisearchatom.php
@@ -165,24 +165,29 @@ class TwitapisearchatomAction extends TwitterapiAction
$search_engine->set_sort_mode('chron');
$search_engine->limit(($this->page - 1) * $this->rpp,
$this->rpp + 1, true);
- $search_engine->query($q);
- $this->cnt = $notice->find();
+ if (false === $search_engine->query($q)) {
+ $this->cnt = 0;
+ } else {
+ $this->cnt = $notice->find();
+ }
$cnt = 0;
- while ($notice->fetch()) {
+ if ($this->cnt > 0) {
+ while ($notice->fetch()) {
- ++$cnt;
+ ++$cnt;
- if (!$this->max_id) {
- $this->max_id = $notice->id;
- }
+ if (!$this->max_id) {
+ $this->max_id = $notice->id;
+ }
- if ($cnt > $this->rpp) {
- break;
- }
+ if ($cnt > $this->rpp) {
+ break;
+ }
- $notices[] = clone($notice);
+ $notices[] = clone($notice);
+ }
}
return $notices;
diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php
index b0e3be687..27a717bfc 100644
--- a/actions/twitapisearchjson.php
+++ b/actions/twitapisearchjson.php
@@ -124,8 +124,11 @@ class TwitapisearchjsonAction extends TwitterapiAction
$search_engine = $notice->getSearchEngine('identica_notices');
$search_engine->set_sort_mode('chron');
$search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true);
- $search_engine->query($q);
- $cnt = $notice->find();
+ if (false === $search_engine->query($q)) {
+ $cnt = 0;
+ } else {
+ $cnt = $notice->find();
+ }
// TODO: since_id, lang, geocode
@@ -146,4 +149,4 @@ class TwitapisearchjsonAction extends TwitterapiAction
{
return true;
}
-} \ No newline at end of file
+}
diff --git a/actions/userdesignsettings.php b/actions/userdesignsettings.php
index 6e745e96f..d7949951a 100644
--- a/actions/userdesignsettings.php
+++ b/actions/userdesignsettings.php
@@ -34,16 +34,37 @@ if (!defined('LACONICA')) {
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
{
-
+ /**
+ * 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)
{
parent::prepare($args);
$this->submitaction = common_local_url('userdesignsettings');
return true;
}
-
+
/**
* Title of the page
*
@@ -72,19 +93,20 @@ class UserDesignSettingsAction extends DesignSettingsAction
*
* @return Design
*/
-
- function getWorkingDesign() {
-
- $user = common_current_user();
+
+ function getWorkingDesign()
+ {
+
+ $user = common_current_user();
$design = $user->getDesign();
if (empty($design)) {
$design = $this->defaultDesign();
}
-
+
return $design;
}
-
+
/**
* Content area of the page
*
@@ -92,7 +114,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
*
* @return void
*/
-
+
function showContent()
{
$this->showDesignForm($this->getWorkingDesign());
@@ -106,14 +128,19 @@ class UserDesignSettingsAction extends DesignSettingsAction
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'));
$ccolor = new WebColor($this->trimmed('design_content'));
$sbcolor = new WebColor($this->trimmed('design_sidebar'));
$tcolor = new WebColor($this->trimmed('design_text'));
$lcolor = new WebColor($this->trimmed('design_links'));
-
} catch (WebColorException $e) {
$this->showForm($e->getMessage());
return;
@@ -137,7 +164,7 @@ class UserDesignSettingsAction extends DesignSettingsAction
$tile = true;
}
- $user = common_current_user();
+ $user = common_current_user();
$design = $user->getDesign();
if (!empty($design)) {
@@ -184,9 +211,9 @@ class UserDesignSettingsAction extends DesignSettingsAction
return;
}
- $original = clone($user);
+ $original = clone($user);
$user->design_id = $id;
- $result = $user->update($original);
+ $result = $user->update($original);
if (empty($result)) {
common_log_db_error($original, 'UPDATE', __FILE__);
@@ -203,4 +230,56 @@ class UserDesignSettingsAction extends DesignSettingsAction
$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);
+ }
+
}
diff --git a/classes/Design.php b/classes/Design.php
index da4b670be..0927fcda7 100644
--- a/classes/Design.php
+++ b/classes/Design.php
@@ -85,7 +85,7 @@ class Design extends Memcached_DataObject
$css .= 'body { background-image:url(' .
Design::url($this->backgroundimage) .
- '); ' . $repeat . ' }' . "\n";
+ '); ' . $repeat . ' background-attachment:fixed; }' . "\n";
}
$out->element('style', array('type' => 'text/css'), $css);
diff --git a/classes/Notice.php b/classes/Notice.php
index fdcef1bc2..2ba2f31b1 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -34,6 +34,8 @@ define('NOTICE_REMOTE_OMB', 0);
define('NOTICE_LOCAL_NONPUBLIC', -1);
define('NOTICE_GATEWAY', -2);
+define('MAX_BOXCARS', 128);
+
class Notice extends Memcached_DataObject
{
###START_AUTOCODE
@@ -221,7 +223,7 @@ class Notice extends Memcached_DataObject
$notice->saveTags();
$notice->addToInboxes();
- $notice->saveGroups();
+
$notice->saveUrls();
$orig2 = clone($notice);
$notice->rendered = common_render_content($final, $notice);
@@ -793,7 +795,7 @@ class Notice extends Memcached_DataObject
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
- $notice->whereAdd('conversation = '.$id);
+ $notice->conversation = $id;
$notice->orderBy('id DESC');
@@ -833,14 +835,55 @@ class Notice extends Memcached_DataObject
if ($enabled === true || $enabled === 'transitional') {
+ // XXX: loads constants
+
+ $inbox = new Notice_inbox();
+
$users = $this->getSubscribedUsers();
// FIXME: kind of ignoring 'transitional'...
// we'll probably stop supporting inboxless mode
// in 0.9.x
+ $ni = array();
+
foreach ($users as $id) {
- $this->addToUserInbox($id, NOTICE_INBOX_SOURCE_SUB);
+ $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
+ }
+
+ $groups = $this->saveGroups();
+
+ foreach ($groups as $group) {
+ $users = $group->getUserMembers();
+ foreach ($users as $id) {
+ if (!array_key_exists($id, $ni)) {
+ $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
+ }
+ }
+ }
+
+ $cnt = 0;
+
+ $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
+ $qry = $qryhdr;
+
+ foreach ($ni as $id => $source) {
+ if ($cnt > 0) {
+ $qry .= ', ';
+ }
+ $qry .= '('.$id.', '.$this->id.', '.$source.', "'.$this->created.'") ';
+ $cnt++;
+ if ($cnt >= MAX_BOXCARS) {
+ $inbox = new Notice_inbox();
+ $inbox->query($qry);
+ $qry = $qryhdr;
+ $cnt = 0;
+ }
+ }
+
+ if ($cnt > 0) {
+ $inbox = new Notice_inbox();
+ $inbox->query($qry);
}
}
@@ -870,27 +913,13 @@ class Notice extends Memcached_DataObject
return $ids;
}
- function addToUserInbox($user_id, $source)
- {
- $inbox = Notice_inbox::pkeyGet(array('user_id' => $user_id,
- 'notice_id' => $this->id));
- if (empty($inbox)) {
- $inbox = new Notice_inbox();
- $inbox->user_id = $user_id;
- $inbox->notice_id = $this->id;
- $inbox->source = $source;
- $inbox->created = $this->created;
- return $inbox->insert();
- }
-
- return true;
- }
-
function saveGroups()
{
+ $groups = array();
+
$enabled = common_config('inboxes', 'enabled');
if ($enabled !== true && $enabled !== 'transitional') {
- return;
+ return $groups;
}
/* extract all !group */
@@ -898,7 +927,7 @@ class Notice extends Memcached_DataObject
strtolower($this->content),
$match);
if (!$count) {
- return true;
+ return $groups;
}
$profile = $this->getProfile();
@@ -930,11 +959,11 @@ class Notice extends Memcached_DataObject
common_log_db_error($gi, 'INSERT', __FILE__);
}
- // FIXME: do this in an offline daemon
-
- $this->addToGroupMemberInboxes($group);
+ $groups[] = clone($group);
}
}
+
+ return $groups;
}
function addToGroupInbox($group)
@@ -956,15 +985,6 @@ class Notice extends Memcached_DataObject
return true;
}
- function addToGroupMemberInboxes($group)
- {
- $users = $group->getUserMembers();
-
- foreach ($users as $id) {
- $this->addToUserInbox($id, NOTICE_INBOX_SOURCE_GROUP);
- }
- }
-
function saveReplies()
{
// Alternative reply format
diff --git a/classes/Session.php b/classes/Session.php
new file mode 100644
index 000000000..93fd99baa
--- /dev/null
+++ b/classes/Session.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Table Definition for session
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Session extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'session'; // table name
+ public $id; // varchar(32) primary_key not_null
+ public $session_data; // text()
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Session',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ static function logdeb($msg)
+ {
+ if (common_config('sessions', 'debug')) {
+ common_debug("Session: " . $msg);
+ }
+ }
+
+ static function open($save_path, $session_name)
+ {
+ return true;
+ }
+
+ static function close()
+ {
+ return true;
+ }
+
+ static function read($id)
+ {
+ self::logdeb("Fetching session '$id'");
+
+ $session = Session::staticGet('id', $id);
+
+ if (empty($session)) {
+ return '';
+ } else {
+ return (string)$session->session_data;
+ }
+ }
+
+ static function write($id, $session_data)
+ {
+ self::logdeb("Writing session '$id'");
+
+ $session = Session::staticGet('id', $id);
+
+ if (empty($session)) {
+ $session = new Session();
+
+ $session->id = $id;
+ $session->session_data = $session_data;
+ $session->created = common_sql_now();
+
+ return $session->insert();
+ } else {
+ $session->session_data = $session_data;
+
+ return $session->update();
+ }
+ }
+
+ static function destroy($id)
+ {
+ self::logdeb("Deleting session $id");
+
+ $session = Session::staticGet('id', $id);
+
+ if (!empty($session)) {
+ return $session->delete();
+ }
+ }
+
+ static function gc($maxlifetime)
+ {
+ self::logdeb("garbage collection (maxlifetime = $maxlifetime)");
+
+ $epoch = time() - $maxlifetime;
+
+ $qry = 'DELETE FROM session ' .
+ 'WHERE modified < "'.$epoch.'"';
+
+ $session = new Session();
+
+ $result = $session->query($qry);
+
+ self::logdeb("garbage collection result = $result");
+ }
+
+ static function setSaveHandler()
+ {
+ self::logdeb("setting save handlers");
+ $result = session_set_save_handler('Session::open', 'Session::close', 'Session::read',
+ 'Session::write', 'Session::destroy', 'Session::gc');
+ self::logdeb("save handlers result = $result");
+ return $result;
+ }
+}
diff --git a/classes/Status_network.php b/classes/Status_network.php
index f8d6756b6..dbd722e88 100644
--- a/classes/Status_network.php
+++ b/classes/Status_network.php
@@ -132,6 +132,13 @@ class Status_network extends DB_DataObject
}
} else {
$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)) {
diff --git a/classes/User_group.php b/classes/User_group.php
index 9b4b01ead..27b444705 100644
--- a/classes/User_group.php
+++ b/classes/User_group.php
@@ -126,6 +126,30 @@ class User_group extends Memcached_DataObject
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)
{
$qry =
diff --git a/classes/laconica.ini b/classes/laconica.ini
index 7e9b2b791..766bed75d 100755..100644
--- a/classes/laconica.ini
+++ b/classes/laconica.ini
@@ -380,6 +380,15 @@ replied_id = 1
notice_id = K
profile_id = K
+[session]
+id = 130
+session_data = 34
+created = 142
+modified = 384
+
+[session__keys]
+id = K
+
[sms_carrier]
id = 129
name = 2
diff --git a/db/laconica.sql b/db/laconica.sql
index 3f8918de6..2c04f680a 100644
--- a/db/laconica.sql
+++ b/db/laconica.sql
@@ -524,3 +524,14 @@ create table group_alias (
index group_alias_group_id_idx (group_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table session (
+
+ id varchar(32) primary key comment 'session ID',
+ session_data text comment 'session data',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ index session_modified_idx (modified)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; \ No newline at end of file
diff --git a/extlib/Mail/mimeDecode.php b/extlib/Mail/mimeDecode.php
new file mode 100644
index 000000000..aaa870c50
--- /dev/null
+++ b/extlib/Mail/mimeDecode.php
@@ -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
diff --git a/extlib/XMPPHP/BOSH.php b/extlib/XMPPHP/BOSH.php
index b147443d7..befaf60a7 100644
--- a/extlib/XMPPHP/BOSH.php
+++ b/extlib/XMPPHP/BOSH.php
@@ -27,7 +27,7 @@
*/
/** XMPPHP_XMLStream */
-require_once "XMPP.php";
+require_once dirname(__FILE__) . "/XMPP.php";
/**
* XMPPHP Main Class
diff --git a/extlib/XMPPHP/XMLStream.php b/extlib/XMPPHP/XMLStream.php
index 0fcfea375..d33411ec5 100644
--- a/extlib/XMPPHP/XMLStream.php
+++ b/extlib/XMPPHP/XMLStream.php
@@ -27,13 +27,13 @@
*/
/** XMPPHP_Exception */
-require_once 'Exception.php';
+require_once dirname(__FILE__) . '/Exception.php';
/** XMPPHP_XMLObj */
-require_once 'XMLObj.php';
+require_once dirname(__FILE__) . '/XMLObj.php';
/** XMPPHP_Log */
-require_once 'Log.php';
+require_once dirname(__FILE__) . '/Log.php';
/**
* XMPPHP XML Stream
@@ -375,7 +375,7 @@ class XMPPHP_XMLStream {
* integer -> process for this amount of time
*/
- private function __process($maximum=0) {
+ private function __process($maximum=5) {
$remaining = $maximum;
diff --git a/extlib/XMPPHP/XMPP.php b/extlib/XMPPHP/XMPP.php
index 73fbd2658..429f45e56 100644
--- a/extlib/XMPPHP/XMPP.php
+++ b/extlib/XMPPHP/XMPP.php
@@ -27,8 +27,8 @@
*/
/** XMPPHP_XMLStream */
-require_once "XMLStream.php";
-require_once "Roster.php";
+require_once dirname(__FILE__) . "/XMLStream.php";
+require_once dirname(__FILE__) . "/Roster.php";
/**
* XMPPHP Main Class
@@ -208,6 +208,15 @@ class XMPPHP_XMPP extends XMPPHP_XMLStream {
$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
diff --git a/index.php b/index.php
index cb6a0fe60..5f9a048f2 100644
--- a/index.php
+++ b/index.php
@@ -73,13 +73,45 @@ function handleError($error)
exit(-1);
}
+function checkMirror($action_obj)
+{
+ global $config;
+
+ static $alwaysRW = array('session', 'remember_me');
+
+ if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
+ if (is_array(common_config('db', 'mirror'))) {
+ // "load balancing", ha ha
+ $arr = common_config('db', 'mirror');
+ $k = array_rand($arr);
+ $mirror = $arr[$k];
+ } else {
+ $mirror = common_config('db', 'mirror');
+ }
+
+ // We ensure that these tables always are used
+ // on the master DB
+
+ $config['db']['database_rw'] = $config['db']['database'];
+ $config['db']['ini_rw'] = INSTALLDIR.'/classes/laconica.ini';
+
+ foreach ($alwaysRW as $table) {
+ $config['db']['table_'.$table] = 'rw';
+ }
+
+ // everyone else uses the mirror
+
+ $config['db']['database'] = $mirror;
+ }
+}
+
function main()
{
// quick check for fancy URL auto-detection support in installer.
if (isset($_SERVER['REDIRECT_URL']) && ((dirname($_SERVER['REQUEST_URI']) . '/check-fancy') === $_SERVER['REDIRECT_URL'])) {
die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
}
- global $user, $action, $config;
+ global $user, $action;
Snapshot::check();
@@ -146,19 +178,7 @@ function main()
} else {
$action_obj = new $action_class();
- // XXX: find somewhere for this little block to live
-
- if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
- if (is_array(common_config('db', 'mirror'))) {
- // "load balancing", ha ha
- $arr = common_config('db', 'mirror');
- $k = array_rand($arr);
- $mirror = $arr[$k];
- } else {
- $mirror = common_config('db', 'mirror');
- }
- $config['db']['database'] = $mirror;
- }
+ checkMirror($action_obj);
try {
if ($action_obj->prepare($args)) {
diff --git a/install.php b/install.php
index b94a92936..570b08edf 100644
--- a/install.php
+++ b/install.php
@@ -72,6 +72,12 @@ function checkPrereqs()
<?
$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;
}
diff --git a/js/jquery.joverlay.min.js b/js/jquery.joverlay.min.js
index c9168506a..ffbd5daac 100644
--- a/js/jquery.joverlay.min.js
+++ b/js/jquery.joverlay.min.js
@@ -1,6 +1,7 @@
/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
- * Version: 0.6 (Abr 23, 2009)
+ * Version: 0.7.1 (JUN 15, 2009
* 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); \ No newline at end of file
+(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?'35%':$(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);
diff --git a/js/userdesign.go.js b/js/userdesign.go.js
index 833b19adc..dda86294e 100644
--- a/js/userdesign.go.js
+++ b/js/userdesign.go.js
@@ -11,7 +11,7 @@ $(document).ready(function() {
C = $(S).val();
switch (parseInt(S.id.slice(-1))) {
case 1: default:
- $('html, body').css({'background-color':C});
+ $('body').css({'background-color':C});
break;
case 2:
$('#content, #site_nav_local_views .current a').css({'background-color':C});
@@ -90,6 +90,7 @@ $(document).ready(function() {
});
$('#design_background-image_on').focus(function() {
$('body').css({'background-image':'url('+$('#design_background-image_onoff img')[0].src+')'});
+ $('body').css({'background-attachment': 'fixed'});
});
$('#design_background-image_repeat').click(function() {
diff --git a/js/util.js b/js/util.js
index e7c54b74a..1e6dc7251 100644
--- a/js/util.js
+++ b/js/util.js
@@ -279,7 +279,9 @@ function NoticeAttachments() {
$('#jOverlayContent').append('<button>&#215;</button>');
$('#jOverlayContent button').click($.closeOverlay);
},
- timeout : 0
+ timeout : 0,
+ autoHide : true,
+ css : {'max-width':'502px'}
};
$('#content .notice a.attachment').click(function() {
diff --git a/lib/common.php b/lib/common.php
index c9e5fb0c9..e10f24b3b 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -256,6 +256,9 @@ $config =
'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
'search' =>
array('type' => 'fulltext'),
+ 'sessions' =>
+ array('handle' => false, // whether to handle sessions ourselves
+ 'debug' => false), // debugging output for sessions
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
diff --git a/lib/daemon.php b/lib/daemon.php
index a0df00bdc..9d89c63e7 100644
--- a/lib/daemon.php
+++ b/lib/daemon.php
@@ -23,6 +23,13 @@ if (!defined('LACONICA')) {
class Daemon
{
+ var $daemonize = true;
+
+ function __construct($daemonize = true)
+ {
+ $this->daemonize = $daemonize;
+ }
+
function name()
{
return null;
@@ -129,12 +136,16 @@ class Daemon
common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
exit(0);
}
- if ($this->background()) {
- $this->writePidFile();
- $this->changeUser();
- $this->run();
- $this->clearPidFile();
+
+ if ($this->daemonize) {
+ common_log(LOG_INFO, 'Backgrounding daemon "'.$this->name().'"');
+ $this->background();
}
+
+ $this->writePidFile();
+ $this->changeUser();
+ $this->run();
+ $this->clearPidFile();
}
function run()
diff --git a/lib/designsettings.php b/lib/designsettings.php
index 9650679ac..fbffdb208 100644
--- a/lib/designsettings.php
+++ b/lib/designsettings.php
@@ -35,6 +35,20 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR . '/lib/accountsettingsaction.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
{
@@ -63,6 +77,14 @@ class DesignSettingsAction extends AccountSettingsAction
'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)
{
@@ -94,7 +116,8 @@ class DesignSettingsAction extends AccountSettingsAction
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' =>
Design::url($design->backgroundimage)));
@@ -136,7 +159,7 @@ class DesignSettingsAction extends AccountSettingsAction
$this->elementStart('li');
$this->checkbox('design_background-image_repeat',
_('Tile background image'),
- ($design->disposition & BACKGROUND_TILE) ? true : false );
+ ($design->disposition & BACKGROUND_TILE) ? true : false);
$this->elementEnd('li');
}
@@ -212,18 +235,19 @@ class DesignSettingsAction extends AccountSettingsAction
'maxlength' => '7',
'size' => '7',
'value' => '#' . $lcolor->hexValue()));
+ $this->elementEnd('li');
- $this->elementEnd('li');
+ } catch (WebColorException $e) {
+ common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id);
+ }
- } catch (WebColorException $e) {
- common_log(LOG_ERR, 'Bad color values in design ID: ' .
- $design->id);
- }
+ $this->elementEnd('ul');
+ $this->elementEnd('fieldset');
- $this->elementEnd('ul');
- $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',
'value' => 'Reset',
'class' => 'submit form_action-primary',
@@ -271,8 +295,8 @@ class DesignSettingsAction extends AccountSettingsAction
if ($this->arg('save')) {
$this->saveDesign();
- } else if ($this->arg('reset')) {
- $this->resetDesign();
+ } else if ($this->arg('defaults')) {
+ $this->restoreDefaults();
} else {
$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
*/
@@ -358,7 +382,16 @@ class DesignSettingsAction extends AccountSettingsAction
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.
// XXX: This is an additional DB hit, but figured having the image
@@ -371,12 +404,12 @@ class DesignSettingsAction extends AccountSettingsAction
$filepath = null;
try {
- $imagefile =
- ImageFile::fromUpload('design_background-image_file');
- } catch (Exception $e) {
- $this->showForm($e->getMessage());
- return;
- }
+ $imagefile =
+ ImageFile::fromUpload('design_background-image_file');
+ } catch (Exception $e) {
+ $this->showForm($e->getMessage());
+ return;
+ }
$filename = Design::filename($design->id,
image_type_to_extension($imagefile->type),
@@ -386,7 +419,14 @@ class DesignSettingsAction extends AccountSettingsAction
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);
+
$design->backgroundimage = $filename;
// 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);
+ }
+
}
diff --git a/lib/language.php b/lib/language.php
index cd6498d30..3ea3dd2aa 100644
--- a/lib/language.php
+++ b/lib/language.php
@@ -108,7 +108,7 @@ function get_all_languages() {
'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-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'),
'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
'fr-fr' => array('q' => 1, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index ae403c65e..c1c4f3309 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -27,11 +27,12 @@ require_once(INSTALLDIR.'/classes/Notice.php');
class QueueHandler extends Daemon
{
-
var $_id = 'generic';
- function QueueHandler($id=null)
+ function __construct($id=null, $daemonize=true)
{
+ parent::__construct($daemonize);
+
if ($id) {
$this->set_id($id);
}
diff --git a/lib/router.php b/lib/router.php
index 1f39c60dc..784ea9882 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -152,6 +152,10 @@ class Router
$m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
array('q' => '.+'));
+ $m->connect('attachment/:attachment',
+ array('action' => 'attachment'),
+ array('attachment' => '[0-9]+'));
+
$m->connect('attachment/:attachment/ajax',
array('action' => 'attachment_ajax'),
array('attachment' => '[0-9]+'));
diff --git a/lib/util.php b/lib/util.php
index f6d50b180..f9ff38c8a 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -139,8 +139,23 @@ function common_have_session()
function common_ensure_session()
{
+ $c = null;
+ if (array_key_exists(session_name, $_COOKIE)) {
+ $c = $_COOKIE[session_name()];
+ }
if (!common_have_session()) {
+ if (common_config('sessions', 'handle')) {
+ common_log(LOG_INFO, "Using our own session handler");
+ Session::setSaveHandler();
+ }
@session_start();
+ if (!isset($_SESSION['started'])) {
+ $_SESSION['started'] = time();
+ if (!empty($c)) {
+ common_log(LOG_WARNING, 'Session cookie "' . $_COOKIE[session_name()] . '" ' .
+ ' is set but started value is null');
+ }
+ }
}
}
@@ -485,17 +500,19 @@ function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying
// functions
$url = htmlspecialchars_decode($url);
- $display = File_redirection::_canonUrl($url);
+
+ $canon = File_redirection::_canonUrl($url);
+
$longurl_data = File_redirection::where($url);
if (is_array($longurl_data)) {
$longurl = $longurl_data['url'];
} elseif (is_string($longurl_data)) {
$longurl = $longurl_data;
} else {
- die('impossible to linkify');
+ throw new ServerException("Can't linkify url '$url'");
}
- $attrs = array('href' => $longurl, 'rel' => 'external');
+ $attrs = array('href' => $canon, 'rel' => 'external');
$is_attachment = false;
$attachment_id = null;
@@ -513,13 +530,13 @@ function common_linkify($url) {
}
}
-// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
-// where ID is the id of the attachment for the given URL.
-//
-// we need a better test telling what can be shown as an attachment
-// we're currently picking up oembeds only.
-// I think the best option is another file_view table in the db
-// and associated dbobject.
+ // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
+ // where ID is the id of the attachment for the given URL.
+ //
+ // we need a better test telling what can be shown as an attachment
+ // we're currently picking up oembeds only.
+ // I think the best option is another file_view table in the db
+ // and associated dbobject.
$query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
$file = new File;
@@ -549,7 +566,7 @@ function common_linkify($url) {
$attrs['id'] = "attachment-{$attachment_id}";
}
- return XMLStringer::estring('a', $attrs, $display);
+ return XMLStringer::estring('a', $attrs, $url);
}
function common_shorten_links($text)
@@ -817,7 +834,12 @@ function common_date_iso8601($dt)
function common_sql_now()
{
- return strftime('%Y-%m-%d %H:%M:%S', time());
+ return common_sql_date(time());
+}
+
+function common_sql_date($datetime)
+{
+ return strftime('%Y-%m-%d %H:%M:%S', $datetime);
}
function common_redirect($url, $code=307)
@@ -1082,15 +1104,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)
{
$logfile = common_config('site', 'logfile');
if ($logfile) {
$log = fopen($logfile, "a");
if ($log) {
- static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
- 'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
- $output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
+ $output = common_log_line($priority, $msg);
fwrite($log, $output);
fclose($log);
}
@@ -1321,18 +1348,39 @@ function common_canonical_sms($sms)
function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
{
switch ($errno) {
+
+ case E_ERROR:
+ case E_COMPILE_ERROR:
+ case E_CORE_ERROR:
case E_USER_ERROR:
- common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
- exit(1);
+ case E_PARSE:
+ case E_RECOVERABLE_ERROR:
+ common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline) [ABORT]");
+ die();
break;
+ case E_WARNING:
+ case E_COMPILE_WARNING:
+ case E_CORE_WARNING:
case E_USER_WARNING:
common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
break;
+ case E_NOTICE:
case E_USER_NOTICE:
common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
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
@@ -1470,4 +1518,28 @@ function common_shorten_url($long_url)
curl_close($curlh);
return $short_url;
-} \ No newline at end of file
+}
+
+function common_client_ip()
+{
+ if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ return null;
+ }
+
+ if ($_SERVER['HTTP_X_FORWARDED_FOR']) {
+ if ($_SERVER['HTTP_CLIENT_IP']) {
+ $proxy = $_SERVER['HTTP_CLIENT_IP'];
+ } else {
+ $proxy = $_SERVER['REMOTE_ADDR'];
+ }
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ } else {
+ if ($_SERVER['HTTP_CLIENT_IP']) {
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
+ } else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ }
+
+ return array($ip, $proxy);
+}
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
index 11dc568a9..d45d2718c 100644
--- a/plugins/FBConnect/FBConnectPlugin.php
+++ b/plugins/FBConnect/FBConnectPlugin.php
@@ -69,95 +69,153 @@ class FBConnectPlugin extends Plugin
// Add in xmlns:fb
function onStartShowHTML($action)
{
- // XXX: Horrible hack to make Safari, FF2, and Chrome work with
- // Facebook Connect. These browser cannot use Facebook's
- // DOM parsing routines unless the mime type of the page is
- // text/html even though Facebook Connect uses XHTML. This is
- // A bug in Facebook Connect, and this is a temporary solution
- // until they fix their JavaScript libs.
- header('Content-Type: text/html');
- $action->extraHeaders();
+ if ($this->requiresFB($action)) {
- $action->startXML('html',
- '-//W3C//DTD XHTML 1.0 Strict//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+ // XXX: Horrible hack to make Safari, FF2, and Chrome work with
+ // Facebook Connect. These browser cannot use Facebook's
+ // DOM parsing routines unless the mime type of the page is
+ // text/html even though Facebook Connect uses XHTML. This is
+ // A bug in Facebook Connect, and this is a temporary solution
+ // until they fix their JavaScript libs.
+ header('Content-Type: text/html');
- $language = $action->getLanguage();
+ $action->extraHeaders();
- $action->elementStart('html',
- array('xmlns' => 'http://www.w3.org/1999/xhtml',
- 'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
- 'xml:lang' => $language,
- 'lang' => $language));
+ $action->startXML('html',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
- return false;
+ $language = $action->getLanguage();
+
+ $action->elementStart('html',
+ array('xmlns' => 'http://www.w3.org/1999/xhtml',
+ 'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
+ 'xml:lang' => $language,
+ 'lang' => $language));
+
+ return false;
+
+ } else {
+
+ return true;
+ }
}
// Note: this script needs to appear in the <body>
function onStartShowHeader($action)
{
- $apikey = common_config('facebook', 'apikey');
- $plugin_path = common_path('plugins/FBConnect');
-
- $login_url = common_local_url('FBConnectAuth');
- $logout_url = common_local_url('logout');
-
- // XXX: Facebook says we don't need this FB_RequireFeatures(),
- // but we actually do, for IE and Safari. Gar.
-
- $html = sprintf('<script type="text/javascript">
- window.onload = function () {
- FB_RequireFeatures(
- ["XFBML"],
- function() {
- FB.Facebook.init("%s", "../xd_receiver.html");
- }
- ); }
-
- function goto_login() {
- window.location = "%s";
- }
-
- function goto_logout() {
- window.location = "%s";
- }
- </script>', $apikey,
- $login_url, $logout_url);
-
- $action->raw($html);
+ if ($this->requiresFB($action)) {
+
+ $apikey = common_config('facebook', 'apikey');
+ $plugin_path = common_path('plugins/FBConnect');
+
+ $login_url = common_local_url('FBConnectAuth');
+ $logout_url = common_local_url('logout');
+
+ // XXX: Facebook says we don't need this FB_RequireFeatures(),
+ // but we actually do, for IE and Safari. Gar.
+
+ $html = sprintf('<script type="text/javascript">
+ window.onload = function () {
+ FB_RequireFeatures(
+ ["XFBML"],
+ function() {
+ FB.Facebook.init("%s", "../xd_receiver.html");
+ }
+ ); }
+
+ function goto_login() {
+ window.location = "%s";
+ }
+
+ function goto_logout() {
+ window.location = "%s";
+ }
+ </script>', $apikey,
+ $login_url, $logout_url);
+
+ $action->raw($html);
+ }
+
}
// Note: this script needs to appear as close as possible to </body>
function onEndShowFooter($action)
{
+ if ($this->requiresFB($action)) {
- $action->element('script',
- array('type' => 'text/javascript',
- 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
- '');
+ $action->element('script',
+ array('type' => 'text/javascript',
+ 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
+ '');
+ }
}
function onEndShowLaconicaStyles($action)
{
- $action->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => common_path('plugins/FBConnect/FBConnectPlugin.css')));
+
+ if ($this->requiresFB($action)) {
+
+ $action->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/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 requiresFB($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();
- if ($user) {
+ if (!empty($user)) {
$flink = Foreign_link::getByUserId($user->id,
FACEBOOK_CONNECT_SERVICE);
$fbuid = 0;
- if ($flink) {
+ if (!empty($flink)) {
try {
@@ -170,23 +228,38 @@ class FBConnectPlugin extends Plugin
$e->getMessage());
}
- // Display Facebook Logged in indicator w/Facebook favicon
-
if ($fbuid > 0) {
+ return $fbuid;
+ }
+ }
+ }
- $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');
+ return null;
+ }
- $iconurl = common_path('/plugins/FBConnect/fbfavicon.ico');
- $action->element('img', array('src' => $iconurl));
+ function onStartPrimaryNav($action)
+ {
- $action->elementEnd('li');
+ $user = common_current_user();
+
+ if (!empty($user)) {
+
+ $fbuid = $this->loggedIn();
+
+ if (!empty($fbuid)) {
+
+ $action->elementStart('li', array('id' => 'nav_fb'));
+ $action->elementStart('fb:profile-pic', array('uid' => $fbuid,
+ 'linked' => 'false',
+ 'width' => 16,
+ 'height' => 16));
+ $action->elementEnd('fb:profile-pic');
+
+ $iconurl = common_path('/plugins/FBConnect/fbfavicon.ico');
+ $action->element('img', array('src' => $iconurl));
+
+ $action->elementEnd('li');
- }
}
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
@@ -209,7 +282,7 @@ class FBConnectPlugin extends Plugin
}
// 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');
$title = _('Logout from the site');
@@ -258,7 +331,7 @@ class FBConnectPlugin extends Plugin
return false;
}
- $connect_actions = array('SmssettingsAction',
+ $connect_actions = array('SmssettingsAction', 'ImsettingsAction',
'TwittersettingsAction', 'FBConnectSettingsAction');
if (in_array($action_name, $connect_actions)) {
@@ -272,23 +345,13 @@ class FBConnectPlugin extends Plugin
function onStartLogout($action)
{
- $user = common_current_user();
-
- $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
-
$action->logout();
+ $fbuid = $this->loggedIn();
- if ($flink) {
-
- $facebook = getFacebook();
-
+ if (!empty($fbuid)) {
try {
- $fbuid = $facebook->get_loggedin_user();
-
- if ($fbuid > 0) {
- $facebook->logout(common_local_url('public'));
- }
-
+ $facebook = getFacebook();
+ $facebook->expire_session();
} catch (Exception $e) {
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
$e->getMessage());
diff --git a/scripts/commandline.inc b/scripts/commandline.inc
index 4a7757fb9..3b6ef6098 100644
--- a/scripts/commandline.inc
+++ b/scripts/commandline.inc
@@ -63,14 +63,21 @@ if (isset($longoptions)) {
$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()
{
global $helptext;
$_default_help_text = <<<END_OF_DEFAULT
-General options:
+ General options:
-q --quiet Quiet (little output)
-v --verbose Verbose (lots of output)
@@ -80,11 +87,11 @@ General options:
-h --help Show this message and quit.
END_OF_DEFAULT;
- if (isset($helptext)) {
- print $helptext;
- }
- print $_default_help_text;
- exit(0);
+ if (isset($helptext)) {
+ print $helptext;
+ }
+ print $_default_help_text;
+ exit(0);
}
foreach ($options as $option) {
@@ -115,24 +122,53 @@ require_once INSTALLDIR . '/lib/common.php';
set_error_handler('common_error_handler');
-function have_option($str)
+function _make_matches($opt, $alt)
{
- global $options;
- foreach ($options as $option) {
- if ($option[0] == $str) {
- return true;
- }
- }
- return false;
+ $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 get_option_value($str)
+function have_option($opt, $alt=null)
{
- global $options;
- foreach ($options as $option) {
- if ($option[0] == $str) {
- return $option[1];
- }
- }
- return null;
-} \ No newline at end of file
+ global $options;
+
+ $matches = _make_matches($opt, $alt);
+
+ foreach ($options as $option) {
+ if (in_array($option[0], $matches)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function get_option_value($opt, $alt=null)
+{
+ global $options;
+
+ $matches = _make_matches($opt, $alt);
+
+ foreach ($options as $option) {
+ if (in_array($option[0], $matches)) {
+ return $option[1];
+ }
+ }
+
+ return null;
+}
diff --git a/scripts/fixup_conversations.php b/scripts/fixup_conversations.php
index 2cfa422e6..0be0b4bac 100755
--- a/scripts/fixup_conversations.php
+++ b/scripts/fixup_conversations.php
@@ -24,22 +24,17 @@ require_once INSTALLDIR.'/scripts/commandline.inc';
common_log(LOG_INFO, 'Fixing up conversations.');
-$notice = new Notice();
-$notice->whereAdd('conversation is null');
-$notice->orderBy('id');
+$nid = new Notice();
+$nid->query('select id, reply_to from notice where conversation is null');
-$cnt = $notice->find();
+while ($nid->fetch()) {
-print "Found $cnt notices.\n";
-
-while ($notice->fetch()) {
-
- print "$notice->id =>";
-
- $orig = clone($notice);
-
- if (empty($notice->reply_to)) {
- $notice->conversation = $notice->id;
+ $cid = null;
+
+ $notice = new Notice();
+
+ if (empty($nid->reply_to)) {
+ $cid = $nid->id;
} else {
$reply = Notice::staticGet('id', $notice->reply_to);
@@ -52,6 +47,9 @@ while ($notice->fetch()) {
} else {
$notice->conversation = $reply->conversation;
}
+
+ unset($reply);
+ $reply = null;
}
print "$notice->conversation";
@@ -63,5 +61,10 @@ while ($notice->fetch()) {
continue;
}
+ $notice = null;
+ $orig = null;
+ unset($notice);
+ unset($orig);
+
print ".\n";
}
diff --git a/scripts/showcache.php b/scripts/showcache.php
new file mode 100644
index 000000000..7a88fdbbb
--- /dev/null
+++ b/scripts/showcache.php
@@ -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";
+}
diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php
index 5ffdda58f..8b10bfbad 100755
--- a/scripts/twitterstatusfetcher.php
+++ b/scripts/twitterstatusfetcher.php
@@ -25,9 +25,14 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('MAXCHILDREN', 2);
define('POLL_INTERVAL', 60); // in seconds
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
$helptext = <<<END_OF_TRIM_HELP
Batch script for retrieving Twitter messages from foreign service.
+ -i --id Identity (default 'generic')
+
END_OF_TRIM_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
@@ -64,7 +69,7 @@ class TwitterStatusFetcher extends Daemon
function name()
{
- return ('twitterstatusfetcher.generic');
+ return ('twitterstatusfetcher.'.$this->_id);
}
/**
@@ -625,6 +630,16 @@ class TwitterStatusFetcher extends Daemon
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();
diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php
index 3eecfec29..ca6218120 100755
--- a/scripts/xmppdaemon.php
+++ b/scripts/xmppdaemon.php
@@ -20,13 +20,14 @@
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-$shortoptions = 'i::';
-$longoptions = array('id::');
+$shortoptions = 'fi::';
+$longoptions = array('id::', 'foreground');
$helptext = <<<END_OF_XMPP_HELP
Daemon script for receiving new notices from Jabber users.
-i --id Identity (default none)
+ -f --foreground Stay in the foreground (default background)
END_OF_XMPP_HELP;
@@ -42,8 +43,10 @@ require_once INSTALLDIR . '/lib/daemon.php';
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');
foreach ($attrs as $attr)
@@ -62,7 +65,6 @@ class XMPPDaemon extends Daemon
function connect()
{
-
$connect_to = ($this->host) ? $this->host : $this->server;
$this->log(LOG_INFO, "Connecting to $connect_to on port $this->port");
@@ -73,10 +75,17 @@ class XMPPDaemon extends Daemon
return false;
}
+ $this->log(LOG_INFO, "Connected");
+
$this->conn->setReconnectTimeout(600);
+ $this->log(LOG_INFO, "Sending initial presence.");
+
jabber_send_presence("Send me a message to post a notice", 'available',
null, 'available', 100);
+
+ $this->log(LOG_INFO, "Done connecting.");
+
return !$this->conn->isDisconnected();
}
@@ -89,17 +98,23 @@ class XMPPDaemon extends Daemon
{
if ($this->connect()) {
+ $this->log(LOG_DEBUG, "Initializing stanza handlers.");
+
$this->conn->addEventHandler('message', 'handle_message', $this);
$this->conn->addEventHandler('presence', 'handle_presence', $this);
$this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
+ $this->log(LOG_DEBUG, "Beginning processing loop.");
+
$this->conn->process();
}
}
function handle_reconnect(&$pl)
{
+ $this->log(LOG_DEBUG, "Got reconnection callback.");
$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);
}
@@ -111,21 +126,27 @@ class XMPPDaemon extends Daemon
function handle_message(&$pl)
{
+ $from = jabber_normalize_jid($pl['from']);
+
if ($pl['type'] != 'chat') {
+ $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from.");
return;
}
+
if (mb_strlen($pl['body']) == 0) {
+ $this->log(LOG_WARNING, "Ignoring message with empty body from $from.");
return;
}
- $from = jabber_normalize_jid($pl['from']);
-
# Forwarded from another daemon (probably a broadcaster) for
# us to handle
if ($this->is_self($from)) {
+ $this->log(LOG_INFO, "Got forwarded notice from self ($from).");
$from = $this->get_ofrom($pl);
+ $this->log(LOG_INFO, "Originally sent by $from.");
if (is_null($from) || $this->is_self($from)) {
+ $this->log(LOG_INFO, "Ignoring notice originally sent by $from.");
return;
}
}
@@ -140,6 +161,7 @@ class XMPPDaemon extends Daemon
return;
}
if ($this->handle_command($user, $pl['body'])) {
+ $this->log(LOG_INFO, "Command messag by $from handled.");
return;
} else if ($this->is_autoreply($pl['body'])) {
$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);
return;
} 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);
$to = preg_replace('/^d([\ ])*/', '', $to[0][0]);
$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);
} else {
+
+ $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
+
$this->add_notice($user, $pl);
}
@@ -261,6 +291,7 @@ class XMPPDaemon extends Daemon
$notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
if (is_string($notice)) {
$this->log(LOG_ERR, $notice);
+ $this->from_site($user->jabber, $notice);
return;
}
common_broadcast_notice($notice);
@@ -307,7 +338,14 @@ class XMPPDaemon extends Daemon
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)
@@ -323,16 +361,16 @@ if (common_config('xmpp','enabled')==false) {
exit();
}
-if (have_option('i')) {
- $id = get_option_value('i');
-} else if (have_option('--id')) {
- $id = get_option_value('--id');
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
} else if (count($args) > 0) {
$id = $args[0];
} else {
$id = null;
}
-$daemon = new XMPPDaemon($id);
+$foreground = have_option('f', 'foreground');
+
+$daemon = new XMPPDaemon($id, !$foreground);
$daemon->runOnce();
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index 78fcd7ece..69d1bec59 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -206,7 +206,10 @@ border-radius:4px;
padding:0 7px;
}
-
+.form_settings input.form_action-default {
+margin-right:11px;
+}
+.form_settings input.form_action-default,
.form_settings input.form_action-primary {
padding:0;
}
@@ -270,7 +273,6 @@ clear:both;
margin-bottom:18px;
}
-
#anon_notice {
float:left;
width:43.2%;
@@ -285,7 +287,6 @@ font-size:1.1em;
font-weight:bold;
}
-
#footer {
float:left;
width:64%;
@@ -588,16 +589,16 @@ font-weight:normal;
content: ")";
font-weight:normal;
}
-
-.entity_profile dt {
-display:none;
-}
+.entity_profile dt,
.entity_profile h2 {
display:none;
}
+.entity_profile .role {
+margin-left:11px;
+font-style:italic;
+}
/* entity_profile */
-
/*entity_actions*/
.entity_actions {
float:right;
@@ -726,7 +727,6 @@ margin-bottom:0;
min-height:60px;
}
-
.profile .form_group_join legend,
.profile .form_group_leave legend,
.profile .form_user_subscribe legend,
@@ -761,13 +761,11 @@ display:inline;
margin-right:11px;
}
-
.profile .entity_profile .form_subscription_edit label {
font-weight:normal;
margin-right:11px;
}
-
/* NOTICE */
.notice,
.profile {
@@ -790,7 +788,6 @@ width:95%;
float:left;
}
-
/* NOTICES */
#notices_primary {
float:left;
@@ -962,7 +959,6 @@ border:0;
padding:0;
}
-
.notice .attachment {
position:relative;
padding-left:16px;
@@ -1031,7 +1027,9 @@ border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
-
+#jOverlayContent #content img {
+max-width:480px;
+}
#attachment_view #oembed_info {
margin-top:11px;
}
@@ -1059,7 +1057,6 @@ margin-bottom:18px;
padding-left:20px;
}
-
#filter_tags {
margin-bottom:11px;
float:left;
@@ -1105,8 +1102,6 @@ top:3px;
left:3px;
}
-
-
.pagination {
float:left;
clear:both;
@@ -1152,7 +1147,6 @@ padding-right:30px;
}
/* END: NOTICE */
-
.hentry .entry-content p {
margin-bottom:18px;
}
@@ -1169,7 +1163,6 @@ margin-bottom:18px;
margin-left:18px;
}
-
/* TOP_POSTERS */
.section tbody td {
padding-right:18px;
@@ -1197,7 +1190,6 @@ margin-right:0;
display:none;
}
-
/* tagcloud */
.tag-cloud {
list-style-type:none;
@@ -1280,6 +1272,10 @@ clear:both;
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 #color-picker {
float:left;