summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Shorturl_api.php27
-rw-r--r--lib/accountsettingsaction.php134
-rw-r--r--lib/action.php1005
-rw-r--r--lib/arraywrapper.php79
-rw-r--r--lib/blockform.php155
-rw-r--r--lib/clienterroraction.php94
-rw-r--r--lib/common.php173
-rw-r--r--lib/connectsettingsaction.php129
-rw-r--r--lib/daemon.php239
-rw-r--r--lib/deleteaction.php103
-rw-r--r--lib/disfavorform.php154
-rw-r--r--lib/error.php136
-rw-r--r--lib/facebookaction.php825
-rw-r--r--lib/facebookutil.php84
-rw-r--r--lib/favorform.php153
-rw-r--r--lib/featureduserssection.php89
-rw-r--r--lib/feedlist.php158
-rw-r--r--lib/form.php166
-rw-r--r--lib/gallery.php320
-rw-r--r--lib/galleryaction.php171
-rw-r--r--lib/groupeditform.php173
-rw-r--r--lib/grouplist.php186
-rw-r--r--lib/groupminilist.php92
-rw-r--r--lib/groupnav.php113
-rw-r--r--lib/groupsbymemberssection.php78
-rw-r--r--lib/groupsbypostssection.php78
-rw-r--r--lib/groupsection.php107
-rw-r--r--lib/grouptagcloudsection.php87
-rw-r--r--lib/htmloutputter.php365
-rw-r--r--lib/imagefile.php113
-rw-r--r--lib/jabber.php678
-rw-r--r--lib/joinform.php116
-rw-r--r--lib/language.php118
-rw-r--r--lib/leaveform.php116
-rw-r--r--lib/logingroupnav.php96
-rw-r--r--lib/mail.php695
-rw-r--r--lib/mailbox.php427
-rw-r--r--lib/messageform.php171
-rw-r--r--lib/microid.php97
-rw-r--r--lib/noticeform.php181
-rw-r--r--lib/noticelist.php592
-rw-r--r--lib/noticesection.php107
-rw-r--r--lib/nudgeform.php130
-rw-r--r--lib/oauthstore.php228
-rw-r--r--lib/omb.php420
-rw-r--r--lib/openid.php377
-rw-r--r--lib/personal.php223
-rw-r--r--lib/personalgroupnav.php135
-rw-r--r--lib/personaltagcloudsection.php86
-rw-r--r--lib/popularnoticesection.php83
-rw-r--r--lib/profilelist.php331
-rw-r--r--lib/profileminilist.php90
-rw-r--r--lib/profilesection.php106
-rw-r--r--lib/publicgroupnav.php95
-rw-r--r--lib/queuehandler.php210
-rw-r--r--lib/rssaction.php407
-rw-r--r--lib/search_engines.php49
-rw-r--r--lib/searchaction.php184
-rw-r--r--lib/searchgroupnav.php87
-rw-r--r--lib/section.php108
-rw-r--r--lib/servererroraction.php92
-rw-r--r--lib/settingsaction.php195
-rw-r--r--lib/stream.php35
-rw-r--r--lib/subgroupnav.php111
-rw-r--r--lib/subs.php101
-rw-r--r--lib/subscribeform.php141
-rw-r--r--lib/tagcloudsection.php124
-rw-r--r--lib/theme.php69
-rw-r--r--lib/topposterssection.php81
-rw-r--r--lib/twitter.php234
-rw-r--r--lib/twitterapi.php1192
-rw-r--r--lib/unblockform.php152
-rw-r--r--lib/unsubscribeform.php139
-rw-r--r--lib/util.php3222
-rw-r--r--lib/widget.php82
-rw-r--r--lib/xmloutputter.php242
-rw-r--r--lib/xmppqueuehandler.php121
77 files changed, 13098 insertions, 5764 deletions
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
index 7beae0ec6..fe106cb83 100644
--- a/lib/Shorturl_api.php
+++ b/lib/Shorturl_api.php
@@ -19,14 +19,17 @@
if (!defined('LACONICA')) { exit(1); }
-class ShortUrlApi {
+class ShortUrlApi
+{
protected $service_url;
- function __construct($service_url) {
+ function __construct($service_url)
+ {
$this->service_url = $service_url;
}
- function shorten($url) {
+ function shorten($url)
+ {
if ($this->is_long($url)) return $this->shorten_imp($url);
return $url;
}
@@ -67,8 +70,10 @@ class ShortUrlApi {
}
}
-class LilUrl extends ShortUrlApi {
- function __construct() {
+class LilUrl extends ShortUrlApi
+{
+ function __construct()
+ {
parent::__construct('http://ur1.ca/');
}
@@ -85,8 +90,10 @@ class LilUrl extends ShortUrlApi {
}
-class PtitUrl extends ShortUrlApi {
- function __construct() {
+class PtitUrl extends ShortUrlApi
+{
+ function __construct()
+ {
parent::__construct('http://ptiturl.com/?creer=oui&action=Reduire&url=');
}
@@ -102,8 +109,10 @@ class PtitUrl extends ShortUrlApi {
}
}
-class TightUrl extends ShortUrlApi {
- function __construct() {
+class TightUrl extends ShortUrlApi
+{
+ function __construct()
+ {
parent::__construct('http://2tu.us/?save=y&url=');
}
diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php
new file mode 100644
index 000000000..46090b8c1
--- /dev/null
+++ b/lib/accountsettingsaction.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for account settings 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 Settings
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/settingsaction.php';
+
+/**
+ * Base class for account settings actions
+ *
+ * @category Settings
+ * @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 Widget
+ */
+
+class AccountSettingsAction extends SettingsAction
+{
+ /**
+ * Show the local navigation menu
+ *
+ * This is the same for all settings, so we show it here.
+ *
+ * @return void
+ */
+
+ function showLocalNav()
+ {
+ $menu = new AccountSettingsNav($this);
+ $menu->show();
+ }
+}
+
+/**
+ * A widget for showing the settings group local nav menu
+ *
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class AccountSettingsNav extends Widget
+{
+ var $action = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ # action => array('prompt', 'title')
+ $menu =
+ array('profilesettings' =>
+ array(_('Profile'),
+ _('Change your profile settings')),
+ 'avatarsettings' =>
+ array(_('Avatar'),
+ _('Upload an avatar')),
+ 'passwordsettings' =>
+ array(_('Password'),
+ _('Change your password')),
+ 'emailsettings' =>
+ array(_('Email'),
+ _('Change email handling')),
+ 'openidsettings' =>
+ array(_('OpenID'),
+ _('Add or remove OpenIDs')),
+ 'othersettings' =>
+ array(_('Other'),
+ _('Other options')));
+
+ $action_name = $this->action->trimmed('action');
+ $this->action->elementStart('ul', array('class' => 'nav'));
+
+ foreach ($menu as $menuaction => $menudesc) {
+ $this->action->menuItem(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action_name === $menuaction);
+ }
+
+ $this->action->elementEnd('ul');
+ }
+}
diff --git a/lib/action.php b/lib/action.php
index 7a2461bb5..8f02b36bf 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Base class for all actions (~views)
+ *
+ * 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.
@@ -15,128 +18,880 @@
*
* 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 Action
+ * @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); }
-
-class Action { // lawsuit
-
- var $args;
-
- function Action() {
- }
-
- # For initializing members of the class
-
- function prepare($argarray) {
- $this->args =& common_copy_args($argarray);
- return true;
- }
-
- # For comparison with If-Last-Modified
- # If not applicable, return NULL
-
- function last_modified() {
- return NULL;
- }
-
- function etag() {
- return NULL;
- }
-
- function is_readonly() {
- return false;
- }
-
- function arg($key, $def=NULL) {
- if (array_key_exists($key, $this->args)) {
- return $this->args[$key];
- } else {
- return $def;
- }
- }
-
- function trimmed($key, $def=NULL) {
- $arg = $this->arg($key, $def);
- return (is_string($arg)) ? trim($arg) : $arg;
- }
-
- # Note: argarray ignored, since it's now passed in in prepare()
-
- function handle($argarray=NULL) {
-
- $lm = $this->last_modified();
- $etag = $this->etag();
-
- if ($etag) {
- header('ETag: ' . $etag);
- }
-
- if ($lm) {
- header('Last-Modified: ' . date(DATE_RFC1123, $lm));
- $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
- if ($if_modified_since) {
- $ims = strtotime($if_modified_since);
- if ($lm <= $ims) {
- if (!$etag || $this->_has_etag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
- header('HTTP/1.1 304 Not Modified');
- # Better way to do this?
- exit(0);
- }
- }
- }
- }
- }
-
- function _has_etag($etag, $if_none_match) {
- return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
- }
-
- function boolean($key, $def=false) {
- $arg = strtolower($this->trimmed($key));
-
- if (is_null($arg)) {
- return $def;
- } else if (in_array($arg, array('true', 'yes', '1'))) {
- return true;
- } else if (in_array($arg, array('false', 'no', '0'))) {
- return false;
- } else {
- return $def;
- }
- }
-
- function server_error($msg, $code=500) {
- $action = $this->trimmed('action');
- common_debug("Server error '$code' on '$action': $msg", __FILE__);
- common_server_error($msg, $code);
- }
-
- function client_error($msg, $code=400) {
- $action = $this->trimmed('action');
- common_debug("User error '$code' on '$action': $msg", __FILE__);
- common_user_error($msg, $code);
- }
-
- function self_url() {
- $action = $this->trimmed('action');
- $args = $this->args;
- unset($args['action']);
- foreach (array_keys($_COOKIE) as $cookie) {
- unset($args[$cookie]);
- }
- return common_local_url($action, $args);
- }
-
- function nav_menu($menu) {
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/noticeform.php';
+require_once INSTALLDIR.'/lib/htmloutputter.php';
+
+/**
+ * Base class for all actions
+ *
+ * This is the base class for all actions in the package. An action is
+ * more or less a "view" in an MVC framework.
+ *
+ * Actions are responsible for extracting and validating parameters; using
+ * model classes to read and write to the database; and doing ouput.
+ *
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+class Action extends HTMLOutputter // lawsuit
+{
+ var $args;
+
+ /**
+ * Constructor
+ *
+ * Just wraps the HTMLOutputter constructor.
+ *
+ * @param string $output URI to output to, default = stdout
+ * @param boolean $indent Whether to indent output, default true
+ *
+ * @see XMLOutputter::__construct
+ * @see HTMLOutputter::__construct
+ */
+ function __construct($output='php://output', $indent=true)
+ {
+ parent::__construct($output, $indent);
+ }
+
+
+ /**
+ * For initializing members of the class.
+ *
+ * @param array $argarray misc. arguments
+ *
+ * @return boolean true
+ */
+ function prepare($argarray)
+ {
+ $this->args =& common_copy_args($argarray);
+ return true;
+ }
+
+ /**
+ * Show page, a template method.
+ *
+ * @return nothing
+ */
+ function showPage()
+ {
+ $this->startHTML();
+ $this->showHead();
+ $this->showBody();
+ $this->endHTML();
+ }
+
+ /**
+ * Show head, a template method.
+ *
+ * @return nothing
+ */
+ function showHead()
+ {
+ // XXX: attributes (profile?)
+ $this->elementStart('head');
+ $this->showTitle();
+ $this->showStylesheets();
+ $this->showScripts();
+ $this->showOpenSearch();
+ $this->showFeeds();
+ $this->showDescription();
+ $this->extraHead();
+ $this->elementEnd('head');
+ }
+
+ /**
+ * Show title, a template method.
+ *
+ * @return nothing
+ */
+ function showTitle()
+ {
+ $this->element('title', null,
+ sprintf(_("%s - %s"),
+ $this->title(),
+ common_config('site', 'name')));
+ }
+
+ /**
+ * Returns the page title
+ *
+ * SHOULD overload
+ *
+ * @return string page title
+ */
+
+ function title()
+ {
+ return _("Untitled page");
+ }
+
+ /**
+ * Show stylesheets
+ *
+ * @return nothing
+ */
+ function showStylesheets()
+ {
+ $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/display.css', null) . '?version=' . LACONICA_VERSION,
+ 'media' => 'screen, projection, tv'));
+ $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
+ 'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
+ foreach (array(6,7) as $ver) {
+ if (file_exists(theme_file('ie'.$ver.'.css'))) {
+ // Yes, IE people should be put in jail.
+ $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
+ 'href="'.theme_path('css/ie'.$ver.'.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
+ }
+ }
+ }
+
+ /**
+ * Show javascript headers
+ *
+ * @return nothing
+ */
+ function showScripts()
+ {
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/jquery.min.js')),
+ ' ');
+ $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/xbImportNode.js')),
+ ' ');
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
+ ' ');
+ }
+
+ /**
+ * Show OpenSearch headers
+ *
+ * @return nothing
+ */
+ function showOpenSearch()
+ {
+ $this->element('link', array('rel' => 'search',
+ 'type' => 'application/opensearchdescription+xml',
+ 'href' => common_local_url('opensearch', array('type' => 'people')),
+ 'title' => common_config('site', 'name').' People Search'));
+ $this->element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
+ 'href' => common_local_url('opensearch', array('type' => 'notice')),
+ 'title' => common_config('site', 'name').' Notice Search'));
+ }
+
+ /**
+ * Show feed headers
+ *
+ * MAY overload
+ *
+ * @return nothing
+ */
+ function showFeeds()
+ {
+ // does nothing by default
+ }
+
+ /**
+ * Show description.
+ *
+ * SHOULD overload
+ *
+ * @return nothing
+ */
+ function showDescription()
+ {
+ // does nothing by default
+ }
+
+ /**
+ * Show extra stuff in <head>.
+ *
+ * MAY overload
+ *
+ * @return nothing
+ */
+ function extraHead()
+ {
+ // does nothing by default
+ }
+
+
+ /**
+ * Show body.
+ *
+ * Calls template methods
+ *
+ * @return nothing
+ */
+ function showBody()
+ {
+ $this->elementStart('body', array('id' => $this->trimmed('action')));
+ $this->elementStart('div', 'wrap');
+ $this->showHeader();
+ $this->showCore();
+ $this->showFooter();
+ $this->elementEnd('div');
+ $this->elementEnd('body');
+ }
+
+ /**
+ * Show header of the page.
+ *
+ * Calls template methods
+ *
+ * @return nothing
+ */
+ function showHeader()
+ {
+ $this->elementStart('div', array('id' => 'header'));
+ $this->showLogo();
+ $this->showPrimaryNav();
+ $this->showSiteNotice();
+ if (common_logged_in()) {
+ $this->showNoticeForm();
+ } else {
+ $this->showAnonymousMessage();
+ }
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show configured logo.
+ *
+ * @return nothing
+ */
+ function showLogo()
+ {
+ $this->elementStart('address', array('id' => 'site_contact',
+ 'class' => 'vcard'));
+ $this->elementStart('a', array('class' => 'url home bookmark',
+ 'href' => common_local_url('public')));
+ if (common_config('site', 'logo') || file_exists(theme_file('logo.png'))) {
+ $this->element('img', array('class' => 'logo photo',
+ 'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : theme_path('logo.png'),
+ 'alt' => common_config('site', 'name')));
+ }
+ $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
+ $this->elementEnd('a');
+ $this->elementEnd('address');
+ }
+
+ /**
+ * Show primary navigation.
+ *
+ * @return nothing
+ */
+ function showPrimaryNav()
+ {
+ $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
+ $this->element('dt', null, _('Primary site navigation'));
+ $this->elementStart('dd');
+ $user = common_current_user();
+ $this->elementStart('ul', array('class' => 'nav'));
+ 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');
+ $this->menuItem(common_local_url('imsettings'),
+ _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
+ $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');
+ 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('doc', array('title' => 'help')),
+ _('Help'), _('Help me!'), false, 'nav_help');
+ $this->elementEnd('ul');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+
+ /**
+ * Show site notice.
+ *
+ * @return nothing
+ */
+ function showSiteNotice()
+ {
+ // Revist. Should probably do an hAtom pattern here
+ $text = common_config('site', 'notice');
+ if ($text) {
+ $this->elementStart('dl', array('id' => 'site_notice',
+ 'class' => 'system_notice'));
+ $this->element('dt', null, _('Site notice'));
+ $this->element('dd', null, $text);
+ $this->elementEnd('dl');
+ }
+ }
+
+ /**
+ * Show notice form.
+ *
+ * MAY overload if no notice form needed... or direct message box????
+ *
+ * @return nothing
+ */
+ function showNoticeForm()
+ {
+ $notice_form = new NoticeForm($this);
+ $notice_form->show();
+ }
+
+ /**
+ * Show anonymous message.
+ *
+ * SHOULD overload
+ *
+ * @return nothing
+ */
+ function showAnonymousMessage()
+ {
+ // needs to be defined by the class
+ }
+
+ /**
+ * Show core.
+ *
+ * Shows local navigation, content block and aside.
+ *
+ * @return nothing
+ */
+ function showCore()
+ {
+ $this->elementStart('div', array('id' => 'core'));
+ $this->showLocalNavBlock();
+ $this->showContentBlock();
+ $this->showAside();
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show local navigation block.
+ *
+ * @return nothing
+ */
+ function showLocalNavBlock()
+ {
+ $this->elementStart('dl', array('id' => 'site_nav_local_views'));
+ $this->element('dt', null, _('Local views'));
+ $this->elementStart('dd');
+ $this->showLocalNav();
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+
+ /**
+ * Show local navigation.
+ *
+ * SHOULD overload
+ *
+ * @return nothing
+ */
+ function showLocalNav()
+ {
+ // does nothing by default
+ }
+
+ /**
+ * Show content block.
+ *
+ * @return nothing
+ */
+ function showContentBlock()
+ {
+ $this->elementStart('div', array('id' => 'content'));
+ $this->showPageTitle();
+ $this->showPageNoticeBlock();
+ $this->elementStart('div', array('id' => 'content_inner'));
+ // show the actual content (forms, lists, whatever)
+ $this->showContent();
+ $this->elementEnd('div');
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show page title.
+ *
+ * @return nothing
+ */
+ function showPageTitle()
+ {
+ $this->element('h1', null, $this->title());
+ }
+
+ /**
+ * Show page notice block.
+ *
+ * @return nothing
+ */
+ function showPageNoticeBlock()
+ {
+ $this->elementStart('dl', array('id' => 'page_notice',
+ 'class' => 'system_notice'));
+ $this->element('dt', null, _('Page notice'));
+ $this->elementStart('dd');
+ $this->showPageNotice();
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+
+ /**
+ * Show page notice.
+ *
+ * SHOULD overload (unless there's not a notice)
+ *
+ * @return nothing
+ */
+ function showPageNotice()
+ {
+ }
+
+ /**
+ * Show content.
+ *
+ * MUST overload (unless there's not a notice)
+ *
+ * @return nothing
+ */
+ function showContent()
+ {
+ }
+
+ /**
+ * Show Aside.
+ *
+ * @return nothing
+ */
+ function showAside()
+ {
+ $this->elementStart('div', array('id' => 'aside_primary',
+ 'class' => 'aside'));
+ $this->showExportData();
+ $this->showSections();
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show export data feeds.
+ *
+ * MAY overload if there are feeds
+ *
+ * @return nothing
+ */
+ function showExportData()
+ {
+ // is there structure to this?
+ // list of (visible!) feed links
+ // can we reuse list of feeds from showFeeds() ?
+ }
+
+ /**
+ * Show sections.
+ *
+ * SHOULD overload
+ *
+ * @return nothing
+ */
+ function showSections()
+ {
+ // for each section, show it
+ }
+
+ /**
+ * Show footer.
+ *
+ * @return nothing
+ */
+ function showFooter()
+ {
+ $this->elementStart('div', array('id' => 'footer'));
+ $this->showSecondaryNav();
+ $this->showLicenses();
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show secondary navigation.
+ *
+ * @return nothing
+ */
+ function showSecondaryNav()
+ {
+ $this->elementStart('dl', array('id' => 'site_nav_global_secondary'));
+ $this->element('dt', null, _('Secondary site navigation'));
+ $this->elementStart('dd', null);
+ $this->elementStart('ul', array('class' => 'nav'));
+ $this->menuItem(common_local_url('doc', array('title' => 'help')),
+ _('Help'));
+ $this->menuItem(common_local_url('doc', array('title' => 'about')),
+ _('About'));
+ $this->menuItem(common_local_url('doc', array('title' => 'faq')),
+ _('FAQ'));
+ $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
+ _('Privacy'));
+ $this->menuItem(common_local_url('doc', array('title' => 'source')),
+ _('Source'));
+ $this->menuItem(common_local_url('doc', array('title' => 'contact')),
+ _('Contact'));
+ $this->elementEnd('ul');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+
+ /**
+ * Show licenses.
+ *
+ * @return nothing
+ */
+ function showLicenses()
+ {
+ $this->elementStart('dl', array('id' => 'licenses'));
+ $this->showLaconicaLicense();
+ $this->showContentLicense();
+ $this->elementEnd('dl');
+ }
+
+ /**
+ * Show Laconica license.
+ *
+ * @return nothing
+ */
+ function showLaconicaLicense()
+ {
+ $this->element('dt', array('id' => 'site_laconica_license'), _('Laconica software license'));
+ $this->elementStart('dd', null);
+ if (common_config('site', 'broughtby')) {
+ $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
+ } else {
+ $instr = _('**%%site.name%%** is a microblogging service. ');
+ }
+ $instr .= sprintf(_('It runs the [Laconica](http://laconi.ca/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), LACONICA_VERSION);
+ $output = common_markup_to_html($instr);
+ $this->raw($output);
+ $this->elementEnd('dd');
+ // do it
+ }
+
+ /**
+ * Show content license.
+ *
+ * @return nothing
+ */
+ function showContentLicense()
+ {
+ $this->element('dt', array('id' => 'site_content_license'), _('Laconica software license'));
+ $this->elementStart('dd', array('id' => 'site_content_license_cc'));
+ $this->elementStart('p');
+ $this->element('img', array('id' => 'license_cc',
+ 'src' => common_config('license', 'image'),
+ 'alt' => common_config('license', 'title')));
+ //TODO: This is dirty: i18n
+ $this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
+ $this->element('a', array('class' => 'license',
+ 'rel' => 'external license',
+ 'href' => common_config('license', 'url')),
+ common_config('license', 'title'));
+ $this->text(_('license.'));
+ $this->elementEnd('p');
+ $this->elementEnd('dd');
+ }
+
+ /**
+ * Return last modified, if applicable.
+ *
+ * MAY override
+ *
+ * @return string last modified http header
+ */
+ function lastModified()
+ {
+ // For comparison with If-Last-Modified
+ // If not applicable, return null
+ return null;
+ }
+
+ /**
+ * Return etag, if applicable.
+ *
+ * MAY override
+ *
+ * @return string etag http header
+ */
+ function etag()
+ {
+ return null;
+ }
+
+ /**
+ * Return true if read only.
+ *
+ * MAY override
+ *
+ * @return boolean is read only action?
+ */
+ function isReadOnly()
+ {
+ return false;
+ }
+
+ /**
+ * Returns query argument or default value if not found
+ *
+ * @param string $key requested argument
+ * @param string $def default value to return if $key is not provided
+ *
+ * @return boolean is read only action?
+ */
+ function arg($key, $def=null)
+ {
+ if (array_key_exists($key, $this->args)) {
+ return $this->args[$key];
+ } else {
+ return $def;
+ }
+ }
+
+ /**
+ * Returns trimmed query argument or default value if not found
+ *
+ * @param string $key requested argument
+ * @param string $def default value to return if $key is not provided
+ *
+ * @return boolean is read only action?
+ */
+ function trimmed($key, $def=null)
+ {
+ $arg = $this->arg($key, $def);
+ return is_string($arg) ? trim($arg) : $arg;
+ }
+
+ /**
+ * Handler method
+ *
+ * @param array $argarray is ignored since it's now passed in in prepare()
+ *
+ * @return boolean is read only action?
+ */
+ function handle($argarray=null)
+ {
+ $lm = $this->lastModified();
+ $etag = $this->etag();
+ if ($etag) {
+ header('ETag: ' . $etag);
+ }
+ if ($lm) {
+ header('Last-Modified: ' . date(DATE_RFC1123, $lm));
+ $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+ if ($if_modified_since) {
+ $ims = strtotime($if_modified_since);
+ if ($lm <= $ims) {
+ if (!$etag ||
+ $this->_hasEtag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
+ header('HTTP/1.1 304 Not Modified');
+ // Better way to do this?
+ exit(0);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Has etag? (private)
+ *
+ * @param string $etag etag http header
+ * @param string $if_none_match ifNoneMatch http header
+ *
+ * @return boolean
+ */
+ function _hasEtag($etag, $if_none_match)
+ {
+ return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
+ }
+
+ /**
+ * Boolean understands english (yes, no, true, false)
+ *
+ * @param string $key query key we're interested in
+ * @param string $def default value
+ *
+ * @return boolean interprets yes/no strings as boolean
+ */
+ function boolean($key, $def=false)
+ {
+ $arg = strtolower($this->trimmed($key));
+
+ if (is_null($arg)) {
+ return $def;
+ } else if (in_array($arg, array('true', 'yes', '1'))) {
+ return true;
+ } else if (in_array($arg, array('false', 'no', '0'))) {
+ return false;
+ } else {
+ return $def;
+ }
+ }
+
+ /**
+ * Server error
+ *
+ * @param string $msg error message to display
+ * @param integer $code http error code, 500 by default
+ *
+ * @return nothing
+ */
+ function serverError($msg, $code=500)
+ {
$action = $this->trimmed('action');
- common_element_start('ul', array('id' => 'nav_views'));
- foreach ($menu as $menuaction => $menudesc) {
- common_menu_item(common_local_url($menuaction, isset($menudesc[2]) ? $menudesc[2] : NULL),
- $menudesc[0],
- $menudesc[1],
- $action == $menuaction);
- }
- common_element_end('ul');
- }
+ common_debug("Server error '$code' on '$action': $msg", __FILE__);
+ common_server_error($msg, $code);
+ }
+
+ /**
+ * Client error
+ *
+ * @param string $msg error message to display
+ * @param integer $code http error code, 400 by default
+ *
+ * @return nothing
+ */
+ function clientError($msg, $code=400)
+ {
+ $action = $this->trimmed('action');
+ common_debug("User error '$code' on '$action': $msg", __FILE__);
+ common_user_error($msg, $code);
+ }
+
+ /**
+ * Returns the current URL
+ *
+ * @return string current URL
+ */
+ function selfUrl()
+ {
+ $action = $this->trimmed('action');
+ $args = $this->args;
+ unset($args['action']);
+ foreach (array_keys($_COOKIE) as $cookie) {
+ unset($args[$cookie]);
+ }
+ return common_local_url($action, $args);
+ }
+
+ /**
+ * Generate a menu item
+ *
+ * @param string $url menu URL
+ * @param string $text menu name
+ * @param string $title title attribute, null by default
+ * @param boolean $is_selected current menu item, false by default
+ * @param string $id element id, null by default
+ *
+ * @return nothing
+ */
+ function menuItem($url, $text, $title=null, $is_selected=false, $id=null)
+ {
+ // Added @id to li for some control.
+ // XXX: We might want to move this to htmloutputter.php
+ $lattrs = array();
+ if ($is_selected) {
+ $lattrs['class'] = 'current';
+ }
+
+ (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
+
+ $this->elementStart('li', $lattrs);
+ $attrs['href'] = $url;
+ if ($title) {
+ $attrs['title'] = $title;
+ }
+ $this->element('a', $attrs, $text);
+ $this->elementEnd('li');
+ }
+
+ /**
+ * Generate pagination links
+ *
+ * @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 pagination($have_before, $have_after, $page, $action, $args=null)
+ {
+ // Does a little before-after block for next/prev page
+ if ($have_before || $have_after) {
+ $this->elementStart('div', array('class' => 'pagination'));
+ $this->elementStart('dl', null);
+ $this->element('dt', null, _('Pagination'));
+ $this->elementStart('dd', null);
+ $this->elementStart('ul', array('class' => 'nav'));
+ }
+ 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'),
+ _('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'),
+ _('Before'));
+ $this->elementEnd('li');
+ }
+ if ($have_before || $have_after) {
+ $this->elementEnd('ul');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ $this->elementEnd('div');
+ }
+ }
}
diff --git a/lib/arraywrapper.php b/lib/arraywrapper.php
new file mode 100644
index 000000000..ef0eeffa5
--- /dev/null
+++ b/lib/arraywrapper.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+class ArrayWrapper
+{
+ var $_items = null;
+ var $_count = 0;
+ var $_i = -1;
+
+ function __construct($items)
+ {
+ $this->_items = $items;
+ $this->_count = count($this->_items);
+ }
+
+ function fetch()
+ {
+ if (!$this->_items) {
+ return false;
+ }
+ $this->_i++;
+ if ($this->_i < $this->_count) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function __set($name, $value)
+ {
+ $item =& $this->_items[$this->_i];
+ $item->$name = $value;
+ return $item->$name;
+ }
+
+ function __get($name)
+ {
+ $item =& $this->_items[$this->_i];
+ return $item->$name;
+ }
+
+ function __isset($name)
+ {
+ $item =& $this->_items[$this->_i];
+ return isset($item->$name);
+ }
+
+ function __unset($name)
+ {
+ $item =& $this->_items[$this->_i];
+ unset($item->$name);
+ }
+
+ function __call($name, $args)
+ {
+ $item =& $this->_items[$this->_i];
+ return call_user_func_array(array($item, $name), $args);
+ }
+} \ No newline at end of file
diff --git a/lib/blockform.php b/lib/blockform.php
new file mode 100644
index 000000000..af766b823
--- /dev/null
+++ b/lib/blockform.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for blocking a user
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for blocking a user
+ *
+ * @category Form
+ * @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/
+ *
+ * @see UnblockForm
+ */
+
+class BlockForm extends Form
+{
+ /**
+ * Profile of user to block
+ */
+
+ var $profile = null;
+
+ /**
+ * Return-to args
+ */
+
+ var $args = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Profile $profile profile of user to block
+ * @param array $args return-to args
+ */
+
+ function __construct($out=null, $profile=null, $args=null)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ $this->args = $args;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'block-' . $this->profile->id;
+ }
+
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_user_block';
+ }
+
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('block');
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Block this user'));
+ }
+
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('blockto-' . $this->profile->id,
+ $this->profile->id,
+ 'blockto');
+ if ($this->args) {
+ foreach ($this->args as $k => $v) {
+ $this->out->hidden('returnto-' . $k, $v);
+ }
+ }
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Block'), 'submit', null, _('Block this user'));
+ }
+}
diff --git a/lib/clienterroraction.php b/lib/clienterroraction.php
new file mode 100644
index 000000000..ef6fd51df
--- /dev/null
+++ b/lib/clienterroraction.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Client error action.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Zach Copley <zach@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/error.php';
+
+/**
+ * Class for displaying HTTP client errors
+ *
+ * @category Action
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ */
+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',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed');
+ $this->default = 400;
+ }
+
+ // XXX: Should these error actions even be invokable via URI?
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ $this->code = $this->trimmed('code');
+
+ if (!$this->code || $code < 400 || $code > 499) {
+ $this->code = $this->default;
+ }
+
+ $this->message = $this->trimmed('message');
+
+ if (!$this->message) {
+ $this->message = "Client Error $this->code";
+ }
+
+ $this->showPage();
+ }
+}
diff --git a/lib/common.php b/lib/common.php
index f7308d0b4..a2f9b9bfe 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-define('LACONICA_VERSION', '0.6.4.3');
+define('LACONICA_VERSION', '0.7.0');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
@@ -54,100 +54,100 @@ require_once(INSTALLDIR.'/lib/language.php');
$config =
array('site' =>
- array('name' => 'Just another Laconica microblog',
- 'server' => 'localhost',
- 'theme' => 'default',
- 'path' => '/',
- 'logfile' => NULL,
- 'fancy' => false,
- 'locale_path' => INSTALLDIR.'/locale',
- 'language' => 'en_US',
- 'languages' => get_all_languages(),
- 'email' =>
- array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : NULL,
- 'broughtby' => NULL,
- 'timezone' => 'UTC',
- 'broughtbyurl' => NULL,
- 'closed' => false,
- 'inviteonly' => false,
+ array('name' => 'Just another Laconica microblog',
+ 'server' => 'localhost',
+ 'theme' => 'default',
+ 'path' => '/',
+ 'logfile' => null,
+ 'fancy' => false,
+ 'locale_path' => INSTALLDIR.'/locale',
+ 'language' => 'en_US',
+ 'languages' => get_all_languages(),
+ 'email' =>
+ array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
+ 'broughtby' => null,
+ 'timezone' => 'UTC',
+ 'broughtbyurl' => null,
+ 'closed' => false,
+ 'inviteonly' => false,
'private' => false),
- 'syslog' =>
- array('appname' => 'laconica', # for syslog
- 'priority' => 'debug'), # XXX: currently ignored
- 'queue' =>
- array('enabled' => false),
- 'license' =>
- array('url' => 'http://creativecommons.org/licenses/by/3.0/',
- 'title' => 'Creative Commons Attribution 3.0',
- 'image' => 'http://i.creativecommons.org/l/by/3.0/88x31.png'),
- 'mail' =>
- array('backend' => 'mail',
- 'params' => NULL),
- 'nickname' =>
- array('blacklist' => array(),
- 'featured' => array()),
- 'profile' =>
- array('banned' => array()),
- 'avatar' =>
- array('server' => NULL),
- 'public' =>
- array('localonly' => true,
- 'blacklist' => array()),
- 'theme' =>
- array('server' => NULL),
- 'throttle' =>
+ 'syslog' =>
+ array('appname' => 'laconica', # for syslog
+ 'priority' => 'debug'), # XXX: currently ignored
+ 'queue' =>
+ array('enabled' => false),
+ 'license' =>
+ array('url' => 'http://creativecommons.org/licenses/by/3.0/',
+ 'title' => 'Creative Commons Attribution 3.0',
+ 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
+ 'mail' =>
+ array('backend' => 'mail',
+ 'params' => null),
+ 'nickname' =>
+ array('blacklist' => array(),
+ 'featured' => array()),
+ 'profile' =>
+ array('banned' => array()),
+ 'avatar' =>
+ array('server' => null),
+ 'public' =>
+ array('localonly' => true,
+ 'blacklist' => array()),
+ 'theme' =>
+ array('server' => null),
+ 'throttle' =>
array('enabled' => false, // whether to throttle edits; false by default
'count' => 20, // number of allowed messages in timespan
'timespan' => 600), // timespan for throttling
- 'xmpp' =>
- array('enabled' => false,
- 'server' => 'INVALID SERVER',
- 'port' => 5222,
- 'user' => 'update',
- 'encryption' => true,
- 'resource' => 'uniquename',
- 'password' => 'blahblahblah',
- 'host' => NULL, # only set if != server
- 'debug' => false, # print extra debug info
- 'public' => array()), # JIDs of users who want to receive the public stream
+ 'xmpp' =>
+ array('enabled' => false,
+ 'server' => 'INVALID SERVER',
+ 'port' => 5222,
+ 'user' => 'update',
+ 'encryption' => true,
+ 'resource' => 'uniquename',
+ 'password' => 'blahblahblah',
+ 'host' => null, # only set if != server
+ 'debug' => false, # print extra debug info
+ 'public' => array()), # JIDs of users who want to receive the public stream
'sphinx' =>
array('enabled' => false,
'server' => 'localhost',
'port' => 3312),
- 'tag' =>
- array('dropoff' => 864000.0),
- 'popular' =>
- array('dropoff' => 864000.0),
- 'daemon' =>
- array('piddir' => '/var/run',
- 'user' => false,
- 'group' => false),
- 'integration' =>
- array('source' => 'Laconica'), # source attribute for Twitter
- 'memcached' =>
- array('enabled' => false,
- 'server' => 'localhost',
- 'port' => 11211),
- 'inboxes' =>
- array('enabled' => true), # on by default for new sites
- );
+ 'tag' =>
+ array('dropoff' => 864000.0),
+ 'popular' =>
+ array('dropoff' => 864000.0),
+ 'daemon' =>
+ array('piddir' => '/var/run',
+ 'user' => false,
+ 'group' => false),
+ 'integration' =>
+ array('source' => 'Laconica'), # source attribute for Twitter
+ 'memcached' =>
+ array('enabled' => false,
+ 'server' => 'localhost',
+ 'port' => 11211),
+ 'inboxes' =>
+ array('enabled' => true), # on by default for new sites
+ );
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
$config['db'] =
array('database' => 'YOU HAVE TO SET THIS IN config.php',
- 'schema_location' => INSTALLDIR . '/classes',
- 'class_location' => INSTALLDIR . '/classes',
- 'require_prefix' => 'classes/',
- 'class_prefix' => '',
- 'mirror' => NULL,
+ 'schema_location' => INSTALLDIR . '/classes',
+ 'class_location' => INSTALLDIR . '/classes',
+ 'require_prefix' => 'classes/',
+ 'class_prefix' => '',
+ 'mirror' => null,
'db_driver' => 'DB', # XXX: JanRain libs only work with DB
- 'quote_identifiers' => false,
- 'type' => 'mysql' );
+ 'quote_identifiers' => false,
+ 'type' => 'mysql' );
if (function_exists('date_default_timezone_set')) {
- /* Work internally in UTC */
- date_default_timezone_set('UTC');
+ /* Work internally in UTC */
+ date_default_timezone_set('UTC');
}
require_once(INSTALLDIR.'/config.php');
@@ -163,10 +163,17 @@ require_once(INSTALLDIR.'/lib/subs.php');
require_once(INSTALLDIR.'/lib/Shorturl_api.php');
require_once(INSTALLDIR.'/lib/twitter.php');
-function __autoload($class) {
- if ($class == 'OAuthRequest') {
- require_once('OAuth.php');
- } else if (file_exists(INSTALLDIR.'/classes/' . $class . '.php')) {
+// XXX: other formats here
+
+define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
+
+function __autoload($class)
+{
+ if ($class == 'OAuthRequest') {
+ require_once('OAuth.php');
+ } else if (file_exists(INSTALLDIR.'/classes/' . $class . '.php')) {
require_once(INSTALLDIR.'/classes/' . $class . '.php');
+ } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) {
+ require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php');
}
}
diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php
new file mode 100644
index 000000000..30629680e
--- /dev/null
+++ b/lib/connectsettingsaction.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for connection settings 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 Settings
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/settingsaction.php';
+
+/**
+ * Base class for connection settings actions
+ *
+ * @category Settings
+ * @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 Widget
+ */
+
+class ConnectSettingsAction extends SettingsAction
+{
+ /**
+ * Show the local navigation menu
+ *
+ * This is the same for all settings, so we show it here.
+ *
+ * @return void
+ */
+
+ function showLocalNav()
+ {
+ $menu = new ConnectSettingsNav($this);
+ $menu->show();
+ }
+}
+
+/**
+ * A widget for showing the connect group local nav menu
+ *
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class ConnectSettingsNav extends Widget
+{
+ var $action = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ # action => array('prompt', 'title')
+ $menu =
+ array('imsettings' =>
+ array(_('IM'),
+ _('Updates by instant messenger (IM)')),
+ 'smssettings' =>
+ array(_('SMS'),
+ _('Updates by SMS')),
+ 'twittersettings' =>
+ array(_('Twitter'),
+ _('Twitter integration options')));
+
+ $action_name = $this->action->trimmed('action');
+ $this->action->elementStart('ul', array('class' => 'nav'));
+
+ foreach ($menu as $menuaction => $menudesc) {
+ if ($menuaction == 'imsettings' &&
+ !common_config('xmpp', 'enabled')) {
+ continue;
+ }
+ $this->action->menuItem(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action_name === $menuaction);
+ }
+
+ $this->action->elementEnd('ul');
+ }
+}
diff --git a/lib/daemon.php b/lib/daemon.php
index 359a4343b..9c1ae50a0 100644
--- a/lib/daemon.php
+++ b/lib/daemon.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
@@ -17,117 +17,128 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('LACONICA')) { exit(1); }
-
-class Daemon {
-
- function name() {
- return NULL;
- }
-
- function background() {
- $pid = pcntl_fork();
- if ($pid < 0) { # error
- common_log(LOG_ERR, "Could not fork.");
- return false;
- } else if ($pid > 0) { # parent
- common_log(LOG_INFO, "Successfully forked.");
- exit(0);
- } else { # child
- return true;
- }
- }
-
- function alreadyRunning() {
-
- $pidfilename = $this->pidFilename();
-
- if (!$pidfilename) {
- return false;
- }
-
- if (!file_exists($pidfilename)) {
- return false;
- }
- $contents = file_get_contents($pidfilename);
- if (posix_kill(trim($contents),0)) {
- return true;
- } else {
- return false;
- }
- }
-
- function writePidFile() {
- $pidfilename = $this->pidFilename();
-
- if (!$pidfilename) {
- return false;
- }
-
- return file_put_contents($pidfilename, posix_getpid() . "\n");
- }
-
- function clearPidFile() {
- $pidfilename = $this->pidFilename();
- if (!$pidfilename) {
- return false;
- }
- return unlink($pidfilename);
- }
-
- function pidFilename() {
- $piddir = common_config('daemon', 'piddir');
- if (!$piddir) {
- return NULL;
- }
- $name = $this->name();
- if (!$name) {
- return NULL;
- }
- return $piddir . '/' . $name . '.pid';
- }
-
- function changeUser() {
-
- $username = common_config('daemon', 'user');
-
- if ($username) {
- $user_info = posix_getpwnam($username);
- if (!$user_info) {
- common_log(LOG_WARNING, 'Ignoring unknown user for daemon: ' . $username);
- } else {
- common_log(LOG_INFO, "Setting user to " . $username);
- posix_setuid($user_info['uid']);
- }
- }
-
- $groupname = common_config('daemon', 'group');
-
- if ($groupname) {
- $group_info = posix_getgrnam($groupname);
- if (!$group_info) {
- common_log(LOG_WARNING, 'Ignoring unknown group for daemon: ' . $groupname);
- } else {
- common_log(LOG_INFO, "Setting group to " . $groupname);
- posix_setgid($group_info['gid']);
- }
- }
- }
-
- function runOnce() {
- if ($this->alreadyRunning()) {
- common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
- exit(0);
- }
- if ($this->background()) {
- $this->writePidFile();
- $this->changeUser();
- $this->run();
- $this->clearPidFile();
- }
- }
-
- function run() {
- return true;
- }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+class Daemon
+{
+ function name()
+ {
+ return null;
+ }
+
+ function background()
+ {
+ $pid = pcntl_fork();
+ if ($pid < 0) { // error
+ common_log(LOG_ERR, "Could not fork.");
+ return false;
+ } else if ($pid > 0) { // parent
+ common_log(LOG_INFO, "Successfully forked.");
+ exit(0);
+ } else { // child
+ return true;
+ }
+ }
+
+ function alreadyRunning()
+ {
+ $pidfilename = $this->pidFilename();
+
+ if (!$pidfilename) {
+ return false;
+ }
+
+ if (!file_exists($pidfilename)) {
+ return false;
+ }
+ $contents = file_get_contents($pidfilename);
+ if (posix_kill(trim($contents), 0)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function writePidFile()
+ {
+ $pidfilename = $this->pidFilename();
+
+ if (!$pidfilename) {
+ return false;
+ }
+
+ return file_put_contents($pidfilename, posix_getpid() . "\n");
+ }
+
+ function clearPidFile()
+ {
+ $pidfilename = $this->pidFilename();
+ if (!$pidfilename) {
+ return false;
+ }
+ return unlink($pidfilename);
+ }
+
+ function pidFilename()
+ {
+ $piddir = common_config('daemon', 'piddir');
+ if (!$piddir) {
+ return null;
+ }
+ $name = $this->name();
+ if (!$name) {
+ return null;
+ }
+ return $piddir . '/' . $name . '.pid';
+ }
+
+ function changeUser()
+ {
+ $username = common_config('daemon', 'user');
+
+ if ($username) {
+ $user_info = posix_getpwnam($username);
+ if (!$user_info) {
+ common_log(LOG_WARNING,
+ 'Ignoring unknown user for daemon: ' . $username);
+ } else {
+ common_log(LOG_INFO, "Setting user to " . $username);
+ posix_setuid($user_info['uid']);
+ }
+ }
+
+ $groupname = common_config('daemon', 'group');
+
+ if ($groupname) {
+ $group_info = posix_getgrnam($groupname);
+ if (!$group_info) {
+ common_log(LOG_WARNING,
+ 'Ignoring unknown group for daemon: ' . $groupname);
+ } else {
+ common_log(LOG_INFO, "Setting group to " . $groupname);
+ posix_setgid($group_info['gid']);
+ }
+ }
+ }
+
+ function runOnce()
+ {
+ if ($this->alreadyRunning()) {
+ common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
+ exit(0);
+ }
+ if ($this->background()) {
+ $this->writePidFile();
+ $this->changeUser();
+ $this->run();
+ $this->clearPidFile();
+ }
+ }
+
+ function run()
+ {
+ return true;
+ }
}
diff --git a/lib/deleteaction.php b/lib/deleteaction.php
index 5ba0e7e44..91c6487a9 100644
--- a/lib/deleteaction.php
+++ b/lib/deleteaction.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Base class for deleting things
+ *
+ * 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.
@@ -15,47 +18,57 @@
*
* 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 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); }
-
-class DeleteAction extends Action {
-
- function handle($args) {
- parent::handle($args);
- $user = common_current_user();
- $notice_id = $this->trimmed('notice');
- $notice = Notice::staticGet($notice_id);
- if (!$notice) {
- common_user_error(_('No such notice.'));
- exit;
- }
-
- $profile = $notice->getProfile();
- $user_profile = $user->getProfile();
-
- if (!common_logged_in()) {
- common_user_error(_('Not logged in.'));
- exit;
- } else if ($notice->profile_id != $user_profile->id) {
- common_user_error(_('Can\'t delete this notice.'));
- exit;
- }
- }
-
- function show_top($arr=NULL) {
- $instr = $this->get_instructions();
- $output = common_markup_to_html($instr);
- common_element_start('div', 'instructions');
- common_raw($output);
- common_element_end('div');
- }
-
- function get_title() {
- return NULL;
- }
-
- function show_header() {
- return;
- }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+class DeleteAction extends Action
+{
+ var $user = null;
+ var $notice = null;
+ var $profile = null;
+ var $user_profile = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->user = common_current_user();
+ $notice_id = $this->trimmed('notice');
+ $this->notice = Notice::staticGet($notice_id);
+
+ if (!$this->notice) {
+ common_user_error(_('No such notice.'));
+ exit;
+ }
+
+ $this->profile = $this->notice->getProfile();
+ $this->user_profile = $this->user->getProfile();
+
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ exit;
+ } else if ($this->notice->profile_id != $this->user_profile->id) {
+ common_user_error(_('Can\'t delete this notice.'));
+ exit;
+ }
+ }
+
}
diff --git a/lib/disfavorform.php b/lib/disfavorform.php
new file mode 100644
index 000000000..45a9ddb1d
--- /dev/null
+++ b/lib/disfavorform.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for disfavoring a notice
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for disfavoring a notice
+ *
+ * @category Form
+ * @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/
+ *
+ * @see FavorForm
+ */
+
+class DisfavorForm extends Form
+{
+ /**
+ * Notice to disfavor
+ */
+
+ var $notice = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Notice $notice notice to disfavor
+ */
+
+ function __construct($out=null, $notice=null)
+ {
+ parent::__construct($out);
+
+ $this->notice = $notice;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'disfavor-' . $this->notice->id;
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('disfavor');
+ }
+
+ /**
+ * Include a session token for CSRF protection
+ *
+ * @return void
+ */
+
+ function sessionToken()
+ {
+ $this->out->hidden('token-' . $this->notice->id,
+ common_session_token());
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Disfavor this notice'));
+ }
+
+
+ /**
+ * Data elements
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('notice-n'.$this->notice->id,
+ $this->notice->id,
+ 'notice');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('disfavor-submit-' . $this->notice->id,
+ _('Disfavor favorite'), 'submit', null, _('Disfavor this notice'));
+ }
+
+ /**
+ * Class of the form.
+ *
+ * @return string the form's class
+ */
+
+ function formClass()
+ {
+ return 'form_disfavor';
+ }
+
+}
diff --git a/lib/error.php b/lib/error.php
new file mode 100644
index 000000000..9842053d8
--- /dev/null
+++ b/lib/error.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * Error action.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Zach Copley <zach@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);
+}
+
+/**
+ * Base class for displaying HTTP errors
+ *
+ * @category Action
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ */
+class ErrorAction extends Action
+{
+ var $code = null;
+ var $message = null;
+ var $status = null;
+ var $default = null;
+
+ function __construct($message, $code, $output='php://output', $indent=true)
+ {
+ parent::__construct($output, $indent);
+
+ $this->code = $code;
+ $this->message = $message;
+
+ // XXX: hack alert: usually we aren't going to
+ // call this page directly, but because it's
+ // an action it needs an args array anyway
+ $this->prepare($_REQUEST);
+ }
+
+ /**
+ * To specify additional HTTP headers for the action
+ *
+ * @return void
+ */
+ function extraHeaders()
+ {
+ $status_string = $this->status[$this->code];
+ header('HTTP/1.1 '.$this->code.' '.$status_string);
+ }
+
+ /**
+ * Display content.
+ *
+ * @return nothing
+ */
+ function showContent()
+ {
+ $this->element('div', array('class' => 'error'), $this->message);
+ }
+
+ /**
+ * Page title.
+ *
+ * @return page title
+ */
+ function title()
+ {
+ return $this->message;
+ }
+
+ function isReadOnly()
+ {
+ return true;
+ }
+
+ function showPage()
+ {
+ parent::showPage();
+
+ // We don't want to have any more output after this
+ exit();
+ }
+
+ // Overload a bunch of stuff so the page isn't too bloated
+
+ function showBody()
+ {
+ $this->elementStart('body', array('id' => 'error'));
+ $this->elementStart('div', 'wrap');
+ $this->showHeader();
+ $this->showCore();
+ $this->showFooter();
+ $this->elementEnd('div');
+ $this->elementEnd('body');
+ }
+
+ function showCore()
+ {
+ $this->elementStart('div', array('id' => 'core'));
+ $this->showContentBlock();
+ $this->elementEnd('div');
+ }
+
+ function showHeader()
+ {
+ $this->elementStart('div', array('id' => 'header'));
+ $this->showLogo();
+ $this->elementEnd('div');
+ }
+
+}
diff --git a/lib/facebookaction.php b/lib/facebookaction.php
index 87a82ba01..2dbe15970 100644
--- a/lib/facebookaction.php
+++ b/lib/facebookaction.php
@@ -1,283 +1,572 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Low-level generator for HTML
+ *
+ * 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
+ * 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/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Faceboook
+ * @package Laconica
+ * @author Zach Copley <zach@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); }
-
-require_once(INSTALLDIR.'/extlib/facebook/facebook.php');
-
-class FacebookAction extends Action {
-
- function handle($args) {
- parent::handle($args);
- }
-
- function get_facebook() {
- $apikey = common_config('facebook', 'apikey');
- $secret = common_config('facebook', 'secret');
- return new Facebook($apikey, $secret);
- }
-
- function update_profile_box($facebook, $fbuid, $user) {
-
- $notice = $user->getCurrentNotice();
-
- # Need to include inline CSS for styling the Profile box
-
- $style = '<style>
- #notices {
- clear: both;
- margin: 0 auto;
- padding: 0;
- list-style-type: none;
- width: 600px;
- border-top: 1px solid #dec5b5;
- }
- #notices a:hover {
- text-decoration: underline;
- }
- .notice_single {
- clear: both;
- display: block;
- margin: 0;
- padding: 5px 5px 5px 0;
- min-height: 48px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 13px;
- line-height: 16px;
- border-bottom: 1px solid #dec5b5;
- background-color:#FCFFF5;
- opacity:1;
- }
- .notice_single:hover {
- background-color: #f7ebcc;
- }
- .notice_single p {
- display: inline;
- margin: 0;
- padding: 0;
- }
- </style>';
-
- $html = $this->render_notice($notice);
-
- $fbml = "<fb:wide>$content $html</fb:wide>";
- $fbml .= "<fb:narrow>$content $html</fb:narrow>";
-
- $fbml_main = "<fb:narrow>$content $html</fb:narrow>";
-
- $facebook->api_client->profile_setFBML(NULL, $fbuid, $fbml, NULL, NULL, $fbml_main);
- }
-
- # Display methods
-
- function show_header($selected ='Home') {
-
- # Add a timestamp to the CSS file so Facebook cache wont ignore our changes
- $ts = filemtime(theme_file('facebookapp.css'));
- $cssurl = theme_path('facebookapp.css') . "?ts=$ts";
-
- $header = '<link rel="stylesheet" type="text/css" href="'. $cssurl . '" />';
- # $header .='<script src="" ></script>';
- $header .= '<fb:dashboard/>';
-
- $header .=
- '<fb:tabs>'
- .'<fb:tab-item title="Home" href="index.php" selected="' . ($selected == 'Home') .'" />'
- .'<fb:tab-item title="Invite Friends" href="invite.php" selected="' . ($selected == 'Invite') . '" />'
- .'<fb:tab-item title="Settings" href="settings.php" selected="' . ($selected == 'Settings') . '" />'
- .'</fb:tabs>';
- $header .= '<div id="main_body">';
-
- echo $header;
-
- }
-
- function show_footer() {
- $footer = '</div>';
- echo $footer;
- }
-
- function show_login_form() {
-
- $loginform =
- ' <h2>To add the Identi.ca application, you need to log into your Identi.ca account.</h2>'
- .'<a href="http://identi.ca/">'
- .' <img src="http://theme.identi.ca/identica/logo.png" alt="Identi.ca" id="logo"/>'
- .'</a>'
- .'<h1 class="pagetitle">Login</h1>'
- .'<div class="instructions">'
- .' <p>Login with your username and password. Don\'t have a username yet?'
- .' <a href="http://identi.ca/main/register">Register</a> a new account.'
- .' </p>'
- .'</div>'
- .'<div id="content">'
- .' <form method="post" id="login">'
- .' <p>'
- .' <label for="nickname">Nickname</label>'
- .' <input name="nickname" type="text" class="input_text" id="nickname"/>'
- .' </p>'
- .' <p>'
- .' <label for="password">Password</label>'
- .' <input name="password" type="password" class="password" id="password"/>'
- .' </p>'
- .' <p>'
- .' <input type="submit" id="submit" name="submit" class="submit" value="Login"/>'
- .' </p>'
- .' </form>'
- .' <p>'
- .' <a href="http://identi.ca/main/recoverpassword">Lost or forgotten password?</a>'
- .' </p>'
- .'</div';
-
- echo $loginform;
- }
-
- function render_notice($notice) {
-
- global $config;
-
- $profile = $notice->getProfile();
- $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
-
- $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
-
- # XXX: we need to figure this out better. Is this right?
- if (strcmp($notice->uri, $noticeurl) != 0 && preg_match('/^http/', $notice->uri)) {
- $noticeurl = $notice->uri;
- }
-
- $html =
- '<li class="notice_single" id="' . $notice->id . '">'
- .'<a href="' . $profile->profileurl . '">'
- .'<img src="';
-
- if ($avatar) {
- $html .= common_avatar_display_url($avatar);
- } else {
- $html .= common_default_avatar(AVATAR_STREAM_SIZE);
- }
-
- $html .=
- '" class="avatar stream" width="'
- . AVATAR_STREAM_SIZE . '" height="' . AVATAR_STREAM_SIZE .'"'
- .' alt="';
-
- if ($profile->fullname) {
- $html .= $profile->fullname;
- } else {
- $html .= $profile->nickname;
- }
-
- $html .=
- '"></a>'
- .'<a href="' . $profile->profileurl . '" class="nickname">' . $profile->nickname . '</a>'
- .'<p class="content">' . $notice->rendered . '</p>'
- .'<p class="time">'
- .'<a class="permalink" href="' . $noticeurl . '" title="' . common_exact_date($notice->created) . '">' . common_date_string($notice->created) . '</a>';
-
- if ($notice->source) {
- $html .= _(' from ');
- $html .= $this->source_link($notice->source);
- }
-
- if ($notice->reply_to) {
- $replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
- $html .=
- ' (<a class="inreplyto" href="' . $replyurl . '">' . _('in reply to...') . ')';
- }
-
- $html .= '</p></li>';
-
- return $html;
- }
-
- function source_link($source) {
- $source_name = _($source);
-
- $html = '<span class="noticesource">';
-
- switch ($source) {
- case 'web':
- case 'xmpp':
- case 'mail':
- case 'omb':
- case 'api':
- $html .= $source_name;
- break;
- default:
- $ns = Notice_source::staticGet($source);
- if ($ns) {
- $html .= '<a href="' . $ns->url . '">' . $ns->name . '</a>';
- } else {
- $html .= $source_name;
- }
- break;
- }
-
- $html .= '</span>';
-
- return $html;
- }
-
- function pagination($have_before, $have_after, $page, $fbaction, $args=NULL) {
-
- $html = '';
-
- if ($have_before || $have_after) {
- $html = '<div id="pagination">';
- $html .'<ul id="nav_pagination">';
- }
-
- if ($have_before) {
- $pargs = array('page' => $page-1);
- $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
- $html .= '<li class="before">';
- $html .'<a href="' . $this->pagination_url($fbaction, $newargs) . '">' . _('« After') . '</a>';
- $html .'</li>';
- }
-
- if ($have_after) {
- $pargs = array('page' => $page+1);
- $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
- $html .= '<li class="after">';
- $html .'<a href="' . $this->pagination_url($fbaction, $newargs) . '">' . _('Before »') . '</a>';
- $html .'</li>';
- }
-
- if ($have_before || $have_after) {
- $html .= '<ul>';
- $html .'<div>';
- }
- }
-
- function pagination_url($fbaction, $args=NULL) {
- global $config;
-
- $extra = '';
-
- if ($args) {
- foreach ($args as $key => $value) {
- $extra .= "&${key}=${value}";
- }
- }
-
- return "$fbaction?${extra}";
- }
+if (!defined('LACONICA'))
+{
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/facebookutil.php';
+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
+ *
+ * Just wraps the HTMLOutputter constructor.
+ *
+ * @param string $output URI to output to, default = stdout
+ * @param boolean $indent Whether to indent output, default true
+ *
+ * @see XMLOutputter::__construct
+ * @see HTMLOutputter::__construct
+ */
+ 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->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()
+ {
+
+ $this->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => getFacebookBaseCSS()));
+
+ $this->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => getFacebookThemeCSS()));
+ }
+
+ function showScripts()
+ {
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => getFacebookJS()),
+ ' ');
+ }
+
+ /**
+ * Start an Facebook ready HTML document
+ *
+ * For Facebook we don't want to actually output any headers,
+ * DTD info, etc.
+ *
+ * If $type isn't specified, will attempt to do content negotiation.
+ *
+ * @param string $type MIME type to use; default is to do negotation.
+ *
+ * @return void
+ */
+
+ function startHTML($type=null)
+ {
+ $this->elementStart('div', array('class' => 'facebook-page'));
+ }
+
+ /**
+ * Ends a Facebook ready HTML document
+ *
+ * @return void
+ */
+ function endHTML()
+ {
+ $this->elementEnd('div');
+ $this->endXML();
+ }
+
+ /**
+ * Show notice form.
+ *
+ * MAY overload if no notice form needed... or direct message box????
+ *
+ * @return nothing
+ */
+ function showNoticeForm()
+ {
+ // don't do it for most of the Facebook pages
+ }
+
+ function showBody()
+ {
+ $this->elementStart('div', 'wrap');
+ $this->showHeader();
+ $this->showCore();
+ $this->showFooter();
+ $this->elementEnd('div');
+ }
+
+ function showAside()
+ {
+ }
+
+ function showHead($error, $success)
+ {
+ $this->showStylesheets();
+ $this->showScripts();
+
+ if ($error) {
+ $this->element("h1", null, $error);
+ }
+
+ if ($success) {
+ $this->element("h1", null, $success);
+ }
+
+ $this->elementStart('fb:if-section-not-added', array('section' => 'profile'));
+ $this->elementStart('span', array('id' => 'add_to_profile'));
+ $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()
+ {
+
+ $this->elementStart('ul', array('class' => 'nav'));
+
+ $this->elementStart('li', array('class' =>
+ ($this->action == 'facebookhome') ? 'current' : 'facebook_home'));
+ $this->element('a',
+ array('href' => 'index.php', 'title' => _('Home')), _('Home'));
+ $this->elementEnd('li');
+
+ $this->elementStart('li',
+ array('class' =>
+ ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite'));
+ $this->element('a',
+ array('href' => 'invite.php', 'title' => _('Invite')), _('Invite'));
+ $this->elementEnd('li');
+
+ $this->elementStart('li',
+ array('class' =>
+ ($this->action == 'facebooksettings') ? 'current' : 'facebook_settings'));
+ $this->element('a',
+ array('href' => 'settings.php',
+ 'title' => _('Settings')), _('Settings'));
+ $this->elementEnd('li');
+
+ $this->elementEnd('ul');
+
+ }
+
+ /**
+ * Show primary navigation.
+ *
+ * @return nothing
+ */
+ function showPrimaryNav()
+ {
+ // we don't want to show anything for this
+ }
+
+ /**
+ * Show header of the page.
+ *
+ * Calls template methods
+ *
+ * @return nothing
+ */
+ function showHeader()
+ {
+ $this->elementStart('div', array('id' => 'header'));
+ $this->showLogo();
+ $this->showNoticeForm();
+ $this->showPrimaryNav();
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show page, a template method.
+ *
+ * @return nothing
+ */
+ function showPage($error = null, $success = null)
+ {
+ $this->startHTML();
+ $this->showHead($error, $success);
+ $this->showBody();
+ $this->endHTML();
+ }
+
+
+ function showInstructions()
+ {
+
+ $this->elementStart('dl', array('class' => 'system_notice'));
+ $this->element('dt', null, 'Page Notice');
+
+ $loginmsg_part1 = _('To use the %s Facebook Application you need to login ' .
+ 'with your username and password. Don\'t have a username yet? ');
+
+ $loginmsg_part2 = _(' a new account.');
+
+ $this->elementStart('dd');
+ $this->elementStart('p');
+ $this->text(sprintf($loginmsg_part1, common_config('site', 'name')));
+ $this->element('a',
+ array('href' => common_local_url('register')), _('Register'));
+ $this->text($loginmsg_part2);
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+
+
+ function showLoginForm($msg = null)
+ {
+
+ $this->elementStart('div', array('class' => 'content'));
+ $this->element('h1', null, _('Login'));
+
+ if ($msg) {
+ $this->element('fb:error', array('message' => $msg));
+ }
+
+ $this->showInstructions();
+
+ $this->elementStart('div', array('id' => 'content_inner'));
+
+ $this->elementStart('form', array('method' => 'post',
+ 'class' => 'form_settings',
+ 'id' => 'login',
+ 'action' => 'index.php'));
+
+ $this->elementStart('fieldset');
+ $this->element('legend', null, _('Login to site'));
+
+ $this->elementStart('ul', array('class' => 'form_datas'));
+ $this->elementStart('li');
+ $this->input('nickname', _('Nickname'));
+ $this->elementEnd('li');
+ $this->elementStart('li');
+ $this->password('password', _('Password'));
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+
+ $this->submit('submit', _('Login'));
+ $this->elementEnd('form');
+
+ $this->elementStart('p');
+ $this->element('a', array('href' => common_local_url('recoverpassword')),
+ _('Lost or forgotten password?'));
+ $this->elementEnd('p');
+
+ $this->elementEnd('div');
+
+ }
+
+
+ function updateProfileBox($notice)
+ {
+
+ // Need to include inline CSS for styling the Profile box
+
+ $style = '<style>
+ #notices {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ }
+ #notices a:hover {
+ text-decoration: underline;
+ }
+ .notice_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #dec5b5;
+ background-color:#FCFFF5;
+ opacity:1;
+ }
+ .notice_single:hover {
+ background-color: #f7ebcc;
+ }
+ .notice_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
+ </style>';
+
+ $this->xw->openMemory();
+
+ $item = new FacebookNoticeListItem($notice, $this);
+ $item->show();
+
+ $fbml = "<fb:wide>$style " . $this->xw->outputMemory(false) . "</fb:wide>";
+ $fbml .= "<fb:narrow>$style " . $this->xw->outputMemory(false) . "</fb:narrow>";
+
+ $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->xw->openURI('php://output');
+ }
+
+
+ /**
+ * Generate pagination links
+ *
+ * @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 pagination($have_before, $have_after, $page, $action, $args=null)
+ {
+ // Does a little before-after block for next/prev page
+ if ($have_before || $have_after) {
+ $this->elementStart('div', array('class' => 'pagination'));
+ $this->elementStart('dl', null);
+ $this->element('dt', null, _('Pagination'));
+ $this->elementStart('dd', null);
+ $this->elementStart('ul', array('class' => 'nav'));
+ }
+ 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' => "$this->app_uri/$action?page=$newargs[page]", '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' => "$this->app_uri/$action?page=$newargs[page]", 'rel' => 'next'),
+ _('Before'));
+ $this->elementEnd('li');
+ }
+ if ($have_before || $have_after) {
+ $this->elementEnd('ul');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ $this->elementEnd('div');
+ }
+ }
+
+
+}
+
+class FacebookNoticeForm extends NoticeForm
+{
+
+ var $post_action = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param string $action action to return to, if any
+ * @param string $content content to pre-fill
+ */
+
+ 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
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return $this->post_action;
+ }
+
+}
+
+class FacebookNoticeList extends NoticeList
+{
+ /**
+ * 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' =>'notices_primary'));
+ $this->out->element('h2', null, _('Notices'));
+ $this->out->elementStart('ul', array('class' => 'notices'));
+
+ $cnt = 0;
+
+ while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > NOTICES_PER_PAGE) {
+ break;
+ }
+
+ $item = $this->newListItem($this->notice);
+ $item->show();
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
+
+ return $cnt;
+ }
+
+ /**
+ * returns a new list item for the current notice
+ *
+ * Overridden to return a Facebook specific list item.
+ *
+ * @param Notice $notice the current notice
+ *
+ * @return FacebookNoticeListItem a list item for displaying the notice
+ * formatted for display in the Facebook App.
+ */
+
+ function newListItem($notice)
+ {
+ return new FacebookNoticeListItem($notice, $this);
+ }
+
+}
+
+class FacebookNoticeListItem extends NoticeListItem
+{
+ /**
+ * recipe function for displaying a single notice in the Facebook App.
+ *
+ * Overridden to strip out some of the controls that we don't
+ * want to be available.
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $this->showStart();
+
+ $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->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');
+ }
}
diff --git a/lib/facebookutil.php b/lib/facebookutil.php
new file mode 100644
index 000000000..a2a2d7cb1
--- /dev/null
+++ b/lib/facebookutil.php
@@ -0,0 +1,84 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+require_once INSTALLDIR.'/extlib/facebook/facebook.php';
+require_once INSTALLDIR.'/lib/facebookaction.php';
+require_once INSTALLDIR.'/lib/noticelist.php';
+
+define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2
+define("FACEBOOK_NOTICE_PREFIX", 1);
+define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
+
+// Gets all the notices from users with a Facebook link since a given ID
+function getFacebookNotices($since)
+{
+ $qry = 'SELECT notice.* ' .
+ 'FROM notice ' .
+ 'JOIN foreign_link ' .
+ 'WHERE notice.profile_id = foreign_link.user_id ' .
+ 'AND foreign_link.service = 2';
+
+ // XXX: What should the limit be?
+ return Notice::getStreamDirect($qry, 0, 100, 0, 0, null, $since);
+}
+
+function getFacebook()
+{
+ $apikey = common_config('facebook', 'apikey');
+ $secret = common_config('facebook', 'secret');
+ return new Facebook($apikey, $secret);
+}
+
+function startFBML($indent = true)
+{
+ global $xw;
+ $xw = new XMLWriter();
+ $xw->openURI('php://output');
+ $xw->setIndent($indent);
+}
+
+function getFacebookBaseCSS()
+{
+ # Add a timestamp to the CSS file so Facebook cache wont ignore our changes
+ $ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.base.css');
+ $cssurl = theme_path('css/facebookapp.base.css', 'base') . "?ts=$ts";
+ return $cssurl;
+}
+
+function getFacebookThemeCSS()
+{
+ # Add a timestamp to the CSS file so Facebook cache wont ignore our changes
+ $ts = filemtime(theme_file('css/facebookapp.theme.css'));
+ $cssurl = theme_path('css/facebookapp.theme.css') . "?ts=$ts";
+ return $cssurl;
+}
+
+function getFacebookJS() {
+
+ # Add a timestamp to the FBJS file so Facebook cache wont ignore our changes
+ $ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
+ $jsurl = common_path('js/facebookapp.js') . "?ts=$ts";
+ return $jsurl;
+}
+
+function updateProfileBox($facebook, $flink, $notice) {
+ $fbaction = new FacebookAction($output='php://output', $indent=true, $facebook, $flink);
+ $fbaction->updateProfileBox($notice);
+}
+
diff --git a/lib/favorform.php b/lib/favorform.php
new file mode 100644
index 000000000..f3a7a9756
--- /dev/null
+++ b/lib/favorform.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for favoring a notice
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for favoring a notice
+ *
+ * @category Form
+ * @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/
+ *
+ * @see DisfavorForm
+ */
+
+class FavorForm extends Form
+{
+ /**
+ * Notice to favor
+ */
+
+ var $notice = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Notice $notice notice to favor
+ */
+
+ function __construct($out=null, $notice=null)
+ {
+ parent::__construct($out);
+
+ $this->notice = $notice;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'favor-' . $this->notice->id;
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('favor');
+ }
+
+ /**
+ * Include a session token for CSRF protection
+ *
+ * @return void
+ */
+
+ function sessionToken()
+ {
+ $this->out->hidden('token-' . $this->notice->id,
+ common_session_token());
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Favor this notice'));
+ }
+
+
+ /**
+ * Data elements
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('notice-n'.$this->notice->id,
+ $this->notice->id,
+ 'notice');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('favor-submit-' . $this->notice->id,
+ _('Favor'), 'submit', null, _('Favor this notice'));
+ }
+
+ /**
+ * Class of the form.
+ *
+ * @return string the form's class
+ */
+
+ function formClass()
+ {
+ return 'form_favor';
+ }
+}
diff --git a/lib/featureduserssection.php b/lib/featureduserssection.php
new file mode 100644
index 000000000..2935d8363
--- /dev/null
+++ b/lib/featureduserssection.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Section for featured users
+ *
+ * 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);
+}
+
+/**
+ * Section for featured users
+ *
+ * @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 FeaturedUsersSection extends ProfileSection
+{
+ function getProfiles()
+ {
+ $featured_nicks = common_config('nickname', 'featured');
+
+ if (!$featured_nicks) {
+ return null;
+ }
+
+ $quoted = array();
+
+ foreach ($featured_nicks as $nick) {
+ $quoted[] = "'$nick'";
+ }
+
+ $qry = 'SELECT profile.* ' .
+ 'FROM profile JOIN user on profile.id = user.id ' .
+ 'WHERE user.nickname in (' . implode(',', $quoted) . ') ' .
+ 'ORDER BY profile.created DESC ';
+
+ $limit = PROFILES_PER_SECTION + 1;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $profile = Memcached_DataObject::cachedQuery('Profile',
+ $qry,
+ 6 * 3600);
+ return $profile;
+ }
+
+ function title()
+ {
+ return _('Featured users');
+ }
+
+ function divId()
+ {
+ return 'featured_users';
+ }
+}
diff --git a/lib/feedlist.php b/lib/feedlist.php
new file mode 100644
index 000000000..47d909e96
--- /dev/null
+++ b/lib/feedlist.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Widget for showing a list of feeds
+ *
+ * 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>
+ * @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 showing a list of feeds
+ *
+ * Typically used for Action::showExportList()
+ *
+ * @category Widget
+ * @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/
+ *
+ * @see Action::showExportList()
+ */
+
+class FeedList extends Widget
+{
+ var $action = null;
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ function show($feeds)
+ {
+ $this->out->elementStart('div', array('id' => 'export_data',
+ 'class' => 'section'));
+ $this->out->element('h2', null, _('Export data'));
+ $this->out->elementStart('ul', array('class' => 'xoxo'));
+
+ foreach ($feeds as $key => $value) {
+ $this->feedItem($feeds[$key]);
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
+ }
+
+ function feedItem($feed)
+ {
+ $nickname = $this->action->trimmed('nickname');
+
+ switch($feed['item']) {
+ case 'notices': default:
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'allrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for $nickname and friends";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'repliesrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for replies to $nickname";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'publicrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Public timeline ".$feed['version']." feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'publicatom':
+ $feed_classname = "atom";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Public timeline ".$feed['version']." feed";
+ $feed['textContent'] = "Atom";
+ break;
+
+ case 'tagrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for this tag";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'favoritedrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Favorited ".$feed['version']." feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'foaf':
+ $feed_classname = "foaf";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's FOAF file";
+ $feed['textContent'] = "FOAF";
+ break;
+
+ case 'favoritesrss':
+ $feed_classname = "favorites";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Feed for favorites of $nickname";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'usertimeline':
+ $feed_classname = "atom";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "Atom";
+ break;
+ }
+ $this->out->elementStart('li');
+ $this->out->element('a', array('href' => $feed['href'],
+ 'class' => $feed_classname,
+ 'type' => $feed_mimetype,
+ 'title' => $feed_title),
+ $feed['textContent']);
+ $this->out->elementEnd('li');
+ }
+}
diff --git a/lib/form.php b/lib/form.php
new file mode 100644
index 000000000..5317df471
--- /dev/null
+++ b/lib/form.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for forms
+ *
+ * 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>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Base class for forms
+ *
+ * We have a lot of common forms (subscribe, fave, delete) and this superclass
+ * lets us abstract out the basic features of the form.
+ *
+ * @category Widget
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class Form extends Widget
+{
+ /**
+ * Show the form
+ *
+ * Uses a recipe to output the form.
+ *
+ * @return void
+ * @see Widget::show()
+ */
+
+ function show()
+ {
+ $this->out->elementStart('form',
+ array('id' => $this->id(),
+ 'class' => $this->formClass(),
+ 'method' => 'post',
+ 'action' => $this->action()));
+ $this->out->elementStart('fieldset');
+ $this->formLegend();
+ $this->sessionToken();
+ $this->formData();
+ $this->formActions();
+ $this->out->elementEnd('fieldset');
+ $this->out->elementEnd('form');
+ }
+
+ /**
+ * Include a session token for CSRF protection
+ *
+ * @return void
+ */
+
+ function sessionToken()
+ {
+ $this->out->hidden('token', common_session_token());
+ }
+
+ /**
+ * Name of the form
+ *
+ * Sub-classes should overload this with the name of their form.
+ *
+ * @return void
+ */
+
+ function formLegend()
+ {
+ }
+
+ /**
+ * Visible or invisible data elements
+ *
+ * Display the form fields that make up the data of the form.
+ * Sub-classes should overload this to show their data.
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ }
+
+ /**
+ * Buttons for form actions
+ *
+ * Submit and cancel buttons (or whatever)
+ * Sub-classes should overload this to show their own buttons.
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ }
+
+ /**
+ * ID of the form
+ *
+ * Should be unique on the page. Sub-classes should overload this
+ * to show their own IDs.
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return null;
+ }
+
+ /**
+ * Action of the form.
+ *
+ * URL to post to. Should be overloaded by subclasses to give
+ * somewhere to post to.
+ *
+ * @return string URL to post to
+ */
+
+ function action()
+ {
+ }
+
+ /**
+ * Class of the form.
+ *
+ * @return string the form's class
+ */
+
+ function formClass()
+ {
+ return 'form';
+ }
+}
diff --git a/lib/gallery.php b/lib/gallery.php
deleted file mode 100644
index 0dd351bab..000000000
--- a/lib/gallery.php
+++ /dev/null
@@ -1,320 +0,0 @@
-<?php
-
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('LACONICA')) { exit(1); }
-
-require_once(INSTALLDIR.'/lib/profilelist.php');
-
-# 10x8
-
-define('AVATARS_PER_PAGE', 80);
-
-class GalleryAction extends Action {
-
- function is_readonly() {
- return true;
- }
-
- function handle($args) {
- parent::handle($args);
-
- # Post from the tag dropdown; redirect to a GET
-
- if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- common_redirect($this->self_url(), 307);
- }
-
- $nickname = common_canonical_nickname($this->arg('nickname'));
- $user = User::staticGet('nickname', $nickname);
-
- if (!$user) {
- $this->no_such_user();
- return;
- }
-
- $profile = $user->getProfile();
-
- if (!$profile) {
- $this->server_error(_('User without matching profile in system.'));
- return;
- }
-
- $page = $this->arg('page');
-
- if (!$page) {
- $page = 1;
- }
-
- $display = $this->arg('display');
-
- if (!$display) {
- $display = 'list';
- }
-
- $tag = $this->arg('tag');
-
- common_show_header($profile->nickname . ": " . $this->gallery_type(),
- NULL, $profile,
- array($this, 'show_top'));
-
- $this->display_links($profile, $page, $display);
- $this->show_tags_dropdown($profile);
-
- $this->show_gallery($profile, $page, $display, $tag);
- common_show_footer();
- }
-
- function no_such_user() {
- $this->client_error(_('No such user.'));
- }
-
- function show_tags_dropdown($profile) {
- $tag = $this->trimmed('tag');
- list($lst, $usr) = $this->fields();
- $tags = $this->get_all_tags($profile, $lst, $usr);
- $content = array();
- foreach ($tags as $t) {
- $content[$t] = $t;
- }
- if ($tags) {
- common_element_start('dl', array('id'=>'filter_tags'));
- common_element('dt', null, _('Filter tags'));
- common_element_start('dd');
- common_element_start('ul');
- common_element_start('li', array('id'=>'filter_tags_all', 'class'=>'child_1'));
- common_element('a', array('href' => common_local_url($this->trimmed('action'),
- array('nickname' => $profile->nickname))),
- _('All'));
- common_element_end('li');
- common_element_start('li', array('id'=>'filter_tags_item'));
- common_element_start('form', array('name' => 'bytag', 'id' => 'bytag', 'method' => 'post'));
- common_dropdown('tag', _('Tag'), $content,
- _('Choose a tag to narrow list'), FALSE, $tag);
- common_submit('go', _('Go'));
- common_element_end('form');
- common_element_end('li');
- common_element_end('ul');
- common_element_end('dd');
- common_element_end('dl');
- }
- }
-
- function show_top($profile) {
- common_element('div', 'instructions',
- $this->get_instructions($profile));
- $this->show_menu();
- }
-
- function show_menu() {
- # action => array('prompt', 'title', $args)
- $action = $this->trimmed('action');
- $nickname = $this->trimmed('nickname');
- $menu =
- array('subscriptions' =>
- array( _('Subscriptions'),
- _('Subscriptions'),
- array('nickname' => $nickname)),
- 'subscribers' =>
- array(
- _('Subscribers'),
- _('Subscribers'),
- array('nickname' => $nickname)),
- );
- $this->nav_menu($menu);
- }
-
- function show_gallery($profile, $page, $display='list', $tag=NULL) {
-
- $other = new Profile();
-
- list($lst, $usr) = $this->fields();
-
- $per_page = ($display == 'list') ? PROFILES_PER_PAGE : AVATARS_PER_PAGE;
-
- $offset = ($page-1)*$per_page;
- $limit = $per_page + 1;
-
- if (common_config('db','type') == 'pgsql') {
- $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
- } else {
- $lim = ' LIMIT ' . $offset . ', ' . $limit;
- }
-
- # XXX: memcached results
- # FIXME: SQL injection on $tag
-
- $other->query('SELECT profile.* ' .
- 'FROM profile JOIN subscription ' .
- 'ON profile.id = subscription.' . $lst . ' ' .
- (($tag) ? 'JOIN profile_tag ON (profile.id = profile_tag.tagged AND subscription.'.$usr.'= profile_tag.tagger) ' : '') .
- 'WHERE ' . $usr . ' = ' . $profile->id . ' ' .
- 'AND subscriber != subscribed ' .
- (($tag) ? 'AND profile_tag.tag= "' . $tag . '" ': '') .
- 'ORDER BY subscription.created DESC, profile.id DESC ' .
- $lim);
-
- if ($display == 'list') {
- $cls = $this->profile_list_class();
- $profile_list = new $cls($other, $profile, $this->trimmed('action'));
- $cnt = $profile_list->show_list();
- } else {
- $cnt = $this->icon_list($other);
- }
-
- # For building the pagination URLs
-
- $args = array('nickname' => $profile->nickname);
-
- if ($display != 'list') {
- $args['display'] = $display;
- }
-
- common_pagination($page > 1,
- $cnt > $per_page,
- $page,
- $this->trimmed('action'),
- $args);
- }
-
- function profile_list_class() {
- return 'ProfileList';
- }
-
- function icon_list($other) {
-
- common_element_start('ul', $this->div_class());
-
- $cnt = 0;
-
- while ($other->fetch()) {
-
- $cnt++;
-
- if ($cnt > AVATARS_PER_PAGE) {
- break;
- }
-
- common_element_start('li');
-
- common_element_start('a', array('title' => ($other->fullname) ?
- $other->fullname :
- $other->nickname,
- 'href' => $other->profileurl,
- 'class' => 'subscription'));
- $avatar = $other->getAvatar(AVATAR_STREAM_SIZE);
- common_element('img',
- array('src' =>
- (($avatar) ? common_avatar_display_url($avatar) :
- common_default_avatar(AVATAR_STREAM_SIZE)),
- 'width' => AVATAR_STREAM_SIZE,
- 'height' => AVATAR_STREAM_SIZE,
- 'class' => 'avatar stream',
- 'alt' => ($other->fullname) ?
- $other->fullname :
- $other->nickname));
- common_element_end('a');
-
- # XXX: subscribe form here
-
- common_element_end('li');
- }
-
- common_element_end('ul');
-
- return $cnt;
- }
-
- function gallery_type() {
- return NULL;
- }
-
- function get_instructions(&$profile) {
- return NULL;
- }
-
- function fields() {
- return NULL;
- }
-
- function div_class() {
- return '';
- }
-
- function display_links($profile, $page, $display) {
- $tag = $this->trimmed('tag');
-
- common_element_start('dl', array('id'=>'subscriptions_nav'));
- common_element('dt', null, _('Subscriptions navigation'));
- common_element_start('dd');
- common_element_start('ul', array('class'=>'nav'));
-
- switch ($display) {
- case 'list':
- common_element('li', array('class'=>'child_1'), _('List'));
- common_element_start('li');
- $url_args = array('display' => 'icons',
- 'nickname' => $profile->nickname,
- 'page' => 1 + floor((($page - 1) * PROFILES_PER_PAGE) / AVATARS_PER_PAGE));
- if ($tag) {
- $url_args['tag'] = $tag;
- }
- $url = common_local_url($this->trimmed('action'), $url_args);
- common_element('a', array('href' => $url),
- _('Icons'));
- common_element_end('li');
- break;
- default:
- common_element_start('li', array('class'=>'child_1'));
- $url_args = array('nickname' => $profile->nickname,
- 'page' => 1 + floor((($page - 1) * AVATARS_PER_PAGE) / PROFILES_PER_PAGE));
- if ($tag) {
- $url_args['tag'] = $tag;
- }
- $url = common_local_url($this->trimmed('action'), $url_args);
- common_element('a', array('href' => $url),
- _('List'));
- common_element_end('li');
- common_element('li', NULL, _('Icons'));
- break;
- }
-
- common_element_end('ul');
- common_element_end('dd');
- common_element_end('dl');
- }
-
- # Get list of tags we tagged other users with
-
- function get_all_tags($profile, $lst, $usr) {
- $profile_tag = new Notice_tag();
- $profile_tag->query('SELECT DISTINCT(tag) ' .
- 'FROM profile_tag, subscription ' .
- 'WHERE tagger = ' . $profile->id . ' ' .
- 'AND ' . $usr . ' = ' . $profile->id . ' ' .
- 'AND ' . $lst . ' = tagged ' .
- 'AND tagger != tagged');
- $tags = array();
- while ($profile_tag->fetch()) {
- $tags[] = $profile_tag->tag;
- }
- $profile_tag->free();
- return $tags;
- }
-} \ No newline at end of file
diff --git a/lib/galleryaction.php b/lib/galleryaction.php
new file mode 100644
index 000000000..25a5e3fd5
--- /dev/null
+++ b/lib/galleryaction.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profilelist.php';
+
+// 10x8
+
+define('AVATARS_PER_PAGE', 80);
+
+class GalleryAction extends Action
+{
+ var $profile = null;
+ var $user = null;
+ var $page = null;
+ var $tag = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ // FIXME very similar code below
+
+ $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('subscriptions', $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->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ $this->tag = $this->trimmed('tag');
+
+ return true;
+ }
+
+ function isReadOnly()
+ {
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ # Post from the tag dropdown; redirect to a GET
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ common_redirect($this->selfUrl(), 307);
+ return;
+ }
+
+ $this->showPage();
+ }
+
+ function showLocalNav()
+ {
+ $nav = new SubGroupNav($this, $this->user);
+ $nav->show();
+ }
+
+ function showContent()
+ {
+ $this->showTagsDropdown();
+ }
+
+ function showTagsDropdown()
+ {
+ $tag = $this->trimmed('tag');
+
+ $tags = $this->getAllTags();
+
+ $content = array();
+
+ foreach ($tags as $t) {
+ $content[$t] = $t;
+ }
+ if ($tags) {
+ $this->elementStart('dl', array('id'=>'filter_tags'));
+ $this->element('dt', null, _('Filter tags'));
+ $this->elementStart('dd');
+ $this->elementStart('ul');
+ $this->elementStart('li', array('id' => 'filter_tags_all',
+ 'class' => 'child_1'));
+ $this->element('a',
+ array('href' =>
+ common_local_url($this->trimmed('action'),
+ array('nickname' =>
+ $this->user->nickname))),
+ _('All'));
+ $this->elementEnd('li');
+ $this->elementStart('li', array('id'=>'filter_tags_item'));
+ $this->elementStart('form', array('name' => 'bytag',
+ 'id' => 'bytag',
+ 'method' => 'post'));
+ $this->dropdown('tag', _('Tag'), $content,
+ _('Choose a tag to narrow list'), false, $tag);
+ $this->submit('go', _('Go'));
+ $this->elementEnd('form');
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+ }
+ }
+
+ // Get list of tags we tagged other users with
+
+ function getTags($lst, $usr)
+ {
+ $profile_tag = new Notice_tag();
+ $profile_tag->query('SELECT DISTINCT(tag) ' .
+ 'FROM profile_tag, subscription ' .
+ 'WHERE tagger = ' . $this->profile->id . ' ' .
+ 'AND ' . $usr . ' = ' . $this->profile->id . ' ' .
+ 'AND ' . $lst . ' = tagged ' .
+ 'AND tagger != tagged');
+ $tags = array();
+ while ($profile_tag->fetch()) {
+ $tags[] = $profile_tag->tag;
+ }
+ $profile_tag->free();
+ return $tags;
+ }
+
+ function getAllTags()
+ {
+ return array();
+ }
+} \ No newline at end of file
diff --git a/lib/groupeditform.php b/lib/groupeditform.php
new file mode 100644
index 000000000..ca674f3c8
--- /dev/null
+++ b/lib/groupeditform.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for editing a group
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for editing a group
+ *
+ * @category Form
+ * @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/
+ *
+ * @see UnsubscribeForm
+ */
+
+class GroupEditForm extends Form
+{
+ /**
+ * group for user to join
+ */
+
+ var $group = null;
+
+ /**
+ * Constructor
+ *
+ * @param Action $out output channel
+ * @param User_group $group group to join
+ */
+
+ function __construct($out=null, $group=null)
+ {
+ parent::__construct($out);
+
+ $this->group = $group;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return string ID of the form
+ */
+
+ function id()
+ {
+ if ($this->group) {
+ return 'form_group_edit-' . $this->group->id;
+ } else {
+ return 'form_group_add';
+ }
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_settings';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ if ($this->group) {
+ return common_local_url('editgroup',
+ array('nickname' => $this->group->nickname));
+ } else {
+ return common_local_url('newgroup');
+ }
+ }
+
+
+ /**
+ * Name of the form
+ *
+ * @return void
+ */
+
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Create a new group'));
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->elementStart('ul', 'form_data');
+ $this->out->elementStart('li');
+ $this->out->hidden('groupid', $this->group->id);
+ $this->out->input('nickname', _('Nickname'),
+ ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $this->group->nickname,
+ _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+ $this->out->elementEnd('li');
+ $this->out->elementStart('li');
+ $this->out->input('fullname', _('Full name'),
+ ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $this->group->fullname);
+ $this->out->elementEnd('li');
+ $this->out->elementStart('li');
+ $this->out->input('homepage', _('Homepage'),
+ ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $this->group->homepage,
+ _('URL of the homepage or blog of the group or topic'));
+ $this->out->elementEnd('li');
+ $this->out->elementStart('li');
+ $this->out->textarea('description', _('Description'),
+ ($this->out->arg('description')) ? $this->out->arg('description') : $this->group->description,
+ _('Describe the group or topic in 140 chars'));
+ $this->out->elementEnd('li');
+ $this->out->elementStart('li');
+ $this->out->input('location', _('Location'),
+ ($this->out->arg('location')) ? $this->out->arg('location') : $this->group->location,
+ _('Location for the group, if any, like "City, State (or Region), Country"'));
+ $this->out->elementEnd('li');
+ $this->out->elementEnd('ul');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Save'));
+ }
+}
diff --git a/lib/grouplist.php b/lib/grouplist.php
new file mode 100644
index 000000000..629bdd05d
--- /dev/null
+++ b/lib/grouplist.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Widget to show a list of groups
+ *
+ * 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 Public
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+define('GROUPS_PER_PAGE', 20);
+
+/**
+ * Widget to show a list of groups
+ *
+ * @category Public
+ * @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 GroupList extends Widget
+{
+ /** Current group, group query. */
+ var $group = null;
+ /** Owner of this list */
+ var $owner = null;
+ /** Action object using us. */
+ var $action = null;
+
+ function __construct($group, $owner=null, $action=null)
+ {
+ parent::__construct($action);
+
+ $this->group = $group;
+ $this->owner = $owner;
+ $this->action = $action;
+ }
+
+ function show()
+ {
+ $this->out->elementStart('ul', 'profiles groups xoxo');
+
+ $cnt = 0;
+
+ while ($this->group->fetch()) {
+ $cnt++;
+ if($cnt > GROUPS_PER_PAGE) {
+ break;
+ }
+ $this->showgroup();
+ }
+
+ $this->out->elementEnd('ul');
+
+ return $cnt;
+ }
+
+ function showGroup()
+ {
+ $this->out->elementStart('li', array('class' => 'profile',
+ 'id' => 'group-' . $this->group->id));
+
+ $user = common_current_user();
+
+ $this->out->elementStart('div', 'entity_profile vcard');
+
+ $logo = ($this->group->stream_logo) ?
+ $this->group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
+
+ $this->out->elementStart('a', array('href' => $this->group->homeUrl(),
+ 'class' => 'url',
+ 'rel' => 'group'));
+ $this->out->element('img', array('src' => $logo,
+ 'class' => 'photo avatar',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($this->group->fullname) ? $this->group->fullname :
+ $this->group->nickname));
+ $hasFN = ($this->group->fullname) ? 'nickname url uid' : 'fn org nickname url uid';
+ $this->out->elementStart('span', $hasFN);
+ $this->out->raw($this->highlight($this->group->nickname));
+ $this->out->elementEnd('span');
+ $this->out->elementEnd('a');
+
+ if ($this->group->fullname) {
+ $this->out->elementStart('dl', 'entity_fn');
+ $this->out->element('dt', null, 'Full name');
+ $this->out->elementStart('dd');
+ $this->out->elementStart('span', 'fn org');
+ $this->out->raw($this->highlight($this->group->fullname));
+ $this->out->elementEnd('span');
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->group->location) {
+ $this->out->elementStart('dl', 'entity_location');
+ $this->out->element('dt', null, _('Location'));
+ $this->out->elementStart('dd', 'location');
+ $this->out->raw($this->highlight($this->group->location));
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->group->homepage) {
+ $this->out->elementStart('dl', 'entity_url');
+ $this->out->element('dt', null, _('URL'));
+ $this->out->elementStart('dd');
+ $this->out->elementStart('a', array('href' => $this->group->homepage,
+ 'class' => 'url'));
+ $this->out->raw($this->highlight($this->group->homepage));
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->group->description) {
+ $this->out->elementStart('dl', 'entity_note');
+ $this->out->element('dt', null, _('Note'));
+ $this->out->elementStart('dd', 'note');
+ $this->out->raw($this->highlight($this->group->description));
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+
+ # If we're on a list with an owner (subscriptions or subscribers)...
+
+ if ($user && $user->id == $this->owner->id) {
+ $this->showOwnerControls();
+ }
+
+ $this->out->elementEnd('div');
+
+ if ($user) {
+ # XXX: special-case for user looking at own
+ # subscriptions page
+ if ($user->isMember($this->group)) {
+ $lf = new LeaveForm($this->out, $this->group);
+ $lf->show();
+ } else {
+ $jf = new JoinForm($this->out, $this->group);
+ $jf->show();
+ }
+ }
+
+ $this->out->elementEnd('li');
+ }
+
+ /* Override this in subclasses. */
+
+ function showOwnerControls()
+ {
+ return;
+ }
+
+ function highlight($text)
+ {
+ return htmlspecialchars($text);
+ }
+}
diff --git a/lib/groupminilist.php b/lib/groupminilist.php
new file mode 100644
index 000000000..fe38d0340
--- /dev/null
+++ b/lib/groupminilist.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Widget to show a mini-list of groups
+ *
+ * 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 Public
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/grouplist.php';
+
+define('GROUPS_PER_MINILIST', 80);
+
+/**
+ * Widget to show a list of groups, good for sidebar
+ *
+ * @category Public
+ * @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 GroupMiniList extends GroupList
+{
+ function show()
+ {
+ $this->out->elementStart('ul', 'entities groups xoxo');
+
+ $cnt = 0;
+
+ while ($this->group->fetch()) {
+ $cnt++;
+ if($cnt > GROUPS_PER_MINILIST) {
+ break;
+ }
+ $this->showGroup();
+ }
+
+ $this->out->elementEnd('ul');
+
+ return $cnt;
+ }
+
+ function showGroup()
+ {
+ $this->out->elementStart('li', 'vcard');
+ $this->out->elementStart('a', array('title' => ($this->group->fullname) ?
+ $this->group->fullname :
+ $this->group->nickname,
+ 'href' => $this->group->homeUrl(),
+ 'rel' => 'contact group',
+ 'class' => 'url'));
+ $logo = ($this->group->stream_logo) ?
+ $this->group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
+
+ $this->out->element('img', array('src' => $logo,
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar photo',
+ 'alt' => ($this->group->fullname) ?
+ $this->group->fullname :
+ $this->group->nickname));
+ $this->out->element('span', 'fn org nickname', $this->group->nickname);
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('li');
+ }
+}
diff --git a/lib/groupnav.php b/lib/groupnav.php
new file mode 100644
index 000000000..90bdc1014
--- /dev/null
+++ b/lib/groupnav.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Tabset for a particular group
+ *
+ * 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 Action
+ * @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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Tabset for a group
+ *
+ * Shows a group of tabs for a particular user group
+ *
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class GroupNav extends Widget
+{
+ var $action = null;
+ var $group = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null, $group=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ $this->group = $group;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $action_name = $this->action->trimmed('action');
+ $nickname = $this->group->nickname;
+
+ $this->out->elementStart('ul', array('class' => 'nav'));
+ $this->out->menuItem(common_local_url('showgroup', array('nickname' =>
+ $nickname)),
+ _('Group'),
+ sprintf(_('%s group'), $nickname),
+ $action_name == 'showgroup',
+ 'nav_group_group');
+ $this->out->menuItem(common_local_url('groupmembers', array('nickname' =>
+ $nickname)),
+ _('Members'),
+ sprintf(_('%s group members'), $nickname),
+ $action_name == 'groupmembers',
+ 'nav_group_members');
+
+ $cur = common_current_user();
+
+ if ($cur && $cur->isAdmin($this->group)) {
+ $this->out->menuItem(common_local_url('editgroup', array('nickname' =>
+ $nickname)),
+ _('Admin'),
+ sprintf(_('Edit %s group properties'), $nickname),
+ $action_name == 'editgroup',
+ 'nav_group_admin');
+ $this->out->menuItem(common_local_url('grouplogo', array('nickname' =>
+ $nickname)),
+ _('Logo'),
+ sprintf(_('Add or edit %s logo'), $nickname),
+ $action_name == 'grouplogo',
+ 'nav_group_logo');
+ }
+ $this->out->elementEnd('ul');
+ }
+}
diff --git a/lib/groupsbymemberssection.php b/lib/groupsbymemberssection.php
new file mode 100644
index 000000000..4fa07a244
--- /dev/null
+++ b/lib/groupsbymemberssection.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Groups with the most members 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);
+}
+
+/**
+ * Groups with the most members 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 GroupsByMembersSection extends GroupSection
+{
+ function getGroups()
+ {
+ $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 ' .
+ 'ORDER BY value DESC ';
+
+ $limit = GROUPS_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $group = Memcached_DataObject::cachedQuery('User_group',
+ $qry,
+ 3600);
+ return $group;
+ }
+
+ function title()
+ {
+ return _('Groups with most members');
+ }
+
+ function divId()
+ {
+ return 'top_groups_by_member';
+ }
+}
diff --git a/lib/groupsbypostssection.php b/lib/groupsbypostssection.php
new file mode 100644
index 000000000..a5e33a93d
--- /dev/null
+++ b/lib/groupsbypostssection.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Groups with the most posts 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);
+}
+
+/**
+ * Groups with the most posts 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 GroupsByPostsSection extends GroupSection
+{
+ function getGroups()
+ {
+ $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 ' .
+ 'ORDER BY value DESC ';
+
+ $limit = GROUPS_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $group = Memcached_DataObject::cachedQuery('User_group',
+ $qry,
+ 3600);
+ return $group;
+ }
+
+ function title()
+ {
+ return _('Groups with most posts');
+ }
+
+ function divId()
+ {
+ return 'top_groups_by_post';
+ }
+}
diff --git a/lib/groupsection.php b/lib/groupsection.php
new file mode 100644
index 000000000..c19299493
--- /dev/null
+++ b/lib/groupsection.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists of groups
+ *
+ * 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('GROUPS_PER_SECTION', 6);
+
+/**
+ * Base class for sections
+ *
+ * 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 GroupSection extends Section
+{
+ function showContent()
+ {
+ $profiles = $this->getGroups();
+
+ if (!$profiles) {
+ return false;
+ }
+
+ $cnt = 0;
+
+ $this->out->elementStart('table');
+
+ while ($profiles->fetch() && ++$cnt <= GROUPS_PER_SECTION) {
+ $this->showGroup($profiles);
+ }
+
+ $this->out->elementEnd('table');
+
+ return ($cnt > GROUPS_PER_SECTION);
+ }
+
+ function getGroups()
+ {
+ return null;
+ }
+
+ function showGroup($group)
+ {
+ $this->out->elementStart('tr');
+ $this->out->elementStart('td');
+ $this->out->elementStart('span', 'vcard');
+ $this->out->elementStart('a', array('title' => ($group->fullname) ?
+ $group->fullname :
+ $group->nickname,
+ 'href' => $group->homeUrl(),
+ 'rel' => 'contact group',
+ 'class' => 'url'));
+ $logo = ($group->stream_logo) ?
+ $group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
+
+ $this->out->element('img', array('src' => $logo,
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar photo',
+ 'alt' => ($group->fullname) ?
+ $group->fullname :
+ $group->nickname));
+ $this->out->element('span', 'fn org nickname', $group->nickname);
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('span');
+ $this->out->elementEnd('td');
+ if ($group->value) {
+ $this->out->element('td', 'value', $group->value);
+ }
+ $this->out->elementEnd('tr');
+ }
+}
diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php
new file mode 100644
index 000000000..f05be85cb
--- /dev/null
+++ b/lib/grouptagcloudsection.php
@@ -0,0 +1,87 @@
+<?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 GroupTagCloudSection extends TagCloudSection
+{
+ var $group = null;
+
+ function __construct($out=null, $group=null)
+ {
+ parent::__construct($out);
+ $this->group = $group;
+ }
+
+ function title()
+ {
+ return sprintf(_('Tags in %s group\'s notices'), $this->group->nickname);
+ }
+
+ function getTags()
+ {
+ $qry = 'SELECT notice_tag.tag, '.
+ 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' .
+ 'FROM notice_tag JOIN notice ' .
+ 'ON notice_tag.notice_id = notice.id ' .
+ 'JOIN group_inbox on group_inbox.notice_id = notice.id ' .
+ 'WHERE group_inbox.group_id = %d ' .
+ 'GROUP BY notice_tag.tag ' .
+ 'ORDER BY weight DESC ';
+
+ $limit = TAGS_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $tag = Memcached_DataObject::cachedQuery('Notice_tag',
+ sprintf($qry,
+ common_config('tag', 'dropoff'),
+ $this->group->id),
+ 3600);
+ return $tag;
+ }
+
+}
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
new file mode 100644
index 000000000..f9245414f
--- /dev/null
+++ b/lib/htmloutputter.php
@@ -0,0 +1,365 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Low-level generator for HTML
+ *
+ * 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 Output
+ * @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);
+}
+
+require_once INSTALLDIR.'/lib/xmloutputter.php';
+
+define('PAGE_TYPE_PREFS',
+ 'text/html,application/xhtml+xml,'.
+ 'application/xml;q=0.3,text/xml;q=0.2');
+
+/**
+ * Low-level generator for HTML
+ *
+ * Abstracts some of the code necessary for HTML generation. Especially
+ * has methods for generating HTML form elements. Note that these have
+ * been created kind of haphazardly, not with an eye to making a general
+ * HTML-creation class.
+ *
+ * @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/
+ *
+ * @see Action
+ * @see XMLOutputter
+ */
+
+class HTMLOutputter extends XMLOutputter
+{
+ /**
+ * Constructor
+ *
+ * Just wraps the XMLOutputter constructor.
+ *
+ * @param string $output URI to output to, default = stdout
+ * @param boolean $indent Whether to indent output, default true
+ */
+
+ function __construct($output='php://output', $indent=true)
+ {
+ parent::__construct($output, $indent);
+ }
+
+ /**
+ * Start an HTML document
+ *
+ * If $type isn't specified, will attempt to do content negotiation.
+ *
+ * Attempts to do content negotiation for language, also.
+ *
+ * @param string $type MIME type to use; default is to do negotation.
+ *
+ * @todo extract content negotiation code to an HTTP module or class.
+ *
+ * @return void
+ */
+
+ function startHTML($type=null)
+ {
+ if (!$type) {
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
+ $_SERVER['HTTP_ACCEPT'] : null;
+
+ // XXX: allow content negotiation for RDF, RSS, or XRDS
+
+ $cp = common_accept_to_prefs($httpaccept);
+ $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
+
+ $type = common_negotiate_type($cp, $sp);
+
+ if (!$type) {
+ common_user_error(_('This page is not available in a '.
+ 'media type you accept'), 406);
+ exit(0);
+ }
+ }
+
+ header('Content-Type: '.$type);
+
+ $this->extraHeaders();
+
+ $this->startXML('html',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+ // FIXME: correct language for interface
+
+ $language = common_language();
+
+ $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+ 'xml:lang' => $language,
+ 'lang' => $language));
+ }
+
+ /**
+ * Ends an HTML document
+ *
+ * @return void
+ */
+ function endHTML()
+ {
+ $this->elementEnd('html');
+ $this->endXML();
+ }
+
+ /**
+ * To specify additional HTTP headers for the action
+ *
+ * @return void
+ */
+ function extraHeaders()
+ {
+ // Needs to be overloaded
+ }
+
+ /**
+ * Output an HTML text input element
+ *
+ * Despite the name, it is specifically for outputting a
+ * text input element, not other <input> elements. It outputs
+ * a cluster of elements, including a <label> and an associated
+ * instructions span.
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of label for the element
+ * @param string $value value of the element, default null
+ * @param string $instructions instructions for valid input
+ *
+ * @todo add a $name parameter
+ * @todo add a $maxLength parameter
+ * @todo add a $size parameter
+ *
+ * @return void
+ */
+
+ function input($id, $label, $value=null, $instructions=null)
+ {
+ $this->element('label', array('for' => $id), $label);
+ $attrs = array('name' => $id,
+ 'type' => 'text',
+ 'id' => $id);
+ if ($value) {
+ $attrs['value'] = htmlspecialchars($value);
+ }
+ $this->element('input', $attrs);
+ if ($instructions) {
+ $this->element('p', 'form_guide', $instructions);
+ }
+ }
+
+ /**
+ * output an HTML checkbox and associated elements
+ *
+ * Note that the value is default 'true' (the string), which can
+ * be used by Action::boolean()
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of label for the element
+ * @param string $checked if the box is checked, default false
+ * @param string $instructions instructions for valid input
+ * @param string $value value of the checkbox, default 'true'
+ * @param string $disabled show the checkbox disabled, default false
+ *
+ * @return void
+ *
+ * @todo add a $name parameter
+ */
+
+ function checkbox($id, $label, $checked=false, $instructions=null,
+ $value='true', $disabled=false)
+ {
+ $attrs = array('name' => $id,
+ 'type' => 'checkbox',
+ 'class' => 'checkbox',
+ 'id' => $id);
+ if ($value) {
+ $attrs['value'] = htmlspecialchars($value);
+ }
+ if ($checked) {
+ $attrs['checked'] = 'checked';
+ }
+ if ($disabled) {
+ $attrs['disabled'] = 'true';
+ }
+ $this->element('input', $attrs);
+ $this->text(' ');
+ $this->element('label', array('class' => 'checkbox',
+ 'for' => $id),
+ $label);
+ $this->text(' ');
+ if ($instructions) {
+ $this->element('p', 'form_guide', $instructions);
+ }
+ }
+
+ /**
+ * output an HTML combobox/select and associated elements
+ *
+ * $content is an array of key-value pairs for the dropdown, where
+ * the key is the option value attribute and the value is the option
+ * text. (Careful on the overuse of 'value' here.)
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of label for the element
+ * @param array $content options array, value => text
+ * @param string $instructions instructions for valid input
+ * @param string $blank_select whether to have a blank entry, default false
+ * @param string $selected selected value, default null
+ *
+ * @return void
+ *
+ * @todo add a $name parameter
+ */
+
+ function dropdown($id, $label, $content, $instructions=null,
+ $blank_select=false, $selected=null)
+ {
+ $this->element('label', array('for' => $id), $label);
+ $this->elementStart('select', array('id' => $id, 'name' => $id));
+ if ($blank_select) {
+ $this->element('option', array('value' => ''));
+ }
+ foreach ($content as $value => $option) {
+ if ($value == $selected) {
+ $this->element('option', array('value' => $value,
+ 'selected' => $value),
+ $option);
+ } else {
+ $this->element('option', array('value' => $value), $option);
+ }
+ }
+ $this->elementEnd('select');
+ if ($instructions) {
+ $this->element('p', 'form_guide', $instructions);
+ }
+ }
+
+ /**
+ * output an HTML hidden element
+ *
+ * $id is re-used as name
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $value hidden element value, default null
+ * @param string $name name, if different than ID
+ *
+ * @return void
+ */
+
+ function hidden($id, $value, $name=null)
+ {
+ $this->element('input', array('name' => ($name) ? $name : $id,
+ 'type' => 'hidden',
+ 'id' => $id,
+ 'value' => $value));
+ }
+
+ /**
+ * output an HTML password input and associated elements
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of label for the element
+ * @param string $instructions instructions for valid input
+ *
+ * @return void
+ *
+ * @todo add a $name parameter
+ */
+
+ function password($id, $label, $instructions=null)
+ {
+ $this->element('label', array('for' => $id), $label);
+ $attrs = array('name' => $id,
+ 'type' => 'password',
+ 'class' => 'password',
+ 'id' => $id);
+ $this->element('input', $attrs);
+ if ($instructions) {
+ $this->element('p', 'form_guide', $instructions);
+ }
+ }
+
+ /**
+ * output an HTML submit input and associated elements
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of the button
+ * @param string $cls class of the button, default 'submit'
+ * @param string $name name, if different than ID
+ *
+ * @return void
+ *
+ * @todo add a $name parameter
+ */
+
+ function submit($id, $label, $cls='submit', $name=null, $title=null)
+ {
+ $this->element('input', array('type' => 'submit',
+ 'id' => $id,
+ 'name' => ($name) ? $name : $id,
+ 'class' => $cls,
+ 'value' => $label,
+ 'title' => $title));
+ }
+
+ /**
+ * output an HTML textarea and associated elements
+ *
+ * @param string $id element ID, must be unique on page
+ * @param string $label text of label for the element
+ * @param string $content content of the textarea, default none
+ * @param string $instructions instructions for valid input
+ *
+ * @return void
+ *
+ * @todo add a $name parameter
+ * @todo add a $cols parameter
+ * @todo add a $rows parameter
+ */
+
+ function textarea($id, $label, $content=null, $instructions=null)
+ {
+ $this->element('label', array('for' => $id), $label);
+ $this->element('textarea', array('rows' => 3,
+ 'cols' => 40,
+ 'name' => $id,
+ 'id' => $id),
+ ($content) ? $content : '');
+ if ($instructions) {
+ $this->element('p', 'form_guide', $instructions);
+ }
+ }
+}
diff --git a/lib/imagefile.php b/lib/imagefile.php
new file mode 100644
index 000000000..7f1db892c
--- /dev/null
+++ b/lib/imagefile.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Abstraction for an image file
+ *
+ * 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 Image
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Zach Copley <zach@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);
+}
+
+/**
+ * A wrapper on uploaded files
+ *
+ * Makes it slightly easier to accept an image file from upload.
+ *
+ * @category Image
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @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 ImageFile
+{
+ var $filename = null;
+ var $barename = null;
+ var $type = null;
+ var $height = null;
+ var $width = null;
+
+ function __construct($filename=null, $type=null, $width=null, $height=null)
+ {
+ $this->filename = $filename;
+ $this->type = $type;
+ $this->width = $type;
+ $this->height = $type;
+ }
+
+ static function fromUpload($param='upload')
+ {
+ switch ($_FILES[$param]['error']) {
+ case UPLOAD_ERR_OK: // success, jump out
+ break;
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ throw new Exception(_('That file is too big.'));
+ return;
+ case UPLOAD_ERR_PARTIAL:
+ @unlink($_FILES[$param]['tmp_name']);
+ throw new Exception(_('Partial upload.'));
+ return;
+ default:
+ throw new Exception(_('System error uploading file.'));
+ return;
+ }
+
+ $imagefile = new ImageFile($_FILES[$param]['tmp_name']);
+ $info = @getimagesize($imagefile->filename);
+
+ if (!$info) {
+ @unlink($imagefile->filename);
+ throw new Exception(_('Not an image or corrupt file.'));
+ return;
+ }
+
+ $imagefile->width = $info[0];
+ $imagefile->height = $info[1];
+
+ switch ($info[2]) {
+ case IMAGETYPE_GIF:
+ case IMAGETYPE_JPEG:
+ case IMAGETYPE_PNG:
+ $imagefile->type = $info[2];
+ break;
+ default:
+ @unlink($imagefile->filename);
+ throw new Exception(_('Unsupported image file format.'));
+ return;
+ }
+
+ return $imagefile;
+ }
+
+ function unlink()
+ {
+ @unlink($this->filename);
+ }
+} \ No newline at end of file
diff --git a/lib/jabber.php b/lib/jabber.php
index ab0fd6af8..84d2a562c 100644
--- a/lib/jabber.php
+++ b/lib/jabber.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * utility functions for Jabber/GTalk/XMPP messages
+ *
+ * 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.
@@ -15,286 +18,471 @@
*
* 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 Network
+ * @package Laconica
+ * @author Evan Prodromou <evan@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); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
-require_once('XMPPHP/XMPP.php');
+require_once 'XMPPHP/XMPP.php';
-function jabber_valid_base_jid($jid) {
- # Cheap but effective
- return Validate::email($jid);
-}
+/**
+ * checks whether a string is a syntactically valid Jabber ID (JID)
+ *
+ * @param string $jid string to check
+ *
+ * @return boolean whether the string is a valid JID
+ */
-function jabber_normalize_jid($jid) {
- if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) {
- $node = $matches[1];
- $server = $matches[2];
- return strtolower($node.'@'.$server);
- } else {
- return NULL;
- }
+function jabber_valid_base_jid($jid)
+{
+ // Cheap but effective
+ return Validate::email($jid);
}
-function jabber_daemon_address() {
- return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
+/**
+ * normalizes a Jabber ID for comparison
+ *
+ * @param string $jid JID to check
+ *
+ * @return string an equivalent JID in normalized (lowercase) form
+ */
+
+function jabber_normalize_jid($jid)
+{
+ if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) {
+ $node = $matches[1];
+ $server = $matches[2];
+ return strtolower($node.'@'.$server);
+ } else {
+ return null;
+ }
}
-function jabber_connect($resource=NULL) {
- static $conn = NULL;
- if (!$conn) {
- $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
- common_config('xmpp', 'host') :
- common_config('xmpp', 'server'),
- common_config('xmpp', 'port'),
- common_config('xmpp', 'user'),
- common_config('xmpp', 'password'),
- ($resource) ? $resource :
- common_config('xmpp', 'resource'),
- common_config('xmpp', 'server'),
- common_config('xmpp', 'debug') ?
- true : false,
- common_config('xmpp', 'debug') ?
- XMPPHP_Log::LEVEL_VERBOSE : NULL
- );
-
- if (!$conn) {
- return false;
- }
-
- $conn->autoSubscribe();
- $conn->useEncryption(common_config('xmpp', 'encryption'));
-
- try {
- $conn->connect(true); # true = persistent connection
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERROR, $e->getMessage());
- return false;
- }
-
- $conn->processUntil('session_start');
- }
- return $conn;
+/**
+ * the JID of the Jabber daemon for this Laconica instance
+ *
+ * @return string JID of the Jabber daemon
+ */
+
+function jabber_daemon_address()
+{
+ return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
}
-function jabber_send_notice($to, $notice) {
- $conn = jabber_connect();
- if (!$conn) {
- return false;
- }
- $profile = Profile::staticGet($notice->profile_id);
- if (!$profile) {
- common_log(LOG_WARNING, 'Refusing to send notice with ' .
- 'unknown profile ' . common_log_objstring($notice),
- __FILE__);
- return false;
- }
- $msg = jabber_format_notice($profile, $notice);
- $entry = jabber_format_entry($profile, $notice);
- $conn->message($to, $msg, 'chat', NULL, $entry);
- $profile->free();
- return true;
+/**
+ * connect the configured Jabber account to the configured server
+ *
+ * @param string $resource Resource to connect (defaults to configured resource)
+ *
+ * @return XMPPHP connection to the configured server
+ */
+
+function jabber_connect($resource=null)
+{
+ static $conn = null;
+ if (!$conn) {
+ $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
+ common_config('xmpp', 'host') :
+ common_config('xmpp', 'server'),
+ common_config('xmpp', 'port'),
+ common_config('xmpp', 'user'),
+ common_config('xmpp', 'password'),
+ ($resource) ? $resource :
+ common_config('xmpp', 'resource'),
+ common_config('xmpp', 'server'),
+ common_config('xmpp', 'debug') ?
+ true : false,
+ common_config('xmpp', 'debug') ?
+ XMPPHP_Log::LEVEL_VERBOSE : null
+ );
+
+ if (!$conn) {
+ return false;
+ }
+
+ $conn->autoSubscribe();
+ $conn->useEncryption(common_config('xmpp', 'encryption'));
+
+ try {
+ $conn->connect(true); // true = persistent connection
+ } catch (XMPPHP_Exception $e) {
+ common_log(LOG_ERROR, $e->getMessage());
+ return false;
+ }
+
+ $conn->processUntil('session_start');
+ }
+ return $conn;
}
-# Extra stuff defined by Twitter, needed by twitter clients
-
-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);
- $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='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
- $entry .= "<author><name>" . $profile->nickname . "</name></author>\n";
- $entry .= "<icon>" . common_profile_avatar_url($profile, 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";
- $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";
-
- $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
- $address .= "<address type='replyto' jid='" . jabber_daemon_address() . "' />\n";
- $address .= "</addresses>\n";
-
- # FIXME: include a pubsub event, too.
-
- return $html . $entry . $address;
+/**
+ * send a single notice to a given Jabber address
+ *
+ * @param string $to JID to send the notice to
+ * @param Notice $notice notice to send
+ *
+ * @return boolean success value
+ */
+
+function jabber_send_notice($to, $notice)
+{
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $profile = Profile::staticGet($notice->profile_id);
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to send notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
+ $conn->message($to, $msg, 'chat', null, $entry);
+ $profile->free();
+ return true;
}
-function jabber_send_message($to, $body, $type='chat', $subject=NULL) {
- $conn = jabber_connect();
- if (!$conn) {
- return false;
- }
- $conn->message($to, $body, $type, $subject);
- return true;
+/**
+ * extra information for XMPP messages, as defined by Twitter
+ *
+ * @param Profile $profile Profile of the sending user
+ * @param Notice $notice Notice being sent
+ *
+ * @return string Extra information (Atom, HTML, addresses) in string format
+ */
+
+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>" . common_profile_avatar_url($profile, 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";
+ $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";
+
+ $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
+ $address .= "<address type='replyto' jid='" . jabber_daemon_address() . "' />\n";
+ $address .= "</addresses>\n";
+
+ // FIXME: include a pubsub event, too.
+
+ return $html . $entry . $address;
}
-function jabber_send_presence($status, $show='available', $to=NULL,
- $type = 'available', $priority=NULL)
+/**
+ * sends a single text message to a given JID
+ *
+ * @param string $to JID to send the message to
+ * @param string $body body of the message
+ * @param string $type type of the message
+ * @param string $subject subject of the message
+ *
+ * @return boolean success flag
+ */
+
+function jabber_send_message($to, $body, $type='chat', $subject=null)
{
- $conn = jabber_connect();
- if (!$conn) {
- return false;
- }
- $conn->presence($status, $show, $to, $type, $priority);
- return true;
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $conn->message($to, $body, $type, $subject);
+ return true;
}
-function jabber_confirm_address($code, $nickname, $address) {
- $body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' .
- 'has said that your Jabber ID belongs to them. ' .
- 'If that\'s true, you can confirm by clicking on this URL: ' .
- common_local_url('confirmaddress', array('code' => $code)) .
- ' . (If you cannot click it, copy-and-paste it into the ' .
- 'address bar of your browser). If that user isn\'t you, ' .
- 'or if you didn\'t request this confirmation, just ignore this message.';
+/**
+ * sends a presence stanza on the Jabber network
+ *
+ * @param string $status current status, free-form string
+ * @param string $show structured status value
+ * @param string $to recipient of presence, null for general
+ * @param string $type type of status message, related to $show
+ * @param int $priority priority of the presence
+ *
+ * @return boolean success value
+ */
- return jabber_send_message($address, $body);
+function jabber_send_presence($status, $show='available', $to=null,
+ $type = 'available', $priority=null)
+{
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $conn->presence($status, $show, $to, $type, $priority);
+ return true;
}
-function jabber_special_presence($type, $to=NULL, $show=NULL, $status=NULL) {
- $conn = jabber_connect();
-
- $to = htmlspecialchars($to);
- $status = htmlspecialchars($status);
- $out = "<presence";
- if($to) $out .= " to='$to'";
- if($type) $out .= " type='$type'";
- if($show == 'available' and !$status) {
- $out .= "/>";
- } else {
- $out .= ">";
- if($show && ($show != 'available')) $out .= "<show>$show</show>";
- if($status) $out .= "<status>$status</status>";
- $out .= "</presence>";
- }
- $conn->send($out);
+/**
+ * sends a confirmation request to a JID
+ *
+ * @param string $code confirmation code for confirmation URL
+ * @param string $nickname nickname of confirming user
+ * @param string $address JID to send confirmation to
+ *
+ * @return boolean success flag
+ */
+
+function jabber_confirm_address($code, $nickname, $address)
+{
+ $body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' .
+ 'has said that your Jabber ID belongs to them. ' .
+ 'If that\'s true, you can confirm by clicking on this URL: ' .
+ common_local_url('confirmaddress', array('code' => $code)) .
+ ' . (If you cannot click it, copy-and-paste it into the ' .
+ 'address bar of your browser). If that user isn\'t you, ' .
+ 'or if you didn\'t request this confirmation, just ignore this message.';
+
+ return jabber_send_message($address, $body);
}
-function jabber_broadcast_notice($notice) {
-
- if (!common_config('xmpp', 'enabled')) {
- return true;
- }
- $profile = Profile::staticGet($notice->profile_id);
-
- if (!$profile) {
- common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
- 'unknown profile ' . common_log_objstring($notice),
- __FILE__);
- return false;
- }
-
- $msg = jabber_format_notice($profile, $notice);
- $entry = jabber_format_entry($profile, $notice);
-
- $profile->free();
- unset($profile);
-
- $sent_to = array();
- $conn = jabber_connect();
-
- # First, get users to whom this is a direct reply
- $user = new User();
- $user->query('SELECT user.id, user.jabber ' .
- 'FROM user JOIN reply ON user.id = reply.profile_id ' .
- 'WHERE reply.notice_id = ' . $notice->id . ' ' .
- 'AND user.jabber is not null ' .
- 'AND user.jabbernotify = 1 ' .
- 'AND user.jabberreplies = 1 ');
-
- while ($user->fetch()) {
- common_log(LOG_INFO,
- 'Sending reply notice ' . $notice->id . ' to ' . $user->jabber,
- __FILE__);
- $conn->message($user->jabber, $msg, 'chat', NULL, $entry);
- $conn->processTime(0);
- $sent_to[$user->id] = 1;
- }
-
- $user->free();
-
- # Now, get users subscribed to this profile
-
- $user = new User();
- $user->query('SELECT user.id, user.jabber ' .
- 'FROM user JOIN subscription ON user.id = subscription.subscriber ' .
- 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
- 'AND user.jabber is not null ' .
- 'AND user.jabbernotify = 1 ' .
- 'AND subscription.jabber = 1 ');
+/**
+ * sends a "special" presence stanza on the Jabber network
+ *
+ * @param string $type Type of presence
+ * @param string $to JID to send presence to
+ * @param string $show show value for presence
+ * @param string $status status value for presence
+ *
+ * @return boolean success flag
+ *
+ * @see jabber_send_presence()
+ */
- while ($user->fetch()) {
- if (!array_key_exists($user->id, $sent_to)) {
- common_log(LOG_INFO,
- 'Sending notice ' . $notice->id . ' to ' . $user->jabber,
- __FILE__);
- $conn->message($user->jabber, $msg, 'chat', NULL, $entry);
- # To keep the incoming queue from filling up, we service it after each send.
- $conn->processTime(0);
- }
- }
+function jabber_special_presence($type, $to=null, $show=null, $status=null)
+{
+ // FIXME: why use this instead of jabber_send_presence()?
+ $conn = jabber_connect();
+
+ $to = htmlspecialchars($to);
+ $status = htmlspecialchars($status);
+
+ $out = "<presence";
+ if ($to) {
+ $out .= " to='$to'";
+ }
+ if ($type) {
+ $out .= " type='$type'";
+ }
+ if ($show == 'available' and !$status) {
+ $out .= "/>";
+ } else {
+ $out .= ">";
+ if ($show && ($show != 'available')) {
+ $out .= "<show>$show</show>";
+ }
+ if ($status) {
+ $out .= "<status>$status</status>";
+ }
+ $out .= "</presence>";
+ }
+ $conn->send($out);
+}
- $user->free();
+/**
+ * broadcast a notice to all subscribers and reply recipients
+ *
+ * This function will send a notice to all subscribers on the local server
+ * who have Jabber addresses, and have Jabber notification enabled, and
+ * have this subscription enabled for Jabber. It also sends the notice to
+ * all recipients of @-replies who have Jabber addresses and Jabber notification
+ * enabled. This is really the heart of Jabber distribution in Laconica.
+ *
+ * @param Notice $notice The notice to broadcast
+ *
+ * @return boolean success flag
+ */
- return true;
+function jabber_broadcast_notice($notice)
+{
+ if (!common_config('xmpp', 'enabled')) {
+ return true;
+ }
+ $profile = Profile::staticGet($notice->profile_id);
+
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
+
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
+
+ $profile->free();
+ unset($profile);
+
+ $sent_to = array();
+
+ $conn = jabber_connect();
+
+ // First, get users to whom this is a direct reply
+ $user = new User();
+ $user->query('SELECT user.id, user.jabber ' .
+ 'FROM user JOIN reply ON user.id = reply.profile_id ' .
+ 'WHERE reply.notice_id = ' . $notice->id . ' ' .
+ 'AND user.jabber is not null ' .
+ 'AND user.jabbernotify = 1 ' .
+ 'AND user.jabberreplies = 1 ');
+
+ while ($user->fetch()) {
+ common_log(LOG_INFO,
+ 'Sending reply notice ' . $notice->id . ' to ' . $user->jabber,
+ __FILE__);
+ $conn->message($user->jabber, $msg, 'chat', null, $entry);
+ $conn->processTime(0);
+ $sent_to[$user->id] = 1;
+ }
+
+ $user->free();
+
+ // Now, get users subscribed to this profile
+
+ $user = new User();
+ $user->query('SELECT user.id, user.jabber ' .
+ 'FROM user JOIN subscription ' .
+ 'ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+ 'AND user.jabber is not null ' .
+ 'AND user.jabbernotify = 1 ' .
+ 'AND subscription.jabber = 1 ');
+
+ while ($user->fetch()) {
+ if (!array_key_exists($user->id, $sent_to)) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user->jabber,
+ __FILE__);
+ $conn->message($user->jabber, $msg, 'chat', null, $entry);
+ // To keep the incoming queue from filling up,
+ // we service it after each send.
+ $conn->processTime(0);
+ $sent_to[$user->id] = 1;
+ }
+ }
+
+ // Now, get users who have it in their inbox because of groups
+
+ $user = new User();
+ $user->query('SELECT user.id, user.jabber ' .
+ 'FROM user JOIN notice_inbox ' .
+ 'ON user.id = notice_inbox.user_id ' .
+ 'WHERE notice_inbox.notice_id = ' . $notice->id . ' ' .
+ 'AND notice_inbox.source = 2 ');
+
+ while ($user->fetch()) {
+ if (!array_key_exists($user->id, $sent_to)) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user->jabber,
+ __FILE__);
+ $conn->message($user->jabber, $msg, 'chat', null, $entry);
+ // To keep the incoming queue from filling up,
+ // we service it after each send.
+ $conn->processTime(0);
+ $sent_to[$user->id] = 1;
+ }
+ }
+
+ $user->free();
+
+ return true;
}
-function jabber_public_notice($notice) {
+/**
+ * send a notice to all public listeners
+ *
+ * For notices that are generated on the local system (by users), we can optionally
+ * forward them to remote listeners by XMPP.
+ *
+ * @param Notice $notice notice to broadcast
+ *
+ * @return boolean success flag
+ */
- # Now, users who want everything
+function jabber_public_notice($notice)
+{
+ // Now, users who want everything
- $public = common_config('xmpp', 'public');
+ $public = common_config('xmpp', 'public');
- # FIXME PRIV don't send out private messages here
- # XXX: should we send out non-local messages if public,localonly
- # = false? I think not
+ // FIXME PRIV don't send out private messages here
+ // XXX: should we send out non-local messages if public,localonly
+ // = false? I think not
- if ($public && $notice->is_local) {
- $profile = Profile::staticGet($notice->profile_id);
+ if ($public && $notice->is_local) {
+ $profile = Profile::staticGet($notice->profile_id);
- if (!$profile) {
- common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
- 'unknown profile ' . common_log_objstring($notice),
- __FILE__);
- return false;
- }
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
- $msg = jabber_format_notice($profile, $notice);
- $entry = jabber_format_entry($profile, $notice);
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
- $conn = jabber_connect();
+ $conn = jabber_connect();
- foreach ($public as $address) {
- common_log(LOG_INFO,
- 'Sending notice ' . $notice->id . ' to public listener ' . $address,
- __FILE__);
- $conn->message($address, $msg, 'chat', NULL, $entry);
- $conn->processTime(0);
- }
- $profile->free();
- }
+ foreach ($public as $address) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id .
+ ' to public listener ' . $address,
+ __FILE__);
+ $conn->message($address, $msg, 'chat', null, $entry);
+ $conn->processTime(0);
+ }
+ $profile->free();
+ }
- return true;
+ return true;
}
-function jabber_format_notice(&$profile, &$notice) {
- return $profile->nickname . ': ' . $notice->content;
+/**
+ * makes a plain-text formatted version of a notice, suitable for Jabber distribution
+ *
+ * @param Profile &$profile profile of the sending user
+ * @param Notice &$notice notice being sent
+ *
+ * @return string plain-text version of the notice, with user nickname prefixed
+ */
+
+function jabber_format_notice(&$profile, &$notice)
+{
+ return $profile->nickname . ': ' . $notice->content;
}
diff --git a/lib/joinform.php b/lib/joinform.php
new file mode 100644
index 000000000..1edb2f72d
--- /dev/null
+++ b/lib/joinform.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for joining a group
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for joining a group
+ *
+ * @category Form
+ * @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/
+ *
+ * @see UnsubscribeForm
+ */
+
+class JoinForm extends Form
+{
+ /**
+ * group for user to join
+ */
+
+ var $group = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param group $group group to join
+ */
+
+ function __construct($out=null, $group=null)
+ {
+ parent::__construct($out);
+
+ $this->group = $group;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return string ID of the form
+ */
+
+ function id()
+ {
+ return 'group-join-' . $this->group->id;
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_group_join';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('joingroup',
+ array('nickname' => $this->group->nickname));
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Join'));
+ }
+}
diff --git a/lib/language.php b/lib/language.php
index f474c4999..a73b73f28 100644
--- a/lib/language.php
+++ b/lib/language.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * utility functions for i18n
+ *
+ * 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.
@@ -15,49 +18,87 @@
*
* 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 I18n
+ * @package Laconica
+ * @author Matthew Gregg <matthew.gregg@gmail.com>
+ * @author Ciaran Gultnieks <ciaran@ciarang.com>
+ * @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/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Content negotiation for language codes
+ *
+ * @param string $httplang HTTP Accept-Language header
+ *
+ * @return string language code for best language match
*/
-if (!defined('LACONICA')) { exit(1); }
+function client_prefered_language($httplang)
+{
+ $client_langs = array();
-function client_prefered_language($httplang) {
- $client_langs = array();
- $all_languages = common_config('site','languages');
+ $all_languages = common_config('site', 'languages');
- preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',strtolower($httplang),$httplang);
- for ($i = 0; $i < count($httplang); $i++) {
- if(!empty($httplang[2][$i])) {
- #if no q default to 1.0
- $client_langs[$httplang[2][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
- }
- if(!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
- #if a catchall default 0.01 lower
- $client_langs[$httplang[3][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i]-0.01 : 0.99);
- }
- }
- #sort in decending q
- arsort($client_langs);
+ preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',
+ strtolower($httplang), $httplang);
- foreach ($client_langs as $lang => $q) {
- if (isset($all_languages[$lang])) {
- return($all_languages[$lang]['lang']);
- }
- }
- return FALSE;
-}
+ for ($i = 0; $i < count($httplang); $i++) {
+ if (!empty($httplang[2][$i])) {
+ // if no q default to 1.0
+ $client_langs[$httplang[2][$i]] =
+ ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
+ }
+ if (!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
+ // if a catchall default 0.01 lower
+ $client_langs[$httplang[3][$i]] =
+ ($httplang[6][$i]? (float) $httplang[6][$i]-0.01 : 0.99);
+ }
+ }
+ // sort in decending q
+ arsort($client_langs);
-function get_nice_language_list() {
- $nice_lang = array();
- $all_languages = common_config('site','languages');
- foreach ($all_languages as $lang) {
- $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
+ foreach ($client_langs as $lang => $q) {
+ if (isset($all_languages[$lang])) {
+ return($all_languages[$lang]['lang']);
}
- return $nice_lang;
+ }
+ return false;
}
-// 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
-// common_config('site','languages')
+/**
+ * returns a simple code -> name mapping for languages
+ *
+ * @return array map of available languages by code to language name.
+ */
+
+function get_nice_language_list()
+{
+ $nice_lang = array();
+
+ $all_languages = common_config('site', 'languages');
+
+ foreach ($all_languages as $lang) {
+ $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
+ }
+ return $nice_lang;
+}
+
+/**
+ * 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
+ * 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'),
@@ -90,4 +131,3 @@ function get_all_languages() {
'zh-hant' => array('q' => 0.2, 'lang' => 'zh_hant', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'),
);
}
-
diff --git a/lib/leaveform.php b/lib/leaveform.php
new file mode 100644
index 000000000..696559a25
--- /dev/null
+++ b/lib/leaveform.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for leaving a group
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for leaving a group
+ *
+ * @category Form
+ * @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/
+ *
+ * @see UnsubscribeForm
+ */
+
+class LeaveForm extends Form
+{
+ /**
+ * group for user to leave
+ */
+
+ var $group = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param group $group group to leave
+ */
+
+ function __construct($out=null, $group=null)
+ {
+ parent::__construct($out);
+
+ $this->group = $group;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return string ID of the form
+ */
+
+ function id()
+ {
+ return 'group-leave-' . $this->group->id;
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_group_leave';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('leavegroup',
+ array('nickname' => $this->group->nickname));
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Leave'));
+ }
+}
diff --git a/lib/logingroupnav.php b/lib/logingroupnav.php
new file mode 100644
index 000000000..8c03eccea
--- /dev/null
+++ b/lib/logingroupnav.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Menu for login group of 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 Menu
+ * @package Laconica
+ * @author Evan Prodromou <evan@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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Menu for login group of actions
+ *
+ * @category Output
+ * @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 Widget
+ */
+
+class LoginGroupNav extends Widget
+{
+ var $action = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ 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')),
+ 'openid' =>
+ array(_('OpenID'),
+ _('Login or register with OpenID')));
+
+ $action_name = $this->action->trimmed('action');
+ $this->action->elementStart('ul', array('class' => 'nav'));
+
+ foreach ($menu as $menuaction => $menudesc) {
+ $this->action->menuItem(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action_name === $menuaction);
+ }
+
+ $this->action->elementEnd('ul');
+ }
+}
diff --git a/lib/mail.php b/lib/mail.php
index a7cbab858..5638ae9bf 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * utilities for sending email
+ *
+ * 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.
@@ -15,295 +18,557 @@
*
* 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 Mail
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @author Robin Millette <millette@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); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
-require_once('Mail.php');
+require_once 'Mail.php';
-function mail_backend() {
- static $backend = NULL;
+/**
+ * return the configured mail backend
+ *
+ * Uses the $config array to make a mail backend. Cached so it is safe to call
+ * more than once.
+ *
+ * @return Mail backend
+ */
- if (!$backend) {
- global $config;
- $backend = Mail::factory($config['mail']['backend'],
- ($config['mail']['params']) ? $config['mail']['params'] : array());
- if (PEAR::isError($backend)) {
- common_server_error($backend->getMessage(), 500);
- }
- }
- return $backend;
+function mail_backend()
+{
+ static $backend = null;
+
+ if (!$backend) {
+ global $config;
+ $backend = Mail::factory($config['mail']['backend'],
+ ($config['mail']['params']) ?
+ $config['mail']['params'] :
+ array());
+ if (PEAR::isError($backend)) {
+ common_server_error($backend->getMessage(), 500);
+ }
+ }
+ return $backend;
}
-# XXX: use Mail_Queue... maybe
+/**
+ * send an email to one or more recipients
+ *
+ * @param array $recipients array of strings with email addresses of recipients
+ * @param array $headers array mapping strings to strings for email headers
+ * @param string $body body of the email
+ *
+ * @return boolean success flag
+ */
-function mail_send($recipients, $headers, $body) {
- $backend = mail_backend();
+function mail_send($recipients, $headers, $body)
+{
+ // XXX: use Mail_Queue... maybe
+ $backend = mail_backend();
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'text/plain; charset=UTF-8';
}
- assert($backend); # throws an error if it's bad
- $sent = $backend->send($recipients, $headers, $body);
- if (PEAR::isError($sent)) {
- common_log(LOG_ERR, 'Email error: ' . $sent->getMessage());
- return false;
- }
- return true;
+ assert($backend); // throws an error if it's bad
+ $sent = $backend->send($recipients, $headers, $body);
+ if (PEAR::isError($sent)) {
+ common_log(LOG_ERR, 'Email error: ' . $sent->getMessage());
+ return false;
+ }
+ return true;
}
-function mail_domain() {
- $maildomain = common_config('mail', 'domain');
- if (!$maildomain) {
- $maildomain = common_config('site', 'server');
- }
- return $maildomain;
-}
+/**
+ * returns the configured mail domain
+ *
+ * Defaults to the server name.
+ *
+ * @return string mail domain, suitable for making email addresses.
+ */
-function mail_notify_from() {
- $notifyfrom = common_config('mail', 'notifyfrom');
- if (!$notifyfrom) {
- $domain = mail_domain();
- $notifyfrom = common_config('site', 'name') .' <noreply@'.$domain.'>';
- }
- return $notifyfrom;
+function mail_domain()
+{
+ $maildomain = common_config('mail', 'domain');
+ if (!$maildomain) {
+ $maildomain = common_config('site', 'server');
+ }
+ return $maildomain;
}
-function mail_to_user(&$user, $subject, $body, $address=NULL) {
- if (!$address) {
- $address = $user->email;
- }
+/**
+ * returns a good address for sending email from this server
+ *
+ * Uses either the configured value or a faked-up value made
+ * from the mail domain.
+ *
+ * @return string notify from address
+ */
- $recipients = $address;
- $profile = $user->getProfile();
+function mail_notify_from()
+{
+ $notifyfrom = common_config('mail', 'notifyfrom');
- $headers['From'] = mail_notify_from();
- $headers['To'] = $profile->getBestName() . ' <' . $address . '>';
- $headers['Subject'] = $subject;
+ if (!$notifyfrom) {
- return mail_send($recipients, $headers, $body);
+ $domain = mail_domain();
+
+ $notifyfrom = common_config('site', 'name') .' <noreply@'.$domain.'>';
+ }
+
+ return $notifyfrom;
}
-# For confirming a Jabber address
+/**
+ * sends email to a user
+ *
+ * @param User &$user user to send email to
+ * @param string $subject subject of the email
+ * @param string $body body of the email
+ * @param string $address optional specification of email address
+ *
+ * @return boolean success flag
+ */
-function mail_confirm_address($user, $code, $nickname, $address) {
+function mail_to_user(&$user, $subject, $body, $address=null)
+{
+ if (!$address) {
+ $address = $user->email;
+ }
+
+ $recipients = $address;
+ $profile = $user->getProfile();
- $subject = _('Email address confirmation');
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $profile->getBestName() . ' <' . $address . '>';
+ $headers['Subject'] = $subject;
- $body = sprintf(_("Hey, %s.\n\nSomeone just entered this email address on %s.\n\n" .
- "If it was you, and you want to confirm your entry, use the URL below:\n\n\t%s\n\n" .
- "If not, just ignore this message.\n\nThanks for your time, \n%s\n")
- , $nickname, common_config('site', 'name')
- , common_local_url('confirmaddress', array('code' => $code)), common_config('site', 'name'));
- return mail_to_user($user, $subject, $body, $address);
+ return mail_send($recipients, $headers, $body);
}
-function mail_subscribe_notify($listenee, $listener) {
- $other = $listener->getProfile();
- mail_subscribe_notify_profile($listenee, $other);
+/**
+ * Send an email to confirm a user's control of an email address
+ *
+ * @param User $user User claiming the email address
+ * @param string $code Confirmation code
+ * @param string $nickname Nickname of user
+ * @param string $address email address to confirm
+ *
+ * @see common_confirmation_code()
+ *
+ * @return success flag
+ */
+
+function mail_confirm_address($user, $code, $nickname, $address)
+{
+ $subject = _('Email address confirmation');
+
+ $body = sprintf(_("Hey, %s.\n\n".
+ "Someone just entered this email address on %s.\n\n" .
+ "If it was you, and you want to confirm your entry, ".
+ "use the URL below:\n\n\t%s\n\n" .
+ "If not, just ignore this message.\n\n".
+ "Thanks for your time, \n%s\n"),
+ $nickname, common_config('site', 'name'),
+ common_local_url('confirmaddress', array('code' => $code)),
+ common_config('site', 'name'));
+ return mail_to_user($user, $subject, $body, $address);
}
-function mail_subscribe_notify_profile($listenee, $other) {
- if ($listenee->email && $listenee->emailnotifysub) {
- // use the recipients localization
+/**
+ * notify a user of subscription by another user
+ *
+ * This is just a wrapper around the profile-based version.
+ *
+ * @param User $listenee user who is being subscribed to
+ * @param User $listener user who is subscribing
+ *
+ * @see mail_subscribe_notify_profile()
+ *
+ * @return void
+ */
+
+function mail_subscribe_notify($listenee, $listener)
+{
+ $other = $listener->getProfile();
+ mail_subscribe_notify_profile($listenee, $other);
+}
+
+/**
+ * notify a user of subscription by a profile (remote or local)
+ *
+ * This function checks to see if the listenee has an email
+ * address and wants subscription notices.
+ *
+ * @param User $listenee user who's being subscribed to
+ * @param Profile $other profile of person who's listening
+ *
+ * @return void
+ */
+
+function mail_subscribe_notify_profile($listenee, $other)
+{
+ if ($listenee->email && $listenee->emailnotifysub) {
+
+ // use the recipient's localization
common_init_locale($listenee->language);
- $profile = $listenee->getProfile();
- $name = $profile->getBestName();
- $long_name = ($other->fullname) ? ($other->fullname . ' (' . $other->nickname . ')') : $other->nickname;
- $recipients = $listenee->email;
- $headers['From'] = mail_notify_from();
- $headers['To'] = $name . ' <' . $listenee->email . '>';
- $headers['Subject'] = sprintf(_('%1$s is now listening to your notices on %2$s.'), $other->getBestName(),
- common_config('site', 'name'));
- $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n".
- "\t".'%3$s'."\n\n".
- 'Faithfully yours,'."\n".'%4$s.'."\n"),
- $long_name,
- common_config('site', 'name'),
- $other->profileurl,
- common_config('site', 'name'));
+
+ $profile = $listenee->getProfile();
+
+ $name = $profile->getBestName();
+
+ $long_name = ($other->fullname) ?
+ ($other->fullname . ' (' . $other->nickname . ')') : $other->nickname;
+
+ $recipients = $listenee->email;
+
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $name . ' <' . $listenee->email . '>';
+ $headers['Subject'] = sprintf(_('%1$s is now listening to '.
+ 'your notices on %2$s.'),
+ $other->getBestName(),
+ common_config('site', 'name'));
+
+ $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n".
+ "\t".'%3$s'."\n\n".
+ '%4$s'.
+ '%5$s'.
+ '%6$s'.
+ "\n".'Faithfully yours,'."\n".'%7$s.'."\n\n".
+ "----\n".
+ "Change your email address or ".
+ "notification options at %8$s\n"),
+ $long_name,
+ common_config('site', 'name'),
+ $other->profileurl,
+ ($other->location) ?
+ sprintf(_("Location: %s\n"), $other->location) : '',
+ ($other->homepage) ?
+ sprintf(_("Homepage: %s\n"), $other->homepage) : '',
+ ($other->bio) ?
+ sprintf(_("Bio: %s\n\n"), $other->bio) : '',
+ common_config('site', 'name'),
+ common_local_url('emailsettings'));
// reset localization
common_init_locale();
- mail_send($recipients, $headers, $body);
- }
+ mail_send($recipients, $headers, $body);
+ }
}
-function mail_new_incoming_notify($user) {
+/**
+ * notify a user of their new incoming email address
+ *
+ * User's email and incoming fields should already be updated.
+ *
+ * @param User $user user with the new address
+ *
+ * @return void
+ */
- $profile = $user->getProfile();
- $name = $profile->getBestName();
+function mail_new_incoming_notify($user)
+{
+ $profile = $user->getProfile();
- $headers['From'] = $user->incomingemail;
- $headers['To'] = $name . ' <' . $user->email . '>';
- $headers['Subject'] = sprintf(_('New email address for posting to %s'),
- common_config('site', 'name'));
+ $name = $profile->getBestName();
- $body = sprintf(_("You have a new posting address on %1\$s.\n\n".
- "Send email to %2\$s to post new messages.\n\n".
- "More email instructions at %3\$s.\n\n".
- "Faithfully yours,\n%4\$s"),
- common_config('site', 'name'),
- $user->incomingemail,
- common_local_url('doc', array('title' => 'email')),
- common_config('site', 'name'));
+ $headers['From'] = $user->incomingemail;
+ $headers['To'] = $name . ' <' . $user->email . '>';
+ $headers['Subject'] = sprintf(_('New email address for posting to %s'),
+ common_config('site', 'name'));
- mail_send($user->email, $headers, $body);
+ $body = sprintf(_("You have a new posting address on %1\$s.\n\n".
+ "Send email to %2\$s to post new messages.\n\n".
+ "More email instructions at %3\$s.\n\n".
+ "Faithfully yours,\n%4\$s"),
+ common_config('site', 'name'),
+ $user->incomingemail,
+ common_local_url('doc', array('title' => 'email')),
+ common_config('site', 'name'));
+
+ mail_send($user->email, $headers, $body);
}
-function mail_new_incoming_address() {
- $prefix = common_confirmation_code(64);
- $suffix = mail_domain();
- return $prefix . '@' . $suffix;
+/**
+ * generate a new address for incoming messages
+ *
+ * @todo check the database for uniqueness
+ *
+ * @return string new email address for incoming messages
+ */
+
+function mail_new_incoming_address()
+{
+ $prefix = common_confirmation_code(64);
+ $suffix = mail_domain();
+ return $prefix . '@' . $suffix;
}
-function mail_broadcast_notice_sms($notice) {
+/**
+ * broadcast a notice to all subscribers with SMS notification on
+ *
+ * This function sends SMS messages to all users who have sms addresses;
+ * have sms notification on; and have sms enabled for this particular
+ * subscription.
+ *
+ * @param Notice $notice The notice to broadcast
+ *
+ * @return success flag
+ */
- # Now, get users subscribed to this profile
+function mail_broadcast_notice_sms($notice)
+{
+ // Now, get users subscribed to this profile
- $user = new User();
+ $user = new User();
- $user->query('SELECT nickname, smsemail, incomingemail ' .
- 'FROM user JOIN subscription ' .
- 'ON user.id = subscription.subscriber ' .
- 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
- 'AND user.smsemail IS NOT NULL ' .
- 'AND user.smsnotify = 1 ' .
+ $user->query('SELECT nickname, smsemail, incomingemail ' .
+ 'FROM user JOIN subscription ' .
+ 'ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+ 'AND user.smsemail IS NOT null ' .
+ 'AND user.smsnotify = 1 ' .
'AND subscription.sms = 1 ');
- while ($user->fetch()) {
- common_log(LOG_INFO,
- 'Sending notice ' . $notice->id . ' to ' . $user->smsemail,
- __FILE__);
- $success = mail_send_sms_notice_address($notice, $user->smsemail, $user->incomingemail);
- if (!$success) {
- # XXX: Not sure, but I think that's the right thing to do
- common_log(LOG_WARNING,
- 'Sending notice ' . $notice->id . ' to ' . $user->smsemail . ' FAILED, cancelling.',
- __FILE__);
- return false;
- }
- }
-
- $user->free();
- unset($user);
-
- return true;
+ while ($user->fetch()) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user->smsemail,
+ __FILE__);
+ $success = mail_send_sms_notice_address($notice,
+ $user->smsemail,
+ $user->incomingemail);
+ if (!$success) {
+ // XXX: Not sure, but I think that's the right thing to do
+ common_log(LOG_WARNING,
+ 'Sending notice ' . $notice->id . ' to ' .
+ $user->smsemail . ' FAILED, cancelling.',
+ __FILE__);
+ return false;
+ }
+ }
+
+ $user->free();
+ unset($user);
+
+ return true;
}
-function mail_send_sms_notice($notice, $user) {
- return mail_send_sms_notice_address($notice, $user->smsemail, $user->incomingemail);
+/**
+ * send a notice to a user via SMS
+ *
+ * A convenience wrapper around mail_send_sms_notice_address()
+ *
+ * @param Notice $notice notice to send
+ * @param User $user user to receive notice
+ *
+ * @see mail_send_sms_notice_address()
+ *
+ * @return boolean success flag
+ */
+
+function mail_send_sms_notice($notice, $user)
+{
+ return mail_send_sms_notice_address($notice,
+ $user->smsemail,
+ $user->incomingemail);
}
-function mail_send_sms_notice_address($notice, $smsemail, $incomingemail) {
+/**
+ * send a notice to an SMS email address from a given address
+ *
+ * We use the user's incoming email address as the "From" address to make
+ * replying to notices easier.
+ *
+ * @param Notice $notice notice to send
+ * @param string $smsemail email address to send to
+ * @param string $incomingemail email address to set as 'from'
+ *
+ * @return boolean success flag
+ */
+
+function mail_send_sms_notice_address($notice, $smsemail, $incomingemail)
+{
+ $to = $nickname . ' <' . $smsemail . '>';
- $to = $nickname . ' <' . $smsemail . '>';
- $other = $notice->getProfile();
+ $other = $notice->getProfile();
- common_log(LOG_INFO, "Sending notice " . $notice->id . " to " . $smsemail, __FILE__);
+ common_log(LOG_INFO, 'Sending notice ' . $notice->id .
+ ' to ' . $smsemail, __FILE__);
- $headers = array();
- $headers['From'] = (isset($incomingemail)) ? $incomingemail : mail_notify_from();
- $headers['To'] = $to;
- $headers['Subject'] = sprintf(_('%s status'),
- $other->getBestName());
- $body = $notice->content;
+ $headers = array();
- return mail_send($smsemail, $headers, $body);
+ $headers['From'] = ($incomingemail) ? $incomingemail : mail_notify_from();
+ $headers['To'] = $to;
+ $headers['Subject'] = sprintf(_('%s status'),
+ $other->getBestName());
+
+ $body = $notice->content;
+
+ return mail_send($smsemail, $headers, $body);
}
-function mail_confirm_sms($code, $nickname, $address) {
+/**
+ * send a message to confirm a claim for an SMS number
+ *
+ * @param string $code confirmation code
+ * @param string $nickname nickname of user claiming number
+ * @param string $address email address to send the confirmation to
+ *
+ * @see common_confirmation_code()
+ *
+ * @return void
+ */
+
+function mail_confirm_sms($code, $nickname, $address)
+{
+ $recipients = $address;
- $recipients = $address;
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $nickname . ' <' . $address . '>';
+ $headers['Subject'] = _('SMS confirmation');
- $headers['From'] = mail_notify_from();
- $headers['To'] = $nickname . ' <' . $address . '>';
- $headers['Subject'] = _('SMS confirmation');
+ // FIXME: I18N
- $body = "$nickname: confirm you own this phone number with this code:";
- $body .= "\n\n";
- $body .= $code;
- $body .= "\n\n";
+ $body = "$nickname: confirm you own this phone number with this code:";
+ $body .= "\n\n";
+ $body .= $code;
+ $body .= "\n\n";
- mail_send($recipients, $headers, $body);
+ mail_send($recipients, $headers, $body);
}
-function mail_notify_nudge($from, $to) {
+/**
+ * send a mail message to notify a user of a 'nudge'
+ *
+ * @param User $from user nudging
+ * @param User $to user being nudged
+ *
+ * @return boolean success flag
+ */
+
+function mail_notify_nudge($from, $to)
+{
common_init_locale($to->language);
- $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname);
-
- $from_profile = $from->getProfile();
-
- $body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to these days and is inviting you to post some news.\n\n".
- "So let's hear from you :)\n\n".
- "%3\$s\n\n".
- "Don't reply to this email; it won't get to them.\n\n".
- "With kind regards,\n".
- "%4\$s\n"),
- $from_profile->getBestName(),
- $from->nickname,
- common_local_url('all', array('nickname' => $to->nickname)),
- common_config('site', 'name'));
+ $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname);
+
+ $from_profile = $from->getProfile();
+
+ $body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to ".
+ "these days and is inviting you to post some news.\n\n".
+ "So let's hear from you :)\n\n".
+ "%3\$s\n\n".
+ "Don't reply to this email; it won't get to them.\n\n".
+ "With kind regards,\n".
+ "%4\$s\n"),
+ $from_profile->getBestName(),
+ $from->nickname,
+ common_local_url('all', array('nickname' => $to->nickname)),
+ common_config('site', 'name'));
common_init_locale();
- return mail_to_user($to, $subject, $body);
+ return mail_to_user($to, $subject, $body);
}
-function mail_notify_message($message, $from=NULL, $to=NULL) {
+/**
+ * send a message to notify a user of a direct message (DM)
+ *
+ * This function checks to see if the recipient wants notification
+ * of DMs and has a configured email address.
+ *
+ * @param Message $message message to notify about
+ * @param User $from user sending message; default to sender
+ * @param User $to user receiving message; default to recipient
+ *
+ * @return boolean success code
+ */
- if (is_null($from)) {
- $from = User::staticGet('id', $message->from_profile);
- }
+function mail_notify_message($message, $from=null, $to=null)
+{
+ if (is_null($from)) {
+ $from = User::staticGet('id', $message->from_profile);
+ }
- if (is_null($to)) {
- $to = User::staticGet('id', $message->to_profile);
- }
+ if (is_null($to)) {
+ $to = User::staticGet('id', $message->to_profile);
+ }
- if (is_null($to->email) || !$to->emailnotifymsg) {
- return true;
- }
+ if (is_null($to->email) || !$to->emailnotifymsg) {
+ return true;
+ }
common_init_locale($to->language);
- $subject = sprintf(_('New private message from %s'), $from->nickname);
-
- $from_profile = $from->getProfile();
-
- $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
- "------------------------------------------------------\n".
- "%3\$s\n".
- "------------------------------------------------------\n\n".
- "You can reply to their message here:\n\n".
- "%4\$s\n\n".
- "Don't reply to this email; it won't get to them.\n\n".
- "With kind regards,\n".
- "%5\$s\n"),
- $from_profile->getBestName(),
- $from->nickname,
- $message->content,
- common_local_url('newmessage', array('to' => $from->id)),
- common_config('site', 'name'));
+ $subject = sprintf(_('New private message from %s'), $from->nickname);
+
+ $from_profile = $from->getProfile();
+
+ $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
+ "------------------------------------------------------\n".
+ "%3\$s\n".
+ "------------------------------------------------------\n\n".
+ "You can reply to their message here:\n\n".
+ "%4\$s\n\n".
+ "Don't reply to this email; it won't get to them.\n\n".
+ "With kind regards,\n".
+ "%5\$s\n"),
+ $from_profile->getBestName(),
+ $from->nickname,
+ $message->content,
+ common_local_url('newmessage', array('to' => $from->id)),
+ common_config('site', 'name'));
common_init_locale();
- return mail_to_user($to, $subject, $body);
+ return mail_to_user($to, $subject, $body);
}
-function mail_notify_fave($other, $user, $notice) {
+/**
+ * notify a user that one of their notices has been chosen as a 'fave'
+ *
+ * Doesn't check that the user has an email address nor if they
+ * want to receive notification of faves. Maybe this happens higher
+ * up the stack...?
+ *
+ * @param User $other The user whose notice was faved
+ * @param User $user The user who faved the notice
+ * @param Notice $notice The notice that was faved
+ *
+ * @return void
+ */
+
+function mail_notify_fave($other, $user, $notice)
+{
+ $profile = $user->getProfile();
+
+ $bestname = $profile->getBestName();
- $profile = $user->getProfile();
- $bestname = $profile->getBestName();
common_init_locale($other->language);
- $subject = sprintf(_('%s added your notice as a favorite'), $bestname);
- $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" .
- "%3\$s\n\n" .
- "You can see the list of %1\$s's favorites here:\n\n" .
- "%4\$s\n\n" .
- "Faithfully yours,\n" .
- "%5\$s\n"),
- $bestname,
- common_exact_date($notice->created),
- common_local_url('shownotice', array('notice' => $notice->id)),
- common_local_url('showfavorites', array('nickname' => $user->nickname)),
- common_config('site', 'name'));
+
+ $subject = sprintf(_('%s added your notice as a favorite'), $bestname);
+
+ $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" .
+ "%3\$s\n\n" .
+ "You can see the list of %1\$s's favorites here:\n\n" .
+ "%4\$s\n\n" .
+ "Faithfully yours,\n" .
+ "%5\$s\n"),
+ $bestname,
+ common_exact_date($notice->created),
+ common_local_url('shownotice',
+ array('notice' => $notice->id)),
+ common_local_url('showfavorites',
+ array('nickname' => $user->nickname)),
+ common_config('site', 'name'));
common_init_locale();
- mail_to_user($other, $subject, $body);
+ mail_to_user($other, $subject, $body);
}
diff --git a/lib/mailbox.php b/lib/mailbox.php
index 4ed8d1758..8d5d44e49 100644
--- a/lib/mailbox.php
+++ b/lib/mailbox.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * common superclass for direct messages inbox and outbox
+ *
+ * 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.
@@ -15,158 +18,278 @@
*
* 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 Message
+ * @package Laconica
+ * @author Evan Prodromou <evan@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); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
-require_once(INSTALLDIR.'/lib/personal.php');
+require_once INSTALLDIR.'/lib/personal.php';
define('MESSAGES_PER_PAGE', 20);
-class MailboxAction extends PersonalAction {
-
- function handle($args) {
-
- parent::handle($args);
-
- $nickname = common_canonical_nickname($this->arg('nickname'));
- $user = User::staticGet('nickname', $nickname);
-
- if (!$user) {
- $this->client_error(_('No such user.'), 404);
- return;
- }
-
- $cur = common_current_user();
-
- if (!$cur || $cur->id != $user->id) {
- $this->client_error(_('Only the user can read their own mailboxes.'), 403);
- return;
- }
-
- $profile = $user->getProfile();
-
- if (!$profile) {
- $this->server_error(_('User has no profile.'));
- return;
- }
-
- $page = $this->trimmed('page');
-
- if (!$page) {
- $page = 1;
- }
-
- $this->show_page($user, $page);
- }
-
- function get_title($user, $page) {
- return '';
- }
-
- function get_instructions() {
- return '';
- }
-
- function show_top() {
-
- $cur = common_current_user();
-
- common_message_form(NULL, $cur, NULL);
-
- $this->views_menu();
- }
-
- function show_page($user, $page) {
-
- common_show_header($this->get_title($user, $page),
- NULL, NULL,
- array($this, 'show_top'));
-
- $this->show_box($user, $page);
-
- common_show_footer();
- }
-
- function show_box($user, $page) {
-
- $message = $this->get_messages($user, $page);
-
- if ($message) {
-
- $cnt = 0;
- common_element_start('ul', array('id' => 'messages'));
-
- while ($message->fetch() && $cnt <= MESSAGES_PER_PAGE) {
- $cnt++;
-
- if ($cnt > MESSAGES_PER_PAGE) {
- break;
- }
-
- $this->show_message($message);
- }
-
- common_element_end('ul');
-
- common_pagination($page > 1, $cnt > MESSAGES_PER_PAGE,
- $page, $this->trimmed('action'),
- array('nickname' => $user->nickname));
-
- $message->free();
- unset($message);
- }
- }
-
- # returns the profile we want to show with the message
-
- function get_message_profile($message) {
- return NULL;
- }
-
- function show_message($message) {
-
- common_element_start('li', array('class' => 'message_single',
- 'id' => 'message-' . $message->id));
-
- $profile = $this->get_message_profile($message);
-
- $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
- common_element_start('a', array('href' => $profile->profileurl));
- common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
- 'class' => 'avatar stream',
- 'width' => AVATAR_STREAM_SIZE,
- 'height' => AVATAR_STREAM_SIZE,
- 'alt' =>
- ($profile->fullname) ? $profile->fullname :
- $profile->nickname));
- common_element_end('a');
- common_element('a', array('href' => $profile->profileurl,
- 'class' => 'nickname'),
- $profile->nickname);
- # FIXME: URL, image, video, audio
- common_element_start('p', array('class' => 'content'));
- common_raw($message->rendered);
- common_element_end('p');
-
- $messageurl = common_local_url('showmessage', array('message' => $message->id));
-
- # XXX: we need to figure this out better. Is this right?
- if (strcmp($message->uri, $messageurl) != 0 && preg_match('/^http/', $message->uri)) {
- $messageurl = $message->uri;
- }
- common_element_start('p', 'time');
- common_element('a', array('class' => 'permalink',
- 'href' => $messageurl,
- 'title' => common_exact_date($message->created)),
- common_date_string($message->created));
- if ($message->source) {
- common_text(_(' from '));
- $this->source_link($message->source);
- }
-
- common_element_end('p');
-
- common_element_end('li');
- }
+/**
+ * common superclass for direct messages inbox and outbox
+ *
+ * @category Message
+ * @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 InboxAction
+ * @see OutboxAction
+ */
+
+class MailboxAction extends PersonalAction
+{
+ var $page = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $this->user = User::staticGet('nickname', $nickname);
+ $this->page = $this->trimmed('page');
+
+ if (!$this->page) {
+ $this->page = 1;
+ }
+
+ return true;
+ }
+
+ /**
+ * output page based on arguments
+ *
+ * @param array $args HTTP arguments (from $_REQUEST)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (!$this->user) {
+ $this->clientError(_('No such user.'), 404);
+ return;
+ }
+
+ $cur = common_current_user();
+
+ if (!$cur || $cur->id != $this->user->id) {
+ $this->clientError(_('Only the user can read their own mailboxes.'),
+ 403);
+ return;
+ }
+
+ $this->showPage();
+ }
+
+ function showLocalNav()
+ {
+ $nav = new PersonalGroupNav($this);
+ $nav->show();
+ }
+
+ function showNoticeForm()
+ {
+ $message_form = new MessageForm($this);
+ $message_form->show();
+ }
+
+ function showContent()
+ {
+ $message = $this->getMessages();
+
+ if ($message) {
+ $cnt = 0;
+ $this->elementStart('div', array('id' =>'notices_primary'));
+ $this->element('h2', null, _('Notices'));
+ $this->elementStart('ul', 'notices');
+
+ while ($message->fetch() && $cnt <= MESSAGES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > MESSAGES_PER_PAGE) {
+ break;
+ }
+
+ $this->showMessage($message);
+ }
+
+ $this->elementEnd('ul');
+
+ $this->pagination($this->page > 1, $cnt > MESSAGES_PER_PAGE,
+ $this->page, $this->trimmed('action'),
+ array('nickname' => $this->user->nickname));
+ $this->elementEnd('div');
+ $message->free();
+ unset($message);
+ }
+ }
+
+ function getMessages()
+ {
+ return null;
+ }
+
+ /**
+ * returns the profile we want to show with the message
+ *
+ * For inboxes, we show the sender; for outboxes, the recipient.
+ *
+ * @param Message $message The message to get the profile for
+ *
+ * @return Profile The profile that matches the message
+ */
+
+ function getMessageProfile($message)
+ {
+ return null;
+ }
+
+ /**
+ * show a single message in the list format
+ *
+ * XXX: This needs to be extracted out into a MessageList similar
+ * to NoticeList.
+ *
+ * @param Message $message the message to show
+ *
+ * @return void
+ */
+
+ function showMessage($message)
+ {
+ $this->elementStart('li', array('class' => 'hentry notice',
+ 'id' => 'message-' . $message->id));
+
+ $profile = $this->getMessageProfile($message);
+
+ $this->elementStart('div', 'entry-title');
+ $this->elementStart('span', 'vcard author');
+ $this->elementStart('a', array('href' => $profile->profileurl,
+ 'class' => 'url'));
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+ $this->element('img', array('src' => ($avatar) ?
+ common_avatar_display_url($avatar) :
+ common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'photo avatar',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($profile->fullname) ? $profile->fullname :
+ $profile->nickname));
+ $this->element('span', array('class' => 'nickname fn'),
+ $profile->nickname);
+ $this->elementEnd('a');
+ $this->elementEnd('span');
+
+ // FIXME: URL, image, video, audio
+ $this->elementStart('p', array('class' => 'entry-content'));
+ $this->raw($message->rendered);
+ $this->elementEnd('p');
+ $this->elementEnd('div');
+
+ $messageurl = common_local_url('showmessage',
+ array('message' => $message->id));
+
+ // XXX: we need to figure this out better. Is this right?
+ if (strcmp($message->uri, $messageurl) != 0 &&
+ preg_match('/^http/', $message->uri)) {
+ $messageurl = $message->uri;
+ }
+
+ $this->elementStart('div', 'entry-content');
+ $this->elementStart('dl', 'timestamp');
+ $this->element('dt', null, _('Published'));
+ $this->elementStart('dd', null);
+ $dt = common_date_iso8601($message->created);
+ $this->elementStart('a', array('rel' => 'bookmark',
+ 'href' => $messageurl));
+ $this->element('abbr', array('class' => 'published',
+ 'title' => $dt),
+ common_date_string($message->created));
+ $this->elementEnd('a');
+ $this->elementEnd('dd');
+ $this->elementEnd('dl');
+
+ if ($message->source) {
+ $this->elementStart('dl', 'device');
+ $this->elementStart('dt');
+ $this->text(_('From'));
+ $this->elementEnd('dt');
+ $this->showSource($message->source);
+ $this->elementEnd('dl');
+ }
+ $this->elementEnd('div');
+
+ $this->elementEnd('li');
+ }
+
+ /**
+ * Show the page notice
+ *
+ * Shows instructions for the page
+ *
+ * @return void
+ */
+
+ function showPageNotice()
+ {
+ $instr = $this->getInstructions();
+ $output = common_markup_to_html($instr);
+
+ $this->elementStart('div', 'instructions');
+ $this->raw($output);
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Show the source of the message
+ *
+ * Returns 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 message
+ *
+ * @return void
+ */
+
+ function showSource($source)
+ {
+ $source_name = _($source);
+ switch ($source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ $this->element('dd', null, $source_name);
+ break;
+ default:
+ $ns = Notice_source::staticGet($source);
+ if ($ns) {
+ $this->elementStart('dd', null);
+ $this->element('a', array('href' => $ns->url,
+ 'rel' => 'external'),
+ $ns->name);
+ $this->elementEnd('dd');
+ } else {
+ $this->element('dd', null, $source_name);
+ }
+ break;
+ }
+ return;
+ }
+
}
diff --git a/lib/messageform.php b/lib/messageform.php
new file mode 100644
index 000000000..61d3d75af
--- /dev/null
+++ b/lib/messageform.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for posting a direct message
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for posting a direct message
+ *
+ * @category Form
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class MessageForm extends Form
+{
+ /**
+ * User to send a direct message to
+ */
+
+ var $to = null;
+
+ /**
+ * Pre-filled content of the form
+ */
+
+ var $content = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param User $to user to send a message to
+ * @param string $content content to pre-fill
+ */
+
+ function __construct($out=null, $to=null, $content=null)
+ {
+ parent::__construct($out);
+
+ $this->to = $to;
+ $this->content = $content;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'form_notice';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('newmessage');
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Send a direct notice'));
+ }
+
+
+ /**
+ * Data elements
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $user = common_current_user();
+
+ $mutual_users = $user->mutuallySubscribedUsers();
+
+ $mutual = array();
+
+ while ($mutual_users->fetch()) {
+ if ($mutual_users->id != $user->id) {
+ $mutual[$mutual_users->id] = $mutual_users->nickname;
+ }
+ }
+
+ $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->id);
+ $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');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ 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/microid.php b/lib/microid.php
new file mode 100644
index 000000000..806b7ee7d
--- /dev/null
+++ b/lib/microid.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Microid class
+ *
+ * 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 ID
+ * @package Laconica
+ * @author Evan Prodromou <evan@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);
+}
+
+/**
+ * A class for microids
+ *
+ * @category ID
+ * @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 http://microid.org/
+ */
+
+class Microid
+{
+ /** Agent part of the ID. */
+
+ var $agent = null;
+
+ /** Resource part of the ID. */
+
+ var $resource = null;
+
+ /**
+ * Constructor
+ *
+ * @param string $agent Agent of the ID
+ * @param string $resource Resource part
+ */
+
+ function __construct($agent, $resource)
+ {
+ $this->agent = $agent;
+ $this->resource = $resource;
+
+ }
+
+ /**
+ * Generate a MicroID string
+ *
+ * @return string MicroID for agent and resource
+ */
+
+ function toString()
+ {
+ $agent_proto = $this->_getProto($this->agent);
+ $resource_proto = $this->_getProto($this->resource);
+
+ return $agent_proto.'+'.$resource_proto.':sha1:'.
+ sha1(sha1($this->agent).sha1($this->resource));
+ }
+
+ /**
+ * Utility for getting the protocol part of a URI
+ *
+ * @param string $uri URI to parse
+ *
+ * @return string scheme part of the URI
+ */
+
+ function _getProto($uri)
+ {
+ $colon = strpos($uri, ':');
+ return substr($uri, 0, $colon);
+ }
+}
diff --git a/lib/noticeform.php b/lib/noticeform.php
new file mode 100644
index 000000000..0c991c969
--- /dev/null
+++ b/lib/noticeform.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for posting a notice
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for posting a notice
+ *
+ * Frequently-used form for posting a notice
+ *
+ * @category Form
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class NoticeForm extends Form
+{
+ /**
+ * Current action, used for returning to this page.
+ */
+
+ var $action = null;
+
+ /**
+ * Pre-filled content of the form
+ */
+
+ var $content = null;
+
+ /**
+ * The current user
+ */
+
+ var $user = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param string $action action to return to, if any
+ * @param string $content content to pre-fill
+ */
+
+ function __construct($out=null, $action=null, $content=null, $user=null)
+ {
+ parent::__construct($out);
+
+ $this->action = $action;
+ $this->content = $content;
+
+ if ($user) {
+ $this->user = $user;
+ } else {
+ $this->user = common_current_user();
+ }
+
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'form_notice';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('newnotice');
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Send a notice'));
+ }
+
+
+ /**
+ * Data elements
+ *
+ * @return void
+ */
+
+ 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
+ $this->out->element('textarea', array('id' => 'notice_data-text',
+ 'cols' => 35,
+ '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'));
+ $this->out->element('dd', array('id' => 'notice_text-count'),
+ '140');
+ $this->out->elementEnd('dl');
+
+ if ($this->action) {
+ $this->out->hidden('notice_return-to', $this->action, 'returnto');
+ }
+ $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ 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 415c062e4..20bf3c9f1 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -1,163 +1,383 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * 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
+ * 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/>.
+ * 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);
+}
+
+require_once INSTALLDIR.'/lib/favorform.php';
+require_once INSTALLDIR.'/lib/disfavorform.php';
+
+/**
+ * widget for displaying a list of notices
+ *
+ * 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
*/
-if (!defined('LACONICA')) { exit(1); }
+class NoticeList extends Widget
+{
+ /** the current stream of notices being displayed. */
-class NoticeList {
+ var $notice = null;
- var $notice = NULL;
+ /**
+ * constructor
+ *
+ * @param Notice $notice stream of notices from DB_DataObject
+ */
- function __construct($notice) {
+ function __construct($notice, $out=null)
+ {
+ parent::__construct($out);
$this->notice = $notice;
}
- function show() {
+ /**
+ * 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.
+ */
- common_element_start('ul', array('id' => 'notices'));
+ function show()
+ {
+ $this->out->elementStart('div', array('id' =>'notices_primary'));
+ $this->out->element('h2', null, _('Notices'));
+ $this->out->elementStart('ul', array('class' => 'notices'));
- $cnt = 0;
+ $cnt = 0;
- while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
- $cnt++;
+ while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+ $cnt++;
- if ($cnt > NOTICES_PER_PAGE) {
- break;
- }
+ if ($cnt > NOTICES_PER_PAGE) {
+ break;
+ }
- $item = $this->new_list_item($this->notice);
+ $item = $this->newListItem($this->notice);
$item->show();
- }
+ }
- common_element_end('ul');
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
return $cnt;
- }
+ }
+
+ /**
+ * 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 new_list_item($notice) {
- return new NoticeListItem($notice);
+ function newListItem($notice)
+ {
+ return new NoticeListItem($notice, $this->out);
}
}
-class NoticeListItem {
+/**
+ * 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
+ */
- var $notice = NULL;
- var $profile = NULL;
+class NoticeListItem extends Widget
+{
+ /** The notice this item will show. */
- function __construct($notice) {
- $this->notice = $notice;
- $this->profile = $notice->getProfile();
- }
-
- function show() {
- $this->show_start();
- $this->show_fave_form();
- $this->show_author();
- $this->show_content();
- $this->show_start_time_section();
- $this->show_notice_link();
- $this->show_notice_source();
- $this->show_reply_to();
- $this->show_reply_link();
- $this->show_delete_link();
- $this->show_end_time_section();
- $this->show_end();
- }
-
- function show_start() {
- # XXX: RDFa
- common_element_start('li', array('class' => 'notice_single hentry',
- 'id' => 'notice-' . $this->notice->id));
- }
-
- function show_fave_form() {
+ var $notice = null;
+
+ /** The profile of the author of the notice, extracted once for convenience. */
+
+ var $profile = null;
+
+ /**
+ * constructor
+ *
+ * Also initializes the profile attribute.
+ *
+ * @param Notice $notice The notice we'll display
+ */
+
+ function __construct($notice, $out=null)
+ {
+ parent::__construct($out);
+ $this->notice = $notice;
+ $this->profile = $notice->getProfile();
+ }
+
+ /**
+ * 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->showNotice();
+ $this->showNoticeInfo();
+ $this->showNoticeOptions();
+ $this->showEnd();
+ }
+
+ function showNotice()
+ {
+ $this->out->elementStart('div', 'entry-title');
+ $this->showAuthor();
+ $this->showContent();
+ $this->out->elementEnd('div');
+ }
+
+ function showNoticeInfo()
+ {
+ $this->out->elementStart('div', 'entry-content');
+ $this->showNoticeLink();
+ $this->showNoticeSource();
+ $this->showReplyTo();
+ $this->out->elementEnd('div');
+ }
+
+ function showNoticeOptions()
+ {
+ $this->out->elementStart('div', 'notice-options');
+ $this->showFaveForm();
+ $this->showReplyLink();
+ $this->showDeleteLink();
+ $this->out->elementEnd('div');
+ }
+
+ /**
+ * 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', array('class' => 'hentry notice',
+ 'id' => 'notice-' . $this->notice->id));
+ }
+
+ /**
+ * show the "favorite" form
+ *
+ * @return void
+ */
+
+ function showFaveForm()
+ {
$user = common_current_user();
- if ($user) {
- if ($user->hasFave($this->notice)) {
- common_disfavor_form($this->notice);
- } else {
- common_favor_form($this->notice);
- }
- }
- }
-
- function show_author() {
- common_element_start('span', 'vcard author');
- $this->show_avatar();
- $this->show_nickname();
- common_element_end('span');
- }
-
- function show_avatar() {
- $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
- common_element_start('a', array('href' => $this->profile->profileurl));
- common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
- 'class' => 'avatar stream photo',
- 'width' => AVATAR_STREAM_SIZE,
- 'height' => AVATAR_STREAM_SIZE,
- 'alt' =>
- ($this->profile->fullname) ? $this->profile->fullname :
- $this->profile->nickname));
- common_element_end('a');
- }
-
- function show_nickname() {
- common_element('a', array('href' => $this->profile->profileurl,
- 'class' => 'nickname fn url'),
- $this->profile->nickname);
- }
-
- function show_content() {
- # FIXME: URL, image, video, audio
- common_element_start('p', array('class' => 'content entry-title'));
- if ($this->notice->rendered) {
- common_raw($this->notice->rendered);
- } else {
- # XXX: may be some uncooked notices in the DB,
- # we cook them right now. This should probably disappear in future
- # versions (>> 0.4.x)
- common_raw(common_render_content($this->notice->content, $this->notice));
- }
- common_element_end('p');
- }
-
- function show_start_time_section() {
- common_element_start('p', 'time');
- }
-
- function show_notice_link() {
- $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;
- }
- common_element_start('a', array('class' => 'permalink',
- 'rel' => 'bookmark',
- 'href' => $noticeurl));
- common_element('abbr', array('class' => 'published',
- 'title' => common_date_iso8601($this->notice->created)),
- common_date_string($this->notice->created));
- common_element_end('a');
- }
-
- function show_notice_source() {
- if ($this->notice->source) {
- common_element('span', null, _(' from '));
+ if ($user) {
+ if ($user->hasFave($this->notice)) {
+ $disfavor = new DisfavorForm($this->out, $this->notice);
+ $disfavor->show();
+ } else {
+ $favor = new FavorForm($this->out, $this->notice);
+ $favor->show();
+ }
+ }
+ }
+
+ /**
+ * show the author of a notice
+ *
+ * By default, this shows the avatar and (linked) nickname of the author.
+ *
+ * @return void
+ */
+
+ function showAuthor()
+ {
+ $this->out->elementStart('span', 'vcard author');
+ $this->out->elementStart('a', array('href' => $this->profile->profileurl,
+ 'class' => 'url'));
+ $this->showAvatar();
+ $this->showNickname();
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('span');
+ }
+
+ /**
+ * show the avatar of the notice's author
+ *
+ * This will use the default avatar if no avatar is assigned for the author.
+ * It makes a link to the author's profile.
+ *
+ * @return void
+ */
+
+ function showAvatar()
+ {
+ if ('shownotice' === $this->out->trimmed('action')) {
+ $avatar_size = AVATAR_PROFILE_SIZE;
+ } else {
+ $avatar_size = AVATAR_STREAM_SIZE;
+ }
+ $avatar = $this->profile->getAvatar($avatar_size);
+
+ $this->out->element('img', array('src' => ($avatar) ?
+ common_avatar_display_url($avatar) :
+ common_default_avatar($avatar_size),
+ 'class' => 'avatar photo',
+ 'width' => $avatar_size,
+ 'height' => $avatar_size,
+ 'alt' =>
+ ($this->profile->fullname) ?
+ $this->profile->fullname :
+ $this->profile->nickname));
+ }
+
+ /**
+ * show the nickname of the author
+ *
+ * Links to the author's profile page
+ *
+ * @return void
+ */
+
+ function showNickname()
+ {
+ $this->out->element('span', array('class' => 'nickname fn'),
+ $this->profile->nickname);
+ }
+
+ /**
+ * show the content of the notice
+ *
+ * Shows the content of the notice. This is pre-rendered for efficiency
+ * at save time. Some very old notices might not be pre-rendered, so
+ * they're rendered on the spot.
+ *
+ * @return void
+ */
+
+ function showContent()
+ {
+ // FIXME: URL, image, video, audio
+ $this->out->elementStart('p', array('class' => 'entry-content'));
+ if ($this->notice->rendered) {
+ $this->out->raw($this->notice->rendered);
+ } else {
+ // XXX: may be some uncooked notices in the DB,
+ // we cook them right now. This should probably disappear in future
+ // versions (>> 0.4.x)
+ $this->out->raw(common_render_content($this->notice->content, $this->notice));
+ }
+ $this->out->elementEnd('p');
+ }
+
+ /**
+ * show the link to the main page for the notice
+ *
+ * Displays a link to the page for a notice, with "relative" time. Tries to
+ * get remote notice URLs correct, but doesn't always succeed.
+ *
+ * @return void
+ */
+
+ 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');
+ }
+
+ /**
+ * 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.
+ *
+ * @return void
+ */
+
+ function showNoticeSource()
+ {
+ if ($this->notice->source) {
+ $this->out->elementStart('dl', 'device');
+ $this->out->element('dt', null, _('From'));
$source_name = _($this->notice->source);
switch ($this->notice->source) {
case 'web':
@@ -165,60 +385,108 @@ class NoticeListItem {
case 'mail':
case 'omb':
case 'api':
- common_element('span', 'noticesource', $source_name);
+ $this->out->element('dd', null, $source_name);
break;
default:
$ns = Notice_source::staticGet($this->notice->source);
if ($ns) {
- common_element('a', array('href' => $ns->url),
- $ns->name);
+ $this->out->elementStart('dd', null);
+ $this->out->element('a', array('href' => $ns->url,
+ 'rel' => 'external'),
+ $ns->name);
+ $this->out->elementEnd('dd');
} else {
- common_element('span', 'noticesource', $source_name);
+ $this->out->element('dd', null, $source_name);
}
break;
}
- }
+ $this->out->elementEnd('dl');
+ }
}
- function show_reply_to() {
- if ($this->notice->reply_to) {
- $replyurl = common_local_url('shownotice', array('notice' => $this->notice->reply_to));
- common_text(' (');
- common_element('a', array('class' => 'inreplyto',
- 'href' => $replyurl),
- _('in reply to...'));
- common_text(')');
- }
+ /**
+ * show link to notice this notice is a reply to
+ *
+ * If this notice is a reply, show a link to the notice it is replying to. The
+ * heavy lifting for figuring out replies happens at save time.
+ *
+ * @return void
+ */
+
+ function showReplyTo()
+ {
+ if ($this->notice->reply_to) {
+ $replyurl = common_local_url('shownotice',
+ array('notice' => $this->notice->reply_to));
+ $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->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
}
- function show_reply_link() {
- common_element_start('a',
- array('href' => common_local_url('newnotice',
- array('replyto' => $this->profile->nickname)),
- 'onclick' => 'return doreply("'.$this->profile->nickname.'", '.$this->notice->id.');',
- 'title' => _('reply'),
- 'class' => 'replybutton'));
- common_raw(' &#8594;');
- common_element_end('a');
+ /**
+ * show a link to reply to the current notice
+ *
+ * Should either do the reply in the current notice form (if available), or
+ * link out to the notice-posting form. A little flakey, doesn't always work.
+ *
+ * @return void
+ */
+
+ function showReplyLink()
+ {
+ $reply_url = common_local_url('newnotice',
+ array('replyto' => $this->profile->nickname));
+
+ $this->out->elementStart('dl', 'notice_reply');
+ $this->out->element('dt', null, _('Reply to this notice'));
+ $this->out->elementStart('dd');
+ $this->out->elementStart('a', array('href' => $reply_url,
+ 'title' => _('Reply to this notice')));
+ $this->out->text(_('Reply'));
+ $this->out->element('span', 'notice_id', $this->notice->id);
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
}
- function show_delete_link() {
+ /**
+ * if the user is the author, let them delete the notice
+ *
+ * @return void
+ */
+
+ function showDeleteLink()
+ {
$user = common_current_user();
- if ($user && $this->notice->profile_id == $user->id) {
- $deleteurl = common_local_url('deletenotice', array('notice' => $this->notice->id));
- common_element_start('a', array('class' => 'deletenotice',
- 'href' => $deleteurl,
- 'title' => _('delete')));
- common_raw(' &#215;');
- common_element_end('a');
- }
+ if ($user && $this->notice->profile_id == $user->id) {
+ $deleteurl = common_local_url('deletenotice',
+ array('notice' => $this->notice->id));
+ $this->out->elementStart('dl', 'notice_delete');
+ $this->out->element('dt', null, _('Delete this notice'));
+ $this->out->elementStart('dd');
+ $this->out->element('a', array('href' => $deleteurl,
+ 'title' => _('Delete this notice')), _('Delete'));
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
}
- function show_end_time_section() {
- common_element_end('p');
- }
+ /**
+ * finish the notice
+ *
+ * Close the last elements in the notice list item
+ *
+ * @return void
+ */
- function show_end() {
- common_element_end('li');
+ function showEnd()
+ {
+ $this->out->elementEnd('li');
}
}
diff --git a/lib/noticesection.php b/lib/noticesection.php
new file mode 100644
index 000000000..7dfa0472d
--- /dev/null
+++ b/lib/noticesection.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists 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 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('NOTICES_PER_SECTION', 6);
+
+/**
+ * Base class for sections showing lists of notices
+ *
+ * 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 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);
+ }
+
+ function getNotices()
+ {
+ return null;
+ }
+
+ function showNotice($notice)
+ {
+ $profile = $notice->getProfile();
+ $this->out->elementStart('li', 'hentry notice');
+ $this->out->elementStart('div', 'entry-title');
+ $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
+ $this->out->elementStart('span', 'vcard author');
+ $this->out->elementStart('a', array('title' => ($profile->fullname) ?
+ $profile->fullname :
+ $profile->nickname,
+ 'href' => $profile->noticeurl,
+ 'class' => 'url'));
+ $this->out->element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar photo',
+ 'alt' => ($profile->fullname) ?
+ $profile->fullname :
+ $profile->nickname));
+ $this->out->element('span', 'fn nickname', $profile->nickname);
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('span');
+
+ $this->out->elementStart('p', 'entry-content');
+ $this->out->raw($notice->rendered);
+ $this->out->elementEnd('p');
+ if ($notice->value) {
+ $this->out->elementStart('p');
+ $this->out->text($notice->value);
+ $this->out->elementEnd('p');
+ }
+ $this->out->elementEnd('div');
+ $this->out->elementEnd('li');
+ }
+}
diff --git a/lib/nudgeform.php b/lib/nudgeform.php
new file mode 100644
index 000000000..7380462a7
--- /dev/null
+++ b/lib/nudgeform.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for nudging a user
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for nudging a user
+ *
+ * @category Form
+ * @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/
+ *
+ * @see DisfavorForm
+ */
+
+class NudgeForm extends Form
+{
+ /**
+ * Profile of user to nudge
+ */
+
+ var $profile = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Profile $profile profile of user to nudge
+ */
+
+ function __construct($out=null, $profile=null)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'form_user_nudge';
+ }
+
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_user_nudge';
+ }
+
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('nudge',
+ array('nickname' => $this->profile->nickname));
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Nudge this user'));
+ }
+
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Nudge'), 'submit', null, _('Send a nudge to this user'));
+ }
+}
diff --git a/lib/oauthstore.php b/lib/oauthstore.php
index d7f9c9ff1..7ad3be20e 100644
--- a/lib/oauthstore.php
+++ b/lib/oauthstore.php
@@ -21,124 +21,132 @@ if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/omb.php');
-class LaconicaOAuthDataStore extends OAuthDataStore {
+class LaconicaOAuthDataStore extends OAuthDataStore
+{
- # We keep a record of who's contacted us
+ // We keep a record of who's contacted us
- function lookup_consumer($consumer_key) {
- $con = Consumer::staticGet('consumer_key', $consumer_key);
- if (!$con) {
- $con = new Consumer();
- $con->consumer_key = $consumer_key;
- $con->seed = common_good_rand(16);
- $con->created = DB_DataObject_Cast::dateTime();
- if (!$con->insert()) {
- return NULL;
- }
- }
- return new OAuthConsumer($con->consumer_key, '');
- }
+ function lookup_consumer($consumer_key)
+ {
+ $con = Consumer::staticGet('consumer_key', $consumer_key);
+ if (!$con) {
+ $con = new Consumer();
+ $con->consumer_key = $consumer_key;
+ $con->seed = common_good_rand(16);
+ $con->created = DB_DataObject_Cast::dateTime();
+ if (!$con->insert()) {
+ return null;
+ }
+ }
+ return new OAuthConsumer($con->consumer_key, '');
+ }
- function lookup_token($consumer, $token_type, $token_key) {
- $t = new Token();
- $t->consumer_key = $consumer->key;
- $t->tok = $token_key;
- $t->type = ($token_type == 'access') ? 1 : 0;
- if ($t->find(true)) {
- return new OAuthToken($t->tok, $t->secret);
- } else {
- return NULL;
- }
- }
+ function lookup_token($consumer, $token_type, $token_key)
+ {
+ $t = new Token();
+ $t->consumer_key = $consumer->key;
+ $t->tok = $token_key;
+ $t->type = ($token_type == 'access') ? 1 : 0;
+ if ($t->find(true)) {
+ return new OAuthToken($t->tok, $t->secret);
+ } else {
+ return null;
+ }
+ }
- function lookup_nonce($consumer, $token, $nonce, $timestamp) {
- $n = new Nonce();
- $n->consumer_key = $consumer->key;
- $n->tok = $token->key;
- $n->nonce = $nonce;
- if ($n->find(TRUE)) {
- return TRUE;
- } else {
- $n->timestamp = $timestamp;
- $n->created = DB_DataObject_Cast::dateTime();
- $n->insert();
- return FALSE;
- }
- }
+ function lookup_nonce($consumer, $token, $nonce, $timestamp)
+ {
+ $n = new Nonce();
+ $n->consumer_key = $consumer->key;
+ $n->tok = $token->key;
+ $n->nonce = $nonce;
+ if ($n->find(true)) {
+ return true;
+ } else {
+ $n->timestamp = $timestamp;
+ $n->created = DB_DataObject_Cast::dateTime();
+ $n->insert();
+ return false;
+ }
+ }
- function new_request_token($consumer) {
- $t = new Token();
- $t->consumer_key = $consumer->key;
- $t->tok = common_good_rand(16);
- $t->secret = common_good_rand(16);
- $t->type = 0; # request
- $t->state = 0; # unauthorized
- $t->created = DB_DataObject_Cast::dateTime();
- if (!$t->insert()) {
- return NULL;
- } else {
- return new OAuthToken($t->tok, $t->secret);
- }
- }
+ function new_request_token($consumer)
+ {
+ $t = new Token();
+ $t->consumer_key = $consumer->key;
+ $t->tok = common_good_rand(16);
+ $t->secret = common_good_rand(16);
+ $t->type = 0; // request
+ $t->state = 0; // unauthorized
+ $t->created = DB_DataObject_Cast::dateTime();
+ if (!$t->insert()) {
+ return null;
+ } else {
+ return new OAuthToken($t->tok, $t->secret);
+ }
+ }
- # defined in OAuthDataStore, but not implemented anywhere
+ // defined in OAuthDataStore, but not implemented anywhere
- function fetch_request_token($consumer) {
- return $this->new_request_token($consumer);
- }
+ function fetch_request_token($consumer)
+ {
+ return $this->new_request_token($consumer);
+ }
- function new_access_token($token, $consumer) {
- common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
- $rt = new Token();
- $rt->consumer_key = $consumer->key;
- $rt->tok = $token->key;
- $rt->type = 0; # request
- if ($rt->find(TRUE) && $rt->state == 1) { # authorized
- common_debug('request token found.', __FILE__);
- $at = new Token();
- $at->consumer_key = $consumer->key;
- $at->tok = common_good_rand(16);
- $at->secret = common_good_rand(16);
- $at->type = 1; # access
- $at->created = DB_DataObject_Cast::dateTime();
- if (!$at->insert()) {
- $e = $at->_lastError;
- common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
- return NULL;
- } else {
- common_debug('access token "'.$at->tok.'" inserted', __FILE__);
- # burn the old one
- $orig_rt = clone($rt);
- $rt->state = 2; # used
- if (!$rt->update($orig_rt)) {
- return NULL;
- }
- common_debug('request token "'.$rt->tok.'" updated', __FILE__);
- # Update subscription
- # XXX: mixing levels here
- $sub = Subscription::staticGet('token', $rt->tok);
- if (!$sub) {
- return NULL;
- }
- common_debug('subscription for request token found', __FILE__);
- $orig_sub = clone($sub);
- $sub->token = $at->tok;
- $sub->secret = $at->secret;
- if (!$sub->update($orig_sub)) {
- return NULL;
- } else {
- common_debug('subscription updated to use access token', __FILE__);
- return new OAuthToken($at->tok, $at->secret);
- }
- }
- } else {
- return NULL;
- }
- }
+ function new_access_token($token, $consumer)
+ {
+ common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
+ $rt = new Token();
+ $rt->consumer_key = $consumer->key;
+ $rt->tok = $token->key;
+ $rt->type = 0; // request
+ if ($rt->find(true) && $rt->state == 1) { // authorized
+ common_debug('request token found.', __FILE__);
+ $at = new Token();
+ $at->consumer_key = $consumer->key;
+ $at->tok = common_good_rand(16);
+ $at->secret = common_good_rand(16);
+ $at->type = 1; // access
+ $at->created = DB_DataObject_Cast::dateTime();
+ if (!$at->insert()) {
+ $e = $at->_lastError;
+ common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
+ return null;
+ } else {
+ common_debug('access token "'.$at->tok.'" inserted', __FILE__);
+ // burn the old one
+ $orig_rt = clone($rt);
+ $rt->state = 2; // used
+ if (!$rt->update($orig_rt)) {
+ return null;
+ }
+ common_debug('request token "'.$rt->tok.'" updated', __FILE__);
+ // Update subscription
+ // XXX: mixing levels here
+ $sub = Subscription::staticGet('token', $rt->tok);
+ if (!$sub) {
+ return null;
+ }
+ common_debug('subscription for request token found', __FILE__);
+ $orig_sub = clone($sub);
+ $sub->token = $at->tok;
+ $sub->secret = $at->secret;
+ if (!$sub->update($orig_sub)) {
+ return null;
+ } else {
+ common_debug('subscription updated to use access token', __FILE__);
+ return new OAuthToken($at->tok, $at->secret);
+ }
+ }
+ } else {
+ return null;
+ }
+ }
- # defined in OAuthDataStore, but not implemented anywhere
+ // defined in OAuthDataStore, but not implemented anywhere
- function fetch_access_token($consumer) {
- return $this->new_access_token($consumer);
- }
+ function fetch_access_token($consumer)
+ {
+ return $this->new_access_token($consumer);
+ }
}
diff --git a/lib/omb.php b/lib/omb.php
index 96736b4d4..f2dbef5ba 100644
--- a/lib/omb.php
+++ b/lib/omb.php
@@ -43,257 +43,271 @@ define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
-function omb_oauth_consumer() {
- static $con = NULL;
- if (!$con) {
- $con = new OAuthConsumer(common_root_url(), '');
- }
- return $con;
+function omb_oauth_consumer()
+{
+ static $con = null;
+ if (!$con) {
+ $con = new OAuthConsumer(common_root_url(), '');
+ }
+ return $con;
}
-function omb_oauth_server() {
- static $server = null;
- if (!$server) {
- $server = new OAuthServer(omb_oauth_datastore());
- $server->add_signature_method(omb_hmac_sha1());
- }
- return $server;
+function omb_oauth_server()
+{
+ static $server = null;
+ if (!$server) {
+ $server = new OAuthServer(omb_oauth_datastore());
+ $server->add_signature_method(omb_hmac_sha1());
+ }
+ return $server;
}
-function omb_oauth_datastore() {
- static $store = NULL;
- if (!$store) {
- $store = new LaconicaOAuthDataStore();
- }
- return $store;
+function omb_oauth_datastore()
+{
+ static $store = null;
+ if (!$store) {
+ $store = new LaconicaOAuthDataStore();
+ }
+ return $store;
}
-function omb_hmac_sha1() {
- static $hmac_method = NULL;
- if (!$hmac_method) {
- $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
- }
- return $hmac_method;
+function omb_hmac_sha1()
+{
+ static $hmac_method = null;
+ if (!$hmac_method) {
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ }
+ return $hmac_method;
}
-function omb_get_services($xrd, $type) {
- return $xrd->services(array(omb_service_filter($type)));
+function omb_get_services($xrd, $type)
+{
+ return $xrd->services(array(omb_service_filter($type)));
}
-function omb_service_filter($type) {
- return create_function('$s',
- 'return omb_match_service($s, \''.$type.'\');');
+function omb_service_filter($type)
+{
+ return create_function('$s',
+ 'return omb_match_service($s, \''.$type.'\');');
}
-function omb_match_service($service, $type) {
- return in_array($type, $service->getTypes());
+function omb_match_service($service, $type)
+{
+ return in_array($type, $service->getTypes());
}
-function omb_service_uri($service) {
- if (!$service) {
- return NULL;
- }
- $uris = $service->getURIs();
- if (!$uris) {
- return NULL;
- }
- return $uris[0];
+function omb_service_uri($service)
+{
+ if (!$service) {
+ return null;
+ }
+ $uris = $service->getURIs();
+ if (!$uris) {
+ return null;
+ }
+ return $uris[0];
}
-function omb_local_id($service) {
- if (!$service) {
- return NULL;
- }
- $els = $service->getElements('xrd:LocalID');
- if (!$els) {
- return NULL;
- }
- $el = $els[0];
- return $service->parser->content($el);
+function omb_local_id($service)
+{
+ if (!$service) {
+ return null;
+ }
+ $els = $service->getElements('xrd:LocalID');
+ if (!$els) {
+ return null;
+ }
+ $el = $els[0];
+ return $service->parser->content($el);
}
-function omb_broadcast_remote_subscribers($notice) {
+function omb_broadcast_remote_subscribers($notice)
+{
- # First, get remote users subscribed to this profile
- $rp = new Remote_profile();
+ # First, get remote users subscribed to this profile
+ $rp = new Remote_profile();
- $rp->query('SELECT postnoticeurl, token, secret ' .
- 'FROM subscription JOIN remote_profile ' .
- 'ON subscription.subscriber = remote_profile.id ' .
- 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ');
+ $rp->query('SELECT postnoticeurl, token, secret ' .
+ 'FROM subscription JOIN remote_profile ' .
+ 'ON subscription.subscriber = remote_profile.id ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ');
- $posted = array();
+ $posted = array();
- while ($rp->fetch()) {
- if (!$posted[$rp->postnoticeurl]) {
- common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl);
- if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) {
- common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl);
- $posted[$rp->postnoticeurl] = TRUE;
- } else {
- common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl);
- }
- }
- }
+ while ($rp->fetch()) {
+ if (!$posted[$rp->postnoticeurl]) {
+ common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl);
+ if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) {
+ common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl);
+ $posted[$rp->postnoticeurl] = true;
+ } else {
+ common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl);
+ }
+ }
+ }
- $rp->free();
- unset($rp);
+ $rp->free();
+ unset($rp);
- return true;
+ return true;
}
-function omb_post_notice($notice, $remote_profile, $subscription) {
- return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
+function omb_post_notice($notice, $remote_profile, $subscription)
+{
+ return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
}
-function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) {
+function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
+{
- common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
+ common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
- $user = User::staticGet('id', $notice->profile_id);
+ $user = User::staticGet('id', $notice->profile_id);
- if (!$user) {
- common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
- return false;
- }
+ if (!$user) {
+ common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
+ return false;
+ }
- $con = omb_oauth_consumer();
+ $con = omb_oauth_consumer();
- $token = new OAuthToken($tk, $secret);
+ $token = new OAuthToken($tk, $secret);
- $url = $postnoticeurl;
- $parsed = parse_url($url);
- $params = array();
- parse_str($parsed['query'], $params);
+ $url = $postnoticeurl;
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
- $req = OAuthRequest::from_consumer_and_token($con, $token,
- 'POST', $url, $params);
+ $req = OAuthRequest::from_consumer_and_token($con, $token,
+ 'POST', $url, $params);
- $req->set_parameter('omb_version', OMB_VERSION_01);
- $req->set_parameter('omb_listenee', $user->uri);
- $req->set_parameter('omb_notice', $notice->uri);
- $req->set_parameter('omb_notice_content', $notice->content);
- $req->set_parameter('omb_notice_url', common_local_url('shownotice',
- array('notice' =>
- $notice->id)));
- $req->set_parameter('omb_notice_license', common_config('license', 'url'));
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+ $req->set_parameter('omb_listenee', $user->uri);
+ $req->set_parameter('omb_notice', $notice->uri);
+ $req->set_parameter('omb_notice_content', $notice->content);
+ $req->set_parameter('omb_notice_url', common_local_url('shownotice',
+ array('notice' =>
+ $notice->id)));
+ $req->set_parameter('omb_notice_license', common_config('license', 'url'));
- $user->free();
- unset($user);
+ $user->free();
+ unset($user);
- $req->sign_request(omb_hmac_sha1(), $con, $token);
+ $req->sign_request(omb_hmac_sha1(), $con, $token);
- # We re-use this tool's fetcher, since it's pretty good
+ # We re-use this tool's fetcher, since it's pretty good
- $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
- if (!$fetcher) {
- common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
- return false;
- }
+ if (!$fetcher) {
+ common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
+ return false;
+ }
- $result = $fetcher->post($req->get_normalized_http_url(),
- $req->to_postdata(),
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
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
- common_debug('403 result, deleting subscription', __FILE__);
- # FIXME: figure out how to delete this
- # $subscription->delete();
- return false;
- } else if ($result->status != 200) {
- common_debug('Error status '.$result->status, __FILE__);
- return false;
- } else { # success!
- parse_str($result->body, $return);
- if ($return['omb_version'] == OMB_VERSION_01) {
- return true;
- } else {
- return false;
- }
- }
+ common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
+
+ if ($result->status == 403) { # not authorized, don't send again
+ common_debug('403 result, deleting subscription', __FILE__);
+ # FIXME: figure out how to delete this
+ # $subscription->delete();
+ return false;
+ } else if ($result->status != 200) {
+ common_debug('Error status '.$result->status, __FILE__);
+ return false;
+ } else { # success!
+ parse_str($result->body, $return);
+ if ($return['omb_version'] == OMB_VERSION_01) {
+ return true;
+ } else {
+ return false;
+ }
+ }
}
-function omb_broadcast_profile($profile) {
- # First, get remote users subscribed to this profile
- # XXX: use a join here rather than looping through results
- $sub = new Subscription();
- $sub->subscribed = $profile->id;
- if ($sub->find()) {
- $updated = array();
- while ($sub->fetch()) {
- $rp = Remote_profile::staticGet('id', $sub->subscriber);
- if ($rp) {
- if (!$updated[$rp->updateprofileurl]) {
- if (omb_update_profile($profile, $rp, $sub)) {
- $updated[$rp->updateprofileurl] = TRUE;
- }
- }
- }
- }
- }
+function omb_broadcast_profile($profile)
+{
+ # First, get remote users subscribed to this profile
+ # XXX: use a join here rather than looping through results
+ $sub = new Subscription();
+ $sub->subscribed = $profile->id;
+ if ($sub->find()) {
+ $updated = array();
+ while ($sub->fetch()) {
+ $rp = Remote_profile::staticGet('id', $sub->subscriber);
+ if ($rp) {
+ if (!$updated[$rp->updateprofileurl]) {
+ if (omb_update_profile($profile, $rp, $sub)) {
+ $updated[$rp->updateprofileurl] = true;
+ }
+ }
+ }
+ }
+ }
}
-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);
- $url = $remote_profile->updateprofileurl;
- $parsed = parse_url($url);
- $params = array();
- parse_str($parsed['query'], $params);
- $req = OAuthRequest::from_consumer_and_token($con, $token,
- "POST", $url, $params);
- $req->set_parameter('omb_version', OMB_VERSION_01);
- $req->set_parameter('omb_listenee', $user->uri);
- $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname));
- $req->set_parameter('omb_listenee_nickname', $profile->nickname);
-
- # We use blanks to force emptying any existing values in these optional fields
-
- $req->set_parameter('omb_listenee_fullname',
- ($profile->fullname) ? $profile->fullname : '');
- $req->set_parameter('omb_listenee_homepage',
- ($profile->homepage) ? $profile->homepage : '');
- $req->set_parameter('omb_listenee_bio',
- ($profile->bio) ? $profile->bio : '');
- $req->set_parameter('omb_listenee_location',
- ($profile->location) ? $profile->location : '');
-
- $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
- $req->set_parameter('omb_listenee_avatar',
- ($avatar) ? $avatar->url : '');
-
- $req->sign_request(omb_hmac_sha1(), $con, $token);
-
- # We re-use this tool's fetcher, since it's pretty good
-
- $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-
- common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
- common_debug('postdata = '.$req->to_postdata(), __FILE__);
- $result = $fetcher->post($req->get_normalized_http_url(),
- $req->to_postdata(),
+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);
+ $url = $remote_profile->updateprofileurl;
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+ $req = OAuthRequest::from_consumer_and_token($con, $token,
+ "POST", $url, $params);
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+ $req->set_parameter('omb_listenee', $user->uri);
+ $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname));
+ $req->set_parameter('omb_listenee_nickname', $profile->nickname);
+
+ # We use blanks to force emptying any existing values in these optional fields
+
+ $req->set_parameter('omb_listenee_fullname',
+ ($profile->fullname) ? $profile->fullname : '');
+ $req->set_parameter('omb_listenee_homepage',
+ ($profile->homepage) ? $profile->homepage : '');
+ $req->set_parameter('omb_listenee_bio',
+ ($profile->bio) ? $profile->bio : '');
+ $req->set_parameter('omb_listenee_location',
+ ($profile->location) ? $profile->location : '');
+
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ $req->set_parameter('omb_listenee_avatar',
+ ($avatar) ? $avatar->url : '');
+
+ $req->sign_request(omb_hmac_sha1(), $con, $token);
+
+ # We re-use this tool's fetcher, since it's pretty good
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
+ common_debug('postdata = '.$req->to_postdata(), __FILE__);
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
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
- common_debug('403 result, deleting subscription', __FILE__);
- $subscription->delete();
- return false;
- } else if ($result->status != 200) {
- common_debug('Error status '.$result->status, __FILE__);
- return false;
- } else { # success!
- parse_str($result->body, $return);
- if ($return['omb_version'] == OMB_VERSION_01) {
- return true;
- } else {
- return false;
- }
- }
+ common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
+
+ if ($result->status == 403) { # not authorized, don't send again
+ common_debug('403 result, deleting subscription', __FILE__);
+ $subscription->delete();
+ return false;
+ } else if ($result->status != 200) {
+ common_debug('Error status '.$result->status, __FILE__);
+ return false;
+ } else { # success!
+ parse_str($result->body, $return);
+ if ($return['omb_version'] == OMB_VERSION_01) {
+ return true;
+ } else {
+ return false;
+ }
+ }
}
diff --git a/lib/openid.php b/lib/openid.php
index 6e501c2b1..860573702 100644
--- a/lib/openid.php
+++ b/lib/openid.php
@@ -31,212 +31,247 @@ require_once('Auth/OpenID/MySQLStore.php');
define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60));
define('OPENID_COOKIE_KEY', 'lastusedopenid');
-function oid_store() {
- static $store = NULL;
- if (!$store) {
- # Can't be called statically
- $user = new User();
- $conn = $user->getDatabaseConnection();
- $store = new Auth_OpenID_MySQLStore($conn);
- }
- return $store;
+function oid_store()
+{
+ static $store = null;
+ if (!$store) {
+ # Can't be called statically
+ $user = new User();
+ $conn = $user->getDatabaseConnection();
+ $store = new Auth_OpenID_MySQLStore($conn);
+ }
+ return $store;
}
-function oid_consumer() {
- $store = oid_store();
- $consumer = new Auth_OpenID_Consumer($store);
- return $consumer;
+function oid_consumer()
+{
+ $store = oid_store();
+ $consumer = new Auth_OpenID_Consumer($store);
+ return $consumer;
}
-function oid_clear_last() {
- oid_set_last('');
+function oid_clear_last()
+{
+ oid_set_last('');
}
-function oid_set_last($openid_url) {
- common_set_cookie(OPENID_COOKIE_KEY,
- $openid_url,
- time() + OPENID_COOKIE_EXPIRY);
+function oid_set_last($openid_url)
+{
+ common_set_cookie(OPENID_COOKIE_KEY,
+ $openid_url,
+ time() + OPENID_COOKIE_EXPIRY);
}
-function oid_get_last() {
- $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
- if ($openid_url && strlen($openid_url) > 0) {
- return $openid_url;
- } else {
- return NULL;
- }
+function oid_get_last()
+{
+ $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
+ if ($openid_url && strlen($openid_url) > 0) {
+ return $openid_url;
+ } else {
+ return null;
+ }
}
-function oid_link_user($id, $canonical, $display) {
+function oid_link_user($id, $canonical, $display)
+{
- $oid = new User_openid();
- $oid->user_id = $id;
- $oid->canonical = $canonical;
- $oid->display = $display;
- $oid->created = DB_DataObject_Cast::dateTime();
+ $oid = new User_openid();
+ $oid->user_id = $id;
+ $oid->canonical = $canonical;
+ $oid->display = $display;
+ $oid->created = DB_DataObject_Cast::dateTime();
- if (!$oid->insert()) {
- $err = PEAR::getStaticProperty('DB_DataObject','lastError');
- common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
- return false;
- }
+ if (!$oid->insert()) {
+ $err = PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
+ return false;
+ }
- return true;
+ return true;
}
-function oid_get_user($openid_url) {
- $user = NULL;
- $oid = User_openid::staticGet('canonical', $openid_url);
- if ($oid) {
- $user = User::staticGet('id', $oid->user_id);
- }
- return $user;
+function oid_get_user($openid_url)
+{
+ $user = null;
+ $oid = User_openid::staticGet('canonical', $openid_url);
+ if ($oid) {
+ $user = User::staticGet('id', $oid->user_id);
+ }
+ return $user;
}
-function oid_check_immediate($openid_url, $backto=NULL) {
- if (!$backto) {
- $action = $_REQUEST['action'];
- $args = common_copy_args($_GET);
- unset($args['action']);
- $backto = common_local_url($action, $args);
- }
- common_debug('going back to "' . $backto . '"', __FILE__);
-
- common_ensure_session();
-
- $_SESSION['openid_immediate_backto'] = $backto;
- common_debug('passed-in variable is "' . $backto . '"', __FILE__);
- common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
-
- oid_authenticate($openid_url,
- 'finishimmediate',
- true);
+function oid_check_immediate($openid_url, $backto=null)
+{
+ if (!$backto) {
+ $action = $_REQUEST['action'];
+ $args = common_copy_args($_GET);
+ unset($args['action']);
+ $backto = common_local_url($action, $args);
+ }
+ common_debug('going back to "' . $backto . '"', __FILE__);
+
+ common_ensure_session();
+
+ $_SESSION['openid_immediate_backto'] = $backto;
+ common_debug('passed-in variable is "' . $backto . '"', __FILE__);
+ common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
+
+ oid_authenticate($openid_url,
+ 'finishimmediate',
+ true);
}
-function oid_authenticate($openid_url, $returnto, $immediate=false) {
-
- $consumer = oid_consumer();
-
- if (!$consumer) {
- common_server_error(_('Cannot instantiate OpenID consumer object.'));
- return false;
- }
-
- common_ensure_session();
-
- $auth_request = $consumer->begin($openid_url);
-
- // Handle failure status return values.
- if (!$auth_request) {
- return _('Not a valid OpenID.');
- } else if (Auth_OpenID::isFailure($auth_request)) {
- return sprintf(_('OpenID failure: %s'), $auth_request->message);
- }
-
- $sreg_request = Auth_OpenID_SRegRequest::build(// Required
- array(),
- // Optional
- array('nickname',
- 'email',
- 'fullname',
- 'language',
- 'timezone',
- 'postcode',
- 'country'));
-
- if ($sreg_request) {
- $auth_request->addExtension($sreg_request);
- }
-
- $trust_root = common_local_url('public');
- $process_url = common_local_url($returnto);
-
- if ($auth_request->shouldSendRedirect()) {
- $redirect_url = $auth_request->redirectURL($trust_root,
- $process_url,
- $immediate);
- if (!$redirect_url) {
- } else if (Auth_OpenID::isFailure($redirect_url)) {
- return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
- } else {
- common_redirect($redirect_url);
- }
- } else {
- // Generate form markup and render it.
- $form_id = 'openid_message';
- $form_html = $auth_request->formMarkup($trust_root, $process_url,
- $immediate, array('id' => $form_id));
-
- # XXX: This is cheap, but things choke if we don't escape ampersands
- # in the HTML attributes
-
- $form_html = preg_replace('/&/', '&amp;', $form_html);
-
- // Display an error if the form markup couldn't be generated;
- // otherwise, render the HTML.
- if (Auth_OpenID::isFailure($form_html)) {
- $this->show_form(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
- } else {
- common_show_header(_('OpenID Auto-Submit'), NULL, NULL, '_oid_print_instructions');
- common_raw($form_html);
- common_element('script', NULL,
- '$(document).ready(function() { ' .
- ' $("#'. $form_id .'").submit(); '.
- '});');
- common_show_footer();
- }
- }
+function oid_authenticate($openid_url, $returnto, $immediate=false)
+{
+
+ $consumer = oid_consumer();
+
+ if (!$consumer) {
+ common_server_error(_('Cannot instantiate OpenID consumer object.'));
+ return false;
+ }
+
+ common_ensure_session();
+
+ $auth_request = $consumer->begin($openid_url);
+
+ // Handle failure status return values.
+ if (!$auth_request) {
+ return _('Not a valid OpenID.');
+ } else if (Auth_OpenID::isFailure($auth_request)) {
+ return sprintf(_('OpenID failure: %s'), $auth_request->message);
+ }
+
+ $sreg_request = Auth_OpenID_SRegRequest::build(// Required
+ array(),
+ // Optional
+ array('nickname',
+ 'email',
+ 'fullname',
+ 'language',
+ 'timezone',
+ 'postcode',
+ 'country'));
+
+ if ($sreg_request) {
+ $auth_request->addExtension($sreg_request);
+ }
+
+ $trust_root = common_local_url('public');
+ $process_url = common_local_url($returnto);
+
+ if ($auth_request->shouldSendRedirect()) {
+ $redirect_url = $auth_request->redirectURL($trust_root,
+ $process_url,
+ $immediate);
+ if (!$redirect_url) {
+ } else if (Auth_OpenID::isFailure($redirect_url)) {
+ return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
+ } else {
+ common_redirect($redirect_url);
+ }
+ } else {
+ // Generate form markup and render it.
+ $form_id = 'openid_message';
+ $form_html = $auth_request->formMarkup($trust_root, $process_url,
+ $immediate, array('id' => $form_id));
+
+ # XXX: This is cheap, but things choke if we don't escape ampersands
+ # in the HTML attributes
+
+ $form_html = preg_replace('/&/', '&amp;', $form_html);
+
+ // Display an error if the form markup couldn't be generated;
+ // otherwise, render the HTML.
+ if (Auth_OpenID::isFailure($form_html)) {
+ common_server_error(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
+ } else {
+ $action = new AutosubmitAction(); // see below
+ $action->form_html = $form_html;
+ $action->form_id = $form_id;
+ $action->prepare(array('action' => 'autosubmit'));
+ $action->handle(array('action' => 'autosubmit'));
+ }
+ }
}
# Half-assed attempt at a module-private function
-function _oid_print_instructions() {
- common_element('div', 'instructions',
- _('This form should automatically submit itself. '.
- 'If not, click the submit button to go to your '.
- 'OpenID provider.'));
+function _oid_print_instructions()
+{
+ common_element('div', 'instructions',
+ _('This form should automatically submit itself. '.
+ 'If not, click the submit button to go to your '.
+ 'OpenID provider.'));
}
# update a user from sreg parameters
-function oid_update_user(&$user, &$sreg) {
+function oid_update_user(&$user, &$sreg)
+{
- $profile = $user->getProfile();
+ $profile = $user->getProfile();
- $orig_profile = clone($profile);
+ $orig_profile = clone($profile);
- if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
- $profile->fullname = $sreg['fullname'];
- }
+ if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+ $profile->fullname = $sreg['fullname'];
+ }
- if ($sreg['country']) {
- if ($sreg['postcode']) {
- # XXX: use postcode to get city and region
- # XXX: also, store postcode somewhere -- it's valuable!
- $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
- } else {
- $profile->location = $sreg['country'];
- }
- }
+ if ($sreg['country']) {
+ if ($sreg['postcode']) {
+ # XXX: use postcode to get city and region
+ # XXX: also, store postcode somewhere -- it's valuable!
+ $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
+ } else {
+ $profile->location = $sreg['country'];
+ }
+ }
- # XXX save language if it's passed
- # XXX save timezone if it's passed
+ # XXX save language if it's passed
+ # XXX save timezone if it's passed
- if (!$profile->update($orig_profile)) {
- common_server_error(_('Error saving the profile.'));
- return false;
- }
+ if (!$profile->update($orig_profile)) {
+ common_server_error(_('Error saving the profile.'));
+ return false;
+ }
- $orig_user = clone($user);
+ $orig_user = clone($user);
- if ($sreg['email'] && Validate::email($sreg['email'], true)) {
- $user->email = $sreg['email'];
- }
+ if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+ $user->email = $sreg['email'];
+ }
- if (!$user->update($orig_user)) {
- common_server_error(_('Error saving the user.'));
- return false;
- }
+ if (!$user->update($orig_user)) {
+ common_server_error(_('Error saving the user.'));
+ return false;
+ }
+
+ return true;
+}
- return true;
+class AutosubmitAction extends Action
+{
+ var $form_html = null;
+ var $form_id = null;
+
+ function handle($args)
+ {
+ parent::handle($args);
+ $this->showPage();
+ }
+
+ function title()
+ {
+ return _('OpenID Auto-Submit');
+ }
+
+ function showContent()
+ {
+ $this->raw($this->form_html);
+ $this->element('script', null,
+ '$(document).ready(function() { ' .
+ ' $(\'#'. $this->form_id .'\').submit(); '.
+ '});');
+ }
}
diff --git a/lib/personal.php b/lib/personal.php
index 86433b486..900df0257 100644
--- a/lib/personal.php
+++ b/lib/personal.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * User profile page
+ *
+ * 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.
@@ -15,192 +18,44 @@
*
* 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); }
-
-class PersonalAction extends Action {
-
- function is_readonly() {
- return true;
- }
-
- function handle($args) {
- parent::handle($args);
- common_set_returnto($this->self_url());
- }
-
- function views_menu() {
-
- $user = NULL;
- $action = $this->trimmed('action');
- $nickname = $this->trimmed('nickname');
-
- if ($nickname) {
- $user = User::staticGet('nickname', $nickname);
- $user_profile = $user->getProfile();
- } else {
- $user_profile = false;
- }
-
- common_element_start('ul', array('id' => 'nav_views'));
-
- common_menu_item(common_local_url('all', array('nickname' =>
- $nickname)),
- _('Personal'),
- sprintf(_('%s and friends'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
- $action == 'all');
- common_menu_item(common_local_url('replies', array('nickname' =>
- $nickname)),
- _('Replies'),
- sprintf(_('Replies to %s'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
- $action == 'replies');
- common_menu_item(common_local_url('showstream', array('nickname' =>
- $nickname)),
- _('Profile'),
- ($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname,
- $action == 'showstream');
- common_menu_item(common_local_url('showfavorites', array('nickname' =>
- $nickname)),
- _('Favorites'),
- sprintf(_('%s\'s favorite notices'), ($user_profile) ? $user_profile->getBestName() : _('User')),
- $action == 'showfavorites');
-
- $cur = common_current_user();
-
- if ($cur && $cur->id == $user->id) {
-
- common_menu_item(common_local_url('inbox', array('nickname' =>
- $nickname)),
- _('Inbox'),
- _('Your incoming messages'),
- $action == 'inbox');
- common_menu_item(common_local_url('outbox', array('nickname' =>
- $nickname)),
- _('Outbox'),
- _('Your sent messages'),
- $action == 'outbox');
- }
-
- common_element_end('ul');
- }
-
- function show_feeds_list($feeds) {
- common_element_start('div', array('class' => 'feeds'));
- common_element('p', null, 'Feeds:');
- common_element_start('ul', array('class' => 'xoxo'));
-
- foreach ($feeds as $key => $value) {
- $this->common_feed_item($feeds[$key]);
- }
- common_element_end('ul');
- common_element_end('div');
- }
-
- function common_feed_item($feed) {
- $nickname = $this->trimmed('nickname');
-
- switch($feed['item']) {
- case 'notices': default:
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "$nickname's ".$feed['version']." notice feed";
- $feed['textContent'] = "RSS";
- break;
-
- case 'allrss':
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = $feed['version']." feed for $nickname and friends";
- $feed['textContent'] = "RSS";
- break;
-
- case 'repliesrss':
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = $feed['version']." feed for replies to $nickname";
- $feed['textContent'] = "RSS";
- break;
-
- case 'publicrss':
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "Public timeline ".$feed['version']." feed";
- $feed['textContent'] = "RSS";
- break;
-
- case 'publicatom':
- $feed_classname = "atom";
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "Public timeline ".$feed['version']." feed";
- $feed['textContent'] = "Atom";
- break;
+if (!defined('LACONICA')) {
+ exit(1);
+}
- case 'tagrss':
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = $feed['version']." feed for this tag";
- $feed['textContent'] = "RSS";
- break;
+/**
+ * Base class for user profile page
+ *
+ * @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/
+ */
- case 'favoritedrss':
- $feed_classname = $feed['type'];
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "Favorited ".$feed['version']." feed";
- $feed['textContent'] = "RSS";
- break;
+class PersonalAction extends Action
+{
- case 'foaf':
- $feed_classname = "foaf";
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "$nickname's FOAF file";
- $feed['textContent'] = "FOAF";
- break;
+ var $user = null;
- case 'favoritesrss':
- $feed_classname = "favorites";
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "Feed for favorites of $nickname";
- $feed['textContent'] = "RSS";
- break;
+ function isReadOnly()
+ {
+ return true;
+ }
- case 'usertimeline':
- $feed_classname = "atom";
- $feed_mimetype = "application/".$feed['type']."+xml";
- $feed_title = "$nickname's ".$feed['version']." notice feed";
- $feed['textContent'] = "Atom";
- break;
- }
- common_element_start('li');
- common_element('a', array('href' => $feed['href'],
- 'class' => $feed_classname,
- 'type' => $feed_mimetype,
- 'title' => $feed_title),
- $feed['textContent']);
- common_element_end('li');
- }
+ function handle($args)
+ {
+ parent::handle($args);
+ common_set_returnto($this->selfUrl());
+ }
-
- function source_link($source) {
- $source_name = _($source);
- switch ($source) {
- case 'web':
- case 'xmpp':
- case 'mail':
- case 'omb':
- case 'api':
- common_element('span', 'noticesource', $source_name);
- break;
- default:
- $ns = Notice_source::staticGet($source);
- if ($ns) {
- common_element('a', array('href' => $ns->url),
- $ns->name);
- } else {
- common_element('span', 'noticesource', $source_name);
- }
- break;
- }
- return;
- }
}
diff --git a/lib/personalgroupnav.php b/lib/personalgroupnav.php
new file mode 100644
index 000000000..63e6138df
--- /dev/null
+++ b/lib/personalgroupnav.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for all actions (~views)
+ *
+ * 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 Action
+ * @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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Base class for all actions
+ *
+ * This is the base class for all actions in the package. An action is
+ * more or less a "view" in an MVC framework.
+ *
+ * Actions are responsible for extracting and validating parameters; using
+ * model classes to read and write to the database; and doing ouput.
+ *
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class PersonalGroupNav extends Widget
+{
+ var $action = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $user = null;
+
+ // FIXME: we should probably pass this in
+
+ $action = $this->action->trimmed('action');
+ $nickname = $this->action->trimmed('nickname');
+
+ if ($nickname) {
+ $user = User::staticGet('nickname', $nickname);
+ $user_profile = $user->getProfile();
+ } else {
+ $user_profile = false;
+ }
+
+ $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');
+
+ $cur = common_current_user();
+
+ 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->elementEnd('ul');
+ }
+}
diff --git a/lib/personaltagcloudsection.php b/lib/personaltagcloudsection.php
new file mode 100644
index 000000000..0882822db
--- /dev/null
+++ b/lib/personaltagcloudsection.php
@@ -0,0 +1,86 @@
+<?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 PersonalTagCloudSection extends TagCloudSection
+{
+ var $user = null;
+
+ function __construct($out=null, $user=null)
+ {
+ parent::__construct($out);
+ $this->user = $user;
+ }
+
+ function title()
+ {
+ return sprintf(_('Tags in %s\'s notices'), $this->user->nickname);
+ }
+
+ function getTags()
+ {
+ $qry = 'SELECT notice_tag.tag, '.
+ 'sum(exp(-(now() - notice_tag.created)/%s)) as weight ' .
+ 'FROM notice_tag JOIN notice ' .
+ 'ON notice_tag.notice_id = notice.id ' .
+ 'WHERE notice.profile_id = %d ' .
+ 'GROUP BY notice_tag.tag ' .
+ 'ORDER BY weight DESC ';
+
+ $limit = TAGS_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $tag = Memcached_DataObject::cachedQuery('Notice_tag',
+ sprintf($qry,
+ common_config('tag', 'dropoff'),
+ $this->user->id),
+ 3600);
+ return $tag;
+ }
+
+}
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
new file mode 100644
index 000000000..89daaa563
--- /dev/null
+++ b/lib/popularnoticesection.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists 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 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('NOTICES_PER_SECTION', 6);
+
+/**
+ * Base class for sections showing lists of notices
+ *
+ * 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 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';
+
+ $offset = 0;
+ $limit = NOTICES_PER_SECTION + 1;
+
+ if (common_config('db', 'type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $notice = Memcached_DataObject::cachedQuery('Notice',
+ sprintf($qry, common_config('popular', 'dropoff')),
+ 1200);
+ return $notice;
+ }
+
+ function title()
+ {
+ return _('Popular notices');
+ }
+
+ function divId()
+ {
+ return 'popular_notices';
+ }
+}
diff --git a/lib/profilelist.php b/lib/profilelist.php
index 9079ea9d7..499d74f7b 100644
--- a/lib/profilelist.php
+++ b/lib/profilelist.php
@@ -1,10 +1,13 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Widget to show a list of profiles
+ *
+ * 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.
@@ -16,154 +19,202 @@
*
* 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 Public
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
*/
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
define('PROFILES_PER_PAGE', 20);
-class ProfileList {
-
- var $profile = NULL;
- var $owner = NULL;
- var $action = NULL;
-
- function __construct($profile, $owner=NULL, $action=NULL) {
- $this->profile = $profile;
- $this->owner = $owner;
- $this->action = $action;
- }
-
- function show_list() {
-
- common_element_start('ul', array('id' => 'profiles', 'class' => 'profile_list'));
-
- $cnt = 0;
-
- while ($this->profile->fetch()) {
- $cnt++;
- if($cnt > PROFILES_PER_PAGE) {
- break;
- }
- $this->show();
- }
-
- common_element_end('ul');
-
- return $cnt;
- }
-
- function show() {
-
- common_element_start('li', array('class' => 'profile_single',
- 'id' => 'profile-' . $this->profile->id));
-
- $user = common_current_user();
-
- if ($user && $user->id != $this->profile->id) {
- # XXX: special-case for user looking at own
- # subscriptions page
- if ($user->isSubscribed($this->profile)) {
- common_unsubscribe_form($this->profile);
- } else {
- common_subscribe_form($this->profile);
- }
- }
-
- $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
- common_element_start('a', array('href' => $this->profile->profileurl));
- common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
- 'class' => 'avatar stream',
- 'width' => AVATAR_STREAM_SIZE,
- 'height' => AVATAR_STREAM_SIZE,
- 'alt' =>
- ($this->profile->fullname) ? $this->profile->fullname :
- $this->profile->nickname));
- common_element_end('a');
- common_element_start('p');
- common_element_start('a', array('href' => $this->profile->profileurl,
- 'class' => 'nickname'));
- common_raw($this->highlight($this->profile->nickname));
- common_element_end('a');
- if ($this->profile->fullname) {
- common_text(' | ');
- common_element_start('span', 'fullname');
- common_raw($this->highlight($this->profile->fullname));
- common_element_end('span');
- }
- if ($this->profile->location) {
- common_text(' | ');
- common_element_start('span', 'location');
- common_raw($this->highlight($this->profile->location));
- common_element_end('span');
- }
- common_element_end('p');
- if ($this->profile->homepage) {
- common_element_start('p', 'website');
- common_element_start('a', array('href' => $this->profile->homepage));
- common_raw($this->highlight($this->profile->homepage));
- common_element_end('a');
- common_element_end('p');
- }
- if ($this->profile->bio) {
- common_element_start('p', 'bio');
- common_raw($this->highlight($this->profile->bio));
- common_element_end('p');
- }
-
- # If we're on a list with an owner (subscriptions or subscribers)...
-
- if ($this->owner) {
- # Get tags
- $tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
-
- common_element_start('div', 'tags_user');
- common_element_start('dl');
- common_element_start('dt');
- if ($user->id == $this->owner->id) {
- common_element('a', array('href' => common_local_url('tagother',
- array('id' => $this->profile->id))),
- _('Tags'));
- } else {
- common_text(_('Tags'));
- }
- common_text(":");
- common_element_end('dt');
- common_element_start('dd');
- if ($tags) {
- common_element_start('ul', 'tags xoxo');
- foreach ($tags as $tag) {
- common_element_start('li');
- common_element('a', array('rel' => 'tag',
- 'href' => common_local_url($this->action,
- array('nickname' => $this->owner->nickname,
- 'tag' => $tag))),
- $tag);
- common_element_end('li');
- }
- common_element_end('ul');
- } else {
- common_text(_('(none)'));
- }
- common_element_end('dd');
- common_element_end('dl');
- common_element_end('div');
- }
+/**
+ * Widget to show a list of profiles
+ *
+ * @category Public
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @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 ProfileList extends Widget
+{
+ /** Current profile, profile query. */
+ var $profile = null;
+ /** Owner of this list */
+ var $owner = null;
+ /** Action object using us. */
+ var $action = null;
+
+ function __construct($profile, $owner=null, $action=null)
+ {
+ parent::__construct($action);
+
+ $this->profile = $profile;
+ $this->owner = $owner;
+ $this->action = $action;
+ }
+
+ function show()
+ {
+
+ $this->out->elementStart('ul', 'profiles');
+
+ $cnt = 0;
+
+ while ($this->profile->fetch()) {
+ $cnt++;
+ if($cnt > PROFILES_PER_PAGE) {
+ break;
+ }
+ $this->showProfile();
+ }
+
+ $this->out->elementEnd('ul');
+
+ return $cnt;
+ }
+
+ function showProfile()
+ {
+ $this->out->elementStart('li', array('class' => 'profile',
+ 'id' => 'profile-' . $this->profile->id));
+
+ $user = common_current_user();
+
+ $this->out->elementStart('div', 'entity_profile vcard');
+
+ $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
+ $this->out->elementStart('a', array('href' => $this->profile->profileurl,
+ 'class' => 'url'));
+ $this->out->element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'photo avatar',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($this->profile->fullname) ? $this->profile->fullname :
+ $this->profile->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) {
+ $this->out->elementStart('dl', 'entity_fn');
+ $this->out->element('dt', null, 'Full name');
+ $this->out->elementStart('dd');
+ $this->out->elementStart('span', 'fn');
+ $this->out->raw($this->highlight($this->profile->fullname));
+ $this->out->elementEnd('span');
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->profile->location) {
+ $this->out->elementStart('dl', 'entity_location');
+ $this->out->element('dt', null, _('Location'));
+ $this->out->elementStart('dd', 'location');
+ $this->out->raw($this->highlight($this->profile->location));
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->profile->homepage) {
+ $this->out->elementStart('dl', 'entity_url');
+ $this->out->element('dt', null, _('URL'));
+ $this->out->elementStart('dd');
+ $this->out->elementStart('a', array('href' => $this->profile->homepage,
+ 'class' => 'url'));
+ $this->out->raw($this->highlight($this->profile->homepage));
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+ if ($this->profile->bio) {
+ $this->out->elementStart('dl', 'entity_note');
+ $this->out->element('dt', null, _('Note'));
+ $this->out->elementStart('dd', 'note');
+ $this->out->raw($this->highlight($this->profile->bio));
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
+
+ # If we're on a list with an owner (subscriptions or subscribers)...
+
+ if ($this->owner) {
+ # Get tags
+ $tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
+
+ $this->out->elementStart('dl', 'entity_tags');
+ $this->out->elementStart('dt');
+ if ($user->id == $this->owner->id) {
+ $this->out->element('a', array('href' => common_local_url('tagother',
+ array('id' => $this->profile->id))),
+ _('Tags'));
+ } else {
+ $this->out->text(_('Tags'));
+ }
+ $this->out->elementEnd('dt');
+ $this->out->elementStart('dd');
+ if ($tags) {
+ $this->out->elementStart('ul', 'tags xoxo');
+ foreach ($tags as $tag) {
+ $this->out->elementStart('li');
+ $this->out->element('span', 'mark_hash', '#');
+ $this->out->element('a', array('rel' => 'tag',
+ 'href' => common_local_url($this->action->trimmed('action'),
+ array('nickname' => $this->owner->nickname,
+ 'tag' => $tag))),
+ $tag);
+ $this->out->elementEnd('li');
+ }
+ $this->out->elementEnd('ul');
+ } else {
+ $this->out->text(_('(none)'));
+ }
+ $this->out->elementEnd('dd');
+ $this->out->elementEnd('dl');
+ }
if ($user && $user->id == $this->owner->id) {
- $this->show_owner_controls($this->profile);
+ $this->showOwnerControls($this->profile);
+ }
+
+ $this->out->elementEnd('div');
+
+ if ($user && $user->id != $this->profile->id) {
+ # XXX: special-case for user looking at own
+ # subscriptions page
+ if ($user->isSubscribed($this->profile)) {
+ $usf = new UnsubscribeForm($this->out, $this->profile);
+ $usf->show();
+ } else {
+ $sf = new SubscribeForm($this->out, $this->profile);
+ $sf->show();
+ }
}
- common_element_end('li');
- }
+ $this->out->elementEnd('li');
+ }
/* Override this in subclasses. */
- function show_owner_controls($profile) {
+ function showOwnerControls($profile)
+ {
return;
}
- function highlight($text) {
- return htmlspecialchars($text);
- }
-} \ No newline at end of file
+ function highlight($text)
+ {
+ return htmlspecialchars($text);
+ }
+}
diff --git a/lib/profileminilist.php b/lib/profileminilist.php
new file mode 100644
index 000000000..56b768419
--- /dev/null
+++ b/lib/profileminilist.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Widget to show a list of profiles
+ *
+ * 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 Public
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profilelist.php';
+
+define('PROFILES_PER_MINILIST', 80);
+
+/**
+ * Widget to show a list of profiles, good for sidebar
+ *
+ * @category Public
+ * @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 ProfileMiniList extends ProfileList
+{
+ function show()
+ {
+ $this->out->elementStart('ul', 'entities users xoxo');
+
+ $cnt = 0;
+
+ while ($this->profile->fetch()) {
+ $cnt++;
+ if($cnt > PROFILES_PER_MINILIST) {
+ break;
+ }
+ $this->showProfile();
+ }
+
+ $this->out->elementEnd('ul');
+
+ return $cnt;
+ }
+
+ function showProfile()
+ {
+ $this->out->elementStart('li', 'vcard');
+ $this->out->elementStart('a', array('title' => ($this->profile->fullname) ?
+ $this->profile->fullname :
+ $this->profile->nickname,
+ 'href' => $this->profile->profileurl,
+ 'rel' => 'contact member',
+ 'class' => 'url'));
+ $avatar = $this->profile->getAvatar(AVATAR_MINI_SIZE);
+ $this->out->element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar photo',
+ 'alt' => ($this->profile->fullname) ?
+ $this->profile->fullname :
+ $this->profile->nickname));
+ $this->out->element('span', 'fn nickname', $this->profile->nickname);
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('li');
+ }
+}
diff --git a/lib/profilesection.php b/lib/profilesection.php
new file mode 100644
index 000000000..3642ae164
--- /dev/null
+++ b/lib/profilesection.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists of people
+ *
+ * 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('PROFILES_PER_SECTION', 6);
+
+/**
+ * Base class for sections
+ *
+ * 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 ProfileSection extends Section
+{
+ function showContent()
+ {
+ $profiles = $this->getProfiles();
+
+ if (!$profiles) {
+ return false;
+ }
+
+ $cnt = 0;
+
+ $this->out->elementStart('table');
+ $this->out->elementStart('tbody');
+ while ($profiles->fetch() && ++$cnt <= PROFILES_PER_SECTION) {
+ $this->showProfile($profiles);
+ }
+ $this->out->elementEnd('tbody');
+ $this->out->elementEnd('table');
+
+ return ($cnt > PROFILES_PER_SECTION);
+ }
+
+ function getProfiles()
+ {
+ return null;
+ }
+
+ function showProfile($profile)
+ {
+ $this->out->elementStart('tr');
+ $this->out->elementStart('td');
+ $this->out->elementStart('span', 'vcard');
+ $this->out->elementStart('a', array('title' => ($profile->fullname) ?
+ $profile->fullname :
+ $profile->nickname,
+ 'href' => $profile->profileurl,
+ 'rel' => 'contact member',
+ 'class' => 'url'));
+ $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
+ $this->out->element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar photo',
+ 'alt' => ($profile->fullname) ?
+ $profile->fullname :
+ $profile->nickname));
+ $this->out->element('span', 'fn nickname', $profile->nickname);
+ $this->out->elementEnd('span');
+ $this->out->elementEnd('a');
+ $this->out->elementEnd('td');
+ if ($profile->value) {
+ $this->out->element('td', 'value', $profile->value);
+ }
+
+ $this->out->elementEnd('tr');
+ }
+}
diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php
new file mode 100644
index 000000000..d72475e20
--- /dev/null
+++ b/lib/publicgroupnav.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Menu for public group of 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 Menu
+ * @package Laconica
+ * @author Evan Prodromou <evan@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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Menu for public group of actions
+ *
+ * @category Output
+ * @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 Widget
+ */
+
+class PublicGroupNav extends Widget
+{
+ var $action = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $action_name = $this->action->trimmed('action');
+
+ $this->action->elementStart('ul', array('class' => 'nav'));
+
+ $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('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');
+ }
+
+ $this->out->menuItem(common_local_url('favorited'), _('Popular'),
+ _("Popular notices"), $action_name == 'favorited', 'nav_timeline_favorited');
+
+ $this->action->elementEnd('ul');
+ }
+}
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index 23f295c45..9ce9e32b3 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -25,108 +25,122 @@ require_once(INSTALLDIR.'/lib/daemon.php');
require_once(INSTALLDIR.'/classes/Queue_item.php');
require_once(INSTALLDIR.'/classes/Notice.php');
-class QueueHandler extends Daemon {
+class QueueHandler extends Daemon
+{
- var $_id = 'generic';
+ var $_id = 'generic';
- function QueueHandler($id=NULL) {
- if ($id) {
- $this->set_id($id);
- }
- }
-
- function class_name() {
- return ucfirst($this->transport()) . 'Handler';
- }
+ function QueueHandler($id=null)
+ {
+ if ($id) {
+ $this->set_id($id);
+ }
+ }
+
+ function class_name()
+ {
+ return ucfirst($this->transport()) . 'Handler';
+ }
- function name() {
- return strtolower($this->class_name().'.'.$this->get_id());
- }
-
- function get_id() {
- return $this->_id;
- }
+ function name()
+ {
+ return strtolower($this->class_name().'.'.$this->get_id());
+ }
+
+ function get_id()
+ {
+ return $this->_id;
+ }
- function set_id($id) {
- $this->_id = $id;
- }
-
- function transport() {
- return NULL;
- }
-
- function start() {
- }
-
- function finish() {
- }
+ function set_id($id)
+ {
+ $this->_id = $id;
+ }
+
+ function transport()
+ {
+ return null;
+ }
+
+ function start()
+ {
+ }
+
+ function finish()
+ {
+ }
- function handle_notice($notice) {
- return true;
- }
-
- function run() {
- if (!$this->start()) {
- return false;
- }
- $this->log(LOG_INFO, 'checking for queued notices');
- $transport = $this->transport();
- do {
- $qi = Queue_item::top($transport);
- if ($qi) {
- $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
- $notice = Notice::staticGet($qi->notice_id);
- if ($notice) {
- $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
- # XXX: what to do if broadcast fails?
- $result = $this->handle_notice($notice);
- if (!$result) {
- $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
- $orig = $qi;
- $qi->claimed = NULL;
- $qi->update($orig);
- $this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id);
- continue;
- }
- $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
- $notice->free();
- unset($notice);
- $notice = NULL;
- } else {
- $this->log(LOG_WARNING, 'queue item for notice that does not exist');
- }
- $qi->delete();
- $qi->free();
- unset($qi);
- $this->idle(0);
- } else {
- $this->clear_old_claims();
- $this->idle(5);
- }
- } while (true);
- if (!$this->finish()) {
- return false;
- }
- return true;
- }
+ function handle_notice($notice)
+ {
+ return true;
+ }
+
+ function run()
+ {
+ if (!$this->start()) {
+ return false;
+ }
+ $this->log(LOG_INFO, 'checking for queued notices');
+ $transport = $this->transport();
+ do {
+ $qi = Queue_item::top($transport);
+ if ($qi) {
+ $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
+ $notice = Notice::staticGet($qi->notice_id);
+ if ($notice) {
+ $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
+ # XXX: what to do if broadcast fails?
+ $result = $this->handle_notice($notice);
+ if (!$result) {
+ $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
+ $orig = $qi;
+ $qi->claimed = null;
+ $qi->update($orig);
+ $this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id);
+ continue;
+ }
+ $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
+ $notice->free();
+ unset($notice);
+ $notice = null;
+ } else {
+ $this->log(LOG_WARNING, 'queue item for notice that does not exist');
+ }
+ $qi->delete();
+ $qi->free();
+ unset($qi);
+ $this->idle(0);
+ } else {
+ $this->clear_old_claims();
+ $this->idle(5);
+ }
+ } while (true);
+ if (!$this->finish()) {
+ return false;
+ }
+ return true;
+ }
- function idle($timeout=0) {
- if ($timeout>0) {
- sleep($timeout);
- }
- }
-
- function clear_old_claims() {
- $qi = new Queue_item();
- $qi->transport = $this->transport();
- $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
- $qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
- $qi->free();
- unset($qi);
- }
-
- function log($level, $msg) {
- common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
- }
+ function idle($timeout=0)
+ {
+ if ($timeout>0) {
+ sleep($timeout);
+ }
+ }
+
+ function clear_old_claims()
+ {
+ $qi = new Queue_item();
+ $qi->transport = $this->transport();
+ $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
+ $qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
+ $qi->free();
+ unset($qi);
+ }
+
+ function log($level, $msg)
+ {
+ common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
+ }
}
- \ No newline at end of file
+ \ No newline at end of file
diff --git a/lib/rssaction.php b/lib/rssaction.php
index 777511506..f19c8e1c5 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Base class for RSS 1.0 feed 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.
@@ -15,175 +18,245 @@
*
* 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 Mail
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Earle Martin <earle@downlode.org>
+ * @copyright 2008-9 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('DEFAULT_RSS_LIMIT', 48);
-class Rss10Action extends Action {
-
- # This will contain the details of each feed item's author and be used to generate SIOC data.
- var $creators = array();
-
- function is_readonly() {
- return true;
- }
-
- function handle($args) {
- parent::handle($args);
- $limit = (int) $this->trimmed('limit');
- if ($limit == 0) {
- $limit = DEFAULT_RSS_LIMIT;
- }
- $this->show_rss($limit);
- }
-
- function init() {
- return true;
- }
-
- function get_notices() {
- return array();
- }
-
- function get_channel() {
- return array('url' => '',
- 'title' => '',
- 'link' => '',
- 'description' => '');
- }
-
- function get_image() {
- return NULL;
- }
-
- function show_rss($limit=0) {
-
- if (!$this->init()) {
- return;
- }
-
- $notices = $this->get_notices($limit);
-
- $this->init_rss();
- $this->show_channel($notices);
- $this->show_image();
-
- foreach ($notices as $n) {
- $this->show_item($n);
- }
-
- $this->show_creators();
- $this->end_rss();
- }
-
- function show_channel($notices) {
-
- $channel = $this->get_channel();
- $image = $this->get_image();
-
- common_element_start('channel', array('rdf:about' => $channel['url']));
- common_element('title', NULL, $channel['title']);
- common_element('link', NULL, $channel['link']);
- common_element('description', NULL, $channel['description']);
- common_element('cc:licence', array('rdf:resource' => common_config('license','url')));
-
- if ($image) {
- common_element('image', array('rdf:resource' => $image));
- }
-
- common_element_start('items');
- common_element_start('rdf:Seq');
-
- foreach ($notices as $notice) {
- common_element('sioct:MicroblogPost', array('rdf:resource' => $notice->uri));
- }
-
- common_element_end('rdf:Seq');
- common_element_end('items');
-
- common_element_end('channel');
- }
-
- function show_image() {
- $image = $this->get_image();
- if ($image) {
- $channel = $this->get_channel();
- common_element_start('image', array('rdf:about' => $image));
- common_element('title', NULL, $channel['title']);
- common_element('link', NULL, $channel['link']);
- common_element('url', NULL, $image);
- common_element_end('image');
- }
- }
-
- function show_item($notice) {
- $profile = Profile::staticGet($notice->profile_id);
- $nurl = common_local_url('shownotice', array('notice' => $notice->id));
- $creator_uri = common_profile_uri($profile);
- common_element_start('item', array('rdf:about' => $notice->uri));
- $title = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
- common_element('title', NULL, $title);
- common_element('link', NULL, $nurl);
- common_element('description', NULL, $profile->nickname."'s status on ".common_exact_date($notice->created));
- common_element('dc:date', NULL, common_date_w3dtf($notice->created));
- common_element('dc:creator', NULL, ($profile->fullname) ? $profile->fullname : $profile->nickname);
- common_element('sioc:has_creator', array('rdf:resource' => $creator_uri));
- common_element('laconica:postIcon', array('rdf:resource' => common_profile_avatar_url($profile)));
- common_element('cc:licence', array('rdf:resource' => common_config('license', 'url')));
- common_element_end('item');
- $this->creators[$creator_uri] = $profile;
- }
-
- function show_creators() {
- foreach ($this->creators as $uri => $profile) {
- $id = $profile->id;
- $nickname = $profile->nickname;
- common_element_start('sioc:User', array('rdf:about' => $uri));
- common_element('foaf:nick', NULL, $nickname);
- if ($profile->fullname) {
- common_element('foaf:name', NULL, $profile->fullname);
- }
- common_element('sioc:id', NULL, $id);
- $avatar = common_profile_avatar_url($profile);
- common_element('sioc:avatar', array('rdf:resource' => $avatar));
- common_element_end('sioc:User');
- }
- }
-
- function init_rss() {
- $channel = $this->get_channel();
- header('Content-Type: application/rdf+xml');
-
- common_start_xml();
- common_element_start('rdf:RDF', array('xmlns:rdf' =>
- 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
- 'xmlns:dc' =>
- 'http://purl.org/dc/elements/1.1/',
- 'xmlns:cc' =>
- 'http://web.resource.org/cc/',
+class Rss10Action extends Action
+{
+ # This will contain the details of each feed item's author and be used to generate SIOC data.
+
+ var $creators = array();
+ var $limit = DEFAULT_RSS_LIMIT;
+
+ /**
+ * Constructor
+ *
+ * Just wraps the Action constructor.
+ *
+ * @param string $output URI to output to, default = stdout
+ * @param boolean $indent Whether to indent output, default true
+ *
+ * @see Action::__construct
+ */
+
+ function __construct($output='php://output', $indent=true)
+ {
+ parent::__construct($output, $indent);
+ }
+
+ /**
+ * Do we need to write to the database?
+ *
+ * @return boolean true
+ */
+
+ function isReadonly()
+ {
+ return true;
+ }
+
+ /**
+ * Read arguments and initialize members
+ *
+ * @param array $args Arguments from $_REQUEST
+ * @return boolean success
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+ $this->limit = (int) $this->trimmed('limit');
+ if ($this->limit == 0) {
+ $this->limit = DEFAULT_RSS_LIMIT;
+ }
+ return true;
+ }
+
+ /**
+ * Handle a request
+ *
+ * @param array $args Arguments from $_REQUEST
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+ $this->showRss($this->limit);
+ }
+
+ /**
+ * Get the notices to output in this stream
+ *
+ * @return array an array of Notice objects sorted in reverse chron
+ */
+
+ function getNotices()
+ {
+ return array();
+ }
+
+ /**
+ * Get a description of the channel
+ *
+ * Returns an array with the following
+ * @return array
+ */
+
+ function getChannel()
+ {
+ return array('url' => '',
+ 'title' => '',
+ 'link' => '',
+ 'description' => '');
+ }
+
+ function getImage()
+ {
+ return null;
+ }
+
+ function showRss($limit=0)
+ {
+ $notices = $this->getNotices($limit);
+
+ $this->initRss();
+ $this->showChannel($notices);
+ $this->showImage();
+
+ foreach ($notices as $n) {
+ $this->showItem($n);
+ }
+
+ $this->showCreators();
+ $this->endRss();
+ }
+
+ function showChannel($notices)
+ {
+
+ $channel = $this->getChannel();
+ $image = $this->getImage();
+
+ $this->elementStart('channel', array('rdf:about' => $channel['url']));
+ $this->element('title', null, $channel['title']);
+ $this->element('link', null, $channel['link']);
+ $this->element('description', null, $channel['description']);
+ $this->element('cc:licence', array('rdf:resource' => common_config('license','url')));
+
+ if ($image) {
+ $this->element('image', array('rdf:resource' => $image));
+ }
+
+ $this->elementStart('items');
+ $this->elementStart('rdf:Seq');
+
+ foreach ($notices as $notice) {
+ $this->element('sioct:MicroblogPost', array('rdf:resource' => $notice->uri));
+ }
+
+ $this->elementEnd('rdf:Seq');
+ $this->elementEnd('items');
+
+ $this->elementEnd('channel');
+ }
+
+ function showImage()
+ {
+ $image = $this->getImage();
+ if ($image) {
+ $channel = $this->getChannel();
+ common_element_start('image', array('rdf:about' => $image));
+ common_element('title', null, $channel['title']);
+ common_element('link', null, $channel['link']);
+ common_element('url', null, $image);
+ common_element_end('image');
+ }
+ }
+
+ function showItem($notice)
+ {
+ $profile = Profile::staticGet($notice->profile_id);
+ $nurl = common_local_url('shownotice', array('notice' => $notice->id));
+ $creator_uri = common_profile_uri($profile);
+ $this->elementStart('item', array('rdf:about' => $notice->uri));
+ $title = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+ $this->element('title', null, $title);
+ $this->element('link', null, $nurl);
+ $this->element('description', null, $profile->nickname."'s status on ".common_exact_date($notice->created));
+ $this->element('dc:date', null, common_date_w3dtf($notice->created));
+ $this->element('dc:creator', null, ($profile->fullname) ? $profile->fullname : $profile->nickname);
+ $this->element('sioc:has_creator', array('rdf:resource' => $creator_uri));
+ $this->element('laconica:postIcon', array('rdf:resource' => common_profile_avatar_url($profile)));
+ $this->element('cc:licence', array('rdf:resource' => common_config('license', 'url')));
+ $this->elementEnd('item');
+ $this->creators[$creator_uri] = $profile;
+ }
+
+ function showCreators()
+ {
+ foreach ($this->creators as $uri => $profile) {
+ $id = $profile->id;
+ $nickname = $profile->nickname;
+ $this->elementStart('sioc:User', array('rdf:about' => $uri));
+ $this->element('foaf:nick', null, $nickname);
+ if ($profile->fullname) {
+ $this->element('foaf:name', null, $profile->fullname);
+ }
+ $this->element('sioc:id', null, $id);
+ $avatar = common_profile_avatar_url($profile);
+ $this->element('sioc:avatar', array('rdf:resource' => $avatar));
+ $this->elementEnd('sioc:User');
+ }
+ }
+
+ function initRss()
+ {
+ $channel = $this->getChannel();
+ header('Content-Type: application/rdf+xml');
+
+ $this->startXml();
+ $this->elementStart('rdf:RDF', array('xmlns:rdf' =>
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'xmlns:dc' =>
+ 'http://purl.org/dc/elements/1.1/',
+ 'xmlns:cc' =>
+ 'http://web.resource.org/cc/',
'xmlns:content' =>
'http://purl.org/rss/1.0/modules/content/',
- 'xmlns:foaf' =>
- 'http://xmlns.com/foaf/0.1/',
- 'xmlns:sioc' =>
- 'http://rdfs.org/sioc/ns#',
- 'xmlns:sioct' =>
- 'http://rdfs.org/sioc/types#',
- 'xmlns:laconica' =>
- 'http://laconi.ca/ont/',
- 'xmlns' => 'http://purl.org/rss/1.0/'));
- common_element_start('sioc:Site', array('rdf:about' => common_root_url()));
- common_element('sioc:name', NULL, common_config('site', 'name'));
- common_element_start('sioc:container_of');
- common_element('sioc:Container', array('rdf:about' =>
- $channel['url']));
- common_element_end('sioc:container_of');
- common_element_end('sioc:Site');
- }
-
- function end_rss() {
- common_element_end('rdf:RDF');
- }
+ 'xmlns:foaf' =>
+ 'http://xmlns.com/foaf/0.1/',
+ 'xmlns:sioc' =>
+ 'http://rdfs.org/sioc/ns#',
+ 'xmlns:sioct' =>
+ 'http://rdfs.org/sioc/types#',
+ 'xmlns:laconica' =>
+ 'http://laconi.ca/ont/',
+ 'xmlns' => 'http://purl.org/rss/1.0/'));
+ $this->elementStart('sioc:Site', array('rdf:about' => common_root_url()));
+ $this->element('sioc:name', null, common_config('site', 'name'));
+ $this->elementStart('sioc:container_of');
+ $this->element('sioc:Container', array('rdf:about' =>
+ $channel['url']));
+ $this->elementEnd('sioc:container_of');
+ $this->elementEnd('sioc:Site');
+ }
+
+ function endRss()
+ {
+ $this->elementEnd('rdf:RDF');
+ }
}
+
diff --git a/lib/search_engines.php b/lib/search_engines.php
index 7fcc1ffcb..559107910 100644
--- a/lib/search_engines.php
+++ b/lib/search_engines.php
@@ -19,33 +19,40 @@
if (!defined('LACONICA')) { exit(1); }
-class SearchEngine {
+class SearchEngine
+{
protected $target;
protected $table;
- function __construct($target, $table) {
+ function __construct($target, $table)
+ {
$this->target = $target;
$this->table = $table;
}
- function query($q) {
+ function query($q)
+ {
}
- function limit($offset, $count, $rss = false) {
+ function limit($offset, $count, $rss = false)
+ {
return $this->target->limit($offset, $count);
}
- function set_sort_mode($mode) {
+ function set_sort_mode($mode)
+ {
if ('chron' === $mode)
return $this->target->orderBy('created desc');
}
}
-class SphinxSearch extends SearchEngine {
+class SphinxSearch extends SearchEngine
+{
private $sphinx;
private $connected;
- function __construct($target, $table) {
+ function __construct($target, $table)
+ {
$fp = @fsockopen(common_config('sphinx', 'server'), common_config('sphinx', 'port'));
if (!$fp) {
$this->connected = false;
@@ -58,11 +65,13 @@ class SphinxSearch extends SearchEngine {
$this->connected = true;
}
- function is_connected() {
+ function is_connected()
+ {
return $this->connected;
}
- function limit($offset, $count, $rss = false) {
+ function limit($offset, $count, $rss = false)
+ {
//FIXME without LARGEST_POSSIBLE, the most recent results aren't returned
// this probably has a large impact on performance
$LARGEST_POSSIBLE = 1e6;
@@ -78,7 +87,8 @@ class SphinxSearch extends SearchEngine {
return $this->target->limit(0, $count);
}
- function query($q) {
+ function query($q)
+ {
$result = $this->sphinx->query($q, $this->table);
if (!isset($result['matches'])) return false;
$id_set = join(', ', array_keys($result['matches']));
@@ -86,7 +96,8 @@ class SphinxSearch extends SearchEngine {
return true;
}
- function set_sort_mode($mode) {
+ function set_sort_mode($mode)
+ {
if ('chron' === $mode) {
$this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts');
return $this->target->orderBy('created desc');
@@ -94,19 +105,23 @@ class SphinxSearch extends SearchEngine {
}
}
-class MySQLSearch extends SearchEngine {
- function query($q) {
+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).'\')');
+ 'against (\''.addslashes($q).'\')');
if ('identica_notices' === $this->table)
return $this->target->whereAdd('MATCH(content) ' .
- 'against (\''.addslashes($q).'\')');
+ 'against (\''.addslashes($q).'\')');
}
}
-class PGSearch extends SearchEngine {
- function query($q) {
+class PGSearch extends SearchEngine
+{
+ function query($q)
+ {
if ('identica_people' === $this->table)
return $this->target->whereAdd('textsearch @@ plainto_tsquery(\''.addslashes($q).'\')');
if ('identica_notices' === $this->table)
diff --git a/lib/searchaction.php b/lib/searchaction.php
index f99883b25..70e63146a 100644
--- a/lib/searchaction.php
+++ b/lib/searchaction.php
@@ -1,5 +1,16 @@
<?php
-/*
+/**
+ * Base search action class.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @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.
*
@@ -17,94 +28,107 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/searchgroupnav.php';
-class SearchAction extends Action {
+/**
+ * Base search action class.
+ *
+ * @category Action
+ * @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/
+ */
+class SearchAction extends Action
+{
+ /**
+ * Return true if read only.
+ *
+ * @return boolean true
+ */
+ function isReadOnly()
+ {
+ return true;
+ }
- function is_readonly() {
- return true;
- }
+ function handle($args)
+ {
+ parent::handle($args);
+ $this->showPage();
+ }
- function handle($args) {
- parent::handle($args);
- $this->show_form();
- }
+ /**
+ * Show tabset for this page
+ *
+ * Uses the SearchGroupNav widget
+ *
+ * @return void
+ * @see SearchGroupNav
+ */
- function show_top($arr=NULL) {
- if ($arr) {
- $error = $arr[1];
- }
- if ($error) {
- common_element('p', 'error', $error);
- } else {
- $instr = $this->get_instructions();
- $output = common_markup_to_html($instr);
- common_element_start('div', 'instructions');
- common_raw($output);
- common_element_end('div');
- }
- $this->search_menu();
- }
+ function showLocalNav()
+ {
+ $nav = new SearchGroupNav($this, $this->trimmed('q'));
+ $nav->show();
+ }
- function get_title() {
- return NULL;
- }
+ function showTop($arr=null)
+ {
+ if ($arr) {
+ $error = $arr[1];
+ }
+ if ($error) {
+ $this->element('p', 'error', $error);
+ } else {
+ $instr = $this->getInstructions();
+ $output = common_markup_to_html($instr);
+ $this->elementStart('div', 'instructions');
+ $this->raw($output);
+ $this->elementEnd('div');
+ }
+ }
- function show_header($arr) {
- return;
- }
+ function title()
+ {
+ return null;
+ }
- function show_form($error=NULL) {
- global $config;
+ function showNoticeForm() {
+ // remote post notice form
+ }
- $q = $this->trimmed('q');
- $page = $this->trimmed('page', 1);
+ function showContent() {
+ $this->showTop();
+ $this->showForm();
+ }
- common_show_header($this->get_title(), array($this, 'show_header'), array($q, $error),
- array($this, 'show_top'));
- common_element_start('form', array('method' => 'get',
- 'id' => 'login',
- 'action' => common_local_url($this->trimmed('action'))));
- common_element_start('p');
- if (!isset($config['site']['fancy']) || !$config['site']['fancy']) {
- common_element('input', array('name' => 'action',
- 'type' => 'hidden',
- 'value' => $this->trimmed('action')));
- }
- common_element('input', array('name' => 'q',
- 'id' => 'q',
- 'type' => 'text',
- 'class' => 'input_text',
- 'value' => ($q) ? $q : ''));
- common_text(' ');
- common_element('input', array('type' => 'submit',
- 'id' => 'search',
- 'name' => 'search',
- 'class' => 'submit',
- 'value' => _('Search')));
+ function showForm($error=null)
+ {
+ global $config;
- common_element_end('p');
- common_element_end('form');
- if ($q) {
- $this->show_results($q, $page);
- }
- common_show_footer();
- }
+ $q = $this->trimmed('q');
+ $page = $this->trimmed('page', 1);
+ $this->elementStart('form', array('method' => 'get',
+ 'id' => 'login',
+ 'action' => common_local_url($this->trimmed('action'))));
+ $this->elementStart('p');
+ if (!isset($config['site']['fancy']) || !$config['site']['fancy']) {
+ $this->hidden('action', $this->trimmed('action'));
+ }
+ $this->input('q', '', $q);
+ $this->text(' ');
+ $this->submit('search', 'Search');
- function search_menu() {
- # action => array('prompt', 'title', $args)
- $action = $this->trimmed('action');
- $menu =
- array('peoplesearch' =>
- array(
- _('People'),
- _('Find people on this site'),
- ($action != 'peoplesearch' && $this->trimmed('q')) ? array('q' => $this->trimmed('q')) : NULL),
- 'noticesearch' =>
- array( _('Text'),
- _('Find content of notices'),
- ($action != 'noticesearch' && $this->trimmed('q')) ? array('q' => $this->trimmed('q')) : NULL)
- );
- $this->nav_menu($menu);
- }
+ $this->elementEnd('p');
+ $this->elementEnd('form');
+ if ($q) {
+ $this->showResults($q, $page);
+ }
+ }
}
+
diff --git a/lib/searchgroupnav.php b/lib/searchgroupnav.php
new file mode 100644
index 000000000..4ea226692
--- /dev/null
+++ b/lib/searchgroupnav.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Menu for search 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 Menu
+ * @package Laconica
+ * @author Evan Prodromou <evan@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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Menu for public group of actions
+ *
+ * @category Output
+ * @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 Widget
+ */
+
+class SearchGroupNav extends Widget
+{
+ var $action = null;
+ var $q = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null, $q = null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ $this->q = $q;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $action_name = $this->action->trimmed('action');
+ $this->action->elementStart('ul', array('class' => 'nav'));
+ $args = array();
+ if ($this->q) {
+ $args['q'] = $this->q;
+ }
+ $this->out->menuItem(common_local_url('peoplesearch', $args), _('People'),
+ _('Find people on this site'), $action_name == 'peoplesearch', 'nav_search_people');
+ $this->out->menuItem(common_local_url('noticesearch', $args), _('Notice'),
+ _('Find content of notices'), $action_name == 'noticesearch', 'nav_search_notice');
+ $this->action->elementEnd('ul');
+ }
+}
+
diff --git a/lib/section.php b/lib/section.php
new file mode 100644
index 000000000..0c32ddcf8
--- /dev/null
+++ b/lib/section.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections (sidebar widgets)
+ *
+ * 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);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Base class for sections
+ *
+ * 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 Section extends Widget
+{
+ /**
+ * Show the form
+ *
+ * Uses a recipe to output the form.
+ *
+ * @return void
+ * @see Widget::show()
+ */
+
+ function show()
+ {
+ $this->out->elementStart('div',
+ array('id' => $this->divId(),
+ 'class' => 'section'));
+
+ $this->out->element('h2', null,
+ $this->title());
+
+ $have_more = $this->showContent();
+
+ if ($have_more) {
+ $this->out->elementStart('p');
+ $this->out->element('a', array('href' => $this->moreUrl(),
+ 'class' => 'more'),
+ $this->moreTitle());
+ $this->out->elementEnd('p');
+ }
+
+ $this->out->elementEnd('div');
+ }
+
+ function divId()
+ {
+ return 'generic_section';
+ }
+
+ function title()
+ {
+ return _('Untitled section');
+ }
+
+ function showContent()
+ {
+ $this->out->element('p', null,
+ _('(None)'));
+ return false;
+ }
+
+ function moreUrl()
+ {
+ return null;
+ }
+
+ function moreTitle()
+ {
+ return null;
+ }
+}
diff --git a/lib/servererroraction.php b/lib/servererroraction.php
new file mode 100644
index 000000000..a39886591
--- /dev/null
+++ b/lib/servererroraction.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Server error action.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Zach Copley <zach@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/error.php';
+
+/**
+ * Class for displaying HTTP server errors
+ *
+ * Note: The older util.php class simply printed a string, but the spec
+ * 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.
+ *
+ * See: http://tools.ietf.org/html/rfc2616#section-10
+ *
+ * @category Action
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://laconi.ca/
+ */
+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);
+
+ $this->code = $this->trimmed('code');
+
+ if (!$this->code || $code < 500 || $code > 599) {
+ $this->code = $this->default;
+ }
+
+ $this->message = $this->trimmed('message');
+
+ if (!$this->message) {
+ $this->message = "Server Error $this->code";
+ }
+
+ $this->showPage();
+ }
+
+}
diff --git a/lib/settingsaction.php b/lib/settingsaction.php
index 9e783431f..dfe1f114b 100644
--- a/lib/settingsaction.php
+++ b/lib/settingsaction.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Base class for settings 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.
@@ -15,105 +18,133 @@
*
* 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 Settings
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
*/
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Base class for settings group of actions
+ *
+ * @category Settings
+ * @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 Widget
+ */
+
+class SettingsAction extends Action
+{
+ /**
+ * A message for the user.
+ */
+
+ var $msg = null;
+
+ /**
+ * Whether the message is a good one or a bad one.
+ */
-class SettingsAction extends Action {
+ var $success = false;
- function handle($args) {
+ /**
+ * Handle input and output a page
+ *
+ * @param array $args $_REQUEST arguments
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
parent::handle($args);
if (!common_logged_in()) {
- common_user_error(_('Not logged in.'));
+ $this->clientError(_('Not logged in.'));
return;
} else if (!common_is_real_login()) {
- # Cookie theft means that automatic logins can't
- # change important settings or see private info, and
- # _all_ our settings are important
- common_set_returnto($this->self_url());
+ // Cookie theft means that automatic logins can't
+ // change important settings or see private info, and
+ // _all_ our settings are important
+ common_set_returnto($this->selfUrl());
common_redirect(common_local_url('login'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- $this->handle_post();
+ $this->handlePost();
} else {
- $this->show_form();
+ $this->showForm();
}
}
- # override!
- function handle_post() {
+ /**
+ * Handle a POST request
+ *
+ * @return boolean success flag
+ */
+
+ function handlePost()
+ {
return false;
}
- function show_form($msg=NULL, $success=false) {
- return false;
+ /**
+ * show the settings form
+ *
+ * @param string $msg an extra message for the user
+ * @param string $success good message or bad message?
+ *
+ * @return void
+ */
+
+ function showForm($msg=null, $success=false)
+ {
+ $this->msg = $msg;
+ $this->success = $success;
+
+ $this->showPage();
}
- function message($msg, $success) {
- if ($msg) {
- common_element('div', ($success) ? 'success' : 'error',
- $msg);
+ /**
+ * show human-readable instructions for the page
+ *
+ * @return void
+ */
+
+ function showPageNotice()
+ {
+ if ($this->msg) {
+ $this->element('div', ($this->success) ? 'success' : 'error',
+ $this->msg);
+ } else {
+ $inst = $this->getInstructions();
+ $output = common_markup_to_html($inst);
+
+ $this->elementStart('div', 'instructions');
+ $this->raw($output);
+ $this->elementEnd('div');
}
}
- function form_header($title, $msg=NULL, $success=false) {
- common_show_header($title,
- NULL,
- array($msg, $success),
- array($this, 'show_top'));
- }
-
- function show_top($arr) {
- $msg = $arr[0];
- $success = $arr[1];
- if ($msg) {
- $this->message($msg, $success);
- } else {
- $inst = $this->get_instructions();
- $output = common_markup_to_html($inst);
- common_element_start('div', 'instructions');
- common_raw($output);
- common_element_end('div');
- }
- $this->settings_menu();
- }
-
- function settings_menu() {
- # action => array('prompt', 'title')
- $menu =
- array('profilesettings' =>
- array(_('Profile'),
- _('Change your profile settings')),
- 'emailsettings' =>
- array(_('Email'),
- _('Change email handling')),
- 'openidsettings' =>
- array(_('OpenID'),
- _('Add or remove OpenIDs')),
- 'smssettings' =>
- array(_('SMS'),
- _('Updates by SMS')),
- 'imsettings' =>
- array(_('IM'),
- _('Updates by instant messenger (IM)')),
- 'twittersettings' =>
- array(_('Twitter'),
- _('Twitter integration options')),
- 'othersettings' =>
- array(_('Other'),
- _('Other options')));
-
- $action = $this->trimmed('action');
- common_element_start('ul', array('id' => 'nav_views'));
- foreach ($menu as $menuaction => $menudesc) {
- if ($menuaction == 'imsettings' &&
- !common_config('xmpp', 'enabled')) {
- continue;
- }
- common_menu_item(common_local_url($menuaction),
- $menudesc[0],
- $menudesc[1],
- $action == $menuaction);
- }
- common_element_end('ul');
+ /**
+ * instructions recipe for sub-classes
+ *
+ * Subclasses should override this to return readable instructions. They'll
+ * be processed by common_markup_to_html().
+ *
+ * @return string instructions text
+ */
+
+ function getInstructions()
+ {
+ return '';
}
+
}
diff --git a/lib/stream.php b/lib/stream.php
index 27ab78137..0cb9e0bf4 100644
--- a/lib/stream.php
+++ b/lib/stream.php
@@ -10,11 +10,11 @@
*
* 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
+ * 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/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
@@ -22,33 +22,10 @@ if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/personal.php');
require_once(INSTALLDIR.'/lib/noticelist.php');
-class StreamAction extends PersonalAction {
-
- function public_views_menu() {
-
- $action = $this->trimmed('action');
-
- common_element_start('ul', array('id' => 'nav_views'));
-
- common_menu_item(common_local_url('public'), _('Public'),
- _('Public timeline'), $action == 'public');
-
- common_menu_item(common_local_url('tag'), _('Recent tags'),
- _('Recent tags'), $action == 'tag');
-
- if (count(common_config('nickname', 'featured')) > 0) {
- common_menu_item(common_local_url('featured'), _('Featured'),
- _('Featured users'), $action == 'featured');
- }
-
- common_menu_item(common_local_url('favorited'), _('Popular'),
- _("Popular notices"), $action == 'favorited');
-
- common_element_end('ul');
-
- }
-
- function show_notice_list($notice) {
+class StreamAction extends PersonalAction
+{
+ function show_notice_list($notice)
+ {
$nl = new NoticeList($notice);
return $nl->show();
}
diff --git a/lib/subgroupnav.php b/lib/subgroupnav.php
new file mode 100644
index 000000000..5fd8a72a2
--- /dev/null
+++ b/lib/subgroupnav.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Local navigation for subscriptions group of pages
+ *
+ * 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 Subs
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/widget.php';
+
+/**
+ * Local nav menu for subscriptions, subscribers
+ *
+ * @category Subs
+ * @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 SubGroupNav extends Widget
+{
+ var $action = null;
+ var $user = null;
+
+ /**
+ * Construction
+ *
+ * @param Action $action current action, used for output
+ */
+
+ function __construct($action=null, $user=null)
+ {
+ parent::__construct($action);
+ $this->action = $action;
+ $this->user = $user;
+ }
+
+ /**
+ * Show the menu
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $cur = common_current_user();
+ $action = $this->action->trimmed('action');
+
+ $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');
+ }
+ $this->out->elementEnd('ul');
+ }
+}
diff --git a/lib/subs.php b/lib/subs.php
index 483b2f78e..0e7b9ded5 100644
--- a/lib/subs.php
+++ b/lib/subs.php
@@ -25,15 +25,16 @@ require_once('XMPPHP/XMPP.php');
Returns true or an error message.
*/
-function subs_subscribe_user($user, $other_nickname) {
+function subs_subscribe_user($user, $other_nickname)
+{
- $other = User::staticGet('nickname', $other_nickname);
+ $other = User::staticGet('nickname', $other_nickname);
- if (!$other) {
- return _('No such user.');
- }
+ if (!$other) {
+ return _('No such user.');
+ }
- return subs_subscribe_to($user, $other);
+ return subs_subscribe_to($user, $other);
}
/* Subscribe user $user to other user $other.
@@ -41,90 +42,96 @@ function subs_subscribe_user($user, $other_nickname) {
* Because the other way is quite a bit more complicated.
*/
-function subs_subscribe_to($user, $other) {
+function subs_subscribe_to($user, $other)
+{
- if ($user->isSubscribed($other)) {
- return _('Already subscribed!');
- }
+ if ($user->isSubscribed($other)) {
+ return _('Already subscribed!.');
+ }
if ($other->hasBlocked($user)) {
- return _('User has blocked you.');
+ return _('User has blocked you.');
}
- if (!$user->subscribeTo($other)) {
- return _('Could not subscribe.');
- return;
- }
+ if (!$user->subscribeTo($other)) {
+ return _('Could not subscribe.');
+ return;
+ }
subs_notify($other, $user);
- $cache = common_memcache();
+ $cache = common_memcache();
if ($cache) {
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
}
- if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
- if (!$other->subscribeTo($user)) {
- return _('Could not subscribe other to you.');
- }
+
+ if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
+ if (!$other->subscribeTo($user)) {
+ return _('Could not subscribe other to you.');
+ }
$cache = common_memcache();
if ($cache) {
$cache->delete(common_cache_key('user:notices_with_friends:' . $other->id));
}
- subs_notify($user, $other);
- }
+ subs_notify($user, $other);
+ }
- return true;
+ return true;
}
-function subs_notify($listenee, $listener) {
- # XXX: add other notifications (Jabber, SMS) here
- # XXX: queue this and handle it offline
- # XXX: Whatever happens, do it in Twitter-like API, too
- subs_notify_email($listenee, $listener);
+function subs_notify($listenee, $listener)
+{
+ # XXX: add other notifications (Jabber, SMS) here
+ # XXX: queue this and handle it offline
+ # XXX: Whatever happens, do it in Twitter-like API, too
+ subs_notify_email($listenee, $listener);
}
-function subs_notify_email($listenee, $listener) {
- mail_subscribe_notify($listenee, $listener);
+function subs_notify_email($listenee, $listener)
+{
+ mail_subscribe_notify($listenee, $listener);
}
/* Unsubscribe $user from nickname $other_nickname
Returns true or an error message.
*/
-function subs_unsubscribe_user($user, $other_nickname) {
+function subs_unsubscribe_user($user, $other_nickname)
+{
- $other = User::staticGet('nickname', $other_nickname);
+ $other = User::staticGet('nickname', $other_nickname);
- if (!$other) {
- return _('No such user.');
- }
+ if (!$other) {
+ return _('No such user.');
+ }
- return subs_unsubscribe_to($user, $other->getProfile());
+ return subs_unsubscribe_to($user, $other->getProfile());
}
/* Unsubscribe user $user from profile $other
* NB: other can be a remote user. */
-function subs_unsubscribe_to($user, $other) {
+function subs_unsubscribe_to($user, $other)
+{
- if (!$user->isSubscribed($other))
- return _('Not subscribed!');
+ if (!$user->isSubscribed($other))
+ return _('Not subscribed!.');
- $sub = DB_DataObject::factory('subscription');
+ $sub = DB_DataObject::factory('subscription');
- $sub->subscriber = $user->id;
- $sub->subscribed = $other->id;
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $other->id;
- $sub->find(true);
+ $sub->find(true);
- // note we checked for existence above
+ // note we checked for existence above
- if (!$sub->delete())
- return _('Couldn\'t delete subscription.');
+ if (!$sub->delete())
+ return _('Couldn\'t delete subscription.');
$cache = common_memcache();
@@ -132,6 +139,6 @@ function subs_unsubscribe_to($user, $other) {
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
}
- return true;
+ return true;
}
diff --git a/lib/subscribeform.php b/lib/subscribeform.php
new file mode 100644
index 000000000..c65134e46
--- /dev/null
+++ b/lib/subscribeform.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for subscribing to a user
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for subscribing to a user
+ *
+ * @category Form
+ * @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/
+ *
+ * @see UnsubscribeForm
+ */
+
+class SubscribeForm extends Form
+{
+ /**
+ * Profile of user to subscribe to
+ */
+
+ var $profile = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Profile $profile profile of user to subscribe to
+ */
+
+ function __construct($out=null, $profile=null)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'subscribe-' . $this->profile->id;
+ }
+
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_user_subscribe';
+ }
+
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('subscribe');
+ }
+
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Subscribe to this user'));
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('subscribeto-' . $this->profile->id,
+ $this->profile->id,
+ 'subscribeto');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Subscribe'), 'submit', null, _('Subscribe to this user'));
+ }
+}
diff --git a/lib/tagcloudsection.php b/lib/tagcloudsection.php
new file mode 100644
index 000000000..ff2aca6d6
--- /dev/null
+++ b/lib/tagcloudsection.php
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing tag clouds
+ *
+ * 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('TAGS_PER_SECTION', 20);
+
+/**
+ * Base class for sections
+ *
+ * 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 TagCloudSection extends Section
+{
+ function showContent()
+ {
+ $tags = $this->getTags();
+
+ if (!$tags) {
+ $this->out->element('p', null, _('None'));
+ return false;
+ }
+
+ $cnt = 0;
+
+ $tw = array();
+ $sum = 0;
+
+ while ($tags->fetch() && ++$cnt <= TAGS_PER_SECTION) {
+ $tw[$tags->tag] = $tags->weight;
+ $sum += $tags->weight;
+ }
+
+ if ($cnt == 0) {
+ $this->out->element('p', null, _('(None)'));
+ return false;
+ }
+
+ ksort($tw);
+
+ $this->out->elementStart('ul', 'tags xoxo tag-cloud');
+ foreach ($tw as $tag => $weight) {
+ $this->showTag($tag, $weight, ($sum == 0) ? 0 : $weight/$sum);
+ }
+ $this->out->elementEnd('ul');
+
+ return ($cnt > TAGS_PER_SECTION);
+ }
+
+ function getTags()
+ {
+ return null;
+ }
+
+ function showTag($tag, $weight, $relative)
+ {
+ if ($relative > 0.1) {
+ $rel = 'tag-cloud-7';
+ } else if ($relative > 0.05) {
+ $rel = 'tag-cloud-6';
+ } else if ($relative > 0.02) {
+ $rel = 'tag-cloud-5';
+ } else if ($relative > 0.01) {
+ $rel = 'tag-cloud-4';
+ } else if ($relative > 0.005) {
+ $rel = 'tag-cloud-3';
+ } else if ($relative > 0.002) {
+ $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 tagUrl($tag)
+ {
+ return common_local_url('tag', array('tag' => $tag));
+ }
+
+ function divId()
+ {
+ return 'tagcloud';
+ }
+}
diff --git a/lib/theme.php b/lib/theme.php
index 80982aa82..95030affe 100644
--- a/lib/theme.php
+++ b/lib/theme.php
@@ -1,9 +1,12 @@
<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * Utilities for theme files and paths
+ *
+ * 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.
@@ -15,21 +18,55 @@
*
* 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 Paths
+ * @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); }
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Gets the full path of a file in a theme dir based on its relative name
+ *
+ * @param string $relative relative path within the theme directory
+ * @param string $theme name of the theme; defaults to current theme
+ *
+ * @return string File path to the theme file
+ */
-function theme_file($relative) {
- $theme = common_config('site', 'theme');
- return INSTALLDIR.'/theme/'.$theme.'/'.$relative;
+function theme_file($relative, $theme=null)
+{
+ if (!$theme) {
+ $theme = common_config('site', 'theme');
+ }
+ return INSTALLDIR.'/theme/'.$theme.'/'.$relative;
}
-function theme_path($relative) {
- $theme = common_config('site', 'theme');
- $server = common_config('theme', 'server');
- if ($server) {
- return 'http://'.$server.'/'.$theme.'/'.$relative;
- } else {
- return common_path('theme/'.$theme.'/'.$relative);
- }
+/**
+ * Gets the full URL of a file in a theme dir based on its relative name
+ *
+ * @param string $relative relative path within the theme directory
+ * @param string $theme name of the theme; defaults to current theme
+ *
+ * @return string URL of the file
+ */
+
+function theme_path($relative, $theme=null)
+{
+ if (!$theme) {
+ $theme = common_config('site', 'theme');
+ }
+ $server = common_config('theme', 'server');
+ if ($server) {
+ return 'http://'.$server.'/'.$theme.'/'.$relative;
+ } else {
+ return common_path('theme/'.$theme.'/'.$relative);
+ }
} \ No newline at end of file
diff --git a/lib/topposterssection.php b/lib/topposterssection.php
new file mode 100644
index 000000000..4bd59ac79
--- /dev/null
+++ b/lib/topposterssection.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for sections showing lists of people
+ *
+ * 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);
+}
+
+/**
+ * Base class for sections
+ *
+ * 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 TopPostersSection extends ProfileSection
+{
+ function getProfiles()
+ {
+ $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 ' .
+ 'ORDER BY value DESC ';
+
+ $limit = PROFILES_PER_SECTION;
+ $offset = 0;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $profile = Memcached_DataObject::cachedQuery('Profile',
+ $qry,
+ 6 * 3600);
+ return $profile;
+ }
+
+ function title()
+ {
+ return _('Top posters');
+ }
+
+ function divId()
+ {
+ return 'top_posters';
+ }
+}
diff --git a/lib/twitter.php b/lib/twitter.php
index 7f75a1afd..5eb15005a 100644
--- a/lib/twitter.php
+++ b/lib/twitter.php
@@ -19,184 +19,190 @@
if (!defined('LACONICA')) { exit(1); }
-function get_twitter_data($uri, $screen_name, $password) {
-
- $options = array(
- CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_FAILONERROR => true,
- CURLOPT_HEADER => false,
- CURLOPT_FOLLOWLOCATION => true,
- // CURLOPT_USERAGENT => "identi.ca",
- CURLOPT_CONNECTTIMEOUT => 120,
+function get_twitter_data($uri, $screen_name, $password)
+{
+
+ $options = array(
+ CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ # CURLOPT_USERAGENT => "identi.ca",
+ CURLOPT_CONNECTTIMEOUT => 120,
CURLOPT_TIMEOUT => 120,
-
# Twitter is strict about accepting invalid "Expect" headers
CURLOPT_HTTPHEADER => array('Expect:')
- );
+ );
- $ch = curl_init($uri);
+ $ch = curl_init($uri);
curl_setopt_array($ch, $options);
$data = curl_exec($ch);
$errmsg = curl_error($ch);
- if ($errmsg) {
- common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $twit_user.",
- __FILE__);
- }
+ if ($errmsg) {
+ common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $twit_user.",
+ __FILE__);
+ }
- curl_close($ch);
+ curl_close($ch);
- return $data;
+ return $data;
}
-function twitter_user_info($screen_name, $password) {
+function twitter_user_info($screen_name, $password)
+{
- $uri = "http://twitter.com/users/show/$screen_name.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) {
- return false;
- }
+ if (!$data) {
+ return false;
+ }
- $twit_user = json_decode($data);
+ $twit_user = json_decode($data);
- if (!$twit_user) {
- return false;
- }
+ if (!$twit_user) {
+ return false;
+ }
- return $twit_user;
+ return $twit_user;
}
-function update_twitter_user($fuser, $twitter_id, $screen_name) {
+function update_twitter_user($fuser, $twitter_id, $screen_name)
+{
- $original = clone($fuser);
- $fuser->nickname = $screen_name;
- $fuser->uri = 'http://twitter.com/' . $screen_name;
- $result = $fuser->updateKeys($original);
+ $original = clone($fuser);
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $result = $fuser->updateKeys($original);
- if (!$result) {
- common_log_db_error($fuser, 'UPDATE', __FILE__);
- return false;
- }
+ if (!$result) {
+ common_log_db_error($fuser, 'UPDATE', __FILE__);
+ return false;
+ }
- return true;
+ return true;
}
-function add_twitter_user($twitter_id, $screen_name) {
+function add_twitter_user($twitter_id, $screen_name)
+{
- // Otherwise, create a new Twitter user
- $fuser = DB_DataObject::factory('foreign_user');
+ // Otherwise, create a new Twitter user
+ $fuser = DB_DataObject::factory('foreign_user');
- $fuser->nickname = $screen_name;
- $fuser->uri = 'http://twitter.com/' . $screen_name;
- $fuser->id = $twitter_id;
- $fuser->service = 1; // Twitter
- $fuser->created = common_sql_now();
- $result = $fuser->insert();
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $fuser->id = $twitter_id;
+ $fuser->service = 1; // Twitter
+ $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_db_error($fuser, 'INSERT', __FILE__);
- return false;
- }
+ if (!$result) {
+ common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+ common_log_db_error($fuser, 'INSERT', __FILE__);
+ return false;
+ }
- common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+ common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
- return true;
+ return true;
}
// Creates or Updates a Twitter user
-function save_twitter_user($twitter_id, $screen_name) {
+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);
+ // 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);
- if ($fuser) {
+ if ($fuser) {
- // Only update if Twitter screen name has changed
- if ($fuser->nickname != $screen_name) {
+ // Only update if Twitter screen name has changed
+ if ($fuser->nickname != $screen_name) {
- common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
- "$fuser->id to $screen_name, was $fuser->nickname");
+ 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);
- }
+ return update_twitter_user($fuser, $twitter_id, $screen_name);
+ }
- } else {
- return add_twitter_user($twitter_id, $screen_name);
- }
+ } else {
+ return add_twitter_user($twitter_id, $screen_name);
+ }
- return true;
+ return true;
}
-function retreive_twitter_friends($twitter_id, $screen_name, $password) {
+function retreive_twitter_friends($twitter_id, $screen_name, $password)
+{
- $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
- $twitter_user = twitter_user_info($screen_name, $password);
+ $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
+ $twitter_user = twitter_user_info($screen_name, $password);
- // Calculate how many pages to get...
- $pages = ceil($twitter_user->friends_count / 100);
+ // Calculate how many pages to get...
+ $pages = ceil($twitter_user->friends_count / 100);
- if ($pages == 0) {
- common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
- }
+ if ($pages == 0) {
+ common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
+ }
- $friends = array();
+ $friends = array();
- for ($i = 1; $i <= $pages; $i++) {
+ for ($i = 1; $i <= $pages; $i++) {
- $data = get_twitter_data($uri . $i, $screen_name, $password);
+ $data = get_twitter_data($uri . $i, $screen_name, $password);
- if (!$data) {
- return NULL;
- }
+ if (!$data) {
+ return null;
+ }
- $more_friends = json_decode($data);
+ $more_friends = json_decode($data);
- if (!$more_friends) {
- return NULL;
- }
+ if (!$more_friends) {
+ return null;
+ }
- $friends = array_merge($friends, $more_friends);
- }
+ $friends = array_merge($friends, $more_friends);
+ }
- return $friends;
+ return $friends;
}
-function save_twitter_friends($user, $twitter_id, $screen_name, $password) {
+function save_twitter_friends($user, $twitter_id, $screen_name, $password)
+{
- $friends = retreive_twitter_friends($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.");
- return false;
- }
+ if (is_null($friends)) {
+ common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
+ return false;
+ }
foreach ($friends as $friend) {
- $friend_name = $friend->screen_name;
- $friend_id = $friend->id;
+ $friend_name = $friend->screen_name;
+ $friend_id = $friend->id;
- // Update or create the Foreign_user record
- if (!save_twitter_user($friend_id, $friend_name)) {
- return false;
- }
+ // Update or create the Foreign_user record
+ if (!save_twitter_user($friend_id, $friend_name)) {
+ return false;
+ }
- // Check to see if there's a related local user
- $flink = Foreign_link::getByForeignID($friend_id, 1);
+ // Check to see if there's a related local user
+ $flink = Foreign_link::getByForeignID($friend_id, 1);
- if ($flink) {
+ if ($flink) {
- // 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.");
- }
- }
+ // 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.");
+ }
+ }
- return true;
+ return true;
}
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
index 2083e8961..3d2c74feb 100644
--- a/lib/twitterapi.php
+++ b/lib/twitterapi.php
@@ -19,565 +19,675 @@
if (!defined('LACONICA')) { exit(1); }
-class TwitterapiAction extends Action {
+class TwitterapiAction extends Action
+{
- var $auth_user;
+ var $auth_user;
- function handle($args) {
- parent::handle($args);
- }
+ function handle($args)
+ {
+ parent::handle($args);
+ }
- function twitter_user_array($profile, $get_notice=false) {
+ function twitter_user_array($profile, $get_notice=false)
+ {
- $twitter_user = array();
+ $twitter_user = array();
- $twitter_user['name'] = $profile->getBestName();
- $twitter_user['followers_count'] = $this->count_subscriptions($profile);
- $twitter_user['screen_name'] = $profile->nickname;
- $twitter_user['description'] = ($profile->bio) ? $profile->bio : NULL;
- $twitter_user['location'] = ($profile->location) ? $profile->location : NULL;
- $twitter_user['id'] = intval($profile->id);
+ $twitter_user['name'] = $profile->getBestName();
+ $twitter_user['followers_count'] = $this->count_subscriptions($profile);
+ $twitter_user['screen_name'] = $profile->nickname;
+ $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
+ $twitter_user['location'] = ($profile->location) ? $profile->location : null;
+ $twitter_user['id'] = intval($profile->id);
- $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
- $twitter_user['profile_image_url'] = ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE);
- $twitter_user['protected'] = 'false'; # not supported by Laconica yet
- $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : NULL;
+ $twitter_user['profile_image_url'] = ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE);
+ $twitter_user['protected'] = 'false'; # not supported by Laconica yet
+ $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
- if ($get_notice) {
- $notice = $profile->getCurrentNotice();
- if ($notice) {
- # don't get user!
- $twitter_user['status'] = $this->twitter_status_array($notice, false);
- }
- }
+ if ($get_notice) {
+ $notice = $profile->getCurrentNotice();
+ if ($notice) {
+ # don't get user!
+ $twitter_user['status'] = $this->twitter_status_array($notice, false);
+ }
+ }
- return $twitter_user;
- }
+ return $twitter_user;
+ }
- function twitter_status_array($notice, $include_user=true) {
+ function twitter_status_array($notice, $include_user=true)
+ {
- $profile = $notice->getProfile();
+ $profile = $notice->getProfile();
- $twitter_status = array();
- $twitter_status['text'] = $notice->content;
- $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['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;
+ $twitter_status = array();
+ $twitter_status['text'] = $notice->content;
+ $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['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;
- if (isset($this->auth_user)) {
- $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false';
- } else {
- $twitter_status['favorited'] = 'false';
- }
+ if (isset($this->auth_user)) {
+ $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false';
+ } else {
+ $twitter_status['favorited'] = 'false';
+ }
- if ($include_user) {
- # Don't get notice (recursive!)
- $twitter_user = $this->twitter_user_array($profile, false);
- $twitter_status['user'] = $twitter_user;
- }
+ if ($include_user) {
+ # Don't get notice (recursive!)
+ $twitter_user = $this->twitter_user_array($profile, false);
+ $twitter_status['user'] = $twitter_user;
+ }
- return $twitter_status;
- }
+ return $twitter_status;
+ }
- function twitter_rss_entry_array($notice) {
+ function twitter_rss_entry_array($notice)
+ {
- $profile = $notice->getProfile();
+ $profile = $notice->getProfile();
- $server = common_config('site', 'server');
- $entry = array();
+ $server = common_config('site', 'server');
+ $entry = array();
# We trim() to avoid extraneous whitespace in the output
- $entry['content'] = common_xml_safe_str(trim($notice->rendered));
- $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]";
- $entry['updated'] = $entry['published'];
-
- # RSS Item specific
- $entry['description'] = $entry['content'];
- $entry['pubDate'] = common_date_rfc2822($notice->created);
- $entry['guid'] = $entry['link'];
-
- return $entry;
- }
-
- function twitter_rss_dmsg_array($message) {
-
- $server = common_config('site', 'server');
- $entry = array();
-
- $entry['title'] = sprintf('Message from %s to %s',
- $message->getFrom()->nickname, $message->getTo()->nickname);
-
- $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]";
- $entry['updated'] = $entry['published'];
-
- # RSS Item specific
- $entry['description'] = $entry['content'];
- $entry['pubDate'] = common_date_rfc2822($message->created);
- $entry['guid'] = $entry['link'];
-
- return $entry;
- }
-
- function twitter_dmsg_array($message) {
-
- $twitter_dm = array();
-
- $from_profile = $message->getFrom();
- $to_profile = $message->getTo();
-
- $twitter_dm['id'] = $message->id;
- $twitter_dm['sender_id'] = $message->from_profile;
- $twitter_dm['text'] = trim($message->content);
- $twitter_dm['recipient_id'] = $message->to_profile;
- $twitter_dm['created_at'] = $this->date_twitter($message->created);
- $twitter_dm['sender_screen_name'] = $from_profile->nickname;
- $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
- $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
- $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
-
- return $twitter_dm;
- }
-
- function show_twitter_xml_status($twitter_status) {
- common_element_start('status');
- foreach($twitter_status as $element => $value) {
- switch ($element) {
- case 'user':
- $this->show_twitter_xml_user($twitter_status['user']);
- break;
- case 'text':
- common_element($element, NULL, common_xml_safe_str($value));
- break;
- default:
- common_element($element, NULL, $value);
- }
- }
- common_element_end('status');
- }
-
- function show_twitter_xml_user($twitter_user, $role='user') {
- common_element_start($role);
- foreach($twitter_user as $element => $value) {
- if ($element == 'status') {
- $this->show_twitter_xml_status($twitter_user['status']);
- } else {
- common_element($element, NULL, $value);
- }
- }
- common_element_end($role);
- }
-
- function show_twitter_rss_item($entry) {
- common_element_start('item');
- common_element('title', NULL, $entry['title']);
- common_element('description', NULL, $entry['description']);
- common_element('pubDate', NULL, $entry['pubDate']);
- common_element('guid', NULL, $entry['guid']);
- common_element('link', NULL, $entry['link']);
- common_element_end('item');
- }
-
- function show_twitter_atom_entry($entry) {
- common_element_start('entry');
- common_element('title', NULL, $entry['title']);
- common_element('content', array('type' => 'html'), $entry['content']);
- common_element('id', NULL, $entry['id']);
- common_element('published', NULL, $entry['published']);
- common_element('updated', NULL, $entry['updated']);
- common_element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), NULL);
- common_element_end('entry');
- }
-
- function show_json_objects($objects) {
- print(json_encode($objects));
- }
-
- function show_single_xml_status($notice) {
- $this->init_document('xml');
- $twitter_status = $this->twitter_status_array($notice);
- $this->show_twitter_xml_status($twitter_status);
- $this->end_document('xml');
- }
-
- function show_single_json_status($notice) {
- $this->init_document('json');
- $status = $this->twitter_status_array($notice);
- $this->show_json_objects($status);
- $this->end_document('json');
- }
-
- function show_single_xml_dmsg($message) {
- $this->init_document('xml');
- $dmsg = $this->twitter_dmsg_array($message);
- $this->show_twitter_xml_dmsg($dmsg);
- $this->end_document('xml');
- }
-
- function show_single_json_dmsg($message) {
- $this->init_document('json');
- $dmsg = $this->twitter_dmsg_array($message);
- $this->show_json_objects($dmsg);
- $this->end_document('json');
- }
-
- function show_twitter_xml_dmsg($twitter_dm) {
- common_element_start('direct_message');
- foreach($twitter_dm as $element => $value) {
- switch ($element) {
- case 'sender':
- case 'recipient':
- $this->show_twitter_xml_user($value, $element);
- break;
- case 'text':
- common_element($element, NULL, common_xml_safe_str($value));
- break;
- default:
- common_element($element, NULL, $value);
- }
- }
- common_element_end('direct_message');
- }
-
- function show_xml_timeline($notice) {
-
- $this->init_document('xml');
- common_element_start('statuses', array('type' => 'array'));
-
- if (is_array($notice)) {
- foreach ($notice as $n) {
- $twitter_status = $this->twitter_status_array($n);
- $this->show_twitter_xml_status($twitter_status);
- }
- } else {
- while ($notice->fetch()) {
- $twitter_status = $this->twitter_status_array($notice);
- $this->show_twitter_xml_status($twitter_status);
- }
- }
-
- common_element_end('statuses');
- $this->end_document('xml');
- }
-
- function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=NULL) {
-
- $this->init_document('rss');
-
- common_element_start('channel');
- common_element('title', NULL, $title);
- common_element('link', NULL, $link);
- if (!is_null($suplink)) {
- # For FriendFeed's SUP protocol
- common_element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
- 'rel' => 'http://api.friendfeed.com/2008/03#sup',
- 'href' => $suplink,
- 'type' => 'application/json'));
- }
- common_element('description', NULL, $subtitle);
- common_element('language', NULL, 'en-us');
- common_element('ttl', NULL, '40');
-
- if (is_array($notice)) {
- foreach ($notice as $n) {
- $entry = $this->twitter_rss_entry_array($n);
- $this->show_twitter_rss_item($entry);
- }
- } else {
- while ($notice->fetch()) {
- $entry = $this->twitter_rss_entry_array($notice);
- $this->show_twitter_rss_item($entry);
- }
- }
-
- common_element_end('channel');
- $this->end_twitter_rss();
- }
-
- function show_atom_timeline($notice, $title, $id, $link, $subtitle=NULL, $suplink=NULL) {
-
- $this->init_document('atom');
-
- common_element('title', NULL, $title);
- common_element('id', NULL, $id);
- common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
- if (!is_null($suplink)) {
- # For FriendFeed's SUP protocol
- common_element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
- 'href' => $suplink,
- 'type' => 'application/json'));
- }
- common_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);
- }
- } else {
- while ($notice->fetch()) {
- $entry = $this->twitter_rss_entry_array($notice);
- $this->show_twitter_atom_entry($entry);
- }
- }
-
- $this->end_document('atom');
-
- }
-
- function show_json_timeline($notice) {
-
- $this->init_document('json');
-
- $statuses = array();
-
- if (is_array($notice)) {
- foreach ($notice as $n) {
- $twitter_status = $this->twitter_status_array($n);
- array_push($statuses, $twitter_status);
- }
- } else {
- while ($notice->fetch()) {
- $twitter_status = $this->twitter_status_array($notice);
- array_push($statuses, $twitter_status);
- }
- }
-
- $this->show_json_objects($statuses);
-
- $this->end_document('json');
- }
-
- // Anyone know what date format this is?
- // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
- 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;
- }
-
- // XXX: Candidate for a general utility method somewhere?
- function count_subscriptions($profile) {
-
- $count = 0;
- $sub = new Subscription();
- $sub->subscribed = $profile->id;
-
- $count = $sub->find();
-
- if ($count > 0) {
- return $count - 1;
- } else {
- return 0;
- }
- }
-
- function init_document($type='xml') {
- switch ($type) {
- case 'xml':
- header('Content-Type: application/xml; charset=utf-8');
- common_start_xml();
- break;
- case 'json':
- header('Content-Type: application/json; charset=utf-8');
-
- // Check for JSONP callback
- $callback = $this->arg('callback');
- if ($callback) {
- print $callback . '(';
- }
- break;
- case 'rss':
- header("Content-Type: application/rss+xml; charset=utf-8");
- $this->init_twitter_rss();
- break;
- case 'atom':
- header('Content-Type: application/atom+xml; charset=utf-8');
- $this->init_twitter_atom();
- break;
- default:
- $this->client_error(_('Not a supported data format.'));
- break;
- }
-
- return;
- }
-
- function end_document($type='xml') {
- switch ($type) {
- case 'xml':
- common_end_xml();
- break;
- case 'json':
-
- // Check for JSONP callback
- $callback = $this->arg('callback');
- if ($callback) {
- print ')';
- }
- break;
- case 'rss':
- $this->end_twitter_rss();
- break;
- case 'atom':
- $this->end_twitter_rss();
- break;
- default:
- $this->client_error(_('Not a supported data format.'));
- break;
- }
- return;
- }
-
- function client_error($msg, $code = 400, $content_type = 'json') {
-
- static $status = array(400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed');
-
- $action = $this->trimmed('action');
-
- common_debug("User error '$code' on '$action': $msg", __FILE__);
-
- if (!array_key_exists($code, $status)) {
- $code = 400;
- }
-
- $status_string = $status[$code];
- header('HTTP/1.1 '.$code.' '.$status_string);
-
- if ($content_type == 'xml') {
- $this->init_document('xml');
- common_element_start('hash');
- common_element('error', NULL, $msg);
- common_element('request', NULL, $_SERVER['REQUEST_URI']);
- common_element_end('hash');
- $this->end_document('xml');
- } else {
- $this->init_document('json');
- $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
- print(json_encode($error_array));
- $this->end_document('json');
- }
-
- }
-
- function init_twitter_rss() {
- common_start_xml();
- common_element_start('rss', array('version' => '2.0'));
- }
-
- function end_twitter_rss() {
- common_element_end('rss');
- common_end_xml();
- }
-
- function init_twitter_atom() {
- common_start_xml();
- common_element_start('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
- }
-
- function end_twitter_atom() {
- common_end_xml();
- common_element_end('feed');
- }
-
- function show_profile($profile, $content_type='xml', $notice=NULL) {
- $profile_array = $this->twitter_user_array($profile, true);
- switch ($content_type) {
- case 'xml':
- $this->show_twitter_xml_user($profile_array);
- break;
- case 'json':
- $this->show_json_objects($profile_array);
- break;
- default:
- $this->client_error(_('Not a supported data format.'));
- return;
- }
- return;
- }
-
- function get_user($id, $apidata=NULL) {
- if (!$id) {
- return $apidata['user'];
- } else if (is_numeric($id)) {
- return User::staticGet($id);
- } else {
- $nickname = common_canonical_nickname($id);
- return User::staticGet('nickname', $nickname);
- }
- }
-
- function get_profile($id) {
- if (is_numeric($id)) {
- return Profile::staticGet($id);
- } else {
- $user = User::staticGet('nickname', $id);
- if ($user) {
- return $user->getProfile();
- } else {
- return NULL;
- }
- }
- }
-
- function source_link($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;
- }
-
-} \ No newline at end of file
+ $entry['content'] = common_xml_safe_str(trim($notice->rendered));
+ $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]";
+ $entry['updated'] = $entry['published'];
+
+ # RSS Item specific
+ $entry['description'] = $entry['content'];
+ $entry['pubDate'] = common_date_rfc2822($notice->created);
+ $entry['guid'] = $entry['link'];
+
+ return $entry;
+ }
+
+ function twitter_rss_dmsg_array($message)
+ {
+
+ $server = common_config('site', 'server');
+ $entry = array();
+
+ $entry['title'] = sprintf('Message from %s to %s',
+ $message->getFrom()->nickname, $message->getTo()->nickname);
+
+ $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]";
+ $entry['updated'] = $entry['published'];
+
+ # RSS Item specific
+ $entry['description'] = $entry['content'];
+ $entry['pubDate'] = common_date_rfc2822($message->created);
+ $entry['guid'] = $entry['link'];
+
+ return $entry;
+ }
+
+ function twitter_dmsg_array($message)
+ {
+
+ $twitter_dm = array();
+
+ $from_profile = $message->getFrom();
+ $to_profile = $message->getTo();
+
+ $twitter_dm['id'] = $message->id;
+ $twitter_dm['sender_id'] = $message->from_profile;
+ $twitter_dm['text'] = trim($message->content);
+ $twitter_dm['recipient_id'] = $message->to_profile;
+ $twitter_dm['created_at'] = $this->date_twitter($message->created);
+ $twitter_dm['sender_screen_name'] = $from_profile->nickname;
+ $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
+ $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
+ $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
+
+ return $twitter_dm;
+ }
+
+ function show_twitter_xml_status($twitter_status)
+ {
+ common_element_start('status');
+ foreach($twitter_status as $element => $value) {
+ switch ($element) {
+ case 'user':
+ $this->show_twitter_xml_user($twitter_status['user']);
+ break;
+ case 'text':
+ common_element($element, null, common_xml_safe_str($value));
+ break;
+ default:
+ common_element($element, null, $value);
+ }
+ }
+ common_element_end('status');
+ }
+
+ function show_twitter_xml_user($twitter_user, $role='user')
+ {
+ common_element_start($role);
+ foreach($twitter_user as $element => $value) {
+ if ($element == 'status') {
+ $this->show_twitter_xml_status($twitter_user['status']);
+ } else {
+ common_element($element, null, $value);
+ }
+ }
+ common_element_end($role);
+ }
+
+ function show_twitter_rss_item($entry)
+ {
+ common_element_start('item');
+ common_element('title', null, $entry['title']);
+ common_element('description', null, $entry['description']);
+ common_element('pubDate', null, $entry['pubDate']);
+ common_element('guid', null, $entry['guid']);
+ common_element('link', null, $entry['link']);
+ common_element_end('item');
+ }
+
+ function show_twitter_atom_entry($entry)
+ {
+ common_element_start('entry');
+ common_element('title', null, $entry['title']);
+ common_element('content', array('type' => 'html'), $entry['content']);
+ common_element('id', null, $entry['id']);
+ common_element('published', null, $entry['published']);
+ common_element('updated', null, $entry['updated']);
+ common_element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), null);
+ common_element_end('entry');
+ }
+
+ function show_json_objects($objects)
+ {
+ print(json_encode($objects));
+ }
+
+ function show_single_xml_status($notice)
+ {
+ $this->init_document('xml');
+ $twitter_status = $this->twitter_status_array($notice);
+ $this->show_twitter_xml_status($twitter_status);
+ $this->end_document('xml');
+ }
+
+ function show_single_json_status($notice)
+ {
+ $this->init_document('json');
+ $status = $this->twitter_status_array($notice);
+ $this->show_json_objects($status);
+ $this->end_document('json');
+ }
+
+ function show_single_xml_dmsg($message)
+ {
+ $this->init_document('xml');
+ $dmsg = $this->twitter_dmsg_array($message);
+ $this->show_twitter_xml_dmsg($dmsg);
+ $this->end_document('xml');
+ }
+
+ function show_single_json_dmsg($message)
+ {
+ $this->init_document('json');
+ $dmsg = $this->twitter_dmsg_array($message);
+ $this->show_json_objects($dmsg);
+ $this->end_document('json');
+ }
+
+ function show_twitter_xml_dmsg($twitter_dm)
+ {
+ common_element_start('direct_message');
+ foreach($twitter_dm as $element => $value) {
+ switch ($element) {
+ case 'sender':
+ case 'recipient':
+ $this->show_twitter_xml_user($value, $element);
+ break;
+ case 'text':
+ common_element($element, null, common_xml_safe_str($value));
+ break;
+ default:
+ common_element($element, null, $value);
+ }
+ }
+ common_element_end('direct_message');
+ }
+
+ function show_xml_timeline($notice)
+ {
+
+ $this->init_document('xml');
+ common_element_start('statuses', array('type' => 'array'));
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $twitter_status = $this->twitter_status_array($n);
+ $this->show_twitter_xml_status($twitter_status);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $twitter_status = $this->twitter_status_array($notice);
+ $this->show_twitter_xml_status($twitter_status);
+ }
+ }
+
+ common_element_end('statuses');
+ $this->end_document('xml');
+ }
+
+ function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
+ {
+
+ $this->init_document('rss');
+
+ common_element_start('channel');
+ common_element('title', null, $title);
+ common_element('link', null, $link);
+ if (!is_null($suplink)) {
+ # For FriendFeed's SUP protocol
+ common_element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
+ 'rel' => 'http://api.friendfeed.com/2008/03#sup',
+ 'href' => $suplink,
+ 'type' => 'application/json'));
+ }
+ common_element('description', null, $subtitle);
+ common_element('language', null, 'en-us');
+ common_element('ttl', null, '40');
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $entry = $this->twitter_rss_entry_array($n);
+ $this->show_twitter_rss_item($entry);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $entry = $this->twitter_rss_entry_array($notice);
+ $this->show_twitter_rss_item($entry);
+ }
+ }
+
+ common_element_end('channel');
+ $this->end_twitter_rss();
+ }
+
+ function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null)
+ {
+
+ $this->init_document('atom');
+
+ common_element('title', null, $title);
+ common_element('id', null, $id);
+ common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+ if (!is_null($suplink)) {
+ # For FriendFeed's SUP protocol
+ common_element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
+ 'href' => $suplink,
+ 'type' => 'application/json'));
+ }
+ common_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);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $entry = $this->twitter_rss_entry_array($notice);
+ $this->show_twitter_atom_entry($entry);
+ }
+ }
+
+ $this->end_document('atom');
+
+ }
+
+ function show_json_timeline($notice)
+ {
+
+ $this->init_document('json');
+
+ $statuses = array();
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $twitter_status = $this->twitter_status_array($n);
+ array_push($statuses, $twitter_status);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $twitter_status = $this->twitter_status_array($notice);
+ array_push($statuses, $twitter_status);
+ }
+ }
+
+ $this->show_json_objects($statuses);
+
+ $this->end_document('json');
+ }
+
+ // Anyone know what date format this is?
+ // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
+ 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;
+ }
+
+ // XXX: Candidate for a general utility method somewhere?
+ function count_subscriptions($profile)
+ {
+
+ $count = 0;
+ $sub = new Subscription();
+ $sub->subscribed = $profile->id;
+
+ $count = $sub->find();
+
+ if ($count > 0) {
+ return $count - 1;
+ } else {
+ return 0;
+ }
+ }
+
+ function init_document($type='xml')
+ {
+ switch ($type) {
+ case 'xml':
+ header('Content-Type: application/xml; charset=utf-8');
+ common_start_xml();
+ break;
+ case 'json':
+ header('Content-Type: application/json; charset=utf-8');
+
+ // Check for JSONP callback
+ $callback = $this->arg('callback');
+ if ($callback) {
+ print $callback . '(';
+ }
+ break;
+ case 'rss':
+ header("Content-Type: application/rss+xml; charset=utf-8");
+ $this->init_twitter_rss();
+ break;
+ case 'atom':
+ header('Content-Type: application/atom+xml; charset=utf-8');
+ $this->init_twitter_atom();
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ break;
+ }
+
+ return;
+ }
+
+ function end_document($type='xml')
+ {
+ switch ($type) {
+ case 'xml':
+ common_end_xml();
+ break;
+ case 'json':
+
+ // Check for JSONP callback
+ $callback = $this->arg('callback');
+ if ($callback) {
+ print ')';
+ }
+ break;
+ case 'rss':
+ $this->end_twitter_rss();
+ break;
+ case 'atom':
+ $this->end_twitter_rss();
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ break;
+ }
+ return;
+ }
+
+ function client_error($msg, $code = 400, $content_type = 'json')
+ {
+
+ static $status = array(400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed');
+
+ $action = $this->trimmed('action');
+
+ common_debug("User error '$code' on '$action': $msg", __FILE__);
+
+ if (!array_key_exists($code, $status)) {
+ $code = 400;
+ }
+
+ $status_string = $status[$code];
+ header('HTTP/1.1 '.$code.' '.$status_string);
+
+ if ($content_type == 'xml') {
+ $this->init_document('xml');
+ common_element_start('hash');
+ common_element('error', null, $msg);
+ common_element('request', null, $_SERVER['REQUEST_URI']);
+ common_element_end('hash');
+ $this->end_document('xml');
+ } else {
+ $this->init_document('json');
+ $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+ print(json_encode($error_array));
+ $this->end_document('json');
+ }
+
+ }
+
+ function init_twitter_rss()
+ {
+ common_start_xml();
+ common_element_start('rss', array('version' => '2.0'));
+ }
+
+ function end_twitter_rss()
+ {
+ common_element_end('rss');
+ common_end_xml();
+ }
+
+ function init_twitter_atom()
+ {
+ common_start_xml();
+ common_element_start('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
+ }
+
+ function end_twitter_atom()
+ {
+ common_end_xml();
+ common_element_end('feed');
+ }
+
+ function show_profile($profile, $content_type='xml', $notice=null)
+ {
+ $profile_array = $this->twitter_user_array($profile, true);
+ switch ($content_type) {
+ case 'xml':
+ $this->show_twitter_xml_user($profile_array);
+ break;
+ case 'json':
+ $this->show_json_objects($profile_array);
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ return;
+ }
+ return;
+ }
+
+ function get_user($id, $apidata=null)
+ {
+ if (!$id) {
+ return $apidata['user'];
+ } else if (is_numeric($id)) {
+ return User::staticGet($id);
+ } else {
+ $nickname = common_canonical_nickname($id);
+ return User::staticGet('nickname', $nickname);
+ }
+ }
+
+ function get_profile($id)
+ {
+ if (is_numeric($id)) {
+ return Profile::staticGet($id);
+ } else {
+ $user = User::staticGet('nickname', $id);
+ if ($user) {
+ return $user->getProfile();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ function source_link($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;
+ }
+
+ 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/unblockform.php b/lib/unblockform.php
new file mode 100644
index 000000000..6a8831b29
--- /dev/null
+++ b/lib/unblockform.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for unblocking a user
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for unblocking a user
+ *
+ * @category Form
+ * @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/
+ *
+ * @see BlockForm
+ */
+
+class UnblockForm extends Form
+{
+ /**
+ * Profile of user to unblock
+ */
+
+ var $profile = null;
+
+ /**
+ * Return-to args
+ */
+
+ var $args = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Profile $profile profile of user to unblock
+ * @param array $args return-to args
+ */
+
+ function __construct($out=null, $profile=null, $args=null)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ $this->args = $args;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'unblock-' . $this->profile->id;
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_user_unblock';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('unblock');
+ }
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Unblock this user'));
+ }
+
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('unblockto-' . $this->profile->id,
+ $this->profile->id,
+ 'unblockto');
+ if ($this->args) {
+ foreach ($this->args as $k => $v) {
+ $this->out->hidden('returnto-' . $k, $v);
+ }
+ }
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Unblock'), 'submit', null, _('Unblock this user'));
+ }
+}
diff --git a/lib/unsubscribeform.php b/lib/unsubscribeform.php
new file mode 100644
index 000000000..ce91a1340
--- /dev/null
+++ b/lib/unsubscribeform.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Form for unsubscribing from a user
+ *
+ * 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 Form
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for unsubscribing from a user
+ *
+ * @category Form
+ * @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/
+ *
+ * @see SubscribeForm
+ */
+
+class UnsubscribeForm extends Form
+{
+ /**
+ * Profile of user to unsubscribe from
+ */
+
+ var $profile = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Profile $profile profile of user to unsub from
+ */
+
+ function __construct($out=null, $profile=null)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'unsubscribe-' . $this->profile->id;
+ }
+
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_user_unsubscribe';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('unsubscribe');
+ }
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Unsubscribe from this user'));
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->hidden('unsubscribeto-' . $this->profile->id,
+ $this->profile->id,
+ 'unsubscribeto');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Unsubscribe'), 'submit', null, _('Unsubscribe from this user'));
+ }
+}
diff --git a/lib/util.php b/lib/util.php
index fc45311fc..03d6b6199 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -17,647 +17,237 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-/* XXX: break up into separate modules (HTTP, HTML, user, files) */
-
-# Show a server error
-
-function common_server_error($msg, $code=500) {
- static $status = array(500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported');
-
- if (!array_key_exists($code, $status)) {
- $code = 500;
- }
-
- $status_string = $status[$code];
-
- header('HTTP/1.1 '.$code.' '.$status_string);
- header('Content-type: text/plain');
-
- print $msg;
- print "\n";
- exit();
-}
-
-# Show a user error
-function common_user_error($msg, $code=400) {
- static $status = array(400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed');
-
- if (!array_key_exists($code, $status)) {
- $code = 400;
- }
-
- $status_string = $status[$code];
-
- header('HTTP/1.1 '.$code.' '.$status_string);
-
- common_show_header('Error');
- common_element('div', array('class' => 'error'), $msg);
- common_show_footer();
-}
-
-$xw = null;
-
-# Start an HTML element
-function common_element_start($tag, $attrs=NULL) {
- global $xw;
- $xw->startElement($tag);
- if (is_array($attrs)) {
- foreach ($attrs as $name => $value) {
- $xw->writeAttribute($name, $value);
- }
- } else if (is_string($attrs)) {
- $xw->writeAttribute('class', $attrs);
- }
-}
-
-function common_element_end($tag) {
- static $empty_tag = array('base', 'meta', 'link', 'hr',
- 'br', 'param', 'img', 'area',
- 'input', 'col');
- global $xw;
- # XXX: check namespace
- if (in_array($tag, $empty_tag)) {
- $xw->endElement();
- } else {
- $xw->fullEndElement();
- }
-}
-
-function common_element($tag, $attrs=NULL, $content=NULL) {
- common_element_start($tag, $attrs);
- global $xw;
- if (!is_null($content)) {
- $xw->text($content);
- }
- common_element_end($tag);
-}
-
-function common_start_xml($doc=NULL, $public=NULL, $system=NULL, $indent=true) {
- global $xw;
- $xw = new XMLWriter();
- $xw->openURI('php://output');
- $xw->setIndent($indent);
- $xw->startDocument('1.0', 'UTF-8');
- if ($doc) {
- $xw->writeDTD($doc, $public, $system);
- }
-}
-
-function common_end_xml() {
- global $xw;
- $xw->endDocument();
- $xw->flush();
-}
-
-function common_init_locale($language=null) {
+/* XXX: break up into separate modules (HTTP, user, files) */
+
+// Show a server error
+
+function common_server_error($msg, $code=500)
+{
+ $err = new ServerErrorAction($msg, $code);
+ $err->showPage();
+}
+
+// Show a user error
+function common_user_error($msg, $code=400)
+{
+ $err = new ClientErrorAction($msg, $code);
+ $err->showPage();
+}
+
+function common_init_locale($language=null)
+{
if(!$language) {
$language = common_language();
}
putenv('LANGUAGE='.$language);
putenv('LANG='.$language);
return setlocale(LC_ALL, $language . ".utf8",
- $language . ".UTF8",
- $language . ".utf-8",
- $language . ".UTF-8",
- $language);
-}
-
-function common_init_language() {
- mb_internal_encoding('UTF-8');
- $language = common_language();
- # So we don't have to make people install the gettext locales
- $locale_set = common_init_locale($language);
- bindtextdomain("laconica", common_config('site','locale_path'));
- bind_textdomain_codeset("laconica", "UTF-8");
- textdomain("laconica");
- setlocale(LC_CTYPE, 'C');
- if(!$locale_set) {
- common_log(LOG_INFO,'Language requested:'.$language.' - locale could not be set:',__FILE__);
- }
-}
-
-define('PAGE_TYPE_PREFS', 'text/html,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2');
-
-function common_show_header($pagetitle, $callable=NULL, $data=NULL, $headercall=NULL) {
-
- global $config, $xw;
- global $action; /* XXX: kind of cheating here. */
-
- common_start_html();
-
- common_element_start('head');
- common_element('title', NULL,
- $pagetitle . " - " . $config['site']['name']);
- common_element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('display.css') . '?version=' . LACONICA_VERSION,
- 'media' => 'screen, projection, tv'));
- foreach (array(6,7) as $ver) {
- if (file_exists(theme_file('ie'.$ver.'.css'))) {
- # Yes, IE people should be put in jail.
- $xw->writeComment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
- 'href="'.theme_path('ie'.$ver.'.css').'?version='.LACONICA_VERSION.'" /><![endif]');
- }
- }
-
- common_element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.min.js')),
- ' ');
- common_element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.form.js')),
- ' ');
- common_element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/xbImportNode.js')),
- ' ');
- common_element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
- ' ');
- common_element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
- 'href' => common_local_url('opensearch', array('type' => 'people')),
- 'title' => common_config('site', 'name').' People Search'));
-
- common_element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
- 'href' => common_local_url('opensearch', array('type' => 'notice')),
- 'title' => common_config('site', 'name').' Notice Search'));
-
- if ($callable) {
- if ($data) {
- call_user_func($callable, $data);
- } else {
- call_user_func($callable);
- }
- }
- common_element_end('head');
- common_element_start('body', $action);
- common_element_start('div', array('id' => 'wrap'));
- common_element_start('div', array('id' => 'header'));
- common_nav_menu();
- if ((isset($config['site']['logo']) && is_string($config['site']['logo']) && (strlen($config['site']['logo']) > 0))
- || file_exists(theme_file('logo.png')))
- {
- common_element_start('a', array('href' => common_local_url('public')));
- common_element('img', array('src' => isset($config['site']['logo']) ?
- ($config['site']['logo']) : theme_path('logo.png'),
- 'alt' => $config['site']['name'],
- 'id' => 'logo'));
- common_element_end('a');
- } else {
- common_element_start('p', array('id' => 'branding'));
- common_element('a', array('href' => common_local_url('public')),
- $config['site']['name']);
- common_element_end('p');
- }
-
- common_element('h1', 'pagetitle', $pagetitle);
-
- if ($headercall) {
- if ($data) {
- call_user_func($headercall, $data);
- } else {
- call_user_func($headercall);
- }
- }
- common_element_end('div');
- common_element_start('div', array('id' => 'content'));
-}
-
-function common_start_html($type=NULL, $indent=true) {
-
- if (!$type) {
- $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : NULL;
-
- # XXX: allow content negotiation for RDF, RSS, or XRDS
-
- $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
- common_accept_to_prefs(PAGE_TYPE_PREFS));
-
- if (!$type) {
- common_user_error(_('This page is not available in a media type you accept'), 406);
- exit(0);
- }
- }
-
- header('Content-Type: '.$type);
-
- common_start_xml('html',
- '-//W3C//DTD XHTML 1.0 Strict//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', $indent);
-
- # FIXME: correct language for interface
-
- $language = common_language();
-
- common_element_start('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
- 'xml:lang' => $language,
- 'lang' => $language));
-}
-
-function common_show_footer() {
- global $xw, $config;
- common_element_end('div'); # content div
- common_foot_menu();
- common_element_start('div', array('id' => 'footer'));
- common_element_start('div', 'laconica');
- if (common_config('site', 'broughtby')) {
- $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
- } else {
- $instr = _('**%%site.name%%** is a microblogging service. ');
- }
- $instr .= sprintf(_('It runs the [Laconica](http://laconi.ca/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), LACONICA_VERSION);
- $output = common_markup_to_html($instr);
- common_raw($output);
- common_element_end('div');
- common_element('img', array('id' => 'cc',
- 'src' => $config['license']['image'],
- 'alt' => $config['license']['title']));
- common_element_start('p');
- common_text(_('Unless otherwise specified, contents of this site are copyright by the contributors and available under the '));
- common_element('a', array('class' => 'license',
- 'rel' => 'license',
- 'href' => $config['license']['url']),
- $config['license']['title']);
- common_text(_('. Contributors should be attributed by full name or nickname.'));
- common_element_end('p');
- common_element_end('div');
- common_element_end('div');
- common_element_end('body');
- common_element_end('html');
- common_end_xml();
-}
-
-function common_text($txt) {
- global $xw;
- $xw->text($txt);
-}
-
-function common_raw($xml) {
- global $xw;
- $xw->writeRaw($xml);
-}
-
-function common_nav_menu() {
- $user = common_current_user();
- common_element_start('ul', array('id' => 'nav'));
- if ($user) {
- common_menu_item(common_local_url('all', array('nickname' => $user->nickname)),
- _('Home'));
- }
- common_menu_item(common_local_url('peoplesearch'), _('Search'));
- if ($user) {
- common_menu_item(common_local_url('profilesettings'),
- _('Settings'));
- common_menu_item(common_local_url('invite'),
- _('Invite'));
- common_menu_item(common_local_url('logout'),
- _('Logout'));
- } else {
- common_menu_item(common_local_url('login'), _('Login'));
- if (!common_config('site', 'closed')) {
- common_menu_item(common_local_url('register'), _('Register'));
- }
- common_menu_item(common_local_url('openidlogin'), _('OpenID'));
- }
- common_menu_item(common_local_url('doc', array('title' => 'help')),
- _('Help'));
- common_element_end('ul');
-}
-
-function common_foot_menu() {
- common_element_start('ul', array('id' => 'nav_sub'));
- common_menu_item(common_local_url('doc', array('title' => 'help')),
- _('Help'));
- common_menu_item(common_local_url('doc', array('title' => 'about')),
- _('About'));
- common_menu_item(common_local_url('doc', array('title' => 'faq')),
- _('FAQ'));
- common_menu_item(common_local_url('doc', array('title' => 'privacy')),
- _('Privacy'));
- common_menu_item(common_local_url('doc', array('title' => 'source')),
- _('Source'));
- common_menu_item(common_local_url('doc', array('title' => 'contact')),
- _('Contact'));
- common_element_end('ul');
-}
-
-function common_menu_item($url, $text, $title=NULL, $is_selected=false) {
- $lattrs = array();
- if ($is_selected) {
- $lattrs['class'] = 'current';
- }
- common_element_start('li', $lattrs);
- $attrs['href'] = $url;
- if ($title) {
- $attrs['title'] = $title;
- }
- common_element('a', $attrs, $text);
- common_element_end('li');
-}
-
-function common_input($id, $label, $value=NULL,$instructions=NULL) {
- common_element_start('p');
- common_element('label', array('for' => $id), $label);
- $attrs = array('name' => $id,
- 'type' => 'text',
- 'class' => 'input_text',
- 'id' => $id);
- if ($value) {
- $attrs['value'] = htmlspecialchars($value);
- }
- common_element('input', $attrs);
- if ($instructions) {
- common_element('span', 'input_instructions', $instructions);
- }
- common_element_end('p');
-}
-
-function common_checkbox($id, $label, $checked=false, $instructions=NULL, $value='true', $disabled=false)
-{
- common_element_start('p');
- $attrs = array('name' => $id,
- 'type' => 'checkbox',
- 'class' => 'checkbox',
- 'id' => $id);
- if ($value) {
- $attrs['value'] = htmlspecialchars($value);
- }
- if ($checked) {
- $attrs['checked'] = 'checked';
- }
- if ($disabled) {
- $attrs['disabled'] = 'true';
- }
- common_element('input', $attrs);
- common_text(' ');
- common_element('label', array('class' => 'checkbox_label', 'for' => $id), $label);
- common_text(' ');
- if ($instructions) {
- common_element('span', 'input_instructions', $instructions);
- }
- common_element_end('p');
-}
-
-function common_dropdown($id, $label, $content, $instructions=NULL, $blank_select=FALSE, $selected=NULL) {
- common_element_start('p');
- common_element('label', array('for' => $id), $label);
- common_element_start('select', array('id' => $id, 'name' => $id));
- if ($blank_select) {
- common_element('option', array('value' => ''));
- }
- foreach ($content as $value => $option) {
- if ($value == $selected) {
- common_element('option', array('value' => $value, 'selected' => $value), $option);
- } else {
- common_element('option', array('value' => $value), $option);
- }
- }
- common_element_end('select');
- if ($instructions) {
- common_element('span', 'input_instructions', $instructions);
- }
- common_element_end('p');
-}
-function common_hidden($id, $value) {
- common_element('input', array('name' => $id,
- 'type' => 'hidden',
- 'id' => $id,
- 'value' => $value));
-}
-
-function common_password($id, $label, $instructions=NULL) {
- common_element_start('p');
- common_element('label', array('for' => $id), $label);
- $attrs = array('name' => $id,
- 'type' => 'password',
- 'class' => 'password',
- 'id' => $id);
- common_element('input', $attrs);
- if ($instructions) {
- common_element('span', 'input_instructions', $instructions);
- }
- common_element_end('p');
-}
-
-function common_submit($id, $label, $cls='submit') {
- global $xw;
- common_element_start('p');
- common_element('input', array('type' => 'submit',
- 'id' => $id,
- 'name' => $id,
- 'class' => $cls,
- 'value' => $label));
- common_element_end('p');
-}
-
-function common_textarea($id, $label, $content=NULL, $instructions=NULL) {
- common_element_start('p');
- common_element('label', array('for' => $id), $label);
- common_element('textarea', array('rows' => 3,
- 'cols' => 40,
- 'name' => $id,
- 'id' => $id),
- ($content) ? $content : '');
- if ($instructions) {
- common_element('span', 'input_instructions', $instructions);
- }
- common_element_end('p');
-}
-
-function common_timezone() {
- if (common_logged_in()) {
- $user = common_current_user();
- if ($user->timezone) {
- return $user->timezone;
- }
- }
-
- global $config;
- return $config['site']['timezone'];
-}
-
-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()) {
- $user = common_current_user();
- $user_language = $user->language;
- if ($user_language)
- return $user_language;
+ $language . ".UTF8",
+ $language . ".utf-8",
+ $language . ".UTF-8",
+ $language);
+}
+
+function common_init_language()
+{
+ mb_internal_encoding('UTF-8');
+ $language = common_language();
+ // So we don't have to make people install the gettext locales
+ $locale_set = common_init_locale($language);
+ bindtextdomain("laconica", common_config('site','locale_path'));
+ bind_textdomain_codeset("laconica", "UTF-8");
+ textdomain("laconica");
+ setlocale(LC_CTYPE, 'C');
+ if(!$locale_set) {
+ common_log(LOG_INFO,'Language requested:'.$language.' - locale could not be set:',__FILE__);
+ }
+}
+
+function common_timezone()
+{
+ if (common_logged_in()) {
+ $user = common_current_user();
+ if ($user->timezone) {
+ return $user->timezone;
}
+ }
- // Otherwise, find the best match for the languages requested by the
- // user's browser...
- $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : NULL;
- if (!empty($httplang)) {
- $language = client_prefered_language($httplang);
- if ($language)
- return $language;
- }
+ global $config;
+ return $config['site']['timezone'];
+}
+
+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()) {
+ $user = common_current_user();
+ $user_language = $user->language;
+ if ($user_language)
+ return $user_language;
+ }
- // Finally, if none of the above worked, use the site's default...
- return common_config('site', 'language');
+ // Otherwise, find the best match for the languages requested by the
+ // user's browser...
+ $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
+ if (!empty($httplang)) {
+ $language = client_prefered_language($httplang);
+ if ($language)
+ return $language;
+ }
+
+ // Finally, if none of the above worked, use the site's default...
+ return common_config('site', 'language');
}
-# salted, hashed passwords are stored in the DB
+// salted, hashed passwords are stored in the DB
-function common_munge_password($password, $id) {
- return md5($password . $id);
+function common_munge_password($password, $id)
+{
+ return md5($password . $id);
}
-# check if a username exists and has matching password
-function common_check_user($nickname, $password) {
- # NEVER allow blank passwords, even if they match the DB
- if (mb_strlen($password) == 0) {
- return false;
- }
- $user = User::staticGet('nickname', $nickname);
- if (is_null($user)) {
- return false;
- } else {
- if (0 == strcmp(common_munge_password($password, $user->id),
- $user->password)) {
- return $user;
- } else {
- return false;
- }
- }
+// check if a username exists and has matching password
+function common_check_user($nickname, $password)
+{
+ // NEVER allow blank passwords, even if they match the DB
+ if (mb_strlen($password) == 0) {
+ return false;
+ }
+ $user = User::staticGet('nickname', $nickname);
+ if (is_null($user)) {
+ return false;
+ } else {
+ if (0 == strcmp(common_munge_password($password, $user->id),
+ $user->password)) {
+ return $user;
+ } else {
+ return false;
+ }
+ }
}
-# is the current user logged in?
-function common_logged_in() {
- return (!is_null(common_current_user()));
+// is the current user logged in?
+function common_logged_in()
+{
+ return (!is_null(common_current_user()));
}
-function common_have_session() {
- return (0 != strcmp(session_id(), ''));
+function common_have_session()
+{
+ return (0 != strcmp(session_id(), ''));
}
-function common_ensure_session() {
- if (!common_have_session()) {
- @session_start();
- }
+function common_ensure_session()
+{
+ if (!common_have_session()) {
+ @session_start();
+ }
}
-# Three kinds of arguments:
-# 1) a user object
-# 2) a nickname
-# 3) NULL to clear
+// Three kinds of arguments:
+// 1) a user object
+// 2) a nickname
+// 3) null to clear
-# Initialize to false; set to NULL if none found
+// Initialize to false; set to null if none found
$_cur = false;
-function common_set_user($user) {
+function common_set_user($user)
+{
global $_cur;
- if (is_null($user) && common_have_session()) {
- $_cur = NULL;
- unset($_SESSION['userid']);
- return true;
- } else if (is_string($user)) {
- $nickname = $user;
- $user = User::staticGet('nickname', $nickname);
- } else if (!($user instanceof User)) {
- return false;
- }
-
- if ($user) {
- common_ensure_session();
- $_SESSION['userid'] = $user->id;
+ if (is_null($user) && common_have_session()) {
+ $_cur = null;
+ unset($_SESSION['userid']);
+ return true;
+ } else if (is_string($user)) {
+ $nickname = $user;
+ $user = User::staticGet('nickname', $nickname);
+ } else if (!($user instanceof User)) {
+ return false;
+ }
+
+ if ($user) {
+ common_ensure_session();
+ $_SESSION['userid'] = $user->id;
$_cur = $user;
- return $_cur;
- }
- return false;
+ return $_cur;
+ }
+ return false;
}
-function common_set_cookie($key, $value, $expiration=0) {
- $path = common_config('site', 'path');
- $server = common_config('site', 'server');
+function common_set_cookie($key, $value, $expiration=0)
+{
+ $path = common_config('site', 'path');
+ $server = common_config('site', 'server');
- if ($path && ($path != '/')) {
- $cookiepath = '/' . $path . '/';
- } else {
- $cookiepath = '/';
- }
- return setcookie($key,
- $value,
- $expiration,
- $cookiepath,
- $server);
+ if ($path && ($path != '/')) {
+ $cookiepath = '/' . $path . '/';
+ } else {
+ $cookiepath = '/';
+ }
+ return setcookie($key,
+ $value,
+ $expiration,
+ $cookiepath,
+ $server);
}
define('REMEMBERME', 'rememberme');
-define('REMEMBERME_EXPIRY', 30 * 24 * 60 * 60); # 30 days
+define('REMEMBERME_EXPIRY', 30 * 24 * 60 * 60); // 30 days
-function common_rememberme($user=NULL) {
- if (!$user) {
- $user = common_current_user();
- if (!$user) {
- common_debug('No current user to remember', __FILE__);
- return false;
- }
- }
+function common_rememberme($user=null)
+{
+ if (!$user) {
+ $user = common_current_user();
+ if (!$user) {
+ common_debug('No current user to remember', __FILE__);
+ return false;
+ }
+ }
- $rm = new Remember_me();
+ $rm = new Remember_me();
- $rm->code = common_good_rand(16);
- $rm->user_id = $user->id;
+ $rm->code = common_good_rand(16);
+ $rm->user_id = $user->id;
- # Wrap the insert in some good ol' fashioned transaction code
+ // Wrap the insert in some good ol' fashioned transaction code
$rm->query('BEGIN');
- $result = $rm->insert();
+ $result = $rm->insert();
- if (!$result) {
- common_log_db_error($rm, 'INSERT', __FILE__);
- common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__);
- return false;
+ if (!$result) {
+ common_log_db_error($rm, 'INSERT', __FILE__);
+ common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__);
+ return false;
}
$rm->query('COMMIT');
- common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__);
+ common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__);
$cookieval = $rm->user_id . ':' . $rm->code;
- common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname);
+ common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname);
- common_set_cookie(REMEMBERME, $cookieval, time() + REMEMBERME_EXPIRY);
+ common_set_cookie(REMEMBERME, $cookieval, time() + REMEMBERME_EXPIRY);
- return true;
+ return true;
}
-function common_remembered_user() {
+function common_remembered_user()
+{
- $user = NULL;
+ $user = null;
- $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : NULL;
+ $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : null;
- if (!$packed) {
- return NULL;
+ if (!$packed) {
+ return null;
}
list($id, $code) = explode(':', $packed);
@@ -665,7 +255,7 @@ function common_remembered_user() {
if (!$id || !$code) {
common_log(LOG_WARNING, 'Malformed rememberme cookie: ' . $packed);
common_forgetme();
- return NULL;
+ return null;
}
$rm = Remember_me::staticGet($code);
@@ -673,13 +263,13 @@ function common_remembered_user() {
if (!$rm) {
common_log(LOG_WARNING, 'No such remember code: ' . $code);
common_forgetme();
- return NULL;
+ return null;
}
if ($rm->user_id != $id) {
common_log(LOG_WARNING, 'Rememberme code for wrong user: ' . $rm->user_id . ' != ' . $id);
common_forgetme();
- return NULL;
+ return null;
}
$user = User::staticGet($rm->user_id);
@@ -687,17 +277,17 @@ function common_remembered_user() {
if (!$user) {
common_log(LOG_WARNING, 'No such user for rememberme: ' . $rm->user_id);
common_forgetme();
- return NULL;
+ return null;
}
- # successful!
+ // successful!
$result = $rm->delete();
if (!$result) {
common_log_db_error($rm, 'DELETE', __FILE__);
common_log(LOG_WARNING, 'Could not delete rememberme: ' . $code);
common_forgetme();
- return NULL;
+ return null;
}
common_log(LOG_INFO, 'logging in ' . $user->nickname . ' using rememberme code ' . $rm->code);
@@ -705,22 +295,24 @@ function common_remembered_user() {
common_set_user($user);
common_real_login(false);
- # We issue a new cookie, so they can log in
- # automatically again after this session
+ // We issue a new cookie, so they can log in
+ // automatically again after this session
common_rememberme($user);
- return $user;
+ return $user;
}
-# must be called with a valid user!
+// must be called with a valid user!
-function common_forgetme() {
- common_set_cookie(REMEMBERME, '', 0);
+function common_forgetme()
+{
+ common_set_cookie(REMEMBERME, '', 0);
}
-# who is the current user?
-function common_current_user() {
+// who is the current user?
+function common_current_user()
+{
global $_cur;
if ($_cur === false) {
@@ -734,585 +326,646 @@ function common_current_user() {
}
}
- # that didn't work; try to remember; will init $_cur to NULL on failure
+ // that didn't work; try to remember; will init $_cur to null on failure
$_cur = common_remembered_user();
if ($_cur) {
common_debug("Got User " . $_cur->nickname);
common_debug("Faking session on remembered user");
- # XXX: Is this necessary?
+ // XXX: Is this necessary?
$_SESSION['userid'] = $_cur->id;
}
}
- return $_cur;
+ return $_cur;
}
-# Logins that are 'remembered' aren't 'real' -- they're subject to
-# cookie-stealing. So, we don't let them do certain things. New reg,
-# OpenID, and password logins _are_ real.
+// Logins that are 'remembered' aren't 'real' -- they're subject to
+// cookie-stealing. So, we don't let them do certain things. New reg,
+// OpenID, and password logins _are_ real.
-function common_real_login($real=true) {
- common_ensure_session();
- $_SESSION['real_login'] = $real;
+function common_real_login($real=true)
+{
+ common_ensure_session();
+ $_SESSION['real_login'] = $real;
}
-function common_is_real_login() {
- return common_logged_in() && $_SESSION['real_login'];
+function common_is_real_login()
+{
+ return common_logged_in() && $_SESSION['real_login'];
}
-# get canonical version of nickname for comparison
-function common_canonical_nickname($nickname) {
- # XXX: UTF-8 canonicalization (like combining chars)
- return strtolower($nickname);
+// get canonical version of nickname for comparison
+function common_canonical_nickname($nickname)
+{
+ // XXX: UTF-8 canonicalization (like combining chars)
+ return strtolower($nickname);
}
-# get canonical version of email for comparison
-function common_canonical_email($email) {
- # XXX: canonicalize UTF-8
- # XXX: lcase the domain part
- return $email;
+// get canonical version of email for comparison
+function common_canonical_email($email)
+{
+ // XXX: canonicalize UTF-8
+ // XXX: lcase the domain part
+ return $email;
}
define('URL_REGEX', '^|[ \t\r\n])((ftp|http|https|gopher|mailto|news|nntp|telnet|wais|file|prospero|aim|webcal):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))');
-function common_render_content($text, $notice) {
- $r = common_render_text($text);
- $id = $notice->profile_id;
- $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
- $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
- $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
- return $r;
-}
-
-function common_render_text($text) {
- $r = htmlspecialchars($text);
-
- $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
- $r = preg_replace_callback('@https?://[^\]>\s]+@', 'common_render_uri_thingy', $r);
- $r = preg_replace('/(^|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
- # XXX: machine tags
- return $r;
-}
-
-function common_render_uri_thingy($matches) {
- $uri = $matches[0];
- $trailer = '';
-
- # Some heuristics for extracting URIs from surrounding punctuation
- # Strip from trailing text...
- if (preg_match('/^(.*)([,.:"\']+)$/', $uri, $matches)) {
- $uri = $matches[1];
- $trailer = $matches[2];
- }
-
- $pairs = array(
- ']' => '[', # technically disallowed in URIs, but used in Java docs
- ')' => '(', # far too frequent in Wikipedia and MSDN
- );
- $final = substr($uri, -1, 1);
- if (isset($pairs[$final])) {
- $openers = substr_count($uri, $pairs[$final]);
- $closers = substr_count($uri, $final);
- if ($closers > $openers) {
- // Assume the paren was opened outside the URI
- $uri = substr($uri, 0, -1);
- $trailer = $final . $trailer;
- }
- }
- if ($longurl = common_longurl($uri)) {
- $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8');
- $title = " title='$longurl'";
- }
- else $title = '';
-
- return '<a href="' . $uri . '"' . $title . ' class="extlink">' . $uri . '</a>' . $trailer;
-}
-
-function common_longurl($short_url) {
+function common_render_content($text, $notice)
+{
+ $r = common_render_text($text);
+ $id = $notice->profile_id;
+ $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
+ $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
+ $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
+ $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
+ return $r;
+}
+
+function common_render_text($text)
+{
+ $r = htmlspecialchars($text);
+
+ $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
+ $r = preg_replace_callback('@https?://[^\]>\s]+@', 'common_render_uri_thingy', $r);
+ $r = preg_replace('/(^|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
+ // XXX: machine tags
+ return $r;
+}
+
+function common_render_uri_thingy($matches)
+{
+ $uri = $matches[0];
+ $trailer = '';
+
+ // Some heuristics for extracting URIs from surrounding punctuation
+ // Strip from trailing text...
+ if (preg_match('/^(.*)([,.:"\']+)$/', $uri, $matches)) {
+ $uri = $matches[1];
+ $trailer = $matches[2];
+ }
+
+ $pairs = array(
+ ']' => '[', // technically disallowed in URIs, but used in Java docs
+ ')' => '(', // far too frequent in Wikipedia and MSDN
+ );
+ $final = substr($uri, -1, 1);
+ if (isset($pairs[$final])) {
+ $openers = substr_count($uri, $pairs[$final]);
+ $closers = substr_count($uri, $final);
+ if ($closers > $openers) {
+ // Assume the paren was opened outside the URI
+ $uri = substr($uri, 0, -1);
+ $trailer = $final . $trailer;
+ }
+ }
+ if ($longurl = common_longurl($uri)) {
+ $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8');
+ $title = " title='$longurl'";
+ }
+ else $title = '';
+
+ return '<a href="' . $uri . '"' . $title . ' class="extlink">' . $uri . '</a>' . $trailer;
+}
+
+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_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) {
+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] = preg_replace('@https?://[^)\]>\s]+@e', "common_shorten_link('\\0')", $text);
+ return $cache[$text] = preg_replace('@https?://[^)\]>\s]+@e', "common_shorten_link('\\0')", $text);
}
-function common_shorten_link($url, $reverse = false) {
- static $url_cache = array();
+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) {
+ $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;
-}
-
-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);
-}
-
-function common_tag_link($tag) {
- $canonical = common_canonical_tag($tag);
- $url = common_local_url('tag', array('tag' => $canonical));
- return '<a href="' . htmlspecialchars($url) . '" rel="tag" class="hashlink">' . htmlspecialchars($tag) . '</a>';
-}
-
-function common_canonical_tag($tag) {
- return strtolower(str_replace(array('-', '_', '.'), '', $tag));
-}
-
-function common_valid_profile_tag($str) {
- return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
-}
-
-function common_at_link($sender_id, $nickname) {
- $sender = Profile::staticGet($sender_id);
- $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
- if ($recipient) {
- return '<a href="'.htmlspecialchars($recipient->profileurl).'" class="atlink">'.$nickname.'</a>';
- } else {
- return $nickname;
- }
-}
-
-function common_at_hash_link($sender_id, $tag) {
- $user = User::staticGet($sender_id);
- if (!$user) {
- return $tag;
- }
- $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
- if ($tagged) {
- $url = common_local_url('subscriptions',
- array('nickname' => $user->nickname,
- 'tag' => $tag));
- return '<a href="'.htmlspecialchars($url).'" class="atlink">'.$tag.'</a>';
- } else {
- return $tag;
- }
-}
-
-function common_relative_profile($sender, $nickname, $dt=NULL) {
- # Try to find profiles this profile is subscribed to that have this nickname
- $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');
- if ($recipient->find(TRUE)) {
- # XXX: should probably differentiate between profiles with
- # the same name by date of most recent update
- return $recipient;
- }
- # Try to find profiles that listen to this profile and that have this nickname
- $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');
- if ($recipient->find(TRUE)) {
- # XXX: should probably differentiate between profiles with
- # the same name by date of most recent update
- return $recipient;
- }
- # If this is a local user, try to find a local user with that nickname.
- $sender = User::staticGet($sender->id);
- if ($sender) {
- $recipient_user = User::staticGet('nickname', $nickname);
- if ($recipient_user) {
- return $recipient_user->getProfile();
- }
- }
- # Otherwise, no links. @messages from local users to remote users,
- # or from remote users to other remote users, are just
- # outside our ability to make intelligent guesses about
- return NULL;
+ return (string)$short_url;
+ }
+ return $url;
+}
+
+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);
+}
+
+function common_tag_link($tag)
+{
+ $canonical = common_canonical_tag($tag);
+ $url = common_local_url('tag', array('tag' => $canonical));
+ return '<span class="tag"><a href="' . htmlspecialchars($url) . '" rel="tag">' . htmlspecialchars($tag) . '</a></span>';
+}
+
+function common_canonical_tag($tag)
+{
+ return strtolower(str_replace(array('-', '_', '.'), '', $tag));
+}
+
+function common_valid_profile_tag($str)
+{
+ return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
+}
+
+function common_at_link($sender_id, $nickname)
+{
+ $sender = Profile::staticGet($sender_id);
+ $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
+ if ($recipient) {
+ return '<span class="vcard"><a href="'.htmlspecialchars($recipient->profileurl).'" class="url"><span class="fn nickname">'.$nickname.'</span></a></span>';
+ } else {
+ return $nickname;
+ }
+}
+
+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)) {
+ return '<span class="vcard"><a href="'.htmlspecialchars($group->permalink()).'" class="url"><span class="fn nickname">'.$nickname.'</span></a></span>';
+ } else {
+ return $nickname;
+ }
+}
+
+function common_at_hash_link($sender_id, $tag)
+{
+ $user = User::staticGet($sender_id);
+ if (!$user) {
+ return $tag;
+ }
+ $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
+ if ($tagged) {
+ $url = common_local_url('subscriptions',
+ array('nickname' => $user->nickname,
+ 'tag' => $tag));
+ return '<span class="tag"><a href="'.htmlspecialchars($url).'" rel="tag">'.$tag.'</a></span>';
+ } else {
+ return $tag;
+ }
+}
+
+function common_relative_profile($sender, $nickname, $dt=null)
+{
+ // Try to find profiles this profile is subscribed to that have this nickname
+ $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');
+ if ($recipient->find(true)) {
+ // XXX: should probably differentiate between profiles with
+ // the same name by date of most recent update
+ return $recipient;
+ }
+ // Try to find profiles that listen to this profile and that have this nickname
+ $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');
+ if ($recipient->find(true)) {
+ // XXX: should probably differentiate between profiles with
+ // the same name by date of most recent update
+ return $recipient;
+ }
+ // If this is a local user, try to find a local user with that nickname.
+ $sender = User::staticGet($sender->id);
+ if ($sender) {
+ $recipient_user = User::staticGet('nickname', $nickname);
+ if ($recipient_user) {
+ return $recipient_user->getProfile();
+ }
+ }
+ // Otherwise, no links. @messages from local users to remote users,
+ // or from remote users to other remote users, are just
+ // outside our ability to make intelligent guesses about
+ return null;
}
// where should the avatar go for this user?
-function common_avatar_filename($id, $extension, $size=NULL, $extra=NULL) {
- global $config;
-
- if ($size) {
- return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension;
- } else {
- return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension;
- }
-}
-
-function common_avatar_path($filename) {
- global $config;
- return INSTALLDIR . '/avatar/' . $filename;
-}
-
-function common_avatar_url($filename) {
- return common_path('avatar/'.$filename);
-}
-
-function common_avatar_display_url($avatar) {
- $server = common_config('avatar', 'server');
- if ($server) {
- return 'http://'.$server.'/'.$avatar->filename;
- } else {
- return $avatar->url;
- }
-}
-
-function common_default_avatar($size) {
- static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
- AVATAR_STREAM_SIZE => 'stream',
- AVATAR_MINI_SIZE => 'mini');
- return theme_path('default-avatar-'.$sizenames[$size].'.png');
-}
-
-function common_local_url($action, $args=NULL, $fragment=NULL) {
- $url = NULL;
- if (common_config('site','fancy')) {
- $url = common_fancy_url($action, $args);
- } else {
- $url = common_simple_url($action, $args);
- }
- if (!is_null($fragment)) {
- $url .= '#'.$fragment;
- }
- return $url;
-}
-
-function common_fancy_url($action, $args=NULL) {
- switch (strtolower($action)) {
- case 'public':
- if ($args && isset($args['page'])) {
- return common_path('?page=' . $args['page']);
- } else {
- return common_path('');
- }
- case 'featured':
- if ($args && isset($args['page'])) {
- return common_path('featured?page=' . $args['page']);
- } else {
- return common_path('featured');
- }
- case 'favorited':
- if ($args && isset($args['page'])) {
- return common_path('favorited?page=' . $args['page']);
- } else {
- return common_path('favorited');
- }
- case 'publicrss':
- return common_path('rss');
- case 'publicatom':
- return common_path("api/statuses/public_timeline.atom");
- case 'publicxrds':
- return common_path('xrds');
- case 'featuredrss':
- return common_path('featuredrss');
- case 'favoritedrss':
- return common_path('favoritedrss');
- case 'opensearch':
- if ($args && $args['type']) {
- return common_path('opensearch/'.$args['type']);
- } else {
- return common_path('opensearch/people');
- }
- case 'doc':
- return common_path('doc/'.$args['title']);
+function common_avatar_filename($id, $extension, $size=null, $extra=null)
+{
+ global $config;
+
+ if ($size) {
+ return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension;
+ } else {
+ return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension;
+ }
+}
+
+function common_avatar_path($filename)
+{
+ global $config;
+ return INSTALLDIR . '/avatar/' . $filename;
+}
+
+function common_avatar_url($filename)
+{
+ return common_path('avatar/'.$filename);
+}
+
+function common_avatar_display_url($avatar)
+{
+ $server = common_config('avatar', 'server');
+ if ($server) {
+ return 'http://'.$server.'/'.$avatar->filename;
+ } else {
+ return $avatar->url;
+ }
+}
+
+function common_default_avatar($size)
+{
+ static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
+ AVATAR_STREAM_SIZE => 'stream',
+ AVATAR_MINI_SIZE => 'mini');
+ return theme_path('default-avatar-'.$sizenames[$size].'.png');
+}
+
+function common_local_url($action, $args=null, $fragment=null)
+{
+ $url = null;
+ if (common_config('site','fancy')) {
+ $url = common_fancy_url($action, $args);
+ } else {
+ $url = common_simple_url($action, $args);
+ }
+ if (!is_null($fragment)) {
+ $url .= '#'.$fragment;
+ }
+ return $url;
+}
+
+function common_fancy_url($action, $args=null)
+{
+ switch (strtolower($action)) {
+ case 'public':
+ if ($args && isset($args['page'])) {
+ return common_path('?page=' . $args['page']);
+ } else {
+ return common_path('');
+ }
+ case 'featured':
+ if ($args && isset($args['page'])) {
+ return common_path('featured?page=' . $args['page']);
+ } else {
+ return common_path('featured');
+ }
+ case 'favorited':
+ if ($args && isset($args['page'])) {
+ return common_path('favorited?page=' . $args['page']);
+ } else {
+ return common_path('favorited');
+ }
+ case 'publicrss':
+ return common_path('rss');
+ case 'publicatom':
+ return common_path("api/statuses/public_timeline.atom");
+ case 'publicxrds':
+ return common_path('xrds');
+ case 'featuredrss':
+ return common_path('featuredrss');
+ case 'favoritedrss':
+ return common_path('favoritedrss');
+ case 'opensearch':
+ if ($args && $args['type']) {
+ return common_path('opensearch/'.$args['type']);
+ } else {
+ return common_path('opensearch/people');
+ }
+ case 'doc':
+ return common_path('doc/'.$args['title']);
case 'block':
- case 'login':
- case 'logout':
- case 'subscribe':
- case 'unsubscribe':
- case 'invite':
- return common_path('main/'.$action);
- case 'tagother':
- return common_path('main/tagother?id='.$args['id']);
- case 'register':
- if ($args && $args['code']) {
- return common_path('main/register/'.$args['code']);
- } else {
- return common_path('main/register');
- }
- case 'remotesubscribe':
- if ($args && $args['nickname']) {
- return common_path('main/remote?nickname=' . $args['nickname']);
- } else {
- return common_path('main/remote');
- }
- case 'nudge':
- return common_path($args['nickname'].'/nudge');
- case 'openidlogin':
- return common_path('main/openid');
- case 'profilesettings':
- return common_path('settings/profile');
- case 'emailsettings':
- return common_path('settings/email');
- case 'openidsettings':
- return common_path('settings/openid');
- case 'smssettings':
- return common_path('settings/sms');
- case 'twittersettings':
- return common_path('settings/twitter');
- case 'othersettings':
- return common_path('settings/other');
+ case 'login':
+ case 'logout':
+ case 'subscribe':
+ case 'unsubscribe':
+ case 'invite':
+ return common_path('main/'.$action);
+ case 'tagother':
+ return common_path('main/tagother?id='.$args['id']);
+ case 'register':
+ if ($args && $args['code']) {
+ return common_path('main/register/'.$args['code']);
+ } else {
+ return common_path('main/register');
+ }
+ case 'remotesubscribe':
+ if ($args && $args['nickname']) {
+ return common_path('main/remote?nickname=' . $args['nickname']);
+ } else {
+ return common_path('main/remote');
+ }
+ case 'nudge':
+ return common_path($args['nickname'].'/nudge');
+ case 'openidlogin':
+ return common_path('main/openid');
+ case 'profilesettings':
+ return common_path('settings/profile');
+ case 'passwordsettings':
+ return common_path('settings/password');
+ case 'emailsettings':
+ return common_path('settings/email');
+ case 'openidsettings':
+ return common_path('settings/openid');
+ case 'smssettings':
+ return common_path('settings/sms');
+ case 'twittersettings':
+ return common_path('settings/twitter');
+ case 'othersettings':
+ return common_path('settings/other');
case 'deleteprofile':
return common_path('settings/delete');
- case 'newnotice':
- if ($args && $args['replyto']) {
- return common_path('notice/new?replyto='.$args['replyto']);
- } else {
- return common_path('notice/new');
- }
- case 'shownotice':
- return common_path('notice/'.$args['notice']);
- case 'deletenotice':
- if ($args && $args['notice']) {
- return common_path('notice/delete/'.$args['notice']);
- } else {
- return common_path('notice/delete');
- }
- case 'microsummary':
- case 'xrds':
- case 'foaf':
- return common_path($args['nickname'].'/'.$action);
- case 'all':
- case 'replies':
- case 'inbox':
- case 'outbox':
- if ($args && isset($args['page'])) {
- return common_path($args['nickname'].'/'.$action.'?page=' . $args['page']);
- } else {
- return common_path($args['nickname'].'/'.$action);
- }
- case 'subscriptions':
- case 'subscribers':
- $nickname = $args['nickname'];
- unset($args['nickname']);
- if (isset($args['tag'])) {
- $tag = $args['tag'];
- unset($args['tag']);
- }
- $params = http_build_query($args);
- if ($params) {
- return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '') . '?' . $params);
- } else {
- return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : ''));
- }
- case 'allrss':
- return common_path($args['nickname'].'/all/rss');
- case 'repliesrss':
- return common_path($args['nickname'].'/replies/rss');
- case 'userrss':
+ case 'newnotice':
+ if ($args && $args['replyto']) {
+ return common_path('notice/new?replyto='.$args['replyto']);
+ } else {
+ return common_path('notice/new');
+ }
+ case 'shownotice':
+ return common_path('notice/'.$args['notice']);
+ case 'deletenotice':
+ if ($args && $args['notice']) {
+ return common_path('notice/delete/'.$args['notice']);
+ } else {
+ return common_path('notice/delete');
+ }
+ case 'microsummary':
+ case 'xrds':
+ case 'foaf':
+ return common_path($args['nickname'].'/'.$action);
+ case 'all':
+ case 'replies':
+ case 'inbox':
+ case 'outbox':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'/'.$action.'?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname'].'/'.$action);
+ }
+ case 'subscriptions':
+ case 'subscribers':
+ $nickname = $args['nickname'];
+ unset($args['nickname']);
+ if (isset($args['tag'])) {
+ $tag = $args['tag'];
+ unset($args['tag']);
+ }
+ $params = http_build_query($args);
+ if ($params) {
+ return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '') . '?' . $params);
+ } else {
+ return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : ''));
+ }
+ case 'allrss':
+ return common_path($args['nickname'].'/all/rss');
+ case 'repliesrss':
+ return common_path($args['nickname'].'/replies/rss');
+ case 'userrss':
if (isset($args['limit']))
- return common_path($args['nickname'].'/rss?limit=' . $args['limit']);
- return common_path($args['nickname'].'/rss');
- case 'showstream':
- if ($args && isset($args['page'])) {
- return common_path($args['nickname'].'?page=' . $args['page']);
- } else {
- return common_path($args['nickname']);
- }
-
- case 'usertimeline':
- return common_path("api/statuses/user_timeline/".$args['nickname'].".atom");
- case 'confirmaddress':
- return common_path('main/confirmaddress/'.$args['code']);
- case 'userbyid':
- return common_path('user/'.$args['id']);
- case 'recoverpassword':
- $path = 'main/recoverpassword';
- if ($args['code']) {
- $path .= '/' . $args['code'];
- }
- return common_path($path);
- case 'imsettings':
- return common_path('settings/im');
- case 'peoplesearch':
- return common_path('search/people' . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'noticesearch':
- return common_path('search/notice' . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'noticesearchrss':
- return common_path('search/notice/rss' . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'avatarbynickname':
- return common_path($args['nickname'].'/avatar/'.$args['size']);
- case 'tag':
- if (isset($args['tag']) && $args['tag']) {
- $path = 'tag/' . $args['tag'];
- unset($args['tag']);
- } else {
- $path = 'tags';
- }
- return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'tagrss':
- $path = 'tag/' . $args['tag'] . '/rss';
+ return common_path($args['nickname'].'/rss?limit=' . $args['limit']);
+ return common_path($args['nickname'].'/rss');
+ case 'showstream':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname']);
+ }
+
+ case 'usertimeline':
+ return common_path("api/statuses/user_timeline/".$args['nickname'].".atom");
+ case 'confirmaddress':
+ return common_path('main/confirmaddress/'.$args['code']);
+ case 'userbyid':
+ return common_path('user/'.$args['id']);
+ case 'recoverpassword':
+ $path = 'main/recoverpassword';
+ if ($args['code']) {
+ $path .= '/' . $args['code'];
+ }
+ return common_path($path);
+ case 'imsettings':
+ return common_path('settings/im');
+ case 'avatarsettings':
+ return common_path('settings/avatar');
+ case 'peoplesearch':
+ return common_path('search/people' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'noticesearch':
+ return common_path('search/notice' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'noticesearchrss':
+ return common_path('search/notice/rss' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'avatarbynickname':
+ return common_path($args['nickname'].'/avatar/'.$args['size']);
+ case 'tag':
+ $path = 'tag/' . $args['tag'];
+ unset($args['tag']);
+ return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'publictagcloud':
+ return common_path('tags');
+ case 'peopletag':
+ $path = 'peopletag/' . $args['tag'];
unset($args['tag']);
- return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'peopletag':
- $path = 'peopletag/' . $args['tag'];
- unset($args['tag']);
- return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'tags':
- return common_path('tags' . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'favor':
- return common_path('main/favor');
- case 'disfavor':
- return common_path('main/disfavor');
- case 'showfavorites':
- if ($args && isset($args['page'])) {
- return common_path($args['nickname'].'/favorites?page=' . $args['page']);
- } else {
- return common_path($args['nickname'].'/favorites');
- }
- case 'favoritesrss':
- return common_path($args['nickname'].'/favorites/rss');
- case 'showmessage':
- return common_path('message/' . $args['message']);
- case 'newmessage':
- return common_path('message/new' . (($args) ? ('?' . http_build_query($args)) : ''));
- case 'api':
- # XXX: do fancy URLs for all the API methods
- switch (strtolower($args['apiaction'])) {
- case 'statuses':
- switch (strtolower($args['method'])) {
- case 'user_timeline.rss':
- return common_path('api/statuses/user_timeline/'.$args['argument'].'.rss');
- case 'user_timeline.atom':
- return common_path('api/statuses/user_timeline/'.$args['argument'].'.atom');
- case 'user_timeline.json':
- return common_path('api/statuses/user_timeline/'.$args['argument'].'.json');
- case 'user_timeline.xml':
- return common_path('api/statuses/user_timeline/'.$args['argument'].'.xml');
- default: return common_simple_url($action, $args);
- }
- default: return common_simple_url($action, $args);
- }
- case 'sup':
- if ($args && isset($args['seconds'])) {
- return common_path('main/sup?seconds='.$args['seconds']);
- } else {
- return common_path('main/sup');
- }
- default:
- return common_simple_url($action, $args);
- }
-}
-
-function common_simple_url($action, $args=NULL) {
- global $config;
- /* XXX: pretty URLs */
- $extra = '';
- if ($args) {
- foreach ($args as $key => $value) {
- $extra .= "&${key}=${value}";
- }
- }
- return common_path("index.php?action=${action}${extra}");
-}
-
-function common_path($relative) {
- global $config;
- $pathpart = ($config['site']['path']) ? $config['site']['path']."/" : '';
- return "http://".$config['site']['server'].'/'.$pathpart.$relative;
-}
-
-function common_date_string($dt) {
- // XXX: do some sexy date formatting
- // return date(DATE_RFC822, $dt);
- $t = strtotime($dt);
- $now = time();
- $diff = $now - $t;
-
- if ($now < $t) { # that shouldn't happen!
- return common_exact_date($dt);
- } else if ($diff < 60) {
- return _('a few seconds ago');
- } else if ($diff < 92) {
- return _('about a minute ago');
- } else if ($diff < 3300) {
- return sprintf(_('about %d minutes ago'), round($diff/60));
- } else if ($diff < 5400) {
- return _('about an hour ago');
- } else if ($diff < 22 * 3600) {
- return sprintf(_('about %d hours ago'), round($diff/3600));
- } else if ($diff < 37 * 3600) {
- return _('about a day ago');
- } else if ($diff < 24 * 24 * 3600) {
- return sprintf(_('about %d days ago'), round($diff/(24*3600)));
- } else if ($diff < 46 * 24 * 3600) {
- return _('about a month ago');
- } else if ($diff < 330 * 24 * 3600) {
- return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
- } else if ($diff < 480 * 24 * 3600) {
- return _('about a year ago');
- } else {
- return common_exact_date($dt);
- }
-}
-
-function common_exact_date($dt) {
+ return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'tags':
+ return common_path('tags' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'favor':
+ return common_path('main/favor');
+ case 'disfavor':
+ return common_path('main/disfavor');
+ case 'showfavorites':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'/favorites?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname'].'/favorites');
+ }
+ case 'favoritesrss':
+ return common_path($args['nickname'].'/favorites/rss');
+ case 'showmessage':
+ return common_path('message/' . $args['message']);
+ case 'newmessage':
+ return common_path('message/new' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'api':
+ // XXX: do fancy URLs for all the API methods
+ switch (strtolower($args['apiaction'])) {
+ case 'statuses':
+ switch (strtolower($args['method'])) {
+ case 'user_timeline.rss':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.rss');
+ case 'user_timeline.atom':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.atom');
+ case 'user_timeline.json':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.json');
+ case 'user_timeline.xml':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.xml');
+ default: return common_simple_url($action, $args);
+ }
+ default: return common_simple_url($action, $args);
+ }
+ case 'sup':
+ if ($args && isset($args['seconds'])) {
+ return common_path('main/sup?seconds='.$args['seconds']);
+ } else {
+ return common_path('main/sup');
+ }
+ case 'newgroup':
+ return common_path('group/new');
+ case 'showgroup':
+ return common_path('group/'.$args['nickname']);
+ case 'editgroup':
+ return common_path('group/'.$args['nickname'].'/edit');
+ case 'joingroup':
+ return common_path('group/'.$args['nickname'].'/join');
+ case 'leavegroup':
+ return common_path('group/'.$args['nickname'].'/leave');
+ case 'groupbyid':
+ return common_path('group/'.$args['id'].'/id');
+ case 'grouprss':
+ return common_path('group/'.$args['nickname'].'/rss');
+ case 'groupmembers':
+ return common_path('group/'.$args['nickname'].'/members');
+ case 'grouplogo':
+ return common_path('group/'.$args['nickname'].'/logo');
+ case 'usergroups':
+ return common_path($args['nickname'].'/groups');
+ case 'groups':
+ return common_path('group');
+ default:
+ return common_simple_url($action, $args);
+ }
+}
+
+function common_simple_url($action, $args=null)
+{
+ global $config;
+ /* XXX: pretty URLs */
+ $extra = '';
+ if ($args) {
+ foreach ($args as $key => $value) {
+ $extra .= "&${key}=${value}";
+ }
+ }
+ return common_path("index.php?action=${action}${extra}");
+}
+
+function common_path($relative)
+{
+ global $config;
+ $pathpart = ($config['site']['path']) ? $config['site']['path']."/" : '';
+ return "http://".$config['site']['server'].'/'.$pathpart.$relative;
+}
+
+function common_date_string($dt)
+{
+ // XXX: do some sexy date formatting
+ // return date(DATE_RFC822, $dt);
+ $t = strtotime($dt);
+ $now = time();
+ $diff = $now - $t;
+
+ if ($now < $t) { // that shouldn't happen!
+ return common_exact_date($dt);
+ } else if ($diff < 60) {
+ return _('a few seconds ago');
+ } else if ($diff < 92) {
+ return _('about a minute ago');
+ } else if ($diff < 3300) {
+ return sprintf(_('about %d minutes ago'), round($diff/60));
+ } else if ($diff < 5400) {
+ return _('about an hour ago');
+ } else if ($diff < 22 * 3600) {
+ return sprintf(_('about %d hours ago'), round($diff/3600));
+ } else if ($diff < 37 * 3600) {
+ return _('about a day ago');
+ } else if ($diff < 24 * 24 * 3600) {
+ return sprintf(_('about %d days ago'), round($diff/(24*3600)));
+ } else if ($diff < 46 * 24 * 3600) {
+ return _('about a month ago');
+ } else if ($diff < 330 * 24 * 3600) {
+ return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
+ } else if ($diff < 480 * 24 * 3600) {
+ return _('about a year ago');
+ } else {
+ return common_exact_date($dt);
+ }
+}
+
+function common_exact_date($dt)
+{
static $_utc;
static $_siteTz;
@@ -1321,907 +974,614 @@ function common_exact_date($dt) {
$_siteTz = new DateTimeZone(common_timezone());
}
- $dateStr = date('d F Y H:i:s', strtotime($dt));
- $d = new DateTime($dateStr, $_utc);
- $d->setTimezone($_siteTz);
- return $d->format(DATE_RFC850);
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, $_utc);
+ $d->setTimezone($_siteTz);
+ return $d->format(DATE_RFC850);
}
-function common_date_w3dtf($dt) {
- $dateStr = date('d F Y H:i:s', strtotime($dt));
- $d = new DateTime($dateStr, new DateTimeZone('UTC'));
- $d->setTimezone(new DateTimeZone(common_timezone()));
- return $d->format(DATE_W3C);
+function common_date_w3dtf($dt)
+{
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format(DATE_W3C);
}
-function common_date_rfc2822($dt) {
- $dateStr = date('d F Y H:i:s', strtotime($dt));
- $d = new DateTime($dateStr, new DateTimeZone('UTC'));
- $d->setTimezone(new DateTimeZone(common_timezone()));
- return $d->format('r');
+function common_date_rfc2822($dt)
+{
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format('r');
}
-function common_date_iso8601($dt) {
- $dateStr = date('d F Y H:i:s', strtotime($dt));
- $d = new DateTime($dateStr, new DateTimeZone('UTC'));
- $d->setTimezone(new DateTimeZone(common_timezone()));
- return $d->format('c');
+function common_date_iso8601($dt)
+{
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format('c');
}
-function common_sql_now() {
- return strftime('%Y-%m-%d %H:%M:%S', time());
+function common_sql_now()
+{
+ return strftime('%Y-%m-%d %H:%M:%S', time());
}
-function common_redirect($url, $code=307) {
- static $status = array(301 => "Moved Permanently",
- 302 => "Found",
- 303 => "See Other",
- 307 => "Temporary Redirect");
- header("Status: ${code} $status[$code]");
- header("Location: $url");
-
- common_start_xml('a',
- '-//W3C//DTD XHTML 1.0 Strict//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
- common_element('a', array('href' => $url), $url);
- common_end_xml();
+function common_redirect($url, $code=307)
+{
+ static $status = array(301 => "Moved Permanently",
+ 302 => "Found",
+ 303 => "See Other",
+ 307 => "Temporary Redirect");
+
+ header("Status: ${code} $status[$code]");
+ header("Location: $url");
+
+ $xo = new XMLOutputter();
+ $xo->startXML('a',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+ $xo->element('a', array('href' => $url), $url);
+ $xo->endXML();
exit;
}
-function common_save_replies($notice) {
- # Alternative reply format
- $tname = false;
- if (preg_match('/^T ([A-Z0-9]{1,64}) /', $notice->content, $match)) {
- $tname = $match[1];
- }
- # extract all @messages
- $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $notice->content, $match);
-
- $names = array();
-
- if ($cnt || $tname) {
- # XXX: is there another way to make an array copy?
- $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]);
- }
-
- $sender = Profile::staticGet($notice->profile_id);
-
- $replied = array();
-
- # store replied only for first @ (what user/notice what the reply directed,
- # we assume first @ is it)
-
- for ($i=0; $i<count($names); $i++) {
- $nickname = $names[$i];
- $recipient = common_relative_profile($sender, $nickname, $notice->created);
- if (!$recipient) {
- continue;
- }
- if ($i == 0 && ($recipient->id != $sender->id) && !$notice->reply_to) { # Don't save reply to self
- $reply_for = $recipient;
- $recipient_notice = $reply_for->getCurrentNotice();
- if ($recipient_notice) {
- $orig = clone($notice);
- $notice->reply_to = $recipient_notice->id;
- $notice->update($orig);
- }
- }
- # Don't save replies from blocked profile to local user
- $recipient_user = User::staticGet('id', $recipient->id);
- if ($recipient_user && $recipient_user->hasBlocked($sender)) {
- continue;
+function common_broadcast_notice($notice, $remote=false)
+{
+
+ // Check to see if notice should go to Twitter
+ $flink = Foreign_link::getByUserID($notice->profile_id, 1); // 1 == Twitter
+ if (($flink->noticesync & FOREIGN_NOTICE_SEND) == 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) ||
+ (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) {
+
+ $result = common_twitter_broadcast($notice, $flink);
+
+ if (!$result) {
+ common_debug('Unable to send notice: ' . $notice->id . ' to Twitter.', __FILE__);
+ }
}
- $reply = new Reply();
- $reply->notice_id = $notice->id;
- $reply->profile_id = $recipient->id;
- $id = $reply->insert();
- if (!$id) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message);
- common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message));
- return;
- } else {
- $replied[$recipient->id] = 1;
- }
- }
-
- # Hash format replies, too
- $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $notice->content, $match);
- if ($cnt) {
- foreach ($match[1] as $tag) {
- $tagged = Profile_tag::getTagged($sender->id, $tag);
- foreach ($tagged as $t) {
- if (!$replied[$t->id]) {
- # Don't save replies from blocked profile to local user
- $t_user = User::staticGet('id', $t->id);
- if ($t_user && $t_user->hasBlocked($sender)) {
- continue;
- }
- $reply = new Reply();
- $reply->notice_id = $notice->id;
- $reply->profile_id = $t->id;
- $id = $reply->insert();
- if (!$id) {
- common_log_db_error($reply, 'INSERT', __FILE__);
- return;
- }
- }
- }
- }
- }
-}
-
-function common_broadcast_notice($notice, $remote=false) {
-
- // Check to see if notice should go to Twitter
- $flink = Foreign_link::getByUserID($notice->profile_id, 1); // 1 == Twitter
- if (($flink->noticesync & FOREIGN_NOTICE_SEND) == 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) ||
- (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) {
-
- $result = common_twitter_broadcast($notice, $flink);
-
- if (!$result) {
- common_debug('Unable to send notice: ' . $notice->id . ' to Twitter.', __FILE__);
- }
- }
- }
-
- if (common_config('queue', 'enabled')) {
- # Do it later!
- return common_enqueue_notice($notice);
- } else {
- return common_real_broadcast($notice, $remote);
- }
-}
-
-function common_twitter_broadcast($notice, $flink) {
- global $config;
- $success = true;
- $fuser = $flink->getForeignUser();
- $twitter_user = $fuser->nickname;
- $twitter_password = $flink->credentials;
- $uri = 'http://www.twitter.com/statuses/update.json';
-
- // XXX: Hack to get around PHP cURL's use of @ being a a meta character
- $statustxt = preg_replace('/^@/', ' @', $notice->content);
-
- $options = array(
- CURLOPT_USERPWD => "$twitter_user:$twitter_password",
- CURLOPT_POST => true,
- CURLOPT_POSTFIELDS => array(
- 'status' => $statustxt,
- 'source' => $config['integration']['source']
- ),
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_FAILONERROR => true,
- CURLOPT_HEADER => false,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_USERAGENT => "Laconica",
- CURLOPT_CONNECTTIMEOUT => 120, // XXX: Scary!!!! How long should this be?
- CURLOPT_TIMEOUT => 120,
-
- # Twitter is strict about accepting invalid "Expect" headers
- CURLOPT_HTTPHEADER => array('Expect:')
- );
-
- $ch = curl_init($uri);
+ }
+
+ if (common_config('queue', 'enabled')) {
+ // Do it later!
+ return common_enqueue_notice($notice);
+ } else {
+ return common_real_broadcast($notice, $remote);
+ }
+}
+
+function common_twitter_broadcast($notice, $flink)
+{
+ global $config;
+ $success = true;
+ $fuser = $flink->getForeignUser();
+ $twitter_user = $fuser->nickname;
+ $twitter_password = $flink->credentials;
+ $uri = 'http://www.twitter.com/statuses/update.json';
+
+ // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+ $statustxt = preg_replace('/^@/', ' @', $notice->content);
+
+ $options = array(
+ CURLOPT_USERPWD => "$twitter_user:$twitter_password",
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => array(
+ 'status' => $statustxt,
+ 'source' => $config['integration']['source']
+ ),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_USERAGENT => "Laconica",
+ CURLOPT_CONNECTTIMEOUT => 120, // XXX: Scary!!!! How long should this be?
+ CURLOPT_TIMEOUT => 120,
+
+ # Twitter is strict about accepting invalid "Expect" headers
+ CURLOPT_HTTPHEADER => array('Expect:')
+ );
+
+ $ch = curl_init($uri);
curl_setopt_array($ch, $options);
$data = curl_exec($ch);
$errmsg = curl_error($ch);
- if ($errmsg) {
- common_debug("cURL error: $errmsg - trying to send notice for $twitter_user.",
- __FILE__);
- $success = false;
- }
+ if ($errmsg) {
+ common_debug("cURL error: $errmsg - trying to send notice for $twitter_user.",
+ __FILE__);
+ $success = false;
+ }
- curl_close($ch);
+ curl_close($ch);
- if (!$data) {
- common_debug("No data returned by Twitter's API trying to send update for $twitter_user",
- __FILE__);
- $success = false;
- }
+ if (!$data) {
+ common_debug("No data returned by Twitter's API trying to send update for $twitter_user",
+ __FILE__);
+ $success = false;
+ }
- // Twitter should return a status
- $status = json_decode($data);
+ // Twitter should return a status
+ $status = json_decode($data);
- if (!$status->id) {
- common_debug("Unexpected data returned by Twitter API trying to send update for $twitter_user",
- __FILE__);
- $success = false;
- }
+ if (!$status->id) {
+ common_debug("Unexpected data returned by Twitter API trying to send update for $twitter_user",
+ __FILE__);
+ $success = false;
+ }
- return $success;
+ return $success;
}
-# Stick the notice on the queue
+// Stick the notice on the queue
-function common_enqueue_notice($notice) {
- foreach (array('jabber', 'omb', 'sms', 'public') as $transport) {
- $qi = new Queue_item();
- $qi->notice_id = $notice->id;
- $qi->transport = $transport;
- $qi->created = $notice->created;
+function common_enqueue_notice($notice)
+{
+ foreach (array('jabber', 'omb', 'sms', 'public') 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);
- }
- return $result;
-}
-
-function common_dequeue_notice($notice) {
- $qi = Queue_item::staticGet($notice->id);
- if ($qi) {
- $result = $qi->delete();
- if (!$result) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, 'DB error deleting queue item: ' . $last_error->message);
- return false;
- }
- common_log(LOG_DEBUG, 'complete dequeueing notice ID = ' . $notice->id);
- return $result;
- } else {
+ 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);
+ }
+ return $result;
}
-function common_real_broadcast($notice, $remote=false) {
- $success = true;
- if (!$remote) {
- # Make sure we have the OMB stuff
- require_once(INSTALLDIR.'/lib/omb.php');
- $success = omb_broadcast_remote_subscribers($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- require_once(INSTALLDIR.'/lib/jabber.php');
- $success = jabber_broadcast_notice($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- require_once(INSTALLDIR.'/lib/mail.php');
- $success = mail_broadcast_notice_sms($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
- }
- }
- if ($success) {
- $success = jabber_public_notice($notice);
- if (!$success) {
- common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
- }
- }
- // XXX: broadcast notices to other IM
- return $success;
-}
-
-function common_broadcast_profile($profile) {
- // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
- require_once(INSTALLDIR.'/lib/omb.php');
- omb_broadcast_profile($profile);
- // XXX: Other broadcasts...?
- return true;
-}
-
-function common_profile_url($nickname) {
- return common_local_url('showstream', array('nickname' => $nickname));
-}
-
-# Don't call if nobody's logged in
-
-function common_notice_form($action=NULL, $content=NULL) {
- $user = common_current_user();
- assert(!is_null($user));
- common_element_start('form', array('id' => 'status_form',
- 'method' => 'post',
- 'action' => common_local_url('newnotice')));
- common_element_start('p');
- common_element('label', array('for' => 'status_textarea',
- 'id' => 'status_label'),
- sprintf(_('What\'s up, %s?'), $user->nickname));
- common_element('span', array('id' => 'counter', 'class' => 'counter'), '140');
- common_element('textarea', array('id' => 'status_textarea',
- 'cols' => 60,
- 'rows' => 3,
- 'name' => 'status_textarea'),
- ($content) ? $content : '');
- common_hidden('token', common_session_token());
- if ($action) {
- common_hidden('returnto', $action);
- }
- # set by JavaScript
- common_hidden('inreplyto', 'false');
- common_element('input', array('id' => 'status_submit',
- 'name' => 'status_submit',
- 'type' => 'submit',
- 'value' => _('Send')));
- common_element_end('p');
- common_element_end('form');
-}
-
-# Should make up a reasonable root URL
-
-function common_root_url() {
- return common_path('');
-}
-
-# returns $bytes bytes of random data as a hexadecimal string
-# "good" here is a goal and not a guarantee
-
-function common_good_rand($bytes) {
- # XXX: use random.org...?
- if (file_exists('/dev/urandom')) {
- return common_urandom($bytes);
- } else { # FIXME: this is probably not good enough
- return common_mtrand($bytes);
- }
-}
-
-function common_urandom($bytes) {
- $h = fopen('/dev/urandom', 'rb');
- # should not block
- $src = fread($h, $bytes);
- fclose($h);
- $enc = '';
- for ($i = 0; $i < $bytes; $i++) {
- $enc .= sprintf("%02x", (ord($src[$i])));
- }
- return $enc;
-}
-
-function common_mtrand($bytes) {
- $enc = '';
- for ($i = 0; $i < $bytes; $i++) {
- $enc .= sprintf("%02x", mt_rand(0, 255));
- }
- return $enc;
-}
-
-function common_set_returnto($url) {
- common_ensure_session();
- $_SESSION['returnto'] = $url;
-}
-
-function common_get_returnto() {
- common_ensure_session();
- return $_SESSION['returnto'];
-}
-
-function common_timestamp() {
- return date('YmdHis');
-}
-
-function common_ensure_syslog() {
- static $initialized = false;
- if (!$initialized) {
- global $config;
- openlog($config['syslog']['appname'], 0, LOG_USER);
- $initialized = true;
- }
-}
-
-function common_log($priority, $msg, $filename=NULL) {
- $logfile = common_config('site', 'logfile');
- if ($logfile) {
- $log = fopen($logfile, "a");
- if ($log) {
- static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
- 'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
- $output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
- fwrite($log, $output);
- fclose($log);
- }
- } else {
- common_ensure_syslog();
- syslog($priority, $msg);
- }
-}
-
-function common_debug($msg, $filename=NULL) {
- if ($filename) {
- common_log(LOG_DEBUG, basename($filename).' - '.$msg);
- } else {
- common_log(LOG_DEBUG, $msg);
- }
-}
-
-function common_log_db_error(&$object, $verb, $filename=NULL) {
- $objstr = common_log_objstring($object);
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, $last_error->message . '(' . $verb . ' on ' . $objstr . ')', $filename);
+function common_dequeue_notice($notice)
+{
+ $qi = Queue_item::staticGet($notice->id);
+ if ($qi) {
+ $result = $qi->delete();
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error deleting queue item: ' . $last_error->message);
+ return false;
+ }
+ common_log(LOG_DEBUG, 'complete dequeueing notice ID = ' . $notice->id);
+ return $result;
+ } else {
+ return false;
+ }
}
-function common_log_objstring(&$object) {
- if (is_null($object)) {
- return "NULL";
- }
- $arr = $object->toArray();
- $fields = array();
- foreach ($arr as $k => $v) {
- $fields[] = "$k='$v'";
- }
- $objstring = $object->tableName() . '[' . implode(',', $fields) . ']';
- return $objstring;
+function common_real_broadcast($notice, $remote=false)
+{
+ $success = true;
+ if (!$remote) {
+ // Make sure we have the OMB stuff
+ require_once(INSTALLDIR.'/lib/omb.php');
+ $success = omb_broadcast_remote_subscribers($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ require_once(INSTALLDIR.'/lib/jabber.php');
+ $success = jabber_broadcast_notice($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ require_once(INSTALLDIR.'/lib/mail.php');
+ $success = mail_broadcast_notice_sms($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ $success = jabber_public_notice($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
+ }
+ }
+ // XXX: broadcast notices to other IM
+ return $success;
}
-function common_valid_http_url($url) {
- return Validate::uri($url, array('allowed_schemes' => array('http', 'https')));
-}
+function common_broadcast_profile($profile)
+{
+ // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
+ require_once(INSTALLDIR.'/lib/omb.php');
+ omb_broadcast_profile($profile);
+ // XXX: Other broadcasts...?
+ return true;
+}
-function common_valid_tag($tag) {
- if (preg_match('/^tag:(.*?),(\d{4}(-\d{2}(-\d{2})?)?):(.*)$/', $tag, $matches)) {
- return (Validate::email($matches[1]) ||
- preg_match('/^([\w-\.]+)$/', $matches[1]));
- }
- return false;
+function common_profile_url($nickname)
+{
+ return common_local_url('showstream', array('nickname' => $nickname));
}
-# Does a little before-after block for next/prev page
-
-function common_pagination($have_before, $have_after, $page, $action, $args=NULL) {
+// Should make up a reasonable root URL
- if ($have_before || $have_after) {
- common_element_start('div', array('id' => 'pagination'));
- common_element_start('ul', array('id' => 'nav_pagination'));
- }
+function common_root_url()
+{
+ return common_path('');
+}
- if ($have_before) {
- $pargs = array('page' => $page-1);
- $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
+// returns $bytes bytes of random data as a hexadecimal string
+// "good" here is a goal and not a guarantee
- common_element_start('li', 'before');
- common_element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'prev'),
- _('« After'));
- common_element_end('li');
- }
+function common_good_rand($bytes)
+{
+ // XXX: use random.org...?
+ if (file_exists('/dev/urandom')) {
+ return common_urandom($bytes);
+ } else { // FIXME: this is probably not good enough
+ return common_mtrand($bytes);
+ }
+}
- if ($have_after) {
- $pargs = array('page' => $page+1);
- $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
- common_element_start('li', 'after');
- common_element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'next'),
- _('Before »'));
- common_element_end('li');
- }
+function common_urandom($bytes)
+{
+ $h = fopen('/dev/urandom', 'rb');
+ // should not block
+ $src = fread($h, $bytes);
+ fclose($h);
+ $enc = '';
+ for ($i = 0; $i < $bytes; $i++) {
+ $enc .= sprintf("%02x", (ord($src[$i])));
+ }
+ return $enc;
+}
- if ($have_before || $have_after) {
- common_element_end('ul');
- common_element_end('div');
- }
+function common_mtrand($bytes)
+{
+ $enc = '';
+ for ($i = 0; $i < $bytes; $i++) {
+ $enc .= sprintf("%02x", mt_rand(0, 255));
+ }
+ return $enc;
}
-/* Following functions are copied from MediaWiki GlobalFunctions.php
- * and written by Evan Prodromou. */
+function common_set_returnto($url)
+{
+ common_ensure_session();
+ $_SESSION['returnto'] = $url;
+}
-function common_accept_to_prefs($accept, $def = '*/*') {
- # No arg means accept anything (per HTTP spec)
- if(!$accept) {
- return array($def => 1);
- }
-
- $prefs = array();
-
- $parts = explode(',', $accept);
-
- foreach($parts as $part) {
- # FIXME: doesn't deal with params like 'text/html; level=1'
- @list($value, $qpart) = explode(';', $part);
- $match = array();
- if(!isset($qpart)) {
- $prefs[$value] = 1;
- } elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)) {
- $prefs[$value] = $match[1];
- }
- }
-
- return $prefs;
-}
-
-function common_mime_type_match($type, $avail) {
- if(array_key_exists($type, $avail)) {
- return $type;
- } else {
- $parts = explode('/', $type);
- if(array_key_exists($parts[0] . '/*', $avail)) {
- return $parts[0] . '/*';
- } elseif(array_key_exists('*/*', $avail)) {
- return '*/*';
- } else {
- return NULL;
- }
- }
-}
-
-function common_negotiate_type($cprefs, $sprefs) {
- $combine = array();
-
- foreach(array_keys($sprefs) as $type) {
- $parts = explode('/', $type);
- if($parts[1] != '*') {
- $ckey = common_mime_type_match($type, $cprefs);
- if($ckey) {
- $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
- }
- }
- }
-
- foreach(array_keys($cprefs) as $type) {
- $parts = explode('/', $type);
- if($parts[1] != '*' && !array_key_exists($type, $sprefs)) {
- $skey = common_mime_type_match($type, $sprefs);
- if($skey) {
- $combine[$type] = $sprefs[$skey] * $cprefs[$type];
- }
- }
- }
-
- $bestq = 0;
- $besttype = "text/html";
-
- foreach(array_keys($combine) as $type) {
- if($combine[$type] > $bestq) {
- $besttype = $type;
- $bestq = $combine[$type];
- }
- }
-
- return $besttype;
-}
-
-function common_config($main, $sub) {
- global $config;
- return isset($config[$main][$sub]) ? $config[$main][$sub] : false;
-}
-
-function common_copy_args($from) {
- $to = array();
- $strip = get_magic_quotes_gpc();
- foreach ($from as $k => $v) {
- $to[$k] = ($strip) ? stripslashes($v) : $v;
- }
- return $to;
+function common_get_returnto()
+{
+ common_ensure_session();
+ return $_SESSION['returnto'];
}
-// Neutralise the evil effects of magic_quotes_gpc in the current request.
-// This is used before handing a request off to OAuthRequest::from_request.
-function common_remove_magic_from_request() {
- if(get_magic_quotes_gpc()) {
- $_POST=array_map('stripslashes',$_POST);
- $_GET=array_map('stripslashes',$_GET);
- }
+function common_timestamp()
+{
+ return date('YmdHis');
}
-function common_user_uri(&$user) {
- return common_local_url('userbyid', array('id' => $user->id));
+function common_ensure_syslog()
+{
+ static $initialized = false;
+ if (!$initialized) {
+ global $config;
+ openlog($config['syslog']['appname'], 0, LOG_USER);
+ $initialized = true;
+ }
}
-function common_notice_uri(&$notice) {
- return common_local_url('shownotice',
- array('notice' => $notice->id));
+function common_log($priority, $msg, $filename=null)
+{
+ $logfile = common_config('site', 'logfile');
+ if ($logfile) {
+ $log = fopen($logfile, "a");
+ if ($log) {
+ static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
+ 'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
+ $output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
+ fwrite($log, $output);
+ fclose($log);
+ }
+ } else {
+ common_ensure_syslog();
+ syslog($priority, $msg);
+ }
}
-# 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
+function common_debug($msg, $filename=null)
+{
+ if ($filename) {
+ common_log(LOG_DEBUG, basename($filename).' - '.$msg);
+ } else {
+ common_log(LOG_DEBUG, $msg);
+ }
+}
-function common_confirmation_code($bits) {
- # 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
- static $codechars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
- $chars = ceil($bits/5);
- $code = '';
- for ($i = 0; $i < $chars; $i++) {
- # XXX: convert to string and back
- $num = hexdec(common_good_rand(1));
- # XXX: randomness is too precious to throw away almost
- # 40% of the bits we get!
- $code .= $codechars[$num%32];
- }
- return $code;
+function common_log_db_error(&$object, $verb, $filename=null)
+{
+ $objstr = common_log_objstring($object);
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, $last_error->message . '(' . $verb . ' on ' . $objstr . ')', $filename);
}
-# convert markup to HTML
+function common_log_objstring(&$object)
+{
+ if (is_null($object)) {
+ return "null";
+ }
+ $arr = $object->toArray();
+ $fields = array();
+ foreach ($arr as $k => $v) {
+ $fields[] = "$k='$v'";
+ }
+ $objstring = $object->tableName() . '[' . implode(',', $fields) . ']';
+ return $objstring;
+}
-function common_markup_to_html($c) {
- $c = preg_replace('/%%action.(\w+)%%/e', "common_local_url('\\1')", $c);
- $c = preg_replace('/%%doc.(\w+)%%/e', "common_local_url('doc', array('title'=>'\\1'))", $c);
- $c = preg_replace('/%%(\w+).(\w+)%%/e', 'common_config(\'\\1\', \'\\2\')', $c);
- return Markdown($c);
+function common_valid_http_url($url)
+{
+ return Validate::uri($url, array('allowed_schemes' => array('http', 'https')));
}
-function common_profile_avatar_url($profile, $size=AVATAR_PROFILE_SIZE) {
- $avatar = $profile->getAvatar($size);
- if ($avatar) {
- return common_avatar_display_url($avatar);
- } else {
- return common_default_avatar($size);
- }
+function common_valid_tag($tag)
+{
+ if (preg_match('/^tag:(.*?),(\d{4}(-\d{2}(-\d{2})?)?):(.*)$/', $tag, $matches)) {
+ return (Validate::email($matches[1]) ||
+ preg_match('/^([\w-\.]+)$/', $matches[1]));
+ }
+ return false;
}
-function common_profile_uri($profile) {
- if (!$profile) {
- return NULL;
- }
- $user = User::staticGet($profile->id);
- if ($user) {
- return $user->uri;
- }
+/* Following functions are copied from MediaWiki GlobalFunctions.php
+ * and written by Evan Prodromou. */
+
+function common_accept_to_prefs($accept, $def = '*/*')
+{
+ // No arg means accept anything (per HTTP spec)
+ if(!$accept) {
+ return array($def => 1);
+ }
+
+ $prefs = array();
+
+ $parts = explode(',', $accept);
- $remote = Remote_profile::staticGet($profile->id);
- if ($remote) {
- return $remote->uri;
- }
- # XXX: this is a very bad profile!
- return NULL;
+ foreach($parts as $part) {
+ // FIXME: doesn't deal with params like 'text/html; level=1'
+ @list($value, $qpart) = explode(';', $part);
+ $match = array();
+ if(!isset($qpart)) {
+ $prefs[$value] = 1;
+ } elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)) {
+ $prefs[$value] = $match[1];
+ }
+ }
+
+ return $prefs;
}
-function common_canonical_sms($sms) {
- # strip non-digits
- preg_replace('/\D/', '', $sms);
- return $sms;
+function common_mime_type_match($type, $avail)
+{
+ if(array_key_exists($type, $avail)) {
+ return $type;
+ } else {
+ $parts = explode('/', $type);
+ if(array_key_exists($parts[0] . '/*', $avail)) {
+ return $parts[0] . '/*';
+ } elseif(array_key_exists('*/*', $avail)) {
+ return '*/*';
+ } else {
+ return null;
+ }
+ }
}
-function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
- switch ($errno) {
- case E_USER_ERROR:
- common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
- exit(1);
- break;
+function common_negotiate_type($cprefs, $sprefs)
+{
+ $combine = array();
+
+ foreach(array_keys($sprefs) as $type) {
+ $parts = explode('/', $type);
+ if($parts[1] != '*') {
+ $ckey = common_mime_type_match($type, $cprefs);
+ if($ckey) {
+ $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
+ }
+ }
+ }
- case E_USER_WARNING:
- common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
- break;
+ foreach(array_keys($cprefs) as $type) {
+ $parts = explode('/', $type);
+ if($parts[1] != '*' && !array_key_exists($type, $sprefs)) {
+ $skey = common_mime_type_match($type, $sprefs);
+ if($skey) {
+ $combine[$type] = $sprefs[$skey] * $cprefs[$type];
+ }
+ }
+ }
- case E_USER_NOTICE:
- common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
- break;
+ $bestq = 0;
+ $besttype = "text/html";
+
+ foreach(array_keys($combine) as $type) {
+ if($combine[$type] > $bestq) {
+ $besttype = $type;
+ $bestq = $combine[$type];
+ }
}
- # FIXME: show error page if we're on the Web
- /* Don't execute PHP internal error handler */
- return true;
+ return $besttype;
}
-function common_session_token() {
- common_ensure_session();
- if (!array_key_exists('token', $_SESSION)) {
- $_SESSION['token'] = common_good_rand(64);
- }
- return $_SESSION['token'];
-}
-
-function common_disfavor_form($notice) {
- common_element_start('form', array('id' => 'disfavor-' . $notice->id,
- 'method' => 'post',
- 'class' => 'disfavor',
- 'action' => common_local_url('disfavor')));
-
- common_element('input', array('type' => 'hidden',
- 'name' => 'token-'. $notice->id,
- 'id' => 'token-'. $notice->id,
- 'class' => 'token',
- 'value' => common_session_token()));
-
- common_element('input', array('type' => 'hidden',
- 'name' => 'notice',
- 'id' => 'notice-n'. $notice->id,
- 'class' => 'notice',
- 'value' => $notice->id));
-
- common_element('input', array('type' => 'submit',
- 'id' => 'disfavor-submit-' . $notice->id,
- 'name' => 'disfavor-submit-' . $notice->id,
- 'class' => 'disfavor',
- 'value' => 'Disfavor favorite',
- 'title' => 'Remove this message from favorites'));
- common_element_end('form');
-}
-
-function common_favor_form($notice) {
- common_element_start('form', array('id' => 'favor-' . $notice->id,
- 'method' => 'post',
- 'class' => 'favor',
- 'action' => common_local_url('favor')));
-
- common_element('input', array('type' => 'hidden',
- 'name' => 'token-'. $notice->id,
- 'id' => 'token-'. $notice->id,
- 'class' => 'token',
- 'value' => common_session_token()));
-
- common_element('input', array('type' => 'hidden',
- 'name' => 'notice',
- 'id' => 'notice-n'. $notice->id,
- 'class' => 'notice',
- 'value' => $notice->id));
-
- common_element('input', array('type' => 'submit',
- 'id' => 'favor-submit-' . $notice->id,
- 'name' => 'favor-submit-' . $notice->id,
- 'class' => 'favor',
- 'value' => 'Add to favorites',
- 'title' => 'Add this message to favorites'));
- common_element_end('form');
-}
-
-function common_nudge_form($profile) {
- common_element_start('form', array('id' => 'nudge', 'method' => 'post',
- 'action' => common_local_url('nudge', array('nickname' => $profile->nickname))));
- common_hidden('token', common_session_token());
- common_element('input', array('type' => 'submit',
- 'class' => 'submit',
- 'value' => _('Send a nudge')));
- common_element_end('form');
-}
-function common_nudge_response() {
- common_element('p', array('id' => 'nudge_response'), _('Nudge sent!'));
-}
-
-function common_subscribe_form($profile) {
- common_element_start('form', array('id' => 'subscribe-' . $profile->id,
- 'method' => 'post',
- 'class' => 'subscribe',
- 'action' => common_local_url('subscribe')));
- common_hidden('token', common_session_token());
- common_element('input', array('id' => 'subscribeto-' . $profile->id,
- 'name' => 'subscribeto',
- 'type' => 'hidden',
- 'value' => $profile->id));
- common_element('input', array('type' => 'submit',
- 'class' => 'submit',
- 'value' => _('Subscribe')));
- common_element_end('form');
-}
-
-function common_unsubscribe_form($profile) {
- common_element_start('form', array('id' => 'unsubscribe-' . $profile->id,
- 'method' => 'post',
- 'class' => 'unsubscribe',
- 'action' => common_local_url('unsubscribe')));
- common_hidden('token', common_session_token());
- common_element('input', array('id' => 'unsubscribeto-' . $profile->id,
- 'name' => 'unsubscribeto',
- 'type' => 'hidden',
- 'value' => $profile->id));
- common_element('input', array('type' => 'submit',
- 'class' => 'submit',
- 'value' => _('Unsubscribe')));
- common_element_end('form');
-}
-
-// XXX: Refactor this code
-function common_profile_new_message_nudge ($cur, $profile) {
- $user = User::staticGet('id', $profile->id);
-
- if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) {
- common_element_start('li', array('id' => 'profile_send_a_new_message'));
- common_element('a', array('href' => common_local_url('newmessage', array('to' => $user->id))),
- _('Send a message'));
- common_element_end('li');
-
- if ($user->email && $user->emailnotifynudge) {
- common_element_start('li', array('id' => 'profile_nudge'));
- common_nudge_form($user);
- common_element_end('li');
- }
- }
+function common_config($main, $sub)
+{
+ global $config;
+ return isset($config[$main][$sub]) ? $config[$main][$sub] : false;
}
-function common_cache_key($extra) {
- return 'laconica:' . common_keyize(common_config('site', 'name')) . ':' . $extra;
+function common_copy_args($from)
+{
+ $to = array();
+ $strip = get_magic_quotes_gpc();
+ foreach ($from as $k => $v) {
+ $to[$k] = ($strip) ? stripslashes($v) : $v;
+ }
+ return $to;
}
-function common_keyize($str) {
- $str = strtolower($str);
- $str = preg_replace('/\s/', '_', $str);
- return $str;
+// Neutralise the evil effects of magic_quotes_gpc in the current request.
+// This is used before handing a request off to OAuthRequest::from_request.
+function common_remove_magic_from_request()
+{
+ if(get_magic_quotes_gpc()) {
+ $_POST=array_map('stripslashes',$_POST);
+ $_GET=array_map('stripslashes',$_GET);
+ }
}
-function common_message_form($content, $user, $to) {
+function common_user_uri(&$user)
+{
+ return common_local_url('userbyid', array('id' => $user->id));
+}
- common_element_start('form', array('id' => 'message_form',
- 'method' => 'post',
- 'action' => common_local_url('newmessage')));
+function common_notice_uri(&$notice)
+{
+ return common_local_url('shownotice',
+ array('notice' => $notice->id));
+}
- $mutual_users = $user->mutuallySubscribedUsers();
+// 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
- $mutual = array();
+function common_confirmation_code($bits)
+{
+ // 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
+ static $codechars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
+ $chars = ceil($bits/5);
+ $code = '';
+ for ($i = 0; $i < $chars; $i++) {
+ // XXX: convert to string and back
+ $num = hexdec(common_good_rand(1));
+ // XXX: randomness is too precious to throw away almost
+ // 40% of the bits we get!
+ $code .= $codechars[$num%32];
+ }
+ return $code;
+}
- while ($mutual_users->fetch()) {
- if ($mutual_users->id != $user->id) {
- $mutual[$mutual_users->id] = $mutual_users->nickname;
- }
- }
+// convert markup to HTML
- $mutual_users->free();
- unset($mutual_users);
+function common_markup_to_html($c)
+{
+ $c = preg_replace('/%%action.(\w+)%%/e', "common_local_url('\\1')", $c);
+ $c = preg_replace('/%%doc.(\w+)%%/e', "common_local_url('doc', array('title'=>'\\1'))", $c);
+ $c = preg_replace('/%%(\w+).(\w+)%%/e', 'common_config(\'\\1\', \'\\2\')', $c);
+ return Markdown($c);
+}
+
+function common_profile_avatar_url($profile, $size=AVATAR_PROFILE_SIZE)
+{
+ $avatar = $profile->getAvatar($size);
+ if ($avatar) {
+ return common_avatar_display_url($avatar);
+ } else {
+ return common_default_avatar($size);
+ }
+}
- common_dropdown('to', _('To'), $mutual, NULL, FALSE, $to->id);
+function common_profile_uri($profile)
+{
+ if (!$profile) {
+ return null;
+ }
+ $user = User::staticGet($profile->id);
+ if ($user) {
+ return $user->uri;
+ }
- common_element_start('p');
+ $remote = Remote_profile::staticGet($profile->id);
+ if ($remote) {
+ return $remote->uri;
+ }
+ // XXX: this is a very bad profile!
+ return null;
+}
- common_element('textarea', array('id' => 'message_content',
- 'cols' => 60,
- 'rows' => 3,
- 'name' => 'content'),
- ($content) ? $content : '');
+function common_canonical_sms($sms)
+{
+ // strip non-digits
+ preg_replace('/\D/', '', $sms);
+ return $sms;
+}
- common_element('input', array('id' => 'message_send',
- 'name' => 'message_send',
- 'type' => 'submit',
- 'value' => _('Send')));
+function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
+{
+ switch ($errno) {
+ case E_USER_ERROR:
+ common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
+ exit(1);
+ break;
- common_hidden('token', common_session_token());
+ case E_USER_WARNING:
+ common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
+ break;
- common_element_end('p');
- common_element_end('form');
-}
+ case E_USER_NOTICE:
+ common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
+ break;
+ }
-function common_memcache() {
- static $cache = NULL;
- if (!common_config('memcached', 'enabled')) {
- return NULL;
- } else {
- if (!$cache) {
- $cache = new Memcache();
- $servers = common_config('memcached', 'server');
- if (is_array($servers)) {
- foreach($servers as $server) {
- $cache->addServer($server);
- }
- } else {
- $cache->addServer($servers);
- }
- }
- return $cache;
- }
+ // FIXME: show error page if we're on the Web
+ /* Don't execute PHP internal error handler */
+ return true;
}
-function common_compatible_license($from, $to) {
- # XXX: better compatibility check needed here!
- return ($from == $to);
+function common_session_token()
+{
+ common_ensure_session();
+ if (!array_key_exists('token', $_SESSION)) {
+ $_SESSION['token'] = common_good_rand(64);
+ }
+ return $_SESSION['token'];
}
-/* These are almost identical, so we use a helper function */
-
-function common_block_form($profile, $args=NULL) {
- common_blocking_form('block', _('Block'), $profile, $args);
+function common_cache_key($extra)
+{
+ return 'laconica:' . common_keyize(common_config('site', 'name')) . ':' . $extra;
}
-function common_unblock_form($profile, $args=NULL) {
- common_blocking_form('unblock', _('Unblock'), $profile, $args);
+function common_keyize($str)
+{
+ $str = strtolower($str);
+ $str = preg_replace('/\s/', '_', $str);
+ return $str;
}
-function common_blocking_form($type, $label, $profile, $args=NULL) {
- common_element_start('form', array('id' => $type . '-' . $profile->id,
- 'method' => 'post',
- 'class' => $type,
- 'action' => common_local_url($type)));
- common_hidden('token', common_session_token());
- common_element('input', array('id' => $type . 'to-' . $profile->id,
- 'name' => $type . 'to',
- 'type' => 'hidden',
- 'value' => $profile->id));
- common_element('input', array('type' => 'submit',
- 'class' => 'submit',
- 'name' => $type,
- 'value' => $label));
- if ($args) {
- foreach ($args as $k => $v) {
- common_hidden('returnto-' . $k, $v);
+function common_memcache()
+{
+ static $cache = null;
+ if (!common_config('memcached', 'enabled')) {
+ return null;
+ } else {
+ if (!$cache) {
+ $cache = new Memcache();
+ $servers = common_config('memcached', 'server');
+ if (is_array($servers)) {
+ foreach($servers as $server) {
+ $cache->addServer($server);
+ }
+ } else {
+ $cache->addServer($servers);
+ }
}
+ return $cache;
}
- common_element_end('form');
- return;
}
+function common_compatible_license($from, $to)
+{
+ // XXX: better compatibility check needed here!
+ return ($from == $to);
+}
diff --git a/lib/widget.php b/lib/widget.php
new file mode 100644
index 000000000..c70505c44
--- /dev/null
+++ b/lib/widget.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for UI widgets
+ *
+ * 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>
+ * @author Sarven Capadisli <csarven@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);
+}
+
+/**
+ * Base class for UI widgets
+ *
+ * A widget is a cluster of HTML elements that provide some functionality
+ * that's used on different parts of the site. Examples would be profile
+ * lists, notice lists, navigation menus (tabsets) and common forms.
+ *
+ * @category Widget
+ * @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/
+ *
+ * @see HTMLOutputter
+ */
+
+class Widget
+{
+ /**
+ * HTMLOutputter to use for output
+ */
+
+ var $out = null;
+
+ /**
+ * Prepare the widget for use
+ *
+ * @param HTMLOutputter $out output helper, defaults to null
+ */
+
+ function __construct($out=null)
+ {
+ $this->out = $out;
+ }
+
+ /**
+ * Show the widget
+ *
+ * Emit the HTML for the widget, using the configured outputter.
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ }
+}
diff --git a/lib/xmloutputter.php b/lib/xmloutputter.php
new file mode 100644
index 000000000..64935da40
--- /dev/null
+++ b/lib/xmloutputter.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Low-level generator for XML
+ *
+ * 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 Output
+ * @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);
+}
+
+/**
+ * Low-level generator for XML
+ *
+ * This is a thin wrapper around PHP's XMLWriter. The main
+ * advantage is the element() method, which simplifies outputting
+ * an element.
+ *
+ * @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/
+ * @see Action
+ * @see HTMLOutputter
+ */
+
+class XMLOutputter
+{
+ /**
+ * Wrapped XMLWriter object, which does most of the heavy lifting
+ * for output.
+ */
+
+ var $xw = null;
+
+ /**
+ * Constructor
+ *
+ * Initializes the wrapped XMLWriter.
+ *
+ * @param string $output URL for outputting, defaults to stdout
+ * @param boolean $indent Whether to indent output, default true
+ */
+
+ function __construct($output='php://output', $indent=true)
+ {
+ $this->xw = new XMLWriter();
+ $this->xw->openURI($output);
+ $this->xw->setIndent($indent);
+ }
+
+ /**
+ * Start a new XML document
+ *
+ * @param string $doc document element
+ * @param string $public public identifier
+ * @param string $system system identifier
+ *
+ * @return void
+ */
+
+ function startXML($doc=null, $public=null, $system=null)
+ {
+ $this->xw->startDocument('1.0', 'UTF-8');
+ if ($doc) {
+ $this->xw->writeDTD($doc, $public, $system);
+ }
+ }
+
+ /**
+ * finish an XML document
+ *
+ * It's probably a bad idea to continue to use this object
+ * after calling endXML().
+ *
+ * @return void
+ */
+
+ function endXML()
+ {
+ $this->xw->endDocument();
+ $this->xw->flush();
+ }
+
+ /**
+ * output an XML element
+ *
+ * Utility for outputting an XML element. A convenient wrapper
+ * for a bunch of longer XMLWriter calls. This is best for
+ * when an element doesn't have any sub-elements; if that's the
+ * case, use elementStart() and elementEnd() instead.
+ *
+ * The $content element will be escaped for XML. If you need
+ * raw output, use elementStart() and elementEnd() with a call
+ * to raw() in the middle.
+ *
+ * If $attrs is a string instead of an array, it will be treated
+ * as the class attribute of the element.
+ *
+ * @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)
+ {
+ $this->elementStart($tag, $attrs);
+ if (!is_null($content)) {
+ $this->xw->text($content);
+ }
+ $this->elementEnd($tag);
+ }
+
+ /**
+ * output a start tag for an element
+ *
+ * Mostly used for when an element has content that's
+ * not a simple string.
+ *
+ * If $attrs is a string instead of an array, it will be treated
+ * as the class attribute of the element.
+ *
+ * @param string $tag Element type or tagname
+ * @param array $attrs Array of element attributes
+ *
+ * @return void
+ */
+
+ function elementStart($tag, $attrs=null)
+ {
+ $this->xw->startElement($tag);
+ if (is_array($attrs)) {
+ foreach ($attrs as $name => $value) {
+ $this->xw->writeAttribute($name, $value);
+ }
+ } else if (is_string($attrs)) {
+ $this->xw->writeAttribute('class', $attrs);
+ }
+ }
+
+ /**
+ * output an end tag for an element
+ *
+ * Used in conjunction with elementStart(). $tag param
+ * should match the elementStart() param.
+ *
+ * For HTML 4 compatibility, this method will force
+ * a full end element (</tag>) even if the element is
+ * empty, except for a handful of exception tagnames.
+ * This is a hack.
+ *
+ * @param string $tag Element type or tagname.
+ *
+ * @return void
+ */
+
+ function elementEnd($tag)
+ {
+ static $empty_tag = array('base', 'meta', 'link', 'hr',
+ 'br', 'param', 'img', 'area',
+ 'input', 'col');
+ // XXX: check namespace
+ if (in_array($tag, $empty_tag)) {
+ $this->xw->endElement();
+ } else {
+ $this->xw->fullEndElement();
+ }
+ }
+
+ /**
+ * output plain text
+ *
+ * Text will be escaped. If you need it not to be,
+ * use raw() instead.
+ *
+ * @param string $txt Text to output.
+ *
+ * @return void
+ */
+
+ function text($txt)
+ {
+ $this->xw->text($txt);
+ }
+
+ /**
+ * output raw xml
+ *
+ * This will spit out its argument verbatim -- no escaping is
+ * done.
+ *
+ * @param string $xml XML to output.
+ *
+ * @return void
+ */
+
+ function raw($xml)
+ {
+ $this->xw->writeRaw($xml);
+ }
+
+ /**
+ * output a comment
+ *
+ * @param string $txt text of the comment
+ *
+ * @return void
+ */
+
+ function comment($txt)
+ {
+ $this->xw->writeComment($txt);
+ }
+}
diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php
index cfc9642e4..91015fd45 100644
--- a/lib/xmppqueuehandler.php
+++ b/lib/xmppqueuehandler.php
@@ -28,64 +28,71 @@ require_once(INSTALLDIR.'/lib/queuehandler.php');
* superclass.
*/
-class XmppQueueHandler extends QueueHandler {
-
- function start() {
- # Low priority; we don't want to receive messages
- $this->log(LOG_INFO, "INITIALIZE");
- $this->conn = jabber_connect($this->_id);
- if ($this->conn) {
- $this->conn->addEventHandler('message', 'forward_message', $this);
- $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
- $this->conn->setReconnectTimeout(600);
- jabber_send_presence("Send me a message to post a notice", 'available', NULL, 'available', -1);
- }
- return !is_null($this->conn);
- }
-
- function handle_reconnect(&$pl) {
- $this->conn->processUntil('session_start');
- $this->conn->presence(NULL, 'available', NULL, 'available', -1);
- }
+class XmppQueueHandler extends QueueHandler
+{
+
+ function start()
+ {
+ # Low priority; we don't want to receive messages
+ $this->log(LOG_INFO, "INITIALIZE");
+ $this->conn = jabber_connect($this->_id);
+ if ($this->conn) {
+ $this->conn->addEventHandler('message', 'forward_message', $this);
+ $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
+ $this->conn->setReconnectTimeout(600);
+ jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1);
+ }
+ return !is_null($this->conn);
+ }
+
+ function handle_reconnect(&$pl)
+ {
+ $this->conn->processUntil('session_start');
+ $this->conn->presence(null, 'available', null, 'available', -1);
+ }
- function idle($timeout=0) {
- # Process the queue for as long as needed
- try {
- if ($this->conn) {
- $this->conn->processTime($timeout);
- }
- } catch (XMPPHP_Exception $e) {
- $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
- die($e->getMessage());
- }
- }
-
- function forward_message(&$pl) {
- if ($pl['type'] != 'chat') {
- $this->log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']);
- return;
- }
- $listener = $this->listener();
- if (strtolower($listener) == strtolower($pl['from'])) {
- $this->log(LOG_WARNING, 'Ignoring loop message.');
- return;
- }
- $this->log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener);
- $this->conn->message($this->listener(), $pl['body'], 'chat', NULL, $this->ofrom($pl['from']));
- }
+ function idle($timeout=0)
+ {
+ # Process the queue for as long as needed
+ try {
+ if ($this->conn) {
+ $this->conn->processTime($timeout);
+ }
+ } catch (XMPPHP_Exception $e) {
+ $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
+ die($e->getMessage());
+ }
+ }
+
+ function forward_message(&$pl)
+ {
+ if ($pl['type'] != 'chat') {
+ $this->log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']);
+ return;
+ }
+ $listener = $this->listener();
+ if (strtolower($listener) == strtolower($pl['from'])) {
+ $this->log(LOG_WARNING, 'Ignoring loop message.');
+ return;
+ }
+ $this->log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener);
+ $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from']));
+ }
- function ofrom($from) {
- $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
- $address .= "<address type='ofrom' jid='$from' />\n";
- $address .= "</addresses>\n";
- return $address;
- }
+ function ofrom($from)
+ {
+ $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
+ $address .= "<address type='ofrom' jid='$from' />\n";
+ $address .= "</addresses>\n";
+ return $address;
+ }
- function listener() {
- if (common_config('xmpp', 'listener')) {
- return common_config('xmpp', 'listener');
- } else {
- return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
- }
- }
+ function listener()
+ {
+ if (common_config('xmpp', 'listener')) {
+ return common_config('xmpp', 'listener');
+ } else {
+ return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
+ }
+ }
}