summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/adminpanelaction.php8
-rw-r--r--lib/apiaction.php2
-rw-r--r--lib/cache.php2
-rw-r--r--lib/channel.php62
-rw-r--r--lib/command.php4
-rw-r--r--lib/common.php18
-rw-r--r--lib/connectsettingsaction.php4
-rw-r--r--lib/default.php8
-rw-r--r--lib/designform.php293
-rw-r--r--lib/designsettings.php173
-rw-r--r--lib/htmloutputter.php2
-rw-r--r--lib/imchannel.php104
-rw-r--r--lib/immanager.php56
-rw-r--r--lib/implugin.php626
-rw-r--r--lib/imqueuehandler.php (renamed from lib/jabberqueuehandler.php)33
-rw-r--r--lib/imreceiverqueuehandler.php (renamed from lib/publicqueuehandler.php)29
-rw-r--r--lib/imsenderqueuehandler.php43
-rw-r--r--lib/jabber.php640
-rw-r--r--lib/plugindisableform.php93
-rw-r--r--lib/pluginenableform.php114
-rw-r--r--lib/pluginlist.php213
-rw-r--r--lib/queued_xmpp.php127
-rw-r--r--lib/queuehandler.php14
-rw-r--r--lib/queuemanager.php41
-rw-r--r--lib/queuemonitor.php2
-rw-r--r--lib/router.php16
-rw-r--r--lib/spawningdaemon.php2
-rw-r--r--lib/statusnet.php21
-rw-r--r--lib/stompqueuemanager.php2
-rw-r--r--lib/urlshortenerplugin.php155
-rw-r--r--lib/util.php121
-rw-r--r--lib/xmppmanager.php486
-rw-r--r--lib/xmppoutqueuehandler.php55
-rw-r--r--lib/xrd.php172
34 files changed, 2052 insertions, 1689 deletions
diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php
index fae9f4fa5..8dd16e9d0 100644
--- a/lib/adminpanelaction.php
+++ b/lib/adminpanelaction.php
@@ -404,6 +404,14 @@ class AdminPanelNav extends Widget
$menu_title, $action_name == 'licenseadminpanel', 'nav_license_admin_panel');
}
+ if (AdminPanelAction::canAdmin('plugins')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Plugins configuration');
+ // TRANS: Menu item for site administration
+ $this->out->menuItem(common_local_url('pluginsadminpanel'), _('Plugins'),
+ $menu_title, $action_name == 'pluginsadminpanel', 'nav_design_admin_panel');
+ }
+
Event::handle('EndAdminPanelNav', array($this));
}
$this->action->elementEnd('ul');
diff --git a/lib/apiaction.php b/lib/apiaction.php
index 0ebf88282..d8249055a 100644
--- a/lib/apiaction.php
+++ b/lib/apiaction.php
@@ -98,6 +98,8 @@ if (!defined('STATUSNET')) {
exit(1);
}
+class ApiValidationException extends Exception { }
+
/**
* Contains most of the Twitter-compatible API output functions.
*
diff --git a/lib/cache.php b/lib/cache.php
index ea0ff769d..3d78c79ad 100644
--- a/lib/cache.php
+++ b/lib/cache.php
@@ -80,7 +80,7 @@ class Cache
$base_key = common_config('cache', 'base');
if (empty($base_key)) {
- $base_key = common_keyize(common_config('site', 'name'));
+ $base_key = self::keyize(common_config('site', 'name'));
}
return 'statusnet:' . $base_key . ':' . $extra;
diff --git a/lib/channel.php b/lib/channel.php
index fbc2e8697..ae9b2d214 100644
--- a/lib/channel.php
+++ b/lib/channel.php
@@ -69,62 +69,6 @@ class CLIChannel extends Channel
}
}
-class XMPPChannel extends Channel
-{
- var $conn = null;
-
- function source()
- {
- return 'xmpp';
- }
-
- function __construct($conn)
- {
- $this->conn = $conn;
- }
-
- function on($user)
- {
- return $this->set_notify($user, 1);
- }
-
- function off($user)
- {
- return $this->set_notify($user, 0);
- }
-
- function output($user, $text)
- {
- $text = '['.common_config('site', 'name') . '] ' . $text;
- jabber_send_message($user->jabber, $text);
- }
-
- function error($user, $text)
- {
- $text = '['.common_config('site', 'name') . '] ' . $text;
- jabber_send_message($user->jabber, $text);
- }
-
- function set_notify(&$user, $notify)
- {
- $orig = clone($user);
- $user->jabbernotify = $notify;
- $result = $user->update($orig);
- if (!$result) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR,
- 'Could not set notify flag to ' . $notify .
- ' for user ' . common_log_objstring($user) .
- ': ' . $last_error->message);
- return false;
- } else {
- common_log(LOG_INFO,
- 'User ' . $user->nickname . ' set notify flag to ' . $notify);
- return true;
- }
- }
-}
-
class WebChannel extends Channel
{
var $out = null;
@@ -216,12 +160,12 @@ class MailChannel extends Channel
function on($user)
{
- return $this->set_notify($user, 1);
+ return $this->setNotify($user, 1);
}
function off($user)
{
- return $this->set_notify($user, 0);
+ return $this->setNotify($user, 0);
}
function output($user, $text)
@@ -246,7 +190,7 @@ class MailChannel extends Channel
return mail_send(array($this->addr), $headers, $text);
}
- function set_notify($user, $value)
+ function setNotify($user, $value)
{
$orig = clone($user);
$user->smsnotify = $value;
diff --git a/lib/command.php b/lib/command.php
index 658262a09..efe917fb1 100644
--- a/lib/command.php
+++ b/lib/command.php
@@ -714,7 +714,7 @@ class OffCommand extends Command
}
function handle($channel)
{
- if ($other) {
+ if ($this->other) {
// TRANS: Error text shown when issuing the command "off" with a setting which has not yet been implemented.
$channel->error($this->user, _("Command not yet implemented."));
} else {
@@ -740,7 +740,7 @@ class OnCommand extends Command
function handle($channel)
{
- if ($other) {
+ if ($this->other) {
// TRANS: Error text shown when issuing the command "on" with a setting which has not yet been implemented.
$channel->error($this->user, _("Command not yet implemented."));
} else {
diff --git a/lib/common.php b/lib/common.php
index 236f2d68a..2a11ab722 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -71,6 +71,7 @@ if (!function_exists('dl')) {
# global configuration object
require_once('PEAR.php');
+require_once('PEAR/Exception.php');
require_once('DB/DataObject.php');
require_once('DB/DataObject/Cast.php'); # for dates
@@ -127,6 +128,23 @@ require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
+
+//set PEAR error handling to use regular PHP exceptions
+function PEAR_ErrorToPEAR_Exception($err)
+{
+ //DB_DataObject throws error when an empty set would be returned
+ //That behavior is weird, and not how the rest of StatusNet works.
+ //So just ignore those errors.
+ if ($err->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
+ return;
+ }
+ if ($err->getCode()) {
+ throw new PEAR_Exception($err->getMessage(), $err->getCode());
+ }
+ throw new PEAR_Exception($err->getMessage());
+}
+PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception');
+
try {
StatusNet::init(@$server, @$path, @$conffile);
} catch (NoConfigException $e) {
diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php
index bb2e86176..c2e759f0f 100644
--- a/lib/connectsettingsaction.php
+++ b/lib/connectsettingsaction.php
@@ -100,7 +100,9 @@ class ConnectSettingsNav extends Widget
# action => array('prompt', 'title')
$menu = array();
- if (common_config('xmpp', 'enabled')) {
+ $transports = array();
+ Event::handle('GetImTransports', array(&$transports));
+ if ($transports) {
$menu['imsettings'] =
// TRANS: Menu item for Instant Messaging settings.
array(_m('MENU','IM'),
diff --git a/lib/default.php b/lib/default.php
index 45e35e83d..79b54fc3b 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -297,11 +297,13 @@ $default =
'OStatus' => null,
'WikiHashtags' => null,
'RSSCloud' => null,
+ 'ClientSideShorten' => null,
'OpenID' => null),
'locale_path' => false, // Set to a path to use *instead of* each plugin's own locale subdirectories
),
+ 'pluginlist' => array(),
'admin' =>
- array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'license')),
+ array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'license', 'plugins')),
'singleuser' =>
array('enabled' => false,
'nickname' => null),
@@ -315,6 +317,10 @@ $default =
array('subscribers' => true,
'members' => true,
'peopletag' => true),
+ 'url' =>
+ array('shortener' => 'ur1.ca',
+ 'maxlength' => 25,
+ 'maxnoticelength' => -1),
'http' => // HTTP client settings when contacting other sites
array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt')
'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.)
diff --git a/lib/designform.php b/lib/designform.php
new file mode 100644
index 000000000..b22d77f31
--- /dev/null
+++ b/lib/designform.php
@@ -0,0 +1,293 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for choosing a design
+ *
+ * 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 StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Sarven Capadisli <csarven@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Form for choosing a design
+ *
+ * Used for choosing a site design, user design, or group design.
+ *
+ * @category Form
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Sarven Capadisli <csarven@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ *
+ */
+
+class DesignForm extends Form
+{
+ /**
+ * Return-to args
+ */
+
+ var $design = null;
+ var $actionurl = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param Design $design initial design
+ * @param Design $actionurl url of action (for form posting)
+ */
+
+ function __construct($out, $design, $actionurl)
+ {
+ parent::__construct($out);
+
+ $this->design = $design;
+ $this->actionurl = $actionurl;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'design';
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_design';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return $this->actionurl;
+ }
+
+ /**
+ * Legend of the Form
+ *
+ * @return void
+ */
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Change design'));
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->elementStart('ul', 'form_data');
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'design_background-image_file'),
+ _('Upload file'));
+ $this->out->element('input', array('name' => 'design_background-image_file',
+ 'type' => 'file',
+ 'id' => 'design_background-image_file'));
+ $this->out->element('p', 'form_guide', _('You can upload your personal ' .
+ 'background image. The maximum file size is 2Mb.'));
+ $this->out->element('input', array('name' => 'MAX_FILE_SIZE',
+ 'type' => 'hidden',
+ 'id' => 'MAX_FILE_SIZE',
+ 'value' => ImageFile::maxFileSizeInt()));
+ $this->out->elementEnd('li');
+
+ if (!empty($design->backgroundimage)) {
+
+ $this->out->elementStart('li', array('id' =>
+ 'design_background-image_onoff'));
+
+ $this->out->element('img', array('src' =>
+ Design::url($design->backgroundimage)));
+
+ $attrs = array('name' => 'design_background-image_onoff',
+ 'type' => 'radio',
+ 'id' => 'design_background-image_on',
+ 'class' => 'radio',
+ 'value' => 'on');
+
+ if ($design->disposition & BACKGROUND_ON) {
+ $attrs['checked'] = 'checked';
+ }
+
+ $this->out->element('input', $attrs);
+
+ $this->out->element('label', array('for' => 'design_background-image_on',
+ 'class' => 'radio'),
+ _('On'));
+
+ $attrs = array('name' => 'design_background-image_onoff',
+ 'type' => 'radio',
+ 'id' => 'design_background-image_off',
+ 'class' => 'radio',
+ 'value' => 'off');
+
+ if ($design->disposition & BACKGROUND_OFF) {
+ $attrs['checked'] = 'checked';
+ }
+
+ $this->out->element('input', $attrs);
+
+ $this->out->element('label', array('for' => 'design_background-image_off',
+ 'class' => 'radio'),
+ _('Off'));
+ $this->out->element('p', 'form_guide', _('Turn background image on or off.'));
+ $this->out->elementEnd('li');
+
+ $this->out->elementStart('li');
+ $this->out->checkbox('design_background-image_repeat',
+ _('Tile background image'),
+ ($design->disposition & BACKGROUND_TILE) ? true : false);
+ $this->out->elementEnd('li');
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('fieldset');
+
+ $this->out->elementStart('fieldset', array('id' => 'settings_design_color'));
+ $this->out->element('legend', null, _('Change colours'));
+ $this->out->elementStart('ul', 'form_data');
+
+ try {
+
+ $bgcolor = new WebColor($design->backgroundcolor);
+
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'swatch-1'), _('Background'));
+ $this->out->element('input', array('name' => 'design_background',
+ 'type' => 'text',
+ 'id' => 'swatch-1',
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => ''));
+ $this->out->elementEnd('li');
+
+ $ccolor = new WebColor($design->contentcolor);
+
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'swatch-2'), _('Content'));
+ $this->out->element('input', array('name' => 'design_content',
+ 'type' => 'text',
+ 'id' => 'swatch-2',
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => ''));
+ $this->out->elementEnd('li');
+
+ $sbcolor = new WebColor($design->sidebarcolor);
+
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'swatch-3'), _('Sidebar'));
+ $this->out->element('input', array('name' => 'design_sidebar',
+ 'type' => 'text',
+ 'id' => 'swatch-3',
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => ''));
+ $this->out->elementEnd('li');
+
+ $tcolor = new WebColor($design->textcolor);
+
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'swatch-4'), _('Text'));
+ $this->out->element('input', array('name' => 'design_text',
+ 'type' => 'text',
+ 'id' => 'swatch-4',
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => ''));
+ $this->out->elementEnd('li');
+
+ $lcolor = new WebColor($design->linkcolor);
+
+ $this->out->elementStart('li');
+ $this->out->element('label', array('for' => 'swatch-5'), _('Links'));
+ $this->out->element('input', array('name' => 'design_links',
+ 'type' => 'text',
+ 'id' => 'swatch-5',
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => ''));
+ $this->out->elementEnd('li');
+
+ } catch (WebColorException $e) {
+ common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id);
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('fieldset');
+
+ $this->out->elementStart('fieldset');
+
+ $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default',
+ 'defaults', _('Restore default designs'));
+
+ $this->out->element('input', array('id' => 'settings_design_reset',
+ 'type' => 'reset',
+ 'value' => 'Reset',
+ 'class' => 'submit form_action-primary',
+ 'title' => _('Reset back to default')));
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('save', _('Save'), 'submit form_action-secondary',
+ 'save', _('Save design'));
+ }
+}
diff --git a/lib/designsettings.php b/lib/designsettings.php
index 4955e9219..98ef8256c 100644
--- a/lib/designsettings.php
+++ b/lib/designsettings.php
@@ -87,177 +87,8 @@ class DesignSettingsAction extends AccountSettingsAction
function showDesignForm($design)
{
-
- $this->elementStart('form', array('method' => 'post',
- 'enctype' => 'multipart/form-data',
- 'id' => 'form_settings_design',
- 'class' => 'form_settings',
- 'action' => $this->submitaction));
- $this->elementStart('fieldset');
- $this->hidden('token', common_session_token());
-
- $this->elementStart('fieldset', array('id' =>
- 'settings_design_background-image'));
- $this->element('legend', null, _('Change background image'));
- $this->elementStart('ul', 'form_data');
- $this->elementStart('li');
- $this->element('label', array('for' => 'design_background-image_file'),
- _('Upload file'));
- $this->element('input', array('name' => 'design_background-image_file',
- 'type' => 'file',
- 'id' => 'design_background-image_file'));
- $this->element('p', 'form_guide', _('You can upload your personal ' .
- 'background image. The maximum file size is 2MB.'));
- $this->element('input', array('name' => 'MAX_FILE_SIZE',
- 'type' => 'hidden',
- 'id' => 'MAX_FILE_SIZE',
- 'value' => ImageFile::maxFileSizeInt()));
- $this->elementEnd('li');
-
- if (!empty($design->backgroundimage)) {
-
- $this->elementStart('li', array('id' =>
- 'design_background-image_onoff'));
-
- $this->element('img', array('src' =>
- Design::url($design->backgroundimage)));
-
- $attrs = array('name' => 'design_background-image_onoff',
- 'type' => 'radio',
- 'id' => 'design_background-image_on',
- 'class' => 'radio',
- 'value' => 'on');
-
- if ($design->disposition & BACKGROUND_ON) {
- $attrs['checked'] = 'checked';
- }
-
- $this->element('input', $attrs);
-
- $this->element('label', array('for' => 'design_background-image_on',
- 'class' => 'radio'),
- _('On'));
-
- $attrs = array('name' => 'design_background-image_onoff',
- 'type' => 'radio',
- 'id' => 'design_background-image_off',
- 'class' => 'radio',
- 'value' => 'off');
-
- if ($design->disposition & BACKGROUND_OFF) {
- $attrs['checked'] = 'checked';
- }
-
- $this->element('input', $attrs);
-
- $this->element('label', array('for' => 'design_background-image_off',
- 'class' => 'radio'),
- _('Off'));
- $this->element('p', 'form_guide', _('Turn background image on or off.'));
- $this->elementEnd('li');
-
- $this->elementStart('li');
- $this->checkbox('design_background-image_repeat',
- _('Tile background image'),
- ($design->disposition & BACKGROUND_TILE) ? true : false);
- $this->elementEnd('li');
- }
-
- $this->elementEnd('ul');
- $this->elementEnd('fieldset');
-
- $this->elementStart('fieldset', array('id' => 'settings_design_color'));
- $this->element('legend', null, _('Change colours'));
- $this->elementStart('ul', 'form_data');
-
- try {
-
- $bgcolor = new WebColor($design->backgroundcolor);
-
- $this->elementStart('li');
- $this->element('label', array('for' => 'swatch-1'), _('Background'));
- $this->element('input', array('name' => 'design_background',
- 'type' => 'text',
- 'id' => 'swatch-1',
- 'class' => 'swatch',
- 'maxlength' => '7',
- 'size' => '7',
- 'value' => ''));
- $this->elementEnd('li');
-
- $ccolor = new WebColor($design->contentcolor);
-
- $this->elementStart('li');
- $this->element('label', array('for' => 'swatch-2'), _('Content'));
- $this->element('input', array('name' => 'design_content',
- 'type' => 'text',
- 'id' => 'swatch-2',
- 'class' => 'swatch',
- 'maxlength' => '7',
- 'size' => '7',
- 'value' => ''));
- $this->elementEnd('li');
-
- $sbcolor = new WebColor($design->sidebarcolor);
-
- $this->elementStart('li');
- $this->element('label', array('for' => 'swatch-3'), _('Sidebar'));
- $this->element('input', array('name' => 'design_sidebar',
- 'type' => 'text',
- 'id' => 'swatch-3',
- 'class' => 'swatch',
- 'maxlength' => '7',
- 'size' => '7',
- 'value' => ''));
- $this->elementEnd('li');
-
- $tcolor = new WebColor($design->textcolor);
-
- $this->elementStart('li');
- $this->element('label', array('for' => 'swatch-4'), _('Text'));
- $this->element('input', array('name' => 'design_text',
- 'type' => 'text',
- 'id' => 'swatch-4',
- 'class' => 'swatch',
- 'maxlength' => '7',
- 'size' => '7',
- 'value' => ''));
- $this->elementEnd('li');
-
- $lcolor = new WebColor($design->linkcolor);
-
- $this->elementStart('li');
- $this->element('label', array('for' => 'swatch-5'), _('Links'));
- $this->element('input', array('name' => 'design_links',
- 'type' => 'text',
- 'id' => 'swatch-5',
- 'class' => 'swatch',
- 'maxlength' => '7',
- 'size' => '7',
- 'value' => ''));
- $this->elementEnd('li');
-
- } catch (WebColorException $e) {
- common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id);
- }
-
- $this->elementEnd('ul');
- $this->elementEnd('fieldset');
-
- $this->submit('defaults', _('Use defaults'), 'submit form_action-default',
- 'defaults', _('Restore default designs'));
-
- $this->element('input', array('id' => 'settings_design_reset',
- 'type' => 'reset',
- 'value' => 'Reset',
- 'class' => 'submit form_action-primary',
- 'title' => _('Reset back to default')));
-
- $this->submit('save', _('Save'), 'submit form_action-secondary',
- 'save', _('Save design'));
-
- $this->elementEnd('fieldset');
- $this->elementEnd('form');
+ $form = new DesignForm($this, $design, $this->selfUrl());
+ $form->show();
}
/**
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 44b029604..9780dc424 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -177,7 +177,7 @@ class HTMLOutputter extends XMLOutputter
$attrs = array('name' => $id,
'type' => 'text',
'id' => $id);
- if ($value) {
+ if (!is_null($value)) { // value can be 0 or ''
$attrs['value'] = $value;
}
$this->element('input', $attrs);
diff --git a/lib/imchannel.php b/lib/imchannel.php
new file mode 100644
index 000000000..61355a429
--- /dev/null
+++ b/lib/imchannel.php
@@ -0,0 +1,104 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+class IMChannel extends Channel
+{
+
+ var $imPlugin;
+
+ function source()
+ {
+ return $imPlugin->transport;
+ }
+
+ function __construct($imPlugin)
+ {
+ $this->imPlugin = $imPlugin;
+ }
+
+ function on($user)
+ {
+ return $this->setNotify($user, 1);
+ }
+
+ function off($user)
+ {
+ return $this->setNotify($user, 0);
+ }
+
+ function output($user, $text)
+ {
+ $text = '['.common_config('site', 'name') . '] ' . $text;
+ $this->imPlugin->sendMessage($this->imPlugin->getScreenname($user), $text);
+ }
+
+ function error($user, $text)
+ {
+ $text = '['.common_config('site', 'name') . '] ' . $text;
+
+ $screenname = $this->imPlugin->getScreenname($user);
+ if($screenname){
+ $this->imPlugin->sendMessage($screenname, $text);
+ return true;
+ }else{
+ common_log(LOG_ERR,
+ 'Could not send error message to user ' . common_log_objstring($user) .
+ ' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
+ return false;
+ }
+ }
+
+ function setNotify($user, $notify)
+ {
+ $user_im_prefs = new User_im_prefs();
+ $user_im_prefs->transport = $this->imPlugin->transport;
+ $user_im_prefs->user_id = $user->id;
+ if($user_im_prefs->find() && $user_im_prefs->fetch()){
+ if($user_im_prefs->notify == $notify){
+ //notify is already set the way they want
+ return true;
+ }else{
+ $original = clone($user_im_prefs);
+ $user_im_prefs->notify = $notify;
+ $result = $user_im_prefs->update($original);
+
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR,
+ 'Could not set notify flag to ' . $notify .
+ ' for user ' . common_log_objstring($user) .
+ ' on transport ' . $this->imPlugin->transport .' : ' . $last_error->message);
+ return false;
+ } else {
+ common_log(LOG_INFO,
+ 'User ' . $user->nickname . ' set notify flag to ' . $notify);
+ return true;
+ }
+ }
+ }else{
+ common_log(LOG_ERR,
+ 'Could not set notify flag to ' . $notify .
+ ' for user ' . common_log_objstring($user) .
+ ' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
+ return false;
+ }
+ }
+}
diff --git a/lib/immanager.php b/lib/immanager.php
new file mode 100644
index 000000000..9563a5326
--- /dev/null
+++ b/lib/immanager.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * IKM background connection manager for IM-using queue handlers,
+ * allowing them to send outgoing messages on the right connection.
+ *
+ * In a multi-site queuedaemon.php run, one connection will be instantiated
+ * for each site being handled by the current process that has IM enabled.
+ *
+ * Implementations that extend this class will likely want to:
+ * 1) override start() with their connection process.
+ * 2) override handleInput() with what to do when data is waiting on
+ * one of the sockets
+ * 3) override idle($timeout) to do keepalives (if necessary)
+ * 4) implement send_raw_message() to send raw data that ImPlugin::enqueueOutgoingRaw
+ * enqueued
+ */
+
+abstract class ImManager extends IoManager
+{
+ abstract function send_raw_message($data);
+
+ function __construct($imPlugin)
+ {
+ $this->plugin = $imPlugin;
+ $this->plugin->imManager = $this;
+ }
+
+ /**
+ * Fetch the singleton manager for the current site.
+ * @return mixed ImManager, or false if unneeded
+ */
+ public static function get()
+ {
+ throw new Exception('ImManager should be created using it\'s constructor, not the static get method');
+ }
+}
diff --git a/lib/implugin.php b/lib/implugin.php
new file mode 100644
index 000000000..2811e7d64
--- /dev/null
+++ b/lib/implugin.php
@@ -0,0 +1,626 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Superclass for plugins that do instant messaging
+ *
+ * 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 Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Superclass for plugins that do authentication
+ *
+ * Implementations will likely want to override onStartIoManagerClasses() so that their
+ * IO manager is used
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+abstract class ImPlugin extends Plugin
+{
+ //name of this IM transport
+ public $transport = null;
+ //list of screennames that should get all public notices
+ public $public = array();
+
+ /**
+ * normalize a screenname for comparison
+ *
+ * @param string $screenname screenname to normalize
+ *
+ * @return string an equivalent screenname in normalized form
+ */
+ abstract function normalize($screenname);
+
+ /**
+ * validate (ensure the validity of) a screenname
+ *
+ * @param string $screenname screenname to validate
+ *
+ * @return boolean
+ */
+ abstract function validate($screenname);
+
+ /**
+ * get the internationalized/translated display name of this IM service
+ *
+ * @return string
+ */
+ abstract function getDisplayName();
+
+ /**
+ * send a single notice to a given screenname
+ * The implementation should put raw data, ready to send, into the outgoing
+ * queue using enqueueOutgoingRaw()
+ *
+ * @param string $screenname screenname to send to
+ * @param Notice $notice notice to send
+ *
+ * @return boolean success value
+ */
+ function sendNotice($screenname, $notice)
+ {
+ return $this->sendMessage($screenname, $this->formatNotice($notice));
+ }
+
+ /**
+ * send a message (text) to a given screenname
+ * The implementation should put raw data, ready to send, into the outgoing
+ * queue using enqueueOutgoingRaw()
+ *
+ * @param string $screenname screenname to send to
+ * @param Notice $body text to send
+ *
+ * @return boolean success value
+ */
+ abstract function sendMessage($screenname, $body);
+
+ /**
+ * receive a raw message
+ * Raw IM data is taken from the incoming queue, and passed to this function.
+ * It should parse the raw message and call handleIncoming()
+ *
+ * Returning false may CAUSE REPROCESSING OF THE QUEUE ITEM, and should
+ * be used for temporary failures only. For permanent failures such as
+ * unrecognized addresses, return true to indicate your processing has
+ * completed.
+ *
+ * @param object $data raw IM data
+ *
+ * @return boolean true if processing completed, false for temporary failures
+ */
+ abstract function receiveRawMessage($data);
+
+ /**
+ * get the screenname of the daemon that sends and receives message for this service
+ *
+ * @return string screenname of this plugin
+ */
+ abstract function daemonScreenname();
+
+ /**
+ * get the microid uri of a given screenname
+ *
+ * @param string $screenname screenname
+ *
+ * @return string microid uri
+ */
+ function microiduri($screenname)
+ {
+ return $this->transport . ':' . $screenname;
+ }
+ //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\
+
+ /**
+ * Put raw message data (ready to send) into the outgoing queue
+ *
+ * @param object $data
+ */
+ function enqueueOutgoingRaw($data)
+ {
+ $qm = QueueManager::get();
+ $qm->enqueue($data, $this->transport . '-out');
+ }
+
+ /**
+ * Put raw message data (received, ready to be processed) into the incoming queue
+ *
+ * @param object $data
+ */
+ function enqueueIncomingRaw($data)
+ {
+ $qm = QueueManager::get();
+ $qm->enqueue($data, $this->transport . '-in');
+ }
+
+ /**
+ * given a screenname, get the corresponding user
+ *
+ * @param string $screenname
+ *
+ * @return User user
+ */
+ function getUser($screenname)
+ {
+ $user_im_prefs = $this->getUserImPrefsFromScreenname($screenname);
+ if($user_im_prefs){
+ $user = User::staticGet('id', $user_im_prefs->user_id);
+ $user_im_prefs->free();
+ return $user;
+ }else{
+ return false;
+ }
+ }
+
+ /**
+ * given a screenname, get the User_im_prefs object for this transport
+ *
+ * @param string $screenname
+ *
+ * @return User_im_prefs user_im_prefs
+ */
+ function getUserImPrefsFromScreenname($screenname)
+ {
+ $user_im_prefs = User_im_prefs::pkeyGet(
+ array('transport' => $this->transport,
+ 'screenname' => $this->normalize($screenname)));
+ if ($user_im_prefs) {
+ return $user_im_prefs;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * given a User, get their screenname
+ *
+ * @param User $user
+ *
+ * @return string screenname of that user
+ */
+ function getScreenname($user)
+ {
+ $user_im_prefs = $this->getUserImPrefsFromUser($user);
+ if ($user_im_prefs) {
+ return $user_im_prefs->screenname;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * given a User, get their User_im_prefs
+ *
+ * @param User $user
+ *
+ * @return User_im_prefs user_im_prefs of that user
+ */
+ function getUserImPrefsFromUser($user)
+ {
+ $user_im_prefs = User_im_prefs::pkeyGet(
+ array('transport' => $this->transport,
+ 'user_id' => $user->id));
+ if ($user_im_prefs){
+ return $user_im_prefs;
+ } else {
+ return false;
+ }
+ }
+ //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - SENDING ========================\
+ /**
+ * Send a message to a given screenname from the site
+ *
+ * @param string $screenname screenname to send the message to
+ * @param string $msg message contents to send
+ *
+ * @param boolean success
+ */
+ protected function sendFromSite($screenname, $msg)
+ {
+ $text = '['.common_config('site', 'name') . '] ' . $msg;
+ $this->sendMessage($screenname, $text);
+ }
+
+ /**
+ * send a confirmation code to a user
+ *
+ * @param string $screenname screenname sending to
+ * @param string $code the confirmation code
+ * @param User $user user sending to
+ *
+ * @return boolean success value
+ */
+ function sendConfirmationCode($screenname, $code, $user)
+ {
+ $body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' .
+ 'If that\'s true, you can confirm by clicking on this URL: ' .
+ '%s' .
+ ' . (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.'),
+ $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
+
+ return $this->sendMessage($screenname, $body);
+ }
+
+ /**
+ * 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
+ */
+
+ function publicNotice($notice)
+ {
+ // Now, users who want everything
+
+ // FIXME PRIV don't send out private messages here
+ // XXX: should we send out non-local messages if public,localonly
+ // = false? I think not
+
+ foreach ($this->public as $screenname) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id .
+ ' to public listener ' . $screenname,
+ __FILE__);
+ $this->sendNotice($screenname, $notice);
+ }
+
+ return true;
+ }
+
+ /**
+ * broadcast a notice to all subscribers and reply recipients
+ *
+ * This function will send a notice to all subscribers on the local server
+ * who have IM addresses, and have IM notification enabled, and
+ * have this subscription enabled for IM. It also sends the notice to
+ * all recipients of @-replies who have IM addresses and IM notification
+ * enabled. This is really the heart of IM distribution in StatusNet.
+ *
+ * @param Notice $notice The notice to broadcast
+ *
+ * @return boolean success flag
+ */
+
+ function broadcastNotice($notice)
+ {
+
+ $ni = $notice->whoGets();
+
+ foreach ($ni as $user_id => $reason) {
+ $user = User::staticGet($user_id);
+ if (empty($user)) {
+ // either not a local user, or just not found
+ continue;
+ }
+ $user_im_prefs = $this->getUserImPrefsFromUser($user);
+ if(!$user_im_prefs || !$user_im_prefs->notify){
+ continue;
+ }
+
+ switch ($reason) {
+ case NOTICE_INBOX_SOURCE_REPLY:
+ if (!$user_im_prefs->replies) {
+ continue 2;
+ }
+ break;
+ case NOTICE_INBOX_SOURCE_SUB:
+ $sub = Subscription::pkeyGet(array('subscriber' => $user->id,
+ 'subscribed' => $notice->profile_id));
+ if (empty($sub) || !$sub->jabber) {
+ continue 2;
+ }
+ break;
+ case NOTICE_INBOX_SOURCE_GROUP:
+ break;
+ default:
+ throw new Exception(sprintf(_("Unknown inbox source %d."), $reason));
+ }
+
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user_im_prefs->screenname,
+ __FILE__);
+ $this->sendNotice($user_im_prefs->screenname, $notice);
+ $user_im_prefs->free();
+ }
+
+ return true;
+ }
+
+ /**
+ * makes a plain-text formatted version of a notice, suitable for IM distribution
+ *
+ * @param Notice $notice notice being sent
+ *
+ * @return string plain-text version of the notice, with user nickname prefixed
+ */
+
+ function formatNotice($notice)
+ {
+ $profile = $notice->getProfile();
+ return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']';
+ }
+ //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - RECEIVING ========================\
+
+ /**
+ * Attempt to handle a message as a command
+ * @param User $user user the message is from
+ * @param string $body message text
+ * @return boolean true if the message was a command and was executed, false if it was not a command
+ */
+ protected function handleCommand($user, $body)
+ {
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $body);
+ if ($cmd) {
+ $chan = new IMChannel($this);
+ $cmd->execute($chan);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Is some text an autoreply message?
+ * @param string $txt message text
+ * @return boolean true if autoreply
+ */
+ protected function isAutoreply($txt)
+ {
+ if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
+ return true;
+ } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Is some text an OTR message?
+ * @param string $txt message text
+ * @return boolean true if OTR
+ */
+ protected function isOtr($txt)
+ {
+ if (preg_match('/^\?OTR/', $txt)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Helper for handling incoming messages
+ * Your incoming message handler will probably want to call this function
+ *
+ * @param string $from screenname the message was sent from
+ * @param string $message message contents
+ *
+ * @param boolean success
+ */
+ protected function handleIncoming($from, $notice_text)
+ {
+ $user = $this->getUser($from);
+ // For common_current_user to work
+ global $_cur;
+ $_cur = $user;
+
+ if (!$user) {
+ $this->sendFromSite($from, 'Unknown user; go to ' .
+ common_local_url('imsettings') .
+ ' to add your address to your account');
+ common_log(LOG_WARNING, 'Message from unknown user ' . $from);
+ return;
+ }
+ if ($this->handleCommand($user, $notice_text)) {
+ common_log(LOG_INFO, "Command message by $from handled.");
+ return;
+ } else if ($this->isAutoreply($notice_text)) {
+ common_log(LOG_INFO, 'Ignoring auto reply from ' . $from);
+ return;
+ } else if ($this->isOtr($notice_text)) {
+ common_log(LOG_INFO, 'Ignoring OTR from ' . $from);
+ return;
+ } else {
+
+ common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
+
+ $this->addNotice($from, $user, $notice_text);
+ }
+
+ $user->free();
+ unset($user);
+ unset($_cur);
+ unset($message);
+ }
+
+ /**
+ * Helper for handling incoming messages
+ * Your incoming message handler will probably want to call this function
+ *
+ * @param string $from screenname the message was sent from
+ * @param string $message message contents
+ *
+ * @param boolean success
+ */
+ protected function addNotice($screenname, $user, $body)
+ {
+ $body = trim(strip_tags($body));
+ $content_shortened = common_shorten_links($body);
+ if (Notice::contentTooLong($content_shortened)) {
+ $this->sendFromSite($screenname, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'),
+ Notice::maxContent(),
+ mb_strlen($content_shortened)));
+ return;
+ }
+
+ try {
+ $notice = Notice::saveNew($user->id, $content_shortened, $this->transport);
+ } catch (Exception $e) {
+ common_log(LOG_ERR, $e->getMessage());
+ $this->sendFromSite($from, $e->getMessage());
+ return;
+ }
+
+ common_log(LOG_INFO,
+ 'Added notice ' . $notice->id . ' from user ' . $user->nickname);
+ $notice->free();
+ unset($notice);
+ }
+
+ //========================EVENT HANDLERS========================\
+
+ /**
+ * Register notice queue handler
+ *
+ * @param QueueManager $manager
+ *
+ * @return boolean hook return
+ */
+ function onEndInitializeQueueManager($manager)
+ {
+ $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im');
+ $manager->connect($this->transport, new ImQueueHandler($this));
+ $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im');
+ return true;
+ }
+
+ function onStartImDaemonIoManagers(&$classes)
+ {
+ //$classes[] = new ImManager($this); // handles sending/receiving/pings/reconnects
+ return true;
+ }
+
+ function onStartEnqueueNotice($notice, &$transports)
+ {
+ $profile = Profile::staticGet($notice->profile_id);
+
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ }else{
+ $transports[] = $this->transport;
+ }
+
+ return true;
+ }
+
+ function onEndShowHeadElements($action)
+ {
+ $aname = $action->trimmed('action');
+
+ if ($aname == 'shownotice') {
+
+ $user_im_prefs = new User_im_prefs();
+ $user_im_prefs->user_id = $action->profile->id;
+ $user_im_prefs->transport = $this->transport;
+
+ if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) {
+ $id = new Microid($this->microiduri($user_im_prefs->screenname),
+ $action->notice->uri);
+ $action->element('meta', array('name' => 'microid',
+ 'content' => $id->toString()));
+ }
+
+ } else if ($aname == 'showstream') {
+
+ $user_im_prefs = new User_im_prefs();
+ $user_im_prefs->user_id = $action->user->id;
+ $user_im_prefs->transport = $this->transport;
+
+ if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->profile->profileurl) {
+ $id = new Microid($this->microiduri($user_im_prefs->screenname),
+ $action->selfUrl());
+ $action->element('meta', array('name' => 'microid',
+ 'content' => $id->toString()));
+ }
+ }
+ }
+
+ function onNormalizeImScreenname($transport, &$screenname)
+ {
+ if($transport == $this->transport)
+ {
+ $screenname = $this->normalize($screenname);
+ return false;
+ }
+ }
+
+ function onValidateImScreenname($transport, $screenname, &$valid)
+ {
+ if($transport == $this->transport)
+ {
+ $valid = $this->validate($screenname);
+ return false;
+ }
+ }
+
+ function onGetImTransports(&$transports)
+ {
+ $transports[$this->transport] = array(
+ 'display' => $this->getDisplayName(),
+ 'daemonScreenname' => $this->daemonScreenname());
+ }
+
+ function onSendImConfirmationCode($transport, $screenname, $code, $user)
+ {
+ if($transport == $this->transport)
+ {
+ $this->sendConfirmationCode($screenname, $code, $user);
+ return false;
+ }
+ }
+
+ function onUserDeleteRelated($user, &$tables)
+ {
+ $tables[] = 'User_im_prefs';
+ return true;
+ }
+
+ function initialize()
+ {
+ if( ! common_config('queue', 'enabled'))
+ {
+ throw new ServerException("Queueing must be enabled to use IM plugins");
+ }
+
+ if(is_null($this->transport)){
+ throw new ServerException('transport cannot be null');
+ }
+ }
+}
diff --git a/lib/jabberqueuehandler.php b/lib/imqueuehandler.php
index d6b4b7416..9c35890c6 100644
--- a/lib/jabberqueuehandler.php
+++ b/lib/imqueuehandler.php
@@ -17,31 +17,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
- * Queue handler for pushing new notices to Jabber users.
- * @fixme this exception handling doesn't look very good.
+ * Common superclass for all IM sending queue handlers.
*/
-class JabberQueueHandler extends QueueHandler
-{
- var $conn = null;
- function transport()
+class ImQueueHandler extends QueueHandler
+{
+ function __construct($plugin)
{
- return 'jabber';
+ $this->plugin = $plugin;
}
+ /**
+ * Handle a notice
+ * @param Notice $notice
+ * @return boolean success
+ */
function handle($notice)
{
- require_once(INSTALLDIR.'/lib/jabber.php');
- try {
- return jabber_broadcast_notice($notice);
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
- return false;
+ $this->plugin->broadcastNotice($notice);
+ if ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC) {
+ $this->plugin->publicNotice($notice);
}
+ return true;
}
+
}
diff --git a/lib/publicqueuehandler.php b/lib/imreceiverqueuehandler.php
index a497d1385..aa4a663b7 100644
--- a/lib/publicqueuehandler.php
+++ b/lib/imreceiverqueuehandler.php
@@ -17,29 +17,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
- * Queue handler for pushing new notices to public XMPP subscribers.
+ * Common superclass for all IM receiving queue handlers.
*/
-class PublicQueueHandler extends QueueHandler
-{
- function transport()
+class ImReceiverQueueHandler extends QueueHandler
+{
+ function __construct($plugin)
{
- return 'public';
+ $this->plugin = $plugin;
}
- function handle($notice)
+ /**
+ * Handle incoming IM data sent by a user to the IM bot
+ * @param object $data
+ * @return boolean success
+ */
+ function handle($data)
{
- require_once(INSTALLDIR.'/lib/jabber.php');
- try {
- return jabber_public_notice($notice);
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
- return false;
- }
+ return $this->plugin->receiveRawMessage($data);
}
}
diff --git a/lib/imsenderqueuehandler.php b/lib/imsenderqueuehandler.php
new file mode 100644
index 000000000..790dd7b10
--- /dev/null
+++ b/lib/imsenderqueuehandler.php
@@ -0,0 +1,43 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * Common superclass for all IM sending queue handlers.
+ */
+
+class ImSenderQueueHandler extends QueueHandler
+{
+ function __construct($plugin)
+ {
+ $this->plugin = $plugin;
+ }
+
+ /**
+ * Handle outgoing IM data to be sent from the bot to a user
+ * @param object $data
+ * @return boolean success
+ */
+ function handle($data)
+ {
+ return $this->plugin->imManager->send_raw_message($data);
+ }
+}
+
diff --git a/lib/jabber.php b/lib/jabber.php
deleted file mode 100644
index cdcfc4423..000000000
--- a/lib/jabber.php
+++ /dev/null
@@ -1,640 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * 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.
- *
- * 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 Network
- * @package StatusNet
- * @author Evan Prodromou <evan@status.net>
- * @copyright 2008 StatusNet, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once 'XMPPHP/XMPP.php';
-
-/**
- * Splits a Jabber ID (JID) into node, domain, and resource portions.
- *
- * Based on validation routine submitted by:
- * @copyright 2009 Patrick Georgi <patrick@georgi-clan.de>
- * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact.
- *
- * @param string $jid string to check
- *
- * @return array with "node", "domain", and "resource" indices
- * @throws Exception if input is not valid
- */
-
-function jabber_split_jid($jid)
-{
- $chars = '';
- /* the following definitions come from stringprep, Appendix C,
- which is used in its entirety by nodeprop, Chapter 5, "Prohibited Output" */
- /* C1.1 ASCII space characters */
- $chars .= "\x{20}";
- /* C1.2 Non-ASCII space characters */
- $chars .= "\x{a0}\x{1680}\x{2000}-\x{200b}\x{202f}\x{205f}\x{3000a}";
- /* C2.1 ASCII control characters */
- $chars .= "\x{00}-\x{1f}\x{7f}";
- /* C2.2 Non-ASCII control characters */
- $chars .= "\x{80}-\x{9f}\x{6dd}\x{70f}\x{180e}\x{200c}\x{200d}\x{2028}\x{2029}\x{2060}-\x{2063}\x{206a}-\x{206f}\x{feff}\x{fff9}-\x{fffc}\x{1d173}-\x{1d17a}";
- /* C3 - Private Use */
- $chars .= "\x{e000}-\x{f8ff}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}";
- /* C4 - Non-character code points */
- $chars .= "\x{fdd0}-\x{fdef}\x{fffe}\x{ffff}\x{1fffe}\x{1ffff}\x{2fffe}\x{2ffff}\x{3fffe}\x{3ffff}\x{4fffe}\x{4ffff}\x{5fffe}\x{5ffff}\x{6fffe}\x{6ffff}\x{7fffe}\x{7ffff}\x{8fffe}\x{8ffff}\x{9fffe}\x{9ffff}\x{afffe}\x{affff}\x{bfffe}\x{bffff}\x{cfffe}\x{cffff}\x{dfffe}\x{dffff}\x{efffe}\x{effff}\x{ffffe}\x{fffff}\x{10fffe}\x{10ffff}";
- /* C5 - Surrogate codes */
- $chars .= "\x{d800}-\x{dfff}";
- /* C6 - Inappropriate for plain text */
- $chars .= "\x{fff9}-\x{fffd}";
- /* C7 - Inappropriate for canonical representation */
- $chars .= "\x{2ff0}-\x{2ffb}";
- /* C8 - Change display properties or are deprecated */
- $chars .= "\x{340}\x{341}\x{200e}\x{200f}\x{202a}-\x{202e}\x{206a}-\x{206f}";
- /* C9 - Tagging characters */
- $chars .= "\x{e0001}\x{e0020}-\x{e007f}";
-
- /* Nodeprep forbids some more characters */
- $nodeprepchars = $chars;
- $nodeprepchars .= "\x{22}\x{26}\x{27}\x{2f}\x{3a}\x{3c}\x{3e}\x{40}";
-
- $parts = explode("/", $jid, 2);
- if (count($parts) > 1) {
- $resource = $parts[1];
- if ($resource == '') {
- // Warning: empty resource isn't legit.
- // But if we're normalizing, we may as well take it...
- }
- } else {
- $resource = null;
- }
-
- $node = explode("@", $parts[0]);
- if ((count($node) > 2) || (count($node) == 0)) {
- throw new Exception("Invalid JID: too many @s");
- } else if (count($node) == 1) {
- $domain = $node[0];
- $node = null;
- } else {
- $domain = $node[1];
- $node = $node[0];
- if ($node == '') {
- throw new Exception("Invalid JID: @ but no node");
- }
- }
-
- // Length limits per http://xmpp.org/rfcs/rfc3920.html#addressing
- if ($node !== null) {
- if (strlen($node) > 1023) {
- throw new Exception("Invalid JID: node too long.");
- }
- if (preg_match("/[".$nodeprepchars."]/u", $node)) {
- throw new Exception("Invalid JID node '$node'");
- }
- }
-
- if (strlen($domain) > 1023) {
- throw new Exception("Invalid JID: domain too long.");
- }
- if (!common_valid_domain($domain)) {
- throw new Exception("Invalid JID domain name '$domain'");
- }
-
- if ($resource !== null) {
- if (strlen($resource) > 1023) {
- throw new Exception("Invalid JID: resource too long.");
- }
- if (preg_match("/[".$chars."]/u", $resource)) {
- throw new Exception("Invalid JID resource '$resource'");
- }
- }
-
- return array('node' => is_null($node) ? null : mb_strtolower($node),
- 'domain' => is_null($domain) ? null : mb_strtolower($domain),
- 'resource' => $resource);
-}
-
-/**
- * Checks whether a string is a syntactically valid Jabber ID (JID),
- * either with or without a resource.
- *
- * Note that a bare domain can be a valid JID.
- *
- * @param string $jid string to check
- * @param bool $check_domain whether we should validate that domain...
- *
- * @return boolean whether the string is a valid JID
- */
-function jabber_valid_full_jid($jid, $check_domain=false)
-{
- try {
- $parts = jabber_split_jid($jid);
- if ($check_domain) {
- if (!jabber_check_domain($parts['domain'])) {
- return false;
- }
- }
- return $parts['resource'] !== ''; // missing or present; empty ain't kosher
- } catch (Exception $e) {
- return false;
- }
-}
-
-/**
- * Checks whether a string is a syntactically valid base Jabber ID (JID).
- * A base JID won't include a resource specifier on the end; since we
- * take it off when reading input we can't really use them reliably
- * to direct outgoing messages yet (sorry guys!)
- *
- * Note that a bare domain can be a valid JID.
- *
- * @param string $jid string to check
- * @param bool $check_domain whether we should validate that domain...
- *
- * @return boolean whether the string is a valid JID
- */
-function jabber_valid_base_jid($jid, $check_domain=false)
-{
- try {
- $parts = jabber_split_jid($jid);
- if ($check_domain) {
- if (!jabber_check_domain($parts['domain'])) {
- return false;
- }
- }
- return ($parts['resource'] === null); // missing; empty ain't kosher
- } catch (Exception $e) {
- return false;
- }
-}
-
-/**
- * Normalizes a Jabber ID for comparison, dropping the resource component if any.
- *
- * @param string $jid JID to check
- * @param bool $check_domain if true, reject if the domain isn't findable
- *
- * @return string an equivalent JID in normalized (lowercase) form
- */
-
-function jabber_normalize_jid($jid)
-{
- try {
- $parts = jabber_split_jid($jid);
- if ($parts['node'] !== null) {
- return $parts['node'] . '@' . $parts['domain'];
- } else {
- return $parts['domain'];
- }
- } catch (Exception $e) {
- return null;
- }
-}
-
-/**
- * Check if this domain's got some legit DNS record
- */
-function jabber_check_domain($domain)
-{
- if (checkdnsrr("_xmpp-server._tcp." . $domain, "SRV")) {
- return true;
- }
- if (checkdnsrr($domain, "ANY")) {
- return true;
- }
- return false;
-}
-
-/**
- * the JID of the Jabber daemon for this StatusNet instance
- *
- * @return string JID of the Jabber daemon
- */
-
-function jabber_daemon_address()
-{
- return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
-}
-
-class Sharing_XMPP extends XMPPHP_XMPP
-{
- function getSocket()
- {
- return $this->socket;
- }
-}
-
-/**
- * Build an XMPP proxy connection that'll save outgoing messages
- * to the 'xmppout' queue to be picked up by xmppdaemon later.
- *
- * If queueing is disabled, we'll grab a live connection.
- *
- * @return XMPPHP
- */
-function jabber_proxy()
-{
- if (common_config('queue', 'enabled')) {
- $proxy = new Queued_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'),
- common_config('xmpp', 'resource') . 'daemon',
- common_config('xmpp', 'server'),
- common_config('xmpp', 'debug') ?
- true : false,
- common_config('xmpp', 'debug') ?
- XMPPHP_Log::LEVEL_VERBOSE : null);
- return $proxy;
- } else {
- return jabber_connect();
- }
-}
-
-/**
- * Lazy-connect the configured Jabber account to the configured server;
- * if already opened, the same connection will be returned.
- *
- * In a multi-site background process, each site configuration
- * will get its own connection.
- *
- * @param string $resource Resource to connect (defaults to configured resource)
- *
- * @return XMPPHP connection to the configured server
- */
-
-function jabber_connect($resource=null)
-{
- static $connections = array();
- $site = common_config('site', 'server');
- if (empty($connections[$site])) {
- if (empty($resource)) {
- $resource = common_config('xmpp', 'resource');
- }
- $conn = new Sharing_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,
- common_config('xmpp', 'server'),
- common_config('xmpp', 'debug') ?
- true : false,
- common_config('xmpp', 'debug') ?
- XMPPHP_Log::LEVEL_VERBOSE : null
- );
-
- if (!$conn) {
- return false;
- }
- $connections[$site] = $conn;
-
- $conn->autoSubscribe();
- $conn->useEncryption(common_config('xmpp', 'encryption'));
-
- try {
- common_log(LOG_INFO, __METHOD__ . ": connecting " .
- common_config('xmpp', 'user') . '/' . $resource);
- //$conn->connect(true); // true = persistent connection
- $conn->connect(); // persistent connections break multisite
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERR, $e->getMessage());
- return false;
- }
-
- $conn->processUntil('session_start');
- }
- return $connections[$site];
-}
-
-/**
- * Queue send for 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_proxy();
- $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;
-}
-
-/**
- * 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)
-{
- $entry = $notice->asAtomEntry(true, true);
-
- $xs = new XMLStringer();
- $xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im'));
- $xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml'));
- $xs->element('a', array('href' => $profile->profileurl),
- $profile->nickname);
- $xs->text(": ");
- if (!empty($notice->rendered)) {
- $xs->raw($notice->rendered);
- } else {
- $xs->raw(common_render_content($notice->content, $notice));
- }
- $xs->text(" ");
- $xs->element('a', array(
- 'href'=>common_local_url('conversation',
- array('id' => $notice->conversation)).'#notice-'.$notice->id
- ),sprintf(_('[%s]'),$notice->id));
- $xs->elementEnd('body');
- $xs->elementEnd('html');
-
- $html = $xs->getString();
-
- return $html . ' ' . $entry;
-}
-
-/**
- * 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_proxy();
- $conn->message($to, $body, $type, $subject);
- return true;
-}
-
-/**
- * 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
- */
-
-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;
-}
-
-/**
- * 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);
-}
-
-/**
- * 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()
- */
-
-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);
-}
-
-/**
- * Queue broadcast of 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 StatusNet.
- *
- * @param Notice $notice The notice to broadcast
- *
- * @return boolean success flag
- */
-
-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 true; // not recoverable; discard.
- }
-
- $msg = jabber_format_notice($profile, $notice);
- $entry = jabber_format_entry($profile, $notice);
-
- $profile->free();
- unset($profile);
-
- $sent_to = array();
-
- $conn = jabber_proxy();
-
- $ni = $notice->whoGets();
-
- foreach ($ni as $user_id => $reason) {
- $user = User::staticGet($user_id);
- if (empty($user) ||
- empty($user->jabber) ||
- !$user->jabbernotify) {
- // either not a local user, or just not found
- continue;
- }
- switch ($reason) {
- case NOTICE_INBOX_SOURCE_REPLY:
- if (!$user->jabberreplies) {
- continue 2;
- }
- break;
- case NOTICE_INBOX_SOURCE_SUB:
- $sub = Subscription::pkeyGet(array('subscriber' => $user->id,
- 'subscribed' => $notice->profile_id));
- if (empty($sub) || !$sub->jabber) {
- continue 2;
- }
- break;
- case NOTICE_INBOX_SOURCE_GROUP:
- break;
- default:
- throw new Exception(sprintf(_("Unknown inbox source %d."), $reason));
- }
-
- common_log(LOG_INFO,
- 'Sending notice ' . $notice->id . ' to ' . $user->jabber,
- __FILE__);
- $conn->message($user->jabber, $msg, 'chat', null, $entry);
- }
-
- return true;
-}
-
-/**
- * Queue send of 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
- */
-
-function jabber_public_notice($notice)
-{
- // Now, users who want everything
-
- $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
-
- if ($public && $notice->is_local == Notice::LOCAL_PUBLIC) {
- $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 true; // not recoverable; discard.
- }
-
- $msg = jabber_format_notice($profile, $notice);
- $entry = jabber_format_entry($profile, $notice);
-
- $conn = jabber_proxy();
-
- foreach ($public as $address) {
- common_log(LOG_INFO,
- 'Sending notice ' . $notice->id .
- ' to public listener ' . $address,
- __FILE__);
- $conn->message($address, $msg, 'chat', null, $entry);
- }
- $profile->free();
- }
-
- return true;
-}
-
-/**
- * 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 . ' [' . $notice->id . ']';
-}
diff --git a/lib/plugindisableform.php b/lib/plugindisableform.php
new file mode 100644
index 000000000..3cbabdb2c
--- /dev/null
+++ b/lib/plugindisableform.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for enabling/disabling plugins
+ *
+ * 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 StatusNet
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Form for joining a group
+ *
+ * @category Form
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ *
+ * @see PluginEnableForm
+ */
+
+class PluginDisableForm extends PluginEnableForm
+{
+ /**
+ * ID of the form
+ *
+ * @return string ID of the form
+ */
+
+ function id()
+ {
+ return 'plugin-disable-' . $this->plugin;
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_plugin_disable';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('plugindisable',
+ array('plugin' => $this->plugin));
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ // TRANS: Plugin admin panel controls
+ $this->out->submit('submit', _m('plugin', 'Disable'));
+ }
+
+}
diff --git a/lib/pluginenableform.php b/lib/pluginenableform.php
new file mode 100644
index 000000000..8683ffd0b
--- /dev/null
+++ b/lib/pluginenableform.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for enabling/disabling plugins
+ *
+ * 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 StatusNet
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for joining a group
+ *
+ * @category Form
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ *
+ * @see PluginDisableForm
+ */
+
+class PluginEnableForm extends Form
+{
+ /**
+ * Plugin to enable/disable
+ */
+
+ var $plugin = null;
+
+ /**
+ * Constructor
+ *
+ * @param HTMLOutputter $out output channel
+ * @param string $plugin plugin to enable/disable
+ */
+
+ function __construct($out=null, $plugin=null)
+ {
+ parent::__construct($out);
+
+ $this->plugin = $plugin;
+ }
+
+ /**
+ * ID of the form
+ *
+ * @return string ID of the form
+ */
+
+ function id()
+ {
+ return 'plugin-enable-' . $this->plugin;
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string of the form class
+ */
+
+ function formClass()
+ {
+ return 'form_plugin_enable';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('pluginenable',
+ array('plugin' => $this->plugin));
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ // TRANS: Plugin admin panel controls
+ $this->out->submit('submit', _m('plugin', 'Enable'));
+ }
+}
diff --git a/lib/pluginlist.php b/lib/pluginlist.php
new file mode 100644
index 000000000..07a17ba39
--- /dev/null
+++ b/lib/pluginlist.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugins administration panel
+ *
+ * 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 StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require INSTALLDIR . "/lib/pluginenableform.php";
+require INSTALLDIR . "/lib/plugindisableform.php";
+
+/**
+ * Plugin list
+ *
+ * @category Admin
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class PluginList extends Widget
+{
+ var $plugins = array();
+
+ function __construct($plugins, $out)
+ {
+ parent::__construct($out);
+ $this->plugins = $plugins;
+ }
+
+ function show()
+ {
+ $this->startList();
+ $this->showPlugins();
+ $this->endList();
+ }
+
+ function startList()
+ {
+ $this->out->elementStart('table', 'plugin_list');
+ }
+
+ function endList()
+ {
+ $this->out->elementEnd('table');
+ }
+
+ function showPlugins()
+ {
+ foreach ($this->plugins as $plugin) {
+ $pli = $this->newListItem($plugin);
+ $pli->show();
+ }
+ }
+
+ function newListItem($plugin)
+ {
+ return new PluginListItem($plugin, $this->out);
+ }
+}
+
+class PluginListItem extends Widget
+{
+ /** Current plugin. */
+ var $plugin = null;
+
+ /** Local cache for plugin version info */
+ protected static $versions = false;
+
+ function __construct($plugin, $out)
+ {
+ parent::__construct($out);
+ $this->plugin = $plugin;
+ }
+
+ function show()
+ {
+ $meta = $this->metaInfo();
+
+ $this->out->elementStart('tr', array('id' => 'plugin-' . $this->plugin));
+
+ // Name and controls
+ $this->out->elementStart('td');
+ $this->out->elementStart('div');
+ if (!empty($meta['homepage'])) {
+ $this->out->elementStart('a', array('href' => $meta['homepage']));
+ }
+ $this->out->text($this->plugin);
+ if (!empty($meta['homepage'])) {
+ $this->out->elementEnd('a');
+ }
+ $this->out->elementEnd('div');
+
+ $form = $this->getControlForm();
+ $form->show();
+
+ $this->out->elementEnd('td');
+
+ // Version and authors
+ $this->out->elementStart('td');
+ if (!empty($meta['version'])) {
+ $this->out->elementStart('div');
+ $this->out->text($meta['version']);
+ $this->out->elementEnd('div');
+ }
+ if (!empty($meta['author'])) {
+ $this->out->elementStart('div');
+ $this->out->text($meta['author']);
+ $this->out->elementEnd('div');
+ }
+ $this->out->elementEnd('td');
+
+ // Description
+ $this->out->elementStart('td');
+ if (!empty($meta['rawdescription'])) {
+ $this->out->raw($meta['rawdescription']);
+ }
+ $this->out->elementEnd('td');
+
+ $this->out->elementEnd('tr');
+ }
+
+ /**
+ * Pull up the appropriate control form for this plugin, depending
+ * on its current state.
+ *
+ * @return Form
+ */
+ protected function getControlForm()
+ {
+ $key = 'disable-' . $this->plugin;
+ if (common_config('plugins', $key)) {
+ return new PluginEnableForm($this->out, $this->plugin);
+ } else {
+ return new PluginDisableForm($this->out, $this->plugin);
+ }
+ }
+
+ /**
+ * Grab metadata about this plugin...
+ * Warning: horribly inefficient and may explode!
+ * Doesn't work for disabled plugins either.
+ *
+ * @fixme pull structured data from plugin source
+ */
+ function metaInfo()
+ {
+ $versions = self::getPluginVersions();
+ $found = false;
+
+ foreach ($versions as $info) {
+ // hack for URL shorteners... "LilUrl (ur1.ca)" etc
+ list($name, ) = explode(' ', $info['name']);
+
+ if ($name == $this->plugin) {
+ if ($found) {
+ // hack for URL shorteners...
+ $found['rawdescription'] .= "<br />\n" . $info['rawdescription'];
+ } else {
+ $found = $info;
+ }
+ }
+ }
+
+ if ($found) {
+ return $found;
+ } else {
+ return array('name' => $this->plugin,
+ 'rawdescription' => _m('plugin-description',
+ '(Plugin descriptions unavailable when disabled.)'));
+ }
+ }
+
+ /**
+ * Lazy-load the set of active plugin version info
+ * @return array
+ */
+ protected static function getPluginVersions()
+ {
+ if (!is_array(self::$versions)) {
+ $versions = array();
+ Event::handle('PluginVersion', array(&$versions));
+ self::$versions = $versions;
+ }
+ return self::$versions;
+ }
+}
diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php
deleted file mode 100644
index f6bccfd5b..000000000
--- a/lib/queued_xmpp.php
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Queue-mediated proxy class for outgoing 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.
- *
- * 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 Network
- * @package StatusNet
- * @author Brion Vibber <brion@status.net>
- * @copyright 2010 StatusNet, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR . '/lib/jabber.php';
-
-class Queued_XMPP extends XMPPHP_XMPP
-{
- /**
- * Constructor
- *
- * @param string $host
- * @param integer $port
- * @param string $user
- * @param string $password
- * @param string $resource
- * @param string $server
- * @param boolean $printlog
- * @param string $loglevel
- */
- public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null)
- {
- parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel);
-
- // We use $host to connect, but $server to build JIDs if specified.
- // This seems to fix an upstream bug where $host was used to build
- // $this->basejid, never seen since it isn't actually used in the base
- // classes.
- if (!$server) {
- $server = $this->host;
- }
- $this->basejid = $this->user . '@' . $server;
-
- // Normally the fulljid is filled out by the server at resource binding
- // time, but we need to do it since we're not talking to a real server.
- $this->fulljid = "{$this->basejid}/{$this->resource}";
- }
-
- /**
- * Send a formatted message to the outgoing queue for later forwarding
- * to a real XMPP connection.
- *
- * @param string $msg
- */
- public function send($msg, $timeout=NULL)
- {
- $qm = QueueManager::get('xmppout');
- $qm->enqueue(strval($msg), 'xmppout');
- }
-
- /**
- * Since we'll be getting input through a queue system's run loop,
- * we'll process one standalone message at a time rather than our
- * own XMPP message pump.
- *
- * @param string $message
- */
- public function processMessage($message) {
- $frame = array_shift($this->frames);
- xml_parse($this->parser, $frame->body, false);
- }
-
- //@{
- /**
- * Stream i/o functions disabled; push input through processMessage()
- */
- public function connect($timeout = 30, $persistent = false, $sendinit = true)
- {
- throw new Exception("Can't connect to server from XMPP queue proxy.");
- }
-
- public function disconnect()
- {
- throw new Exception("Can't connect to server from XMPP queue proxy.");
- }
-
- public function process()
- {
- throw new Exception("Can't read stream from XMPP queue proxy.");
- }
-
- public function processUntil($event, $timeout=-1)
- {
- throw new Exception("Can't read stream from XMPP queue proxy.");
- }
-
- public function read()
- {
- throw new Exception("Can't read stream from XMPP queue proxy.");
- }
-
- public function readyToProcess()
- {
- throw new Exception("Can't read stream from XMPP queue proxy.");
- }
- //@}
-}
-
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
index 2909cd83b..2194dd161 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -37,20 +37,6 @@ class QueueHandler
{
/**
- * Return transport keyword which identifies items this queue handler
- * services; must be defined for all subclasses.
- *
- * Must be 8 characters or less to fit in the queue_item database.
- * ex "email", "jabber", "sms", "irc", ...
- *
- * @return string
- */
- function transport()
- {
- return null;
- }
-
- /**
* Here's the meat of your queue handler -- you're handed a Notice
* or other object, which you may do as you will with.
*
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
index 0829c8a8b..6666a6cb5 100644
--- a/lib/queuemanager.php
+++ b/lib/queuemanager.php
@@ -156,21 +156,14 @@ abstract class QueueManager extends IoManager
}
/**
- * Encode an object or variable for queued storage.
- * Notice objects are currently stored as an id reference;
- * other items are serialized.
+ * Encode an object for queued storage.
*
* @param mixed $item
* @return string
*/
protected function encode($item)
{
- if ($item instanceof Notice) {
- // Backwards compat
- return $item->id;
- } else {
- return serialize($item);
- }
+ return serialize($item);
}
/**
@@ -182,25 +175,7 @@ abstract class QueueManager extends IoManager
*/
protected function decode($frame)
{
- if (is_numeric($frame)) {
- // Back-compat for notices...
- return Notice::staticGet(intval($frame));
- } elseif (substr($frame, 0, 1) == '<') {
- // Back-compat for XML source
- return $frame;
- } else {
- // Deserialize!
- #$old = error_reporting();
- #error_reporting($old & ~E_NOTICE);
- $out = unserialize($frame);
- #error_reporting($old);
-
- if ($out === false && $frame !== 'b:0;') {
- common_log(LOG_ERR, "Couldn't unserialize queued frame: $frame");
- return false;
- }
- return $out;
- }
+ return unserialize($frame);
}
/**
@@ -270,16 +245,6 @@ abstract class QueueManager extends IoManager
// Broadcasting profile updates to OMB remote subscribers
$this->connect('profile', 'ProfileQueueHandler');
- // XMPP output handlers...
- if (common_config('xmpp', 'enabled')) {
- // Delivery prep, read by queuedaemon.php:
- $this->connect('jabber', 'JabberQueueHandler');
- $this->connect('public', 'PublicQueueHandler');
-
- // Raw output, read by xmppdaemon.php:
- $this->connect('xmppout', 'XmppOutQueueHandler', 'xmpp');
- }
-
// For compat with old plugins not registering their own handlers.
$this->connect('plugin', 'PluginQueueHandler');
}
diff --git a/lib/queuemonitor.php b/lib/queuemonitor.php
index 1c306a629..3dc0ea65a 100644
--- a/lib/queuemonitor.php
+++ b/lib/queuemonitor.php
@@ -36,7 +36,7 @@ class QueueMonitor
* Only explicitly listed thread/site/queue owners will be incremented.
*
* @param string $key counter name
- * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01'
+ * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01'
*/
public function stats($key, $owners=array())
{
diff --git a/lib/router.php b/lib/router.php
index 00b299373..3bbb4a044 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -151,6 +151,8 @@ class Router
$m->connect('main/xrds',
array('action' => 'publicxrds'));
+ $m->connect('.well-known/host-meta',
+ array('action' => 'hostmeta'));
// these take a code
@@ -655,6 +657,12 @@ class Router
$m->connect('api/statusnet/groups/create.:format',
array('action' => 'ApiGroupCreate',
'format' => '(xml|json)'));
+
+ $m->connect('api/statusnet/groups/update/:id.:format',
+ array('action' => 'ApiGroupProfileUpdate',
+ 'id' => '[a-zA-Z0-9]+',
+ 'format' => '(xml|json)'));
+
// Tags
$m->connect('api/statusnet/tags/timeline/:tag.:format',
array('action' => 'ApiTimelineTag',
@@ -691,7 +699,13 @@ class Router
$m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
$m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
$m->connect('admin/license', array('action' => 'licenseadminpanel'));
-
+ $m->connect('admin/plugins', array('action' => 'pluginsadminpanel'));
+ $m->connect('admin/plugins/enable/:plugin',
+ array('action' => 'pluginenable'),
+ array('plugin' => '[A-Za-z0-9_]+'));
+ $m->connect('admin/plugins/disable/:plugin',
+ array('action' => 'plugindisable'),
+ array('plugin' => '[A-Za-z0-9_]+'));
$m->connect('getfile/:filename',
array('action' => 'getfile'),
diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php
index 2f9f6e32e..ea09b6fb2 100644
--- a/lib/spawningdaemon.php
+++ b/lib/spawningdaemon.php
@@ -204,7 +204,7 @@ abstract class SpawningDaemon extends Daemon
// Reconnect main memcached, or threads will stomp on
// each other and corrupt their requests.
- $cache = common_memcache();
+ $cache = Cache::instance();
if ($cache) {
$cache->reconnect();
}
diff --git a/lib/statusnet.php b/lib/statusnet.php
index 7212a4a47..ff0502915 100644
--- a/lib/statusnet.php
+++ b/lib/statusnet.php
@@ -177,6 +177,11 @@ class StatusNet
{
// Load default plugins
foreach (common_config('plugins', 'default') as $name => $params) {
+ $key = 'disable-' . $name;
+ if (common_config('plugins', $key)) {
+ continue;
+ }
+
if (is_null($params)) {
addPlugin($name);
} else if (is_array($params)) {
@@ -354,7 +359,6 @@ class StatusNet
}
// Backwards compatibility
-
if (array_key_exists('memcached', $config)) {
if ($config['memcached']['enabled']) {
addPlugin('Memcache', array('servers' => $config['memcached']['server']));
@@ -364,6 +368,21 @@ class StatusNet
$config['cache']['base'] = $config['memcached']['base'];
}
}
+ if (array_key_exists('xmpp', $config)) {
+ if ($config['xmpp']['enabled']) {
+ addPlugin('xmpp', array(
+ 'server' => $config['xmpp']['server'],
+ 'port' => $config['xmpp']['port'],
+ 'user' => $config['xmpp']['user'],
+ 'resource' => $config['xmpp']['resource'],
+ 'encryption' => $config['xmpp']['encryption'],
+ 'password' => $config['xmpp']['password'],
+ 'host' => $config['xmpp']['host'],
+ 'debug' => $config['xmpp']['debug'],
+ 'public' => $config['xmpp']['public']
+ ));
+ }
+ }
}
}
diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php
index fc98c77d4..1d9a5ad20 100644
--- a/lib/stompqueuemanager.php
+++ b/lib/stompqueuemanager.php
@@ -578,7 +578,7 @@ class StompQueueManager extends QueueManager
function incDeliveryCount($msgId)
{
$count = 0;
- $cache = common_memcache();
+ $cache = Cache::instance();
if ($cache) {
$key = 'statusnet:stomp:message-retries:' . $msgId;
$count = $cache->increment($key);
diff --git a/lib/urlshortenerplugin.php b/lib/urlshortenerplugin.php
new file mode 100644
index 000000000..8acfac26f
--- /dev/null
+++ b/lib/urlshortenerplugin.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Superclass for plugins that do URL shortening
+ *
+ * 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 Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Superclass for plugins that do URL shortening
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+abstract class UrlShortenerPlugin extends Plugin
+{
+ public $shortenerName;
+ public $freeService = false;
+
+ // Url Shortener plugins should implement some (or all)
+ // of these methods
+
+ /**
+ * Make an URL shorter.
+ *
+ * @param string $url URL to shorten
+ *
+ * @return string shortened version of the url, or null on failure
+ */
+
+ protected abstract function shorten($url);
+
+ /**
+ * Utility to get the data at an URL
+ *
+ * @param string $url URL to fetch
+ *
+ * @return string response body
+ *
+ * @todo rename to code-standard camelCase httpGet()
+ */
+
+ protected function http_get($url)
+ {
+ $request = HTTPClient::start();
+ $response = $request->get($url);
+ return $response->getBody();
+ }
+
+ /**
+ * Utility to post a request and get a response URL
+ *
+ * @param string $url URL to fetch
+ * @param array $data post parameters
+ *
+ * @return string response body
+ *
+ * @todo rename to code-standard httpPost()
+ */
+
+ protected function http_post($url, $data)
+ {
+ $request = HTTPClient::start();
+ $response = $request->post($url, null, $data);
+ return $response->getBody();
+ }
+
+ // Hook handlers
+
+ /**
+ * Called when all plugins have been initialized
+ *
+ * @return boolean hook value
+ */
+
+ function onInitializePlugin()
+ {
+ if (!isset($this->shortenerName)) {
+ throw new Exception("must specify a shortenerName");
+ }
+ return true;
+ }
+
+ /**
+ * Called when a showing the URL shortener drop-down box
+ *
+ * Properties of the shortening service currently only
+ * include whether it's a free service.
+ *
+ * @param array &$shorteners array mapping shortener name to properties
+ *
+ * @return boolean hook value
+ */
+
+ function onGetUrlShorteners(&$shorteners)
+ {
+ $shorteners[$this->shortenerName] =
+ array('freeService' => $this->freeService);
+ return true;
+ }
+
+ /**
+ * Called to shorten an URL
+ *
+ * @param string $url URL to shorten
+ * @param string $shortenerName Shortening service. Don't handle if it's
+ * not you!
+ * @param string &$shortenedUrl URL after shortening; out param.
+ *
+ * @return boolean hook value
+ */
+
+ function onStartShortenUrl($url, $shortenerName, &$shortenedUrl)
+ {
+ if ($shortenerName == $this->shortenerName) {
+ $result = $this->shorten($url);
+ if (isset($result) && $result != null && $result !== false) {
+ $shortenedUrl = $result;
+ common_log(LOG_INFO,
+ __CLASS__ . ": $this->shortenerName ".
+ "shortened $url to $shortenedUrl");
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/lib/util.php b/lib/util.php
index dc853f657..b20ed8225 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -145,7 +145,6 @@ function common_switch_locale($language=null)
textdomain("statusnet");
}
-
function common_timezone()
{
if (common_logged_in()) {
@@ -158,22 +157,38 @@ function common_timezone()
return common_config('site', 'timezone');
}
+function common_valid_language($lang)
+{
+ if ($lang) {
+ // Validate -- we don't want to end up with a bogus code
+ // left over from some old junk.
+ foreach (common_config('site', 'languages') as $code => $info) {
+ if ($info['lang'] == $lang) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
function common_language()
{
+ // Allow ?uselang=xx override, very useful for debugging
+ // and helping translators check usage and context.
+ if (isset($_GET['uselang'])) {
+ $uselang = strval($_GET['uselang']);
+ if (common_valid_language($uselang)) {
+ return $uselang;
+ }
+ }
+
// If there is a user logged in and they've set a language preference
// then return that one...
if (_have_config() && common_logged_in()) {
$user = common_current_user();
- $user_language = $user->language;
-
- if ($user->language) {
- // Validate -- we don't want to end up with a bogus code
- // left over from some old junk.
- foreach (common_config('site', 'languages') as $code => $info) {
- if ($info['lang'] == $user_language) {
- return $user_language;
- }
- }
+
+ if (common_valid_language($user->language)) {
+ return $user->language;
}
}
@@ -901,9 +916,21 @@ function common_linkify($url) {
function common_shorten_links($text, $always = false)
{
- $maxLength = Notice::maxContent();
- if (!$always && ($maxLength == 0 || mb_strlen($text) <= $maxLength)) return $text;
- return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
+ common_debug("common_shorten_links() called");
+
+ $user = common_current_user();
+
+ $maxLength = User_urlshortener_prefs::maxNoticeLength($user);
+
+ common_debug("maxLength = $maxLength");
+
+ if ($always || mb_strlen($text) > $maxLength) {
+ common_debug("Forcing shortening");
+ return common_replace_urls_callback($text, array('File_redirection', 'forceShort'));
+ } else {
+ common_debug("Not forcing shortening");
+ return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
+ }
}
function common_xml_safe_str($str)
@@ -1249,14 +1276,8 @@ function common_redirect($url, $code=307)
exit;
}
-function common_broadcast_notice($notice, $remote=false)
-{
- // DO NOTHING!
-}
+// Stick the notice on the queue
-/**
- * Stick the notice on the queue.
- */
function common_enqueue_notice($notice)
{
static $localTransports = array('omb',
@@ -1270,18 +1291,9 @@ function common_enqueue_notice($notice)
$transports[] = 'plugin';
}
- $xmpp = common_config('xmpp', 'enabled');
-
- if ($xmpp) {
- $transports[] = 'jabber';
- }
-
// We can skip these for gatewayed notices.
if ($notice->isLocal()) {
$transports = array_merge($transports, $localTransports);
- if ($xmpp) {
- $transports[] = 'public';
- }
}
if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) {
@@ -1813,21 +1825,6 @@ function common_session_token()
return $_SESSION['token'];
}
-function common_cache_key($extra)
-{
- return Cache::key($extra);
-}
-
-function common_keyize($str)
-{
- return Cache::keyize($str);
-}
-
-function common_memcache()
-{
- return Cache::instance();
-}
-
function common_license_terms($uri)
{
if(preg_match('/creativecommons.org\/licenses\/([^\/]+)/', $uri, $matches)) {
@@ -1868,30 +1865,42 @@ function common_database_tablename($tablename)
/**
* Shorten a URL with the current user's configured shortening service,
* or ur1.ca if configured, or not at all if no shortening is set up.
- * Length is not considered.
*
- * @param string $long_url
+ * @param string $long_url original URL
+ * @param boolean $force Force shortening (used when notice is too long)
+ *
* @return string may return the original URL if shortening failed
*
* @fixme provide a way to specify a particular shortener
* @fixme provide a way to specify to use a given user's shortening preferences
*/
-function common_shorten_url($long_url)
+
+function common_shorten_url($long_url, $force = false)
{
+ common_debug("Shortening URL '$long_url' (force = $force)");
+
$long_url = trim($long_url);
+
$user = common_current_user();
- if (empty($user)) {
- // common current user does not find a user when called from the XMPP daemon
- // therefore we'll set one here fix, so that XMPP given URLs may be shortened
- $shortenerName = 'ur1.ca';
- } else {
- $shortenerName = $user->urlshorteningservice;
+
+ $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user);
+ common_debug("maxUrlLength = $maxUrlLength");
+
+ // $force forces shortening even if it's not strictly needed
+
+ if (mb_strlen($long_url) < $maxUrlLength && !$force) {
+ common_debug("Skipped shortening URL.");
+ return $long_url;
}
- if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){
+ $shortenerName = User_urlshortener_prefs::urlShorteningService($user);
+
+ common_debug("Shortener name = '$shortenerName'");
+
+ if (Event::handle('StartShortenUrl', array($long_url, $shortenerName, &$shortenedUrl))) {
//URL wasn't shortened, so return the long url
return $long_url;
- }else{
+ } else {
//URL was shortened, so return the result
return trim($shortenedUrl);
}
diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php
deleted file mode 100644
index 829eaa36c..000000000
--- a/lib/xmppmanager.php
+++ /dev/null
@@ -1,486 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
-
-/**
- * XMPP background connection manager for XMPP-using queue handlers,
- * allowing them to send outgoing messages on the right connection.
- *
- * Input is handled during socket select loop, keepalive pings during idle.
- * Any incoming messages will be forwarded to the main XmppDaemon process,
- * which handles direct user interaction.
- *
- * In a multi-site queuedaemon.php run, one connection will be instantiated
- * for each site being handled by the current process that has XMPP enabled.
- */
-
-class XmppManager extends IoManager
-{
- protected $site = null;
- protected $pingid = 0;
- protected $lastping = null;
- protected $conn = null;
-
- static protected $singletons = array();
-
- const PING_INTERVAL = 120;
-
- /**
- * Fetch the singleton XmppManager for the current site.
- * @return mixed XmppManager, or false if unneeded
- */
- public static function get()
- {
- if (common_config('xmpp', 'enabled')) {
- $site = StatusNet::currentSite();
- if (empty(self::$singletons[$site])) {
- self::$singletons[$site] = new XmppManager();
- }
- return self::$singletons[$site];
- } else {
- return false;
- }
- }
-
- /**
- * Tell the i/o master we need one instance for each supporting site
- * being handled in this process.
- */
- public static function multiSite()
- {
- return IoManager::INSTANCE_PER_SITE;
- }
-
- function __construct()
- {
- $this->site = StatusNet::currentSite();
- $this->resource = common_config('xmpp', 'resource') . 'daemon';
- }
-
- /**
- * Initialize connection to server.
- * @return boolean true on success
- */
- public function start($master)
- {
- parent::start($master);
- $this->switchSite();
-
- require_once INSTALLDIR . "/lib/jabber.php";
-
- # Low priority; we don't want to receive messages
-
- common_log(LOG_INFO, "INITIALIZE");
- $this->conn = jabber_connect($this->resource);
-
- if (empty($this->conn)) {
- common_log(LOG_ERR, "Couldn't connect to server.");
- return false;
- }
-
- $this->log(LOG_DEBUG, "Initializing stanza handlers.");
-
- $this->conn->addEventHandler('message', 'handle_message', $this);
- $this->conn->addEventHandler('presence', 'handle_presence', $this);
- $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
-
- $this->conn->setReconnectTimeout(600);
- jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100);
-
- return !is_null($this->conn);
- }
-
- /**
- * Message pump is triggered on socket input, so we only need an idle()
- * call often enough to trigger our outgoing pings.
- */
- function timeout()
- {
- return self::PING_INTERVAL;
- }
-
- /**
- * Lists the XMPP connection socket to allow i/o master to wake
- * when input comes in here as well as from the queue source.
- *
- * @return array of resources
- */
- public function getSockets()
- {
- if ($this->conn) {
- return array($this->conn->getSocket());
- } else {
- return array();
- }
- }
-
- /**
- * Process XMPP events that have come in over the wire.
- * Side effects: may switch site configuration
- * @fixme may kill process on XMPP error
- * @param resource $socket
- */
- public function handleInput($socket)
- {
- $this->switchSite();
-
- # Process the queue for as long as needed
- try {
- if ($this->conn) {
- assert($socket === $this->conn->getSocket());
-
- common_log(LOG_DEBUG, "Servicing the XMPP queue.");
- $this->stats('xmpp_process');
- $this->conn->processTime(0);
- }
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
- die($e->getMessage());
- }
- }
-
- /**
- * Idle processing for io manager's execution loop.
- * Send keepalive pings to server.
- *
- * Side effect: kills process on exception from XMPP library.
- *
- * @fixme non-dying error handling
- */
- public function idle($timeout=0)
- {
- if ($this->conn) {
- $now = time();
- if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) {
- $this->switchSite();
- try {
- $this->sendPing();
- $this->lastping = $now;
- } catch (XMPPHP_Exception $e) {
- common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
- die($e->getMessage());
- }
- }
- }
- }
-
- /**
- * For queue handlers to pass us a message to push out,
- * if we're active.
- *
- * @fixme should this be blocking etc?
- *
- * @param string $msg XML stanza to send
- * @return boolean success
- */
- public function send($msg)
- {
- if ($this->conn && !$this->conn->isDisconnected()) {
- $bytes = $this->conn->send($msg);
- if ($bytes > 0) {
- $this->conn->processTime(0);
- return true;
- } else {
- return false;
- }
- } else {
- // Can't send right now...
- return false;
- }
- }
-
- /**
- * Send a keepalive ping to the XMPP server.
- */
- protected function sendPing()
- {
- $jid = jabber_daemon_address().'/'.$this->resource;
- $server = common_config('xmpp', 'server');
-
- if (!isset($this->pingid)) {
- $this->pingid = 0;
- } else {
- $this->pingid++;
- }
-
- common_log(LOG_DEBUG, "Sending ping #{$this->pingid}");
-
- $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
- }
-
- /**
- * Callback for Jabber reconnect event
- * @param $pl
- */
- function handle_reconnect(&$pl)
- {
- common_log(LOG_NOTICE, 'XMPP reconnected');
-
- $this->conn->processUntil('session_start');
- $this->conn->presence(null, 'available', null, 'available', 100);
- }
-
-
- function get_user($from)
- {
- $user = User::staticGet('jabber', jabber_normalize_jid($from));
- return $user;
- }
-
- /**
- * XMPP callback for handling message input...
- * @param array $pl XMPP payload
- */
- function handle_message(&$pl)
- {
- $from = jabber_normalize_jid($pl['from']);
-
- if ($pl['type'] != 'chat') {
- $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from: " . $pl['xml']->toString());
- return;
- }
-
- if (mb_strlen($pl['body']) == 0) {
- $this->log(LOG_WARNING, "Ignoring message with empty body from $from: " . $pl['xml']->toString());
- return;
- }
-
- // Forwarded from another daemon for us to handle; this shouldn't
- // happen any more but we might get some legacy items.
- if ($this->is_self($from)) {
- $this->log(LOG_INFO, "Got forwarded notice from self ($from).");
- $from = $this->get_ofrom($pl);
- $this->log(LOG_INFO, "Originally sent by $from.");
- if (is_null($from) || $this->is_self($from)) {
- $this->log(LOG_INFO, "Ignoring notice originally sent by $from.");
- return;
- }
- }
-
- $user = $this->get_user($from);
-
- // For common_current_user to work
- global $_cur;
- $_cur = $user;
-
- if (!$user) {
- $this->from_site($from, 'Unknown user; go to ' .
- common_local_url('imsettings') .
- ' to add your address to your account');
- $this->log(LOG_WARNING, 'Message from unknown user ' . $from);
- return;
- }
- if ($this->handle_command($user, $pl['body'])) {
- $this->log(LOG_INFO, "Command message by $from handled.");
- return;
- } else if ($this->is_autoreply($pl['body'])) {
- $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from);
- return;
- } else if ($this->is_otr($pl['body'])) {
- $this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
- return;
- } else {
-
- $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
-
- $this->add_notice($user, $pl);
- }
-
- $user->free();
- unset($user);
- unset($_cur);
-
- unset($pl['xml']);
- $pl['xml'] = null;
-
- $pl = null;
- unset($pl);
- }
-
-
- function is_self($from)
- {
- return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from));
- }
-
- function get_ofrom($pl)
- {
- $xml = $pl['xml'];
- $addresses = $xml->sub('addresses');
- if (!$addresses) {
- $this->log(LOG_WARNING, 'Forwarded message without addresses');
- return null;
- }
- $address = $addresses->sub('address');
- if (!$address) {
- $this->log(LOG_WARNING, 'Forwarded message without address');
- return null;
- }
- if (!array_key_exists('type', $address->attrs)) {
- $this->log(LOG_WARNING, 'No type for forwarded message');
- return null;
- }
- $type = $address->attrs['type'];
- if ($type != 'ofrom') {
- $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom');
- return null;
- }
- if (!array_key_exists('jid', $address->attrs)) {
- $this->log(LOG_WARNING, 'No jid for forwarded message');
- return null;
- }
- $jid = $address->attrs['jid'];
- if (!$jid) {
- $this->log(LOG_WARNING, 'Could not get jid from address');
- return null;
- }
- $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid);
- return $jid;
- }
-
- function is_autoreply($txt)
- {
- if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
- return true;
- } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
- return true;
- } else {
- return false;
- }
- }
-
- function is_otr($txt)
- {
- if (preg_match('/^\?OTR/', $txt)) {
- return true;
- } else {
- return false;
- }
- }
-
- function from_site($address, $msg)
- {
- $text = '['.common_config('site', 'name') . '] ' . $msg;
- jabber_send_message($address, $text);
- }
-
- function handle_command($user, $body)
- {
- $inter = new CommandInterpreter();
- $cmd = $inter->handle_command($user, $body);
- if ($cmd) {
- $chan = new XMPPChannel($this->conn);
- $cmd->execute($chan);
- return true;
- } else {
- return false;
- }
- }
-
- function add_notice(&$user, &$pl)
- {
- $body = trim($pl['body']);
- $content_shortened = common_shorten_links($body);
- if (Notice::contentTooLong($content_shortened)) {
- $from = jabber_normalize_jid($pl['from']);
- $this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'),
- Notice::maxContent(),
- mb_strlen($content_shortened)));
- return;
- }
-
- try {
- $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
- } catch (Exception $e) {
- $this->log(LOG_ERR, $e->getMessage());
- $this->from_site($user->jabber, $e->getMessage());
- return;
- }
-
- common_broadcast_notice($notice);
- $this->log(LOG_INFO,
- 'Added notice ' . $notice->id . ' from user ' . $user->nickname);
- $notice->free();
- unset($notice);
- }
-
- function handle_presence(&$pl)
- {
- $from = jabber_normalize_jid($pl['from']);
- switch ($pl['type']) {
- case 'subscribe':
- # We let anyone subscribe
- $this->subscribed($from);
- $this->log(LOG_INFO,
- 'Accepted subscription from ' . $from);
- break;
- case 'subscribed':
- case 'unsubscribed':
- case 'unsubscribe':
- $this->log(LOG_INFO,
- 'Ignoring "' . $pl['type'] . '" from ' . $from);
- break;
- default:
- if (!$pl['type']) {
- $user = User::staticGet('jabber', $from);
- if (!$user) {
- $this->log(LOG_WARNING, 'Presence from unknown user ' . $from);
- return;
- }
- if ($user->updatefrompresence) {
- $this->log(LOG_INFO, 'Updating ' . $user->nickname .
- ' status from presence.');
- $this->add_notice($user, $pl);
- }
- $user->free();
- unset($user);
- }
- break;
- }
- unset($pl['xml']);
- $pl['xml'] = null;
-
- $pl = null;
- unset($pl);
- }
-
- function log($level, $msg)
- {
- $text = 'XMPPDaemon('.$this->resource.'): '.$msg;
- common_log($level, $text);
- }
-
- function subscribed($to)
- {
- jabber_special_presence('subscribed', $to);
- }
-
- /**
- * Make sure we're on the right site configuration
- */
- protected function switchSite()
- {
- if ($this->site != StatusNet::currentSite()) {
- common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site");
- $this->stats('switch');
- StatusNet::switchSite($this->site);
- }
- }
-}
diff --git a/lib/xmppoutqueuehandler.php b/lib/xmppoutqueuehandler.php
deleted file mode 100644
index 2afa260f1..000000000
--- a/lib/xmppoutqueuehandler.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, 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/>.
- */
-
-/**
- * Queue handler for pre-processed outgoing XMPP messages.
- * Formatted XML stanzas will have been pushed into the queue
- * via the Queued_XMPP connection proxy, probably from some
- * other queue processor.
- *
- * Here, the XML stanzas are simply pulled out of the queue and
- * pushed out over the wire; an XmppManager is needed to set up
- * and maintain the actual server connection.
- *
- * This queue will be run via XmppDaemon rather than QueueDaemon.
- *
- * @author Brion Vibber <brion@status.net>
- */
-class XmppOutQueueHandler extends QueueHandler
-{
- function transport() {
- return 'xmppout';
- }
-
- /**
- * Take a previously-queued XMPP stanza and send it out ot the server.
- * @param string $msg
- * @return boolean true on success
- */
- function handle($msg)
- {
- assert(is_string($msg));
-
- $xmpp = XmppManager::get();
- $ok = $xmpp->send($msg);
-
- return $ok;
- }
-}
-
diff --git a/lib/xrd.php b/lib/xrd.php
new file mode 100644
index 000000000..c8cffed9c
--- /dev/null
+++ b/lib/xrd.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * A sample module to show best practices for StatusNet plugins
+ *
+ * PHP version 5
+ *
+ * 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/>.
+ *
+ * @package StatusNet
+ * @author James Walker <james@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class XRD
+{
+ const XML_NS = 'http://www.w3.org/2000/xmlns/';
+
+ const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0';
+
+ const HOST_META_NS = 'http://host-meta.net/xrd/1.0';
+
+ public $expires;
+
+ public $subject;
+
+ public $host;
+
+ public $alias = array();
+
+ public $types = array();
+
+ public $links = array();
+
+ public static function parse($xml)
+ {
+ $xrd = new XRD();
+
+ $dom = new DOMDocument();
+
+ // Don't spew XML warnings to output
+ $old = error_reporting();
+ error_reporting($old & ~E_WARNING);
+ $ok = $dom->loadXML($xml);
+ error_reporting($old);
+
+ if (!$ok) {
+ // TRANS: Exception.
+ throw new Exception(_m('Invalid XML.'));
+ }
+ $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
+ if (!$xrd_element) {
+ // TRANS: Exception.
+ throw new Exception(_m('Invalid XML, missing XRD root.'));
+ }
+
+ // Check for host-meta host
+ $host = $xrd_element->getElementsByTagName('Host')->item(0);
+ if ($host) {
+ $xrd->host = $host->nodeValue;
+ }
+
+ // Loop through other elements
+ foreach ($xrd_element->childNodes as $node) {
+ if (!($node instanceof DOMElement)) {
+ continue;
+ }
+ switch ($node->tagName) {
+ case 'Expires':
+ $xrd->expires = $node->nodeValue;
+ break;
+ case 'Subject':
+ $xrd->subject = $node->nodeValue;
+ break;
+
+ case 'Alias':
+ $xrd->alias[] = $node->nodeValue;
+ break;
+
+ case 'Link':
+ $xrd->links[] = $xrd->parseLink($node);
+ break;
+
+ case 'Type':
+ $xrd->types[] = $xrd->parseType($node);
+ break;
+
+ }
+ }
+ return $xrd;
+ }
+
+ public function toXML()
+ {
+ $xs = new XMLStringer();
+
+ $xs->startXML();
+ $xs->elementStart('XRD', array('xmlns' => XRD::XRD_NS));
+
+ if ($this->host) {
+ $xs->element('hm:Host', array('xmlns:hm' => XRD::HOST_META_NS), $this->host);
+ }
+
+ if ($this->expires) {
+ $xs->element('Expires', null, $this->expires);
+ }
+
+ if ($this->subject) {
+ $xs->element('Subject', null, $this->subject);
+ }
+
+ foreach ($this->alias as $alias) {
+ $xs->element('Alias', null, $alias);
+ }
+
+ foreach ($this->links as $link) {
+ $titles = array();
+ if (isset($link['title'])) {
+ $titles = $link['title'];
+ unset($link['title']);
+ }
+ $xs->elementStart('Link', $link);
+ foreach ($titles as $title) {
+ $xs->element('Title', null, $title);
+ }
+ $xs->elementEnd('Link');
+ }
+
+ $xs->elementEnd('XRD');
+
+ return $xs->getString();
+ }
+
+ function parseType($element)
+ {
+ return array();
+ }
+
+ function parseLink($element)
+ {
+ $link = array();
+ $link['rel'] = $element->getAttribute('rel');
+ $link['type'] = $element->getAttribute('type');
+ $link['href'] = $element->getAttribute('href');
+ $link['template'] = $element->getAttribute('template');
+ foreach ($element->childNodes as $node) {
+ if ($node instanceof DOMElement) {
+ switch($node->tagName) {
+ case 'Title':
+ $link['title'][] = $node->nodeValue;
+ }
+ }
+ }
+
+ return $link;
+ }
+}