summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Shorturl_api.php3
-rw-r--r--lib/accountsettingsaction.php3
-rw-r--r--lib/action.php203
-rw-r--r--lib/attachmentlist.php300
-rw-r--r--lib/attachmentnoticesection.php75
-rw-r--r--lib/attachmentsection.php80
-rw-r--r--lib/attachmenttagcloudsection.php83
-rw-r--r--lib/clienterroraction.php15
-rw-r--r--lib/common.php41
-rw-r--r--lib/error.php2
-rw-r--r--lib/facebookutil.php15
-rw-r--r--lib/featureduserssection.php9
-rw-r--r--lib/frequentattachmentsection.php66
-rw-r--r--lib/galleryaction.php13
-rw-r--r--lib/grouplist.php2
-rw-r--r--lib/groupminilist.php7
-rw-r--r--lib/groupsbymemberssection.php2
-rw-r--r--lib/groupsbypostssection.php2
-rw-r--r--lib/grouptagcloudsection.php8
-rw-r--r--lib/jabber.php61
-rw-r--r--lib/jsonsearchresultslist.php270
-rw-r--r--lib/language.php61
-rw-r--r--lib/logingroupnav.php20
-rw-r--r--lib/mail.php17
-rw-r--r--lib/mailbox.php3
-rw-r--r--lib/messageform.php10
-rw-r--r--lib/noticeform.php9
-rw-r--r--lib/noticelist.php97
-rw-r--r--lib/noticesection.php42
-rw-r--r--lib/oauthstore.php9
-rw-r--r--lib/omb.php13
-rw-r--r--lib/openid.php7
-rw-r--r--lib/peoplesearchresults.php75
-rw-r--r--lib/personal.php2
-rw-r--r--lib/personalgroupnav.php68
-rw-r--r--lib/personaltagcloudsection.php10
-rw-r--r--lib/ping.php122
-rw-r--r--lib/popularnoticesection.php29
-rw-r--r--lib/profileaction.php248
-rw-r--r--lib/profilelist.php26
-rw-r--r--lib/profileminilist.php2
-rw-r--r--lib/publicgroupnav.php28
-rw-r--r--lib/queuehandler.php91
-rw-r--r--lib/router.php178
-rw-r--r--lib/rssaction.php42
-rw-r--r--lib/search_engines.php34
-rw-r--r--lib/searchaction.php33
-rw-r--r--lib/servererroraction.php20
-rw-r--r--lib/settingsaction.php4
-rw-r--r--lib/subgroupnav.php68
-rw-r--r--lib/subpeopletagcloudsection.php77
-rw-r--r--lib/subscriberspeopleselftagcloudsection.php62
-rw-r--r--lib/subscriberspeopletagcloudsection.php61
-rw-r--r--lib/subscriptionspeopleselftagcloudsection.php61
-rw-r--r--lib/subscriptionspeopletagcloudsection.php61
-rw-r--r--lib/tagcloudsection.php6
-rw-r--r--lib/theme.php29
-rw-r--r--lib/topposterssection.php2
-rw-r--r--lib/twitter.php228
-rw-r--r--lib/twitterapi.php213
-rw-r--r--lib/util.php305
61 files changed, 3032 insertions, 701 deletions
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
index fe106cb83..924aa93a8 100644
--- a/lib/Shorturl_api.php
+++ b/lib/Shorturl_api.php
@@ -22,6 +22,7 @@ if (!defined('LACONICA')) { exit(1); }
class ShortUrlApi
{
protected $service_url;
+ protected $long_limit = 27;
function __construct($service_url)
{
@@ -39,7 +40,7 @@ class ShortUrlApi
}
private function is_long($url) {
- return strlen($url) >= 30;
+ return strlen($url) >= $this->long_limit;
}
protected function http_post($data) {
diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php
index 46090b8c1..86800d2a3 100644
--- a/lib/accountsettingsaction.php
+++ b/lib/accountsettingsaction.php
@@ -115,6 +115,9 @@ class AccountSettingsNav extends Widget
'openidsettings' =>
array(_('OpenID'),
_('Add or remove OpenIDs')),
+ 'designsettings' =>
+ array(_('Design'),
+ _('Design your profile')),
'othersettings' =>
array(_('Other'),
_('Other options')));
diff --git a/lib/action.php b/lib/action.php
index 79e8c9547..6a69d2651 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -93,10 +93,22 @@ class Action extends HTMLOutputter // lawsuit
*/
function showPage()
{
- $this->startHTML();
- $this->showHead();
- $this->showBody();
- $this->endHTML();
+ if (Event::handle('StartShowHTML', array($this))) {
+ $this->startHTML();
+ Event::handle('EndShowHTML', array($this));
+ }
+ if (Event::handle('StartShowHead', array($this))) {
+ $this->showHead();
+ Event::handle('EndShowHead', array($this));
+ }
+ if (Event::handle('StartShowBody', array($this))) {
+ $this->showBody();
+ Event::handle('EndShowBody', array($this));
+ }
+ if (Event::handle('StartEndHTML', array($this))) {
+ $this->endHTML();
+ Event::handle('EndEndHTML', array($this));
+ }
}
/**
@@ -109,8 +121,10 @@ class Action extends HTMLOutputter // lawsuit
// XXX: attributes (profile?)
$this->elementStart('head');
$this->showTitle();
+ $this->showShortcutIcon();
$this->showStylesheets();
$this->showScripts();
+ $this->showRelationshipLinks();
$this->showOpenSearch();
$this->showFeeds();
$this->showDescription();
@@ -145,6 +159,32 @@ class Action extends HTMLOutputter // lawsuit
}
/**
+ * Show themed shortcut icon
+ *
+ * @return nothing
+ */
+ function showShortcutIcon()
+ {
+ if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/favicon.ico')) {
+ $this->element('link', array('rel' => 'shortcut icon',
+ 'href' => theme_path('favicon.ico')));
+ } else {
+ $this->element('link', array('rel' => 'shortcut icon',
+ 'href' => common_path('favicon.ico')));
+ }
+
+ if (common_config('site', 'mobile')) {
+ if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/apple-touch-icon.png')) {
+ $this->element('link', array('rel' => 'apple-touch-icon',
+ 'href' => theme_path('apple-touch-icon.png')));
+ } else {
+ $this->element('link', array('rel' => 'apple-touch-icon',
+ 'href' => common_path('apple-touch-icon.png')));
+ }
+ }
+ }
+
+ /**
* Show stylesheets
*
* @return nothing
@@ -153,21 +193,21 @@ class Action extends HTMLOutputter // lawsuit
{
if (Event::handle('StartShowStyles', array($this))) {
if (Event::handle('StartShowLaconicaStyles', array($this))) {
-
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
- 'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION,
- 'media' => 'screen, projection, tv'));
-
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/modal.css', 'base') . '?version=' . LACONICA_VERSION,
+ 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
'media' => 'screen, projection, tv'));
+ if (common_config('site', 'mobile')) {
+ $this->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
+ // TODO: "handheld" CSS for other mobile devices
+ 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
+ }
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
- 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
- 'media' => 'screen, projection, tv'));
+ 'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION,
+ 'media' => 'print'));
Event::handle('EndShowLaconicaStyles', array($this));
}
if (Event::handle('StartShowUAStyles', array($this))) {
@@ -204,9 +244,8 @@ class Action extends HTMLOutputter // lawsuit
'src' => common_path('js/jquery.form.js')),
' ');
-
$this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.simplemodal-1.2.2.pack.js')),
+ 'src' => common_path('js/jquery.joverlay.min.js')),
' ');
@@ -219,18 +258,9 @@ class Action extends HTMLOutputter // lawsuit
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
' ');
-
-
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/flowplayer-3.0.5.min.js')),
- ' ');
-
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/video.js')),
- ' ');
-
-
-
+ // Frame-busting code to avoid clickjacking attacks.
+ $this->element('script', array('type' => 'text/javascript'),
+ 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
Event::handle('EndShowLaconicaScripts', array($this));
}
Event::handle('EndShowScripts', array($this));
@@ -238,6 +268,19 @@ class Action extends HTMLOutputter // lawsuit
}
/**
+ * Show document relationship links
+ *
+ * SHOULD overload
+ *
+ * @return nothing
+ */
+ function showRelationshipLinks()
+ {
+ // output <link> elements with appropriate HTML4.01 link types:
+ // http://www.w3.org/TR/html401/types.html#type-links
+ }
+
+ /**
* Show OpenSearch headers
*
* @return nothing
@@ -308,7 +351,9 @@ class Action extends HTMLOutputter // lawsuit
*/
function showBody()
{
- $this->elementStart('body', array('id' => $this->trimmed('action')));
+ $this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
+ 'class' => 'user_in')
+ : array('id' => $this->trimmed('action')));
$this->elementStart('div', array('id' => 'wrap'));
if (Event::handle('StartShowHeader', array($this))) {
$this->showHeader();
@@ -382,13 +427,8 @@ class Action extends HTMLOutputter // lawsuit
if ($user) {
$this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
- }
- $this->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
- if ($user) {
$this->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
-
if (common_config('xmpp', 'enabled')) {
$this->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
@@ -396,20 +436,28 @@ class Action extends HTMLOutputter // lawsuit
$this->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
+ $this->menuItem(common_local_url('invite'),
+ _('Invite'),
+ sprintf(_('Invite friends and colleagues to join you on %s'),
+ common_config('site', 'name')),
+ false, 'nav_invitecontact');
$this->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout');
- } else {
- $this->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
+ }
+ else {
if (!common_config('site', 'closed')) {
$this->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
}
$this->menuItem(common_local_url('openidlogin'),
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
+ $this->menuItem(common_local_url('login'),
+ _('Login'), _('Login to the site'), false, 'nav_login');
}
$this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
+ $this->menuItem(common_local_url('peoplesearch'),
+ _('Search'), _('Search for people or text'), false, 'nav_search');
Event::handle('EndPrimaryNav', array($this));
}
$this->elementEnd('ul');
@@ -472,7 +520,10 @@ class Action extends HTMLOutputter // lawsuit
function showCore()
{
$this->elementStart('div', array('id' => 'core'));
- $this->showLocalNavBlock();
+ if (Event::handle('StartShowLocalNavBlock', array($this))) {
+ $this->showLocalNavBlock();
+ Event::handle('EndShowLocalNavBlock', array($this));
+ }
if (Event::handle('StartShowContentBlock', array($this))) {
$this->showContentBlock();
Event::handle('EndShowContentBlock', array($this));
@@ -546,7 +597,10 @@ class Action extends HTMLOutputter // lawsuit
'class' => 'system_notice'));
$this->element('dt', null, _('Page notice'));
$this->elementStart('dd');
- $this->showPageNotice();
+ if (Event::handle('StartShowPageNotice', array($this))) {
+ $this->showPageNotice();
+ Event::handle('EndShowPageNotice', array($this));
+ }
$this->elementEnd('dd');
$this->elementEnd('dl');
}
@@ -583,7 +637,10 @@ class Action extends HTMLOutputter // lawsuit
{
$this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside'));
- $this->showExportData();
+ if (Event::handle('StartShowExportData', array($this))) {
+ $this->showExportData();
+ Event::handle('EndShowExportData', array($this));
+ }
if (Event::handle('StartShowSections', array($this))) {
$this->showSections();
Event::handle('EndShowSections', array($this));
@@ -655,6 +712,8 @@ class Action extends HTMLOutputter // lawsuit
_('Source'));
$this->menuItem(common_local_url('doc', array('title' => 'contact')),
_('Contact'));
+ $this->menuItem(common_local_url('doc', array('title' => 'badge')),
+ _('Badge'));
Event::handle('EndSecondaryNav', array($this));
}
$this->elementEnd('ul');
@@ -751,9 +810,12 @@ class Action extends HTMLOutputter // lawsuit
*
* MAY override
*
+ * @param array $args other arguments
+ *
* @return boolean is read only action?
*/
- function isReadOnly()
+
+ function isReadOnly($args)
{
return false;
}
@@ -805,12 +867,15 @@ class Action extends HTMLOutputter // lawsuit
}
if ($lm) {
header('Last-Modified: ' . date(DATE_RFC1123, $lm));
- $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
- if ($if_modified_since) {
+ if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
+ $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
$ims = strtotime($if_modified_since);
if ($lm <= $ims) {
- if (!$etag ||
- $this->_hasEtag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
+ $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
+ $_SERVER['HTTP_IF_NONE_MATCH'] : null;
+ if (!$if_none_match ||
+ !$etag ||
+ $this->_hasEtag($etag, $if_none_match)) {
header('HTTP/1.1 304 Not Modified');
// Better way to do this?
exit(0);
@@ -828,9 +893,11 @@ class Action extends HTMLOutputter // lawsuit
*
* @return boolean
*/
+
function _hasEtag($etag, $if_none_match)
{
- return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
+ $etags = explode(',', $if_none_match);
+ return in_array($etag, $etags) || in_array('*', $etags);
}
/**
@@ -893,11 +960,15 @@ class Action extends HTMLOutputter // lawsuit
*
* @return string current URL
*/
+
function selfUrl()
{
$action = $this->trimmed('action');
$args = $this->args;
unset($args['action']);
+ if (array_key_exists('submit', $args)) {
+ unset($args['submit']);
+ }
foreach (array_keys($_COOKIE) as $cookie) {
unset($args[$cookie]);
}
@@ -958,17 +1029,17 @@ class Action extends HTMLOutputter // lawsuit
}
if ($have_before) {
$pargs = array('page' => $page-1);
- $newargs = $args ? array_merge($args, $pargs) : $pargs;
$this->elementStart('li', array('class' => 'nav_prev'));
- $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'prev'),
+ $this->element('a', array('href' => common_local_url($action, $args, $pargs),
+ 'rel' => 'prev'),
_('After'));
$this->elementEnd('li');
}
if ($have_after) {
$pargs = array('page' => $page+1);
- $newargs = $args ? array_merge($args, $pargs) : $pargs;
$this->elementStart('li', array('class' => 'nav_next'));
- $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'next'),
+ $this->element('a', array('href' => common_local_url($action, $args, $pargs),
+ 'rel' => 'next'),
_('Before'));
$this->elementEnd('li');
}
@@ -992,4 +1063,36 @@ class Action extends HTMLOutputter // lawsuit
{
return null;
}
+
+ /**
+ * Generate document metadata for sequential navigation
+ *
+ * @param boolean $have_before is there something before?
+ * @param boolean $have_after is there something after?
+ * @param integer $page current page
+ * @param string $action current action
+ * @param array $args rest of query arguments
+ *
+ * @return nothing
+ */
+ function sequenceRelationships($have_next, $have_previous, $page, $action, $args=null)
+ {
+ // Outputs machine-readable pagination in <link> elements.
+ // Pattern taken from $this->pagination() method.
+
+ // "next" is equivalent to "after"
+ if ($have_next) {
+ $pargs = array('page' => $page-1);
+ $this->element('link', array('rel' => 'next',
+ 'href' => common_local_url($action, $args, $pargs),
+ 'title' => _('Next')));
+ }
+ // "previous" is equivalent to "before"
+ if ($have_previous=true) { // FIXME
+ $pargs = array('page' => $page+1);
+ $this->element('link', array('rel' => 'prev',
+ 'href' => common_local_url($action, $args, $pargs),
+ 'title' => _('Previous')));
+ }
+ }
}
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
new file mode 100644
index 000000000..9485fe3d6
--- /dev/null
+++ b/lib/attachmentlist.php
@@ -0,0 +1,300 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * widget for displaying a list of notice attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category UI
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * widget for displaying a list of notice attachments
+ *
+ * There are a number of actions that display a list of notices, in
+ * reverse chronological order. This widget abstracts out most of the
+ * code for UI for notice lists. It's overridden to hide some
+ * data for e.g. the profile page.
+ *
+ * @category UI
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ * @see Notice
+ * @see StreamAction
+ * @see NoticeListItem
+ * @see ProfileNoticeList
+ */
+
+class AttachmentList extends Widget
+{
+ /** the current stream of notices being displayed. */
+
+ var $notice = null;
+
+ /**
+ * constructor
+ *
+ * @param Notice $notice stream of notices from DB_DataObject
+ */
+
+ function __construct($notice, $out=null)
+ {
+ parent::__construct($out);
+ $this->notice = $notice;
+ }
+
+ /**
+ * show the list of notices
+ *
+ * "Uses up" the stream by looping through it. So, probably can't
+ * be called twice on the same list.
+ *
+ * @return int count of notices listed.
+ */
+
+ function show()
+ {
+// $this->out->elementStart('div', array('id' =>'attachments_primary'));
+ $this->out->elementStart('div', array('id' =>'content'));
+ $this->out->element('h2', null, _('Attachments'));
+ $this->out->elementStart('ul', array('class' => 'attachments'));
+
+ $atts = new File;
+ $att = $atts->getAttachments($this->notice->id);
+ foreach ($att as $n=>$attachment) {
+ $item = $this->newListItem($attachment);
+ $item->show();
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
+
+ return count($att);
+ }
+
+ /**
+ * returns a new list item for the current notice
+ *
+ * Recipe (factory?) method; overridden by sub-classes to give
+ * a different list item class.
+ *
+ * @param Notice $notice the current notice
+ *
+ * @return NoticeListItem a list item for displaying the notice
+ */
+
+ function newListItem($attachment)
+ {
+ return new AttachmentListItem($attachment, $this->out);
+ }
+}
+
+/**
+ * widget for displaying a single notice
+ *
+ * This widget has the core smarts for showing a single notice: what to display,
+ * where, and under which circumstances. Its key method is show(); this is a recipe
+ * that calls all the other show*() methods to build up a single notice. The
+ * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
+ * author info (since that's implicit by the data in the page).
+ *
+ * @category UI
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ * @see NoticeList
+ * @see ProfileNoticeListItem
+ */
+
+class AttachmentListItem extends Widget
+{
+ /** The attachment this item will show. */
+
+ var $attachment = null;
+
+ var $oembed = null;
+
+ /**
+ * constructor
+ *
+ * Also initializes the profile attribute.
+ *
+ * @param Notice $notice The notice we'll display
+ */
+
+ function __construct($attachment, $out=null)
+ {
+ parent::__construct($out);
+ $this->attachment = $attachment;
+ $this->oembed = File_oembed::staticGet('file_id', $this->attachment->id);
+ }
+
+ function title() {
+ if (empty($this->attachment->title)) {
+ if (empty($this->oembed->title)) {
+ $title = $this->attachment->url;
+ } else {
+ $title = $this->oembed->title;
+ }
+ } else {
+ $title = $this->attachment->title;
+ }
+
+ return $title;
+ }
+
+ function linkTitle() {
+ return 'Our page for ' . $this->title();
+ }
+
+ /**
+ * recipe function for displaying a single notice.
+ *
+ * This uses all the other methods to correctly display a notice. Override
+ * it or one of the others to fine-tune the output.
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $this->showStart();
+ $this->showNoticeAttachment();
+ $this->showEnd();
+ }
+
+ function linkAttr() {
+ return array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $this->attachment->id)));
+ }
+
+ function showLink() {
+ $attr = $this->linkAttr();
+ $text = $this->linkTitle();
+ $this->out->elementStart('h4');
+ $this->out->element('a', $attr, $text);
+
+ if ($this->attachment->url !== $this->title())
+ $this->out->element('span', null, " ({$this->attachment->url})");
+
+
+ $this->out->elementEnd('h4');
+ }
+
+ function showNoticeAttachment()
+ {
+ $this->showLink();
+ $this->showRepresentation();
+ }
+
+ function showRepresentation() {
+ $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
+ if (!empty($thumbnail)) {
+ $this->out->elementStart('a', $this->linkAttr()/*'href' => $this->linkTo()*/);
+ $this->out->element('img', array('alt' => 'nothing to say', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height));
+ $this->out->elementEnd('a');
+ }
+ }
+
+ /**
+ * start a single notice.
+ *
+ * @return void
+ */
+
+ function showStart()
+ {
+ // XXX: RDFa
+ // TODO: add notice_type class e.g., notice_video, notice_image
+ $this->out->elementStart('li');
+ }
+
+ /**
+ * finish the notice
+ *
+ * Close the last elements in the notice list item
+ *
+ * @return void
+ */
+
+ function showEnd()
+ {
+ $this->out->elementEnd('li');
+ }
+}
+
+class Attachment extends AttachmentListItem
+{
+ function show() {
+ $this->showNoticeAttachment();
+ }
+
+ function linkAttr() {
+ return array('class' => 'external', 'href' => $this->attachment->url);
+ }
+
+ function linkTitle() {
+ return 'Direct link to ' . $this->title();
+ }
+
+ function showRepresentation() {
+ if (empty($this->oembed->type)) {
+ if (empty($this->attachment->mimetype)) {
+ $this->out->element('pre', null, 'oh well... not sure how to handle the following: ' . print_r($this->attachment, true));
+ } else {
+ switch ($this->attachment->mimetype) {
+ case 'image/gif':
+ case 'image/png':
+ case 'image/jpg':
+ case 'image/jpeg':
+ $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
+ break;
+ }
+ }
+ } else {
+ switch ($this->oembed->type) {
+ case 'rich':
+ case 'video':
+ case 'link':
+ if (!empty($this->oembed->html)) {
+ $this->out->raw($this->oembed->html);
+ }
+ break;
+
+ case 'photo':
+ $this->out->element('img', array('src' => $this->oembed->url, 'width' => $this->oembed->width, 'height' => $this->oembed->height, 'alt' => 'alt'));
+ break;
+
+ default:
+ $this->out->element('pre', null, 'oh well... not sure how to handle the following oembed: ' . print_r($this->oembed, true));
+ }
+ }
+ }
+}
+
diff --git a/lib/attachmentnoticesection.php b/lib/attachmentnoticesection.php
new file mode 100644
index 000000000..eb3176376
--- /dev/null
+++ b/lib/attachmentnoticesection.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * FIXME
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * FIXME
+ *
+ * These are the widgets that show interesting data about a person * group, or site.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class AttachmentNoticeSection extends NoticeSection
+{
+ function showContent() {
+ parent::showContent();
+ return false;
+ }
+
+ function getNotices()
+ {
+ $notice = new Notice;
+ $f2p = new File_to_post;
+ $f2p->file_id = $this->out->attachment->id;
+ $notice->joinAdd($f2p);
+ $notice->orderBy('created desc');
+ $notice->selectAdd('post_id as id');
+ $notice->find();
+ return $notice;
+ }
+
+ function title()
+ {
+ return _('Notices where this attachment appears');
+ }
+
+ function divId()
+ {
+ return 'popular_notices';
+ }
+}
+
diff --git a/lib/attachmentsection.php b/lib/attachmentsection.php
new file mode 100644
index 000000000..20e620b9b
--- /dev/null
+++ b/lib/attachmentsection.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists of attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+define('ATTACHMENTS_PER_SECTION', 6);
+
+/**
+ * Base class for sections showing lists of attachments
+ *
+ * These are the widgets that show interesting data about a person
+ * group, or site.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class AttachmentSection extends Section
+{
+ function showContent()
+ {
+ $attachments = $this->getAttachments();
+
+ $cnt = 0;
+
+ $this->out->elementStart('ul', 'attachments');
+
+ while ($attachments->fetch() && ++$cnt <= ATTACHMENTS_PER_SECTION) {
+ $this->showAttachment($attachments);
+ }
+
+ $this->out->elementEnd('ul');
+
+ return ($cnt > ATTACHMENTS_PER_SECTION);
+ }
+
+ function getAttachments()
+ {
+ return null;
+ }
+
+ function showAttachment($attachment)
+ {
+ $this->out->elementStart('li');
+ $this->out->element('a', array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $attachment->file_id))), "Attachment tagged {$attachment->c} times");
+ $this->out->elementEnd('li');
+ }
+}
+
diff --git a/lib/attachmenttagcloudsection.php b/lib/attachmenttagcloudsection.php
new file mode 100644
index 000000000..50bfceccb
--- /dev/null
+++ b/lib/attachmenttagcloudsection.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Attachment tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Attachment tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class AttachmentTagCloudSection extends TagCloudSection
+{
+ function title()
+ {
+ return _('Tags for this attachment');
+ }
+
+ function showTag($tag, $weight, $relative)
+ {
+ if ($relative > 0.5) {
+ $rel = 'tag-cloud-7';
+ } else if ($relative > 0.4) {
+ $rel = 'tag-cloud-6';
+ } else if ($relative > 0.3) {
+ $rel = 'tag-cloud-5';
+ } else if ($relative > 0.2) {
+ $rel = 'tag-cloud-4';
+ } else if ($relative > 0.1) {
+ $rel = 'tag-cloud-3';
+ } else if ($relative > 0.05) {
+ $rel = 'tag-cloud-2';
+ } else {
+ $rel = 'tag-cloud-1';
+ }
+
+ $this->out->elementStart('li', $rel);
+ $this->out->element('a', array('href' => $this->tagUrl($tag)),
+ $tag);
+ $this->out->elementEnd('li');
+ }
+
+ function getTags()
+ {
+ $notice_tag = new Notice_tag;
+ $query = 'select tag,count(tag) as weight from notice_tag join file_to_post on (notice_tag.notice_id=post_id) join notice on notice_id = notice.id where file_id=' . $notice_tag->escape($this->out->attachment->id) . ' group by tag order by weight desc';
+ $notice_tag->query($query);
+ return $notice_tag;
+ }
+}
+
diff --git a/lib/clienterroraction.php b/lib/clienterroraction.php
index 5019dc06d..0c48414d5 100644
--- a/lib/clienterroraction.php
+++ b/lib/clienterroraction.php
@@ -49,7 +49,7 @@ class ClientErrorAction extends ErrorAction
function __construct($message='Error', $code=400)
{
parent::__construct($message, $code);
-
+
$this->status = array(400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
@@ -72,7 +72,7 @@ class ClientErrorAction extends ErrorAction
}
// XXX: Should these error actions even be invokable via URI?
-
+
function handle($args)
{
parent::handle($args);
@@ -84,11 +84,16 @@ class ClientErrorAction extends ErrorAction
}
$this->message = $this->trimmed('message');
-
+
if (!$this->message) {
- $this->message = "Client Error $this->code";
- }
+ $this->message = "Client Error $this->code";
+ }
$this->showPage();
}
+
+ function title()
+ {
+ return $this->status[$this->code];
+ }
}
diff --git a/lib/common.php b/lib/common.php
index 4fc749ca0..abdc22c0e 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-define('LACONICA_VERSION', '0.7.1');
+define('LACONICA_VERSION', '0.7.3');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
@@ -71,8 +71,11 @@ $config =
array('name' => 'Just another Laconica microblog',
'server' => $_server,
'theme' => 'default',
+ 'skin' => 'default',
'path' => $_path,
'logfile' => null,
+ 'logo' => null,
+ 'logdebug' => false,
'fancy' => false,
'locale_path' => INSTALLDIR.'/locale',
'language' => 'en_US',
@@ -84,7 +87,10 @@ $config =
'broughtbyurl' => null,
'closed' => false,
'inviteonly' => false,
- 'private' => false),
+ 'private' => false,
+ 'ssl' => 'never',
+ 'sslserver' => null,
+ 'dupelimit' => 60), # default for same person saying the same thing
'syslog' =>
array('appname' => 'laconica', # for syslog
'priority' => 'debug'), # XXX: currently ignored
@@ -137,14 +143,22 @@ $config =
array('piddir' => '/var/run',
'user' => false,
'group' => false),
+ 'twitterbridge' =>
+ array('enabled' => false),
'integration' =>
- array('source' => 'Laconica'), # source attribute for Twitter
+ array('source' => 'Laconica', # source attribute for Twitter
+ 'taguri' => $_server.',2009'), # base for tag URIs
'memcached' =>
array('enabled' => false,
'server' => 'localhost',
'port' => 11211),
+ 'ping' =>
+ array('notify' => array()),
'inboxes' =>
array('enabled' => true), # on by default for new sites
+ 'newuser' =>
+ array('subscribe' => null,
+ 'welcome' => null),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@@ -178,12 +192,31 @@ if (strlen($_path) > 0) {
$_config_files[] = INSTALLDIR.'/config.php';
+$_have_a_config = false;
+
foreach ($_config_files as $_config_file) {
- if (file_exists($_config_file)) {
+ if (@file_exists($_config_file)) {
include_once($_config_file);
+ $_have_a_config = true;
}
}
+function _have_config()
+{
+ global $_have_a_config;
+ return $_have_a_config;
+}
+
+// XXX: Throw a conniption if database not installed
+
+// Fixup for laconica.ini
+
+$_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1);
+
+if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db'])) {
+ $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini';
+}
+
// XXX: how many of these could be auto-loaded on use?
require_once('Validate.php');
diff --git a/lib/error.php b/lib/error.php
index 526d9f81b..282682133 100644
--- a/lib/error.php
+++ b/lib/error.php
@@ -93,7 +93,7 @@ class ErrorAction extends Action
return $this->message;
}
- function isReadOnly()
+ function isReadOnly($args)
{
return true;
}
diff --git a/lib/facebookutil.php b/lib/facebookutil.php
index ec3987273..242d2e06f 100644
--- a/lib/facebookutil.php
+++ b/lib/facebookutil.php
@@ -27,9 +27,21 @@ define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
function getFacebook()
{
+ static $facebook = null;
+
$apikey = common_config('facebook', 'apikey');
$secret = common_config('facebook', 'secret');
- return new Facebook($apikey, $secret);
+
+ if ($facebook === null) {
+ $facebook = new Facebook($apikey, $secret);
+ }
+
+ if (!$facebook) {
+ common_log(LOG_ERR, 'Could not make new Facebook client obj!',
+ __FILE__);
+ }
+
+ return $facebook;
}
function updateProfileBox($facebook, $flink, $notice) {
@@ -92,7 +104,6 @@ function isFacebookBound($notice, $flink) {
}
-
function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();
diff --git a/lib/featureduserssection.php b/lib/featureduserssection.php
index aed94b1a5..4b9238d47 100644
--- a/lib/featureduserssection.php
+++ b/lib/featureduserssection.php
@@ -57,9 +57,14 @@ class FeaturedUsersSection extends ProfileSection
$quoted[] = "'$nick'";
}
+ $table = "user";
+ if(common_config('db','quote_identifiers')) {
+ $table = '"' . $table . '"';
+ }
+
$qry = 'SELECT profile.* ' .
- 'FROM profile JOIN user on profile.id = user.id ' .
- 'WHERE user.nickname in (' . implode(',', $quoted) . ') ' .
+ 'FROM profile JOIN '. $table .' on profile.id = '. $table .'.id ' .
+ 'WHERE '. $table .'.nickname in (' . implode(',', $quoted) . ') ' .
'ORDER BY profile.created DESC ';
$limit = PROFILES_PER_SECTION + 1;
diff --git a/lib/frequentattachmentsection.php b/lib/frequentattachmentsection.php
new file mode 100644
index 000000000..0ce0d1871
--- /dev/null
+++ b/lib/frequentattachmentsection.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * FIXME
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * FIXME
+ *
+ * These are the widgets that show interesting data about a person
+ * group, or site.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class FrequentAttachmentSection extends AttachmentSection
+{
+ function getAttachments() {
+ $notice_tag = new Notice_tag;
+ $query = 'select file_id, count(file_id) as c from notice_tag join file_to_post on post_id = notice_id where tag="' . $notice_tag->escape($this->out->tag) . '" group by file_id order by c desc';
+ $notice_tag->query($query);
+ return $notice_tag;
+ }
+
+ function title()
+ {
+ return sprintf(_('Attachments frequently tagged with %s'), $this->out->tag);
+ }
+
+ function divId()
+ {
+ return 'frequent_attachments';
+ }
+}
+
diff --git a/lib/galleryaction.php b/lib/galleryaction.php
index 25a5e3fd5..8fa11a756 100644
--- a/lib/galleryaction.php
+++ b/lib/galleryaction.php
@@ -50,7 +50,7 @@ class GalleryAction extends Action
if ($this->arg('page') && $this->arg('page') != 1) {
$args['page'] = $this->arg['page'];
}
- common_redirect(common_local_url('subscriptions', $args), 301);
+ common_redirect(common_local_url($this->trimmed('action'), $args), 301);
return false;
}
@@ -71,11 +71,12 @@ class GalleryAction extends Action
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
$this->tag = $this->trimmed('tag');
+ $this->q = $this->trimmed('q');
return true;
}
- function isReadOnly()
+ function isReadOnly($args)
{
return true;
}
@@ -87,7 +88,7 @@ class GalleryAction extends Action
# Post from the tag dropdown; redirect to a GET
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- common_redirect($this->selfUrl(), 307);
+ common_redirect($this->selfUrl(), 303);
return;
}
@@ -133,10 +134,12 @@ class GalleryAction extends Action
$this->elementStart('li', array('id'=>'filter_tags_item'));
$this->elementStart('form', array('name' => 'bytag',
'id' => 'bytag',
+ 'action' => common_path('?action=' . $this->trimmed('action')),
'method' => 'post'));
$this->dropdown('tag', _('Tag'), $content,
_('Choose a tag to narrow list'), false, $tag);
- $this->submit('go', _('Go'));
+ $this->hidden('nickname', $this->user->nickname);
+ $this->submit('submit', _('Go'));
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');
@@ -168,4 +171,4 @@ class GalleryAction extends Action
{
return array();
}
-} \ No newline at end of file
+}
diff --git a/lib/grouplist.php b/lib/grouplist.php
index 6801ab426..1b8547499 100644
--- a/lib/grouplist.php
+++ b/lib/grouplist.php
@@ -151,7 +151,7 @@ class GroupList extends Widget
# If we're on a list with an owner (subscriptions or subscribers)...
- if ($user && $user->id == $this->owner->id) {
+ if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) {
$this->showOwnerControls();
}
diff --git a/lib/groupminilist.php b/lib/groupminilist.php
index fe38d0340..ae2d237f1 100644
--- a/lib/groupminilist.php
+++ b/lib/groupminilist.php
@@ -33,7 +33,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/grouplist.php';
-define('GROUPS_PER_MINILIST', 80);
+define('GROUPS_PER_MINILIST', 27);
/**
* Widget to show a list of groups, good for sidebar
@@ -75,8 +75,9 @@ class GroupMiniList extends GroupList
'href' => $this->group->homeUrl(),
'rel' => 'contact group',
'class' => 'url'));
- $logo = ($this->group->stream_logo) ?
- $this->group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
+
+ $logo = ($this->group->mini_logo) ?
+ $this->group->mini_logo : User_group::defaultLogo(AVATAR_MINI_SIZE);
$this->out->element('img', array('src' => $logo,
'width' => AVATAR_MINI_SIZE,
diff --git a/lib/groupsbymemberssection.php b/lib/groupsbymemberssection.php
index 4fa07a244..963e21f15 100644
--- a/lib/groupsbymemberssection.php
+++ b/lib/groupsbymemberssection.php
@@ -48,7 +48,7 @@ class GroupsByMembersSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_member '.
'ON user_group.id = group_member.group_id ' .
- 'GROUP BY user_group.id ' .
+ 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;
diff --git a/lib/groupsbypostssection.php b/lib/groupsbypostssection.php
index a5e33a93d..325b4033f 100644
--- a/lib/groupsbypostssection.php
+++ b/lib/groupsbypostssection.php
@@ -48,7 +48,7 @@ class GroupsByPostsSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_inbox '.
'ON user_group.id = group_inbox.group_id ' .
- 'GROUP BY user_group.id ' .
+ 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;
diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php
index f05be85cb..5d68af28b 100644
--- a/lib/grouptagcloudsection.php
+++ b/lib/grouptagcloudsection.php
@@ -58,8 +58,14 @@ class GroupTagCloudSection extends TagCloudSection
function getTags()
{
+ if (common_config('db', 'type') == 'pgsql') {
+ $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))';
+ } else {
+ $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))';
+ }
+
$qry = 'SELECT notice_tag.tag, '.
- 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' .
+ $weightexpr . ' as weight ' .
'FROM notice_tag JOIN notice ' .
'ON notice_tag.notice_id = notice.id ' .
'JOIN group_inbox on group_inbox.notice_id = notice.id ' .
diff --git a/lib/jabber.php b/lib/jabber.php
index 3fbb3e1ab..7d584ad01 100644
--- a/lib/jabber.php
+++ b/lib/jabber.php
@@ -163,50 +163,25 @@ function jabber_send_notice($to, $notice)
function jabber_format_entry($profile, $notice)
{
- // FIXME: notice url might be remote
-
- $noticeurl = common_local_url('shownotice',
- array('notice' => $notice->id));
-
- $msg = jabber_format_notice($profile, $notice);
-
- $self_url = common_local_url('userrss', array('nickname' => $profile->nickname));
-
- $entry = "\n<entry xmlns='http://www.w3.org/2005/Atom'>\n";
- $entry .= "<source>\n";
- $entry .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
- $entry .= "<link href='" . htmlspecialchars($profile->profileurl) . "'/>\n";
- $entry .= "<link rel='self' type='application/rss+xml' href='" . $self_url . "'/>\n";
- $entry .= "<author><name>" . $profile->nickname . "</name></author>\n";
- $entry .= "<icon>" . $profile->avatarUrl(AVATAR_PROFILE_SIZE) . "</icon>\n";
- $entry .= "</source>\n";
- $entry .= "<title>" . htmlspecialchars($msg) . "</title>\n";
- $entry .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
- $entry .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
- $entry .= "<id>". $notice->uri . "</id>\n";
- $entry .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
- $entry .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
- if ($notice->reply_to) {
- $replyurl = common_local_url('shownotice',
- array('notice' => $notice->reply_to));
- $entry .= "<link rel='related' href='" . $replyurl . "'/>\n";
+ $entry = $notice->asAtomEntry(true, true);
+
+ $xs = new XMLStringer();
+ $xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im'));
+ $xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml'));
+ $xs->element('a', array('href' => $profile->profileurl),
+ $profile->nickname);
+ $xs->text(": ");
+ if (!empty($notice->rendered)) {
+ $xs->raw($notice->rendered);
+ } else {
+ $xs->raw(common_render_content($notice->content, $notice));
}
- $entry .= "</entry>\n";
-
- $html = "\n<html xmlns='http://jabber.org/protocol/xhtml-im'>\n";
- $html .= "<body xmlns='http://www.w3.org/1999/xhtml'>\n";
- $html .= "<a href='".htmlspecialchars($profile->profileurl)."'>".$profile->nickname."</a>: ";
- $html .= ($notice->rendered) ? $notice->rendered : common_render_content($notice->content, $notice);
- $html .= "\n</body>\n";
- $html .= "\n</html>\n";
+ $xs->elementEnd('body');
+ $xs->elementEnd('html');
- $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
- $address .= "<address type='replyto' jid='" . jabber_daemon_address() . "' />\n";
- $address .= "</addresses>\n";
+ $html = $xs->getString();
- // FIXME: include a pubsub event, too.
-
- return $html . $entry . $address;
+ return $html . ' ' . $entry;
}
/**
@@ -410,8 +385,8 @@ function jabber_broadcast_notice($notice)
"ON $UT.id = notice_inbox.user_id " .
'WHERE notice_inbox.notice_id = ' . $notice->id . ' ' .
'AND notice_inbox.source = 2 ' .
- 'AND user.jabber is not null ' .
- 'AND user.jabbernotify = 1 ');
+ "AND $UT.jabber is not null " .
+ "AND $UT.jabbernotify = 1 ");
while ($user->fetch()) {
if (!array_key_exists($user->id, $sent_to)) {
diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php
new file mode 100644
index 000000000..f786c20a8
--- /dev/null
+++ b/lib/jsonsearchresultslist.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * widget for displaying a list of notices
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Search
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * widget-like class for showing JSON search results
+ *
+ * @category Search
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+
+class JSONSearchResultsList
+{
+ protected $notice; // protected attrs invisible to json_encode()
+ protected $rpp;
+
+ // The below attributes are carefully named so the JSON output from
+ // this obj matches the output from search.twitter.com
+
+ var $results;
+ var $since_id;
+ var $max_id;
+ var $refresh_url;
+ var $results_per_page;
+ var $completed_in;
+ var $page;
+ var $query;
+
+ /**
+ * constructor
+ *
+ * @param Notice $notice stream of notices from DB_DataObject
+ * @param string $query the original search query
+ * @param int $rpp the number of results to display per page
+ * @param int $page a page offset
+ * @param int $since_id only display notices newer than this
+ */
+
+ function __construct($notice, $query, $rpp, $page, $since_id = 0)
+ {
+ $this->notice = $notice;
+ $this->query = urlencode($query);
+ $this->results_per_page = $rpp;
+ $this->rpp = $rpp;
+ $this->page = $page;
+ $this->since_id = $since_id;
+ $this->results = array();
+ }
+
+ /**
+ * show the list of search results
+ *
+ * @return int $count of the search results listed.
+ */
+
+ function show()
+ {
+ $cnt = 0;
+
+ $time_start = microtime(true);
+
+ while ($this->notice->fetch() && $cnt <= $this->rpp) {
+ $cnt++;
+
+ // XXX: Hmmm. this depends on desc sort order
+ if (!$this->max_id) {
+ $this->max_id = (int)$this->notice->id;
+ }
+
+ if ($cnt > $this->rpp) {
+ break;
+ }
+
+ $item = new ResultItem($this->notice);
+ array_push($this->results, $item);
+ }
+
+ $time_end = microtime(true);
+ $this->completed_in = $time_end - $time_start;
+
+ // Set other attrs
+
+ $this->refresh_url = '?since_id=' . $this->max_id .
+ '&q=' . $this->query;
+
+ // pagination stuff
+
+ if ($cnt > $this->rpp) {
+ $this->next_page = '?page=' . ($this->page + 1) .
+ '&max_id=' . $this->max_id;
+ if ($this->rpp != 15) {
+ $this->next_page .= '&rpp=' . $this->rpp;
+ }
+ $this->next_page .= '&q=' . $this->query;
+ }
+
+ if ($this->page > 1) {
+ $this->previous_page = '?page=' . ($this->page - 1) .
+ '&max_id=' . $this->max_id;
+ if ($this->rpp != 15) {
+ $this->previous_page .= '&rpp=' . $this->rpp;
+ }
+ $this->previous_page .= '&q=' . $this->query;
+ }
+
+ print json_encode($this);
+
+ return $cnt;
+ }
+}
+
+/**
+ * widget for displaying a single JSON search result
+ *
+ * @category UI
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ * @see JSONSearchResultsList
+ */
+
+class ResultItem
+{
+ /** The notice this item is based on. */
+
+ protected $notice; // protected attrs invisible to json_encode()
+
+ /** The profile associated with the notice. */
+
+ protected $profile;
+
+ // The below attributes are carefully named so the JSON output from
+ // this obj matches the output from search.twitter.com
+
+ var $text;
+ var $to_user_id;
+ var $to_user;
+ var $from_user;
+ var $id;
+ var $from_user_id;
+ var $iso_language_code;
+ var $source;
+ var $profile_image_url;
+ var $created_at;
+
+ /**
+ * constructor
+ *
+ * Also initializes the profile attribute.
+ *
+ * @param Notice $notice The notice we'll display
+ */
+
+ function __construct($notice)
+ {
+ $this->notice = $notice;
+ $this->profile = $notice->getProfile();
+ $this->buildResult();
+ }
+
+ /**
+ * Build a search result object
+ *
+ * This populates the the result in preparation for JSON encoding.
+ *
+ * @return void
+ */
+
+ function buildResult()
+ {
+ $this->text = $this->notice->content;
+ $replier_profile = null;
+
+ if ($this->notice->reply_to) {
+ $reply = Notice::staticGet(intval($notice->reply_to));
+ if ($reply) {
+ $replier_profile = $reply->getProfile();
+ }
+ }
+
+ $this->to_user_id = ($replier_profile) ?
+ intval($replier_profile->id) : null;
+ $this->to_user = ($replier_profile) ?
+ $replier_profile->nickname : null;
+
+ $this->from_user = $this->profile->nickname;
+ $this->id = $this->notice->id;
+ $this->from_user_id = $this->profile->id;
+
+ $user = User::staticGet('id', $this->profile->id);
+
+ $this->iso_language_code = $this->user->language;
+
+ $this->source = $this->getSourceLink($this->notice->source);
+
+ $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ $this->profile_image_url = ($avatar) ?
+ $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
+
+ $this->created_at = common_date_rfc2822($this->notice->created);
+ }
+
+ /**
+ * Show the source of the notice
+ *
+ * Either the name (and link) of the API client that posted the notice,
+ * or one of other other channels.
+ *
+ * @param string $source the source of the Notice
+ *
+ * @return string a fully rendered source of the Notice
+ */
+
+ function getSourceLink($source)
+ {
+ $source_name = _($source);
+ switch ($source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ break;
+ default:
+ $ns = Notice_source::staticGet($source);
+ if ($ns) {
+ $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+ }
+ break;
+ }
+
+ return $source_name;
+ }
+
+}
diff --git a/lib/language.php b/lib/language.php
index a73b73f28..cd6498d30 100644
--- a/lib/language.php
+++ b/lib/language.php
@@ -94,40 +94,43 @@ function get_nice_language_list()
* Get a list of all languages that are enabled in the default config
*
* This should ONLY be called when setting up the default config in common.php.
- * Any other attempt to get a list of lanugages should instead call
+ * Any other attempt to get a list of languages should instead call
* common_config('site','languages')
*
* @return array mapping of language codes to language info
*/
function get_all_languages() {
return array(
- 'bg' => array('q' => 0.8, 'lang' => 'bg_BG', 'name' => 'Bulgarian', 'direction' => 'ltr'),
- 'ca' => array('q' => 0.5, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'),
- 'cs' => array('q' => 0.5, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'),
- 'de' => array('q' => 0.5, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'),
- 'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'),
- 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
- 'en-gb' => array('q' => 0.3, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
- 'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'),
- 'es' => array('q' => 0.5, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'),
- 'fr-fr' => array('q' => 0.2, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),
- 'he' => array('q' => 0.5, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'ltr'),
- 'it' => array('q' => 0.9, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'rtl'),
- 'jp' => array('q' => 0.5, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'),
-# 'ko' => array('q' => 0, 'lang' => 'ko', 'name' => 'Korean', 'direction' => 'ltr'),
- 'mk' => array('q' => 0.5, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'),
- 'nb' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (bokmal)', 'direction' => 'ltr'),
- 'nl' => array('q' => 0.5, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'),
- 'pl' => array('q' => 0.5, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'),
-# 'pt' => array('q' => 0, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'),
- 'pt-br' => array('q' => 0.7, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'),
- 'ru' => array('q' => 0.1, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'),
- 'sv' => array('q' => 0.9, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'),
- 'te' => array('q' => 0.3, 'lang' => 'te_IN', 'name' => 'Telugu', 'direction' => 'ltr'),
- 'tr' => array('q' => 0.5, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'),
- 'uk' => array('q' => 0.7, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'),
- 'vi' => array('q' => 0.7, 'lang' => 'vi_VN', 'name' => 'Vietnamese', 'direction' => 'ltr'),
- 'zh-cn' => array('q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'),
- 'zh-hant' => array('q' => 0.2, 'lang' => 'zh_hant', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'),
+ 'bg' => array('q' => 0.8, 'lang' => 'bg_BG', 'name' => 'Bulgarian', 'direction' => 'ltr'),
+ 'ca' => array('q' => 0.5, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'),
+ 'cs' => array('q' => 0.5, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'),
+ 'de' => array('q' => 0.8, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'),
+ 'el' => array('q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'),
+ 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
+ 'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
+ 'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', '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'),
+ 'he' => array('q' => 0.5, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'rtl'),
+ 'it' => array('q' => 1, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'ltr'),
+ 'jp' => array('q' => 0.5, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'),
+ 'ko' => array('q' => 0.9, 'lang' => 'ko_KR', 'name' => 'Korean', 'direction' => 'ltr'),
+ 'mk' => array('q' => 0.5, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'),
+ 'nb' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'),
+ 'no' => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'),
+ 'nn' => array('q' => 1, 'lang' => 'nn_NO', 'name' => 'Norwegian (Nynorsk)', 'direction' => 'ltr'),
+ 'nl' => array('q' => 0.5, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'),
+ 'pl' => array('q' => 0.5, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'),
+ 'pt' => array('q' => 0.1, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'),
+ 'pt-br' => array('q' => 0.9, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'),
+ 'ru' => array('q' => 0.9, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'),
+ 'sv' => array('q' => 0.8, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'),
+ 'te' => array('q' => 0.3, 'lang' => 'te_IN', 'name' => 'Telugu', 'direction' => 'ltr'),
+ 'tr' => array('q' => 0.5, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'),
+ 'uk' => array('q' => 1, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'),
+ 'vi' => array('q' => 0.8, 'lang' => 'vi_VN', 'name' => 'Vietnamese', 'direction' => 'ltr'),
+ 'zh-cn' => array('q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'),
+ 'zh-hant' => array('q' => 0.2, 'lang' => 'zh_TW', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'),
);
}
diff --git a/lib/logingroupnav.php b/lib/logingroupnav.php
index fd909581f..f23985f3a 100644
--- a/lib/logingroupnav.php
+++ b/lib/logingroupnav.php
@@ -70,16 +70,16 @@ class LoginGroupNav extends Widget
function show()
{
// action => array('prompt', 'title')
- $menu =
- array('login' =>
- array(_('Login'),
- _('Login with a username and password')),
- 'register' =>
- array(_('Register'),
- _('Sign up for a new account')),
- 'openidlogin' =>
- array(_('OpenID'),
- _('Login or register with OpenID')));
+ $menu = array();
+
+ $menu['login'] = array(_('Login'),
+ _('Login with a username and password'));
+ if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
+ $menu['register'] = array(_('Register'),
+ _('Sign up for a new account'));
+ }
+ $menu['openidlogin'] = array(_('OpenID'),
+ _('Login or register with OpenID'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
diff --git a/lib/mail.php b/lib/mail.php
index 9fa86de5c..27a1d99dc 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -50,10 +50,9 @@ function mail_backend()
static $backend = null;
if (!$backend) {
- global $config;
- $backend = Mail::factory($config['mail']['backend'],
- ($config['mail']['params']) ?
- $config['mail']['params'] :
+ $backend = Mail::factory(common_config('mail', 'backend'),
+ (common_config('mail', 'params')) ?
+ common_config('mail', 'params') :
array());
if (PEAR::isError($backend)) {
common_server_error($backend->getMessage(), 500);
@@ -555,17 +554,19 @@ function mail_notify_fave($other, $user, $notice)
$body = sprintf(_("%1\$s just added your notice from %2\$s".
" as one of their favorites.\n\n" .
- "In case you forgot, you can see the text".
- " of your notice here:\n\n" .
+ "The URL of your notice is:\n\n" .
"%3\$s\n\n" .
- "You can see the list of %1\$s's favorites here:\n\n" .
+ "The text of your notice is:\n\n" .
"%4\$s\n\n" .
+ "You can see the list of %1\$s's favorites here:\n\n" .
+ "%5\$s\n\n" .
"Faithfully yours,\n" .
- "%5\$s\n"),
+ "%6\$s\n"),
$bestname,
common_exact_date($notice->created),
common_local_url('shownotice',
array('notice' => $notice->id)),
+ $notice->content,
common_local_url('showfavorites',
array('nickname' => $user->nickname)),
common_config('site', 'name'));
diff --git a/lib/mailbox.php b/lib/mailbox.php
index d77234549..01bbf5721 100644
--- a/lib/mailbox.php
+++ b/lib/mailbox.php
@@ -137,6 +137,9 @@ class MailboxAction extends PersonalAction
$message->free();
unset($message);
}
+ else {
+ $this->element('p', 'guide', _('You have no private messages. You can send private message to engage other users in conversation. People can send you messages for your eyes only.'));
+ }
}
function getMessages()
diff --git a/lib/messageform.php b/lib/messageform.php
index f41508305..b8878ec1f 100644
--- a/lib/messageform.php
+++ b/lib/messageform.php
@@ -132,20 +132,14 @@ class MessageForm extends Form
$mutual_users->free();
unset($mutual_users);
- $this->out->elementStart('ul', 'form_data');
- $this->out->elementStart('li', array('id' => 'notice_to'));
$this->out->dropdown('to', _('To'), $mutual, null, false,
($this->to) ? $this->to->id : null);
- $this->out->elementEnd('li');
- $this->out->elementStart('li', array('id' => 'notice_text'));
$this->out->element('textarea', array('id' => 'notice_data-text',
'cols' => 35,
'rows' => 4,
'name' => 'content'),
($this->content) ? $this->content : '');
- $this->out->elementEnd('li');
- $this->out->elementEnd('ul');
}
/**
@@ -156,14 +150,10 @@ class MessageForm extends Form
function formActions()
{
- $this->out->elementStart('ul', 'form_actions');
- $this->out->elementStart('li', array('id' => 'notice_submit'));
$this->out->element('input', array('id' => 'notice_action-submit',
'class' => 'submit',
'name' => 'message_send',
'type' => 'submit',
'value' => _('Send')));
- $this->out->elementEnd('li');
- $this->out->elementEnd('ul');
}
}
diff --git a/lib/noticeform.php b/lib/noticeform.php
index 0c991c969..606b5d028 100644
--- a/lib/noticeform.php
+++ b/lib/noticeform.php
@@ -134,9 +134,6 @@ class NoticeForm extends Form
function formData()
{
-
- $this->out->elementStart('ul', 'form_data');
- $this->out->elementStart('li', array('id' => 'notice_text'));
$this->out->element('label', array('for' => 'notice_data-text'),
sprintf(_('What\'s up, %s?'), $this->user->nickname));
// XXX: vary by defined max size
@@ -145,8 +142,6 @@ class NoticeForm extends Form
'rows' => 4,
'name' => 'status_textarea'),
($this->content) ? $this->content : '');
- $this->out->elementEnd('li');
- $this->out->elementEnd('ul');
$this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters'));
@@ -168,14 +163,10 @@ class NoticeForm extends Form
function formActions()
{
- $this->out->elementStart('ul', 'form_actions');
- $this->out->elementStart('li', array('id' => 'notice_submit'));
$this->out->element('input', array('id' => 'notice_action-submit',
'class' => 'submit',
'name' => 'status_submit',
'type' => 'submit',
'value' => _('Send')));
- $this->out->elementEnd('li');
- $this->out->elementEnd('ul');
}
}
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 9fc0126b3..004905056 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -179,25 +179,89 @@ class NoticeListItem extends Widget
{
$this->showStart();
$this->showNotice();
- $this->showNoticeInfo();
+ $this->showNoticeAttachments();
$this->showNoticeOptions();
+ $this->showNoticeInfo();
$this->showEnd();
}
function showNotice()
{
- $this->out->elementStart('div', 'entry-title');
+if(0)
+ $this->out->elementStart('entry-title');
+else
+
+ if ('shownotice' === $this->out->args['action']) {
+ $width = '85%';
+ } else {
+ $width = '90%';
+ }
+
+
+ $this->out->elementStart('div', array('class' => 'entry-title', 'style' => "float: left; width: $width;"));
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
}
+ function showNoticeAttachments()
+ {
+ $f2p = new File_to_post;
+ $f2p->post_id = $this->notice->id;
+ $file = new File;
+ $file->joinAdd($f2p);
+ $file->selectAdd();
+ $file->selectAdd('file.id as id');
+ $count = $file->find(true);
+ if (!$count) return;
+ if (1 === $count) {
+ $href = common_local_url('attachment', array('attachment' => $file->id));
+ $att_class = 'attachment';
+ } else {
+ $href = common_local_url('attachments', array('notice' => $this->notice->id));
+ $att_class = 'attachments';
+ }
+
+ $clip = theme_path('images/icons/clip', 'base');
+ if ('shownotice' === $this->out->args['action']) {
+ $height = '96px';
+ $width = '83%';
+ $width_att = '15%';
+ $clip .= '-big.png';
+ $top = '70px';
+ } else {
+ $height = '48px';
+ $width = '90%';
+ $width_att = '8%';
+ $clip .= '.png';
+ $top = '20px';
+ }
+if(0)
+ $this->out->elementStart('div', 'entry-attachments');
+else
+ $this->out->elementStart('p', array('class' => 'entry-attachments', 'style' => "float: right; width: $width_att; background: url($clip) no-repeat; text-align: right; height: $height;"));
+ $this->out->element('a', array('class' => $att_class, 'style' => "text-decoration: none; padding-top: $top; display: block; height: $height;", 'href' => $href, 'title' => "# of attachments: $count"), $count === 1 ? '' : $count);
+
+
+ $this->out->elementEnd('p');
+ }
+
function showNoticeInfo()
{
+if(0)
$this->out->elementStart('div', 'entry-content');
+else
+
+ if ('shownotice' === $this->out->args['action']) {
+ $width = '85%';
+ } else {
+ $width = '90%';
+ }
+
+ $this->out->elementStart('div', array('class' => 'entry-content', 'style' => "float: left; width: $width;"));
$this->showNoticeLink();
$this->showNoticeSource();
- $this->showReplyTo();
+ $this->showContext();
$this->out->elementEnd('div');
}
@@ -205,7 +269,10 @@ class NoticeListItem extends Widget
{
$user = common_current_user();
if ($user) {
+if(0)
$this->out->elementStart('div', 'notice-options');
+else
+ $this->out->elementStart('div', array('class' => 'notice-options', 'style' => 'float: right; width: 16%;'));
$this->showFaveForm();
$this->showReplyLink();
$this->showDeleteLink();
@@ -258,8 +325,12 @@ class NoticeListItem extends Widget
function showAuthor()
{
$this->out->elementStart('span', 'vcard author');
- $this->out->elementStart('a', array('href' => $this->profile->profileurl,
- 'class' => 'url'));
+ $attrs = array('href' => $this->profile->profileurl,
+ 'class' => 'url');
+ if (!empty($this->profile->fullname)) {
+ $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') ';
+ }
+ $this->out->elementStart('a', $attrs);
$this->showAvatar();
$this->showNickname();
$this->out->elementEnd('a');
@@ -387,6 +458,7 @@ class NoticeListItem extends Widget
case 'xmpp':
case 'mail':
case 'omb':
+ case 'system':
case 'api':
$this->out->element('dd', null, $source_name);
break;
@@ -416,17 +488,18 @@ class NoticeListItem extends Widget
* @return void
*/
- function showReplyTo()
+ function showContext()
{
- if ($this->notice->reply_to) {
- $replyurl = common_local_url('shownotice',
- array('notice' => $this->notice->reply_to));
+ // XXX: also show context if there are replies to this notice
+ if (!empty($this->notice->conversation)
+ && $this->notice->conversation != $this->notice->id) {
+ $convurl = common_local_url('conversation',
+ array('id' => $this->notice->conversation));
$this->out->elementStart('dl', 'response');
$this->out->element('dt', null, _('To'));
$this->out->elementStart('dd');
- $this->out->element('a', array('href' => $replyurl,
- 'rel' => 'in-reply-to'),
- _('in reply to'));
+ $this->out->element('a', array('href' => $convurl),
+ _('in context'));
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
diff --git a/lib/noticesection.php b/lib/noticesection.php
index 97b517529..37aafdaf6 100644
--- a/lib/noticesection.php
+++ b/lib/noticesection.php
@@ -51,17 +51,13 @@ class NoticeSection extends Section
function showContent()
{
$notices = $this->getNotices();
-
$cnt = 0;
-
$this->out->elementStart('ul', 'notices');
-
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices);
}
$this->out->elementEnd('ul');
-
return ($cnt > NOTICES_PER_SECTION);
}
@@ -73,6 +69,11 @@ class NoticeSection extends Section
function showNotice($notice)
{
$profile = $notice->getProfile();
+ if (empty($profile)) {
+ common_log(LOG_WARNING, sprintf("Notice %d has no profile",
+ $notice->id));
+ return;
+ }
$this->out->elementStart('li', 'hentry notice');
$this->out->elementStart('div', 'entry-title');
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
@@ -95,8 +96,39 @@ class NoticeSection extends Section
$this->out->elementStart('p', 'entry-content');
$this->out->raw($notice->rendered);
+
+ $notice_link_cfg = common_config('site', 'notice_link');
+ if ('direct' === $notice_link_cfg) {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ } elseif ('attachment' === $notice_link_cfg) {
+ if ($count = $notice->hasAttachments()) {
+ // link to attachment(s) pages
+ if (1 === $count) {
+ $f2p = File_to_post::staticGet('post_id', $notice->id);
+ $href = common_local_url('attachment', array('attachment' => $f2p->file_id));
+ $att_class = 'attachment';
+ } else {
+ $href = common_local_url('attachments', array('notice' => $notice->id));
+ $att_class = 'attachments';
+ }
+
+ $clip = theme_path('images/icons/clip.png', 'base');
+ $this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: $count"));
+ $this->out->raw(" ($count&nbsp");
+ $this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt'));
+ $this->out->text(')');
+ $this->out->elementEnd('a');
+ } else {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ }
+ }
+
$this->out->elementEnd('p');
- if ($notice->value) {
+ if (!empty($notice->value)) {
$this->out->elementStart('p');
$this->out->text($notice->value);
$this->out->elementEnd('p');
diff --git a/lib/oauthstore.php b/lib/oauthstore.php
index 7ad3be20e..183164e17 100644
--- a/lib/oauthstore.php
+++ b/lib/oauthstore.php
@@ -54,16 +54,21 @@ class LaconicaOAuthDataStore extends OAuthDataStore
}
}
+ // http://oauth.net/core/1.0/#nonce
+ // "The Consumer SHALL then generate a Nonce value that is unique for
+ // all requests with that timestamp."
+
+ // XXX: It's not clear why the token is here
+
function lookup_nonce($consumer, $token, $nonce, $timestamp)
{
$n = new Nonce();
$n->consumer_key = $consumer->key;
- $n->tok = $token->key;
+ $n->ts = $timestamp;
$n->nonce = $nonce;
if ($n->find(true)) {
return true;
} else {
- $n->timestamp = $timestamp;
$n->created = DB_DataObject_Cast::dateTime();
$n->insert();
return false;
diff --git a/lib/omb.php b/lib/omb.php
index f2dbef5ba..e8e1acc41 100644
--- a/lib/omb.php
+++ b/lib/omb.php
@@ -206,7 +206,7 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
$result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(),
- array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+ array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
@@ -239,7 +239,7 @@ function omb_broadcast_profile($profile)
while ($sub->fetch()) {
$rp = Remote_profile::staticGet('id', $sub->subscriber);
if ($rp) {
- if (!$updated[$rp->updateprofileurl]) {
+ if (!array_key_exists($rp->updateprofileurl, $updated)) {
if (omb_update_profile($profile, $rp, $sub)) {
$updated[$rp->updateprofileurl] = true;
}
@@ -251,7 +251,6 @@ function omb_broadcast_profile($profile)
function omb_update_profile($profile, $remote_profile, $subscription)
{
- global $config; # for license URL
$user = User::staticGet($profile->id);
$con = omb_oauth_consumer();
$token = new OAuthToken($subscription->token, $subscription->secret);
@@ -291,11 +290,13 @@ function omb_update_profile($profile, $remote_profile, $subscription)
common_debug('postdata = '.$req->to_postdata(), __FILE__);
$result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(),
- array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+ array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
- if ($result->status == 403) { # not authorized, don't send again
+ if (empty($result) || !$result) {
+ common_debug("Unable to contact " . $req->get_normalized_http_url());
+ } else if ($result->status == 403) { # not authorized, don't send again
common_debug('403 result, deleting subscription', __FILE__);
$subscription->delete();
return false;
@@ -304,7 +305,7 @@ function omb_update_profile($profile, $remote_profile, $subscription)
return false;
} else { # success!
parse_str($result->body, $return);
- if ($return['omb_version'] == OMB_VERSION_01) {
+ if (isset($return['omb_version']) && $return['omb_version'] === OMB_VERSION_01) {
return true;
} else {
return false;
diff --git a/lib/openid.php b/lib/openid.php
index 860573702..3af7a39cf 100644
--- a/lib/openid.php
+++ b/lib/openid.php
@@ -64,6 +64,9 @@ function oid_set_last($openid_url)
function oid_get_last()
{
+ if (empty($_COOKIE[OPENID_COOKIE_KEY])) {
+ return null;
+ }
$openid_url = $_COOKIE[OPENID_COOKIE_KEY];
if ($openid_url && strlen($openid_url) > 0) {
return $openid_url;
@@ -157,7 +160,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false)
$auth_request->addExtension($sreg_request);
}
- $trust_root = common_local_url('public');
+ $trust_root = common_root_url(true);
$process_url = common_local_url($returnto);
if ($auth_request->shouldSendRedirect()) {
@@ -168,7 +171,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false)
} else if (Auth_OpenID::isFailure($redirect_url)) {
return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
} else {
- common_redirect($redirect_url);
+ common_redirect($redirect_url, 303);
}
} else {
// Generate form markup and render it.
diff --git a/lib/peoplesearchresults.php b/lib/peoplesearchresults.php
new file mode 100644
index 000000000..d3f840852
--- /dev/null
+++ b/lib/peoplesearchresults.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * People search results class
+ *
+ * PHP version 5
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Robin Millette <millette@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profilelist.php';
+
+/**
+ * People search results class
+ *
+ * Derivative of ProfileList with specialization for highlighting search terms.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Robin Millette <millette@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ *
+ * @see PeoplesearchAction
+ */
+
+class PeopleSearchResults extends ProfileList
+{
+ var $terms = null;
+ var $pattern = null;
+
+ function __construct($profile, $terms, $action)
+ {
+ parent::__construct($profile, $terms, $action);
+ $this->terms = array_map('preg_quote',
+ array_map('htmlspecialchars', $terms));
+ $this->pattern = '/('.implode('|',$terms).')/i';
+ }
+
+ function highlight($text)
+ {
+ return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
+ }
+
+ function isReadOnly($args)
+ {
+ return true;
+ }
+}
+
diff --git a/lib/personal.php b/lib/personal.php
index e46350c63..f92732375 100644
--- a/lib/personal.php
+++ b/lib/personal.php
@@ -47,7 +47,7 @@ class PersonalAction extends Action
var $user = null;
- function isReadOnly()
+ function isReadOnly($args)
{
return true;
}
diff --git a/lib/personalgroupnav.php b/lib/personalgroupnav.php
index 63e6138df..acc033667 100644
--- a/lib/personalgroupnav.php
+++ b/lib/personalgroupnav.php
@@ -93,43 +93,45 @@ class PersonalGroupNav extends Widget
$this->out->elementStart('ul', array('class' => 'nav'));
- $this->out->menuItem(common_local_url('all', array('nickname' =>
- $nickname)),
- _('Personal'),
- sprintf(_('%s and friends'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
- $action == 'all', 'nav_timeline_personal');
- $this->out->menuItem(common_local_url('replies', array('nickname' =>
- $nickname)),
- _('Replies'),
- sprintf(_('Replies to %s'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
- $action == 'replies', 'nav_timeline_replies');
- $this->out->menuItem(common_local_url('showstream', array('nickname' =>
- $nickname)),
- _('Profile'),
- ($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname,
- $action == 'showstream', 'nav_profile');
- $this->out->menuItem(common_local_url('showfavorites', array('nickname' =>
- $nickname)),
- _('Favorites'),
- sprintf(_('%s\'s favorite notices'), ($user_profile) ? $user_profile->getBestName() : _('User')),
- $action == 'showfavorites', 'nav_timeline_favorites');
+ if (Event::handle('StartPersonalGroupNav', array($this))) {
+ $this->out->menuItem(common_local_url('all', array('nickname' =>
+ $nickname)),
+ _('Personal'),
+ sprintf(_('%s and friends'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
+ $action == 'all', 'nav_timeline_personal');
+ $this->out->menuItem(common_local_url('replies', array('nickname' =>
+ $nickname)),
+ _('Replies'),
+ sprintf(_('Replies to %s'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
+ $action == 'replies', 'nav_timeline_replies');
+ $this->out->menuItem(common_local_url('showstream', array('nickname' =>
+ $nickname)),
+ _('Profile'),
+ ($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname,
+ $action == 'showstream', 'nav_profile');
+ $this->out->menuItem(common_local_url('showfavorites', array('nickname' =>
+ $nickname)),
+ _('Favorites'),
+ sprintf(_('%s\'s favorite notices'), ($user_profile) ? $user_profile->getBestName() : _('User')),
+ $action == 'showfavorites', 'nav_timeline_favorites');
- $cur = common_current_user();
+ $cur = common_current_user();
- if ($cur && $cur->id == $user->id) {
+ if ($cur && $cur->id == $user->id) {
- $this->out->menuItem(common_local_url('inbox', array('nickname' =>
- $nickname)),
- _('Inbox'),
- _('Your incoming messages'),
- $action == 'inbox');
- $this->out->menuItem(common_local_url('outbox', array('nickname' =>
- $nickname)),
- _('Outbox'),
- _('Your sent messages'),
- $action == 'outbox');
+ $this->out->menuItem(common_local_url('inbox', array('nickname' =>
+ $nickname)),
+ _('Inbox'),
+ _('Your incoming messages'),
+ $action == 'inbox');
+ $this->out->menuItem(common_local_url('outbox', array('nickname' =>
+ $nickname)),
+ _('Outbox'),
+ _('Your sent messages'),
+ $action == 'outbox');
+ }
+ Event::handle('EndPersonalGroupNav', array($this));
}
-
$this->out->elementEnd('ul');
}
}
diff --git a/lib/personaltagcloudsection.php b/lib/personaltagcloudsection.php
index 0882822db..978153a84 100644
--- a/lib/personaltagcloudsection.php
+++ b/lib/personaltagcloudsection.php
@@ -58,8 +58,14 @@ class PersonalTagCloudSection extends TagCloudSection
function getTags()
{
- $qry = 'SELECT notice_tag.tag, '.
- 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' .
+ if (common_config('db', 'type') == 'pgsql') {
+ $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))';
+ } else {
+ $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))';
+ }
+
+ $qry = 'SELECT notice_tag.tag, '.
+ $weightexpr . ' as weight ' .
'FROM notice_tag JOIN notice ' .
'ON notice_tag.notice_id = notice.id ' .
'WHERE notice.profile_id = %d ' .
diff --git a/lib/ping.php b/lib/ping.php
new file mode 100644
index 000000000..3de541e9a
--- /dev/null
+++ b/lib/ping.php
@@ -0,0 +1,122 @@
+<?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/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+function ping_broadcast_notice($notice) {
+
+ if (!$notice->is_local) {
+ return true;
+ }
+
+ # Array of servers, URL => type
+ $notify = common_config('ping', 'notify');
+ $profile = $notice->getProfile();
+ $tags = ping_notice_tags($notice);
+
+ foreach ($notify as $notify_url => $type) {
+ switch ($type) {
+ case 'xmlrpc':
+ case 'extended':
+ $req = xmlrpc_encode_request('weblogUpdates.ping',
+ array($profile->nickname, # site name
+ common_local_url('showstream',
+ array('nickname' => $profile->nickname)),
+ common_local_url('shownotice',
+ array('notice' => $notice->id)),
+ common_local_url('userrss',
+ array('nickname' => $profile->nickname)),
+ $tags));
+
+ $context = stream_context_create(array('http' => array('method' => "POST",
+ 'header' =>
+ "Content-Type: text/xml\r\n".
+ "User-Agent: Laconica/".LACONICA_VERSION."\r\n",
+ 'content' => $req)));
+ $file = file_get_contents($notify_url, false, $context);
+
+ if ($file === false || mb_strlen($file) == 0) {
+ common_log(LOG_WARNING,
+ "XML-RPC empty results for ping ($notify_url, $notice->id) ");
+ continue;
+ }
+
+ $response = xmlrpc_decode($file);
+
+ if (xmlrpc_is_fault($response)) {
+ common_log(LOG_WARNING,
+ "XML-RPC error for ping ($notify_url, $notice->id) ".
+ "$response[faultString] ($response[faultCode])");
+ } else {
+ common_log(LOG_INFO,
+ "Ping success for $notify_url $notice->id");
+ }
+ break;
+
+ case 'get':
+ case 'post':
+ $args = array('name' => $profile->nickname,
+ 'url' => common_local_url('showstream',
+ array('nickname' => $profile->nickname)),
+ 'changesURL' => common_local_url('userrss',
+ array('nickname' => $profile->nickname)));
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ if ($type === 'get') {
+ $result = $fetcher->get($notify_url . '?' . http_build_query($args),
+ array('User-Agent: Laconica/'.LACONICA_VERSION));
+ } else {
+ $result = $fetcher->post($notify_url,
+ http_build_query($args),
+ array('User-Agent: Laconica/'.LACONICA_VERSION));
+ }
+ if ($result->status != '200') {
+ common_log(LOG_WARNING,
+ "Ping error for '$notify_url' ($notice->id): ".
+ "$result->body");
+ } else {
+ common_log(LOG_INFO,
+ "Ping success for '$notify_url' ($notice->id): ".
+ "'$result->body'");
+ }
+ break;
+
+ default:
+ common_log(LOG_WARNING, 'Unknown notify type for ' . $notify_url . ': ' . $type);
+ }
+ }
+
+ return true;
+}
+
+function ping_notice_tags($notice) {
+ $tag = new Notice_tag();
+ $tag->notice_id = $notice->id;
+ $tags = array();
+ if ($tag->find()) {
+ while ($tag->fetch()) {
+ $tags[] = $tag->tag;
+ }
+ $tag->free();
+ unset($tag);
+ return implode('|', $tags);
+ }
+ return NULL;
+} \ No newline at end of file
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
index 5380563b9..375d5538b 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
exit(1);
}
-define('NOTICES_PER_SECTION', 5);
-
/**
* Base class for sections showing lists of notices
*
@@ -50,11 +48,28 @@ class PopularNoticeSection extends NoticeSection
{
function getNotices()
{
- $qry = 'SELECT notice.*, '.
- 'sum(exp(-(now() - fave.modified) / %s)) as weight ' .
- 'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
- 'GROUP BY fave.notice_id ' .
- 'ORDER BY weight DESC';
+ if (common_config('db', 'type') == 'pgsql') {
+ $weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
+ if (!empty($this->out->tag)) {
+ $tag = pg_escape_string($this->out->tag);
+ }
+ } else {
+ $weightexpr='sum(exp(-(now() - fave.modified) / %s))';
+ if (!empty($this->out->tag)) {
+ $tag = mysql_escape_string($this->out->tag);
+ }
+ }
+ $qry = "SELECT notice.*, $weightexpr as weight ";
+ if(isset($tag)) {
+ $qry .= 'FROM notice_tag, notice JOIN fave ON notice.id = fave.notice_id ' .
+ "WHERE notice.id = notice_tag.notice_id and '$tag' = notice_tag.tag";
+ } else {
+ $qry .= 'FROM notice JOIN fave ON notice.id = fave.notice_id';
+ }
+ $qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
+ 'notice.rendered,notice.url,notice.created,notice.modified,' .
+ 'notice.reply_to,notice.is_local,notice.source ' .
+ 'ORDER BY weight DESC';
$offset = 0;
$limit = NOTICES_PER_SECTION + 1;
diff --git a/lib/profileaction.php b/lib/profileaction.php
new file mode 100644
index 000000000..a3437ff4d
--- /dev/null
+++ b/lib/profileaction.php
@@ -0,0 +1,248 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Common parent of Personal and Profile actions
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Personal
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profileminilist.php';
+require_once INSTALLDIR.'/lib/groupminilist.php';
+
+/**
+ * Profile action common superclass
+ *
+ * Abstracts out common code from profile and personal tabs
+ *
+ * @category Personal
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class ProfileAction extends Action
+{
+ var $user = null;
+ var $page = null;
+ var $profile = null;
+ var $tag = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $nickname_arg = $this->arg('nickname');
+ $nickname = common_canonical_nickname($nickname_arg);
+
+ // Permanent redirect on non-canonical nickname
+
+ if ($nickname_arg != $nickname) {
+ $args = array('nickname' => $nickname);
+ if ($this->arg('page') && $this->arg('page') != 1) {
+ $args['page'] = $this->arg['page'];
+ }
+ common_redirect(common_local_url($this->trimmed('action'), $args), 301);
+ return false;
+ }
+
+ $this->user = User::staticGet('nickname', $nickname);
+
+ if (!$this->user) {
+ $this->clientError(_('No such user.'), 404);
+ return false;
+ }
+
+ $this->profile = $this->user->getProfile();
+
+ if (!$this->profile) {
+ $this->serverError(_('User has no profile.'));
+ return false;
+ }
+
+ $this->tag = $this->trimmed('tag');
+ $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+ common_set_returnto($this->selfUrl());
+ return true;
+ }
+
+ function showSections()
+ {
+ $this->showSubscriptions();
+ $this->showSubscribers();
+ $this->showGroups();
+ $this->showStatistics();
+ }
+
+ function showSubscriptions()
+ {
+ $profile = $this->user->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
+
+ $this->elementStart('div', array('id' => 'entity_subscriptions',
+ 'class' => 'section'));
+
+ $this->element('h2', null, _('Subscriptions'));
+
+ if ($profile) {
+ $pml = new ProfileMiniList($profile, $this->user, $this);
+ $cnt = $pml->show();
+ if ($cnt == 0) {
+ $this->element('p', null, _('(None)'));
+ }
+ }
+
+ if ($cnt > PROFILES_PER_MINILIST) {
+ $this->elementStart('p');
+ $this->element('a', array('href' => common_local_url('subscriptions',
+ array('nickname' => $this->profile->nickname)),
+ 'class' => 'more'),
+ _('All subscriptions'));
+ $this->elementEnd('p');
+ }
+
+ $this->elementEnd('div');
+ }
+
+ function showSubscribers()
+ {
+ $profile = $this->user->getSubscribers(0, PROFILES_PER_MINILIST + 1);
+
+ $this->elementStart('div', array('id' => 'entity_subscribers',
+ 'class' => 'section'));
+
+ $this->element('h2', null, _('Subscribers'));
+
+ if ($profile) {
+ $pml = new ProfileMiniList($profile, $this->user, $this);
+ $cnt = $pml->show();
+ if ($cnt == 0) {
+ $this->element('p', null, _('(None)'));
+ }
+ }
+
+ if ($cnt > PROFILES_PER_MINILIST) {
+ $this->elementStart('p');
+ $this->element('a', array('href' => common_local_url('subscribers',
+ array('nickname' => $this->profile->nickname)),
+ 'class' => 'more'),
+ _('All subscribers'));
+ $this->elementEnd('p');
+ }
+
+ $this->elementEnd('div');
+ }
+
+ function showStatistics()
+ {
+ // XXX: WORM cache this
+ $subs = new Subscription();
+ $subs->subscriber = $this->profile->id;
+ $subs_count = (int) $subs->count() - 1;
+
+ $subbed = new Subscription();
+ $subbed->subscribed = $this->profile->id;
+ $subbed_count = (int) $subbed->count() - 1;
+
+ $notices = new Notice();
+ $notices->profile_id = $this->profile->id;
+ $notice_count = (int) $notices->count();
+
+ $this->elementStart('div', array('id' => 'entity_statistics',
+ 'class' => 'section'));
+
+ $this->element('h2', null, _('Statistics'));
+
+ // Other stats...?
+ $this->elementStart('dl', 'entity_user-id');
+ $this->element('dt', null, _('User ID'));
+ $this->element('dd', null, $this->profile->id);
+ $this->elementEnd('dl');
+
+ $this->elementStart('dl', 'entity_member-since');
+ $this->element('dt', null, _('Member since'));
+ $this->element('dd', null, date('j M Y',
+ strtotime($this->profile->created)));
+ $this->elementEnd('dl');
+
+ $this->elementStart('dl', 'entity_subscriptions');
+ $this->elementStart('dt');
+ $this->element('a', array('href' => common_local_url('subscriptions',
+ array('nickname' => $this->profile->nickname))),
+ _('Subscriptions'));
+ $this->elementEnd('dt');
+ $this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
+ $this->elementEnd('dl');
+
+ $this->elementStart('dl', 'entity_subscribers');
+ $this->elementStart('dt');
+ $this->element('a', array('href' => common_local_url('subscribers',
+ array('nickname' => $this->profile->nickname))),
+ _('Subscribers'));
+ $this->elementEnd('dt');
+ $this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
+ $this->elementEnd('dl');
+
+ $this->elementStart('dl', 'entity_notices');
+ $this->element('dt', null, _('Notices'));
+ $this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0');
+ $this->elementEnd('dl');
+
+ $this->elementEnd('div');
+ }
+
+ function showGroups()
+ {
+ $groups = $this->user->getGroups(0, GROUPS_PER_MINILIST + 1);
+
+ $this->elementStart('div', array('id' => 'entity_groups',
+ 'class' => 'section'));
+
+ $this->element('h2', null, _('Groups'));
+
+ if ($groups) {
+ $gml = new GroupMiniList($groups, $this->user, $this);
+ $cnt = $gml->show();
+ if ($cnt == 0) {
+ $this->element('p', null, _('(None)'));
+ }
+ }
+
+ if ($cnt > GROUPS_PER_MINILIST) {
+ $this->elementStart('p');
+ $this->element('a', array('href' => common_local_url('usergroups',
+ array('nickname' => $this->profile->nickname)),
+ 'class' => 'more'),
+ _('All groups'));
+ $this->elementEnd('p');
+ }
+
+ $this->elementEnd('div');
+ }
+}
+
diff --git a/lib/profilelist.php b/lib/profilelist.php
index 8bef49dce..a4cc23555 100644
--- a/lib/profilelist.php
+++ b/lib/profilelist.php
@@ -34,8 +34,6 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/widget.php';
-define('PROFILES_PER_PAGE', 20);
-
/**
* Widget to show a list of profiles
*
@@ -91,6 +89,7 @@ class ProfileList extends Widget
'id' => 'profile-' . $this->profile->id));
$user = common_current_user();
+ $is_own = !is_null($user) && isset($this->owner) && ($user->id === $this->owner->id);
$this->out->elementStart('div', 'entity_profile vcard');
@@ -104,13 +103,13 @@ class ProfileList extends Widget
'alt' =>
($this->profile->fullname) ? $this->profile->fullname :
$this->profile->nickname));
- $hasFN = ($this->profile->fullname) ? 'nickname' : 'fn nickname';
+ $hasFN = ($this->profile->fullname !== '') ? 'nickname' : 'fn nickname';
$this->out->elementStart('span', $hasFN);
$this->out->raw($this->highlight($this->profile->nickname));
$this->out->elementEnd('span');
$this->out->elementEnd('a');
- if ($this->profile->fullname) {
+ if (!empty($this->profile->fullname)) {
$this->out->elementStart('dl', 'entity_fn');
$this->out->element('dt', null, 'Full name');
$this->out->elementStart('dd');
@@ -120,7 +119,7 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
- if ($this->profile->location) {
+ if (!empty($this->profile->location)) {
$this->out->elementStart('dl', 'entity_location');
$this->out->element('dt', null, _('Location'));
$this->out->elementStart('dd', 'label');
@@ -128,7 +127,7 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
- if ($this->profile->homepage) {
+ if (!empty($this->profile->homepage)) {
$this->out->elementStart('dl', 'entity_url');
$this->out->element('dt', null, _('URL'));
$this->out->elementStart('dd');
@@ -139,7 +138,7 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
- if ($this->profile->bio) {
+ if (!empty($this->profile->bio)) {
$this->out->elementStart('dl', 'entity_note');
$this->out->element('dt', null, _('Note'));
$this->out->elementStart('dd', 'note');
@@ -156,7 +155,7 @@ class ProfileList extends Widget
$this->out->elementStart('dl', 'entity_tags');
$this->out->elementStart('dt');
- if ($user->id == $this->owner->id) {
+ if ($is_own) {
$this->out->element('a', array('href' => common_local_url('tagother',
array('id' => $this->profile->id))),
_('Tags'));
@@ -185,7 +184,7 @@ class ProfileList extends Widget
$this->out->elementEnd('dl');
}
- if ($user && $user->id == $this->owner->id) {
+ if ($is_own) {
$this->showOwnerControls($this->profile);
}
@@ -195,9 +194,10 @@ class ProfileList extends Widget
$this->out->elementStart('ul');
- if ($user && $user->id != $this->profile->id) {
- # XXX: special-case for user looking at own
- # subscriptions page
+ // Is this a logged-in user, looking at someone else's
+ // profile?
+
+ if (!empty($user) && $this->profile->id != $user->id) {
$this->out->elementStart('li', 'entity_subscribe');
if ($user->isSubscribed($this->profile)) {
$usf = new UnsubscribeForm($this->out, $this->profile);
@@ -208,7 +208,7 @@ class ProfileList extends Widget
}
$this->out->elementEnd('li');
$this->out->elementStart('li', 'entity_block');
- if ($user && $user->id == $this->owner->id) {
+ if ($user->id == $this->owner->id) {
$this->showBlockForm();
}
$this->out->elementEnd('li');
diff --git a/lib/profileminilist.php b/lib/profileminilist.php
index 0d466bba8..57496d0e9 100644
--- a/lib/profileminilist.php
+++ b/lib/profileminilist.php
@@ -33,7 +33,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/profilelist.php';
-define('PROFILES_PER_MINILIST', 80);
+define('PROFILES_PER_MINILIST', 27);
/**
* Widget to show a list of profiles, good for sidebar
diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php
index d72475e20..485d25e20 100644
--- a/lib/publicgroupnav.php
+++ b/lib/publicgroupnav.php
@@ -39,6 +39,7 @@ require_once INSTALLDIR.'/lib/widget.php';
* @category Output
* @package Laconica
* @author Evan Prodromou <evan@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/
*
@@ -73,23 +74,26 @@ class PublicGroupNav extends Widget
$this->action->elementStart('ul', array('class' => 'nav'));
- $this->out->menuItem(common_local_url('public'), _('Public'),
- _('Public timeline'), $action_name == 'public', 'nav_timeline_public');
+ if (Event::handle('StartPublicGroupNav', array($this))) {
+ $this->out->menuItem(common_local_url('public'), _('Public'),
+ _('Public timeline'), $action_name == 'public', 'nav_timeline_public');
- $this->out->menuItem(common_local_url('groups'), _('Groups'),
- _('User groups'), $action_name == 'groups', 'nav_groups');
+ $this->out->menuItem(common_local_url('groups'), _('Groups'),
+ _('User groups'), $action_name == 'groups', 'nav_groups');
- $this->out->menuItem(common_local_url('publictagcloud'), _('Recent tags'),
- _('Recent tags'), $action_name == 'publictagcloud', 'nav_recent-tags');
+ $this->out->menuItem(common_local_url('publictagcloud'), _('Recent tags'),
+ _('Recent tags'), $action_name == 'publictagcloud', 'nav_recent-tags');
- if (count(common_config('nickname', 'featured')) > 0) {
- $this->out->menuItem(common_local_url('featured'), _('Featured'),
- _('Featured users'), $action_name == 'featured', 'nav_featured');
- }
+ if (count(common_config('nickname', 'featured')) > 0) {
+ $this->out->menuItem(common_local_url('featured'), _('Featured'),
+ _('Featured users'), $action_name == 'featured', 'nav_featured');
+ }
- $this->out->menuItem(common_local_url('favorited'), _('Popular'),
- _("Popular notices"), $action_name == 'favorited', 'nav_timeline_favorited');
+ $this->out->menuItem(common_local_url('favorited'), _('Popular'),
+ _("Popular notices"), $action_name == 'favorited', 'nav_timeline_favorited');
+ Event::handle('EndPublicGroupNav', array($this));
+ }
$this->action->elementEnd('ul');
}
}
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index 9ce9e32b3..f76f16e07 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -36,7 +36,7 @@ class QueueHandler extends Daemon
$this->set_id($id);
}
}
-
+
function class_name()
{
return ucfirst($this->transport()) . 'Handler';
@@ -46,7 +46,7 @@ class QueueHandler extends Daemon
{
return strtolower($this->class_name().'.'.$this->get_id());
}
-
+
function get_id()
{
return $this->_id;
@@ -56,16 +56,16 @@ class QueueHandler extends Daemon
{
$this->_id = $id;
}
-
+
function transport()
{
return null;
}
-
+
function start()
{
}
-
+
function finish()
{
}
@@ -74,16 +74,10 @@ class QueueHandler extends Daemon
{
return true;
}
-
- function run()
- {
- if (!$this->start()) {
- return false;
- }
- $this->log(LOG_INFO, 'checking for queued notices');
- $transport = $this->transport();
+
+ function db_dispatch() {
do {
- $qi = Queue_item::top($transport);
+ $qi = Queue_item::top($this->transport());
if ($qi) {
$this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
$notice = Notice::staticGet($qi->notice_id);
@@ -113,8 +107,69 @@ class QueueHandler extends Daemon
} else {
$this->clear_old_claims();
$this->idle(5);
- }
+ }
} while (true);
+ }
+
+ function stomp_dispatch() {
+ require("Stomp.php");
+ $con = new Stomp(common_config('queue','stomp_server'));
+ if (!$con->connect()) {
+ $this->log(LOG_ERR, 'Failed to connect to queue server');
+ return false;
+ }
+ $queue_basename = common_config('queue','queue_basename');
+ // subscribe to the relevant queue (format: basename-transport)
+ $con->subscribe('/queue/'.$queue_basename.'-'.$this->transport());
+
+ do {
+ $frame = $con->readFrame();
+ if ($frame) {
+ $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($frame->headers['created']));
+
+ // XXX: Now the queue handler receives only the ID of the
+ // notice, and it has to get it from the DB
+ // A massive improvement would be avoid DB query by transmitting
+ // all the notice details via queue server...
+ $notice = Notice::staticGet($frame->body);
+
+ if ($notice) {
+ $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
+ $result = $this->handle_notice($notice);
+ if ($result) {
+ // if the msg has been handled positively, ack it
+ // and the queue server will remove it from the queue
+ $con->ack($frame);
+ $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
+ }
+ else {
+ // no ack
+ $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
+ }
+ $notice->free();
+ unset($notice);
+ $notice = null;
+ } else {
+ $this->log(LOG_WARNING, 'queue item for notice that does not exist');
+ }
+ }
+ } while (true);
+
+ $con->disconnect();
+ }
+
+ function run()
+ {
+ if (!$this->start()) {
+ return false;
+ }
+ $this->log(LOG_INFO, 'checking for queued notices');
+ if (common_config('queue','subsystem') == 'stomp') {
+ $this->stomp_dispatch();
+ }
+ else {
+ $this->db_dispatch();
+ }
if (!$this->finish()) {
return false;
}
@@ -127,7 +182,7 @@ class QueueHandler extends Daemon
sleep($timeout);
}
}
-
+
function clear_old_claims()
{
$qi = new Queue_item();
@@ -137,10 +192,10 @@ class QueueHandler extends Daemon
$qi->free();
unset($qi);
}
-
+
function log($level, $msg)
{
common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
}
}
- \ No newline at end of file
+
diff --git a/lib/router.php b/lib/router.php
index d47ad7118..70ee0f3fb 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -47,8 +47,11 @@ require_once 'Net/URL/Mapper.php';
class Router
{
- static $m = null;
+ var $m = null;
static $inst = null;
+ static $bare = array('requesttoken', 'accesstoken', 'userauthorization',
+ 'postnotice', 'updateprofile', 'finishremotesubscribe',
+ 'finishopenidlogin', 'finishaddopenid');
static function get()
{
@@ -65,8 +68,8 @@ class Router
}
}
- function initialize() {
-
+ function initialize()
+ {
$m = Net_URL_Mapper::getInstance();
// In the "root"
@@ -98,12 +101,17 @@ class Router
$main = array('login', 'logout', 'register', 'subscribe',
'unsubscribe', 'confirmaddress', 'recoverpassword',
'invite', 'favor', 'disfavor', 'sup',
- 'tagother', 'block');
+ 'block', 'subedit');
foreach ($main as $a) {
$m->connect('main/'.$a, array('action' => $a));
}
+ $m->connect('main/sup/:seconds', array('action' => 'sup'),
+ array('seconds' => '[0-9]+'));
+
+ $m->connect('main/tagother/:id', array('action' => 'tagother'));
+
// these take a code
foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
@@ -114,11 +122,16 @@ class Router
$m->connect('main/openid', array('action' => 'openidlogin'));
$m->connect('main/remote', array('action' => 'remotesubscribe'));
+ $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
+
+ foreach (Router::$bare as $action) {
+ $m->connect('index.php?action=' . $action, array('action' => $action));
+ }
// settings
foreach (array('profile', 'avatar', 'password', 'openid', 'im',
- 'email', 'sms', 'twitter', 'other') as $s) {
+ 'email', 'sms', 'twitter', 'design', 'other') as $s) {
$m->connect('settings/'.$s, array('action' => $s.'settings'));
}
@@ -126,13 +139,38 @@ class Router
foreach (array('group', 'people', 'notice') as $s) {
$m->connect('search/'.$s, array('action' => $s.'search'));
+ $m->connect('search/'.$s.'?q=:q',
+ array('action' => $s.'search'),
+ array('q' => '.+'));
}
+ // The second of these is needed to make the link work correctly
+ // when inserted into the page. The first is needed to match the
+ // route on the way in. Seems to be another Net_URL_Mapper bug to me.
$m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
+ $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
+ array('q' => '.+'));
+
+ $m->connect('attachment/:attachment/ajax',
+ array('action' => 'attachment_ajax'),
+ array('notice' => '[0-9]+'));
+
+ $m->connect('attachment/:attachment',
+ array('action' => 'attachment'),
+ array('notice' => '[0-9]+'));
// notice
$m->connect('notice/new', array('action' => 'newnotice'));
+ $m->connect('notice/new?replyto=:replyto',
+ array('action' => 'newnotice'),
+ array('replyto' => '[A-Za-z0-9_-]+'));
+ $m->connect('notice/:notice/attachments/ajax',
+ array('action' => 'attachments_ajax'),
+ array('notice' => '[0-9]+'));
+ $m->connect('notice/:notice/attachments',
+ array('action' => 'attachments'),
+ array('notice' => '[0-9]+'));
$m->connect('notice/:notice',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));
@@ -141,7 +179,14 @@ class Router
array('action' => 'deletenotice'),
array('notice' => '[0-9]+'));
+ // conversation
+
+ $m->connect('conversation/:id',
+ array('action' => 'conversation'),
+ array('id' => '[0-9]+'));
+
$m->connect('message/new', array('action' => 'newmessage'));
+ $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
$m->connect('message/:message',
array('action' => 'showmessage'),
array('message' => '[0-9]+'));
@@ -206,40 +251,52 @@ class Router
$m->connect('api/statuses/:method',
array('action' => 'api',
'apiaction' => 'statuses'),
- array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|friends|followers|featured)(\.(atom|rss|xml|json))?'));
+ array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|friends|followers|featured)(\.(atom|rss|xml|json))?'));
$m->connect('api/statuses/:method/:argument',
array('action' => 'api',
'apiaction' => 'statuses'),
- array('method' => '(user_timeline|show|destroy|friends|followers)'));
+ array('method' => '(user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
// users
- $m->connect('api/users/show/:argument',
+ $m->connect('api/users/:method/:argument',
array('action' => 'api',
- 'apiaction' => 'users'));
+ 'apiaction' => 'users'),
+ array('method' => 'show(\.(xml|json))?'));
$m->connect('api/users/:method',
array('action' => 'api',
'apiaction' => 'users'),
- array('method' => 'show(\.(xml|json|atom|rss))?'));
+ array('method' => 'show(\.(xml|json))?'));
// direct messages
- $m->connect('api/direct_messages/:method',
- array('action' => 'api',
- 'apiaction' => 'direct_messages'),
- array('method' => '(sent|new)(\.(xml|json|atom|rss))?'));
+ foreach (array('xml', 'json') as $e) {
+ $m->connect('api/direct_messages/new.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'direct_messages',
+ 'method' => 'create.'.$e));
+ }
+
+ foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+ $m->connect('api/direct_messages.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'direct_messages',
+ 'method' => 'direct_messages.'.$e));
+ }
+
+ foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+ $m->connect('api/direct_messages/sent.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'direct_messages',
+ 'method' => 'sent.'.$e));
+ }
$m->connect('api/direct_messages/destroy/:argument',
array('action' => 'api',
'apiaction' => 'direct_messages'));
- $m->connect('api/:method',
- array('action' => 'api',
- 'apiaction' => 'direct_messages'),
- array('method' => 'direct_messages(\.(xml|json|atom|rss))?'));
-
// friendships
$m->connect('api/friendships/:method/:argument',
@@ -250,7 +307,33 @@ class Router
$m->connect('api/friendships/:method',
array('action' => 'api',
'apiaction' => 'friendships'),
- array('method' => 'exists(\.(xml|json|rss|atom))'));
+ array('method' => 'exists(\.(xml|json))'));
+
+ // Social graph
+
+ $m->connect('api/friends/ids/:argument',
+ array('action' => 'api',
+ 'apiaction' => 'statuses',
+ 'method' => 'friendsIDs'));
+
+ foreach (array('xml', 'json') as $e) {
+ $m->connect('api/friends/ids.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'statuses',
+ 'method' => 'friendsIDs.'.$e));
+ }
+
+ $m->connect('api/followers/ids/:argument',
+ array('action' => 'api',
+ 'apiaction' => 'statuses',
+ 'method' => 'followersIDs'));
+
+ foreach (array('xml', 'json') as $e) {
+ $m->connect('api/followers/ids.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'statuses',
+ 'method' => 'followersIDs.'.$e));
+ }
// account
@@ -269,10 +352,12 @@ class Router
'apiaction' => 'favorites',
'method' => 'favorites'));
- $m->connect('api/:method',
- array('action' => 'api',
- 'apiaction' => 'favorites'),
- array('method' => 'favorites(\.(xml|json|rss|atom))?'));
+ foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+ $m->connect('api/favorites.'.$e,
+ array('action' => 'api',
+ 'apiaction' => 'favorites',
+ 'method' => 'favorites.'.$e));
+ }
// notifications
@@ -298,6 +383,11 @@ class Router
array('action' => 'api',
'apiaction' => 'laconica'));
+ // search
+ $m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
+ $m->connect('api/search.json', array('action' => 'twitapisearchjson'));
+ $m->connect('api/trends.json', array('action' => 'twitapitrends'));
+
// user stuff
foreach (array('subscriptions', 'subscribers',
@@ -336,19 +426,40 @@ class Router
array('size' => '(original|96|48|24)',
'nickname' => '[a-zA-Z0-9]{1,64}'));
+ $m->connect(':nickname/tag/:tag/rss',
+ array('action' => 'userrss'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
+ $m->connect(':nickname/tag/:tag',
+ array('action' => 'showstream'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
$m->connect(':nickname',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));
+ Event::handle('RouterInitialized', array($m));
+
return $m;
}
function map($path)
{
- return $this->m->match($path);
+ try {
+ $match = $this->m->match($path);
+ } catch (Net_URL_Mapper_InvalidException $e) {
+ common_log(LOG_ERR, "Problem getting route for $path - " .
+ $e->getMessage());
+ $cac = new ClientErrorAction("Page not found.", 404);
+ $cac->showPage();
+ }
+
+ return $match;
}
- function build($action, $args=null, $fragment=null)
+ function build($action, $args=null, $params=null, $fragment=null)
{
$action_arg = array('action' => $action);
@@ -358,6 +469,17 @@ class Router
$args = $action_arg;
}
- return $this->m->generate($args, null, $fragment);
+ $url = $this->m->generate($args, $params, $fragment);
+
+ // Due to a bug in the Net_URL_Mapper code, the returned URL may
+ // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
+ // repair that here rather than modifying the upstream code...
+
+ $qpos = strpos($url, '?');
+ if ($qpos !== false) {
+ $url = substr($url, 0, $qpos+1) .
+ str_replace('?', '&', substr($url, $qpos+1));
+ }
+ return $url;
}
-} \ No newline at end of file
+}
diff --git a/lib/rssaction.php b/lib/rssaction.php
index 131e8ac65..2f25ed7e4 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -38,6 +38,7 @@ class Rss10Action extends Action
var $creators = array();
var $limit = DEFAULT_RSS_LIMIT;
+ var $notices = null;
/**
* Constructor
@@ -93,8 +94,15 @@ class Rss10Action extends Action
function handle($args)
{
+ // Parent handling, including cache check
parent::handle($args);
- $this->showRss($this->limit);
+ // Get the list of notices
+ if (empty($this->tag)) {
+ $this->notices = $this->getNotices($this->limit);
+ } else {
+ $this->notices = $this->getTaggedNotices($this->tag, $this->limit);
+ }
+ $this->showRss();
}
/**
@@ -128,15 +136,13 @@ class Rss10Action extends Action
return null;
}
- function showRss($limit=0)
+ function showRss()
{
- $notices = $this->getNotices($limit);
-
$this->initRss();
- $this->showChannel($notices);
+ $this->showChannel();
$this->showImage();
- foreach ($notices as $n) {
+ foreach ($this->notices as $n) {
$this->showItem($n);
}
@@ -144,7 +150,7 @@ class Rss10Action extends Action
$this->endRss();
}
- function showChannel($notices)
+ function showChannel()
{
$channel = $this->getChannel();
@@ -163,7 +169,7 @@ class Rss10Action extends Action
$this->elementStart('items');
$this->elementStart('rdf:Seq');
- foreach ($notices as $notice) {
+ foreach ($this->notices as $notice) {
$this->element('sioct:MicroblogPost', array('rdf:resource' => $notice->uri));
}
@@ -258,5 +264,25 @@ class Rss10Action extends Action
{
$this->elementEnd('rdf:RDF');
}
+
+ /**
+ * When was this page last modified?
+ *
+ */
+
+ function lastModified()
+ {
+ if (empty($this->notices)) {
+ return null;
+ }
+
+ if (count($this->notices) == 0) {
+ return null;
+ }
+
+ // FIXME: doesn't handle modified profiles, avatars, deleted notices
+
+ return strtotime($this->notices[0]->created);
+ }
}
diff --git a/lib/search_engines.php b/lib/search_engines.php
index 559107910..7b9dbb618 100644
--- a/lib/search_engines.php
+++ b/lib/search_engines.php
@@ -74,7 +74,7 @@ class SphinxSearch extends SearchEngine
{
//FIXME without LARGEST_POSSIBLE, the most recent results aren't returned
// this probably has a large impact on performance
- $LARGEST_POSSIBLE = 1e6;
+ $LARGEST_POSSIBLE = 1e6;
if ($rss) {
$this->sphinx->setLimits($offset, $count, $count, $LARGEST_POSSIBLE);
@@ -109,12 +109,25 @@ class MySQLSearch extends SearchEngine
{
function query($q)
{
- if ('identica_people' === $this->table)
- return $this->target->whereAdd('MATCH(nickname, fullname, location, bio, homepage) ' .
- 'against (\''.addslashes($q).'\')');
- if ('identica_notices' === $this->table)
- return $this->target->whereAdd('MATCH(content) ' .
- 'against (\''.addslashes($q).'\')');
+ if ('identica_people' === $this->table) {
+ $this->target->whereAdd('MATCH(nickname, fullname, location, bio, homepage) ' .
+ 'AGAINST (\''.addslashes($q).'\' IN BOOLEAN MODE)');
+ if (strtolower($q) != $q) {
+ $this->target->whereAdd('MATCH(nickname, fullname, location, bio, homepage) ' .
+ 'AGAINST (\''.addslashes(strtolower($q)).'\' IN BOOLEAN MODE)', 'OR');
+ }
+ return true;
+ } else if ('identica_notices' === $this->table) {
+ $this->target->whereAdd('MATCH(content) ' .
+ 'AGAINST (\''.addslashes($q).'\' IN BOOLEAN MODE)');
+ if (strtolower($q) != $q) {
+ $this->target->whereAdd('MATCH(content) ' .
+ 'AGAINST (\''.addslashes(strtolower($q)).'\' IN BOOLEAN MODE)', 'OR');
+ }
+ return true;
+ } else {
+ throw new ServerException('Unknown table: ' . $this->table);
+ }
}
}
@@ -122,10 +135,13 @@ class PGSearch extends SearchEngine
{
function query($q)
{
- if ('identica_people' === $this->table)
+ if ('identica_people' === $this->table) {
return $this->target->whereAdd('textsearch @@ plainto_tsquery(\''.addslashes($q).'\')');
- if ('identica_notices' === $this->table)
+ } else if ('identica_notices' === $this->table) {
return $this->target->whereAdd('to_tsvector(\'english\', content) @@ plainto_tsquery(\''.addslashes($q).'\')');
+ } else {
+ throw new ServerException('Unknown table: ' . $this->table);
+ }
}
}
diff --git a/lib/searchaction.php b/lib/searchaction.php
index fdfb8dc5a..e7ad4affd 100644
--- a/lib/searchaction.php
+++ b/lib/searchaction.php
@@ -79,10 +79,11 @@ class SearchAction extends Action
function showTop($arr=null)
{
+ $error = null;
if ($arr) {
$error = $arr[1];
}
- if ($error) {
+ if (!empty($error)) {
$this->element('p', 'error', $error);
} else {
$instr = $this->getInstructions();
@@ -109,8 +110,6 @@ class SearchAction extends Action
function showForm($error=null)
{
- global $config;
-
$q = $this->trimmed('q');
$page = $this->trimmed('page', 1);
$this->elementStart('form', array('method' => 'get',
@@ -121,7 +120,7 @@ class SearchAction extends Action
$this->element('legend', null, _('Search site'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
- if (!isset($config['site']['fancy']) || !$config['site']['fancy']) {
+ if (!common_config('site', 'fancy')) {
$this->hidden('action', $this->trimmed('action'));
}
$this->input('q', 'Keyword(s)', $q);
@@ -134,5 +133,31 @@ class SearchAction extends Action
$this->showResults($q, $page);
}
}
+
+ function searchSuggestions($q) {
+ $qe = urlencode($q);
+ $message = sprintf(_(<<<E_O_T
+* Make sure all words are spelled correctly.
+* Try different keywords.
+* Try more general keywords.
+* Try fewer keywords.
+
+You can also try your search on other engines:
+
+* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=identi.ca)
+* [Tweet scan](http://www.tweetscan.com/indexi.php?s=%s)
+* [Google](http://www.google.com/search?q=site%%3A%%%%site.server%%%%+%s)
+* [Yahoo](http://search.yahoo.com/search?p=site%%3A%%%%site.server%%%%+%s)
+
+
+E_O_T
+), $qe, $qe, $qe, $qe);
+ $this->elementStart('dl', array('id' => 'help_search', 'class' => 'help'));
+ $this->element('dt', null, _('Search help'));
+ $this->elementStart('dd', 'instructions');
+ $this->raw(common_markup_to_html($message));
+ $this->elementEnd('dd');
+ $this->elementEnd('div');
+ }
}
diff --git a/lib/servererroraction.php b/lib/servererroraction.php
index 80a3fdd7b..595dcf147 100644
--- a/lib/servererroraction.php
+++ b/lib/servererroraction.php
@@ -42,7 +42,7 @@ require_once INSTALLDIR.'/lib/error.php';
* says that 500 errors should be treated similarly to 400 errors, and
* it's easier to give an HTML response. Maybe we can customize these
* to display some funny animal cartoons. If not, we can probably role
- * these classes up into a single class.
+ * these classes up into a single class.
*
* See: http://tools.ietf.org/html/rfc2616#section-10
*
@@ -57,19 +57,19 @@ class ServerErrorAction extends ErrorAction
function __construct($message='Error', $code=500)
{
parent::__construct($message, $code);
-
+
$this->status = array(500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported');
-
+
$this->default = 500;
}
// XXX: Should these error actions even be invokable via URI?
-
+
function handle($args)
{
parent::handle($args);
@@ -81,12 +81,16 @@ class ServerErrorAction extends ErrorAction
}
$this->message = $this->trimmed('message');
-
+
if (!$this->message) {
- $this->message = "Server Error $this->code";
- }
+ $this->message = "Server Error $this->code";
+ }
$this->showPage();
}
-
+
+ function title()
+ {
+ return $this->status[$this->code];
+ }
}
diff --git a/lib/settingsaction.php b/lib/settingsaction.php
index 53c807c6f..db20c5804 100644
--- a/lib/settingsaction.php
+++ b/lib/settingsaction.php
@@ -78,9 +78,9 @@ class SettingsAction extends Action
common_set_returnto($this->selfUrl());
$user = common_current_user();
if ($user->hasOpenID()) {
- common_redirect(common_local_url('openidlogin'));
+ common_redirect(common_local_url('openidlogin'), 303);
} else {
- common_redirect(common_local_url('login'));
+ common_redirect(common_local_url('login'), 303);
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
diff --git a/lib/subgroupnav.php b/lib/subgroupnav.php
index 5fd8a72a2..4a9b36ae8 100644
--- a/lib/subgroupnav.php
+++ b/lib/subgroupnav.php
@@ -74,38 +74,44 @@ class SubGroupNav extends Widget
$this->out->elementStart('ul', array('class' => 'nav'));
- $this->out->menuItem(common_local_url('subscriptions',
- array('nickname' =>
- $this->user->nickname)),
- _('Subscriptions'),
- sprintf(_('People %s subscribes to'),
- $this->user->nickname),
- $action == 'subscriptions',
- 'nav_subscriptions');
- $this->out->menuItem(common_local_url('subscribers',
- array('nickname' =>
- $this->user->nickname)),
- _('Subscribers'),
- sprintf(_('People subscribed to %s'),
- $this->user->nickname),
- $action == 'subscribers',
- 'nav_subscribers');
- $this->out->menuItem(common_local_url('usergroups',
- array('nickname' =>
- $this->user->nickname)),
- _('Groups'),
- sprintf(_('Groups %s is a member of'),
- $this->user->nickname),
- $action == 'usergroups',
- 'nav_usergroups');
- if ($this->user->id == $cur->id) {
- $this->out->menuItem(common_local_url('invite'),
- _('Invite'),
- sprintf(_('Invite friends and colleagues to join you on %s'),
- common_config('site', 'name')),
- $action == 'invite',
- 'nav_invite');
+ if (Event::handle('StartSubGroupNav', array($this))) {
+
+ $this->out->menuItem(common_local_url('subscriptions',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Subscriptions'),
+ sprintf(_('People %s subscribes to'),
+ $this->user->nickname),
+ $action == 'subscriptions',
+ 'nav_subscriptions');
+ $this->out->menuItem(common_local_url('subscribers',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Subscribers'),
+ sprintf(_('People subscribed to %s'),
+ $this->user->nickname),
+ $action == 'subscribers',
+ 'nav_subscribers');
+ $this->out->menuItem(common_local_url('usergroups',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Groups'),
+ sprintf(_('Groups %s is a member of'),
+ $this->user->nickname),
+ $action == 'usergroups',
+ 'nav_usergroups');
+ if (!is_null($cur) && $this->user->id === $cur->id) {
+ $this->out->menuItem(common_local_url('invite'),
+ _('Invite'),
+ sprintf(_('Invite friends and colleagues to join you on %s'),
+ common_config('site', 'name')),
+ $action == 'invite',
+ 'nav_invite');
+ }
+
+ Event::handle('EndSubGroupNav', array($this));
}
+
$this->out->elementEnd('ul');
}
}
diff --git a/lib/subpeopletagcloudsection.php b/lib/subpeopletagcloudsection.php
new file mode 100644
index 000000000..9f6948dc9
--- /dev/null
+++ b/lib/subpeopletagcloudsection.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Personal tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Personal tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class SubPeopleTagCloudSection extends TagCloudSection
+{
+ function getTags()
+ {
+ $qry = $this->query();
+ $limit = TAGS_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $profile_tag = Memcached_DataObject::cachedQuery('Profile_tag',
+ sprintf($qry,
+ $this->out->user->id));
+ return $profile_tag;
+ }
+
+ function tagUrl($tag) {
+ return common_local_url('peopletag', array('tag' => $tag));
+ }
+
+ function showTag($tag, $weight, $relative) {
+ $rel = 'tag-cloud-';
+ $rel .= 1+intval(7 * $relative * $weight - 0.01);
+
+ $this->out->elementStart('li', $rel);
+ $this->out->element('a', array('href' => $this->tagUrl($tag)), $tag);
+ $this->out->elementEnd('li');
+ }
+}
+
diff --git a/lib/subscriberspeopleselftagcloudsection.php b/lib/subscriberspeopleselftagcloudsection.php
new file mode 100644
index 000000000..115241a53
--- /dev/null
+++ b/lib/subscriberspeopleselftagcloudsection.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Personal tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Personal tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class SubscribersPeopleSelfTagCloudSection extends SubPeopleTagCloudSection
+{
+ function title()
+ {
+ return _('People Tagcloud as self-tagged');
+ }
+
+ function query() {
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscriber where subscribed=%d and subscribed != subscriber and tagger = tagged group by tag order by weight desc';
+
+
+ return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscriber where subscribed=%d and subscribed != subscriber and tagger = tagged and tag is not null group by tag order by weight desc';
+
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscribed where subscriber=%d and subscribed != subscriber and tagger = tagged and tag is not null group by tag order by weight desc';
+
+
+ }
+}
+
diff --git a/lib/subscriberspeopletagcloudsection.php b/lib/subscriberspeopletagcloudsection.php
new file mode 100644
index 000000000..4dafbc1c7
--- /dev/null
+++ b/lib/subscriberspeopletagcloudsection.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Personal tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Personal tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class SubscribersPeopleTagCloudSection extends SubPeopleTagCloudSection
+{
+ function title()
+ {
+ return _('People Tagcloud as tagged');
+ }
+
+ function tagUrl($tag) {
+ $nickname = $this->out->profile->nickname;
+ return common_local_url('subscribers', array('nickname' => $nickname, 'tag' => $tag));
+ }
+
+ function query() {
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on subscriber=tagged and subscribed=tagger where subscribed=%d and subscriber != subscribed group by tag order by weight desc';
+ return 'select tag, count(tag) as weight from subscription left join profile_tag on subscriber=tagged and subscribed=tagger where subscribed=%d and subscriber != subscribed and tag is not null group by tag order by weight desc';
+ }
+}
+
diff --git a/lib/subscriptionspeopleselftagcloudsection.php b/lib/subscriptionspeopleselftagcloudsection.php
new file mode 100644
index 000000000..3896294d2
--- /dev/null
+++ b/lib/subscriptionspeopleselftagcloudsection.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Personal tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Personal tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class SubscriptionsPeopleSelfTagCloudSection extends SubPeopleTagCloudSection
+{
+ function title()
+ {
+ return _('People Tagcloud as self-tagged');
+ }
+
+ function query() {
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscriber where subscribed=%d and subscriber != subscribed and tagger = tagged group by tag order by weight desc';
+
+
+
+ return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscribed where subscriber=%d and subscribed != subscriber and tagger = tagged and tag is not null group by tag order by weight desc';
+
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on tagger = subscriber where subscribed=%d and subscribed != subscriber and tagger = tagged and tag is not null group by tag order by weight desc';
+ }
+}
+
diff --git a/lib/subscriptionspeopletagcloudsection.php b/lib/subscriptionspeopletagcloudsection.php
new file mode 100644
index 000000000..f9c8672e3
--- /dev/null
+++ b/lib/subscriptionspeopletagcloudsection.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Personal tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Personal tag cloud section
+ *
+ * @category Widget
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class SubscriptionsPeopleTagCloudSection extends SubPeopleTagCloudSection
+{
+ function title()
+ {
+ return _('People Tagcloud as tagged');
+ }
+
+ function tagUrl($tag) {
+ $nickname = $this->out->profile->nickname;
+ return common_local_url('subscriptions', array('nickname' => $nickname, 'tag' => $tag));
+ }
+
+ function query() {
+// return 'select tag, count(tag) as weight from subscription left join profile_tag on subscriber=tagger and subscribed=tagged where subscriber=%d and subscriber != subscribed group by tag order by weight desc';
+ return 'select tag, count(tag) as weight from subscription left join profile_tag on subscriber=tagger and subscribed=tagged where subscriber=%d and subscriber != subscribed and tag is not null group by tag order by weight desc';
+ }
+}
+
diff --git a/lib/tagcloudsection.php b/lib/tagcloudsection.php
index ff2aca6d6..62f7d8961 100644
--- a/lib/tagcloudsection.php
+++ b/lib/tagcloudsection.php
@@ -114,7 +114,11 @@ class TagCloudSection extends Section
function tagUrl($tag)
{
- return common_local_url('tag', array('tag' => $tag));
+ if ('showstream' === $this->out->trimmed('action')) {
+ return common_local_url('showstream', array('nickname' => $this->out->profile->nickname, 'tag' => $tag));
+ } else {
+ return common_local_url('tag', array('tag' => $tag));
+ }
}
function divId()
diff --git a/lib/theme.php b/lib/theme.php
index 95030affe..bef660cbf 100644
--- a/lib/theme.php
+++ b/lib/theme.php
@@ -69,4 +69,31 @@ function theme_path($relative, $theme=null)
} else {
return common_path('theme/'.$theme.'/'.$relative);
}
-} \ No newline at end of file
+}
+
+/**
+ * Gets the full URL of a file in a skin dir based on its relative name
+ *
+ * @param string $relative relative path within the theme, skin directory
+ * @param string $theme name of the theme; defaults to current theme
+ * @param string $skin name of the skin; defaults to current theme
+ *
+ * @return string URL of the file
+ */
+
+function skin_path($relative, $theme=null, $skin=null)
+{
+ if (!$theme) {
+ $theme = common_config('site', 'theme');
+ }
+ if (!$skin) {
+ $skin = common_config('site', 'skin');
+ }
+ $server = common_config('theme', 'server');
+ if ($server) {
+ return 'http://'.$server.'/'.$theme.'/skin/'.$skin.'/'.$relative;
+ } else {
+ return common_path('theme/'.$theme.'/skin/'.$skin.'/'.$relative);
+ }
+}
+
diff --git a/lib/topposterssection.php b/lib/topposterssection.php
index 4bd59ac79..1a2ce0014 100644
--- a/lib/topposterssection.php
+++ b/lib/topposterssection.php
@@ -51,7 +51,7 @@ class TopPostersSection extends ProfileSection
$qry = 'SELECT profile.*, count(*) as value ' .
'FROM profile JOIN notice ON profile.id = notice.profile_id ' .
(common_config('public', 'localonly') ? 'WHERE is_local = 1 ' : '') .
- 'GROUP BY profile.id ' .
+ 'GROUP BY profile.id,nickname,fullname,profileurl,homepage,bio,location,profile.created,profile.modified,textsearch ' .
'ORDER BY value DESC ';
$limit = PROFILES_PER_SECTION;
diff --git a/lib/twitter.php b/lib/twitter.php
index deb6fd276..c1d0dc254 100644
--- a/lib/twitter.php
+++ b/lib/twitter.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1
+define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
function get_twitter_data($uri, $screen_name, $password)
{
@@ -45,6 +45,10 @@ function get_twitter_data($uri, $screen_name, $password)
if ($errmsg) {
common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
__FILE__);
+
+ if (defined('SCRIPT_DEBUG')) {
+ print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
+ }
}
curl_close($ch);
@@ -52,63 +56,141 @@ function get_twitter_data($uri, $screen_name, $password)
return $data;
}
-function twitter_user_info($screen_name, $password)
+function twitter_json_data($uri, $screen_name, $password)
{
+ $json_data = get_twitter_data($uri, $screen_name, $password);
- $uri = "http://twitter.com/users/show/$screen_name.json";
- $data = get_twitter_data($uri, $screen_name, $password);
-
- if (!$data) {
+ if (!$json_data) {
return false;
}
- $twit_user = json_decode($data);
+ $data = json_decode($json_data);
- if (!$twit_user) {
+ if (!$data) {
return false;
}
- return $twit_user;
+ return $data;
}
-function update_twitter_user($fuser, $twitter_id, $screen_name)
+function twitter_user_info($screen_name, $password)
{
+ $uri = "http://twitter.com/users/show/$screen_name.json";
+ return twitter_json_data($uri, $screen_name, $password);
+}
- $original = clone($fuser);
- $fuser->nickname = $screen_name;
- $fuser->uri = 'http://twitter.com/' . $screen_name;
- $result = $fuser->updateKeys($original);
+function twitter_friends_ids($screen_name, $password)
+{
+ $uri = "http://twitter.com/friends/ids/$screen_name.json";
+ return twitter_json_data($uri, $screen_name, $password);
+}
+
+function update_twitter_user($twitter_id, $screen_name)
+{
+ $uri = 'http://twitter.com/' . $screen_name;
+
+ $fuser = new Foreign_user();
+
+ $fuser->query('BEGIN');
+
+ // Dropping down to SQL because regular db_object udpate stuff doesn't seem
+ // to work so good with tables that have multiple column primary keys
+
+ // Any time we update the uri for a forein user we have to make sure there
+ // are no dupe entries first -- unique constraint on the uri column
+
+ $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
+ $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
+
+ $result = $fuser->query($qry);
+
+ if ($result) {
+ common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
+ if (defined('SCRIPT_DEBUG')) {
+ print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
+ }
+ }
+
+ // Update the user
+ $qry = 'UPDATE foreign_user SET nickname = ';
+ $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
+ $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
+
+ $result = $fuser->query($qry);
if (!$result) {
+ common_log(LOG_WARNING,
+ "Couldn't update foreign_user data for Twitter user: $screen_name");
common_log_db_error($fuser, 'UPDATE', __FILE__);
+ if (defined('SCRIPT_DEBUG')) {
+ print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
+ print common_log_objstring($fuser) . "\n";
+ $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ print "DB_DataObject Error: " . $error->getMessage() . "\n";
+ }
return false;
}
+ $fuser->query('COMMIT');
+
+ $fuser->free();
+ unset($fuser);
+
return true;
}
function add_twitter_user($twitter_id, $screen_name)
{
+ $new_uri = 'http://twitter.com/' . $screen_name;
+
+ // Clear out any bad old foreign_users with the new user's legit URL
+ // This can happen when users move around or fakester accounts get
+ // repoed, and things like that.
+ $luser = new Foreign_user();
+ $luser->uri = $new_uri;
+ $luser->service = TWITTER_SERVICE;
+ $result = $luser->delete();
+
+ if ($result) {
+ common_log(LOG_WARNING,
+ "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
+ if (defined('SCRIPT_DEBUG')) {
+ print "Removed invalid Twitter user squatting on uri: $new_uri\n";
+ }
+ }
+
+ $luser->free();
+ unset($luser);
+
// Otherwise, create a new Twitter user
- $fuser = DB_DataObject::factory('foreign_user');
+ $fuser = new Foreign_user();
$fuser->nickname = $screen_name;
$fuser->uri = 'http://twitter.com/' . $screen_name;
$fuser->id = $twitter_id;
- $fuser->service = TWITTER_SERVICE; // Twitter
+ $fuser->service = TWITTER_SERVICE;
$fuser->created = common_sql_now();
$result = $fuser->insert();
if (!$result) {
- common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+ common_log(LOG_WARNING,
+ "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
common_log_db_error($fuser, 'INSERT', __FILE__);
- return false;
+ if (defined('SCRIPT_DEBUG')) {
+ print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
+ print common_log_objstring($fuser) . "\n";
+ $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ print "DB_DataObject Error: " . $error->getMessage() . "\n";
+ }
+ } else {
+ common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+ if (defined('SCRIPT_DEBUG')) {
+ print "Added new Twitter user: $screen_name ($twitter_id).\n";
+ }
}
- common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
-
- return true;
+ return $result;
}
// Creates or Updates a Twitter user
@@ -117,53 +199,87 @@ function save_twitter_user($twitter_id, $screen_name)
// Check to see whether the Twitter user is already in the system,
// and update its screen name and uri if so.
- $fuser = Foreign_user::getForeignUser($twitter_id, 1);
+ $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
if ($fuser) {
+ $result = true;
+
// Only update if Twitter screen name has changed
if ($fuser->nickname != $screen_name) {
+ $result = update_twitter_user($twitter_id, $screen_name);
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
"$fuser->id to $screen_name, was $fuser->nickname");
- return update_twitter_user($fuser, $twitter_id, $screen_name);
+ if (defined('SCRIPT_DEBUG')) {
+ print 'Updated nickname (and URI) for Twitter user ' .
+ "$fuser->id to $screen_name, was $fuser->nickname\n";
+ }
}
+ return $result;
+
} else {
return add_twitter_user($twitter_id, $screen_name);
}
+ $fuser->free();
+ unset($fuser);
+
return true;
}
function retreive_twitter_friends($twitter_id, $screen_name, $password)
{
+ $friends = array();
$uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
- $twitter_user = twitter_user_info($screen_name, $password);
+ $friends_ids = twitter_friends_ids($screen_name, $password);
+
+ if (!$friends_ids) {
+ return $friends;
+ }
+
+ if (defined('SCRIPT_DEBUG')) {
+ print "Twitter 'social graph' ids method says $screen_name has " .
+ count($friends_ids) . " friends.\n";
+ }
// Calculate how many pages to get...
- $pages = ceil($twitter_user->friends_count / 100);
+ $pages = ceil(count($friends_ids) / 100);
if ($pages == 0) {
- common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
+ common_log(LOG_WARNING,
+ "Twitter bridge - $screen_name seems to have no friends.");
+ if (defined('SCRIPT_DEBUG')) {
+ print "$screen_name seems to have no friends.\n";
+ }
}
- $friends = array();
-
for ($i = 1; $i <= $pages; $i++) {
$data = get_twitter_data($uri . $i, $screen_name, $password);
if (!$data) {
- return null;
+ common_log(LOG_WARNING,
+ "Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
+ if (defined('SCRIPT_DEBUG')) {
+ print "Couldn't retrieve page $i of $screen_name's friends.\n";
+ }
+ continue;
}
$more_friends = json_decode($data);
if (!$more_friends) {
- return null;
+
+ common_log(LOG_WARNING,
+ "Twitter bridge - No data for page $i of $screen_name's friends.");
+ if (defined('SCRIPT_DEBUG')) {
+ print "No data for page $i of $screen_name's friends.\n";
+ }
+ continue;
}
$friends = array_merge($friends, $more_friends);
@@ -177,19 +293,27 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
$friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
- if (is_null($friends)) {
- common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
+ if (empty($friends)) {
+ common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
+ if (defined('SCRIPT_DEBUG')) {
+ print "Couldn't get friends data from Twitter for $screen_name.\n";
+ }
return false;
}
foreach ($friends as $friend) {
$friend_name = $friend->screen_name;
- $friend_id = $friend->id;
+ $friend_id = (int) $friend->id;
// Update or create the Foreign_user record
if (!save_twitter_user($friend_id, $friend_name)) {
- return false;
+ common_log(LOG_WARNING,
+ "Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
+ if (defined('SCRIPT_DEBUG')) {
+ print "Couldn't save $screen_name's friend, $friend_name.\n";
+ }
+ continue;
}
// Check to see if there's a related local user
@@ -199,8 +323,20 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
// Get associated user and subscribe her
$friend_user = User::staticGet('id', $flink->user_id);
- subs_subscribe_to($user, $friend_user);
- common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
+ if (!empty($friend_user)) {
+ $result = subs_subscribe_to($user, $friend_user);
+
+ if ($result === true) {
+ common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
+ if (defined('SCRIPT_DEBUG')) {
+ print("Subscribed $friend_user->nickname to $user->nickname.\n");
+ }
+ } else {
+ if (defined('SCRIPT_DEBUG')) {
+ print "$result ($friend_user->nickname to $user->nickname)\n";
+ }
+ }
+ }
}
}
@@ -210,7 +346,7 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
function is_twitter_bound($notice, $flink) {
// Check to see if notice should go to Twitter
- if (($flink->noticesync & FOREIGN_NOTICE_SEND)) {
+ if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
// If it's not a Twitter-style reply, or if the user WANTS to send replies.
if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
@@ -218,21 +354,20 @@ function is_twitter_bound($notice, $flink) {
return true;
}
}
-
+
return false;
}
function broadcast_twitter($notice)
{
- global $config;
$success = true;
- $flink = Foreign_link::getByUserID($notice->profile_id,
+ $flink = Foreign_link::getByUserID($notice->profile_id,
TWITTER_SERVICE);
-
- // XXX: Not sure WHERE to check whether a notice should go to
+
+ // XXX: Not sure WHERE to check whether a notice should go to
// Twitter. Should we even put in the queue if it shouldn't? --Zach
- if (is_twitter_bound($notice, $flink)) {
+ if (!is_null($flink) && is_twitter_bound($notice, $flink)) {
$fuser = $flink->getForeignUser();
$twitter_user = $fuser->nickname;
@@ -245,10 +380,10 @@ function broadcast_twitter($notice)
$options = array(
CURLOPT_USERPWD => "$twitter_user:$twitter_password",
CURLOPT_POST => true,
- CURLOPT_POSTFIELDS =>
+ CURLOPT_POSTFIELDS =>
array(
'status' => $statustxt,
- 'source' => $config['integration']['source']
+ 'source' => common_config('integration', 'source')
),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FAILONERROR => true,
@@ -293,7 +428,6 @@ function broadcast_twitter($notice)
$success = false;
}
}
-
+
return $success;
}
-
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
index a4d183fcd..caf8c0716 100644
--- a/lib/twitterapi.php
+++ b/lib/twitterapi.php
@@ -24,10 +24,52 @@ class TwitterapiAction extends Action
var $auth_user;
+ /**
+ * Initialization.
+ *
+ * @param array $args Web and URL arguments
+ *
+ * @return boolean false if user doesn't exist
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+ return true;
+ }
+
+ /**
+ * Handle a request
+ *
+ * @param array $args Arguments from $_REQUEST
+ *
+ * @return void
+ */
+
function handle($args)
{
parent::handle($args);
}
+
+ /**
+ * Overrides XMLOutputter::element to write booleans as strings (true|false).
+ * See that method's documentation for more info.
+ *
+ * @param string $tag Element type or tagname
+ * @param array $attrs Array of element attributes, as
+ * key-value pairs
+ * @param string $content string content of the element
+ *
+ * @return void
+ */
+ function element($tag, $attrs=null, $content=null)
+ {
+ if (is_bool($content)) {
+ $content = ($content ? 'true' : 'false');
+ }
+
+ return parent::element($tag, $attrs, $content);
+ }
function twitter_user_array($profile, $get_notice=false)
{
@@ -44,7 +86,7 @@ class TwitterapiAction extends Action
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
$twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
- $twitter_user['protected'] = 'false'; # not supported by Laconica yet
+ $twitter_user['protected'] = false; # not supported by Laconica yet
$twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
if ($get_notice) {
@@ -60,22 +102,35 @@ class TwitterapiAction extends Action
function twitter_status_array($notice, $include_user=true)
{
-
$profile = $notice->getProfile();
$twitter_status = array();
$twitter_status['text'] = $notice->content;
- $twitter_status['truncated'] = 'false'; # Not possible on Laconica
+ $twitter_status['truncated'] = false; # Not possible on Laconica
$twitter_status['created_at'] = $this->date_twitter($notice->created);
- $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : null;
+ $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
+ intval($notice->reply_to) : null;
$twitter_status['source'] = $this->source_link($notice->source);
$twitter_status['id'] = intval($notice->id);
- $twitter_status['in_reply_to_user_id'] = ($notice->reply_to) ? $this->replier_by_reply(intval($notice->reply_to)) : null;
+
+ $replier_profile = null;
+
+ if ($notice->reply_to) {
+ $reply = Notice::staticGet(intval($notice->reply_to));
+ if ($reply) {
+ $replier_profile = $reply->getProfile();
+ }
+ }
+
+ $twitter_status['in_reply_to_user_id'] =
+ ($replier_profile) ? intval($replier_profile->id) : null;
+ $twitter_status['in_reply_to_screen_name'] =
+ ($replier_profile) ? $replier_profile->nickname : null;
if (isset($this->auth_user)) {
- $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false';
+ $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
} else {
- $twitter_status['favorited'] = 'false';
+ $twitter_status['favorited'] = false;
}
if ($include_user) {
@@ -91,8 +146,6 @@ class TwitterapiAction extends Action
{
$profile = $notice->getProfile();
-
- $server = common_config('site', 'server');
$entry = array();
# We trim() to avoid extraneous whitespace in the output
@@ -101,8 +154,12 @@ class TwitterapiAction extends Action
$entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
$entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
$entry['published'] = common_date_iso8601($notice->created);
- $entry['id'] = "tag:$server,2008:$entry[link]";
+
+ $taguribase = common_config('integration', 'taguri');
+ $entry['id'] = "tag:$taguribase:$entry[link]";
+
$entry['updated'] = $entry['published'];
+ $entry['author'] = $profile->getBestName();
# RSS Item specific
$entry['description'] = $entry['content'];
@@ -115,7 +172,6 @@ class TwitterapiAction extends Action
function twitter_rss_dmsg_array($message)
{
- $server = common_config('site', 'server');
$entry = array();
$entry['title'] = sprintf('Message from %s to %s',
@@ -124,8 +180,12 @@ class TwitterapiAction extends Action
$entry['content'] = common_xml_safe_str(trim($message->content));
$entry['link'] = common_local_url('showmessage', array('message' => $message->id));
$entry['published'] = common_date_iso8601($message->created);
- $entry['id'] = "tag:$server,2008:$entry[link]";
+
+ $taguribase = common_config('integration', 'taguri');
+
+ $entry['id'] = "tag:$taguribase,:$entry[link]";
$entry['updated'] = $entry['published'];
+ $entry['author'] = $message->getFrom()->getBestName();
# RSS Item specific
$entry['description'] = $entry['content'];
@@ -137,7 +197,6 @@ class TwitterapiAction extends Action
function twitter_dmsg_array($message)
{
-
$twitter_dm = array();
$from_profile = $message->getFrom();
@@ -198,18 +257,6 @@ class TwitterapiAction extends Action
$this->elementEnd('item');
}
- function show_twitter_atom_entry($entry)
- {
- $this->elementStart('entry');
- $this->element('title', null, $entry['title']);
- $this->element('content', array('type' => 'html'), $entry['content']);
- $this->element('id', null, $entry['id']);
- $this->element('published', null, $entry['published']);
- $this->element('updated', null, $entry['updated']);
- $this->element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), null);
- $this->elementEnd('entry');
- }
-
function show_json_objects($objects)
{
print(json_encode($objects));
@@ -323,7 +370,7 @@ class TwitterapiAction extends Action
$this->end_twitter_rss();
}
- function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null)
+ function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
{
$this->init_document('atom');
@@ -331,23 +378,29 @@ class TwitterapiAction extends Action
$this->element('title', null, $title);
$this->element('id', null, $id);
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+
if (!is_null($suplink)) {
# For FriendFeed's SUP protocol
$this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
'href' => $suplink,
'type' => 'application/json'));
}
+
+ if (!is_null($selfuri)) {
+ $this->element('link', array('href' => $selfuri,
+ 'rel' => 'self', 'type' => 'application/atom+xml'), null);
+ }
+
+ $this->element('updated', null, common_date_iso8601('now'));
$this->element('subtitle', null, $subtitle);
if (is_array($notice)) {
foreach ($notice as $n) {
- $entry = $this->twitter_rss_entry_array($n);
- $this->show_twitter_atom_entry($entry);
+ $this->raw($n->asAtomEntry());
}
} else {
while ($notice->fetch()) {
- $entry = $this->twitter_rss_entry_array($notice);
- $this->show_twitter_atom_entry($entry);
+ $this->raw($notice->asAtomEntry());
}
}
@@ -384,23 +437,7 @@ class TwitterapiAction extends Action
function date_twitter($dt)
{
$t = strtotime($dt);
- return date("D M d G:i:s O Y", $t);
- }
-
- function replier_by_reply($reply_id)
- {
- $notice = Notice::staticGet($reply_id);
- if ($notice) {
- $profile = $notice->getProfile();
- if ($profile) {
- return intval($profile->id);
- } else {
- common_debug('Can\'t find a profile for notice: ' . $notice->id, __FILE__);
- }
- } else {
- common_debug("Can't get notice: $reply_id", __FILE__);
- }
- return null;
+ return date("D M d H:i:s O Y", $t);
}
// XXX: Candidate for a general utility method somewhere?
@@ -543,13 +580,16 @@ class TwitterapiAction extends Action
function init_twitter_atom()
{
$this->startXML();
- $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
+ // FIXME: don't hardcode the language here!
+ $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
+ 'xml:lang' => 'en-US',
+ 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
}
function end_twitter_atom()
{
- $this->endXML();
$this->elementEnd('feed');
+ $this->endXML();
}
function show_profile($profile, $content_type='xml', $notice=null)
@@ -615,79 +655,4 @@ class TwitterapiAction extends Action
return $source_name;
}
- function show_extended_profile($user, $apidata)
- {
-
- $this->auth_user = $apidata['user'];
-
- $profile = $user->getProfile();
-
- if (!$profile) {
- common_server_error(_('User has no profile.'));
- return;
- }
-
- $twitter_user = $this->twitter_user_array($profile, true);
-
- // Add in extended user fields offered up by this method
- $twitter_user['created_at'] = $this->date_twitter($profile->created);
-
- $subbed = DB_DataObject::factory('subscription');
- $subbed->subscriber = $profile->id;
- $subbed_count = (int) $subbed->count() - 1;
-
- $notices = DB_DataObject::factory('notice');
- $notices->profile_id = $profile->id;
- $notice_count = (int) $notices->count();
-
- $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
- $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
-
- // Other fields Twitter sends...
- $twitter_user['profile_background_color'] = '';
- $twitter_user['profile_text_color'] = '';
- $twitter_user['profile_link_color'] = '';
- $twitter_user['profile_sidebar_fill_color'] = '';
-
- $faves = DB_DataObject::factory('fave');
- $faves->user_id = $user->id;
- $faves_count = (int) $faves->count();
- $twitter_user['favourites_count'] = $faves_count;
-
- $timezone = 'UTC';
-
- if ($user->timezone) {
- $timezone = $user->timezone;
- }
-
- $t = new DateTime;
- $t->setTimezone(new DateTimeZone($timezone));
- $twitter_user['utc_offset'] = $t->format('Z');
- $twitter_user['time_zone'] = $timezone;
-
- $following = 'false';
-
- if (isset($this->auth_user)) {
- if ($this->auth_user->isSubscribed($profile)) {
- $following = 'true';
- }
-
- // Not implemented yet
- $twitter_user['notifications'] = 'false';
- }
-
- $twitter_user['following'] = $following;
-
- if ($apidata['content-type'] == 'xml') {
- $this->init_document('xml');
- $this->show_twitter_xml_user($twitter_user);
- $this->end_document('xml');
- } elseif ($apidata['content-type'] == 'json') {
- $this->init_document('json');
- $this->show_json_objects($twitter_user);
- $this->end_document('json');
- }
-
- }
-
}
diff --git a/lib/util.php b/lib/util.php
index 46aa7b9df..fbef8764a 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -72,8 +72,7 @@ function common_timezone()
}
}
- global $config;
- return $config['site']['timezone'];
+ return common_config('site', 'timezone');
}
function common_language()
@@ -81,7 +80,7 @@ function common_language()
// If there is a user logged in and they've set a language preference
// then return that one...
- if (common_logged_in()) {
+ if (_have_config() && common_logged_in()) {
$user = common_current_user();
$user_language = $user->language;
if ($user_language)
@@ -315,6 +314,10 @@ function common_current_user()
{
global $_cur;
+ if (!_have_config()) {
+ return null;
+ }
+
if ($_cur === false) {
if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
@@ -392,7 +395,7 @@ function common_render_text($text)
return $r;
}
-function common_replace_urls_callback($text, $callback) {
+function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
'(?:'.
@@ -456,11 +459,18 @@ function common_replace_urls_callback($text, $callback) {
if (!in_array($url_parts[2], $tlds)) continue;
+ // Make sure we didn't capture a hash tag
+ if (strpos($url, '#') === 0) continue;
+
// Put the url back the way we found it.
$url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
// Call user specified func
- $modified_url = $callback($url);
+ if (empty($notice_id)) {
+ $modified_url = call_user_func($callback, $url);
+ } else {
+ $modified_url = call_user_func($callback, array($url, $notice_id));
+ }
// Replace it!
$start = mb_strpos($text, $url, $offset);
@@ -474,117 +484,31 @@ function common_replace_urls_callback($text, $callback) {
function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying
// functions
- $ext = pathinfo($url, PATHINFO_EXTENSION);
$url = htmlspecialchars_decode($url);
- $video_ext = array('mp4', 'flv', 'avi', 'mpg', 'mp3', 'ogg');
- $display = $url;
- $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
-
- $attrs = array('href' => $url, 'rel' => 'external');
-
- if (in_array($ext, $video_ext)) {
- $attrs['class'] = 'media';
- }
-
- if ($longurl = common_longurl($url)) {
- $attrs['title'] = $longurl;
+ $display = File_redirection::_canonUrl($url);
+ $longurl_data = File_redirection::where($url);
+ if (is_array($longurl_data)) {
+ $longurl = $longurl_data['url'];
+ } elseif (is_string($longurl_data)) {
+ $longurl = $longurl_data;
+ } else {
+ die('impossible to linkify');
}
+ $attrs = array('href' => $longurl, 'rel' => 'external');
return XMLStringer::estring('a', $attrs, $display);
}
-function common_longurl($short_url)
-{
- $long_url = common_shorten_link($short_url, true);
- if ($long_url === $short_url) return false;
- return $long_url;
-}
-
-function common_longurl2($uri)
-{
- $uri_e = urlencode($uri);
- $longurl = unserialize(file_get_contents("http://api.longurl.org/v1/expand?format=php&url=$uri_e"));
- if (empty($longurl['long_url']) || $uri === $longurl['long_url']) return false;
- return stripslashes($longurl['long_url']);
-}
-
function common_shorten_links($text)
{
if (mb_strlen($text) <= 140) return $text;
- static $cache = array();
- if (isset($cache[$text])) return $cache[$text];
- // \s = not a horizontal whitespace character (since PHP 5.2.4)
- return $cache[$text] = common_replace_urls_callback($text, 'common_shorten_link');;
-}
-
-function common_shorten_link($url, $reverse = false)
-{
- static $url_cache = array();
- if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
-
- $user = common_current_user();
-
- $curlh = curl_init();
- curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
- curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
- curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
- switch($user->urlshorteningservice) {
- case 'ur1.ca':
- $short_url_service = new LilUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case '2tu.us':
- $short_url_service = new TightUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'ptiturl.com':
- $short_url_service = new PtitUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'bit.ly':
- curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($url));
- $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
- break;
-
- case 'is.gd':
- curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'snipr.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'metamark.net':
- curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'tinyurl.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- default:
- $short_url = false;
- }
-
- curl_close($curlh);
-
- if ($short_url) {
- $url_cache[(string)$short_url] = $url;
- return (string)$short_url;
- }
- return $url;
+ return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
}
function common_xml_safe_str($str)
{
- $xmlStr = htmlentities(iconv('UTF-8', 'UTF-8//IGNORE', $str), ENT_NOQUOTES, 'UTF-8');
-
- // Replace control, formatting, and surrogate characters with '*', ala Twitter
- return preg_replace('/[\p{Cc}\p{Cf}\p{Cs}]/u', '*', $str);
+ // Neutralize control codes and surrogates
+ return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
}
function common_tag_link($tag)
@@ -615,10 +539,20 @@ function common_at_link($sender_id, $nickname)
$sender = Profile::staticGet($sender_id);
$recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
if ($recipient) {
+ $user = User::staticGet('id', $recipient->id);
+ if ($user) {
+ $url = common_local_url('userbyid', array('id' => $user->id));
+ } else {
+ $url = $recipient->profileurl;
+ }
$xs = new XMLStringer(false);
+ $attrs = array('href' => $url,
+ 'class' => 'url');
+ if (!empty($recipient->fullname)) {
+ $attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
+ }
$xs->elementStart('span', 'vcard');
- $xs->elementStart('a', array('href' => $recipient->profileurl,
- 'class' => 'url'));
+ $xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $nickname);
$xs->elementEnd('a');
$xs->elementEnd('span');
@@ -633,10 +567,14 @@ function common_group_link($sender_id, $nickname)
$sender = Profile::staticGet($sender_id);
$group = User_group::staticGet('nickname', common_canonical_nickname($nickname));
if ($group && $sender->isMember($group)) {
+ $attrs = array('href' => $group->permalink(),
+ 'class' => 'url');
+ if (!empty($group->fullname)) {
+ $attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
+ }
$xs = new XMLStringer();
$xs->elementStart('span', 'vcard');
- $xs->elementStart('a', array('href' => $group->permalink(),
- 'class' => 'url'));
+ $xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $nickname);
$xs->elementEnd('a');
$xs->elementEnd('span');
@@ -675,7 +613,7 @@ function common_relative_profile($sender, $nickname, $dt=null)
$recipient = new Profile();
// XXX: use a join instead of a subquery
$recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$sender->id.' and subscribed = id)', 'AND');
- $recipient->whereAdd('nickname = "' . trim($nickname) . '"', 'AND');
+ $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
if ($recipient->find(true)) {
// XXX: should probably differentiate between profiles with
// the same name by date of most recent update
@@ -685,7 +623,7 @@ function common_relative_profile($sender, $nickname, $dt=null)
$recipient = new Profile();
// XXX: use a join instead of a subquery
$recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.$sender->id.' and subscriber = id)', 'AND');
- $recipient->whereAdd('nickname = "' . trim($nickname) . '"', 'AND');
+ $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
if ($recipient->find(true)) {
// XXX: should probably differentiate between profiles with
// the same name by date of most recent update
@@ -705,25 +643,48 @@ function common_relative_profile($sender, $nickname, $dt=null)
return null;
}
-function common_local_url($action, $args=null, $fragment=null)
+function common_local_url($action, $args=null, $params=null, $fragment=null)
{
+ static $sensitive = array('login', 'register', 'passwordsettings',
+ 'twittersettings', 'finishopenidlogin',
+ 'finishaddopenid', 'api');
+
$r = Router::get();
- $path = $r->build($action, $args, $fragment);
- if ($path) {
- }
+ $path = $r->build($action, $args, $params, $fragment);
+
+ $ssl = in_array($action, $sensitive);
+
if (common_config('site','fancy')) {
- $url = common_path(mb_substr($path, 1));
+ $url = common_path(mb_substr($path, 1), $ssl);
} else {
- $url = common_path('index.php'.$path);
+ if (mb_strpos($path, '/index.php') === 0) {
+ $url = common_path(mb_substr($path, 1), $ssl);
+ } else {
+ $url = common_path('index.php'.$path, $ssl);
+ }
}
return $url;
}
-function common_path($relative)
+function common_path($relative, $ssl=false)
{
- global $config;
- $pathpart = ($config['site']['path']) ? $config['site']['path']."/" : '';
- return "http://".$config['site']['server'].'/'.$pathpart.$relative;
+ $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
+
+ if (($ssl && (common_config('site', 'ssl') === 'sometimes'))
+ || common_config('site', 'ssl') === 'always') {
+ $proto = 'https';
+ if (is_string(common_config('site', 'sslserver')) &&
+ mb_strlen(common_config('site', 'sslserver')) > 0) {
+ $serverpart = common_config('site', 'sslserver');
+ } else {
+ $serverpart = common_config('site', 'server');
+ }
+ } else {
+ $proto = 'http';
+ $serverpart = common_config('site', 'server');
+ }
+
+ return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
}
function common_date_string($dt)
@@ -813,7 +774,7 @@ function common_redirect($url, $code=307)
303 => "See Other",
307 => "Temporary Redirect");
- header("Status: ${code} $status[$code]");
+ header('HTTP/1.1 '.$code.' '.$status[$code]);
header("Location: $url");
$xo = new XMLOutputter();
@@ -839,18 +800,76 @@ function common_broadcast_notice($notice, $remote=false)
function common_enqueue_notice($notice)
{
- foreach (array('jabber', 'omb', 'sms', 'public', 'twitter', 'facebook') as $transport) {
- $qi = new Queue_item();
- $qi->notice_id = $notice->id;
- $qi->transport = $transport;
- $qi->created = $notice->created;
- $result = $qi->insert();
- if (!$result) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
- return false;
+ if (common_config('queue','subsystem') == 'stomp') {
+ // use an external message queue system via STOMP
+ require_once("Stomp.php");
+ $con = new Stomp(common_config('queue','stomp_server'));
+ if (!$con->connect()) {
+ common_log(LOG_ERR, 'Failed to connect to queue server');
+ return false;
+ }
+ $queue_basename = common_config('queue','queue_basename');
+ foreach (array('jabber', 'omb', 'sms', 'public', 'twitter', 'facebook', 'ping') as $transport) {
+ if (!$con->send(
+ '/queue/'.$queue_basename.'-'.$transport, // QUEUE
+ $notice->id, // BODY of the message
+ array ( // HEADERS of the msg
+ 'created' => $notice->created
+ ))) {
+ common_log(LOG_ERR, 'Error sending to '.$transport.' queue');
+ return false;
+ }
+ common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' . $notice->id . ' for ' . $transport);
+ }
+
+ //send tags as headers, so they can be used as JMS selectors
+ common_log(LOG_DEBUG, 'searching for tags ' . $notice->id);
+ $tags = array();
+ $tag = new Notice_tag();
+ $tag->notice_id = $notice->id;
+ if ($tag->find()) {
+ while ($tag->fetch()) {
+ common_log(LOG_DEBUG, 'tag found = ' . $tag->tag);
+ array_push($tags,$tag->tag);
+ }
+ }
+ $tag->free();
+
+ $con->send('/topic/laconica.'.$notice->profile_id,
+ $notice->content,
+ array(
+ 'profile_id' => $notice->profile_id,
+ 'created' => $notice->created,
+ 'tags' => implode($tags,' - ')
+ )
+ );
+ common_log(LOG_DEBUG, 'sent to personal topic ' . $notice->id);
+ $con->send('/topic/laconica.allusers',
+ $notice->content,
+ array(
+ 'profile_id' => $notice->profile_id,
+ 'created' => $notice->created,
+ 'tags' => implode($tags,' - ')
+ )
+ );
+ common_log(LOG_DEBUG, 'sent to catch-all topic ' . $notice->id);
+ $result = true;
+ }
+ else {
+ // in any other case, 'internal'
+ foreach (array('jabber', 'omb', 'sms', 'public', 'twitter', 'facebook', 'ping') as $transport) {
+ $qi = new Queue_item();
+ $qi->notice_id = $notice->id;
+ $qi->transport = $transport;
+ $qi->created = $notice->created;
+ $result = $qi->insert();
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
+ return false;
+ }
+ common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
}
- common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
}
return $result;
}
@@ -915,9 +934,9 @@ function common_profile_url($nickname)
// Should make up a reasonable root URL
-function common_root_url()
+function common_root_url($ssl=false)
{
- return common_path('');
+ return common_path('', $ssl);
}
// returns $bytes bytes of random data as a hexadecimal string
@@ -926,7 +945,7 @@ function common_root_url()
function common_good_rand($bytes)
{
// XXX: use random.org...?
- if (file_exists('/dev/urandom')) {
+ if (@file_exists('/dev/urandom')) {
return common_urandom($bytes);
} else { // FIXME: this is probably not good enough
return common_mtrand($bytes);
@@ -976,8 +995,7 @@ function common_ensure_syslog()
{
static $initialized = false;
if (!$initialized) {
- global $config;
- openlog($config['syslog']['appname'], 0, LOG_USER);
+ openlog(common_config('syslog', 'appname'), 0, LOG_USER);
$initialized = true;
}
}
@@ -1060,7 +1078,7 @@ function common_accept_to_prefs($accept, $def = '*/*')
foreach($parts as $part) {
// FIXME: doesn't deal with params like 'text/html; level=1'
- @list($value, $qpart) = explode(';', $part);
+ @list($value, $qpart) = explode(';', trim($part));
$match = array();
if(!isset($qpart)) {
$prefs[$value] = 1;
@@ -1287,3 +1305,16 @@ function common_compatible_license($from, $to)
// XXX: better compatibility check needed here!
return ($from == $to);
}
+
+/**
+ * returns a quoted table name, if required according to config
+ */
+function common_database_tablename($tablename)
+{
+
+ if(common_config('db','quote_identifiers')) {
+ $tablename = '"'. $tablename .'"';
+ }
+ //table prefixes could be added here later
+ return $tablename;
+}