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.php14
-rw-r--r--lib/attachmentlist.php292
-rw-r--r--lib/attachmentnoticesection.php75
-rw-r--r--lib/attachmenttagcloudsection.php83
-rw-r--r--lib/common.php9
-rw-r--r--lib/facebookaction.php264
-rw-r--r--lib/noticelist.php39
-rw-r--r--lib/noticesection.php35
-rw-r--r--lib/popularnoticesection.php2
-rw-r--r--lib/profileaction.php13
-rw-r--r--lib/queuehandler.php72
-rw-r--r--lib/router.php27
-rw-r--r--lib/rssaction.php6
-rw-r--r--lib/snapshot.php227
-rw-r--r--lib/tagcloudsection.php6
-rw-r--r--lib/theme.php29
-rw-r--r--lib/twitter.php2
-rw-r--r--lib/util.php228
20 files changed, 1115 insertions, 314 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 6b130b6d5..6a69d2651 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -243,6 +243,12 @@ class Action extends HTMLOutputter // lawsuit
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.form.js')),
' ');
+
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/jquery.joverlay.min.js')),
+ ' ');
+
+
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
@@ -861,10 +867,12 @@ class Action extends HTMLOutputter // lawsuit
}
if ($lm) {
header('Last-Modified: ' . date(DATE_RFC1123, $lm));
- if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
- $ims = strtotime($_SERVER['HTTP_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_none_match = $_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)) {
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
new file mode 100644
index 000000000..559962acc
--- /dev/null
+++ b/lib/attachmentlist.php
@@ -0,0 +1,292 @@
+<?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('dl', array('id' =>'attachments'));
+ $this->out->element('dt', null, _('Attachments'));
+ $this->out->elementStart('dd');
+ $this->out->elementStart('ol', 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('dd');
+ $this->out->elementEnd('ol');
+ $this->out->elementEnd('dl');
+
+ 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 $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' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id);
+ }
+
+ function showLink() {
+ $this->out->elementStart('a', $this->linkAttr());
+ $this->out->element('span', null, $this->linkTitle());
+ $this->showRepresentation();
+ $this->out->elementEnd('a');
+ }
+
+ function showNoticeAttachment()
+ {
+ $this->showLink();
+ }
+
+ function showRepresentation() {
+ $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
+ if (!empty($thumbnail)) {
+ $this->out->element('img', array('alt' => 'nothing to say', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height));
+ }
+ }
+
+ /**
+ * 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 $this->attachment->url;
+ }
+
+ 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/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/common.php b/lib/common.php
index 8f95c2361..4a98741e8 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-define('LACONICA_VERSION', '0.7.3');
+define('LACONICA_VERSION', '0.8.0dev');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
@@ -71,6 +71,7 @@ $config =
array('name' => 'Just another Laconica microblog',
'server' => $_server,
'theme' => 'default',
+ 'skin' => 'default',
'path' => $_path,
'logfile' => null,
'logo' => null,
@@ -142,6 +143,8 @@ $config =
array('piddir' => '/var/run',
'user' => false,
'group' => false),
+ 'twitterbridge' =>
+ array('enabled' => false),
'integration' =>
array('source' => 'Laconica', # source attribute for Twitter
'taguri' => $_server.',2009'), # base for tag URIs
@@ -156,6 +159,10 @@ $config =
'newuser' =>
array('subscribe' => null,
'welcome' => null),
+ 'snapshot' =>
+ array('run' => 'web',
+ 'frequency' => 10000,
+ 'reporturl' => 'http://laconi.ca/stats/report'),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
diff --git a/lib/facebookaction.php b/lib/facebookaction.php
index 043a078cd..a445750f7 100644
--- a/lib/facebookaction.php
+++ b/lib/facebookaction.php
@@ -38,14 +38,14 @@ require_once INSTALLDIR.'/lib/noticeform.php';
class FacebookAction extends Action
{
-
+
var $facebook = null;
var $fbuid = null;
var $flink = null;
var $action = null;
var $app_uri = null;
var $app_name = null;
-
+
/**
* Constructor
*
@@ -60,71 +60,71 @@ class FacebookAction extends Action
function __construct($output='php://output', $indent=true, $facebook=null, $flink=null)
{
parent::__construct($output, $indent);
-
+
$this->facebook = $facebook;
$this->flink = $flink;
-
+
if ($this->flink) {
- $this->fbuid = $flink->foreign_id;
+ $this->fbuid = $flink->foreign_id;
$this->user = $flink->getUser();
}
-
+
$this->args = array();
}
-
+
function prepare($argarray)
- {
+ {
parent::prepare($argarray);
-
+
$this->facebook = getFacebook();
$this->fbuid = $this->facebook->require_login();
-
+
$this->action = $this->trimmed('action');
-
+
$app_props = $this->facebook->api_client->Admin_getAppProperties(
array('canvas_name', 'application_name'));
-
+
$this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
$this->app_name = $app_props['application_name'];
$this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
-
+
return true;
-
+
}
-
+
function showStylesheets()
{
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
-
+
+ $this->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
+
$theme = common_config('site', 'theme');
-
+
$ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
-
+
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
-
+
$ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
-
+
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
}
-
+
function showScripts()
{
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
-
+
$this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
}
-
+
/**
* Start an Facebook ready HTML document
*
@@ -138,11 +138,11 @@ class FacebookAction extends Action
* @return void
*/
- function startHTML($type=null)
- {
+ function startHTML($type=null)
+ {
$this->showStylesheets();
$this->showScripts();
-
+
$this->elementStart('div', array('class' => 'facebook-page'));
}
@@ -177,18 +177,18 @@ class FacebookAction extends Action
$this->showFooter();
$this->elementEnd('div');
}
-
+
function showAside()
{
}
function showHead($error, $success)
{
-
+
if ($error) {
$this->element("h1", null, $error);
}
-
+
if ($success) {
$this->element("h1", null, $success);
}
@@ -198,10 +198,10 @@ class FacebookAction extends Action
$this->element('fb:add-section-button', array('section' => 'profile'));
$this->elementEnd('span');
$this->elementEnd('fb:if-section-not-added');
-
+
}
-
+
// Make this into a widget later
function showLocalNav()
{
@@ -229,8 +229,8 @@ class FacebookAction extends Action
$this->elementEnd('li');
$this->elementEnd('ul');
- }
-
+ }
+
/**
* Show header of the page.
*
@@ -245,7 +245,7 @@ class FacebookAction extends Action
$this->showNoticeForm();
$this->elementEnd('div');
}
-
+
/**
* Show page, a template method.
*
@@ -258,7 +258,7 @@ class FacebookAction extends Action
$this->showBody();
$this->endHTML();
}
-
+
function showInstructions()
{
@@ -278,7 +278,7 @@ class FacebookAction extends Action
$this->element('a',
array('href' => common_local_url('register')), _('Register'));
$this->text($loginmsg_part2);
- $this->elementEnd('p');
+ $this->elementEnd('p');
$this->elementEnd('dd');
$this->elementEnd('dl');
@@ -317,7 +317,7 @@ class FacebookAction extends Action
$this->elementEnd('ul');
$this->submit('submit', _('Login'));
- $this->elementEnd('fieldset');
+ $this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementStart('p');
@@ -329,73 +329,73 @@ class FacebookAction extends Action
$this->elementEnd('div');
}
-
-
+
+
function updateProfileBox($notice)
{
// Need to include inline CSS for styling the Profile box
- $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
- $icon_url = $app_props['icon_url'];
+ $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
+ $icon_url = $app_props['icon_url'];
$style = '<style>
- .entry-title *,
- .entry-content * {
- font-size:14px;
- font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
- }
- .entry-title a,
- .entry-content a {
- color:#002E6E;
- }
+ .entry-title *,
+ .entry-content * {
+ font-size:14px;
+ font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
+ }
+ .entry-title a,
+ .entry-content a {
+ color:#002E6E;
+ }
.entry-title .vcard .photo {
float:left;
display:inline;
- margin-right:11px;
- margin-bottom:11px
+ margin-right:11px;
+ margin-bottom:11px
}
- .entry-title {
- margin-bottom:11px;
- }
+ .entry-title {
+ margin-bottom:11px;
+ }
.entry-title p.entry-content {
display:inline;
- margin-left:5px;
+ margin-left:5px;
}
- div.entry-content {
- clear:both;
- }
+ div.entry-content {
+ clear:both;
+ }
div.entry-content dl,
div.entry-content dt,
div.entry-content dd {
display:inline;
- text-transform:lowercase;
+ text-transform:lowercase;
}
div.entry-content dd,
- div.entry-content .device dt {
- margin-left:0;
- margin-right:5px;
+ div.entry-content .device dt {
+ margin-left:0;
+ margin-right:5px;
}
div.entry-content dl.timestamp dt,
- div.entry-content dl.response dt {
+ div.entry-content dl.response dt {
display:none;
}
div.entry-content dd a {
display:inline-block;
}
- #facebook_laconica_app {
- text-indent:-9999px;
- height:16px;
- width:16px;
- display:block;
- background:url('.$icon_url.') no-repeat 0 0;
- float:right;
- }
- </style>';
+ #facebook_laconica_app {
+ text-indent:-9999px;
+ height:16px;
+ width:16px;
+ display:block;
+ background:url('.$icon_url.') no-repeat 0 0;
+ float:right;
+ }
+ </style>';
$this->xw->openMemory();
@@ -407,12 +407,12 @@ class FacebookAction extends Action
$fbml_main = "<fb:narrow>$style " . $this->xw->outputMemory(false) . "</fb:narrow>";
- $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);
+ $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);
$this->xw->openURI('php://output');
}
-
-
+
+
/**
* Generate pagination links
*
@@ -457,24 +457,24 @@ class FacebookAction extends Action
$this->elementEnd('div');
}
}
-
- function updateFacebookStatus($notice)
+
+ function updateFacebookStatus($notice)
{
$prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid);
$content = "$prefix $notice->content";
-
+
if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) {
$this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true);
}
}
-
+
function saveNewNotice()
{
$user = $this->flink->getUser();
$content = $this->trimmed('status_textarea');
-
+
if (!$content) {
$this->showPage(_('No notice content!'));
return;
@@ -492,9 +492,9 @@ class FacebookAction extends Action
$cmd = $inter->handle_command($user, $content_shortened);
if ($cmd) {
-
+
// XXX fix this
-
+
$cmd->execute(new WebChannel());
return;
}
@@ -510,20 +510,20 @@ class FacebookAction extends Action
}
common_broadcast_notice($notice);
-
+
// Also update the user's Facebook status
$this->updateFacebookStatus($notice);
$this->updateProfileBox($notice);
-
+
}
}
-class FacebookNoticeForm extends NoticeForm
+class FacebookNoticeForm extends NoticeForm
{
-
+
var $post_action = null;
-
+
/**
* Constructor
*
@@ -532,13 +532,13 @@ class FacebookNoticeForm extends NoticeForm
* @param string $content content to pre-fill
*/
- function __construct($out=null, $action=null, $content=null,
+ function __construct($out=null, $action=null, $content=null,
$post_action=null, $user=null)
{
parent::__construct($out, $action, $content, $user);
$this->post_action = $post_action;
}
-
+
/**
* Action of the form
*
@@ -554,7 +554,7 @@ class FacebookNoticeForm extends NoticeForm
class FacebookNoticeList extends NoticeList
{
-
+
/**
* constructor
*
@@ -565,7 +565,7 @@ class FacebookNoticeList extends NoticeList
{
parent::__construct($notice, $out);
}
-
+
/**
* show the list of notices
*
@@ -619,7 +619,7 @@ class FacebookNoticeList extends NoticeList
}
class FacebookNoticeListItem extends NoticeListItem
-{
+{
/**
* constructor
@@ -646,51 +646,19 @@ class FacebookNoticeListItem extends NoticeListItem
function show()
{
$this->showStart();
+ $this->showNotice();
+ $this->showNoticeInfo();
- $this->out->elementStart('div', 'entry-title');
- $this->showAuthor();
- $this->showContent();
- $this->out->elementEnd('div');
-
- $this->out->elementStart('div', 'entry-content');
- $this->showNoticeLink();
- $this->showNoticeSource();
- $this->showReplyTo();
- $this->out->elementEnd('div');
+ // XXX: Need to update to show attachements and controls
$this->showEnd();
}
- function showNoticeLink()
- {
- $noticeurl = common_local_url('shownotice',
- array('notice' => $this->notice->id));
- // XXX: we need to figure this out better. Is this right?
- if (strcmp($this->notice->uri, $noticeurl) != 0 &&
- preg_match('/^http/', $this->notice->uri)) {
- $noticeurl = $this->notice->uri;
- }
-
- $this->out->elementStart('dl', 'timestamp');
- $this->out->element('dt', null, _('Published'));
- $this->out->elementStart('dd', null);
- $this->out->elementStart('a', array('rel' => 'bookmark',
- 'href' => $noticeurl));
- $dt = common_date_iso8601($this->notice->created);
- $this->out->element('abbr', array('class' => 'published',
- 'title' => $dt),
- common_date_string($this->notice->created));
- $this->out->elementEnd('a');
- $this->out->elementEnd('dd');
- $this->out->elementEnd('dl');
- }
-
}
-
class FacebookProfileBoxNotice extends FacebookNoticeListItem
-{
-
+{
+
/**
* constructor
*
@@ -703,36 +671,24 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
{
parent::__construct($notice, $out);
}
-
+
/**
- * Recipe function for displaying a single notice in the
- * Facebook App's Profile
+ * Recipe function for displaying a single notice in the
+ * Facebook App profile notice box
*
* @return void
*/
function show()
{
-
- $this->out->elementStart('div', 'entry-title');
- $this->showAuthor();
- $this->showContent();
- $this->out->elementEnd('div');
-
- $this->out->elementStart('div', 'entry-content');
-
- $this->showNoticeLink();
- $this->showNoticeSource();
- $this->showReplyTo();
- $this->out->elementEnd('div');
-
+ $this->showNotice();
+ $this->showNoticeInfo();
$this->showAppLink();
-
}
- function showAppLink()
+ function showAppLink()
{
-
+
$this->facebook = getFacebook();
$app_props = $this->facebook->api_client->Admin_getAppProperties(
@@ -740,7 +696,7 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
$this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
$this->app_name = $app_props['application_name'];
-
+
$this->out->elementStart('a', array('id' => 'facebook_laconica_app',
'href' => $this->app_uri));
$this->out->text($this->app_name);
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 4182d8808..50a95cfcb 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -34,6 +34,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/favorform.php';
require_once INSTALLDIR.'/lib/disfavorform.php';
+require_once INSTALLDIR.'/lib/attachmentlist.php';
/**
* widget for displaying a list of notices
@@ -179,6 +180,7 @@ class NoticeListItem extends Widget
{
$this->showStart();
$this->showNotice();
+ $this->showNoticeAttachments();
$this->showNoticeInfo();
$this->showNoticeOptions();
$this->showEnd();
@@ -192,12 +194,32 @@ class NoticeListItem extends Widget
$this->out->elementEnd('div');
}
+ function showNoticeAttachments() {
+ if ($this->isUsedInList()) {
+ return;
+ }
+ $al = new AttachmentList($this->notice, $this->out);
+ $al->show();
+ }
+
+ function isUsedInList() {
+ return 'shownotice' !== $this->out->args['action'];
+ }
+
+ function attachmentCount($discriminant = true) {
+ $file_oembed = new File_oembed;
+ $query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id;
+ $file_oembed->query($query);
+ $file_oembed->fetch();
+ return intval($file_oembed->c);
+ }
+
function showNoticeInfo()
{
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
- $this->showReplyTo();
+ $this->showContext();
$this->out->elementEnd('div');
}
@@ -421,17 +443,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 94c2738ef..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);
}
@@ -100,6 +96,37 @@ class NoticeSection extends Section
$this->out->elementStart('p', 'entry-content');
$this->out->raw($notice->rendered);
+
+ $notice_link_cfg = common_config('site', 'notice_link');
+ if ('direct' === $notice_link_cfg) {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ } elseif ('attachment' === $notice_link_cfg) {
+ if ($count = $notice->hasAttachments()) {
+ // link to attachment(s) pages
+ if (1 === $count) {
+ $f2p = File_to_post::staticGet('post_id', $notice->id);
+ $href = common_local_url('attachment', array('attachment' => $f2p->file_id));
+ $att_class = 'attachment';
+ } else {
+ $href = common_local_url('attachments', array('notice' => $notice->id));
+ $att_class = 'attachments';
+ }
+
+ $clip = theme_path('images/icons/clip.png', 'base');
+ $this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: $count"));
+ $this->out->raw(" ($count&nbsp");
+ $this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt'));
+ $this->out->text(')');
+ $this->out->elementEnd('a');
+ } else {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ }
+ }
+
$this->out->elementEnd('p');
if (!empty($notice->value)) {
$this->out->elementStart('p');
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
index a8d47ef54..375d5538b 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -51,7 +51,7 @@ class PopularNoticeSection extends NoticeSection
if (common_config('db', 'type') == 'pgsql') {
$weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
if (!empty($this->out->tag)) {
- $tag = pg_escape_string($this->tag);
+ $tag = pg_escape_string($this->out->tag);
}
} else {
$weightexpr='sum(exp(-(now() - fave.modified) / %s))';
diff --git a/lib/profileaction.php b/lib/profileaction.php
index 1f2e30994..a3437ff4d 100644
--- a/lib/profileaction.php
+++ b/lib/profileaction.php
@@ -49,16 +49,17 @@ require_once INSTALLDIR.'/lib/groupminilist.php';
class ProfileAction extends Action
{
- var $user = null;
- var $page = null;
+ var $user = null;
+ var $page = null;
var $profile = null;
+ var $tag = null;
function prepare($args)
{
parent::prepare($args);
$nickname_arg = $this->arg('nickname');
- $nickname = common_canonical_nickname($nickname_arg);
+ $nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
@@ -85,10 +86,9 @@ class ProfileAction extends Action
return false;
}
+ $this->tag = $this->trimmed('tag');
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-
common_set_returnto($this->selfUrl());
-
return true;
}
@@ -244,4 +244,5 @@ class ProfileAction extends Action
$this->elementEnd('div');
}
-} \ No newline at end of file
+}
+
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index fde650d9e..f76f16e07 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -75,15 +75,9 @@ class QueueHandler extends Daemon
return true;
}
- function run()
- {
- if (!$this->start()) {
- return false;
- }
- $transport = $this->transport();
- $this->log(LOG_INFO, 'checking for queued notices for "' . $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);
@@ -115,6 +109,67 @@ class QueueHandler extends Daemon
$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;
}
@@ -143,3 +198,4 @@ class QueueHandler extends Daemon
common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
}
}
+
diff --git a/lib/router.php b/lib/router.php
index 12590b790..fc119821b 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -131,7 +131,7 @@ class Router
// 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'));
}
@@ -151,12 +151,19 @@ class Router
$m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
array('q' => '.+'));
- // notice
+ $m->connect('attachment/:attachment/ajax',
+ array('action' => 'attachment_ajax'),
+ array('attachment' => '[0-9]+'));
+
+ $m->connect('attachment/:attachment/thumbnail',
+ array('action' => 'attachment_thumbnail'),
+ array('attachment' => '[0-9]+'));
$m->connect('notice/new', array('action' => 'newnotice'));
$m->connect('notice/new?replyto=:replyto',
array('action' => 'newnotice'),
array('replyto' => '[A-Za-z0-9_-]+'));
+
$m->connect('notice/:notice',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));
@@ -165,6 +172,12 @@ 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',
@@ -406,6 +419,16 @@ class Router
array('size' => '(original|96|48|24)',
'nickname' => '[a-zA-Z0-9]{1,64}'));
+ $m->connect(':nickname/tag/:tag/rss',
+ array('action' => 'userrss'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
+ $m->connect(':nickname/tag/:tag',
+ array('action' => 'showstream'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
$m->connect(':nickname',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));
diff --git a/lib/rssaction.php b/lib/rssaction.php
index ddba862dc..2f25ed7e4 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -97,7 +97,11 @@ class Rss10Action extends Action
// Parent handling, including cache check
parent::handle($args);
// Get the list of notices
- $this->notices = $this->getNotices($this->limit);
+ if (empty($this->tag)) {
+ $this->notices = $this->getNotices($this->limit);
+ } else {
+ $this->notices = $this->getTaggedNotices($this->tag, $this->limit);
+ }
$this->showRss();
}
diff --git a/lib/snapshot.php b/lib/snapshot.php
new file mode 100644
index 000000000..4b05b502d
--- /dev/null
+++ b/lib/snapshot.php
@@ -0,0 +1,227 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * A snapshot of site stats that can report itself to headquarters
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Stats
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * A snapshot of site stats that can report itself to headquarters
+ *
+ * This class will collect statistics on the site and report them to
+ * a statistics server of the admin's choice. (Default is the big one
+ * at laconi.ca.)
+ *
+ * It can either be called from a cron job, or run occasionally by the
+ * Web site.
+ *
+ * @category Stats
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+
+class Snapshot
+{
+ var $stats = null;
+
+ /**
+ * Constructor for a snapshot
+ */
+
+ function __construct()
+ {
+ }
+
+ /**
+ * Static function for reporting statistics
+ *
+ * This function checks whether it should report statistics, based on
+ * the current configuation settings. If it should, it creates a new
+ * Snapshot object, takes a snapshot, and reports it to headquarters.
+ *
+ * @return void
+ */
+
+ static function check()
+ {
+ switch (common_config('snapshot', 'run')) {
+ case 'web':
+ // skip if we're not running on the Web.
+ if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ break;
+ }
+ // Run once every frequency hits
+ // XXX: do frequency by time (once a week, etc.) rather than
+ // hits
+ if (rand() % common_config('snapshot', 'frequency') == 0) {
+ $snapshot = new Snapshot();
+ $snapshot->take();
+ $snapshot->report();
+ }
+ break;
+ case 'cron':
+ // skip if we're running on the Web
+ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ break;
+ }
+ common_log(LOG_INFO, 'Running snapshot from cron job');
+ // We're running from the command line; assume
+
+ $snapshot = new Snapshot();
+ $snapshot->take();
+ common_log(LOG_INFO, count($snapshot->stats) . " statistics being uploaded.");
+ $snapshot->report();
+
+ break;
+ case 'never':
+ break;
+ default:
+ common_log(LOG_WARNING, "Unrecognized value for snapshot run config.");
+ }
+ }
+
+ /**
+ * Take a snapshot of the server
+ *
+ * Builds an array of statistical and configuration data based
+ * on the local database and config files. We avoid grabbing any
+ * information that could be personal or private.
+ *
+ * @return void
+ */
+
+ function take()
+ {
+ $this->stats = array();
+
+ // Some basic identification stuff
+
+ $this->stats['version'] = LACONICA_VERSION;
+ $this->stats['phpversion'] = phpversion();
+ $this->stats['name'] = common_config('site', 'name');
+ $this->stats['root'] = common_root_url();
+
+ // non-identifying stats on various tables. Primary
+ // interest is size and rate of activity of service.
+
+ $tables = array('user',
+ 'notice',
+ 'subscription',
+ 'remote_profile',
+ 'user_group');
+
+ foreach ($tables as $table) {
+ $this->tableStats($table);
+ }
+
+ // stats on some important config options
+
+ $this->stats['theme'] = common_config('site', 'theme');
+ $this->stats['dbtype'] = common_config('db', 'type');
+ $this->stats['xmpp'] = common_config('xmpp', 'enabled');
+ $this->stats['inboxes'] = common_config('inboxes', 'enabled');
+ $this->stats['queue'] = common_config('queue', 'enabled');
+ $this->stats['license'] = common_config('license', 'url');
+ $this->stats['fancy'] = common_config('site', 'fancy');
+ $this->stats['private'] = common_config('site', 'private');
+ $this->stats['closed'] = common_config('site', 'closed');
+ $this->stats['memcached'] = common_config('memcached', 'enabled');
+ $this->stats['language'] = common_config('site', 'language');
+ $this->stats['timezone'] = common_config('site', 'timezone');
+
+ }
+
+ /**
+ * Reports statistics to headquarters
+ *
+ * Posts statistics to a reporting server.
+ *
+ * @return void
+ */
+
+ function report()
+ {
+ // XXX: Use OICU2 and OAuth to make authorized requests
+
+ $postdata = http_build_query($this->stats);
+
+ $opts =
+ array('http' =>
+ array(
+ 'method' => 'POST',
+ 'header' => 'Content-type: '.
+ 'application/x-www-form-urlencoded',
+ 'content' => $postdata,
+ 'user_agent' => 'Laconica/'.LACONICA_VERSION
+ )
+ );
+
+ $context = stream_context_create($opts);
+
+ $reporturl = common_config('snapshot', 'reporturl');
+
+ $result = @file_get_contents($reporturl, false, $context);
+
+ return $result;
+ }
+
+ /**
+ * Updates statistics for a single table
+ *
+ * Determines the size of a table and its oldest and newest rows.
+ * Goal here is to see how active a site is. Note that it
+ * fills up the instance stats variable.
+ *
+ * @param string $table name of table to check
+ *
+ * @return void
+ */
+
+ function tableStats($table)
+ {
+ $inst = DB_DataObject::factory($table);
+
+ $inst->selectAdd();
+ $inst->selectAdd('count(*) as cnt, '.
+ 'min(created) as first, '.
+ 'max(created) as last');
+
+ if ($inst->find(true)) {
+ $this->stats[$table.'count'] = $inst->cnt;
+ $this->stats[$table.'first'] = $inst->first;
+ $this->stats[$table.'last'] = $inst->last;
+ }
+
+ $inst->free();
+ unset($inst);
+ }
+}
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/twitter.php b/lib/twitter.php
index ccc6c93ca..c1d0dc254 100644
--- a/lib/twitter.php
+++ b/lib/twitter.php
@@ -346,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) ||
diff --git a/lib/util.php b/lib/util.php
index f862d7fcc..d56f44f7b 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -395,7 +395,7 @@ function common_render_text($text)
return $r;
}
-function common_replace_urls_callback($text, $callback) {
+function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
'(?:'.
@@ -466,7 +466,11 @@ function common_replace_urls_callback($text, $callback) {
$url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
// Call user specified func
- $modified_url = call_user_func($callback, $url);
+ if (empty($notice_id)) {
+ $modified_url = call_user_func($callback, $url);
+ } else {
+ $modified_url = call_user_func($callback, array($url, $notice_id));
+ }
// Replace it!
$start = mb_strpos($text, $url, $offset);
@@ -481,107 +485,45 @@ function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying
// functions
$url = htmlspecialchars_decode($url);
- $display = $url;
- $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
-
- $attrs = array('href' => $url, 'rel' => 'external');
-
- if ($longurl = common_longurl($url)) {
- $attrs['title'] = $longurl;
+ $display = File_redirection::_canonUrl($url);
+ $longurl_data = File_redirection::where($url);
+ if (is_array($longurl_data)) {
+ $longurl = $longurl_data['url'];
+ } elseif (is_string($longurl_data)) {
+ $longurl = $longurl_data;
+ } else {
+ die('impossible to linkify');
}
- return XMLStringer::estring('a', $attrs, $display);
-}
+ $attrs = array('href' => $longurl, 'rel' => 'external');
-function common_longurl($short_url)
-{
- $long_url = common_shorten_link($short_url, true);
- if ($long_url === $short_url) return false;
- return $long_url;
-}
+// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
+// where ID is the id of the attachment for the given URL.
+ $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
+ $file = new File;
+ $file->query($query);
+ $file->fetch();
-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']);
+ if (!empty($file->file_id)) {
+ $query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'";
+ $file2 = new File;
+ $file2->query($query);
+ $file2->fetch();
+
+ if (empty($file2->file_id)) {
+ $attrs['class'] = 'attachment';
+ } else {
+ $attrs['class'] = 'attachment thumbnail';
+ }
+ $attrs['id'] = "attachment-{$file->file_id}";
+ }
+ return XMLStringer::estring('a', $attrs, $display);
}
function common_shorten_links($text)
{
if (mb_strlen($text) <= 140) return $text;
- static $cache = array();
- if (isset($cache[$text])) return $cache[$text];
- // \s = not a horizontal whitespace character (since PHP 5.2.4)
- return $cache[$text] = common_replace_urls_callback($text, 'common_shorten_link');;
-}
-
-function common_shorten_link($url, $reverse = false)
-{
-
- static $url_cache = array();
- if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
-
- $user = common_current_user();
- if (!isset($user)) {
- // common current user does not find a user when called from the XMPP daemon
- // therefore we'll set one here fix, so that XMPP given URLs may be shortened
- $user->urlshorteningservice = 'ur1.ca';
- }
- $curlh = curl_init();
- curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
- curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
- curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
- switch($user->urlshorteningservice) {
- case 'ur1.ca':
- $short_url_service = new LilUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case '2tu.us':
- $short_url_service = new TightUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'ptiturl.com':
- $short_url_service = new PtitUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'bit.ly':
- curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($url));
- $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
- break;
-
- case 'is.gd':
- curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'snipr.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'metamark.net':
- curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'tinyurl.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- default:
- $short_url = false;
- }
-
- curl_close($curlh);
-
- if ($short_url) {
- $url_cache[(string)$short_url] = $url;
- return (string)$short_url;
- }
- return $url;
+ return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
}
function common_xml_safe_str($str)
@@ -879,34 +821,76 @@ function common_broadcast_notice($notice, $remote=false)
function common_enqueue_notice($notice)
{
- $transports = array('omb', 'sms', 'twitter', 'facebook', 'ping');
-
- if (common_config('xmpp', 'enabled')) {
- $transports = array_merge($transports, array('jabber', 'public'));
- }
-
- if (common_config('memcached', 'enabled')) {
- // Note: limited to 8 chars
- $transports[] = 'memcache';
- }
-
- if (common_config('inboxes', 'enabled') === true ||
- common_config('inboxes', 'enabled') === 'transitional') {
- $transports[] = 'inbox';
- }
-
- foreach ($transports 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;
}
@@ -1115,7 +1099,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;