From 77ea02cac3576f395e4548e7e6cbace90ba566ea Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 20 Jan 2010 16:31:48 -0500 Subject: Any object (not just Notice's) can be queued --- plugins/Enjit/enjitqueuehandler.php | 9 +---- plugins/Facebook/facebookqueuehandler.php | 2 +- plugins/RSSCloud/RSSCloudPlugin.php | 41 ++++------------------ plugins/RSSCloud/RSSCloudQueueHandler.php | 50 +++------------------------ plugins/TwitterBridge/twitterqueuehandler.php | 2 +- 5 files changed, 14 insertions(+), 90 deletions(-) mode change 100755 => 100644 plugins/RSSCloud/RSSCloudQueueHandler.php (limited to 'plugins') diff --git a/plugins/Enjit/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php index f0e706b92..14085cc5e 100644 --- a/plugins/Enjit/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -32,14 +32,7 @@ class EnjitQueueHandler extends QueueHandler return 'enjit'; } - function start() - { - $this->log(LOG_INFO, "Starting EnjitQueueHandler"); - $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = Profile::staticGet($notice->profile_id); diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 1778690e5..524af7bc4 100644 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -28,7 +28,7 @@ class FacebookQueueHandler extends QueueHandler return 'facebook'; } - function handle_notice($notice) + function handle($notice) { if ($this->_isLocal($notice)) { return facebookBroadcastNotice($notice); diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php index 2de162628..9f444c8bb 100644 --- a/plugins/RSSCloud/RSSCloudPlugin.php +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -138,6 +138,9 @@ class RSSCloudPlugin extends Plugin case 'RSSCloudNotifier': include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php'; return false; + case 'RSSCloudQueueHandler': + include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudQueueHandler.php'; + return false; case 'RSSCloudRequestNotifyAction': case 'LoggingAggregatorAction': include_once INSTALLDIR . '/plugins/RSSCloud/' . @@ -193,32 +196,6 @@ class RSSCloudPlugin extends Plugin return true; } - /** - * broadcast the message when not using queuehandler - * - * @param Notice &$notice the notice - * @param array $queue destination queue - * - * @return boolean hook return - */ - - function onUnqueueHandleNotice(&$notice, $queue) - { - if (($queue == 'rsscloud') && ($this->_isLocal($notice))) { - - common_debug('broadcasting rssCloud bound notice ' . $notice->id); - - $profile = $notice->getProfile(); - - $notifier = new RSSCloudNotifier(); - $notifier->notify($profile); - - return false; - } - - return true; - } - /** * Determine whether the notice was locally created * @@ -261,19 +238,15 @@ class RSSCloudPlugin extends Plugin } /** - * Add RSSCloudQueueHandler to the list of valid daemons to - * start + * Register RSSCloud notice queue handler * - * @param array $daemons the list of daemons to run + * @param QueueManager $manager * * @return boolean hook return - * */ - - function onGetValidDaemons($daemons) + function onEndInitializeQueueManager($manager) { - array_push($daemons, INSTALLDIR . - '/plugins/RSSCloud/RSSCloudQueueHandler.php'); + $manager->connect('rsscloud', 'RSSCloudQueueHandler'); return true; } diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php old mode 100755 new mode 100644 index 693dd27c1..295c26189 --- a/plugins/RSSCloud/RSSCloudQueueHandler.php +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - $this->notifier = new RSSCloudNotifier(); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = $notice->getProfile(); - return $this->notifier->notify($profile); - } - - function finish() - { + $notifier = new RSSCloudNotifier(); + return $notifier->notify($profile); } - -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; } -$handler = new RSSCloudQueueHandler($id); - -$handler->runOnce(); diff --git a/plugins/TwitterBridge/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php index 5089ca7b7..b5a624e83 100644 --- a/plugins/TwitterBridge/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -28,7 +28,7 @@ class TwitterQueueHandler extends QueueHandler return 'twitter'; } - function handle_notice($notice) + function handle($notice) { return broadcast_twitter($notice); } -- cgit v1.2.3-54-g00ecf From 78eb9c78a781ba8d6929a260e5f9c07714d59ee3 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 17:19:32 -0500 Subject: Will re-enable anything queueing after 0.9.x merge Revert "Any object (not just Notice's) can be queued" This reverts commit 77ea02cac3576f395e4548e7e6cbace90ba566ea. --- classes/Queue_item.php | 13 +++- classes/statusnet.ini | 6 +- db/statusnet.sql | 5 +- lib/dbqueuemanager.php | 95 ++++++++++++++++++--------- lib/jabberqueuehandler.php | 4 +- lib/ombqueuehandler.php | 2 +- lib/pingqueuehandler.php | 2 +- lib/pluginqueuehandler.php | 2 +- lib/publicqueuehandler.php | 6 +- lib/queuehandler.php | 95 ++++++++++++++++++++++++--- lib/smsqueuehandler.php | 2 +- lib/stompqueuemanager.php | 38 +++++++---- lib/xmppmanager.php | 24 ------- plugins/Enjit/enjitqueuehandler.php | 9 ++- plugins/Facebook/facebookqueuehandler.php | 2 +- plugins/RSSCloud/RSSCloudPlugin.php | 41 ++++++++++-- plugins/RSSCloud/RSSCloudQueueHandler.php | 50 ++++++++++++-- plugins/TwitterBridge/twitterqueuehandler.php | 2 +- scripts/handlequeued.php | 2 +- 19 files changed, 291 insertions(+), 109 deletions(-) mode change 100644 => 100755 plugins/RSSCloud/RSSCloudQueueHandler.php (limited to 'plugins') diff --git a/classes/Queue_item.php b/classes/Queue_item.php index 4d90e1d23..cf805a606 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -10,8 +10,7 @@ class Queue_item extends Memcached_DataObject /* the code below is auto generated do not remove the above tag */ public $__table = 'queue_item'; // table name - public $id; // int(4) primary_key not_null - public $frame; // blob not_null + public $notice_id; // int(4) primary_key not_null public $transport; // varchar(8) primary_key not_null public $created; // datetime() not_null public $claimed; // datetime() @@ -23,6 +22,9 @@ class Queue_item extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + function sequenceKey() + { return array(false, false); } + static function top($transport=null) { $qi = new Queue_item(); @@ -40,7 +42,7 @@ class Queue_item extends Memcached_DataObject # XXX: potential race condition # can we force it to only update if claimed is still null # (or old)? - common_log(LOG_INFO, 'claiming queue item id=' . $qi->id . + common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $qi->transport); $orig = clone($qi); $qi->claimed = common_sql_now(); @@ -55,4 +57,9 @@ class Queue_item extends Memcached_DataObject $qi = null; return null; } + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Queue_item', $kv); + } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6203650a6..44088cf6b 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -428,14 +428,14 @@ tagged = K tag = K [queue_item] -id = 129 -frame = 66 +notice_id = 129 transport = 130 created = 142 claimed = 14 [queue_item__keys] -id = K +notice_id = K +transport = K [related_group] group_id = 129 diff --git a/db/statusnet.sql b/db/statusnet.sql index cb7dad3e2..2a9ab74c7 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -274,12 +274,13 @@ create table remember_me ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table queue_item ( - id integer auto_increment primary key comment 'unique identifier', - frame blob not null comment 'serialized object', + + notice_id integer not null comment 'notice queued' references notice (id), transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', + constraint primary key (notice_id, transport), index queue_item_created_idx (created) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 139f50234..889365b64 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -31,17 +31,19 @@ class DBQueueManager extends QueueManager { /** - * Saves an object into the queue item table. + * Saves a notice object reference into the queue item table. * @return boolean true on success * @throws ServerException on failure */ public function enqueue($object, $queue) { + $notice = $object; + $qi = new Queue_item(); - $qi->frame = serialize($object); + $qi->notice_id = $notice->id; $qi->transport = $queue; - $qi->created = common_sql_now(); + $qi->created = $notice->created; $result = $qi->insert(); if (!$result) { @@ -71,35 +73,34 @@ class DBQueueManager extends QueueManager */ public function poll() { - $this->_log(LOG_DEBUG, 'Checking for queued objects...'); - $qi = $this->_nextItem(); - if ($qi === false) { - $this->_log(LOG_DEBUG, 'No queue items waiting; idling.'); + $this->_log(LOG_DEBUG, 'Checking for notices...'); + $item = $this->_nextItem(); + if ($item === false) { + $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); return false; } - if ($qi === true) { - // We dequeued an entry for a deleted or invalid object. + if ($item === true) { + // We dequeued an entry for a deleted or invalid notice. // Consider it a hit for poll rate purposes. return true; } - $queue = $qi->transport; - $object = unserialize($qi->frame); - $this->_log(LOG_INFO, 'Got item id=' . $qi->id . ' for transport ' . $queue); + list($queue, $notice) = $item; + $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue); // Yay! Got one! $handler = $this->getHandler($queue); if ($handler) { - if ($handler->handle($object)) { - $this->_log(LOG_INFO, "[$queue] Successfully handled object"); - $this->_done($qi); + if ($handler->handle_notice($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice"); + $this->_done($notice, $queue); } else { - $this->_log(LOG_INFO, "[$queue] Failed to handle object"); - $this->_fail($qi); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice"); + $this->_fail($notice, $queue); } } else { - $this->_log(LOG_INFO, "[$queue] No handler for queue $queue; discarding."); - $this->_done($qi); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue; discarding."); + $this->_done($notice, $queue); } return true; } @@ -107,7 +108,8 @@ class DBQueueManager extends QueueManager /** * Pop the oldest unclaimed item off the queue set and claim it. * - * @return mixed false if no items; true if bogus hit; otherwise Queue_item + * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice) + * giving the queue transport name. */ protected function _nextItem() { @@ -119,42 +121,70 @@ class DBQueueManager extends QueueManager return false; } - return $qi; + $queue = $qi->transport; + $notice = Notice::staticGet('id', $qi->notice_id); + if (empty($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice"); + $qi->delete(); + return true; + } + + $result = $notice; + return array($queue, $notice); } /** * Delete our claimed item from the queue after successful processing. * - * @param QueueItem $qi + * @param Notice $object + * @param string $queue */ - protected function _done($qi) + protected function _done($object, $queue) { + // XXX: right now, we only handle notices + + $notice = $object; + + $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, + 'transport' => $queue)); + if (empty($qi)) { - $this->_log(LOG_INFO, "_done passed an empty queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item"); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item"); } $qi->delete(); $qi->free(); } - $this->_log(LOG_INFO, "done with item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item"); + $this->stats('handled', $queue); + + $notice->free(); } /** * Free our claimed queue item for later reprocessing in case of * temporary failure. * - * @param QueueItem $qi + * @param Notice $object + * @param string $queue */ - protected function _fail($qi) + protected function _fail($object, $queue) { + // XXX: right now, we only handle notices + + $notice = $object; + + $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, + 'transport' => $queue)); + if (empty($qi)) { - $this->_log(LOG_INFO, "_fail passed an empty queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "Ignoring failure for unclaimed queue item"); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item"); } else { $orig = clone($qi); $qi->claimed = null; @@ -163,7 +193,10 @@ class DBQueueManager extends QueueManager } } - $this->_log(LOG_INFO, "done with queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item"); + $this->stats('error', $queue); + + $notice->free(); } protected function _log($level, $msg) diff --git a/lib/jabberqueuehandler.php b/lib/jabberqueuehandler.php index 83471f2df..b1518866d 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/jabberqueuehandler.php @@ -34,14 +34,14 @@ class JabberQueueHandler extends QueueHandler return 'jabber'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_broadcast_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; + exit(1); } } } diff --git a/lib/ombqueuehandler.php b/lib/ombqueuehandler.php index 24896c784..3ffc1313b 100644 --- a/lib/ombqueuehandler.php +++ b/lib/ombqueuehandler.php @@ -36,7 +36,7 @@ class OmbQueueHandler extends QueueHandler * @fixme doesn't currently report failure back to the queue manager * because omb_broadcast_notice() doesn't report it to us */ - function handle($notice) + function handle_notice($notice) { if ($this->is_remote($notice)) { $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); diff --git a/lib/pingqueuehandler.php b/lib/pingqueuehandler.php index 4e4d74cb1..8bb218078 100644 --- a/lib/pingqueuehandler.php +++ b/lib/pingqueuehandler.php @@ -30,7 +30,7 @@ class PingQueueHandler extends QueueHandler { return 'ping'; } - function handle($notice) { + function handle_notice($notice) { require_once INSTALLDIR . '/lib/ping.php'; return ping_broadcast_notice($notice); } diff --git a/lib/pluginqueuehandler.php b/lib/pluginqueuehandler.php index 9653ccad4..24d504699 100644 --- a/lib/pluginqueuehandler.php +++ b/lib/pluginqueuehandler.php @@ -42,7 +42,7 @@ class PluginQueueHandler extends QueueHandler return 'plugin'; } - function handle($notice) + function handle_notice($notice) { Event::handle('HandleQueuedNotice', array(&$notice)); return true; diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php index c9edb8d5d..9ea9ee73a 100644 --- a/lib/publicqueuehandler.php +++ b/lib/publicqueuehandler.php @@ -23,6 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * Queue handler for pushing new notices to public XMPP subscribers. + * @fixme correct this exception handling */ class PublicQueueHandler extends QueueHandler { @@ -32,14 +33,15 @@ class PublicQueueHandler extends QueueHandler return 'public'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_public_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; + die($e->getMessage()); } + return true; } } diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 2909cd83b..613be6e33 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -22,20 +22,51 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** * Base class for queue handlers. * - * As of 0.9, queue handlers are short-lived for items as they are - * dequeued by a QueueManager running in an IoMaster in a daemon - * such as queuedaemon.php. - * - * Extensions requiring long-running maintenance or polling should - * register an IoManager. + * As extensions of the Daemon class, each queue handler has the ability + * to launch itself in the background, at which point it'll pass control + * to the configured QueueManager class to poll for updates. * * Subclasses must override at least the following methods: * - transport - * - handle + * - handle_notice */ +#class QueueHandler extends Daemon class QueueHandler { +# function __construct($id=null, $daemonize=true) +# { +# parent::__construct($daemonize); +# +# if ($id) { +# $this->set_id($id); +# } +# } + + /** + * How many seconds a polling-based queue manager should wait between + * checks for new items to handle. + * + * Defaults to 60 seconds; override to speed up or slow down. + * + * @fixme not really compatible with global queue manager + * @return int timeout in seconds + */ +# function timeout() +# { +# return 60; +# } + +# function class_name() +# { +# return ucfirst($this->transport()) . 'Handler'; +# } + +# function name() +# { +# return strtolower($this->class_name().'.'.$this->get_id()); +# } + /** * Return transport keyword which identifies items this queue handler * services; must be defined for all subclasses. @@ -52,17 +83,61 @@ class QueueHandler /** * Here's the meat of your queue handler -- you're handed a Notice - * or other object, which you may do as you will with. + * object, which you may do as you will with. * * If this function indicates failure, a warning will be logged * and the item is placed back in the queue to be re-run. * - * @param mixed $object + * @param Notice $notice + * @return boolean true on success, false on failure + */ + function handle_notice($notice) + { + return true; + } + + /** + * Setup and start of run loop for this queue handler as a daemon. + * Most of the heavy lifting is passed on to the QueueManager's service() + * method, which passes control back to our handle_notice() method for + * each notice that comes in on the queue. + * + * Most of the time this won't need to be overridden in a subclass. + * * @return boolean true on success, false on failure */ - function handle($object) + function run() { + if (!$this->start()) { + $this->log(LOG_WARNING, 'failed to start'); + return false; + } + + $this->log(LOG_INFO, 'checking for queued notices'); + + $queue = $this->transport(); + $timeout = $this->timeout(); + + $qm = QueueManager::get(); + + $qm->service($queue, $this); + + $this->log(LOG_INFO, 'finished servicing the queue'); + + if (!$this->finish()) { + $this->log(LOG_WARNING, 'failed to clean up'); + return false; + } + + $this->log(LOG_INFO, 'terminating normally'); + return true; } + + + function log($level, $msg) + { + common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); + } } diff --git a/lib/smsqueuehandler.php b/lib/smsqueuehandler.php index 6085d2b4a..48a96409d 100644 --- a/lib/smsqueuehandler.php +++ b/lib/smsqueuehandler.php @@ -31,7 +31,7 @@ class SmsQueueHandler extends QueueHandler return 'sms'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/mail.php'); return mail_broadcast_notice_sms($notice); diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 6496b5cf1..00590fdb6 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -125,25 +125,28 @@ class StompQueueManager extends QueueManager } /** - * Saves an object into the queue item table. + * Saves a notice object reference into the queue item table. * @return boolean true on success */ public function enqueue($object, $queue) { - $msg = serialize($object); + $notice = $object; $this->_connect(); + // XXX: serialize and send entire notice + $result = $this->con->send($this->queueName($queue), - $msg, // BODY of the message - array ('created' => $timestamp)); + $notice->id, // BODY of the message + array ('created' => $notice->created)); if (!$result) { common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); return false; } - common_log(LOG_DEBUG, "complete remote queueing $log for $queue"); + common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' + . $notice->id . ' for ' . $queue); $this->stats('enqueued', $queue); } @@ -171,7 +174,7 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleItem($frame); + $ok = $ok && $this->_handleNotice($frame); } return $ok; } @@ -262,10 +265,10 @@ class StompQueueManager extends QueueManager } /** - * Handle and acknowledge an event that's come in through a queue. + * Handle and acknowledge a notice event that's come in through a queue. * * If the queue handler reports failure, the message is requeued for later. - * Missing objects or handler classes will drop the message. + * Missing notices or handler classes will drop the message. * * Side effects: in multi-site mode, may reset site configuration to * match the site that queued the event. @@ -273,15 +276,24 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleItem($frame) + protected function _handleNotice($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); if ($site != common_config('site', 'server')) { $this->stats('switch'); StatusNet::init($site); } - $info = "object posted at {$frame->headers['created']} in queue $queue"; - $item = unserialize($frame->body); + + $id = intval($frame->body); + $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; + + $notice = Notice::staticGet('id', $id); + if (empty($notice)) { + $this->_log(LOG_WARNING, "Skipping missing $info"); + $this->con->ack($frame); + $this->stats('badnotice', $queue); + return false; + } $handler = $this->getHandler($queue); if (!$handler) { @@ -291,7 +303,7 @@ class StompQueueManager extends QueueManager return false; } - $ok = $handler->handle($item); + $ok = $handler->handle_notice($notice); if (!$ok) { $this->_log(LOG_WARNING, "Failed handling $info"); @@ -299,7 +311,7 @@ class StompQueueManager extends QueueManager // this kind of queue management ourselves; // if we don't ack, it should resend... $this->con->ack($frame); - $this->enqueue($item, $queue); + $this->enqueue($notice, $queue); $this->stats('requeued', $queue); return false; } diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index c49986854..dfff63a30 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -175,30 +175,6 @@ class XmppManager extends IoManager } } - /** - * 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) { - return true; - } else { - return false; - } - } else { - // Can't send right now... - return false; - } - } - /** * Send a keepalive ping to the XMPP server. */ diff --git a/plugins/Enjit/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php index 14085cc5e..f0e706b92 100644 --- a/plugins/Enjit/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -32,7 +32,14 @@ class EnjitQueueHandler extends QueueHandler return 'enjit'; } - function handle($notice) + function start() + { + $this->log(LOG_INFO, "Starting EnjitQueueHandler"); + $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); + return true; + } + + function handle_notice($notice) { $profile = Profile::staticGet($notice->profile_id); diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 524af7bc4..1778690e5 100644 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -28,7 +28,7 @@ class FacebookQueueHandler extends QueueHandler return 'facebook'; } - function handle($notice) + function handle_notice($notice) { if ($this->_isLocal($notice)) { return facebookBroadcastNotice($notice); diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php index 9f444c8bb..2de162628 100644 --- a/plugins/RSSCloud/RSSCloudPlugin.php +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -138,9 +138,6 @@ class RSSCloudPlugin extends Plugin case 'RSSCloudNotifier': include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php'; return false; - case 'RSSCloudQueueHandler': - include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudQueueHandler.php'; - return false; case 'RSSCloudRequestNotifyAction': case 'LoggingAggregatorAction': include_once INSTALLDIR . '/plugins/RSSCloud/' . @@ -196,6 +193,32 @@ class RSSCloudPlugin extends Plugin return true; } + /** + * broadcast the message when not using queuehandler + * + * @param Notice &$notice the notice + * @param array $queue destination queue + * + * @return boolean hook return + */ + + function onUnqueueHandleNotice(&$notice, $queue) + { + if (($queue == 'rsscloud') && ($this->_isLocal($notice))) { + + common_debug('broadcasting rssCloud bound notice ' . $notice->id); + + $profile = $notice->getProfile(); + + $notifier = new RSSCloudNotifier(); + $notifier->notify($profile); + + return false; + } + + return true; + } + /** * Determine whether the notice was locally created * @@ -238,15 +261,19 @@ class RSSCloudPlugin extends Plugin } /** - * Register RSSCloud notice queue handler + * Add RSSCloudQueueHandler to the list of valid daemons to + * start * - * @param QueueManager $manager + * @param array $daemons the list of daemons to run * * @return boolean hook return + * */ - function onEndInitializeQueueManager($manager) + + function onGetValidDaemons($daemons) { - $manager->connect('rsscloud', 'RSSCloudQueueHandler'); + array_push($daemons, INSTALLDIR . + '/plugins/RSSCloud/RSSCloudQueueHandler.php'); return true; } diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php old mode 100644 new mode 100755 index 295c26189..693dd27c1 --- a/plugins/RSSCloud/RSSCloudQueueHandler.php +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -1,3 +1,4 @@ +#!/usr/bin/env php . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<log(LOG_INFO, "INITIALIZE"); + $this->notifier = new RSSCloudNotifier(); + return true; + } + + function handle_notice($notice) { $profile = $notice->getProfile(); - $notifier = new RSSCloudNotifier(); - return $notifier->notify($profile); + return $this->notifier->notify($profile); + } + + function finish() + { } + +} + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; } +$handler = new RSSCloudQueueHandler($id); + +$handler->runOnce(); diff --git a/plugins/TwitterBridge/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php index b5a624e83..5089ca7b7 100644 --- a/plugins/TwitterBridge/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -28,7 +28,7 @@ class TwitterQueueHandler extends QueueHandler return 'twitter'; } - function handle($notice) + function handle_notice($notice) { return broadcast_twitter($notice); } diff --git a/scripts/handlequeued.php b/scripts/handlequeued.php index 815884969..9031437aa 100755 --- a/scripts/handlequeued.php +++ b/scripts/handlequeued.php @@ -50,7 +50,7 @@ if (empty($notice)) { exit(1); } -if (!$handler->handle($notice)) { +if (!$handler->handle_notice($notice)) { print "Failed to handle notice id $noticeId on queue '$queue'.\n"; exit(1); } -- cgit v1.2.3-54-g00ecf From e9995b0f6ab0788162e22c996e5ee0af416dd519 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Sat, 23 Jan 2010 01:25:27 -0500 Subject: Create IM plugin, Pluginize XMPP, Create AIM plugin --- EVENTS.txt | 18 + actions/apiaccountupdatedeliverydevice.php | 10 +- actions/confirmaddress.php | 97 +- actions/imsettings.php | 317 ++-- actions/shownotice.php | 6 - actions/showstream.php | 6 - classes/User.php | 7 +- classes/User_im_prefs.php | 71 + classes/statusnet.ini | 22 +- db/statusnet.sql | 28 +- lib/channel.php | 57 - lib/command.php | 4 +- lib/imchannel.php | 104 ++ lib/immanager.php | 70 + lib/implugin.php | 612 +++++++ lib/imqueuehandler.php | 48 + lib/imreceiverqueuehandler.php | 42 + lib/imsenderqueuehandler.php | 44 + lib/jabber.php | 473 ----- lib/jabberqueuehandler.php | 47 - lib/publicqueuehandler.php | 45 - lib/queued_xmpp.php | 117 -- lib/queuehandler.php | 14 - lib/queuemanager.php | 14 +- lib/queuemonitor.php | 2 +- lib/util.php | 9 - lib/xmppmanager.php | 485 ------ lib/xmppoutqueuehandler.php | 55 - plugins/Aim/AimPlugin.php | 162 ++ plugins/Aim/Fake_Aim.php | 43 + plugins/Aim/README | 27 + plugins/Aim/aimmanager.php | 100 ++ plugins/Aim/extlib/phptoclib/README.txt | 169 ++ plugins/Aim/extlib/phptoclib/aimclassw.php | 2370 ++++++++++++++++++++++++++ plugins/Aim/extlib/phptoclib/dconnection.php | 229 +++ plugins/Imap/imapmanager.php | 8 +- plugins/Xmpp/Fake_XMPP.php | 104 ++ plugins/Xmpp/README | 35 + plugins/Xmpp/Sharing_XMPP.php | 43 + plugins/Xmpp/XmppPlugin.php | 247 +++ plugins/Xmpp/xmppmanager.php | 279 +++ scripts/getvaliddaemons.php | 4 +- scripts/imdaemon.php | 93 + scripts/queuedaemon.php | 9 +- scripts/stopdaemons.sh | 4 +- scripts/xmppdaemon.php | 98 -- 46 files changed, 5200 insertions(+), 1648 deletions(-) create mode 100644 classes/User_im_prefs.php create mode 100644 lib/imchannel.php create mode 100644 lib/immanager.php create mode 100644 lib/implugin.php create mode 100644 lib/imqueuehandler.php create mode 100644 lib/imreceiverqueuehandler.php create mode 100644 lib/imsenderqueuehandler.php delete mode 100644 lib/jabber.php delete mode 100644 lib/jabberqueuehandler.php delete mode 100644 lib/publicqueuehandler.php delete mode 100644 lib/queued_xmpp.php delete mode 100644 lib/xmppmanager.php delete mode 100644 lib/xmppoutqueuehandler.php create mode 100644 plugins/Aim/AimPlugin.php create mode 100644 plugins/Aim/Fake_Aim.php create mode 100644 plugins/Aim/README create mode 100644 plugins/Aim/aimmanager.php create mode 100755 plugins/Aim/extlib/phptoclib/README.txt create mode 100755 plugins/Aim/extlib/phptoclib/aimclassw.php create mode 100755 plugins/Aim/extlib/phptoclib/dconnection.php create mode 100644 plugins/Xmpp/Fake_XMPP.php create mode 100644 plugins/Xmpp/README create mode 100644 plugins/Xmpp/Sharing_XMPP.php create mode 100644 plugins/Xmpp/XmppPlugin.php create mode 100644 plugins/Xmpp/xmppmanager.php create mode 100755 scripts/imdaemon.php delete mode 100755 scripts/xmppdaemon.php (limited to 'plugins') diff --git a/EVENTS.txt b/EVENTS.txt index 1ed670697..45f1e9d70 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -699,3 +699,21 @@ StartShowContentLicense: Showing the default license for content EndShowContentLicense: Showing the default license for content - $action: the current action + +GetImTransports: Get IM transports that are available +- &$transports: append your transport to this array like so: $transports[transportName]=array('display'=>display) + +NormalizeImScreenname: Normalize an IM screenname +- $transport: transport the screenname is on +- &$screenname: screenname to be normalized + +ValidateImScreenname: Validate an IM screenname +- $transport: transport the screenname is on +- $screenname: screenname to be validated +- $valid: is the screenname valid? + +SendImConfirmationCode: Send a confirmation code to confirm a user owns an IM screenname +- $transport: transport the screenname exists on +- $screenname: screenname being confirmed +- $code: confirmation code for confirmation URL +- $user: user requesting the confirmation diff --git a/actions/apiaccountupdatedeliverydevice.php b/actions/apiaccountupdatedeliverydevice.php index 684906fe9..4bd6c326f 100644 --- a/actions/apiaccountupdatedeliverydevice.php +++ b/actions/apiaccountupdatedeliverydevice.php @@ -119,10 +119,16 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction if (strtolower($this->device) == 'sms') { $this->user->smsnotify = true; } elseif (strtolower($this->device) == 'im') { - $this->user->jabbernotify = true; + //TODO IM is pluginized now, so what should we do? + //Enable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = true; } elseif (strtolower($this->device == 'none')) { $this->user->smsnotify = false; - $this->user->jabbernotify = false; + //TODO IM is pluginized now, so what should we do? + //Disable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = false; } $result = $this->user->update($original); diff --git a/actions/confirmaddress.php b/actions/confirmaddress.php index cc8351d8d..eaf1c91c1 100644 --- a/actions/confirmaddress.php +++ b/actions/confirmaddress.php @@ -49,7 +49,7 @@ class ConfirmaddressAction extends Action { /** type of confirmation. */ - var $type = null; + var $address; /** * Accept a confirmation code @@ -86,37 +86,75 @@ class ConfirmaddressAction extends Action return; } $type = $confirm->address_type; - if (!in_array($type, array('email', 'jabber', 'sms'))) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) { $this->serverError(sprintf(_('Unrecognized address type %s'), $type)); return; } - if ($cur->$type == $confirm->address) { - $this->clientError(_('That address has already been confirmed.')); - return; - } - + $this->address = $confirm->address; $cur->query('BEGIN'); + if (in_array($type, array('email', 'sms'))) + { + if ($cur->$type == $confirm->address) { + $this->clientError(_('That address has already been confirmed.')); + return; + } + + $orig_user = clone($cur); + + $cur->$type = $confirm->address; + + if ($type == 'sms') { + $cur->carrier = ($confirm->address_extra)+0; + $carrier = Sms_carrier::staticGet($cur->carrier); + $cur->smsemail = $carrier->toEmailAddress($cur->sms); + } + + $result = $cur->updateKeys($orig_user); + + if (!$result) { + common_log_db_error($cur, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user.')); + return; + } + + if ($type == 'email') { + $cur->emailChanged(); + } + + } else { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + if ($user_im_prefs->find() && $user_im_prefs->fetch()) { + if($user_im_prefs->screenname == $confirm->address){ + $this->clientError(_('That address has already been confirmed.')); + return; + } + $user_im_prefs->screenname = $confirm->address; + $result = $user_im_prefs->update(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user im preferences.')); + return; + } + }else{ + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->screenname = $confirm->address; + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + $result = $user_im_prefs->insert(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t insert user im preferences.')); + return; + } + } - $orig_user = clone($cur); - - $cur->$type = $confirm->address; - - if ($type == 'sms') { - $cur->carrier = ($confirm->address_extra)+0; - $carrier = Sms_carrier::staticGet($cur->carrier); - $cur->smsemail = $carrier->toEmailAddress($cur->sms); - } - - $result = $cur->updateKeys($orig_user); - - if (!$result) { - common_log_db_error($cur, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); - return; - } - - if ($type == 'email') { - $cur->emailChanged(); } $result = $confirm->delete(); @@ -128,8 +166,6 @@ class ConfirmaddressAction extends Action } $cur->query('COMMIT'); - - $this->type = $type; $this->showPage(); } @@ -153,11 +189,10 @@ class ConfirmaddressAction extends Action function showContent() { $cur = common_current_user(); - $type = $this->type; $this->element('p', null, sprintf(_('The address "%s" has been '. 'confirmed for your account.'), - $cur->$type)); + $this->address)); } } diff --git a/actions/imsettings.php b/actions/imsettings.php index af4915843..fe1864f0d 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -31,9 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/connectsettingsaction.php'; -require_once INSTALLDIR.'/lib/jabber.php'; - /** * Settings for Jabber/XMPP integration * @@ -68,8 +65,8 @@ class ImsettingsAction extends ConnectSettingsAction function getInstructions() { return _('You can send and receive notices through '. - 'Jabber/GTalk [instant messages](%%doc.im%%). '. - 'Configure your address and settings below.'); + 'instant messaging [instant messages](%%doc.im%%). '. + 'Configure your addresses and settings below.'); } /** @@ -84,85 +81,108 @@ class ImsettingsAction extends ConnectSettingsAction function showContent() { - if (!common_config('xmpp', 'enabled')) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (! $transports) { $this->element('div', array('class' => 'error'), _('IM is not available.')); return; } $user = common_current_user(); - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_settings_im', - 'class' => 'form_settings', - 'action' => - common_local_url('imsettings'))); - $this->elementStart('fieldset', array('id' => 'settings_im_address')); - $this->element('legend', null, _('Address')); - $this->hidden('token', common_session_token()); - - if ($user->jabber) { - $this->element('p', 'form_confirmed', $user->jabber); - $this->element('p', 'form_note', - _('Current confirmed Jabber/GTalk address.')); - $this->hidden('jabber', $user->jabber); - $this->submit('remove', _('Remove')); - } else { - $confirm = $this->getConfirmation(); - if ($confirm) { - $this->element('p', 'form_unconfirmed', $confirm->address); + + $user_im_prefs_by_transport = array(); + + foreach($transports as $transport=>$transport_info) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_address')); + $this->element('legend', null, $transport_info['display']); + $this->hidden('token', common_session_token()); + $this->hidden('transport', $transport); + + if ($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $transport, 'user_id' => $user->id) )) { + $user_im_prefs_by_transport[$transport] = $user_im_prefs; + $this->element('p', 'form_confirmed', $user_im_prefs->screenname); $this->element('p', 'form_note', - sprintf(_('Awaiting confirmation on this address. '. - 'Check your Jabber/GTalk account for a '. - 'message with further instructions. '. - '(Did you add %s to your buddy list?)'), - jabber_daemon_address())); - $this->hidden('jabber', $confirm->address); - $this->submit('cancel', _('Cancel')); + sprintf(_('Current confirmed %s address.'),$transport_info['display'])); + $this->hidden('screenname', $user_im_prefs->screenname); + $this->submit('remove', _('Remove')); } else { - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->input('jabber', _('IM address'), - ($this->arg('jabber')) ? $this->arg('jabber') : null, - sprintf(_('Jabber or GTalk address, '. - 'like "UserName@example.org". '. - 'First, make sure to add %s to your '. - 'buddy list in your IM client or on GTalk.'), - jabber_daemon_address())); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('add', _('Add')); + $confirm = $this->getConfirmation($transport); + if ($confirm) { + $this->element('p', 'form_unconfirmed', $confirm->address); + $this->element('p', 'form_note', + sprintf(_('Awaiting confirmation on this address. '. + 'Check your %s account for a '. + 'message with further instructions.'), + $transport_info['display'])); + $this->hidden('screenname', $confirm->address); + $this->submit('cancel', _('Cancel')); + } else { + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('screenname', _('IM address'), + ($this->arg('screenname')) ? $this->arg('screenname') : null, + sprintf(_('%s screenname.'), + $transport_info['display'])); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('add', _('Add')); + } } + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + if($user_im_prefs_by_transport) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); + $this->element('legend', null, _('Preferences')); + $this->hidden('token', common_session_token()); + $this->elementStart('table'); + $this->elementStart('tr'); + $this->element('th', null, _('Preferences')); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $this->element('th', null, $transports[$transport]['display']); + } + $this->elementEnd('tr'); + $preferences = array( + array('name'=>'notify', 'description'=>_('Send me notices')), + array('name'=>'updatefrompresence', 'description'=>_('Post a notice when my status changes.')), + array('name'=>'replies', 'description'=>_('Send me replies '. + 'from people I\'m not subscribed to.')), + array('name'=>'microid', 'description'=>_('Publish a MicroID')) + ); + foreach($preferences as $preference) + { + $this->elementStart('tr'); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $preference_name = $preference['name']; + $this->elementStart('td'); + $this->checkbox($transport . '_' . $preference['name'], + $preference['description'], + $user_im_prefs->$preference_name); + $this->elementEnd('td'); + } + $this->elementEnd('tr'); + } + $this->elementEnd('table'); + $this->submit('save', _('Save')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); } - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); - $this->element('legend', null, _('Preferences')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->checkbox('jabbernotify', - _('Send me notices through Jabber/GTalk.'), - $user->jabbernotify); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('updatefrompresence', - _('Post a notice when my Jabber/GTalk status changes.'), - $user->updatefrompresence); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabberreplies', - _('Send me replies through Jabber/GTalk '. - 'from people I\'m not subscribed to.'), - $user->jabberreplies); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabbermicroid', - _('Publish a MicroID for my Jabber/GTalk address.'), - $user->jabbermicroid); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('save', _('Save')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); } /** @@ -171,14 +191,14 @@ class ImsettingsAction extends ConnectSettingsAction * @return Confirm_address address object for this user */ - function getConfirmation() + function getConfirmation($transport) { $user = common_current_user(); $confirm = new Confirm_address(); $confirm->user_id = $user->id; - $confirm->address_type = 'jabber'; + $confirm->address_type = $transport; if ($confirm->find(true)) { return $confirm; @@ -232,35 +252,31 @@ class ImsettingsAction extends ConnectSettingsAction function savePreferences() { - - $jabbernotify = $this->boolean('jabbernotify'); - $updatefrompresence = $this->boolean('updatefrompresence'); - $jabberreplies = $this->boolean('jabberreplies'); - $jabbermicroid = $this->boolean('jabbermicroid'); - $user = common_current_user(); - assert(!is_null($user)); // should already be checked - - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabbernotify = $jabbernotify; - $user->updatefrompresence = $updatefrompresence; - $user->jabberreplies = $jabberreplies; - $user->jabbermicroid = $jabbermicroid; - - $result = $user->update($original); - - if ($result === false) { - common_log_db_error($user, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); - return; + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $user->id; + if($user_im_prefs->find() && $user_im_prefs->fetch()) + { + $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); + $user_im_prefs->query('BEGIN'); + do + { + $original = clone($user_im_prefs); + foreach($preferences as $preference) + { + $user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference); + } + $result = $user_im_prefs->update($original); + + if ($result === false) { + common_log_db_error($user, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update IM preferences.')); + return; + } + }while($user_im_prefs->fetch()); + $user_im_prefs->query('COMMIT'); } - - $user->query('COMMIT'); - $this->showForm(_('Preferences saved.'), true); } @@ -268,7 +284,7 @@ class ImsettingsAction extends ConnectSettingsAction * Sends a confirmation to the address given * * Stores a confirmation record and sends out a - * Jabber message with the confirmation info. + * message with the confirmation info. * * @return void */ @@ -277,36 +293,41 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->trimmed('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Some validation - if (!$jabber) { - $this->showForm(_('No Jabber ID.')); + if (!$screenname) { + $this->showForm(_('No screenname.')); return; } - $jabber = jabber_normalize_jid($jabber); - - if (!$jabber) { - $this->showForm(_('Cannot normalize that Jabber ID')); + if (!$transport) { + $this->showForm(_('No transport.')); return; } - if (!jabber_valid_base_jid($jabber)) { - $this->showForm(_('Not a valid Jabber ID')); + + Event::handle('NormalizeImScreenname', array($transport, &$screenname)); + + if (!$screenname) { + $this->showForm(_('Cannot normalize that screenname')); return; - } else if ($user->jabber == $jabber) { - $this->showForm(_('That is already your Jabber ID.')); + } + $valid = false; + Event::handle('ValidateImScreenname', array($transport, $screenname, &$valid)); + if (!$valid) { + $this->showForm(_('Not a valid screenname')); return; - } else if ($this->jabberExists($jabber)) { - $this->showForm(_('Jabber ID already belongs to another user.')); + } else if ($this->screennameExists($transport, $screenname)) { + $this->showForm(_('Screenname already belongs to another user.')); return; } $confirm = new Confirm_address(); - $confirm->address = $jabber; - $confirm->address_type = 'jabber'; + $confirm->address = $screenname; + $confirm->address_type = $transport; $confirm->user_id = $user->id; $confirm->code = common_confirmation_code(64); $confirm->sent = common_sql_now(); @@ -320,15 +341,10 @@ class ImsettingsAction extends ConnectSettingsAction return; } - jabber_confirm_address($confirm->code, - $user->nickname, - $jabber); + Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $user)); - $msg = sprintf(_('A confirmation code was sent '. - 'to the IM address you added. '. - 'You must approve %s for '. - 'sending messages to you.'), - jabber_daemon_address()); + $msg = _('A confirmation code was sent '. + 'to the IM address you added.'); $this->showForm($msg, true); } @@ -343,15 +359,16 @@ class ImsettingsAction extends ConnectSettingsAction function cancelConfirmation() { - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); - $confirm = $this->getConfirmation(); + $confirm = $this->getConfirmation($transport); if (!$confirm) { $this->showForm(_('No pending confirmation to cancel.')); return; } - if ($confirm->address != $jabber) { + if ($confirm->address != $screenname) { $this->showForm(_('That is the wrong IM address.')); return; } @@ -360,7 +377,7 @@ class ImsettingsAction extends ConnectSettingsAction if (!$result) { common_log_db_error($confirm, 'DELETE', __FILE__); - $this->serverError(_('Couldn\'t delete email confirmation.')); + $this->serverError(_('Couldn\'t delete confirmation.')); return; } @@ -379,29 +396,25 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Maybe an old tab open...? - if ($user->jabber != $jabber) { - $this->showForm(_('That is not your Jabber ID.')); + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $user->id; + if(! ($user_im_prefs->find() && $user_im_prefs->fetch())) { + $this->showForm(_('That is not your screenname.')); return; } - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabber = null; - - $result = $user->updateKeys($original); + $result = $user_im_prefs->delete(); if (!$result) { common_log_db_error($user, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); + $this->serverError(_('Couldn\'t update user im prefs.')); return; } - $user->query('COMMIT'); // XXX: unsubscribe to the old address @@ -409,25 +422,27 @@ class ImsettingsAction extends ConnectSettingsAction } /** - * Does this Jabber ID exist? + * Does this screenname exist? * * Checks if we already have another user with this address. * - * @param string $jabber Address to check + * @param string $transport Transport to check + * @param string $screenname Screenname to check * - * @return boolean whether the Jabber ID exists + * @return boolean whether the screenname exists */ - function jabberExists($jabber) + function screennameExists($transport, $screenname) { $user = common_current_user(); - $other = User::staticGet('jabber', $jabber); - - if (!$other) { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $transport; + $user_im_prefs->screenname = $screenname; + if($user_im_prefs->find() && $user_im_prefs->fetch()){ + return true; + }else{ return false; - } else { - return $other->id != $user->id; } } } diff --git a/actions/shownotice.php b/actions/shownotice.php index d09100f67..d0528a9f0 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -275,12 +275,6 @@ class ShownoticeAction extends OwnerDesignAction 'content' => $id->toString())); } - if ($user->jabbermicroid && $user->jabber && $this->notice->uri) { - $id = new Microid('xmpp:', $user->jabber, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } $this->element('link',array('rel'=>'alternate', 'type'=>'application/json+oembed', 'href'=>common_local_url( diff --git a/actions/showstream.php b/actions/showstream.php index 75e10858d..b9782a3f1 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -166,12 +166,6 @@ class ShowstreamAction extends ProfileAction $this->element('meta', array('name' => 'microid', 'content' => $id->toString())); } - if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) { - $id = new Microid('xmpp:'.$this->user->jabber, - $this->selfUrl()); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } // See https://wiki.mozilla.org/Microsummaries diff --git a/classes/User.php b/classes/User.php index 6ea975202..57c56849d 100644 --- a/classes/User.php +++ b/classes/User.php @@ -48,11 +48,6 @@ class User extends Memcached_DataObject public $language; // varchar(50) public $timezone; // varchar(50) public $emailpost; // tinyint(1) default_1 - public $jabber; // varchar(255) unique_key - public $jabbernotify; // tinyint(1) - public $jabberreplies; // tinyint(1) - public $jabbermicroid; // tinyint(1) default_1 - public $updatefrompresence; // tinyint(1) public $sms; // varchar(64) unique_key public $carrier; // int(4) public $smsnotify; // tinyint(1) @@ -92,7 +87,7 @@ class User extends Memcached_DataObject function updateKeys(&$orig) { $parts = array(); - foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { + foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { if (strcmp($this->$k, $orig->$k) != 0) { $parts[] = $k . ' = ' . $this->_quote($this->$k); } diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php new file mode 100644 index 000000000..8ecdfe9fa --- /dev/null +++ b/classes/User_im_prefs.php @@ -0,0 +1,71 @@ +. + * + * @category Data + * @package StatusNet + * @author Craig Andrews + * @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/ + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class User_im_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_im_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $screenname; // varchar(255) not_null + public $transport; // varchar(255) not_null + public $notify; // tinyint(1) + public $replies; // tinyint(1) + public $microid; // tinyint(1) + public $updatefrompresence; // tinyint(1) + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_im_prefs',$k,$v); } + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('User_im_prefs', $kv); + } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /* + DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function. + In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but + DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric + type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and + manage the sequence itself. This is not the correct behavior for the user_id in this class. + So we override that incorrect behavior, and simply say there is no sequence key. + */ + function sequenceKey() + { + return array(false,false); + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6203650a6..d8a817ebc 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -540,11 +540,6 @@ emailmicroid = 17 language = 2 timezone = 2 emailpost = 17 -jabber = 2 -jabbernotify = 17 -jabberreplies = 17 -jabbermicroid = 17 -updatefrompresence = 17 sms = 2 carrier = 1 smsnotify = 17 @@ -564,7 +559,6 @@ id = K nickname = U email = U incomingemail = U -jabber = U sms = U uri = U @@ -616,3 +610,19 @@ modified = 384 [user_location_prefs__keys] user_id = K +[user_im_prefs] +user_id = 129 +screenname = 130 +transport = 130 +notify = 17 +replies = 17 +microid = 17 +updatefrompresence = 17 +created = 142 +modified = 384 + +[user_im_prefs__keys] +user_id = K +transport = K +transport = U +screenname = U diff --git a/db/statusnet.sql b/db/statusnet.sql index 17de4fd0d..63b50a3f7 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -62,11 +62,6 @@ create table user ( language varchar(50) comment 'preferred language', timezone varchar(50) comment 'timezone', emailpost tinyint default 1 comment 'Post by email', - jabber varchar(255) unique key comment 'jabber ID for notices', - jabbernotify tinyint default 0 comment 'whether to send notices to jabber', - jabberreplies tinyint default 0 comment 'whether to send notices to jabber on replies', - jabbermicroid tinyint default 1 comment 'whether to publish xmpp microid', - updatefrompresence tinyint default 0 comment 'whether to record updates from Jabber presence notices', sms varchar(64) unique key comment 'sms phone number', carrier integer comment 'foreign key to sms_carrier' references sms_carrier (id), smsnotify tinyint default 0 comment 'whether to send notices to SMS', @@ -259,9 +254,9 @@ create table oid_nonces ( create table confirm_address ( code varchar(32) not null primary key comment 'good random code', user_id integer not null comment 'user who requested confirmation' references user (id), - address varchar(255) not null comment 'address (email, Jabber, SMS, etc.)', + address varchar(255) not null comment 'address (email, xmpp, SMS, etc.)', address_extra varchar(255) not null comment 'carrier ID, for SMS', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', claimed datetime comment 'date this was claimed for queueing', sent datetime comment 'date this was sent for queueing', modified timestamp comment 'date this record was modified' @@ -276,7 +271,7 @@ create table remember_me ( create table queue_item ( id integer auto_increment primary key comment 'unique identifier', frame blob not null comment 'data: object reference or opaque string', - transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + transport varchar(8) not null comment 'queue for what? "email", "xmpp", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', @@ -348,7 +343,7 @@ create table invitation ( code varchar(32) not null primary key comment 'random code for an invitation', user_id int not null comment 'who sent the invitation' references user (id), address varchar(255) not null comment 'invitation sent to', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', created datetime not null comment 'date this record was created', index invitation_address_idx (address, address_type), @@ -633,3 +628,18 @@ create table inbox ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table user_im_prefs ( + user_id integer not null comment 'user' references user (id), + screenname varchar(255) not null comment 'screenname on this service', + transport varchar(255) not null comment 'transport (ex xmpp, aim)', + notify tinyint(1) not null default 0 comment 'Notify when a new notice is sent', + replies tinyint(1) not null default 0 comment 'Send replies from people not subscribed to', + microid tinyint(1) not null default 1 comment 'Publish a MicroID', + updatefrompresence tinyint(1) not null default 0 comment 'Send replies from people not subscribed to.', + created timestamp not null DEFAULT CURRENT_TIMESTAMP comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id, transport), + constraint unique key `transport_screenname_key` ( `transport` , `screenname` ) +); diff --git a/lib/channel.php b/lib/channel.php index 3cd168786..05437b4e9 100644 --- a/lib/channel.php +++ b/lib/channel.php @@ -47,63 +47,6 @@ class 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; diff --git a/lib/command.php b/lib/command.php index 2a51fd687..44b7b2274 100644 --- a/lib/command.php +++ b/lib/command.php @@ -596,7 +596,7 @@ class OffCommand extends Command } function execute($channel) { - if ($other) { + if ($this->other) { $channel->error($this->user, _("Command not yet implemented.")); } else { if ($channel->off($this->user)) { @@ -619,7 +619,7 @@ class OnCommand extends Command function execute($channel) { - if ($other) { + if ($this->other) { $channel->error($this->user, _("Command not yet implemented.")); } else { if ($channel->on($this->user)) { diff --git a/lib/imchannel.php b/lib/imchannel.php new file mode 100644 index 000000000..12354ce4b --- /dev/null +++ b/lib/imchannel.php @@ -0,0 +1,104 @@ +. + */ + +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->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + $this->imPlugin->send_message($this->imPlugin->get_screenname($user), $text); + } + + function error($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + + $screenname = $this->imPlugin->get_screenname($user); + if($screenname){ + $this->imPlugin->send_message($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 set_notify($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..4c28ec0e6 --- /dev/null +++ b/lib/immanager.php @@ -0,0 +1,70 @@ +. + */ + +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::enqueue_outgoing_raw + * enqueued + */ + +abstract class ImManager extends IoManager +{ + abstract function send_raw_message($data); + + function __construct($imPlugin) + { + $this->plugin = $imPlugin; + //TODO We only really want to register this event if this is the thread that runs the ImManager + Event::addHandler('EndInitializeQueueManager', array($this, 'onEndInitializeQueueManager')); + } + + /** + * 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'); + } + + /** + * Register notice queue handler + * + * @param QueueManager $manager + * + * @return boolean hook return + */ + function onEndInitializeQueueManager($manager) + { + $manager->connect($this->plugin->transport . '-out', new ImSenderQueueHandler($this->plugin, $this), 'imdaemon'); + return true; + } +} diff --git a/lib/implugin.php b/lib/implugin.php new file mode 100644 index 000000000..5d4d8949c --- /dev/null +++ b/lib/implugin.php @@ -0,0 +1,612 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 + * @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 enqueue_outgoing_raw() + * + * @param string $screenname screenname to send to + * @param Notice $notice notice to send + * + * @return boolean success value + */ + function send_notice($screenname, $notice) + { + return $this->send_message($screenname, $this->format_notice($notice)); + } + + /** + * send a message (text) to a given screenname + * The implementation should put raw data, ready to send, into the outgoing + * queue using enqueue_outgoing_raw() + * + * @param string $screenname screenname to send to + * @param Notice $body text to send + * + * @return boolean success value + */ + abstract function send_message($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 handle_incoming() + * + * @param object $data raw IM data + * + * @return boolean success value + */ + abstract function receive_raw_message($data); + + /** + * get the screenname of the daemon that sends and receives message for this service + * + * @return string screenname of this plugin + */ + abstract function daemon_screenname(); + + /** + * 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 enqueue_outgoing_raw($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 enqueue_incoming_raw($data) + { + $qm = QueueManager::get(); + $qm->enqueue($data, $this->transport . '-in'); + } + + /** + * given a screenname, get the corresponding user + * + * @param string $screenname + * + * @return User user + */ + function get_user($screenname) + { + $user_im_prefs = $this->get_user_im_prefs_from_screenname($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 get_user_im_prefs_from_screenname($screenname) + { + if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'screenname' => $screenname) )){ + return $user_im_prefs; + }else{ + return false; + } + } + + + /** + * given a User, get their screenname + * + * @param User $user + * + * @return string screenname of that user + */ + function get_screenname($user) + { + $user_im_prefs = $this->get_user_im_prefs_from_user($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 get_user_im_prefs_from_user($user) + { + if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'user_id' => $user->id) )){ + 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 send_from_site($screenname, $msg) + { + $text = '['.common_config('site', 'name') . '] ' . $msg; + $this->send_message($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 send_confirmation_code($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->send_message($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 public_notice($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->send_notice($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 broadcast_notice($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->get_user_im_prefs_from_user($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->send_notice($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 format_notice($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 handle_command($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 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; + } + } + + /** + * Is some text an OTR message? + * @param string $txt message text + * @return boolean true if OTR + */ + protected function is_otr($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 handle_incoming($from, $notice_text) + { + $user = $this->get_user($from); + // For common_current_user to work + global $_cur; + $_cur = $user; + + if (!$user) { + $this->send_from_site($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->handle_command($user, $notice_text)) { + common_log(LOG_INFO, "Command message by $from handled."); + return; + } else if ($this->is_autoreply($notice_text)) { + common_log(LOG_INFO, 'Ignoring auto reply from ' . $from); + return; + } else if ($this->is_otr($notice_text)) { + common_log(LOG_INFO, 'Ignoring OTR from ' . $from); + return; + } else { + + common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname); + + $this->add_notice($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 add_notice($screenname, $user, $body) + { + $body = trim(strip_tags($body)); + $content_shortened = common_shorten_links($body); + if (Notice::contentTooLong($content_shortened)) { + $this->send_from_site($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->send_from_site($from, $e->getMessage()); + return; + } + + common_broadcast_notice($notice); + 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)); + $manager->connect($this->transport, new ImQueueHandler($this)); + 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()); + } + + function onSendImConfirmationCode($transport, $screenname, $code, $user) + { + if($transport == $this->transport) + { + $this->send_confirmation_code($screenname, $code, $user); + return false; + } + } + + function onUserDeleteRelated($user, &$tables) + { + $tables[] = 'User_im_prefs'; + return true; + } + + function initialize() + { + if(is_null($this->transport)){ + throw new Exception('transport cannot be null'); + } + } +} diff --git a/lib/imqueuehandler.php b/lib/imqueuehandler.php new file mode 100644 index 000000000..b42d8e7c0 --- /dev/null +++ b/lib/imqueuehandler.php @@ -0,0 +1,48 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle a notice + * @param Notice $notice + * @return boolean success + */ + function handle($notice) + { + $this->plugin->broadcast_notice($notice); + if ($notice->is_local == Notice::LOCAL_PUBLIC || + $notice->is_local == Notice::LOCAL_NONPUBLIC) { + $this->plugin->public_notice($notice); + } + return true; + } + +} diff --git a/lib/imreceiverqueuehandler.php b/lib/imreceiverqueuehandler.php new file mode 100644 index 000000000..269c7db91 --- /dev/null +++ b/lib/imreceiverqueuehandler.php @@ -0,0 +1,42 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM receiving queue handlers. + */ + +class ImReceiverQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle incoming IM data sent by a user to the IM bot + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->plugin->receive_raw_message($data); + } +} diff --git a/lib/imsenderqueuehandler.php b/lib/imsenderqueuehandler.php new file mode 100644 index 000000000..3dd96ff73 --- /dev/null +++ b/lib/imsenderqueuehandler.php @@ -0,0 +1,44 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImSenderQueueHandler extends QueueHandler +{ + function __construct($plugin, $immanager) + { + $this->plugin = $plugin; + $this->immanager = $immanager; + } + + /** + * Handle outgoing IM data to be sent from the bot to a user + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->immanager->send_raw_message($data); + } +} + diff --git a/lib/jabber.php b/lib/jabber.php deleted file mode 100644 index b6b23521b..000000000 --- a/lib/jabber.php +++ /dev/null @@ -1,473 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Evan Prodromou - * @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'; - -/** - * checks whether a string is a syntactically valid Jabber ID (JID) - * - * @param string $jid string to check - * - * @return boolean whether the string is a valid JID - */ - -function jabber_valid_base_jid($jid) -{ - // Cheap but effective - return Validate::email($jid); -} - -/** - * normalizes a Jabber ID for comparison - * - * @param string $jid JID to check - * - * @return string an equivalent JID in normalized (lowercase) form - */ - -function jabber_normalize_jid($jid) -{ - if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) { - $node = $matches[1]; - $server = $matches[2]; - return strtolower($node.'@'.$server); - } else { - return null; - } -} - -/** - * 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. - */ -function jabber_proxy() -{ - $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; -} - -/** - * 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 = "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 false; - } - - $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 false; - } - - $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/jabberqueuehandler.php b/lib/jabberqueuehandler.php deleted file mode 100644 index 83471f2df..000000000 --- a/lib/jabberqueuehandler.php +++ /dev/null @@ -1,47 +0,0 @@ -. - */ - -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. - */ -class JabberQueueHandler extends QueueHandler -{ - var $conn = null; - - function transport() - { - return 'jabber'; - } - - function handle($notice) - { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_broadcast_notice($notice); - } catch (XMPPHP_Exception $e) { - $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; - } - } -} diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php deleted file mode 100644 index c9edb8d5d..000000000 --- a/lib/publicqueuehandler.php +++ /dev/null @@ -1,45 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Queue handler for pushing new notices to public XMPP subscribers. - */ -class PublicQueueHandler extends QueueHandler -{ - - function transport() - { - return 'public'; - } - - function handle($notice) - { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_public_notice($notice); - } catch (XMPPHP_Exception $e) { - $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; - } - } -} diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php deleted file mode 100644 index 4b890c4ca..000000000 --- a/lib/queued_xmpp.php +++ /dev/null @@ -1,117 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Brion Vibber - * @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); - // Normally the fulljid isn't filled out until resource binding time; - // we need to save it here 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(); - $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 @@ -36,20 +36,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } 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 61e28085a..0b405943d 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -213,18 +213,8 @@ abstract class QueueManager extends IoManager $this->connect('sms', 'SmsQueueHandler'); } - // XMPP output handlers... - $this->connect('jabber', 'JabberQueueHandler'); - $this->connect('public', 'PublicQueueHandler'); - - // @fixme this should get an actual queue - //$this->connect('confirm', 'XmppConfirmHandler'); - // For compat with old plugins not registering their own handlers. $this->connect('plugin', 'PluginQueueHandler'); - - $this->connect('xmppout', 'XmppOutQueueHandler', 'xmppdaemon'); - } Event::handle('EndInitializeQueueManager', array($this)); } @@ -251,8 +241,8 @@ abstract class QueueManager extends IoManager $group = 'queuedaemon'; if ($this->master) { // hack hack - if ($this->master instanceof XmppMaster) { - return 'xmppdaemon'; + if ($this->master instanceof ImMaster) { + return 'imdaemon'; } } return $group; 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/util.php b/lib/util.php index 6c9f6316a..e60cb6765 100644 --- a/lib/util.php +++ b/lib/util.php @@ -994,18 +994,9 @@ function common_enqueue_notice($notice) $transports = $allTransports; - $xmpp = common_config('xmpp', 'enabled'); - - if ($xmpp) { - $transports[] = 'jabber'; - } - if ($notice->is_local == Notice::LOCAL_PUBLIC || $notice->is_local == Notice::LOCAL_NONPUBLIC) { $transports = array_merge($transports, $localTransports); - if ($xmpp) { - $transports[] = 'public'; - } } if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) { diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php deleted file mode 100644 index 985e7c32e..000000000 --- a/lib/xmppmanager.php +++ /dev/null @@ -1,485 +0,0 @@ -. - */ - -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; - - 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 = common_config('site', 'server'); - 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 = common_config('site', 'server'); - $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(""); - } - - /** - * 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."); - return; - } - - if (mb_strlen($pl['body']) == 0) { - $this->log(LOG_WARNING, "Ignoring message with empty body from $from."); - 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 != common_config('site', 'server')) { - common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); - $this->stats('switch'); - StatusNet::init($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 @@ -. - */ - -/** - * 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 - */ -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/plugins/Aim/AimPlugin.php b/plugins/Aim/AimPlugin.php new file mode 100644 index 000000000..3855d1fb0 --- /dev/null +++ b/plugins/Aim/AimPlugin.php @@ -0,0 +1,162 @@ +. + * + * @category IM + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} +// We bundle the phptoclib library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib'); + +/** + * Plugin for AIM + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class AimPlugin extends ImPlugin +{ + public $user = null; + public $password = null; + public $publicFeed = array(); + + public $transport = 'aim'; + + function getDisplayName() + { + return _m('AIM'); + } + + function normalize($screenname) + { + $screenname = str_replace(" ","", $screenname); + return strtolower($screenname); + } + + function daemon_screenname() + { + return $this->user; + } + + function validate($screenname) + { + if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { + return true; + }else{ + return false; + } + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'Aim': + require_once(INSTALLDIR.'/plugins/Aim/extlib/phptoclib/aimclassw.php'); + return false; + case 'AimManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + case 'Fake_Aim': + include_once $dir . '/'. $cls .'.php'; + return false; + default: + return true; + } + } + + function onStartImDaemonIoManagers(&$classes) + { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new AimManager($this); // handles sending/receiving + return true; + } + + function microiduri($screenname) + { + return 'aim:' . $screenname; + } + + function send_message($screenname, $body) + { + $this->fake_aim->sendIm($screenname, $body); + $this->enqueue_outgoing_raw($this->fake_aim->would_be_sent); + return true; + } + + function receive_raw_message($message) + { + $info=Aim::getMessageInfo($message); + $from = $info['from']; + $user = $this->get_user($from); + $notice_text = $info['message']; + + return $this->handle_incoming($from, $notice_text); + } + + function initialize(){ + if(!isset($this->user)){ + throw new Exception("must specify a user"); + } + if(!isset($this->password)){ + throw new Exception("must specify a password"); + } + + $this->fake_aim = new Fake_Aim($this->user,$this->password,4); + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'AIM', + 'version' => STATUSNET_VERSION, + 'author' => 'Craig Andrews', + 'homepage' => 'http://status.net/wiki/Plugin:AIM', + 'rawdescription' => + _m('The AIM plugin allows users to send and receive notices over the AIM network.')); + return true; + } +} + diff --git a/plugins/Aim/Fake_Aim.php b/plugins/Aim/Fake_Aim.php new file mode 100644 index 000000000..139b68f82 --- /dev/null +++ b/plugins/Aim/Fake_Aim.php @@ -0,0 +1,43 @@ +. + * + * @category Network + * @package StatusNet + * @author Craig Andrews + * @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); +} + +class Fake_Aim extends Aim +{ + public $would_be_sent = null; + + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $this->would_be_sent = array($sflap_type, $sflap_data, $no_null, $formatted); + } +} + diff --git a/plugins/Aim/README b/plugins/Aim/README new file mode 100644 index 000000000..046591738 --- /dev/null +++ b/plugins/Aim/README @@ -0,0 +1,27 @@ +The AIM plugin allows users to send and receive notices over the AIM network. + +Installation +============ +add "addPlugin('aim', + array('setting'=>'value', 'setting2'=>'value2', ...);" +to the bottom of your config.php + +The daemon included with this plugin must be running. It will be started by +the plugin along with their other daemons when you run scripts/startdaemons.sh. +See the StatusNet README for more about queuing and daemons. + +Settings +======== +user*: username (screenname) to use when logging into AIM +password*: password for that user + +* required +default values are in (parenthesis) + +Example +======= +addPlugin('aim', array( + 'user=>'...', + 'password'=>'...' +)); + diff --git a/plugins/Aim/aimmanager.php b/plugins/Aim/aimmanager.php new file mode 100644 index 000000000..d9b7421fb --- /dev/null +++ b/plugins/Aim/aimmanager.php @@ -0,0 +1,100 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * AIM background connection manager for AIM-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 handled. + * + * 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 AimManager extends ImManager +{ + + public $conn = null; + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + public function getSockets() + { + $this->connect(); + if($this->conn){ + return array($this->conn->myConnection); + }else{ + return array(); + } + } + + /** + * Process AIM events that have come in over the wire. + * @param resource $socket + */ + public function handleInput($socket) + { + common_log(LOG_DEBUG, "Servicing the AIM queue."); + $this->stats('aim_process'); + $this->conn->receive(); + } + + function connect() + { + if (!$this->conn) { + $this->conn=new Aim($this->plugin->user,$this->plugin->password,4); + $this->conn->registerHandler("IMIn",array($this,"handle_aim_message")); + $this->conn->myServer="toc.oscar.aol.com"; + $this->conn->signon(); + $this->conn->setProfile(_m('Send me a message to post a notice'),false); + } + return $this->conn; + } + + function handle_aim_message($data) + { + $this->plugin->enqueue_incoming_raw($data); + return true; + } + + function send_raw_message($data) + { + $this->connect(); + if (!$this->conn) { + return false; + } + $this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]); + return true; + } +} diff --git a/plugins/Aim/extlib/phptoclib/README.txt b/plugins/Aim/extlib/phptoclib/README.txt new file mode 100755 index 000000000..0eec13af8 --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/README.txt @@ -0,0 +1,169 @@ +phpTOCLib version 1.0 RC1 + +This is released under the LGPL. AIM,TOC,OSCAR, and all other related protocols/terms are +copyright AOL/Time Warner. This project is in no way affiliated with them, nor is this +project supported by them. + +Some of the code is loosely based off of a script by Jeffrey Grafton. Mainly the decoding of packets, and the +function for roasting passwords is entirly his. + +TOC documentation used is available at http://simpleaim.sourceforge.net/docs/TOC.txt + + +About: +phpTOCLib aims to be a PHP equivalent to the PERL module NET::AIM. Due to some limitations, +this is difficult. Many features have been excluded in the name of simplicity, and leaves +you alot of room to code with externally, providing function access to the variables that +need them. + +I have aimed to make this extensible, and easy to use, therefore taking away some built in +functionality that I had originally out in. This project comes after several months of +researching the TOC protocol. + +example.php is included with the class. It needs to be executed from the command line +(ie:php -q testscript.php) and you need to call php.exe with the -q +example is provided as a demonstaration only. Though it creats a very simple, functional bot, it lacks any sort of commands, it merely resends the message it recieves in reverse. + + +Revisions: + +----------------------------------- +by Rajiv Makhijani +(02/24/04) + - Fixed Bug in Setting Permit/Deny Mode + - Fixes so Uninitialized string offset notice doesn't appear + - Replaced New Lines Outputed for Each Flap Read with " . " so + that you can still tell it is active but it does not take so much space + - Removed "eh?" message + - Added MySQL Database Connection Message + - New Functions: + update_profile(profile data string, powered by boolean) + * The profile data string is the text that goes in the profile. + * The powered by boolean if set to true displays a link to the + sourceforge page of the script. +(02/28/04) + - Silent option added to set object not to output any information + - To follow silent rule use sEcho function instead of Echo +----------------------------------- +by Jeremy (pickleman78) +(05/26/04) beta 1 release + -Complete overhaul of class design and message handling + -Fixed bug involving sign off after long periods of idling + -Added new function $Aim->registerHandler + -Added the capability to handle all AIM messages + -Processing the messages is still the users responsibility + -Did a little bit of code cleanup + -Added a few internal functions to make the classes internal life easier + -Improved AIM server error message processing + -Updated this document (hopefully Rajiv will clean it up some, since I'm a terrible documenter) +------------------------------------------------------------------------------------------------------------- + + + +Functions: + +Several methods are provided in the class that allow for simple access to some of the +common features of AIM. Below are details. + +$Aim->Aim($sn,$password,$pdmode, $silent=false) +The constructor, it takes 4 arguments. +$sn is your screen name +$password is you password, in plain text +$pdmode is the permit deny mode. This can be as follows: +1 - Allow All +2 - Deny All +3 - Permit only those on your permit list +4 - Permit all those not on your deny list +$silent if set to true prints out nothing + +So, if your screen-name is JohnDoe746 and your password is fertu, and you want to allow +all users of the AIM server to contact you, you would code as follows +$myaim=new Aim("JohnDoe746","fertu",1); + + +$Aim->add_permit($buddy) +This adds the buddy passed to the function to your permit list. +ie: $myaim->add_permit("My friend22"); + +$Aim->block_buddy($buddy) +Blocks a user. This will switch your pd mode to 4. After using this, for the user to remain +out of contact with you, it is required to provide the constructor with a pd mode of 4 +ie:$myaim->block_buddy("Annoying guy 4"); + +$Aim->send_im($to,$message,$auto=false) +Sends $message to $user. If you set the 3rd argument to true, then the recipient will receive it in +the same format as an away message. (Auto Response from me:) +A message longer than 65535 will be truncated +ie:$myaim->send_im("myfriend","This is a happy message"); + +$Aim->set_my_info() +Sends an update buddy command to the server and allows some internal values about yourself +to be set. +ie:$myaim->set_my_info(); + +$Aim->signon() +Call this to connect to the server. This must be called before any other methods will work +properly +ie:$mybot->signon(); + +$Aim->getLastReceived() +Returns $this->myLastReceived['decoded']. This should be the only peice of the gotten data +you need to concern yourself with. This is a preferred method of accessing this variable to prevent +accidental modification of $this->myLastReceived. Accidently modifying this variable can +cause some internal failures. + +$Aim->read_from_aim() +This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data'] +portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this +function instead. This function has a return value. Calling this prevents the need to call +$Aim->getLastReceived() + +$Aim->setWarning($wl) +This allows you to update the bots warning level when warned. + +$Aim->getBuddies() +Returns the $this->myBuddyList array. Use this instead of modifying the internal variable + +$Aim->getPermit() +Returns the $this->myPermitList array. Use this instead of modifying the internal variable + +$Aim->getBlocked() +Returns the $this->myBlockedList array. Use this instead of modifying the internal variable + +$Aim->warn_user($user,$anon=false) +Warn $user. If anon is set to true, then it warns the user anonomously + +$Aim->update_profile($information, $poweredby=false) +Updates Profile to $information. If $poweredby is true a link to +sourceforge page for this script is appended to profile + +$Aim->registerHandler($function_name,$command) +This is by far the best thing about the new release. +For more information please reas supplement.txt. It is not included here because of the sheer size of the document. +supplement.txt contains full details on using registerHandler and what to expect for each input. + + +For convenience, I have provided some functions to simplify message processing. + +They can be read about in the file "supplement.txt". I chose not to include the text here because it +is a huge document + + + +There are a few things you should note about AIM +1)An incoming message has HTML tags in it. You are responsible for stripping those tags +2)Outgoing messages can have HTML tags, but will work fine if they don't. To include things + in the time feild next to the users name, send it as a comment + +Conclusion: +The class is released under the LGPL. If you have any bug reports, comments, questions +feature requests, or want to help/show me what you've created with this(I am very interested in this), +please drop me an email: pickleman78@users.sourceforge.net. This code was written by +Jeremy(a.k.a pickleman78) and Rajiv M (a.k.a compwiz562). + + +Special thanks: +I'd like to thank all of the people who have contributed ideas, testing, bug reports, and code additions to +this project. I'd like to especially thank Rajiv, who has done do much for the project, and has kept this documnet +looking nice. He also has done alot of testing of this script too. I'd also like to thank SpazLink for his help in +testing. And finally I'd like to thank Jeffery Grafton, whose script inspired me to start this project. diff --git a/plugins/Aim/extlib/phptoclib/aimclassw.php b/plugins/Aim/extlib/phptoclib/aimclassw.php new file mode 100755 index 000000000..0657910d9 --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/aimclassw.php @@ -0,0 +1,2370 @@ + + * @author Rajiv Makhijani + * @package phptoclib + * @version 1.0RC1 + * @copyright 2005 + * @access public + * + */ +class Aim +{ + /** + * AIM ScreenName + * + * @var String + * @access private + */ + var $myScreenName; + + /** + * AIM Password (Plain Text) + * + * @var String + * @access private + */ + var $myPassword; + + + /** + * AIM TOC Server + * + * @var String + * @access public + */ + var $myServer="toc.oscar.aol.com"; + + /** + * AIM Formatted ScreenName + * + * @var String + * @access private + */ + var $myFormatSN; + + /** + * AIM TOC Server Port + * + * @var String + * @access public + */ + var $myPort="5190"; + + /** + * Profile Data + * Use setProfile() to update + * + * @var String + * @access private + */ + var $myProfile="Powered by phpTOCLib. Please visit http://sourceforge.net/projects/phptoclib for more information"; //The profile of the bot + + /** + * Socket Connection Resource ID + * + * @var Resource + * @access private + */ + var $myConnection; //Connection resource ID + + /** + * Roasted AIM Password + * + * @var String + * @access private + */ + var $myRoastedPass; + + /** + * Last Message Recieved From Server + * + * @var String + * @access private + */ + var $myLastReceived; + + /** + * Current Seq Number Used to Communicate with Server + * + * @var Integer + * @access private + */ + var $mySeqNum; + + /** + * Current Warning Level + * Getter: getWarning() + * Setter: setWarning() + * + * @var Integer + * @access private + */ + var $myWarnLevel; //Warning Level of the bot + + /** + * Auth Code + * + * @var Integer + * @access private + */ + var $myAuthCode; + + /** + * Buddies + * Getter: getBuddies() + * + * @var Array + * @access private + */ + var $myBuddyList; + + /** + * Blocked Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myBlockedList; + + /** + * Permited Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myPermitList; + + /** + * Permit/Deny Mode + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @var Integer + * @access private + */ + var $myPdMode; + + //Below variables added 4-29 by Jeremy: Implementing chat + + /** + * Contains Chat Room Info + * $myChatRooms['roomid'] = people in room + * + * @var Array + * @access private + */ + var $myChatRooms; + + //End of chat implementation + + + /** + * Event Handler Functions + * + * @var Array + * @access private + */ + var $myEventHandlers = array(); + + /** + * Array of direct connection objects(including file transfers) + * + * @var Array + * @access private + */ + var $myDirectConnections = array(); + + /** + * Array of the actual connections + * + * @var Array + * @access private + */ + var $myConnections = array(); + + /** + * The current state of logging + * + * @var Boolean + * @access private + */ + + var $myLogging = false; + + /** + * Constructor + * + * Permit/Deny Mode Options + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @param String $sn AIM Screenname + * @param String $password AIM Password + * @param Integer $pdmode Permit/Deny Mode + * @access public + */ + function Aim($sn, $password, $pdmode) + { + //Constructor assignment + $this->myScreenName = $this->normalize($sn); + $this->myPassword = $password; + $this->myRoastedPass = $this->roastPass($password); + $this->mySeqNum = 1; + $this->myConnection = 0; + $this->myWarnLevel = 0; + $this->myAuthCode = $this->makeCode(); + $this->myPdMode = $pdmode; + $this->myFormatSN = $this->myScreenName; + + $this->log("PHPTOCLIB v" . PHPTOCLIB_VERSION . " Object Created"); + + } + + /** + * Enables debug logging (Logging is disabled by default) + * + * + * @access public + * @return void + */ + + function setLogging($enable) + { + $this->myLogging=$enable; + } + + function log($data) + { + if($this->myLogging){ + error_log($data); + } + } + + /** + * Logs a packet + * + * + * @access private + * @param Array $packary Packet + * @param String $in Prepend + * @return void + */ + function logPacket($packary,$in) + { + if(!$this->myLogging || sizeof($packary)<=0 || (@strlen($packary['decoded'])<=0 && @isset($packary['decoded']))) + return; + $towrite=$in . ": "; + foreach($packary as $k=>$d) + { + $towrite.=$k . ":" . $d . "\r\n"; + } + $towrite.="\r\n\r\n"; + $this->log($towrite); + } + /** + * Roasts/Hashes Password + * + * @param String $password Password + * @access private + * @return String Roasted Password + */ + function roastPass($password) + { + $roaststring = 'Tic/Toc'; + $roasted_password = '0x'; + for ($i = 0; $i < strlen($password); $i++) + $roasted_password .= bin2hex($password[$i] ^ $roaststring[($i % 7)]); + return $roasted_password; + } + + /** + * Access Method for myScreenName + * + * @access public + * @param $formated Returns formatted Screenname if true as returned by server + * @return String Screenname + */ + function getMyScreenName($formated = false) + { + if ($formated) + { + return $this->myFormatSN; + } + else + { + return $this->normalize($this->myScreenName); + } + } + + /** + * Generated Authorization Code + * + * @access private + * @return Integer Auth Code + */ + function makeCode() + { + $sn = ord($this->myScreenName[0]) - 96; + $pw = ord($this->myPassword[0]) - 96; + $a = $sn * 7696 + 738816; + $b = $sn * 746512; + $c = $pw * $a; + + return $c - $a + $b + 71665152; + } + + + /** + * Reads from Socket + * + * @access private + * @return String Data + */ + function sflapRead() + { + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... Reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + + $header = fread($this->myConnection,SFLAP_HEADER_LEN); + + if (strlen($header) == 0) + { + $this->myLastReceived = ""; + return ""; + } + $header_data = unpack("aast/Ctype/nseq/ndlen", $header); + $this->log(" . ", false); + $packet = fread($this->myConnection, $header_data['dlen']); + if (strlen($packet) <= 0 && $sockinfo['blocked']) + $this->derror("Could not read data"); + + if ($header_data['type'] == SFLAP_TYPE_SIGNON) + { + $packet_data=unpack("Ndecoded", $packet); + } + + if ($header_data['type'] == SFLAP_TYPE_KEEPALIVE) + { + $this->myLastReceived = ''; + return 0; + } + else if (strlen($packet)>0) + { + $packet_data = unpack("a*decoded", $packet); + } + $this->log("socketcheck check now"); + if ($this->socketcheck($this->myConnection)) + { + $this->derror("Connection ended unexpectedly"); + } + + $data = array_merge($header_data, $packet_data); + $this->myLastReceived = $data; + $this->logPacket($data,"in"); + return $data; + } + + /** + * Sends Data on Socket + * + * @param String $sflap_type Type + * @param String $sflap_data Data + * @param boolean $no_null No Null + * @param boolean $formatted Format + * @access private + * @return String Roasted Password + */ + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $packet = ""; + if (strlen($sflap_data) >= MAX_PACKLENGTH) + $sflap_data = substr($sflap_data,0,MAX_PACKLENGTH); + + if ($formatted) + { + $len = strlen($sflap_len); + $sflap_header = pack("aCnn",'*', $sflap_type, $this->mySeqNum, $len); + $packet = $sflap_header . $sflap_data; + } else { + if (!$no_null) + { + $sflap_data = str_replace("\0","", trim($sflap_data)); + $sflap_data .= "\0"; + } + $data = pack("a*", $sflap_data); + $len = strlen($sflap_data); + $header = pack("aCnn","*", $sflap_type, $this->mySeqNum, $len); + $packet = $header . $data; + } + + //Make sure we are still connected + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + $sent = fputs($this->myConnection, $packet) or $this->derror("Error sending packet to AIM"); + $this->mySeqNum++; + sleep(ceil($this->myWarnLevel/10)); + $this->logPacket(array($sflap_type,$sflap_data),"out"); + } + + /** + * Escape the thing that TOC doesn't like,that would be + * ",', $,{,},[,] + * + * @param String $data Data to Escape + * @see decodeData + * @access private + * @return String $data Escaped Data + */ + function encodeData($data) + { + $data = str_replace('"','\"', $data); + $data = str_replace('$','\$', $data); + $data = str_replace("'","\'", $data); + $data = str_replace('{','\{', $data); + $data = str_replace('}','\}', $data); + $data = str_replace('[','\[', $data); + $data = str_replace(']','\]', $data); + return $data; + } + + /** + * Unescape data TOC has escaped + * ",', $,{,},[,] + * + * @param String $data Data to Unescape + * @see encodeData + * @access private + * @return String $data Unescape Data + */ + function decodeData($data) + { + $data = str_replace('\"','"', $data); + $data = str_replace('\$','$', $data); + $data = str_replace("\'","'", $data); + $data = str_replace('\{','{', $data); + $data = str_replace('\}','}', $data); + $data = str_replace('\[','[', $data); + $data = str_replace('\]',']', $data); + $data = str_replace('"','"', $data); + $data = str_replace('&','&', $data); + return $data; + } + + /** + * Normalize ScreenName + * no spaces and all lowercase + * + * @param String $nick ScreenName + * @access public + * @return String $nick Normalized ScreenName + */ + function normalize($nick) + { + $nick = str_replace(" ","", $nick); + $nick = strtolower($nick); + return $nick; + } + + /** + * Sets internal info with update buddy + * Currently only sets warning level + * + * @access public + * @return void + */ + function setMyInfo() + { + //Sets internal values bvase on the update buddy command + $this->log("Setting my warning level ..."); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_get_status " . $this->normalize($this->myScreenName),0,0); + //The rest of this will now be handled by the other functions. It is assumed + //that we may have other data queued in the socket, do we should just add this + //message to the queue instead of trying to set it in here + } + + /** + * Connects to AIM and Signs On Using Info Provided in Constructor + * + * @access public + * @return void + */ + function signon() + { + $this->log("Ready to sign on to the server"); + $this->myConnection = fsockopen($this->myServer, $this->myPort, $errno, $errstr,10) or die("$errorno:$errstr"); + $this->log("Connected to server"); + $this->mySeqNum = (time() % 65536); //Select an arbitrary starting point for + //sequence numbers + if (!$this->myConnection) + $this->derror("Error connecting to toc.oscar.aol.com"); + $this->log("Connected to AOL"); + //Send the flapon packet + fputs($this->myConnection,"FLAPON\r\n\n\0"); //send the initial handshake + $this->log("Sent flapon"); + $this->sflapRead(); //Make sure the server responds with what we expect + if (!$this->myLastReceived) + $this->derror("Error sending the initialization string"); + + //send the FLAP SIGNON packet back with what it needs + //There are 2 parts to the signon packet. They are sent in succession, there + //is no indication if either packet was correctly sent + $signon_packet = pack("Nnna".strlen($this->myScreenName),1,1,strlen($this->myScreenName), $this->myScreenName); + $this->sflapSend(SFLAP_TYPE_SIGNON, $signon_packet,1,0); + $this->log("sent signon packet part one"); + + $signon_packet_part2 = 'toc2_signon login.oscar.aol.com 29999 ' . $this->myScreenName . ' ' . $this->myRoastedPass . ' english-US "TIC:TOC2:REVISION" 160 ' . $this->myAuthCode; + $this->log($signon_packet_part2 . ""); + $this->sflapSend(SFLAP_TYPE_DATA, $signon_packet_part2,0,0); + $this->log("Sent signon packet part 2... Awaiting response..."); + + $this->sflapRead(); + $this->log("Received Sign on packet, beginning initilization..."); + $message = $this->getLastReceived(); + $this->log($message . "\n"); + if (strstr($message,"ERROR:")) + { + $this->onError($message); + die("Fatal signon error"); + } + stream_set_timeout($this->myConnection,2); + //The information sent before the config2 command is utterly useless to us + //So we will just skim through them until we reach it + + //Add the first entry to the connection array + $this->myConnections[] = $this->myConnection; + + + //UPDATED 4/12/03: Now this will use the receive function and send the + //received messaged to the assigned handlers. This is where the signon + //method has no more use + + $this->log("Done with signon proccess"); + //socket_set_blocking($this->myConnection,false); + } + + /** + * Sends Instant Message + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * @param boolean $auto Sent as Auto Response / Away Message Style + * @access public + * @return void + */ + function sendIM($to, $message, $auto = false) + { + if ($auto) $auto = "auto"; + else $auto = ""; + $to = $this->normalize($to); + $message = $this->encodeData($message); + $command = 'toc2_send_im "' . $to . '" "' . $message . '" ' . $auto; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $cleanedmessage = str_replace("
", " ", $this->decodeData($message)); + $cleanedmessage = strip_tags($cleanedmessage); + $this->log("TO - " . $to . " : " . $cleanedmessage); + } + + /** + * Set Away Message + * + * @param String $message Away message (some HTML supported). + * Use null to remove the away message + * @access public + * @return void + */ + function setAway($message) + { + $message = $this->encodeData($message); + $command = 'toc_set_away "' . $message . '"'; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $this->log("SET AWAY MESSAGE - " . $this->decodeData($message)); + } + + /** + * Fills Buddy List + * Not implemented fully yet + * + * @access public + * @return void + */ + function setBuddyList() + { + //This better be the right message + $message = $this->myLastReceived['decoded']; + if (strpos($message,"CONFIG2:") === false) + { + $this->log("setBuddyList cannot be called at this time because I got $message"); + return false; + } + $people = explode("\n",trim($message,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + for($i = 1; $imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + // + } + } + } + + /** + * Adds buddy to Permit list + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function addPermit($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($buddy),0,0); + $this->myPermitList[] = $this->normalize($buddy); + return 1; + } + + /** + * Blocks buddy + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function blockBuddy($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_deny " . $this->normalize($buddy),0,0); + $this->myBlockedList[] = $this->normalize($buddy); + return 1; + } + + /** + * Returns last message received from server + * + * @access private + * @return String Last Message from Server + */ + function getLastReceived() + { + if (@$instuff = $this->myLastReceived['decoded']){ + return $this->myLastReceived['decoded']; + }else{ + return; + } + } + + /** + * Returns Buddy List + * + * @access public + * @return array Buddy List + */ + function getBuddies() + { + return $this->myBuddyList; + } + + /** + * Returns Permit List + * + * @access public + * @return array Permit List + */ + function getPermit() + { + return $this->myPermitList; + } + + /** + * Returns Blocked Buddies + * + * @access public + * @return array Blocked List + */ + function getBlocked() + { + return $this->myBlockedList; + } + + + + + /** + * Reads and returns data from server + * + * This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data'] + * portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this + * function instead. This function has a return value. Calling this prevents the need to call + * $Aim->getLastReceived() + * + * @access public + * @return String Data recieved from server + */ + function read_from_aim() + { + $this->sflapRead(); + $returnme = $this->getLastReceived(); + return $returnme; + } + + /** + * Sets current internal warning level + * + * This allows you to update the bots warning level when warned. + * + * @param int Warning Level % + * @access private + * @return void + */ + function setWarningLevel($warnlevel) + { + $this->myWarnLevel = $warnlevel; + } + + /** + * Warns / "Evils" a User + * + * To successfully warn another user they must have sent you a message. + * There is a limit on how much and how often you can warn another user. + * Normally when you warn another user they are aware who warned them, + * however there is the option to warn anonymously. When warning anon. + * note that the warning is less severe. + * + * @param String $to Screenname to warn + * @param boolean $anon Warn's anonymously if true. (default = false) + * @access public + * @return void + */ + function warnUser($to, $anon = false) + { + if (!$anon) + $anon = '"norm"'; + + else + $anon = '"anon"'; + $this->sflapSend(SFLAP_TYPE_DATA,"toc_evil " . $this->normalize($to) . " $anon",0,0); + } + + /** + * Returns warning level of bot + * + * @access public + * @return void + */ + function getWarningLevel() + { + return $this->myWarningLevel; + } + + /** + * Sets bot's profile/info + * + * Limited to 1024 bytes. + * + * @param String $profiledata Profile Data (Can contain limited html: br,hr,font,b,i,u etc) + * @param boolean $poweredby If true, appends link to phpTOCLib project to profile + * @access public + * @return void + */ + function setProfile($profiledata, $poweredby = false) + { + if ($poweredby == false){ + $this->myProfile = $profiledata; + }else{ + $this->myProfile = $profiledata . "

Powered by phpTOCLib
http://sourceforge.net/projects/phptoclib
"; + } + + $this->sflapSend(SFLAP_TYPE_DATA,"toc_set_info \"" . $this->encodeData($this->myProfile) . "\"",0,0); + $this->setMyInfo(); + $this->log("Profile has been updated..."); + } + + //6/29/04 by Jeremy: + //Added mthod to accept a rvous,decline it, and + //read from the rvous socket + + //Decline + + /** + * Declines a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * + * @access public + * @return void + */ + function declineRvous($nick, $cookie, $uuid) + { + $nick = $this->normalize($nick); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_cancel $nick $cookie $uuid",0,0); + } + + /** + * Accepts a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * @param String $vip IP of User DC with + * @param int $port Port number to connect to + * + * @access public + * @return void + */ + function acceptRvous($nick, $cookie, $uuid, $vip, $port) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_accept $nick $cookie $uuid",0,0); + + //Now open the connection to that user + if ($uuid == IMAGE_UID) + { + $dcon = new Dconnect($vip, $port); + } + else if ($uuid == FILE_SEND_UID) + { + $dcon = new FileSendConnect($vip, $port); + } + if (!$dcon->connected) + { + $this->log("The connection failed"); + return false; + } + + //Place this dcon object inside the array + $this->myDirectConnections[] = $dcon; + //Place the socket in an array to + $this->myConnections[] = $dcon->sock; + + + //Get rid of the first packet because its worthless + //and confusing + $dcon->readDIM(); + //Assign the cookie + $dcon->cookie = $dcon->lastReceived['cookie']; + $dcon->connectedTo = $this->normalize($nick); + return $dcon; + } + + /** + * Sends a Message over a Direct Connection + * + * Only works if a direct connection is already established with user + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * + * @access public + * @return void + */ + function sendDim($to, $message) + { + //Find the connection + for($i = 0;$imyDirectConnections);$i++) + { + if ($this->normalize($to) == $this->myDirectConnections[$i]->connectedTo && $this->myDirectConnections[$i]->type == CONN_TYPE_DC) + { + $dcon = $this->myDirectConnections[$i]; + break; + } + } + if (!$dcon) + { + $this->log("Could not find a direct connection to $to"); + return false; + } + $dcon->sendMessage($message, $this->normalize($this->myScreenName)); + return true; + } + + /** + * Closes an established Direct Connection + * + * @param DConnect $dcon Direct Connection Object to Close + * + * @access public + * @return void + */ + function closeDcon($dcon) + { + + $nary = array(); + for($i = 0;$imyConnections);$i++) + { + if ($dcon->sock == $this->myConnections[$i]) + unset($this->myConnections[$i]); + } + + $this->myConnections = array_values($this->myConnections); + unset($nary); + $nary2 = array(); + + for($i = 0;$imyDirectConnections);$i++) + { + if ($dcon == $this->myDirectConnections[$i]) + unset($this->myDirectConnections[$i]); + } + $this->myDirectConnections = array_values($this->myDirectConnections); + $dcon->close(); + unset($dcon); + } + + //Added 4/29/04 by Jeremy: + //Various chat related methods + + /** + * Accepts a Chat Room Invitation (Joins room) + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function joinChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_accept " . $chatid,0,0); + } + + /** + * Leaves a chat room + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function leaveChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_leave " . $chatid,0,0); + } + + /** + * Sends a message in a chat room + * + * @param String $chatid ID of Chat Room + * @param String $message Message to send + * + * @access public + * @return void + */ + function chatSay($chatid, $message) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_send " . $chatid . " \"" . $this->encodeData($message) . "\"",0,0); + } + + /** + * Invites a user to a chat room + * + * @param String $chatid ID of Chat Room + * @param String $who Screenname of user + * @param String $message Note to include with invitiation + * + * @access public + * @return void + */ + function chatInvite($chatid, $who, $message) + { + $who = $this->normalize($who); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_invite " . $chatid . " \"" . $this->encodeData($message) . "\" " . $who,0,0); + } + + /** + * Joins/Creates a new chat room + * + * @param String $name Name of the new chat room + * @param String $exchange Exchange of new chat room + * + * @access public + * @return void + */ + function joinNewChat($name, $exchange) + { + //Creates a new chat + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_join " . $exchange . " \"" . $name . "\"",0,0); + } + + /** + * Disconnect error handler, attempts to reconnect in 60 seconds + * + * @param String $message Error message (desc of where error encountered etc) + * + * @access private + * @return void + */ + function derror($message) + { + $this->log($message); + $this->log("Error"); + fclose($this->myConnection); + if ((time() - $GLOBALS['errortime']) < 600){ + $this->log("Reconnecting in 60 Seconds"); + sleep(60); + } + $this->signon(); + $GLOBALS['errortime'] = time(); + } + + /** + * Returns connection type of socket (main or rvous etc) + * + * Helper method for recieve() + * + * @param Resource $sock Socket to determine type for + * + * @access private + * @return void + * @see receive + */ + function connectionType($sock) + { + //Is it the main connection? + if ($sock == $this->myConnection) + return CONN_TYPE_NORMAL; + else + { + for($i = 0;$imyDirectConnections);$i++) + { + if ($sock == $this->myDirectConnections[$i]->sock) + return $this->myDirectConnections[$i]->type; + } + } + return false; + } + + /** + * Checks for new data and calls appropriate methods + * + * This method is usually called in an infinite loop to keep checking for new data + * + * @access public + * @return void + * @see connectionType + */ + function receive() + { + //This function will be used to get the incoming data + //and it will be used to call the event handlers + + //First, get an array of sockets that have data that is ready to be read + $ready = array(); + $ready = $this->myConnections; + $numrdy = stream_select($ready, $w = NULL, $x = NULL,NULL); + + //Now that we've waited for something, go through the $ready + //array and read appropriately + + for($i = 0;$iconnectionType($ready[$i]); + if ($type == CONN_TYPE_NORMAL) + { + //Next step:Get the data sitting in the socket + $message = $this->read_from_aim(); + if (strlen($message) <= 0) + { + return; + } + + //Third step: Get the command from the server + @list($cmd, $rest) = explode(":", $message); + + //Fourth step, take the command, test the type, and pass it off + //to the correct internal handler. The internal handler will + //do what needs to be done on the class internals to allow + //it to work, then proceed to pass it off to the user created handle + //if there is one + $this->log($cmd); + switch($cmd) + { + case 'SIGN_ON': + $this->onSignOn($message); + break; + case 'CONFIG2': + $this->onConfig($message); + break; + case 'ERROR': + $this->onError($message); + break; + case 'NICK': + $this->onNick($message); + break; + case 'IM_IN2': + $this->onImIn($message); + break; + case 'UPDATE_BUDDY2': + $this->onUpdateBuddy($message); + break; + case 'EVILED': + $this->onWarn($message); + break; + case 'CHAT_JOIN': + $this->onChatJoin($message); + break; + case 'CHAT_IN': + $this->onChatIn($message); + break; + case 'CHAT_UPDATE_BUDDY': + $this->onChatUpdate($message); + break; + case 'CHAT_INVITE': + $this->onChatInvite($message); + break; + case 'CHAT_LEFT': + $this->onChatLeft($message); + break; + case 'GOTO_URL': + $this->onGotoURL($message); + break; + case 'DIR_STATUS': + $this->onDirStatus($message); + break; + case 'ADMIN_NICK_STATUS': + $this->onAdminNick($message); + break; + case 'ADMIN_PASSWD_STATUS': + $this->onAdminPasswd($message); + break; + case 'PAUSE': + $this->onPause($message); + break; + case 'RVOUS_PROPOSE': + $this->onRvous($message); + break; + default: + $this->log("Fell through: $message"); + $this->CatchAll($message); + break; + } + } + else + { + for($j = 0;$jmyDirectConnections);$j++) + { + if ($this->myDirectConnections[$j]->sock == $ready[$i]) + { + $dcon = $this->myDirectConnections[$j]; + break; + } + } + //Now read from the dcon + if ($dcon->type == CONN_TYPE_DC) + { + if ($dcon->readDIM() == false) + { + $this->closeDcon($dcon); + continue; + } + + $message['message'] = $dcon->lastMessage; + if ($message['message'] == "too big") + { + $this->sendDim("Connection dropped because you sent a message larger that " . MAX_DCON_SIZE . " bytes.", $dcon->connectedTo); + $this->closeDcon($dcon); + continue; + } + $message['from'] = $dcon->connectedTo; + $this->onDimIn($message); + } + } + } + $this->conn->myLastReceived=""; + //Now get out of this function because the handlers should take care + //of everything + } + + //The next block of code is all the event handlers needed by the class + //Some are left blank and only call the users handler because the class + //either does not support the command, or cannot do anything with it + // --------------------------------------------------------------------- + + /** + * Direct IM In Event Handler + * + * Called when Direct IM is received. + * Call's user handler (if available) for DimIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDimIn($data) + { + $this->callHandler("DimIn", $data); + } + + /** + * Sign On Event Handler + * + * Called when Sign On event occurs. + * Call's user handler (if available) for SIGN_ON. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onSignOn($data) + { + $this->callHandler("SignOn", $data); + } + + /** + * Config Event Handler + * + * Called when Config data received. + * Call's user handler (if available) for Config. + * + * Loads buddy list and other info + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onConfig($data) + { + $this->log("onConfig Message: " . $data); + + if (strpos($data,"CONFIG2:") === false) + { + $this->log("get_buddy_list cannot be called at this time because I got $data"); + //return false; + } + $people = explode("\n",trim($data,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + + //AIM decided to add this wonderful new feature, the recent buddy thing, this kind of + //messes this funtion up, so we need to adapt it... unfortuneately, its not really + //clear how this works, so we are just going to add their name to the permit list. + + //Recent buddies I believe are in the format + //number:name:number.... I think the first number counts down from 25 how long its + //been... but I don't know the second number,,,, + + //TODO: Figure out the new recent buddies system + + //Note: adding that at the bottom is a quick hack and may have adverse consequences... + for($i = 1;$imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + //This is assumed to be recent buddies... + $this->myPermitList[]=$name; + } + } + + //We only get the config message once, so now we should send our pd mode + + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_set_pdmode " . $this->myPdMode,0,0); + //Adds yourself to the permit list + //This is to fix an odd behavior if you have nobody on your list + //the server won't send the config command... so this takes care of it + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($this->myScreenName),0,0); + + //Now we allow the user to send a list, update anything they want, etc + $this->callHandler("Config", $data); + //Now that we have taken care of what the user wants, send the init_done message + $this->sflapSend(SFLAP_TYPE_DATA,"toc_init_done",0,0); + //'VOICE_UID' + //'FILE_GET_UID' + //'IMAGE_UID' + //'BUDDY_ICON_UID' + //'STOCKS_UID' + //'GAMES_UID' + $this->sflapSend(SFLAP_TYPE_DATA, "toc_set_caps " . IMAGE_UID . " " . FILE_SEND_UID ." " . FILE_GET_UID . " " . BUDDY_ICON_UID . "",0,0); + } + + + /** + * Error Event Handler + * + * Called when an Error occurs. + * Call's user handler (if available) for Error. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onError($data) + { + static $errarg = ''; + static $ERRORS = array( + 0=>'Success', + 901 =>'$errarg not currently available', + 902 =>'Warning of $errarg not currently available', + 903 =>'A message has been dropped, you are exceeding + the server speed limit', + 911 =>'Error validating input', + 912 =>'Invalid account', + 913 =>'Error encountered while processing request', + 914 =>'Service unavailable', + 950 =>'Chat in $errarg is unavailable.', + 960 =>'You are sending message too fast to $errarg', + 961 =>'You missed an im from $errarg because it was too big.', + 962 =>'You missed an im from $errarg because it was sent too fast.', + 970 =>'Failure', + 971 =>'Too many matches', + 972 =>'Need more qualifiers', + 973 =>'Dir service temporarily unavailable', + 974 =>'Email lookup restricted', + 975 =>'Keyword Ignored', + 976 =>'No Keywords', + 977 =>'Language not supported', + 978 =>'Country not supported', + 979 =>'Failure unknown $errarg', + 980 =>'Incorrect nickname or password.', + 981 =>'The service is temporarily unavailable.', + 982 =>'Your warning level is currently too high to sign on.', + 983 =>'You have been connecting and + disconnecting too frequently. Wait 10 minutes and try again. + If you continue to try, you will need to wait even longer.', + 989 =>'An unknown signon error has occurred $errarg' + ); + $data_array = explode(":", $data); + for($i=0; $ilog($errorstring . "\n"); + + $this->callHandler("Error", $data); + } + + /** + * Nick Event Handler + * + * Called when formatted own ScreenName is receieved + * Call's user handler (if available) for Nick. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onNick($data) + { + //This is our nick, so set a field called "myFormatSN" which will represent + //the actual name given by the server to us, NOT the normalized screen name + @list($cmd, $nick) = explode(":", $data); + $this->myFormatSN = $nick; + + $this->callHandler("Nick", $data); + } + + /** + * IM In Event Handler + * + * Called when an Instant Message is received. + * Call's user handler (if available) for IMIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onImIn($data) + { + //Perhaps we should add an internal log for debugging purposes?? + //But now, this should probably be handled by the user purely + + $this->callHandler("IMIn", $data); + } + + /** + * UpdateBuddy Event Handler + * + * Called when a Buddy Update is receieved. + * Call's user handler (if available) for UpdateBuddy. + * If info is about self, updates self info (Currently ownly warning). + * + * ToDo: Keep track of idle, warning etc on Buddy List + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onUpdateBuddy($data) + { + //Again, since our class currently does not deal with other people without + //outside help, then this is also probably best left to the user. Though + //we should probably allow this to replace the setMyInfo function above + //by handling the input if and only if it is us + //Check and see that this is the command expected + if (strpos($data,"UPDATE_BUDDY2:") == -1) + { + $this->log("A different message than expected was received"); + return false; + } + + //@list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + + //@list($cmd, $sn, $online, $warning, $starttime, $idletime, $uc) = explode(":", $data); + $info = $this->getMessageInfo($data); + if ($this->normalize($info['sn']) == $this->normalize($this->myScreenName)) + { + $warning = rtrim($info['warnlevel'],"%"); + $this->myWarnLevel = $warning; + $this->log("My warning level is $this->myWarnLevel %"); + } + + $this->callHandler("UpdateBuddy", $data); + } + + /** + * Warning Event Handler + * + * Called when bot is warned. + * Call's user handler (if available) for Warn. + * Updates internal warning level + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onWarn($data) + { + /* + For reference: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + */ + //SImply update our warning level + //@list($cmd, $newwarn, $user) = explode(":", $data); + + $info = $this->getMessageInfo($data); + + $this->setWarningLevel(trim($info['warnlevel'],"%")); + $this->log("My warning level is $this->myWarnLevel %"); + + $this->callHandler("Warned", $data); + } + + /** + * Chat Join Handler + * + * Called when bot joins a chat room. + * Call's user handler (if available) for ChatJoin. + * Adds chat room to internal chat room list. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatJoin($data) + { + @list($cmd, $rmid, $rmname) = explode(":", $data); + $this->myChatRooms[$rmid] = 0; + + $this->callHandler("ChatJoin", $data); + } + + /** + * Returns number of chat rooms bot is in + * + * @access public + * @param String $data Raw message from server + * @return int + */ + function getNumChats() + { + return count($this->myChatRooms); + } + + /** + * Chat Update Handler + * + * Called when bot received chat room data (user update). + * Call's user handler (if available) for ChatUpdate. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatUpdate($data) + { + $stuff = explode(":", $data); + $people = sizeof($stuff); + $people -= 2; + + $this->callHandler("ChatUpdate", $data); + } + + /** + * Chat Message In Handler + * + * Called when chat room message is received. + * Call's user handler (if available) for ChatIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatIn($data) + { + $this->callHandler("ChatIn", $data); + } + + + /** + * Chat Invite Handler + * + * Called when bot is invited to a chat room. + * Call's user handler (if available) for ChatInvite. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatInvite($data) + { + //@list($cmd, $name, $id, $from, $data) = explode(":", $data,6); + //$data = explode(":",$data,6); + //$nm = array(); + //@list($nm['cmd'],$nm['name'],$nm['id'],$nm['from'],$nm['message']) = $data; + + + $this->callHandler("ChatInvite", $data); + } + + /** + * Chat Left Handler + * + * Called when bot leaves a chat room + * Call's user handler (if available) for ChatLeft. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatLeft($data) + { + $info = $this->getMessageInfo($data); + unset($this->myChatRooms[$info['chatid']]); + $this->callHandler("ChatLeft", $data); + } + + /** + * Goto URL Handler + * + * Called on GotoURL. + * Call's user handler (if available) for GotoURL. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onGotoURL($data) + { + //This is of no use to the internal class + + $this->callHandler("GotoURL", $data); + } + + /** + * Dir Status Handler + * + * Called on DirStatus. + * Call's user handler (if available) for DirStatus. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDirStatus($data) + { + //This is not currently suported + + $this->callHandler("DirStatus", $data); + } + + /** + * AdminNick Handler + * + * Called on AdminNick. + * Call's user handler (if available) for AdminNick. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminNick($data) + { + //NOt particularly useful to us + $this->callHandler("AdminNick", $data); + } + + /** + * AdminPasswd Handler + * + * Called on AdminPasswd. + * Call's user handler (if available) for AdminPasswd. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminPasswd($data) + { + //Also not particlualry useful to the internals + $this->callHandler("AdminPasswd", $data); + } + + /** + * Pause Handler + * + * Called on Pause. + * Call's user handler (if available) for Pause. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onPause($data) + { + //This is pretty useless to us too... + + $this->callHandler("Pause", $data); + } + + /** + * Direct Connection Handler + * + * Called on Direct Connection Request(Rvous). + * Call's user handler (if available) for Rvous. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onRvous($data) + { + $this->callHandler("Rvous", $data); + } + + /** + * CatchAll Handler + * + * Called for unrecognized commands. + * Logs unsupported messages to array. + * Call's user handler (if available) for CatchAll. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function CatchAll($data) + { + //Add to a log of unsupported messages. + + $this->unsupported[] = $data; + //$this->log($data); + //print_r($data); + + $this->callHandler("CatchAll", $data); + } + + /** + * Calls User Handler + * + * Calls registered handler for a specific event. + * + * @access private + * @param String $event Command (event) name (Rvous etc) + * @param String $data Raw message from server + * @see registerHandler + * @return void + */ + function callHandler($event, $data) + { + + if (isset($this->myEventHandlers[$event])) + { + //$function = $this->myEventHandlers[$event] . "(\$data);"; + //eval($function); + call_user_func($this->myEventHandlers[$event], $data); + } + else + { + $this->noHandler($data); + } + } + + /** + * Registers a user handler + * + * Handler List + * SignOn, Config, ERROR, NICK, IMIn, UpdateBuddy, Eviled, Warned, ChatJoin + * ChatIn, ChatUpdate, ChatInvite, ChatLeft, GotoURL, DirStatus, AdminNick + * AdminPasswd, Pause, Rvous, DimIn, CatchAll + * + * @access private + * @param String $event Event name + * @param String $handler User function to call + * @see callHandler + * @return boolean Returns true if successful + */ + function registerHandler($event, $handler) + { + if (is_callable($handler)) + { + $this->myEventHandlers[$event] = $handler; + return true; + } + else + { + return false; + } + } + + /** + * No user handler method fall back. + * + * Does nothing with message. + * + * @access public + * @param String $message Raw server message + * @return void + */ + function noHandler($message) + { + //This function intentionally left blank + //This is where the handlers will fall to for now. I plan on including a more + //efficent check to avoid the apparent stack jumps that this code will produce + //But for now, just fall into here, and be happy + return; + } + + //GLOBAL FUNCTIONS + + /** + * Finds type, and returns as part of array ['type'] + * Puts message in ['incoming'] + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $message Raw server message + * @see msg_parse + * @see getMessageInfo + * @return array + */ + static function msg_type($message) + { + $command = array(); + @list($cmd, $rest) = explode(":", $message); + switch($cmd) + { + case 'IM_IN2': + $type = AIM_TYPE_MSG; + break; + + case 'UPDATE_BUDDY2': + $type = AIM_TYPE_UPDATEBUDDY; + break; + + case 'EVILED': + $type = AIM_TYPE_WARN; + break; + + case 'SIGN_ON': + $type = AIM_TYPE_SIGNON; + break; + + case 'NICK': + $type = AIM_TYPE_NICK; + break; + + case 'ERROR': + $type = AIM_TYPE_ERROR; + break; + + case 'CHAT_JOIN': + $type = AIM_TYPE_CHATJ; + break; + + case 'CHAT_IN': + $type = AIM_TYPE_CHATI; + break; + + case 'CHAT_UPDATE_BUDDY': + $type = AIM_TYPE_CHATUPDBUD; + break; + + case 'CHAT_INVITE': + $type = AIM_TYPE_CHATINV; + break; + + case 'CHAT_LEFT': + $type = AIM_TYPE_CHATLE; + break; + + case 'GOTO_URL': + $type = AIM_TYPE_URL; + break; + + case 'ADMIN_NICK_STATUS': + $type = AIM_TYPE_NICKSTAT; + break; + + case 'ADMIN_PASSWD_STATUS': + $type = AIM_TYPE_PASSSTAT; + break; + + case 'RVOUS_PROPOSE': + $type = AIM_TYPE_RVOUSP; + break; + + default: + $type = AIM_TYPE_NOT_IMPLEMENTED; + break; + } + $command['type'] = $type; + $command['incoming'] = $message; + return $command; + } + + /** + * Parses message and splits into info array + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $command Message and type (after msg_type) + * @see msg_type + * @see getMessageInfo + * @return array + */ + static function msg_parse($command) + { + $info = array(); + switch($command['type']) + { + case AIM_TYPE_WARN: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + + break; + + case AIM_TYPE_MSG: + $it = explode(":", $command['incoming'],5); + $info['auto'] = $it[2]; + $info['from'] = $it[1]; + $info['message'] = $it[4]; + break; + + case AIM_TYPE_UPDATEBUDDY: + @list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_SIGNON: + @list($cmd, $info['version']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_NICK: + @list($cmd, $info['nickname']) = explode(":", $command['incoming']); + break; + case AIM_TYPE_ERROR: + @list($cmd, $info['errorcode'], $info['args']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATJ: + @list($cmd, $info['chatid'], $info['chatname']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATI: + @list($cmd, $info['chatid'], $info['user'], $info['whisper'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATUPDBUD: + @list($cmd, $info['chatid'], $info['inside'], $info['userlist']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_CHATINV: + @list($cmd, $info['chatname'], $info['chatid'], $info['from'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATLE: + @list($cmd, $info['chatid']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_URL: + @list($cmd, $info['windowname'], $info['url']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_RVOUSP: + @list($cmd,$info['user'],$info['uuid'],$info['cookie'],$info['seq'],$info['rip'],$info['pip'],$info['vip'],$info['port'],$info['tlvs']) = explode(":",$command['incoming'],10); + break; + + case AIM_TYPE_NICKSTAT: + case AIM_TYPE_PASSSTAT: + @list($cmd, $info['returncode'], $info['opt']) = explode(":", $command['incoming'],3); + break; + + default: + $info['command'] = $command['incoming']; + } + return $info; + } + + /** + * Returns a parsed message + * + * Calls msg_parse(msg_type( to first determine message type and then parse accordingly + * + * @access public + * @param String $command Raw server message + * @see msg_type + * @see msg_parse + * @return array + */ + static function getMessageInfo($message) + { + return self::msg_parse(self::msg_type($message)); + } + + /** + * Checks socket for end of file + * + * @access public + * @param Resource $socket Socket to check + * @return boolean true if end of file (socket) + */ + static function socketcheck($socket){ + $info = stream_get_meta_data($socket); + return $info['eof']; + //return(feof($socket)); + } +} + +?> diff --git a/plugins/Aim/extlib/phptoclib/dconnection.php b/plugins/Aim/extlib/phptoclib/dconnection.php new file mode 100755 index 000000000..c6be25ffb --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/dconnection.php @@ -0,0 +1,229 @@ +connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + /* + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return false; + } + */ + $head=fread($this->sock,6); + if(strlen($head)<=0) + { + sEcho("The direct connection has been closed"); + return false; + } + $minihead=unpack("a4ver/nsize",$head); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0 && $allheader['len'] <= MAX_DIM_SIZE) + { + $left=$allheader['len']; + $stuff=""; + $nonin=0; + while(strlen($stuff) < $allheader['len'] && $nonin<3) + { + $stuffg=fread($this->sock,$left); + if(strlen($stuffg)<0) + { + $nonin++; + continue; + } + $left=$left - strlen($stuffg); + $stuff.=$stuffg; + } + $data=unpack("a*decoded",$stuff); + } + + else if($allheader['len'] > MAX_DIM_SIZE) + { + $data['decoded']="too big"; + } + + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + $w=array($this->sock); + stream_select($r=NULL,$w,$e=NULL,NULL); + //Now send it all + fputs($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,0,20000); + return ($changed>0); + } + + function close() + { + $this->connected=false; + return fclose($this->sock); + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed"); + $this->sock=null; + return false; + } + return true; + } +} + + +class FileSendConnect +{ + var $sock; + var $lastReceived; + var $lastMessage; + var $connected; + var $cookie; + var $tpye=3; + + + function FileSendConnect($ip,$port) + { + if(!$this->connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return; + } + + $minihead=unpack("a4ver/nsize",fread($this->sock,6)); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0) + $data=unpack("a*decoded",fread($this->sock,$allheader['len'])); + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + + //Now send it all + fwrite($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,1); + return ($changed>0); + } + + function close() + { + $this->connected=false; + fclose($this->sock); + unset($this->sock); + return true; + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed to" . $ip . ":" . $port); + $this->sock=null; + return false; + } + return true; + } +} diff --git a/plugins/Imap/imapmanager.php b/plugins/Imap/imapmanager.php index e4fda5809..4c0edeaa1 100644 --- a/plugins/Imap/imapmanager.php +++ b/plugins/Imap/imapmanager.php @@ -57,12 +57,14 @@ class ImapManager extends IoManager } /** - * Tell the i/o master we need one instance for each supporting site - * being handled in this process. + * Tell the i/o master we need one instance globally. + * Since this is a plugin manager, the plugin class itself will + * create one instance per site. This prevents the IoMaster from + * making more instances. */ public static function multiSite() { - return IoManager::INSTANCE_PER_SITE; + return IoManager::GLOBAL_SINGLE_ONLY; } /** diff --git a/plugins/Xmpp/Fake_XMPP.php b/plugins/Xmpp/Fake_XMPP.php new file mode 100644 index 000000000..a0f329ca0 --- /dev/null +++ b/plugins/Xmpp/Fake_XMPP.php @@ -0,0 +1,104 @@ +. + * + * @category Network + * @package StatusNet + * @author Brion Vibber + * @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); +} + +class Fake_XMPP extends XMPPHP_XMPP +{ + public $would_be_sent = null; + + /** + * 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); + // Normally the fulljid isn't filled out until resource binding time; + // we need to save it here 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) + { + $this->would_be_sent = $msg; + } + + //@{ + /** + * Stream i/o functions disabled; only do output + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function disconnect() + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function process() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function processUntil($event, $timeout=-1) + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function read() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function readyToProcess() + { + throw new Exception("Can't read stream from fake XMPP."); + } + //@} +} + diff --git a/plugins/Xmpp/README b/plugins/Xmpp/README new file mode 100644 index 000000000..9bd71e980 --- /dev/null +++ b/plugins/Xmpp/README @@ -0,0 +1,35 @@ +The XMPP plugin allows users to send and receive notices over the XMPP/Jabber/GTalk network. + +Installation +============ +add "addPlugin('xmpp', + array('setting'=>'value', 'setting2'=>'value2', ...);" +to the bottom of your config.php + +The daemon included with this plugin must be running. It will be started by +the plugin along with their other daemons when you run scripts/startdaemons.sh. +See the StatusNet README for more about queuing and daemons. + +Settings +======== +user*: user part of the jid +server*: server part of the jid +resource: resource part of the jid +port (5222): port on which to connect to the server +encryption (true): use encryption on the connection +host (same as server): host to connect to. Usually, you won't set this. +debug (false): log extra debug info +public: list of jid's that should get the public feed (firehose) + +* required +default values are in (parenthesis) + +Example +======= +addPlugin('xmpp', array( + 'user=>'update', + 'server=>'identi.ca', + 'password'=>'...', + 'public'=>array('bob@aol.com', 'sue@google.com') +)); + diff --git a/plugins/Xmpp/Sharing_XMPP.php b/plugins/Xmpp/Sharing_XMPP.php new file mode 100644 index 000000000..4b69125da --- /dev/null +++ b/plugins/Xmpp/Sharing_XMPP.php @@ -0,0 +1,43 @@ +. + * + * @category Jabber + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +class Sharing_XMPP extends XMPPHP_XMPP +{ + function getSocket() + { + return $this->socket; + } +} diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php new file mode 100644 index 000000000..b9551b852 --- /dev/null +++ b/plugins/Xmpp/XmppPlugin.php @@ -0,0 +1,247 @@ +. + * + * @category IM + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Plugin for XMPP + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class XmppPlugin extends ImPlugin +{ + public $server = null; + public $port = 5222; + public $user = 'update'; + public $resource = null; + public $encryption = true; + public $password = null; + public $host = null; // only set if != server + public $debug = false; // print extra debug info + + public $transport = 'xmpp'; + + protected $fake_xmpp; + + function getDisplayName(){ + return _m('XMPP/Jabber/GTalk'); + } + + function normalize($screenname) + { + if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $screenname, $matches)) { + $node = $matches[1]; + $server = $matches[2]; + return strtolower($node.'@'.$server); + } else { + return null; + } + } + + function daemon_screenname() + { + $ret = $this->user . '@' . $this->server; + if($this->resource) + { + return $ret . '/' . $this->resource; + }else{ + return $ret; + } + } + + function validate($screenname) + { + // Cheap but effective + return Validate::email($screenname); + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'Sharing_XMPP': + case 'Fake_XMPP': + include_once $dir . '/'.$cls.'.php'; + return false; + case 'XmppManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + function onStartImDaemonIoManagers(&$classes) + { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new XmppManager($this); // handles pings/reconnects + return true; + } + + function microiduri($screenname) + { + return 'xmpp:' . $screenname; + } + + function send_message($screenname, $body) + { + $this->fake_xmpp->message($screenname, $body, 'chat'); + $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + return true; + } + + function send_notice($screenname, $notice) + { + $msg = $this->format_notice($notice); + $entry = $this->format_entry($notice); + + $this->fake_xmpp->message($screenname, $msg, 'chat', null, $entry); + $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + 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 format_entry($notice) + { + $profile = $notice->getProfile(); + + $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; + } + + function receive_raw_message($pl) + { + $from = $this->normalize($pl['from']); + + if ($pl['type'] != 'chat') { + common_log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from."); + return true; + } + + if (mb_strlen($pl['body']) == 0) { + common_log(LOG_WARNING, "Ignoring message with empty body from $from."); + return true; + } + + return $this->handle_incoming($from, $pl['body']); + } + + function initialize(){ + if(!isset($this->server)){ + throw new Exception("must specify a server"); + } + if(!isset($this->port)){ + throw new Exception("must specify a port"); + } + if(!isset($this->user)){ + throw new Exception("must specify a user"); + } + if(!isset($this->password)){ + throw new Exception("must specify a password"); + } + + $this->fake_xmpp = new Fake_XMPP($this->host ? + $this->host : + $this->server, + $this->port, + $this->user, + $this->password, + $this->resource, + $this->server, + $this->debug ? + true : false, + $this->debug ? + XMPPHP_Log::LEVEL_VERBOSE : null + ); + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'XMPP', + 'version' => STATUSNET_VERSION, + 'author' => 'Craig Andrews, Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:XMPP', + 'rawdescription' => + _m('The XMPP plugin allows users to send and receive notices over the XMPP/Jabber network.')); + return true; + } +} + diff --git a/plugins/Xmpp/xmppmanager.php b/plugins/Xmpp/xmppmanager.php new file mode 100644 index 000000000..87d818668 --- /dev/null +++ b/plugins/Xmpp/xmppmanager.php @@ -0,0 +1,279 @@ +. + */ + +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 handled. + * + * 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 ImManager +{ + protected $lastping = null; + protected $pingid = null; + + public $conn = null; + + const PING_INTERVAL = 120; + + + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + function send_raw_message($data) + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $this->conn->send($data); + return true; + } + + /** + * 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; + } + + /** + * Process XMPP events that have come in over the wire. + * @fixme may kill process on XMPP error + * @param resource $socket + */ + public function handleInput($socket) + { + # Process the queue for as long as needed + try { + 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()); + } + } + + /** + * Lists the IM 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() + { + $this->connect(); + if($this->conn){ + return array($this->conn->getSocket()); + }else{ + return array(); + } + } + + /** + * 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) + { + $now = time(); + if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { + try { + $this->send_ping(); + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); + die($e->getMessage()); + } + } + } + + function connect() + { + if (!$this->conn || $this->conn->isDisconnected()) { + $resource = 'queue' . posix_getpid(); + $this->conn = new Sharing_XMPP($this->plugin->host ? + $this->plugin->host : + $this->plugin->server, + $this->plugin->port, + $this->plugin->user, + $this->plugin->password, + $this->plugin->resource, + $this->plugin->server, + $this->plugin->debug ? + true : false, + $this->plugin->debug ? + XMPPHP_Log::LEVEL_VERBOSE : null + ); + + if (!$this->conn) { + return false; + } + $this->conn->addEventHandler('message', 'handle_xmpp_message', $this); + $this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this); + $this->conn->setReconnectTimeout(600); + + $this->conn->autoSubscribe(); + $this->conn->useEncryption($this->plugin->encryption); + + try { + $this->conn->connect(true); // true = persistent connection + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + return false; + } + + $this->conn->processUntil('session_start'); + $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); + } + return $this->conn; + } + + function send_ping() + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $now = time(); + if (!isset($this->pingid)) { + $this->pingid = 0; + } else { + $this->pingid++; + } + + common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); + $this->conn->send(""); + $this->lastping = $now; + return true; + } + + function handle_xmpp_message(&$pl) + { + $this->plugin->enqueue_incoming_raw($pl); + return true; + } + + /** + * Callback for Jabber reconnect event + * @param $pl + */ + function handle_xmpp_reconnect(&$pl) + { + common_log(LOG_NOTICE, 'XMPP reconnected'); + + $this->conn->processUntil('session_start'); + $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); + } + + /** + * sends a presence stanza on the XMPP 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 send_presence($status, $show='available', $to=null, + $type = 'available', $priority=null) + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $this->conn->presence($status, $show, $to, $type, $priority); + return true; + } + + /** + * sends a "special" presence stanza on the XMPP 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 send_presence() + */ + + function special_presence($type, $to=null, $show=null, $status=null) + { + // FIXME: why use this instead of send_presence()? + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + + $to = htmlspecialchars($to); + $status = htmlspecialchars($status); + + $out = "conn->send($out); + return true; + } +} diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index a332e06b5..80c21bce5 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -39,9 +39,7 @@ $daemons = array(); $daemons[] = INSTALLDIR.'/scripts/queuedaemon.php'; -if(common_config('xmpp','enabled')) { - $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php'; -} +$daemons[] = INSTALLDIR.'/scripts/imdaemon.php'; if (Event::handle('GetValidDaemons', array(&$daemons))) { foreach ($daemons as $daemon) { diff --git a/scripts/imdaemon.php b/scripts/imdaemon.php new file mode 100755 index 000000000..c37a26b7c --- /dev/null +++ b/scripts/imdaemon.php @@ -0,0 +1,93 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'fi::'; +$longoptions = array('id::', 'foreground'); + +$helptext = <<get_id()); + $master->init(); + $master->service(); + + common_log(LOG_INFO, 'terminating normally'); + + return true; + } + +} + +class ImMaster extends IoMaster +{ + /** + * Initialize IoManagers for the currently configured site + * which are appropriate to this instance. + */ + function initManagers() + { + $classes = array(); + if (Event::handle('StartImDaemonIoManagers', array(&$classes))) { + $classes[] = 'QueueManager'; + } + Event::handle('EndImDaemonIoManagers', array(&$classes)); + foreach ($classes as $class) { + $this->instantiate($class); + } + } +} + +if (have_option('i', 'id')) { + $id = get_option_value('i', 'id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +$foreground = have_option('f', 'foreground'); + +$daemon = new ImDaemon($id, !$foreground); + +$daemon->runOnce(); diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index a9cfda6d7..bedd14b1a 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -21,7 +21,7 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'fi:at:'; -$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only'); +$longoptions = array('id=', 'foreground', 'all', 'threads='); /** * Attempts to get a count of the processors available on the current system @@ -163,13 +163,6 @@ if (!$threads) { $daemonize = !(have_option('f') || have_option('--foreground')); $all = have_option('a') || have_option('--all'); -if (have_option('--skip-xmpp')) { - define('XMPP_EMERGENCY_FLAG', true); -} -if (have_option('--xmpp-only')) { - define('XMPP_ONLY_FLAG', true); -} - $daemon = new QueueDaemon($id, $daemonize, $threads, $all); $daemon->runOnce(); diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index c790f1f34..bc1230e64 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -23,8 +23,8 @@ SDIR=`dirname $0` DIR=`php $SDIR/getpiddir.php` -for f in jabberhandler ombhandler publichandler smshandler pinghandler \ - xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \ +for f in ombhandler smshandler pinghandler \ + twitterhandler facebookhandler \ twitterstatusfetcher synctwitterfriends pluginhandler rsscloudhandler; do FILES="$DIR/$f.*.pid" diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php deleted file mode 100755 index fd7cf055b..000000000 --- a/scripts/xmppdaemon.php +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'fi::'; -$longoptions = array('id::', 'foreground'); - -$helptext = <<get_id()); - $master->init(); - $master->service(); - - common_log(LOG_INFO, 'terminating normally'); - - return true; - } - -} - -class XmppMaster extends IoMaster -{ - /** - * Initialize IoManagers for the currently configured site - * which are appropriate to this instance. - */ - function initManagers() - { - // @fixme right now there's a hack in QueueManager to determine - // which queues to subscribe to based on the master class. - $this->instantiate('QueueManager'); - $this->instantiate('XmppManager'); - } -} - -// Abort immediately if xmpp is not enabled, otherwise the daemon chews up -// lots of CPU trying to connect to unconfigured servers -if (common_config('xmpp','enabled')==false) { - print "Aborting daemon - xmpp is disabled\n"; - exit(); -} - -if (have_option('i', 'id')) { - $id = get_option_value('i', 'id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$foreground = have_option('f', 'foreground'); - -$daemon = new XMPPDaemon($id, !$foreground); - -$daemon->runOnce(); -- cgit v1.2.3-54-g00ecf From 14adb7cc41e3d5d4e543c1f13f7a60d3cadb5c71 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:40:36 -0400 Subject: Give users more control over URL shortening Users and administrators can set how long an URL can be before it's shortened, and how long a notice can be before all its URLs are shortened. They can also turn off shortening altogether. Squashed commit of the following: commit d136b390115829c4391b3666bb1967f190a0de35 Author: Evan Prodromou Date: Mon Apr 26 02:39:00 2010 -0400 use site and user settings to determine when to shorten URLs commit 1e1c851ff3cb2da5e0dc3a0b06239a9d9c618488 Author: Evan Prodromou Date: Mon Apr 26 02:38:40 2010 -0400 add a method to force shortening URLs commit 4d29ca0b91201f6df42940297ed5b64b070efe49 Author: Evan Prodromou Date: Mon Apr 26 02:37:41 2010 -0400 static method for getting best URL shortening service commit a9c6a3bace0af44bcf38d1c790425a7be9c72147 Author: Evan Prodromou Date: Mon Apr 26 02:37:11 2010 -0400 allow 0 in numeric entries in othersettings commit 767ff2f7ecfd7e76e8418fc79d45e61898f09382 Author: Evan Prodromou Date: Mon Apr 26 02:36:46 2010 -0400 allow 0 or blank string in inputs commit 1e21af42a685f600f4a53f49a194013e78b12f20 Author: Evan Prodromou Date: Mon Apr 26 02:01:11 2010 -0400 add more URL-shortening options to othersettings commit 869a6be0f5779aff69018d02f9ac0273946040d9 Author: Evan Prodromou Date: Sat Apr 24 14:22:51 2010 -0400 move url shortener superclass to lib from plugin commit 9c0c9863d532942b99184f14e923fc3c050f8177 Author: Evan Prodromou Date: Sat Apr 24 14:20:28 2010 -0400 documentation and whitespace on UrlShortenerPlugin commit 7a1dd5798f0fcf2c03d1257a18ddcb9008879de0 Author: Evan Prodromou Date: Sat Apr 24 14:05:46 2010 -0400 add defaults for URL shortening commit d259c37ad231ca0010c60e5cfd397bb1732874a4 Author: Evan Prodromou Date: Sat Apr 24 13:40:10 2010 -0400 Add User_urlshortener_prefs Add a table for URL shortener prefs, a corresponding class, and the correct mumbo-jumbo in statusnet.ini to make everything work. --- README | 20 +++- actions/othersettings.php | 58 ++++++++++- classes/File_redirection.php | 38 ++++++- classes/User_urlshortener_prefs.php | 105 +++++++++++++++++++ classes/statusnet.ini | 11 ++ db/statusnet.sql | 13 +++ lib/default.php | 4 + lib/htmloutputter.php | 2 +- lib/urlshortenerplugin.php | 155 ++++++++++++++++++++++++++++ lib/util.php | 54 +++++++--- plugins/BitlyUrl/BitlyUrlPlugin.php | 2 - plugins/LilUrl/LilUrlPlugin.php | 2 - plugins/PtitUrl/PtitUrlPlugin.php | 1 - plugins/SimpleUrl/SimpleUrlPlugin.php | 2 - plugins/TightUrl/TightUrlPlugin.php | 2 - plugins/UrlShortener/UrlShortenerPlugin.php | 95 ----------------- 16 files changed, 435 insertions(+), 129 deletions(-) create mode 100755 classes/User_urlshortener_prefs.php create mode 100644 lib/urlshortenerplugin.php delete mode 100644 plugins/UrlShortener/UrlShortenerPlugin.php (limited to 'plugins') diff --git a/README b/README index 1e244c448..dcf305ea6 100644 --- a/README +++ b/README @@ -843,9 +843,7 @@ sslserver: use an alternate server name for SSL URLs, like parameters correctly so that both the SSL server and the "normal" server can access the session cookie and preferably other cookies as well. -shorturllength: Length of URL at which URLs in a message exceeding 140 - characters will be sent to the user's chosen - shortening service. +shorturllength: ignored. See 'url' section below. dupelimit: minimum time allowed for one person to say the same thing twice. Default 60s. Anything lower is considered a user or UI error. @@ -1468,6 +1466,22 @@ disallow: Array of (virtual) directories to disallow. Default is 'main', 'search', 'message', 'settings', 'admin'. Ignored when site is private, in which case the entire site ('/') is disallowed. +url +--- + +Everybody loves URL shorteners. These are some options for fine-tuning +how and when the server shortens URLs. + +shortener: URL shortening service to use by default. Users can override + individually. 'ur1.ca' by default. +maxlength: If an URL is strictly longer than this limit, it will be + shortened. Note that the URL shortener service may return an + URL longer than this limit. Defaults to 25. Users can + override. If set to 0, all URLs will be shortened. +maxnoticelength: If a notice is strictly longer than this limit, all + URLs in the notice will be shortened. Users can override. + -1 means the text limit for notices. + Plugins ======= diff --git a/actions/othersettings.php b/actions/othersettings.php index 10e9873b3..8d6e00404 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -98,8 +98,10 @@ class OthersettingsAction extends AccountSettingsAction $this->hidden('token', common_session_token()); $this->elementStart('ul', 'form_data'); - $shorteners = array(); + $shorteners = array(_('[none]') => array('freeService' => false)); + Event::handle('GetUrlShorteners', array(&$shorteners)); + $services = array(); foreach($shorteners as $name=>$value) { @@ -119,8 +121,22 @@ class OthersettingsAction extends AccountSettingsAction $this->elementEnd('li'); } $this->elementStart('li'); + $this->input('maxurllength', + _('URL longer than'), + (!is_null($this->arg('maxurllength'))) ? + $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), + _('URLs longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->input('maxnoticelength', + _('Text longer than'), + (!is_null($this->arg('maxnoticelength'))) ? + $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), + _('URLs in notices longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); $this->checkbox('viewdesigns', _('View profile designs'), - $user->viewdesigns, _('Show or hide profile designs.')); + - $user->viewdesigns, _('Show or hide profile designs.')); $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('save', _('Save')); @@ -156,6 +172,18 @@ class OthersettingsAction extends AccountSettingsAction $viewdesigns = $this->boolean('viewdesigns'); + $maxurllength = $this->trimmed('maxurllength'); + + if (!Validate::number($maxurllength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max url length.')); + } + + $maxnoticelength = $this->trimmed('maxnoticelength'); + + if (!Validate::number($maxnoticelength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max notice length.')); + } + $user = common_current_user(); assert(!is_null($user)); // should already be checked @@ -175,6 +203,32 @@ class OthersettingsAction extends AccountSettingsAction return; } + $prefs = User_urlshortener_prefs::getPrefs($user); + $orig = null; + + if (empty($prefs)) { + $prefs = new User_urlshortener_prefs(); + + $prefs->user_id = $user->id; + $prefs->created = common_sql_now(); + } else { + $orig = clone($prefs); + } + + $prefs->urlshorteningservice = $urlshorteningservice; + $prefs->maxurllength = $maxurllength; + $prefs->maxnoticelength = $maxnoticelength; + + if (!empty($orig)) { + $result = $prefs->update($orig); + } else { + $result = $prefs->insert(); + } + + if (!$result) { + throw new ServerException(_('Error saving user URL shortening preferences.')); + } + $user->query('COMMIT'); $this->showForm(_('Preferences saved.'), true); diff --git a/classes/File_redirection.php b/classes/File_redirection.php index f128b3e07..00ec75309 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -176,22 +176,52 @@ class File_redirection extends Memcached_DataObject * @param string $long_url * @return string */ - function makeShort($long_url) { + function makeShort($long_url) + { $canon = File_redirection::_canonUrl($long_url); $short_url = File_redirection::_userMakeShort($canon); // Did we get one? Is it shorter? - if (!empty($short_url) && mb_strlen($short_url) < mb_strlen($long_url)) { + + if (!empty($short_url)) { + return $short_url; + } else { + return $long_url; + } + } + + /** + * Shorten a URL with the current user's configured shortening + * options, if applicable. + * + * If it cannot be shortened or the "short" URL is longer than the + * original, the original is returned. + * + * If the referenced item has not been seen before, embedding data + * may be saved. + * + * @param string $long_url + * @return string + */ + + function forceShort($long_url) + { + $canon = File_redirection::_canonUrl($long_url); + + $short_url = File_redirection::_userMakeShort($canon, true); + + // Did we get one? Is it shorter? + if (!empty($short_url)) { return $short_url; } else { return $long_url; } } - function _userMakeShort($long_url) { - $short_url = common_shorten_url($long_url); + function _userMakeShort($long_url, $force = false) { + $short_url = common_shorten_url($long_url, $force); if (!empty($short_url) && $short_url != $long_url) { $short_url = (string)$short_url; // store it diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php new file mode 100755 index 000000000..e0f85af01 --- /dev/null +++ b/classes/User_urlshortener_prefs.php @@ -0,0 +1,105 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class User_urlshortener_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_urlshortener_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $urlshorteningservice; // varchar(50) default_ur1.ca + public $maxurllength; // int(4) not_null + public $maxnoticelength; // int(4) not_null + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_urlshortener_prefs',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } + + static function maxUrlLength($user) + { + $def = common_config('url', 'maxlength'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxurllength; + } + } + + static function maxNoticeLength($user) + { + $def = common_config('url', 'maxnoticelength'); + + if ($def == -1) { + $def = Notice::maxContent(); + } + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxnoticelength; + } + } + + static function urlShorteningService($user) + { + $def = common_config('url', 'shortener'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + if (!empty($user)) { + return $user->urlshorteningservice; + } else { + return $def; + } + } else { + return $prefs->urlshorteningservice; + } + } + + static function getPrefs($user) + { + if (empty($user)) { + return null; + } + + $prefs = User_urlshortener_prefs::staticGet('user_id', $user->id); + + return $prefs; + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 473bd6ff5..d13fdfa52 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -649,3 +649,14 @@ user_id = K transport = K transport = U screenname = U + +[user_urlshortener_prefs] +user_id = 129 +urlshorteningservice = 2 +maxurllength = 129 +maxnoticelength = 129 +created = 142 +modified = 384 + +[user_urlshortener_prefs__keys] +user_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index 16d09a11f..a0c497fff 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -665,3 +665,16 @@ create table local_group ( modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table user_urlshortener_prefs ( + + user_id integer not null comment 'user' references user (id), + urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs', + maxurllength integer not null comment 'urls greater than this length will be shortened, 0 = always, null = never', + maxnoticelength integer not null comment 'notices with content greater than this value will have all urls shortened, 0 = always, null = never', + + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/default.php b/lib/default.php index c98f179ae..dec08fc06 100644 --- a/lib/default.php +++ b/lib/default.php @@ -304,4 +304,8 @@ $default = array('subscribers' => true, 'members' => true, 'peopletag' => true), + 'url' => + array('shortener' => 'ur1.ca', + 'maxlength' => 25, + 'maxnoticelength' => -1) ); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 7786b5941..9d06ba23c 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -176,7 +176,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/urlshortenerplugin.php b/lib/urlshortenerplugin.php new file mode 100644 index 000000000..8acfac26f --- /dev/null +++ b/lib/urlshortenerplugin.php @@ -0,0 +1,155 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 + * @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 96d21bc59..c78ed33bd 100644 --- a/lib/util.php +++ b/lib/util.php @@ -828,9 +828,21 @@ function common_linkify($url) { function common_shorten_links($text) { - $maxLength = Notice::maxContent(); - if ($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 (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) @@ -1392,7 +1404,7 @@ function common_valid_tag($tag) * Determine if given domain or address literal is valid * eg for use in JIDs and URLs. Does not check if the domain * exists! - * + * * @param string $domain * @return boolean valid or not */ @@ -1734,30 +1746,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/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php index f7f28b4d6..b649d3d0b 100644 --- a/plugins/BitlyUrl/BitlyUrlPlugin.php +++ b/plugins/BitlyUrl/BitlyUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class BitlyUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index c3e37c0c0..cdff9f4e6 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class LilUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php index ddba942e6..cdf46846b 100644 --- a/plugins/PtitUrl/PtitUrlPlugin.php +++ b/plugins/PtitUrl/PtitUrlPlugin.php @@ -30,7 +30,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; class PtitUrlPlugin extends UrlShortenerPlugin { diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php index 6eac7dbb1..5d3f97d33 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class SimpleUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php index e2d494a7b..f242db6c8 100644 --- a/plugins/TightUrl/TightUrlPlugin.php +++ b/plugins/TightUrl/TightUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class TightUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/plugins/UrlShortener/UrlShortenerPlugin.php deleted file mode 100644 index 027624b7a..000000000 --- a/plugins/UrlShortener/UrlShortenerPlugin.php +++ /dev/null @@ -1,95 +0,0 @@ -. - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @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 - * @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 plugin should implement some (or all) of these methods------------\\ - - /** - * Short a URL - * @param url - * @return string shortened version of the url, or null if URL shortening failed - */ - protected abstract function shorten($url); - - //------------These methods may help you implement your plugin------------\\ - protected function http_get($url) - { - $request = HTTPClient::start(); - $response = $request->get($url); - return $response->getBody(); - } - - protected function http_post($url,$data) - { - $request = HTTPClient::start(); - $response = $request->post($url, null, $data); - return $response->getBody(); - } - - //------------Below are the methods that connect StatusNet to the implementing Url Shortener plugin------------\\ - - function onInitializePlugin(){ - if(!isset($this->shortenerName)){ - throw new Exception("must specify a shortenerName"); - } - } - - function onGetUrlShorteners(&$shorteners) - { - $shorteners[$this->shortenerName]=array('freeService'=>$this->freeService); - } - - function onStartShortenUrl($url,$shortenerName,&$shortenedUrl) - { - if($shortenerName == $this->shortenerName && strlen($url) >= common_config('site', 'shorturllength')){ - $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; - } - } - } -} -- cgit v1.2.3-54-g00ecf From 5414396a2ee9f1401d69b60969e04a1941e24e21 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 30 Apr 2010 14:41:54 -0700 Subject: IM cleanup on 1.0.x branch: * Fake_XMPP back to Queued_XMPP, refactor how we use it and don't create objects and load classes until we need them. * fix fatal error in IM settings while waiting for a Jabber confirmation. * Caching fix for user_im_prefs * fix for saving multiple transport settings * some fixes for AIM & using normalized addresses for lookups --- actions/imsettings.php | 17 +++--- classes/User_im_prefs.php | 23 ++++++++ classes/statusnet.ini | 6 ++- lib/implugin.php | 25 ++++++--- plugins/Aim/AimPlugin.php | 9 +++- plugins/Aim/README | 2 +- plugins/Xmpp/Fake_XMPP.php | 114 ---------------------------------------- plugins/Xmpp/Queued_XMPP.php | 121 +++++++++++++++++++++++++++++++++++++++++++ plugins/Xmpp/XmppPlugin.php | 27 +++++----- 9 files changed, 198 insertions(+), 146 deletions(-) delete mode 100644 plugins/Xmpp/Fake_XMPP.php create mode 100644 plugins/Xmpp/Queued_XMPP.php (limited to 'plugins') diff --git a/actions/imsettings.php b/actions/imsettings.php index 2c2606b76..662b1063e 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -133,8 +133,7 @@ class ImsettingsAction extends ConnectSettingsAction 'message with further instructions. '. '(Did you add %s to your buddy list?)'), $transport_info['display'], - $transport_info['daemon_screenname'], - jabber_daemon_address())); + $transport_info['daemon_screenname'])); $this->hidden('screenname', $confirm->address); // TRANS: Button label to cancel an IM address confirmation procedure. $this->submit('cancel', _m('BUTTON','Cancel')); @@ -163,12 +162,11 @@ class ImsettingsAction extends ConnectSettingsAction 'action' => common_local_url('imsettings'))); $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); - $this->element('legend', null, _('Preferences')); + // TRANS: Header for IM preferences form. + $this->element('legend', null, _('IM Preferences')); $this->hidden('token', common_session_token()); $this->elementStart('table'); $this->elementStart('tr'); - // TRANS: Header for IM preferences form. - $this->element('th', null, _('IM Preferences')); foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) { $this->element('th', null, $transports[$transport]['display']); @@ -278,19 +276,20 @@ class ImsettingsAction extends ConnectSettingsAction $user = common_current_user(); $user_im_prefs = new User_im_prefs(); + $user_im_prefs->query('BEGIN'); $user_im_prefs->user_id = $user->id; if($user_im_prefs->find() && $user_im_prefs->fetch()) { $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); - $user_im_prefs->query('BEGIN'); do { $original = clone($user_im_prefs); + $new = clone($user_im_prefs); foreach($preferences as $preference) { - $user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference); + $new->$preference = $this->boolean($new->transport . '_' . $preference); } - $result = $user_im_prefs->update($original); + $result = $new->update($original); if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); @@ -299,8 +298,8 @@ class ImsettingsAction extends ConnectSettingsAction return; } }while($user_im_prefs->fetch()); - $user_im_prefs->query('COMMIT'); } + $user_im_prefs->query('COMMIT'); // TRANS: Confirmation message for successful IM preferences save. $this->showForm(_('Preferences saved.'), true); } diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php index 8ecdfe9fa..75be8969e 100644 --- a/classes/User_im_prefs.php +++ b/classes/User_im_prefs.php @@ -68,4 +68,27 @@ class User_im_prefs extends Memcached_DataObject { return array(false,false); } + + /** + * We have two compound keys with unique constraints: + * (transport, user_id) which is our primary key, and + * (transport, screenname) which is an additional constraint. + * + * Currently there's not a way to represent that second key + * in the general keys list, so we're adding it here to the + * list of keys to use for caching, ensuring that it gets + * cleared as well when we change. + * + * @return array of cache keys + */ + function _allCacheKeys() + { + $ukeys = 'transport,screenname'; + $uvals = $this->transport . ',' . $this->screenname; + + $ckeys = parent::_allCacheKeys(); + $ckeys[] = $this->cacheKey($this->tableName(), $ukeys, $uvals); + return $ckeys; + } + } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index d13fdfa52..b57d86226 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -647,8 +647,10 @@ modified = 384 [user_im_prefs__keys] user_id = K transport = K -transport = U -screenname = U +; There's another unique index on (transport, screenname) +; but we have no way to represent a compound index other than +; the primary key in here. To ensure proper cache purging, +; we need to tweak the class. [user_urlshortener_prefs] user_id = 129 diff --git a/lib/implugin.php b/lib/implugin.php index 7302859a4..7125aaee8 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -107,10 +107,15 @@ abstract class ImPlugin extends Plugin * 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 handle_incoming() + * + * 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 success value + * @return boolean true if processing completed, false for temporary failures */ abstract function receive_raw_message($data); @@ -185,9 +190,12 @@ abstract class ImPlugin extends Plugin */ function get_user_im_prefs_from_screenname($screenname) { - if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'screenname' => $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{ + } else { return false; } } @@ -203,9 +211,9 @@ abstract class ImPlugin extends Plugin function get_screenname($user) { $user_im_prefs = $this->get_user_im_prefs_from_user($user); - if($user_im_prefs){ + if ($user_im_prefs) { return $user_im_prefs->screenname; - }else{ + } else { return false; } } @@ -220,9 +228,12 @@ abstract class ImPlugin extends Plugin */ function get_user_im_prefs_from_user($user) { - if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'user_id' => $user->id) )){ + $user_im_prefs = User_im_prefs::pkeyGet( + array('transport' => $this->transport, + 'user_id' => $user->id)); + if ($user_im_prefs){ return $user_im_prefs; - }else{ + } else { return false; } } diff --git a/plugins/Aim/AimPlugin.php b/plugins/Aim/AimPlugin.php index 3855d1fb0..30da1dbc7 100644 --- a/plugins/Aim/AimPlugin.php +++ b/plugins/Aim/AimPlugin.php @@ -126,6 +126,11 @@ class AimPlugin extends ImPlugin return true; } + /** + * Accept a queued input message. + * + * @return true if processing completed, false if message should be reprocessed + */ function receive_raw_message($message) { $info=Aim::getMessageInfo($message); @@ -133,7 +138,9 @@ class AimPlugin extends ImPlugin $user = $this->get_user($from); $notice_text = $info['message']; - return $this->handle_incoming($from, $notice_text); + $this->handle_incoming($from, $notice_text); + + return true; } function initialize(){ diff --git a/plugins/Aim/README b/plugins/Aim/README index 046591738..7d486a036 100644 --- a/plugins/Aim/README +++ b/plugins/Aim/README @@ -6,7 +6,7 @@ add "addPlugin('aim', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php -The daemon included with this plugin must be running. It will be started by +scripts/imdaemon.php included with StatusNet must be running. It will be started by the plugin along with their other daemons when you run scripts/startdaemons.sh. See the StatusNet README for more about queuing and daemons. diff --git a/plugins/Xmpp/Fake_XMPP.php b/plugins/Xmpp/Fake_XMPP.php deleted file mode 100644 index 0f7cfd3b4..000000000 --- a/plugins/Xmpp/Fake_XMPP.php +++ /dev/null @@ -1,114 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Brion Vibber - * @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); -} - -class Fake_XMPP extends XMPPHP_XMPP -{ - public $would_be_sent = null; - - /** - * 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) - { - $this->would_be_sent = $msg; - } - - //@{ - /** - * Stream i/o functions disabled; only do output - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) - { - throw new Exception("Can't connect to server from fake XMPP."); - } - - public function disconnect() - { - throw new Exception("Can't connect to server from fake XMPP."); - } - - public function process() - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function processUntil($event, $timeout=-1) - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function read() - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function readyToProcess() - { - throw new Exception("Can't read stream from fake XMPP."); - } - //@} -} - diff --git a/plugins/Xmpp/Queued_XMPP.php b/plugins/Xmpp/Queued_XMPP.php new file mode 100644 index 000000000..73eff2246 --- /dev/null +++ b/plugins/Xmpp/Queued_XMPP.php @@ -0,0 +1,121 @@ +. + * + * @category Network + * @package StatusNet + * @author Brion Vibber + * @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); +} + +class Queued_XMPP extends XMPPHP_XMPP +{ + /** + * Reference to the XmppPlugin object we're hooked up to. + */ + public $plugin; + + /** + * Constructor + * + * @param XmppPlugin $plugin + * @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($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + { + $this->plugin = $plugin; + + 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) + { + $this->plugin->enqueue_outgoing_raw($msg); + } + + //@{ + /** + * Stream i/o functions disabled; only do output + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function disconnect() + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function process() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function processUntil($event, $timeout=-1) + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function read() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function readyToProcess() + { + throw new Exception("Can't read stream from fake XMPP."); + } + //@} + +} + diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index 03bf47fea..a2521536b 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -60,8 +60,6 @@ class XmppPlugin extends ImPlugin public $transport = 'xmpp'; - protected $fake_xmpp; - function getDisplayName(){ return _m('XMPP/Jabber/GTalk'); } @@ -292,7 +290,7 @@ class XmppPlugin extends ImPlugin require_once 'XMPP.php'; return false; case 'Sharing_XMPP': - case 'Fake_XMPP': + case 'Queued_XMPP': require_once $dir . '/'.$cls.'.php'; return false; case 'XmppManager': @@ -317,9 +315,7 @@ class XmppPlugin extends ImPlugin function send_message($screenname, $body) { - $this->fake_xmpp->message($screenname, $body, 'chat'); - $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); - return true; + $this->queuedConnection()->message($screenname, $body, 'chat'); } function send_notice($screenname, $notice) @@ -327,8 +323,7 @@ class XmppPlugin extends ImPlugin $msg = $this->format_notice($notice); $entry = $this->format_entry($notice); - $this->fake_xmpp->message($screenname, $msg, 'chat', null, $entry); - $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + $this->queuedConnection()->message($screenname, $msg, 'chat', null, $entry); return true; } @@ -385,10 +380,19 @@ class XmppPlugin extends ImPlugin return true; } - return $this->handle_incoming($from, $pl['body']); + $this->handle_incoming($from, $pl['body']); + + return true; } - function initialize(){ + /** + * Build a queue-proxied XMPP interface object. Any outgoing messages + * will be run back through us for enqueing rather than sent directly. + * + * @return Queued_XMPP + * @throws Exception if server settings are invalid. + */ + function queuedConnection(){ if(!isset($this->server)){ throw new Exception("must specify a server"); } @@ -402,7 +406,7 @@ class XmppPlugin extends ImPlugin throw new Exception("must specify a password"); } - $this->fake_xmpp = new Fake_XMPP($this->host ? + return new Queued_XMPP($this, $this->host ? $this->host : $this->server, $this->port, @@ -415,7 +419,6 @@ class XmppPlugin extends ImPlugin $this->debug ? XMPPHP_Log::LEVEL_VERBOSE : null ); - return true; } function onPluginVersion(&$versions) -- cgit v1.2.3-54-g00ecf From ecf9dc6d1b5a068b2e5ba23debf2b7bec04d3d2c Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 3 May 2010 21:25:10 -0400 Subject: use the new maxNoticeLength and maxUrlLength functionality introduced in commit 14adb7cc41e3d5d4e543c1f13f7a60d3cadb5c71 --- plugins/ClientSideShorten/ClientSideShortenPlugin.php | 4 +++- plugins/ClientSideShorten/shorten.js | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/ClientSideShorten/ClientSideShortenPlugin.php b/plugins/ClientSideShorten/ClientSideShortenPlugin.php index ba1f7d3a7..454bedb08 100644 --- a/plugins/ClientSideShorten/ClientSideShortenPlugin.php +++ b/plugins/ClientSideShorten/ClientSideShortenPlugin.php @@ -51,8 +51,10 @@ class ClientSideShortenPlugin extends Plugin } function onEndShowScripts($action){ - $action->inlineScript('var Notice_maxContent = ' . Notice::maxContent()); if (common_logged_in()) { + $user = common_current_user(); + $action->inlineScript('var maxNoticeLength = ' . User_urlshortener_prefs::maxNoticeLength($user)); + $action->inlineScript('var maxUrlLength = ' . User_urlshortener_prefs::maxUrlLength($user)); $action->script('plugins/ClientSideShorten/shorten.js'); } } diff --git a/plugins/ClientSideShorten/shorten.js b/plugins/ClientSideShorten/shorten.js index 856c7f05f..bdffb81e2 100644 --- a/plugins/ClientSideShorten/shorten.js +++ b/plugins/ClientSideShorten/shorten.js @@ -31,10 +31,21 @@ })(jQuery,'smartkeypress'); + function longestWordInString(string) + { + var words = string.split(/\s/); + var longestWord = 0; + for(var i=0;i longestWord) longestWord = words[i].length; + return longestWord; + } + function shorten() { - $noticeDataText = $('#'+SN.C.S.NoticeDataText); - if(Notice_maxContent > 0 && $noticeDataText.val().length > Notice_maxContent){ + var $noticeDataText = $('#'+SN.C.S.NoticeDataText); + var noticeText = $noticeDataText.val(); + + if(noticeText.length > maxNoticeLength || longestWordInString(noticeText) > maxUrlLength) { var original = $noticeDataText.val(); shortenAjax = $.ajax({ url: $('address .url')[0].href+'/plugins/ClientSideShorten/shorten', -- cgit v1.2.3-54-g00ecf From ddc7811a7b412c9c3c4b6bfb9350dd18a62fdf51 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 May 2010 16:52:31 -0700 Subject: Move XMPPHP from core extlibs to Xmpp plugin extlibs --- extlib/XMPPHP/BOSH.php | 188 -------- extlib/XMPPHP/Exception.php | 41 -- extlib/XMPPHP/Log.php | 119 ----- extlib/XMPPHP/Roster.php | 163 ------- extlib/XMPPHP/XMLObj.php | 158 ------- extlib/XMPPHP/XMLStream.php | 763 ------------------------------- extlib/XMPPHP/XMPP.php | 432 ----------------- extlib/XMPPHP/XMPP_Old.php | 114 ----- plugins/Xmpp/XmppPlugin.php | 4 +- plugins/Xmpp/extlib/XMPPHP/BOSH.php | 188 ++++++++ plugins/Xmpp/extlib/XMPPHP/Exception.php | 41 ++ plugins/Xmpp/extlib/XMPPHP/Log.php | 119 +++++ plugins/Xmpp/extlib/XMPPHP/Roster.php | 163 +++++++ plugins/Xmpp/extlib/XMPPHP/XMLObj.php | 158 +++++++ plugins/Xmpp/extlib/XMPPHP/XMLStream.php | 763 +++++++++++++++++++++++++++++++ plugins/Xmpp/extlib/XMPPHP/XMPP.php | 432 +++++++++++++++++ plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php | 114 +++++ 17 files changed, 1979 insertions(+), 1981 deletions(-) delete mode 100644 extlib/XMPPHP/BOSH.php delete mode 100644 extlib/XMPPHP/Exception.php delete mode 100644 extlib/XMPPHP/Log.php delete mode 100644 extlib/XMPPHP/Roster.php delete mode 100644 extlib/XMPPHP/XMLObj.php delete mode 100644 extlib/XMPPHP/XMLStream.php delete mode 100644 extlib/XMPPHP/XMPP.php delete mode 100644 extlib/XMPPHP/XMPP_Old.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/BOSH.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/Exception.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/Log.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/Roster.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/XMLObj.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/XMLStream.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/XMPP.php create mode 100644 plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php (limited to 'plugins') diff --git a/extlib/XMPPHP/BOSH.php b/extlib/XMPPHP/BOSH.php deleted file mode 100644 index befaf60a7..000000000 --- a/extlib/XMPPHP/BOSH.php +++ /dev/null @@ -1,188 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMPP.php"; - -/** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_BOSH extends XMPPHP_XMPP { - - protected $rid; - protected $sid; - protected $http_server; - protected $http_buffer = Array(); - protected $session = false; - - public function connect($server, $wait='1', $session=false) { - $this->http_server = $server; - $this->use_encryption = false; - $this->session = $session; - - $this->rid = 3001; - $this->sid = null; - if($session) - { - $this->loadSession(); - } - if(!$this->sid) { - $body = $this->__buildBody(); - $body->addAttribute('hold','1'); - $body->addAttribute('to', $this->host); - $body->addAttribute('route', "xmpp:{$this->host}:{$this->port}"); - $body->addAttribute('secure','true'); - $body->addAttribute('xmpp:version','1.6', 'urn:xmpp:xbosh'); - $body->addAttribute('wait', strval($wait)); - $body->addAttribute('ack','1'); - $body->addAttribute('xmlns:xmpp','urn:xmpp:xbosh'); - $buff = ""; - xml_parse($this->parser, $buff, false); - $response = $this->__sendBody($body); - $rxml = new SimpleXMLElement($response); - $this->sid = $rxml['sid']; - - } else { - $buff = ""; - xml_parse($this->parser, $buff, false); - } - } - - public function __sendBody($body=null, $recv=true) { - if(!$body) { - $body = $this->__buildBody(); - } - $ch = curl_init($this->http_server); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML()); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - $header = array('Accept-Encoding: gzip, deflate','Content-Type: text/xml; charset=utf-8'); - curl_setopt($ch, CURLOPT_HTTPHEADER, $header ); - curl_setopt($ch, CURLOPT_VERBOSE, 0); - $output = ''; - if($recv) { - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $output = curl_exec($ch); - $this->http_buffer[] = $output; - } - curl_close($ch); - return $output; - } - - public function __buildBody($sub=null) { - $xml = new SimpleXMLElement(""); - $xml->addAttribute('content', 'text/xml; charset=utf-8'); - $xml->addAttribute('rid', $this->rid); - $this->rid += 1; - if($this->sid) $xml->addAttribute('sid', $this->sid); - #if($this->sid) $xml->addAttribute('xmlns', 'http://jabber.org/protocol/httpbind'); - $xml->addAttribute('xml:lang', 'en'); - if($sub) { // ok, so simplexml is lame - $p = dom_import_simplexml($xml); - $c = dom_import_simplexml($sub); - $cn = $p->ownerDocument->importNode($c, true); - $p->appendChild($cn); - $xml = simplexml_import_dom($p); - } - return $xml; - } - - public function __process() { - if($this->http_buffer) { - $this->__parseBuffer(); - } else { - $this->__sendBody(); - $this->__parseBuffer(); - } - } - - public function __parseBuffer() { - while ($this->http_buffer) { - $idx = key($this->http_buffer); - $buffer = $this->http_buffer[$idx]; - unset($this->http_buffer[$idx]); - if($buffer) { - $xml = new SimpleXMLElement($buffer); - $children = $xml->xpath('child::node()'); - foreach ($children as $child) { - $buff = $child->asXML(); - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - } - } - } - - public function send($msg) { - $this->log->log("SEND: $msg", XMPPHP_Log::LEVEL_VERBOSE); - $msg = new SimpleXMLElement($msg); - #$msg->addAttribute('xmlns', 'jabber:client'); - $this->__sendBody($this->__buildBody($msg), true); - #$this->__parseBuffer(); - } - - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - #$this->send($this->stream_start); - $body = $this->__buildBody(); - $body->addAttribute('to', $this->host); - $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh'); - $buff = ""; - $response = $this->__sendBody($body); - $this->been_reset = true; - xml_parse($this->parser, $buff, false); - } - - public function loadSession() { - if(isset($_SESSION['XMPPHP_BOSH_RID'])) $this->rid = $_SESSION['XMPPHP_BOSH_RID']; - if(isset($_SESSION['XMPPHP_BOSH_SID'])) $this->sid = $_SESSION['XMPPHP_BOSH_SID']; - if(isset($_SESSION['XMPPHP_BOSH_authed'])) $this->authed = $_SESSION['XMPPHP_BOSH_authed']; - if(isset($_SESSION['XMPPHP_BOSH_jid'])) $this->jid = $_SESSION['XMPPHP_BOSH_jid']; - if(isset($_SESSION['XMPPHP_BOSH_fulljid'])) $this->fulljid = $_SESSION['XMPPHP_BOSH_fulljid']; - } - - public function saveSession() { - $_SESSION['XMPPHP_BOSH_RID'] = (string) $this->rid; - $_SESSION['XMPPHP_BOSH_SID'] = (string) $this->sid; - $_SESSION['XMPPHP_BOSH_authed'] = (boolean) $this->authed; - $_SESSION['XMPPHP_BOSH_jid'] = (string) $this->jid; - $_SESSION['XMPPHP_BOSH_fulljid'] = (string) $this->fulljid; - } -} diff --git a/extlib/XMPPHP/Exception.php b/extlib/XMPPHP/Exception.php deleted file mode 100644 index da59bc791..000000000 --- a/extlib/XMPPHP/Exception.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Exception - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_Exception extends Exception { -} diff --git a/extlib/XMPPHP/Log.php b/extlib/XMPPHP/Log.php deleted file mode 100644 index a9bce3d84..000000000 --- a/extlib/XMPPHP/Log.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Log - * - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_Log { - - const LEVEL_ERROR = 0; - const LEVEL_WARNING = 1; - const LEVEL_INFO = 2; - const LEVEL_DEBUG = 3; - const LEVEL_VERBOSE = 4; - - /** - * @var array - */ - protected $data = array(); - - /** - * @var array - */ - protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE'); - - /** - * @var integer - */ - protected $runlevel; - - /** - * @var boolean - */ - protected $printout; - - /** - * Constructor - * - * @param boolean $printout - * @param string $runlevel - */ - public function __construct($printout = false, $runlevel = self::LEVEL_INFO) { - $this->printout = (boolean)$printout; - $this->runlevel = (int)$runlevel; - } - - /** - * Add a message to the log data array - * If printout in this instance is set to true, directly output the message - * - * @param string $msg - * @param integer $runlevel - */ - public function log($msg, $runlevel = self::LEVEL_INFO) { - $time = time(); - #$this->data[] = array($this->runlevel, $msg, $time); - if($this->printout and $runlevel <= $this->runlevel) { - $this->writeLine($msg, $runlevel, $time); - } - } - - /** - * Output the complete log. - * Log will be cleared if $clear = true - * - * @param boolean $clear - * @param integer $runlevel - */ - public function printout($clear = true, $runlevel = null) { - if($runlevel === null) { - $runlevel = $this->runlevel; - } - foreach($this->data as $data) { - if($runlevel <= $data[0]) { - $this->writeLine($data[1], $runlevel, $data[2]); - } - } - if($clear) { - $this->data = array(); - } - } - - protected function writeLine($msg, $runlevel, $time) { - //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n"; - echo $time." [".$this->names[$runlevel]."]: ".$msg."\n"; - flush(); - } -} diff --git a/extlib/XMPPHP/Roster.php b/extlib/XMPPHP/Roster.php deleted file mode 100644 index 2e459e2a2..000000000 --- a/extlib/XMPPHP/Roster.php +++ /dev/null @@ -1,163 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Roster Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ - -class Roster { - /** - * Roster array, handles contacts and presence. Indexed by jid. - * Contains array with potentially two indexes 'contact' and 'presence' - * @var array - */ - protected $roster_array = array(); - /** - * Constructor - * - */ - public function __construct($roster_array = array()) { - if ($this->verifyRoster($roster_array)) { - $this->roster_array = $roster_array; //Allow for prepopulation with existing roster - } else { - $this->roster_array = array(); - } - } - - /** - * - * Check that a given roster array is of a valid structure (empty is still valid) - * - * @param array $roster_array - */ - protected function verifyRoster($roster_array) { - #TODO once we know *what* a valid roster array looks like - return True; - } - - /** - * - * Add given contact to roster - * - * @param string $jid - * @param string $subscription - * @param string $name - * @param array $groups - */ - public function addContact($jid, $subscription, $name='', $groups=array()) { - $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups); - if ($this->isContact($jid)) { - $this->roster_array[$jid]['contact'] = $contact; - } else { - $this->roster_array[$jid] = array('contact' => $contact); - } - } - - /** - * - * Retrieve contact via jid - * - * @param string $jid - */ - public function getContact($jid) { - if ($this->isContact($jid)) { - return $this->roster_array[$jid]['contact']; - } - } - - /** - * - * Discover if a contact exists in the roster via jid - * - * @param string $jid - */ - public function isContact($jid) { - return (array_key_exists($jid, $this->roster_array)); - } - - /** - * - * Set presence - * - * @param string $presence - * @param integer $priority - * @param string $show - * @param string $status - */ - public function setPresence($presence, $priority, $show, $status) { - list($jid, $resource) = split("/", $presence); - if ($show != 'unavailable') { - if (!$this->isContact($jid)) { - $this->addContact($jid, 'not-in-roster'); - } - $resource = $resource ? $resource : ''; - $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); - } else { //Nuke unavailable resources to save memory - unset($this->roster_array[$jid]['resource'][$resource]); - } - } - - /* - * - * Return best presence for jid - * - * @param string $jid - */ - public function getPresence($jid) { - $split = split("/", $jid); - $jid = $split[0]; - if($this->isContact($jid)) { - $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 - foreach($this->roster_array[$jid]['presence'] as $resource => $presence) { - //Highest available priority or just highest priority - if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) { - $current = $presence; - $current['resource'] = $resource; - } - } - return $current; - } - } - /** - * - * Get roster - * - */ - public function getRoster() { - return $this->roster_array; - } -} -?> diff --git a/extlib/XMPPHP/XMLObj.php b/extlib/XMPPHP/XMLObj.php deleted file mode 100644 index 0d3e21991..000000000 --- a/extlib/XMPPHP/XMLObj.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP XML Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMLObj { - /** - * Tag name - * - * @var string - */ - public $name; - - /** - * Namespace - * - * @var string - */ - public $ns; - - /** - * Attributes - * - * @var array - */ - public $attrs = array(); - - /** - * Subs? - * - * @var array - */ - public $subs = array(); - - /** - * Node data - * - * @var string - */ - public $data = ''; - - /** - * Constructor - * - * @param string $name - * @param string $ns - * @param array $attrs - * @param string $data - */ - public function __construct($name, $ns = '', $attrs = array(), $data = '') { - $this->name = strtolower($name); - $this->ns = $ns; - if(is_array($attrs) && count($attrs)) { - foreach($attrs as $key => $value) { - $this->attrs[strtolower($key)] = $value; - } - } - $this->data = $data; - } - - /** - * Dump this XML Object to output. - * - * @param integer $depth - */ - public function printObj($depth = 0) { - print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data; - print "\n"; - foreach($this->subs as $sub) { - $sub->printObj($depth + 1); - } - } - - /** - * Return this XML Object in xml notation - * - * @param string $str - */ - public function toString($str = '') { - $str .= "<{$this->name} xmlns='{$this->ns}' "; - foreach($this->attrs as $key => $value) { - if($key != 'xmlns') { - $value = htmlspecialchars($value); - $str .= "$key='$value' "; - } - } - $str .= ">"; - foreach($this->subs as $sub) { - $str .= $sub->toString(); - } - $body = htmlspecialchars($this->data); - $str .= "$bodyname}>"; - return $str; - } - - /** - * Has this XML Object the given sub? - * - * @param string $name - * @return boolean - */ - public function hasSub($name, $ns = null) { - foreach($this->subs as $sub) { - if(($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) return true; - } - return false; - } - - /** - * Return a sub - * - * @param string $name - * @param string $attrs - * @param string $ns - */ - public function sub($name, $attrs = null, $ns = null) { - #TODO attrs is ignored - foreach($this->subs as $sub) { - if($sub->name == $name and ($ns == null or $sub->ns == $ns)) { - return $sub; - } - } - } -} diff --git a/extlib/XMPPHP/XMLStream.php b/extlib/XMPPHP/XMLStream.php deleted file mode 100644 index d33411ec5..000000000 --- a/extlib/XMPPHP/XMLStream.php +++ /dev/null @@ -1,763 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_Exception */ -require_once dirname(__FILE__) . '/Exception.php'; - -/** XMPPHP_XMLObj */ -require_once dirname(__FILE__) . '/XMLObj.php'; - -/** XMPPHP_Log */ -require_once dirname(__FILE__) . '/Log.php'; - -/** - * XMPPHP XML Stream - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMLStream { - /** - * @var resource - */ - protected $socket; - /** - * @var resource - */ - protected $parser; - /** - * @var string - */ - protected $buffer; - /** - * @var integer - */ - protected $xml_depth = 0; - /** - * @var string - */ - protected $host; - /** - * @var integer - */ - protected $port; - /** - * @var string - */ - protected $stream_start = ''; - /** - * @var string - */ - protected $stream_end = ''; - /** - * @var boolean - */ - protected $disconnected = false; - /** - * @var boolean - */ - protected $sent_disconnect = false; - /** - * @var array - */ - protected $ns_map = array(); - /** - * @var array - */ - protected $current_ns = array(); - /** - * @var array - */ - protected $xmlobj = null; - /** - * @var array - */ - protected $nshandlers = array(); - /** - * @var array - */ - protected $xpathhandlers = array(); - /** - * @var array - */ - protected $idhandlers = array(); - /** - * @var array - */ - protected $eventhandlers = array(); - /** - * @var integer - */ - protected $lastid = 0; - /** - * @var string - */ - protected $default_ns; - /** - * @var string - */ - protected $until = ''; - /** - * @var string - */ - protected $until_count = ''; - /** - * @var array - */ - protected $until_happened = false; - /** - * @var array - */ - protected $until_payload = array(); - /** - * @var XMPPHP_Log - */ - protected $log; - /** - * @var boolean - */ - protected $reconnect = true; - /** - * @var boolean - */ - protected $been_reset = false; - /** - * @var boolean - */ - protected $is_server; - /** - * @var float - */ - protected $last_send = 0; - /** - * @var boolean - */ - protected $use_ssl = false; - /** - * @var integer - */ - protected $reconnectTimeout = 30; - - /** - * Constructor - * - * @param string $host - * @param string $port - * @param boolean $printlog - * @param string $loglevel - * @param boolean $is_server - */ - public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) { - $this->reconnect = !$is_server; - $this->is_server = $is_server; - $this->host = $host; - $this->port = $port; - $this->setupParser(); - $this->log = new XMPPHP_Log($printlog, $loglevel); - } - - /** - * Destructor - * Cleanup connection - */ - public function __destruct() { - if(!$this->disconnected && $this->socket) { - $this->disconnect(); - } - } - - /** - * Return the log instance - * - * @return XMPPHP_Log - */ - public function getLog() { - return $this->log; - } - - /** - * Get next ID - * - * @return integer - */ - public function getId() { - $this->lastid++; - return $this->lastid; - } - - /** - * Set SSL - * - * @return integer - */ - public function useSSL($use=true) { - $this->use_ssl = $use; - } - - /** - * Add ID Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addIdHandler($id, $pointer, $obj = null) { - $this->idhandlers[$id] = array($pointer, $obj); - } - - /** - * Add Handler - * - * @param string $name - * @param string $ns - * @param string $pointer - * @param string $obj - * @param integer $depth - */ - public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) { - #TODO deprication warning - $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth); - } - - /** - * Add XPath Handler - * - * @param string $xpath - * @param string $pointer - * @param - */ - public function addXPathHandler($xpath, $pointer, $obj = null) { - if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) { - $ns_tags = $regs[0]; - } else { - $ns_tags = array($xpath); - } - foreach($ns_tags as $ns_tag) { - list($l, $r) = split("}", $ns_tag); - if ($r != null) { - $xpart = array(substr($l, 1), $r); - } else { - $xpart = array(null, $l); - } - $xpath_array[] = $xpart; - } - $this->xpathhandlers[] = array($xpath_array, $pointer, $obj); - } - - /** - * Add Event Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addEventHandler($name, $pointer, $obj) { - $this->eventhandlers[] = array($name, $pointer, $obj); - } - - /** - * Connect to XMPP Host - * - * @param integer $timeout - * @param boolean $persistent - * @param boolean $sendinit - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) { - $this->sent_disconnect = false; - $starttime = time(); - - do { - $this->disconnected = false; - $this->sent_disconnect = false; - if($persistent) { - $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT; - } else { - $conflag = STREAM_CLIENT_CONNECT; - } - $conntype = 'tcp'; - if($this->use_ssl) $conntype = 'ssl'; - $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}"); - try { - $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag); - } catch (Exception $e) { - throw new XMPPHP_Exception($e->getMessage()); - } - if(!$this->socket) { - $this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR); - $this->disconnected = true; - # Take it easy for a few seconds - sleep(min($timeout, 5)); - } - } while (!$this->socket && (time() - $starttime) < $timeout); - - if ($this->socket) { - stream_set_blocking($this->socket, 1); - if($sendinit) $this->send($this->stream_start); - } else { - throw new XMPPHP_Exception("Could not connect before timeout."); - } - } - - /** - * Reconnect XMPP Host - */ - public function doReconnect() { - if(!$this->is_server) { - $this->log->log("Reconnecting ($this->reconnectTimeout)...", XMPPHP_Log::LEVEL_WARNING); - $this->connect($this->reconnectTimeout, false, false); - $this->reset(); - $this->event('reconnect'); - } - } - - public function setReconnectTimeout($timeout) { - $this->reconnectTimeout = $timeout; - } - - /** - * Disconnect from XMPP Host - */ - public function disconnect() { - $this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE); - if(false == (bool) $this->socket) { - return; - } - $this->reconnect = false; - $this->send($this->stream_end); - $this->sent_disconnect = true; - $this->processUntil('end_stream', 5); - $this->disconnected = true; - } - - /** - * Are we are disconnected? - * - * @return boolean - */ - public function isDisconnected() { - return $this->disconnected; - } - - /** - * Core reading tool - * 0 -> only read if data is immediately ready - * NULL -> wait forever and ever - * integer -> process for this amount of time - */ - - private function __process($maximum=5) { - - $remaining = $maximum; - - do { - $starttime = (microtime(true) * 1000000); - $read = array($this->socket); - $write = array(); - $except = array(); - if (is_null($maximum)) { - $secs = NULL; - $usecs = NULL; - } else if ($maximum == 0) { - $secs = 0; - $usecs = 0; - } else { - $usecs = $remaining % 1000000; - $secs = floor(($remaining - $usecs) / 1000000); - } - $updated = @stream_select($read, $write, $except, $secs, $usecs); - if ($updated === false) { - $this->log->log("Error on stream_select()", XMPPHP_Log::LEVEL_VERBOSE); - if ($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } else if ($updated > 0) { - # XXX: Is this big enough? - $buff = @fread($this->socket, 4096); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } else { - # $updated == 0 means no changes during timeout. - } - $endtime = (microtime(true)*1000000); - $time_past = $endtime - $starttime; - $remaining = $remaining - $time_past; - } while (is_null($maximum) || $remaining > 0); - return true; - } - - /** - * Process - * - * @return string - */ - public function process() { - $this->__process(NULL); - } - - /** - * Process until a timeout occurs - * - * @param integer $timeout - * @return string - */ - public function processTime($timeout=NULL) { - if (is_null($timeout)) { - return $this->__process(NULL); - } else { - return $this->__process($timeout * 1000000); - } - } - - /** - * Process until a specified event or a timeout occurs - * - * @param string|array $event - * @param integer $timeout - * @return string - */ - public function processUntil($event, $timeout=-1) { - $start = time(); - if(!is_array($event)) $event = array($event); - $this->until[] = $event; - end($this->until); - $event_key = key($this->until); - reset($this->until); - $this->until_count[$event_key] = 0; - $updated = ''; - while(!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) { - $this->__process(); - } - if(array_key_exists($event_key, $this->until_payload)) { - $payload = $this->until_payload[$event_key]; - unset($this->until_payload[$event_key]); - unset($this->until_count[$event_key]); - unset($this->until[$event_key]); - } else { - $payload = array(); - } - return $payload; - } - - /** - * Obsolete? - */ - public function Xapply_socket($socket) { - $this->socket = $socket; - } - - /** - * XML start callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function startXML($parser, $name, $attr) { - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth++; - if(array_key_exists('XMLNS', $attr)) { - $this->current_ns[$this->xml_depth] = $attr['XMLNS']; - } else { - $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1]; - if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns; - } - $ns = $this->current_ns[$this->xml_depth]; - foreach($attr as $key => $value) { - if(strstr($key, ":")) { - $key = explode(':', $key); - $key = $key[1]; - $this->ns_map[$key] = $value; - } - } - if(!strstr($name, ":") === false) - { - $name = explode(':', $name); - $ns = $this->ns_map[$name[0]]; - $name = $name[1]; - } - $obj = new XMPPHP_XMLObj($name, $ns, $attr); - if($this->xml_depth > 1) { - $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj; - } - $this->xmlobj[$this->xml_depth] = $obj; - } - - /** - * XML end callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function endXML($parser, $name) { - #$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG); - #print "$name\n"; - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth--; - if($this->xml_depth == 1) { - #clean-up old objects - #$found = false; #FIXME This didn't appear to be in use --Gar - foreach($this->xpathhandlers as $handler) { - if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - $nstag = array_shift($handler[0]); - if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) { - foreach($handler[0] as $nstag) { - if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) { - $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]); - } else { - $searchxml = null; - break; - } - } - if ($searchxml !== null) { - if($handler[2] === null) $handler[2] = $this; - $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[2]->$handler[1]($this->xmlobj[2]); - } - } - } - } - foreach($this->nshandlers as $handler) { - if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) { - $searchxml = $this->xmlobj[2]->sub($handler[0]); - } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - } - if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { - if($handler[3] === null) $handler[3] = $this; - $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[3]->$handler[2]($this->xmlobj[2]); - } - } - foreach($this->idhandlers as $id => $handler) { - if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { - if($handler[1] === null) $handler[1] = $this; - $handler[1]->$handler[0]($this->xmlobj[2]); - #id handlers are only used once - unset($this->idhandlers[$id]); - break; - } - } - if(is_array($this->xmlobj)) { - $this->xmlobj = array_slice($this->xmlobj, 0, 1); - if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) { - $this->xmlobj[0]->subs = null; - } - } - unset($this->xmlobj[2]); - } - if($this->xml_depth == 0 and !$this->been_reset) { - if(!$this->disconnected) { - if(!$this->sent_disconnect) { - $this->send($this->stream_end); - } - $this->disconnected = true; - $this->sent_disconnect = true; - fclose($this->socket); - if($this->reconnect) { - $this->doReconnect(); - } - } - $this->event('end_stream'); - } - } - - /** - * XML character callback - * @see xml_set_character_data_handler - * - * @param resource $parser - * @param string $data - */ - public function charXML($parser, $data) { - if(array_key_exists($this->xml_depth, $this->xmlobj)) { - $this->xmlobj[$this->xml_depth]->data .= $data; - } - } - - /** - * Event? - * - * @param string $name - * @param string $payload - */ - public function event($name, $payload = null) { - $this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG); - foreach($this->eventhandlers as $handler) { - if($name == $handler[0]) { - if($handler[2] === null) { - $handler[2] = $this; - } - $handler[2]->$handler[1]($payload); - } - } - foreach($this->until as $key => $until) { - if(is_array($until)) { - if(in_array($name, $until)) { - $this->until_payload[$key][] = array($name, $payload); - if(!isset($this->until_count[$key])) { - $this->until_count[$key] = 0; - } - $this->until_count[$key] += 1; - #$this->until[$key] = false; - } - } - } - } - - /** - * Read from socket - */ - public function read() { - $buff = @fread($this->socket, 1024); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - - /** - * Send to socket - * - * @param string $msg - */ - public function send($msg, $timeout=NULL) { - - if (is_null($timeout)) { - $secs = NULL; - $usecs = NULL; - } else if ($timeout == 0) { - $secs = 0; - $usecs = 0; - } else { - $maximum = $timeout * 1000000; - $usecs = $maximum % 1000000; - $secs = floor(($maximum - $usecs) / 1000000); - } - - $read = array(); - $write = array($this->socket); - $except = array(); - - $select = @stream_select($read, $write, $except, $secs, $usecs); - - if($select === False) { - $this->log->log("ERROR sending message; reconnecting."); - $this->doReconnect(); - # TODO: retry send here - return false; - } elseif ($select > 0) { - $this->log->log("Socket is ready; send it.", XMPPHP_Log::LEVEL_VERBOSE); - } else { - $this->log->log("Socket is not ready; break.", XMPPHP_Log::LEVEL_ERROR); - return false; - } - - $sentbytes = @fwrite($this->socket, $msg); - $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE); - if($sentbytes === FALSE) { - $this->log->log("ERROR sending message; reconnecting.", XMPPHP_Log::LEVEL_ERROR); - $this->doReconnect(); - return false; - } - $this->log->log("Successfully sent $sentbytes bytes.", XMPPHP_Log::LEVEL_VERBOSE); - return $sentbytes; - } - - public function time() { - list($usec, $sec) = explode(" ", microtime()); - return (float)$sec + (float)$usec; - } - - /** - * Reset connection - */ - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - if(!$this->is_server) { - $this->send($this->stream_start); - } - $this->been_reset = true; - } - - /** - * Setup the XML parser - */ - public function setupParser() { - $this->parser = xml_parser_create('UTF-8'); - xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); - xml_set_object($this->parser, $this); - xml_set_element_handler($this->parser, 'startXML', 'endXML'); - xml_set_character_data_handler($this->parser, 'charXML'); - } - - public function readyToProcess() { - $read = array($this->socket); - $write = array(); - $except = array(); - $updated = @stream_select($read, $write, $except, 0); - return (($updated !== false) && ($updated > 0)); - } -} diff --git a/extlib/XMPPHP/XMPP.php b/extlib/XMPPHP/XMPP.php deleted file mode 100644 index c0f896339..000000000 --- a/extlib/XMPPHP/XMPP.php +++ /dev/null @@ -1,432 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMLStream.php"; -require_once dirname(__FILE__) . "/Roster.php"; - -/** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMPP extends XMPPHP_XMLStream { - /** - * @var string - */ - public $server; - - /** - * @var string - */ - public $user; - - /** - * @var string - */ - protected $password; - - /** - * @var string - */ - protected $resource; - - /** - * @var string - */ - protected $fulljid; - - /** - * @var string - */ - protected $basejid; - - /** - * @var boolean - */ - protected $authed = false; - protected $session_started = false; - - /** - * @var boolean - */ - protected $auto_subscribe = false; - - /** - * @var boolean - */ - protected $use_encryption = true; - - /** - * @var boolean - */ - public $track_presence = true; - - /** - * @var object - */ - public $roster; - - /** - * 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, $printlog, $loglevel); - - $this->user = $user; - $this->password = $password; - $this->resource = $resource; - if(!$server) $server = $host; - $this->basejid = $this->user . '@' . $this->host; - - $this->roster = new Roster(); - $this->track_presence = true; - - $this->stream_start = ''; - $this->stream_end = ''; - $this->default_ns = 'jabber:client'; - - $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler'); - $this->addXPathHandler('{jabber:client}message', 'message_handler'); - $this->addXPathHandler('{jabber:client}presence', 'presence_handler'); - $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler'); - } - - /** - * Turn encryption on/ff - * - * @param boolean $useEncryption - */ - public function useEncryption($useEncryption = true) { - $this->use_encryption = $useEncryption; - } - - /** - * Turn on auto-authorization of subscription requests. - * - * @param boolean $autoSubscribe - */ - public function autoSubscribe($autoSubscribe = true) { - $this->auto_subscribe = $autoSubscribe; - } - - /** - * Send XMPP Message - * - * @param string $to - * @param string $body - * @param string $type - * @param string $subject - */ - public function message($to, $body, $type = 'chat', $subject = null, $payload = null) { - if(is_null($type)) - { - $type = 'chat'; - } - - $to = htmlspecialchars($to); - $body = htmlspecialchars($body); - $subject = htmlspecialchars($subject); - - $out = "fulljid}\" to=\"$to\" type='$type'>"; - if($subject) $out .= "$subject"; - $out .= "$body"; - if($payload) $out .= $payload; - $out .= ""; - - $this->send($out); - } - - /** - * Set Presence - * - * @param string $status - * @param string $show - * @param string $to - */ - public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) { - if($type == 'available') $type = ''; - $to = htmlspecialchars($to); - $status = htmlspecialchars($status); - if($show == 'unavailable') $type = 'unavailable'; - - $out = "send($out); - } - /** - * Send Auth request - * - * @param string $jid - */ - public function subscribe($jid) { - $this->send(""); - #$this->send(""); - } - - /** - * Message handler - * - * @param string $xml - */ - public function message_handler($xml) { - if(isset($xml->attrs['type'])) { - $payload['type'] = $xml->attrs['type']; - } else { - $payload['type'] = 'chat'; - } - $payload['from'] = $xml->attrs['from']; - $payload['body'] = $xml->sub('body')->data; - $payload['xml'] = $xml; - $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG); - $this->event('message', $payload); - } - - /** - * Presence handler - * - * @param string $xml - */ - public function presence_handler($xml) { - $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available'; - $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type']; - $payload['from'] = $xml->attrs['from']; - $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : ''; - $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0; - $payload['xml'] = $xml; - if($this->track_presence) { - $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']); - } - $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG); - if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') { - if($this->auto_subscribe) { - $this->send(""); - $this->send(""); - } - $this->event('subscription_requested', $payload); - } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') { - $this->event('subscription_accepted', $payload); - } else { - $this->event('presence', $payload); - } - } - - /** - * Features handler - * - * @param string $xml - */ - protected function features_handler($xml) { - if($xml->hasSub('starttls') and $this->use_encryption) { - $this->send(""); - } elseif($xml->hasSub('bind') and $this->authed) { - $id = $this->getId(); - $this->addIdHandler($id, 'resource_bind_handler'); - $this->send("{$this->resource}"); - } else { - $this->log->log("Attempting Auth..."); - if ($this->password) { - $this->send("" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . ""); - } else { - $this->send(""); - } - } - } - - /** - * SASL success handler - * - * @param string $xml - */ - protected function sasl_success_handler($xml) { - $this->log->log("Auth success!"); - $this->authed = true; - $this->reset(); - } - - /** - * SASL feature handler - * - * @param string $xml - */ - protected function sasl_failure_handler($xml) { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - - throw new XMPPHP_Exception('Auth failed!'); - } - - /** - * Resource bind handler - * - * @param string $xml - */ - protected function resource_bind_handler($xml) { - if($xml->attrs['type'] == 'result') { - $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data); - $this->fulljid = $xml->sub('bind')->sub('jid')->data; - $jidarray = explode('/',$this->fulljid); - $this->jid = $jidarray[0]; - } - $id = $this->getId(); - $this->addIdHandler($id, 'session_start_handler'); - $this->send(""); - } - - /** - * Retrieves the roster - * - */ - public function getRoster() { - $id = $this->getID(); - $this->send(""); - } - - /** - * Roster iq handler - * Gets all packets matching XPath "iq/{jabber:iq:roster}query' - * - * @param string $xml - */ - protected function roster_iq_handler($xml) { - $status = "result"; - $xmlroster = $xml->sub('query'); - foreach($xmlroster->subs as $item) { - $groups = array(); - if ($item->name == 'item') { - $jid = $item->attrs['jid']; //REQUIRED - $name = $item->attrs['name']; //MAY - $subscription = $item->attrs['subscription']; - foreach($item->subs as $subitem) { - if ($subitem->name == 'group') { - $groups[] = $subitem->data; - } - } - $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen - } else { - $status = "error"; - } - } - if ($status == "result") { //No errors, add contacts - foreach($contacts as $contact) { - $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]); - } - } - if ($xml->attrs['type'] == 'set') { - $this->send("attrs['id']}\" to=\"{$xml->attrs['from']}\" />"); - } - } - - /** - * Session start handler - * - * @param string $xml - */ - protected function session_start_handler($xml) { - $this->log->log("Session started"); - $this->session_started = true; - $this->event('session_start'); - } - - /** - * TLS proceed handler - * - * @param string $xml - */ - protected function tls_proceed_handler($xml) { - $this->log->log("Starting TLS encryption"); - stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); - $this->reset(); - } - - /** - * Retrieves the vcard - * - */ - public function getVCard($jid = Null) { - $id = $this->getID(); - $this->addIdHandler($id, 'vcard_get_handler'); - if($jid) { - $this->send(""); - } else { - $this->send(""); - } - } - - /** - * VCard retrieval handler - * - * @param XML Object $xml - */ - protected function vcard_get_handler($xml) { - $vcard_array = array(); - $vcard = $xml->sub('vcard'); - // go through all of the sub elements and add them to the vcard array - foreach ($vcard->subs as $sub) { - if ($sub->subs) { - $vcard_array[$sub->name] = array(); - foreach ($sub->subs as $sub_child) { - $vcard_array[$sub->name][$sub_child->name] = $sub_child->data; - } - } else { - $vcard_array[$sub->name] = $sub->data; - } - } - $vcard_array['from'] = $xml->attrs['from']; - $this->event('vcard', $vcard_array); - } -} diff --git a/extlib/XMPPHP/XMPP_Old.php b/extlib/XMPPHP/XMPP_Old.php deleted file mode 100644 index 43f56b154..000000000 --- a/extlib/XMPPHP/XMPP_Old.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMPP - * - * This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's. - * In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it. - * The old Jabber protocol wasn't standardized, so use at your own risk. - * - */ -require_once "XMPP.php"; - - class XMPPHP_XMPPOld extends XMPPHP_XMPP { - /** - * - * @var string - */ - protected $session_id; - - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - if(!$server) $server = $host; - $this->stream_start = ''; - $this->fulljid = "{$user}@{$server}/{$resource}"; - } - - /** - * Override XMLStream's startXML - * - * @param parser $parser - * @param string $name - * @param array $attr - */ - public function startXML($parser, $name, $attr) { - if($this->xml_depth == 0) { - $this->session_id = $attr['ID']; - $this->authenticate(); - } - parent::startXML($parser, $name, $attr); - } - - /** - * Send Authenticate Info Request - * - */ - public function authenticate() { - $id = $this->getId(); - $this->addidhandler($id, 'authfieldshandler'); - $this->send("{$this->user}"); - } - - /** - * Retrieve auth fields and send auth attempt - * - * @param XMLObj $xml - */ - public function authFieldsHandler($xml) { - $id = $this->getId(); - $this->addidhandler($id, 'oldAuthResultHandler'); - if($xml->sub('query')->hasSub('digest')) { - $hash = sha1($this->session_id . $this->password); - print "{$this->session_id} {$this->password}\n"; - $out = "{$this->user}{$hash}{$this->resource}"; - } else { - $out = "{$this->user}{$this->password}{$this->resource}"; - } - $this->send($out); - - } - - /** - * Determine authenticated or failure - * - * @param XMLObj $xml - */ - public function oldAuthResultHandler($xml) { - if($xml->attrs['type'] != 'result') { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - throw new XMPPHP_Exception('Auth failed!'); - } else { - $this->log->log("Session started"); - $this->event('session_start'); - } - } - } - - -?> diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index a2521536b..9d75e2475 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -34,8 +34,6 @@ if (!defined('STATUSNET')) { exit(1); } -set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/XMPPHP'); - /** * Plugin for XMPP * @@ -287,7 +285,7 @@ class XmppPlugin extends ImPlugin switch ($cls) { case 'XMPPHP_XMPP': - require_once 'XMPP.php'; + require_once $dir . '/extlib/XMPPHP/XMPP.php'; return false; case 'Sharing_XMPP': case 'Queued_XMPP': diff --git a/plugins/Xmpp/extlib/XMPPHP/BOSH.php b/plugins/Xmpp/extlib/XMPPHP/BOSH.php new file mode 100644 index 000000000..befaf60a7 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/BOSH.php @@ -0,0 +1,188 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** XMPPHP_XMLStream */ +require_once dirname(__FILE__) . "/XMPP.php"; + +/** + * XMPPHP Main Class + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_BOSH extends XMPPHP_XMPP { + + protected $rid; + protected $sid; + protected $http_server; + protected $http_buffer = Array(); + protected $session = false; + + public function connect($server, $wait='1', $session=false) { + $this->http_server = $server; + $this->use_encryption = false; + $this->session = $session; + + $this->rid = 3001; + $this->sid = null; + if($session) + { + $this->loadSession(); + } + if(!$this->sid) { + $body = $this->__buildBody(); + $body->addAttribute('hold','1'); + $body->addAttribute('to', $this->host); + $body->addAttribute('route', "xmpp:{$this->host}:{$this->port}"); + $body->addAttribute('secure','true'); + $body->addAttribute('xmpp:version','1.6', 'urn:xmpp:xbosh'); + $body->addAttribute('wait', strval($wait)); + $body->addAttribute('ack','1'); + $body->addAttribute('xmlns:xmpp','urn:xmpp:xbosh'); + $buff = ""; + xml_parse($this->parser, $buff, false); + $response = $this->__sendBody($body); + $rxml = new SimpleXMLElement($response); + $this->sid = $rxml['sid']; + + } else { + $buff = ""; + xml_parse($this->parser, $buff, false); + } + } + + public function __sendBody($body=null, $recv=true) { + if(!$body) { + $body = $this->__buildBody(); + } + $ch = curl_init($this->http_server); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML()); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $header = array('Accept-Encoding: gzip, deflate','Content-Type: text/xml; charset=utf-8'); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header ); + curl_setopt($ch, CURLOPT_VERBOSE, 0); + $output = ''; + if($recv) { + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($ch); + $this->http_buffer[] = $output; + } + curl_close($ch); + return $output; + } + + public function __buildBody($sub=null) { + $xml = new SimpleXMLElement(""); + $xml->addAttribute('content', 'text/xml; charset=utf-8'); + $xml->addAttribute('rid', $this->rid); + $this->rid += 1; + if($this->sid) $xml->addAttribute('sid', $this->sid); + #if($this->sid) $xml->addAttribute('xmlns', 'http://jabber.org/protocol/httpbind'); + $xml->addAttribute('xml:lang', 'en'); + if($sub) { // ok, so simplexml is lame + $p = dom_import_simplexml($xml); + $c = dom_import_simplexml($sub); + $cn = $p->ownerDocument->importNode($c, true); + $p->appendChild($cn); + $xml = simplexml_import_dom($p); + } + return $xml; + } + + public function __process() { + if($this->http_buffer) { + $this->__parseBuffer(); + } else { + $this->__sendBody(); + $this->__parseBuffer(); + } + } + + public function __parseBuffer() { + while ($this->http_buffer) { + $idx = key($this->http_buffer); + $buffer = $this->http_buffer[$idx]; + unset($this->http_buffer[$idx]); + if($buffer) { + $xml = new SimpleXMLElement($buffer); + $children = $xml->xpath('child::node()'); + foreach ($children as $child) { + $buff = $child->asXML(); + $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } + } + } + } + + public function send($msg) { + $this->log->log("SEND: $msg", XMPPHP_Log::LEVEL_VERBOSE); + $msg = new SimpleXMLElement($msg); + #$msg->addAttribute('xmlns', 'jabber:client'); + $this->__sendBody($this->__buildBody($msg), true); + #$this->__parseBuffer(); + } + + public function reset() { + $this->xml_depth = 0; + unset($this->xmlobj); + $this->xmlobj = array(); + $this->setupParser(); + #$this->send($this->stream_start); + $body = $this->__buildBody(); + $body->addAttribute('to', $this->host); + $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh'); + $buff = ""; + $response = $this->__sendBody($body); + $this->been_reset = true; + xml_parse($this->parser, $buff, false); + } + + public function loadSession() { + if(isset($_SESSION['XMPPHP_BOSH_RID'])) $this->rid = $_SESSION['XMPPHP_BOSH_RID']; + if(isset($_SESSION['XMPPHP_BOSH_SID'])) $this->sid = $_SESSION['XMPPHP_BOSH_SID']; + if(isset($_SESSION['XMPPHP_BOSH_authed'])) $this->authed = $_SESSION['XMPPHP_BOSH_authed']; + if(isset($_SESSION['XMPPHP_BOSH_jid'])) $this->jid = $_SESSION['XMPPHP_BOSH_jid']; + if(isset($_SESSION['XMPPHP_BOSH_fulljid'])) $this->fulljid = $_SESSION['XMPPHP_BOSH_fulljid']; + } + + public function saveSession() { + $_SESSION['XMPPHP_BOSH_RID'] = (string) $this->rid; + $_SESSION['XMPPHP_BOSH_SID'] = (string) $this->sid; + $_SESSION['XMPPHP_BOSH_authed'] = (boolean) $this->authed; + $_SESSION['XMPPHP_BOSH_jid'] = (string) $this->jid; + $_SESSION['XMPPHP_BOSH_fulljid'] = (string) $this->fulljid; + } +} diff --git a/plugins/Xmpp/extlib/XMPPHP/Exception.php b/plugins/Xmpp/extlib/XMPPHP/Exception.php new file mode 100644 index 000000000..da59bc791 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/Exception.php @@ -0,0 +1,41 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** + * XMPPHP Exception + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_Exception extends Exception { +} diff --git a/plugins/Xmpp/extlib/XMPPHP/Log.php b/plugins/Xmpp/extlib/XMPPHP/Log.php new file mode 100644 index 000000000..a9bce3d84 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/Log.php @@ -0,0 +1,119 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** + * XMPPHP Log + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_Log { + + const LEVEL_ERROR = 0; + const LEVEL_WARNING = 1; + const LEVEL_INFO = 2; + const LEVEL_DEBUG = 3; + const LEVEL_VERBOSE = 4; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var array + */ + protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE'); + + /** + * @var integer + */ + protected $runlevel; + + /** + * @var boolean + */ + protected $printout; + + /** + * Constructor + * + * @param boolean $printout + * @param string $runlevel + */ + public function __construct($printout = false, $runlevel = self::LEVEL_INFO) { + $this->printout = (boolean)$printout; + $this->runlevel = (int)$runlevel; + } + + /** + * Add a message to the log data array + * If printout in this instance is set to true, directly output the message + * + * @param string $msg + * @param integer $runlevel + */ + public function log($msg, $runlevel = self::LEVEL_INFO) { + $time = time(); + #$this->data[] = array($this->runlevel, $msg, $time); + if($this->printout and $runlevel <= $this->runlevel) { + $this->writeLine($msg, $runlevel, $time); + } + } + + /** + * Output the complete log. + * Log will be cleared if $clear = true + * + * @param boolean $clear + * @param integer $runlevel + */ + public function printout($clear = true, $runlevel = null) { + if($runlevel === null) { + $runlevel = $this->runlevel; + } + foreach($this->data as $data) { + if($runlevel <= $data[0]) { + $this->writeLine($data[1], $runlevel, $data[2]); + } + } + if($clear) { + $this->data = array(); + } + } + + protected function writeLine($msg, $runlevel, $time) { + //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n"; + echo $time." [".$this->names[$runlevel]."]: ".$msg."\n"; + flush(); + } +} diff --git a/plugins/Xmpp/extlib/XMPPHP/Roster.php b/plugins/Xmpp/extlib/XMPPHP/Roster.php new file mode 100644 index 000000000..2e459e2a2 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/Roster.php @@ -0,0 +1,163 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** + * XMPPHP Roster Object + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ + +class Roster { + /** + * Roster array, handles contacts and presence. Indexed by jid. + * Contains array with potentially two indexes 'contact' and 'presence' + * @var array + */ + protected $roster_array = array(); + /** + * Constructor + * + */ + public function __construct($roster_array = array()) { + if ($this->verifyRoster($roster_array)) { + $this->roster_array = $roster_array; //Allow for prepopulation with existing roster + } else { + $this->roster_array = array(); + } + } + + /** + * + * Check that a given roster array is of a valid structure (empty is still valid) + * + * @param array $roster_array + */ + protected function verifyRoster($roster_array) { + #TODO once we know *what* a valid roster array looks like + return True; + } + + /** + * + * Add given contact to roster + * + * @param string $jid + * @param string $subscription + * @param string $name + * @param array $groups + */ + public function addContact($jid, $subscription, $name='', $groups=array()) { + $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups); + if ($this->isContact($jid)) { + $this->roster_array[$jid]['contact'] = $contact; + } else { + $this->roster_array[$jid] = array('contact' => $contact); + } + } + + /** + * + * Retrieve contact via jid + * + * @param string $jid + */ + public function getContact($jid) { + if ($this->isContact($jid)) { + return $this->roster_array[$jid]['contact']; + } + } + + /** + * + * Discover if a contact exists in the roster via jid + * + * @param string $jid + */ + public function isContact($jid) { + return (array_key_exists($jid, $this->roster_array)); + } + + /** + * + * Set presence + * + * @param string $presence + * @param integer $priority + * @param string $show + * @param string $status + */ + public function setPresence($presence, $priority, $show, $status) { + list($jid, $resource) = split("/", $presence); + if ($show != 'unavailable') { + if (!$this->isContact($jid)) { + $this->addContact($jid, 'not-in-roster'); + } + $resource = $resource ? $resource : ''; + $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); + } else { //Nuke unavailable resources to save memory + unset($this->roster_array[$jid]['resource'][$resource]); + } + } + + /* + * + * Return best presence for jid + * + * @param string $jid + */ + public function getPresence($jid) { + $split = split("/", $jid); + $jid = $split[0]; + if($this->isContact($jid)) { + $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 + foreach($this->roster_array[$jid]['presence'] as $resource => $presence) { + //Highest available priority or just highest priority + if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) { + $current = $presence; + $current['resource'] = $resource; + } + } + return $current; + } + } + /** + * + * Get roster + * + */ + public function getRoster() { + return $this->roster_array; + } +} +?> diff --git a/plugins/Xmpp/extlib/XMPPHP/XMLObj.php b/plugins/Xmpp/extlib/XMPPHP/XMLObj.php new file mode 100644 index 000000000..0d3e21991 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/XMLObj.php @@ -0,0 +1,158 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** + * XMPPHP XML Object + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_XMLObj { + /** + * Tag name + * + * @var string + */ + public $name; + + /** + * Namespace + * + * @var string + */ + public $ns; + + /** + * Attributes + * + * @var array + */ + public $attrs = array(); + + /** + * Subs? + * + * @var array + */ + public $subs = array(); + + /** + * Node data + * + * @var string + */ + public $data = ''; + + /** + * Constructor + * + * @param string $name + * @param string $ns + * @param array $attrs + * @param string $data + */ + public function __construct($name, $ns = '', $attrs = array(), $data = '') { + $this->name = strtolower($name); + $this->ns = $ns; + if(is_array($attrs) && count($attrs)) { + foreach($attrs as $key => $value) { + $this->attrs[strtolower($key)] = $value; + } + } + $this->data = $data; + } + + /** + * Dump this XML Object to output. + * + * @param integer $depth + */ + public function printObj($depth = 0) { + print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data; + print "\n"; + foreach($this->subs as $sub) { + $sub->printObj($depth + 1); + } + } + + /** + * Return this XML Object in xml notation + * + * @param string $str + */ + public function toString($str = '') { + $str .= "<{$this->name} xmlns='{$this->ns}' "; + foreach($this->attrs as $key => $value) { + if($key != 'xmlns') { + $value = htmlspecialchars($value); + $str .= "$key='$value' "; + } + } + $str .= ">"; + foreach($this->subs as $sub) { + $str .= $sub->toString(); + } + $body = htmlspecialchars($this->data); + $str .= "$bodyname}>"; + return $str; + } + + /** + * Has this XML Object the given sub? + * + * @param string $name + * @return boolean + */ + public function hasSub($name, $ns = null) { + foreach($this->subs as $sub) { + if(($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) return true; + } + return false; + } + + /** + * Return a sub + * + * @param string $name + * @param string $attrs + * @param string $ns + */ + public function sub($name, $attrs = null, $ns = null) { + #TODO attrs is ignored + foreach($this->subs as $sub) { + if($sub->name == $name and ($ns == null or $sub->ns == $ns)) { + return $sub; + } + } + } +} diff --git a/plugins/Xmpp/extlib/XMPPHP/XMLStream.php b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php new file mode 100644 index 000000000..d33411ec5 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php @@ -0,0 +1,763 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** XMPPHP_Exception */ +require_once dirname(__FILE__) . '/Exception.php'; + +/** XMPPHP_XMLObj */ +require_once dirname(__FILE__) . '/XMLObj.php'; + +/** XMPPHP_Log */ +require_once dirname(__FILE__) . '/Log.php'; + +/** + * XMPPHP XML Stream + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_XMLStream { + /** + * @var resource + */ + protected $socket; + /** + * @var resource + */ + protected $parser; + /** + * @var string + */ + protected $buffer; + /** + * @var integer + */ + protected $xml_depth = 0; + /** + * @var string + */ + protected $host; + /** + * @var integer + */ + protected $port; + /** + * @var string + */ + protected $stream_start = ''; + /** + * @var string + */ + protected $stream_end = ''; + /** + * @var boolean + */ + protected $disconnected = false; + /** + * @var boolean + */ + protected $sent_disconnect = false; + /** + * @var array + */ + protected $ns_map = array(); + /** + * @var array + */ + protected $current_ns = array(); + /** + * @var array + */ + protected $xmlobj = null; + /** + * @var array + */ + protected $nshandlers = array(); + /** + * @var array + */ + protected $xpathhandlers = array(); + /** + * @var array + */ + protected $idhandlers = array(); + /** + * @var array + */ + protected $eventhandlers = array(); + /** + * @var integer + */ + protected $lastid = 0; + /** + * @var string + */ + protected $default_ns; + /** + * @var string + */ + protected $until = ''; + /** + * @var string + */ + protected $until_count = ''; + /** + * @var array + */ + protected $until_happened = false; + /** + * @var array + */ + protected $until_payload = array(); + /** + * @var XMPPHP_Log + */ + protected $log; + /** + * @var boolean + */ + protected $reconnect = true; + /** + * @var boolean + */ + protected $been_reset = false; + /** + * @var boolean + */ + protected $is_server; + /** + * @var float + */ + protected $last_send = 0; + /** + * @var boolean + */ + protected $use_ssl = false; + /** + * @var integer + */ + protected $reconnectTimeout = 30; + + /** + * Constructor + * + * @param string $host + * @param string $port + * @param boolean $printlog + * @param string $loglevel + * @param boolean $is_server + */ + public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) { + $this->reconnect = !$is_server; + $this->is_server = $is_server; + $this->host = $host; + $this->port = $port; + $this->setupParser(); + $this->log = new XMPPHP_Log($printlog, $loglevel); + } + + /** + * Destructor + * Cleanup connection + */ + public function __destruct() { + if(!$this->disconnected && $this->socket) { + $this->disconnect(); + } + } + + /** + * Return the log instance + * + * @return XMPPHP_Log + */ + public function getLog() { + return $this->log; + } + + /** + * Get next ID + * + * @return integer + */ + public function getId() { + $this->lastid++; + return $this->lastid; + } + + /** + * Set SSL + * + * @return integer + */ + public function useSSL($use=true) { + $this->use_ssl = $use; + } + + /** + * Add ID Handler + * + * @param integer $id + * @param string $pointer + * @param string $obj + */ + public function addIdHandler($id, $pointer, $obj = null) { + $this->idhandlers[$id] = array($pointer, $obj); + } + + /** + * Add Handler + * + * @param string $name + * @param string $ns + * @param string $pointer + * @param string $obj + * @param integer $depth + */ + public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) { + #TODO deprication warning + $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth); + } + + /** + * Add XPath Handler + * + * @param string $xpath + * @param string $pointer + * @param + */ + public function addXPathHandler($xpath, $pointer, $obj = null) { + if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) { + $ns_tags = $regs[0]; + } else { + $ns_tags = array($xpath); + } + foreach($ns_tags as $ns_tag) { + list($l, $r) = split("}", $ns_tag); + if ($r != null) { + $xpart = array(substr($l, 1), $r); + } else { + $xpart = array(null, $l); + } + $xpath_array[] = $xpart; + } + $this->xpathhandlers[] = array($xpath_array, $pointer, $obj); + } + + /** + * Add Event Handler + * + * @param integer $id + * @param string $pointer + * @param string $obj + */ + public function addEventHandler($name, $pointer, $obj) { + $this->eventhandlers[] = array($name, $pointer, $obj); + } + + /** + * Connect to XMPP Host + * + * @param integer $timeout + * @param boolean $persistent + * @param boolean $sendinit + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) { + $this->sent_disconnect = false; + $starttime = time(); + + do { + $this->disconnected = false; + $this->sent_disconnect = false; + if($persistent) { + $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT; + } else { + $conflag = STREAM_CLIENT_CONNECT; + } + $conntype = 'tcp'; + if($this->use_ssl) $conntype = 'ssl'; + $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}"); + try { + $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag); + } catch (Exception $e) { + throw new XMPPHP_Exception($e->getMessage()); + } + if(!$this->socket) { + $this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR); + $this->disconnected = true; + # Take it easy for a few seconds + sleep(min($timeout, 5)); + } + } while (!$this->socket && (time() - $starttime) < $timeout); + + if ($this->socket) { + stream_set_blocking($this->socket, 1); + if($sendinit) $this->send($this->stream_start); + } else { + throw new XMPPHP_Exception("Could not connect before timeout."); + } + } + + /** + * Reconnect XMPP Host + */ + public function doReconnect() { + if(!$this->is_server) { + $this->log->log("Reconnecting ($this->reconnectTimeout)...", XMPPHP_Log::LEVEL_WARNING); + $this->connect($this->reconnectTimeout, false, false); + $this->reset(); + $this->event('reconnect'); + } + } + + public function setReconnectTimeout($timeout) { + $this->reconnectTimeout = $timeout; + } + + /** + * Disconnect from XMPP Host + */ + public function disconnect() { + $this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE); + if(false == (bool) $this->socket) { + return; + } + $this->reconnect = false; + $this->send($this->stream_end); + $this->sent_disconnect = true; + $this->processUntil('end_stream', 5); + $this->disconnected = true; + } + + /** + * Are we are disconnected? + * + * @return boolean + */ + public function isDisconnected() { + return $this->disconnected; + } + + /** + * Core reading tool + * 0 -> only read if data is immediately ready + * NULL -> wait forever and ever + * integer -> process for this amount of time + */ + + private function __process($maximum=5) { + + $remaining = $maximum; + + do { + $starttime = (microtime(true) * 1000000); + $read = array($this->socket); + $write = array(); + $except = array(); + if (is_null($maximum)) { + $secs = NULL; + $usecs = NULL; + } else if ($maximum == 0) { + $secs = 0; + $usecs = 0; + } else { + $usecs = $remaining % 1000000; + $secs = floor(($remaining - $usecs) / 1000000); + } + $updated = @stream_select($read, $write, $except, $secs, $usecs); + if ($updated === false) { + $this->log->log("Error on stream_select()", XMPPHP_Log::LEVEL_VERBOSE); + if ($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + $this->socket = NULL; + return false; + } + } else if ($updated > 0) { + # XXX: Is this big enough? + $buff = @fread($this->socket, 4096); + if(!$buff) { + if($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + $this->socket = NULL; + return false; + } + } + $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } else { + # $updated == 0 means no changes during timeout. + } + $endtime = (microtime(true)*1000000); + $time_past = $endtime - $starttime; + $remaining = $remaining - $time_past; + } while (is_null($maximum) || $remaining > 0); + return true; + } + + /** + * Process + * + * @return string + */ + public function process() { + $this->__process(NULL); + } + + /** + * Process until a timeout occurs + * + * @param integer $timeout + * @return string + */ + public function processTime($timeout=NULL) { + if (is_null($timeout)) { + return $this->__process(NULL); + } else { + return $this->__process($timeout * 1000000); + } + } + + /** + * Process until a specified event or a timeout occurs + * + * @param string|array $event + * @param integer $timeout + * @return string + */ + public function processUntil($event, $timeout=-1) { + $start = time(); + if(!is_array($event)) $event = array($event); + $this->until[] = $event; + end($this->until); + $event_key = key($this->until); + reset($this->until); + $this->until_count[$event_key] = 0; + $updated = ''; + while(!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) { + $this->__process(); + } + if(array_key_exists($event_key, $this->until_payload)) { + $payload = $this->until_payload[$event_key]; + unset($this->until_payload[$event_key]); + unset($this->until_count[$event_key]); + unset($this->until[$event_key]); + } else { + $payload = array(); + } + return $payload; + } + + /** + * Obsolete? + */ + public function Xapply_socket($socket) { + $this->socket = $socket; + } + + /** + * XML start callback + * + * @see xml_set_element_handler + * + * @param resource $parser + * @param string $name + */ + public function startXML($parser, $name, $attr) { + if($this->been_reset) { + $this->been_reset = false; + $this->xml_depth = 0; + } + $this->xml_depth++; + if(array_key_exists('XMLNS', $attr)) { + $this->current_ns[$this->xml_depth] = $attr['XMLNS']; + } else { + $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1]; + if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns; + } + $ns = $this->current_ns[$this->xml_depth]; + foreach($attr as $key => $value) { + if(strstr($key, ":")) { + $key = explode(':', $key); + $key = $key[1]; + $this->ns_map[$key] = $value; + } + } + if(!strstr($name, ":") === false) + { + $name = explode(':', $name); + $ns = $this->ns_map[$name[0]]; + $name = $name[1]; + } + $obj = new XMPPHP_XMLObj($name, $ns, $attr); + if($this->xml_depth > 1) { + $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj; + } + $this->xmlobj[$this->xml_depth] = $obj; + } + + /** + * XML end callback + * + * @see xml_set_element_handler + * + * @param resource $parser + * @param string $name + */ + public function endXML($parser, $name) { + #$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG); + #print "$name\n"; + if($this->been_reset) { + $this->been_reset = false; + $this->xml_depth = 0; + } + $this->xml_depth--; + if($this->xml_depth == 1) { + #clean-up old objects + #$found = false; #FIXME This didn't appear to be in use --Gar + foreach($this->xpathhandlers as $handler) { + if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) { + $searchxml = $this->xmlobj[2]; + $nstag = array_shift($handler[0]); + if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) { + foreach($handler[0] as $nstag) { + if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) { + $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]); + } else { + $searchxml = null; + break; + } + } + if ($searchxml !== null) { + if($handler[2] === null) $handler[2] = $this; + $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG); + $handler[2]->$handler[1]($this->xmlobj[2]); + } + } + } + } + foreach($this->nshandlers as $handler) { + if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) { + $searchxml = $this->xmlobj[2]->sub($handler[0]); + } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) { + $searchxml = $this->xmlobj[2]; + } + if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { + if($handler[3] === null) $handler[3] = $this; + $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG); + $handler[3]->$handler[2]($this->xmlobj[2]); + } + } + foreach($this->idhandlers as $id => $handler) { + if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { + if($handler[1] === null) $handler[1] = $this; + $handler[1]->$handler[0]($this->xmlobj[2]); + #id handlers are only used once + unset($this->idhandlers[$id]); + break; + } + } + if(is_array($this->xmlobj)) { + $this->xmlobj = array_slice($this->xmlobj, 0, 1); + if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) { + $this->xmlobj[0]->subs = null; + } + } + unset($this->xmlobj[2]); + } + if($this->xml_depth == 0 and !$this->been_reset) { + if(!$this->disconnected) { + if(!$this->sent_disconnect) { + $this->send($this->stream_end); + } + $this->disconnected = true; + $this->sent_disconnect = true; + fclose($this->socket); + if($this->reconnect) { + $this->doReconnect(); + } + } + $this->event('end_stream'); + } + } + + /** + * XML character callback + * @see xml_set_character_data_handler + * + * @param resource $parser + * @param string $data + */ + public function charXML($parser, $data) { + if(array_key_exists($this->xml_depth, $this->xmlobj)) { + $this->xmlobj[$this->xml_depth]->data .= $data; + } + } + + /** + * Event? + * + * @param string $name + * @param string $payload + */ + public function event($name, $payload = null) { + $this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG); + foreach($this->eventhandlers as $handler) { + if($name == $handler[0]) { + if($handler[2] === null) { + $handler[2] = $this; + } + $handler[2]->$handler[1]($payload); + } + } + foreach($this->until as $key => $until) { + if(is_array($until)) { + if(in_array($name, $until)) { + $this->until_payload[$key][] = array($name, $payload); + if(!isset($this->until_count[$key])) { + $this->until_count[$key] = 0; + } + $this->until_count[$key] += 1; + #$this->until[$key] = false; + } + } + } + } + + /** + * Read from socket + */ + public function read() { + $buff = @fread($this->socket, 1024); + if(!$buff) { + if($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + return false; + } + } + $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } + + /** + * Send to socket + * + * @param string $msg + */ + public function send($msg, $timeout=NULL) { + + if (is_null($timeout)) { + $secs = NULL; + $usecs = NULL; + } else if ($timeout == 0) { + $secs = 0; + $usecs = 0; + } else { + $maximum = $timeout * 1000000; + $usecs = $maximum % 1000000; + $secs = floor(($maximum - $usecs) / 1000000); + } + + $read = array(); + $write = array($this->socket); + $except = array(); + + $select = @stream_select($read, $write, $except, $secs, $usecs); + + if($select === False) { + $this->log->log("ERROR sending message; reconnecting."); + $this->doReconnect(); + # TODO: retry send here + return false; + } elseif ($select > 0) { + $this->log->log("Socket is ready; send it.", XMPPHP_Log::LEVEL_VERBOSE); + } else { + $this->log->log("Socket is not ready; break.", XMPPHP_Log::LEVEL_ERROR); + return false; + } + + $sentbytes = @fwrite($this->socket, $msg); + $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE); + if($sentbytes === FALSE) { + $this->log->log("ERROR sending message; reconnecting.", XMPPHP_Log::LEVEL_ERROR); + $this->doReconnect(); + return false; + } + $this->log->log("Successfully sent $sentbytes bytes.", XMPPHP_Log::LEVEL_VERBOSE); + return $sentbytes; + } + + public function time() { + list($usec, $sec) = explode(" ", microtime()); + return (float)$sec + (float)$usec; + } + + /** + * Reset connection + */ + public function reset() { + $this->xml_depth = 0; + unset($this->xmlobj); + $this->xmlobj = array(); + $this->setupParser(); + if(!$this->is_server) { + $this->send($this->stream_start); + } + $this->been_reset = true; + } + + /** + * Setup the XML parser + */ + public function setupParser() { + $this->parser = xml_parser_create('UTF-8'); + xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + xml_set_object($this->parser, $this); + xml_set_element_handler($this->parser, 'startXML', 'endXML'); + xml_set_character_data_handler($this->parser, 'charXML'); + } + + public function readyToProcess() { + $read = array($this->socket); + $write = array(); + $except = array(); + $updated = @stream_select($read, $write, $except, 0); + return (($updated !== false) && ($updated > 0)); + } +} diff --git a/plugins/Xmpp/extlib/XMPPHP/XMPP.php b/plugins/Xmpp/extlib/XMPPHP/XMPP.php new file mode 100644 index 000000000..c0f896339 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/XMPP.php @@ -0,0 +1,432 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** XMPPHP_XMLStream */ +require_once dirname(__FILE__) . "/XMLStream.php"; +require_once dirname(__FILE__) . "/Roster.php"; + +/** + * XMPPHP Main Class + * + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ + */ +class XMPPHP_XMPP extends XMPPHP_XMLStream { + /** + * @var string + */ + public $server; + + /** + * @var string + */ + public $user; + + /** + * @var string + */ + protected $password; + + /** + * @var string + */ + protected $resource; + + /** + * @var string + */ + protected $fulljid; + + /** + * @var string + */ + protected $basejid; + + /** + * @var boolean + */ + protected $authed = false; + protected $session_started = false; + + /** + * @var boolean + */ + protected $auto_subscribe = false; + + /** + * @var boolean + */ + protected $use_encryption = true; + + /** + * @var boolean + */ + public $track_presence = true; + + /** + * @var object + */ + public $roster; + + /** + * 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, $printlog, $loglevel); + + $this->user = $user; + $this->password = $password; + $this->resource = $resource; + if(!$server) $server = $host; + $this->basejid = $this->user . '@' . $this->host; + + $this->roster = new Roster(); + $this->track_presence = true; + + $this->stream_start = ''; + $this->stream_end = ''; + $this->default_ns = 'jabber:client'; + + $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler'); + $this->addXPathHandler('{jabber:client}message', 'message_handler'); + $this->addXPathHandler('{jabber:client}presence', 'presence_handler'); + $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler'); + } + + /** + * Turn encryption on/ff + * + * @param boolean $useEncryption + */ + public function useEncryption($useEncryption = true) { + $this->use_encryption = $useEncryption; + } + + /** + * Turn on auto-authorization of subscription requests. + * + * @param boolean $autoSubscribe + */ + public function autoSubscribe($autoSubscribe = true) { + $this->auto_subscribe = $autoSubscribe; + } + + /** + * Send XMPP Message + * + * @param string $to + * @param string $body + * @param string $type + * @param string $subject + */ + public function message($to, $body, $type = 'chat', $subject = null, $payload = null) { + if(is_null($type)) + { + $type = 'chat'; + } + + $to = htmlspecialchars($to); + $body = htmlspecialchars($body); + $subject = htmlspecialchars($subject); + + $out = "fulljid}\" to=\"$to\" type='$type'>"; + if($subject) $out .= "$subject"; + $out .= "$body"; + if($payload) $out .= $payload; + $out .= ""; + + $this->send($out); + } + + /** + * Set Presence + * + * @param string $status + * @param string $show + * @param string $to + */ + public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) { + if($type == 'available') $type = ''; + $to = htmlspecialchars($to); + $status = htmlspecialchars($status); + if($show == 'unavailable') $type = 'unavailable'; + + $out = "send($out); + } + /** + * Send Auth request + * + * @param string $jid + */ + public function subscribe($jid) { + $this->send(""); + #$this->send(""); + } + + /** + * Message handler + * + * @param string $xml + */ + public function message_handler($xml) { + if(isset($xml->attrs['type'])) { + $payload['type'] = $xml->attrs['type']; + } else { + $payload['type'] = 'chat'; + } + $payload['from'] = $xml->attrs['from']; + $payload['body'] = $xml->sub('body')->data; + $payload['xml'] = $xml; + $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG); + $this->event('message', $payload); + } + + /** + * Presence handler + * + * @param string $xml + */ + public function presence_handler($xml) { + $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available'; + $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type']; + $payload['from'] = $xml->attrs['from']; + $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : ''; + $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0; + $payload['xml'] = $xml; + if($this->track_presence) { + $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']); + } + $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG); + if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') { + if($this->auto_subscribe) { + $this->send(""); + $this->send(""); + } + $this->event('subscription_requested', $payload); + } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') { + $this->event('subscription_accepted', $payload); + } else { + $this->event('presence', $payload); + } + } + + /** + * Features handler + * + * @param string $xml + */ + protected function features_handler($xml) { + if($xml->hasSub('starttls') and $this->use_encryption) { + $this->send(""); + } elseif($xml->hasSub('bind') and $this->authed) { + $id = $this->getId(); + $this->addIdHandler($id, 'resource_bind_handler'); + $this->send("{$this->resource}"); + } else { + $this->log->log("Attempting Auth..."); + if ($this->password) { + $this->send("" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . ""); + } else { + $this->send(""); + } + } + } + + /** + * SASL success handler + * + * @param string $xml + */ + protected function sasl_success_handler($xml) { + $this->log->log("Auth success!"); + $this->authed = true; + $this->reset(); + } + + /** + * SASL feature handler + * + * @param string $xml + */ + protected function sasl_failure_handler($xml) { + $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); + $this->disconnect(); + + throw new XMPPHP_Exception('Auth failed!'); + } + + /** + * Resource bind handler + * + * @param string $xml + */ + protected function resource_bind_handler($xml) { + if($xml->attrs['type'] == 'result') { + $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data); + $this->fulljid = $xml->sub('bind')->sub('jid')->data; + $jidarray = explode('/',$this->fulljid); + $this->jid = $jidarray[0]; + } + $id = $this->getId(); + $this->addIdHandler($id, 'session_start_handler'); + $this->send(""); + } + + /** + * Retrieves the roster + * + */ + public function getRoster() { + $id = $this->getID(); + $this->send(""); + } + + /** + * Roster iq handler + * Gets all packets matching XPath "iq/{jabber:iq:roster}query' + * + * @param string $xml + */ + protected function roster_iq_handler($xml) { + $status = "result"; + $xmlroster = $xml->sub('query'); + foreach($xmlroster->subs as $item) { + $groups = array(); + if ($item->name == 'item') { + $jid = $item->attrs['jid']; //REQUIRED + $name = $item->attrs['name']; //MAY + $subscription = $item->attrs['subscription']; + foreach($item->subs as $subitem) { + if ($subitem->name == 'group') { + $groups[] = $subitem->data; + } + } + $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen + } else { + $status = "error"; + } + } + if ($status == "result") { //No errors, add contacts + foreach($contacts as $contact) { + $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]); + } + } + if ($xml->attrs['type'] == 'set') { + $this->send("attrs['id']}\" to=\"{$xml->attrs['from']}\" />"); + } + } + + /** + * Session start handler + * + * @param string $xml + */ + protected function session_start_handler($xml) { + $this->log->log("Session started"); + $this->session_started = true; + $this->event('session_start'); + } + + /** + * TLS proceed handler + * + * @param string $xml + */ + protected function tls_proceed_handler($xml) { + $this->log->log("Starting TLS encryption"); + stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); + $this->reset(); + } + + /** + * Retrieves the vcard + * + */ + public function getVCard($jid = Null) { + $id = $this->getID(); + $this->addIdHandler($id, 'vcard_get_handler'); + if($jid) { + $this->send(""); + } else { + $this->send(""); + } + } + + /** + * VCard retrieval handler + * + * @param XML Object $xml + */ + protected function vcard_get_handler($xml) { + $vcard_array = array(); + $vcard = $xml->sub('vcard'); + // go through all of the sub elements and add them to the vcard array + foreach ($vcard->subs as $sub) { + if ($sub->subs) { + $vcard_array[$sub->name] = array(); + foreach ($sub->subs as $sub_child) { + $vcard_array[$sub->name][$sub_child->name] = $sub_child->data; + } + } else { + $vcard_array[$sub->name] = $sub->data; + } + } + $vcard_array['from'] = $xml->attrs['from']; + $this->event('vcard', $vcard_array); + } +} diff --git a/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php b/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php new file mode 100644 index 000000000..43f56b154 --- /dev/null +++ b/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php @@ -0,0 +1,114 @@ + + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + */ + +/** XMPPHP_XMPP + * + * This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's. + * In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it. + * The old Jabber protocol wasn't standardized, so use at your own risk. + * + */ +require_once "XMPP.php"; + + class XMPPHP_XMPPOld extends XMPPHP_XMPP { + /** + * + * @var string + */ + protected $session_id; + + public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { + parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); + if(!$server) $server = $host; + $this->stream_start = ''; + $this->fulljid = "{$user}@{$server}/{$resource}"; + } + + /** + * Override XMLStream's startXML + * + * @param parser $parser + * @param string $name + * @param array $attr + */ + public function startXML($parser, $name, $attr) { + if($this->xml_depth == 0) { + $this->session_id = $attr['ID']; + $this->authenticate(); + } + parent::startXML($parser, $name, $attr); + } + + /** + * Send Authenticate Info Request + * + */ + public function authenticate() { + $id = $this->getId(); + $this->addidhandler($id, 'authfieldshandler'); + $this->send("{$this->user}"); + } + + /** + * Retrieve auth fields and send auth attempt + * + * @param XMLObj $xml + */ + public function authFieldsHandler($xml) { + $id = $this->getId(); + $this->addidhandler($id, 'oldAuthResultHandler'); + if($xml->sub('query')->hasSub('digest')) { + $hash = sha1($this->session_id . $this->password); + print "{$this->session_id} {$this->password}\n"; + $out = "{$this->user}{$hash}{$this->resource}"; + } else { + $out = "{$this->user}{$this->password}{$this->resource}"; + } + $this->send($out); + + } + + /** + * Determine authenticated or failure + * + * @param XMLObj $xml + */ + public function oldAuthResultHandler($xml) { + if($xml->attrs['type'] != 'result') { + $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); + $this->disconnect(); + throw new XMPPHP_Exception('Auth failed!'); + } else { + $this->log->log("Session started"); + $this->event('session_start'); + } + } + } + + +?> -- cgit v1.2.3-54-g00ecf From bcca10f5268ac2c2945479dd93d2f302478e02f9 Mon Sep 17 00:00:00 2001 From: Marcel van der Boom Date: Thu, 27 May 2010 19:25:45 +0200 Subject: Add implementation of API method home_timeline method --- plugins/TwitterBridge/twitteroauthclient.php | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'plugins') diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index d895d8c73..6b821ba18 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -217,6 +217,36 @@ class TwitterOAuthClient extends OAuthClient return $statuses; } + /** + * Calls Twitter's /statuses/home_timeline API method + * + * @param int $since_id show statuses after this id + * @param int $max_id show statuses before this id + * @param int $cnt number of statuses to show + * @param int $page page number + * + * @return mixed an array of statuses, similare to friends_timeline, except including retweets + */ + function statusesHomeTimeline($since_id = null, $max_id = null, + $cnt = null, $page = null) + { + + $url = 'https://twitter.com/statuses/home_timeline.json'; + $params = array('since_id' => $since_id, + 'max_id' => $max_id, + 'count' => $cnt, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->oAuthGet($url); + $statuses = json_decode($response); + return $statuses; + } + /** * Calls Twitter's /statuses/friends API method * -- cgit v1.2.3-54-g00ecf From 4211b7f01188b4ab64407e32b380366a048102f4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 27 May 2010 11:21:52 -0700 Subject: - Implement statusesHomeTimeline() in TwitterBasicAuthClient - Make TwitterStatusFetcher pull home_timeline (includes retweets) instead of friends_timeline --- .../TwitterBridge/daemons/twitterstatusfetcher.php | 2 +- plugins/TwitterBridge/twitterbasicauthclient.php | 31 +++++++++++++++++++++- plugins/TwitterBridge/twitteroauthclient.php | 2 +- 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 7c624fdb3..03a4bd3f3 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -186,7 +186,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon $timeline = null; try { - $timeline = $client->statusesFriendsTimeline(); + $timeline = $client->statusesHomeTimeline(); } catch (Exception $e) { common_log(LOG_WARNING, $this->name() . ' - Twitter client unable to get friends timeline for user ' . diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index 2c18c9469..cc68b5010 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Class for doing OAuth calls against Twitter + * Class for doing HTTP basic auth calls against Twitter * * PHP version 5 * @@ -125,6 +125,35 @@ class TwitterBasicAuthClient return $statuses; } + /** + * Calls Twitter's /statuses/home_timeline API method + * + * @param int $since_id show statuses after this id + * @param int $max_id show statuses before this id + * @param int $cnt number of statuses to show + * @param int $page page number + * + * @return mixed an array of statuses similar to friends timeline but including retweets + */ + function statusesFriendsTimeline($since_id = null, $max_id = null, + $cnt = null, $page = null) + { + $url = 'https://twitter.com/statuses/home_timeline.json'; + $params = array('since_id' => $since_id, + 'max_id' => $max_id, + 'count' => $cnt, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->httpRequest($url); + $statuses = json_decode($response); + return $statuses; + } + /** * Calls Twitter's /statuses/friends API method * diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index 6b821ba18..f6ef78675 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -225,7 +225,7 @@ class TwitterOAuthClient extends OAuthClient * @param int $cnt number of statuses to show * @param int $page page number * - * @return mixed an array of statuses, similare to friends_timeline, except including retweets + * @return mixed an array of statuses, similar to friends_timeline but including retweets */ function statusesHomeTimeline($since_id = null, $max_id = null, $cnt = null, $page = null) -- cgit v1.2.3-54-g00ecf From 7cc58b97feb822ab999b7fefa3a50ce53a7838d5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 10 Jun 2010 15:23:57 -0700 Subject: Fix for compile error (misnamed function) in 4211b7f01188b4ab64407e32b380366a048102f4 --- plugins/TwitterBridge/twitterbasicauthclient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index cc68b5010..23828ed4a 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -135,7 +135,7 @@ class TwitterBasicAuthClient * * @return mixed an array of statuses similar to friends timeline but including retweets */ - function statusesFriendsTimeline($since_id = null, $max_id = null, + function statusesHomeTimeline($since_id = null, $max_id = null, $cnt = null, $page = null) { $url = 'https://twitter.com/statuses/home_timeline.json'; -- cgit v1.2.3-54-g00ecf From d645b342ac4a678b9f7932ee38858e25cd611f35 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 12 Jul 2010 14:22:36 -0700 Subject: Commit hubprepqueuehandler.php -- fix for OStatus bulk output. --- plugins/OStatus/lib/hubprepqueuehandler.php | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 plugins/OStatus/lib/hubprepqueuehandler.php (limited to 'plugins') diff --git a/plugins/OStatus/lib/hubprepqueuehandler.php b/plugins/OStatus/lib/hubprepqueuehandler.php new file mode 100644 index 000000000..0d585938f --- /dev/null +++ b/plugins/OStatus/lib/hubprepqueuehandler.php @@ -0,0 +1,87 @@ +. + */ + +/** + * When we have a large batch of PuSH consumers, we break the data set + * into smaller chunks. Enqueue final destinations... + * + * @package Hub + * @author Brion Vibber + */ +class HubPrepQueueHandler extends QueueHandler +{ + // Enqueue this many low-level distributions before re-queueing the rest + // of the batch to be processed later. Helps to keep latency down for other + // things happening during a particularly long OStatus delivery session. + // + // [Could probably ditch this if we had working message delivery priorities + // for queueing, but this isn't supported in ActiveMQ 5.3.] + const ROLLING_BATCH = 20; + + function transport() + { + return 'hubprep'; + } + + function handle($data) + { + $topic = $data['topic']; + $atom = $data['atom']; + $pushCallbacks = $data['pushCallbacks']; + + assert(is_string($atom)); + assert(is_string($topic)); + assert(is_array($pushCallbacks)); + + // Set up distribution for the first n subscribing sites... + // If we encounter an uncatchable error, queue handling should + // automatically re-run the batch, which could lead to some dupe + // distributions. + // + // Worst case is if one of these hubprep entries dies too many + // times and gets dropped; the rest of the batch won't get processed. + try { + $n = 0; + while (count($pushCallbacks) && $n < self::ROLLING_BATCH) { + $n++; + $callback = array_shift($pushCallbacks); + $sub = HubSub::staticGet($topic, $callback); + if (!$sub) { + common_log(LOG_ERR, "Skipping PuSH delivery for deleted(?) consumer $callback on $topic"); + continue; + } + + $sub->distribute($atom); + } + } catch (Exception $e) { + common_log(LOG_ERR, "Exception during PuSH batch out: " . + $e->getMessage() . + " prepping $topic to $callback"); + } + + // And re-queue the rest of the batch! + if (count($pushCallbacks) > 0) { + $sub = new HubSub(); + $sub->topic = $topic; + $sub->bulkDistribute($atom, $pushCallbacks); + } + + return true; + } +} -- cgit v1.2.3-54-g00ecf From 659e8b26acffd3bfbe097693d3f75e20d2f78a0f Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 28 Jul 2010 17:50:36 -0400 Subject: add admin panel for Adsense --- plugins/Adsense/AdsensePlugin.php | 48 ++++++++ plugins/Adsense/adsenseadminpanel.php | 223 ++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 plugins/Adsense/adsenseadminpanel.php (limited to 'plugins') diff --git a/plugins/Adsense/AdsensePlugin.php b/plugins/Adsense/AdsensePlugin.php index ab2b9a6fb..cd6fc3503 100644 --- a/plugins/Adsense/AdsensePlugin.php +++ b/plugins/Adsense/AdsensePlugin.php @@ -83,6 +83,21 @@ class AdsensePlugin extends UAPPlugin public $adScript = 'http://pagead2.googlesyndication.com/pagead/show_ads.js'; public $client = null; + function initialize() + { + parent::initialize(); + + // A little bit of chicanery so we avoid overwriting values that + // are passed in with the constructor + + foreach (array('mediumRectangle', 'rectangle', 'leaderboard', 'wideSkyscraper', 'adScript', 'client') as $setting) { + $value = common_config('adsense', strtolower($setting)); + if (!empty($value)) { // not found + $this->$setting = $value; + } + } + } + /** * Show a medium rectangle 'ad' * @@ -157,4 +172,37 @@ class AdsensePlugin extends UAPPlugin $action->script($this->adScript); } + + function onRouterInitialized($m) + { + $m->connect('admin/adsense', + array('action' => 'adsenseadminpanel')); + + return true; + } + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'AdsenseadminpanelAction': + require_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + default: + return true; + } + } + + function onEndAdminPanelNav($menu) { + if (AdminPanelAction::canAdmin('adsense')) { + // TRANS: Menu item title/tooltip + $menu_title = _('Adsense configuration'); + // TRANS: Menu item for site administration + $menu->out->menuItem(common_local_url('adsenseadminpanel'), _('Adsense'), + $menu_title, $action_name == 'adsenseadminpanel', 'nav_adsense_admin_panel'); + } + return true; + } } \ No newline at end of file diff --git a/plugins/Adsense/adsenseadminpanel.php b/plugins/Adsense/adsenseadminpanel.php new file mode 100644 index 000000000..7b99cf805 --- /dev/null +++ b/plugins/Adsense/adsenseadminpanel.php @@ -0,0 +1,223 @@ +. + * + * @category Adsense + * @package StatusNet + * @author Evan Prodromou + * @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); +} + +/** + * Administer adsense settings + * + * @category Adsense + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class AdsenseadminpanelAction extends AdminPanelAction +{ + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + return _('Adsense'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + return _('Adsense settings for this StatusNet site'); + } + + /** + * Show the site admin panel form + * + * @return void + */ + + function showForm() + { + $form = new AdsenseAdminPanelForm($this); + $form->show(); + return; + } + + /** + * Save settings from the form + * + * @return void + */ + + function saveSettings() + { + static $settings = array('adsense' => array('adScript', 'client', 'mediumRectangle', 'rectangle', 'leaderboard', 'wideSkyscraper')); + + $values = array(); + + foreach ($settings as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = $this->trimmed($setting); + } + } + + // This throws an exception on validation errors + + $this->validate($values); + + // assert(all values are valid); + + $config = new Config(); + + $config->query('BEGIN'); + + foreach ($settings as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); + + return; + } + + function validate(&$values) + { + } +} + +/** + * Form for the adsense admin panel + */ + +class AdsenseAdminPanelForm extends AdminForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'form_adsense_admin_panel'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_adsense'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('adsenseadminpanel'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'adsense_admin')); + $this->out->elementStart('ul', 'form_data'); + $this->li(); + $this->input('client', + _('Client ID'), + _('Google client ID'), + 'adsense'); + $this->unli(); + $this->li(); + $this->input('adScript', + _('Ad Script URL'), + _('Script URL (advanced)'), + 'adsense'); + $this->unli(); + $this->li(); + $this->input('mediumRectangle', + _('Medium rectangle'), + _('Medium rectangle slot code'), + 'adsense'); + $this->unli(); + $this->li(); + $this->input('rectangle', + _('Rectangle'), + _('Rectangle slot code'), + 'adsense'); + $this->unli(); + $this->li(); + $this->input('leaderboard', + _('Leaderboard'), + _('Leaderboard slot code'), + 'adsense'); + $this->unli(); + $this->li(); + $this->input('wideSkyscraper', + _('Skyscraper'), + _('Wide skyscraper slot code'), + 'adsense'); + $this->unli(); + $this->out->elementEnd('ul'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save'), 'submit', null, _('Save AdSense settings')); + } +} -- cgit v1.2.3-54-g00ecf From 84726791d33f63ba229aeba8b3c6035244ad2899 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 30 Jul 2010 14:12:37 -0700 Subject: Fix for ticket #2286: [mobile] Text extends beyond border of repeat confirmation dialog floater box on iPhone http://status.net/open-source/issues/2286 This bit of CSS was constricting the vertical size of the popup form for repeats: .notice-options form { width:16px; height:16px; } I can only assume this was originally meant to constrain the mini inline AJAX forms to the size of the clickable buttons, but it doesn't make a difference to how those are displayed on iPhone, Android, or Opera Mini. Removing the statement lets the popup form go to its natural size, covering the button. --- plugins/MobileProfile/mp-screen.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 0fc801612..1f70b5612 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -2,7 +2,7 @@ * * @package StatusNet * @author Sarven Capadisli - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-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/ */ @@ -195,10 +195,6 @@ width:43px; margin-right:1%; } -.notice-options form { -width:16px; -height:16px; -} .notice-options form.processing { background-image:none; } -- cgit v1.2.3-54-g00ecf From 517c7483d1b55fcc78b1d69e8ffd7de763faa772 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 2 Aug 2010 13:23:55 -0400 Subject: move to rel="salmon" (per latest spec) --- plugins/OStatus/OStatusPlugin.php | 3 +++ plugins/OStatus/lib/salmon.php | 4 +++- plugins/OStatus/lib/xrdaction.php | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index c61e2cc5f..c735c02db 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -158,6 +158,9 @@ class OStatusPlugin extends Plugin // Also, we'll add in the salmon link $salmon = common_local_url($salmonAction, array('id' => $id)); + $feed->addLink($salmon, array('rel' => Salmon::REL_SALMON)); + + // XXX: these are deprecated $feed->addLink($salmon, array('rel' => Salmon::NS_REPLIES)); $feed->addLink($salmon, array('rel' => Salmon::NS_MENTIONS)); } diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 3d3341bc6..ef7719a40 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -28,9 +28,11 @@ */ class Salmon { + const REL_SALMON = 'salmon'; + const REL_MENTIONED = 'mentioned'; + // XXX: these are deprecated const NS_REPLIES = "http://salmon-protocol.org/ns/salmon-replies"; - const NS_MENTIONS = "http://salmon-protocol.org/ns/salmon-mention"; /** diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php index f1a56e0a8..71c70b96e 100644 --- a/plugins/OStatus/lib/xrdaction.php +++ b/plugins/OStatus/lib/xrdaction.php @@ -76,6 +76,9 @@ class XrdAction extends Action $salmon_url = common_local_url('usersalmon', array('id' => $this->user->id)); + $xrd->links[] = array('rel' => Salmon::REL_SALMON, + 'href' => $salmon_url); + // XXX : Deprecated - to be removed. $xrd->links[] = array('rel' => Salmon::NS_REPLIES, 'href' => $salmon_url); -- cgit v1.2.3-54-g00ecf From 56294016a753c43c366bf4680da28a17cccc21d5 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 2 Aug 2010 14:47:13 -0400 Subject: fix #2478 - ensure all XRD documents get proper content-type headers --- plugins/OStatus/actions/hostmeta.php | 3 +-- plugins/OStatus/lib/xrdaction.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/OStatus/actions/hostmeta.php b/plugins/OStatus/actions/hostmeta.php index 6d35ada6c..8ca07f916 100644 --- a/plugins/OStatus/actions/hostmeta.php +++ b/plugins/OStatus/actions/hostmeta.php @@ -35,14 +35,13 @@ class HostMetaAction extends Action $url = common_local_url('userxrd'); $url.= '?uri={uri}'; - $xrd = new XRD(); - $xrd = new XRD(); $xrd->host = $domain; $xrd->links[] = array('rel' => Discovery::LRDD_REL, 'template' => $url, 'title' => array('Resource Descriptor')); + header('Content-type: application/xrd+xml'); print $xrd->toXML(); } } diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php index 71c70b96e..d8cf648d6 100644 --- a/plugins/OStatus/lib/xrdaction.php +++ b/plugins/OStatus/lib/xrdaction.php @@ -101,7 +101,7 @@ class XrdAction extends Action $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => $url ); - header('Content-type: text/xml'); + header('Content-type: application/xrd+xml'); print $xrd->toXML(); } -- cgit v1.2.3-54-g00ecf From c56939d59632560e93d1e4f3b29713c3cfdb61c6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 2 Aug 2010 13:00:50 -0700 Subject: Remove the 'Enable Twitter import' checkbox from Twitter admin panel by default; can be re-added with setting: addPlugin('TwitterBridge', array('adminImportControl' => true, ....)); Added a note on the label that it requires manual daemon setup. (Note that by default the admin panel won't be shown, so it's no biggie to be hiding this for now.) --- plugins/TwitterBridge/TwitterBridgePlugin.php | 13 ++++++++++++ plugins/TwitterBridge/twitteradminpanel.php | 29 ++++++++++++++++++--------- 2 files changed, 33 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 65b3a6b38..0505a328f 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -50,6 +50,7 @@ class TwitterBridgePlugin extends Plugin { const VERSION = STATUSNET_VERSION; + public $adminImportControl = false; // Should the 'import' checkbox be exposed in the admin panel? /** * Initializer for the plugin. @@ -322,5 +323,17 @@ class TwitterBridgePlugin extends Plugin return true; } + /** + * Expose the adminImportControl setting to the administration panel code. + * This allows us to disable the import bridge enabling checkbox for administrators, + * since on a bulk farm site we can't yet automate the import daemon setup. + * + * @return boolean hook value; + */ + function onTwitterBridgeAdminImportControl() + { + return (bool)$this->adminImportControl; + } + } diff --git a/plugins/TwitterBridge/twitteradminpanel.php b/plugins/TwitterBridge/twitteradminpanel.php index a78a92c66..69f8da078 100644 --- a/plugins/TwitterBridge/twitteradminpanel.php +++ b/plugins/TwitterBridge/twitteradminpanel.php @@ -92,9 +92,11 @@ class TwitteradminpanelAction extends AdminPanelAction ); static $booleans = array( - 'twitter' => array('signin'), - 'twitterimport' => array('enabled') + 'twitter' => array('signin') ); + if (Event::handle('TwitterBridgeAdminImportControl')) { + $booleans['twitterimport'] = array('enabled'); + } $values = array(); @@ -155,6 +157,13 @@ class TwitteradminpanelAction extends AdminPanelAction ); } } + + function isImportEnabled() + { + // Since daemon setup isn't automated yet... + // @todo: if merged into main queues, detect presence of daemon config + return true; + } } class TwitterAdminPanelForm extends AdminForm @@ -263,13 +272,15 @@ class TwitterAdminPanelForm extends AdminForm ); $this->unli(); - $this->li(); - $this->out->checkbox( - 'enabled', _m('Enable Twitter import'), - (bool) $this->value('enabled', 'twitterimport'), - _m('Allow users to import their Twitter friends\' timelines') - ); - $this->unli(); + if (Event::handle('TwitterBridgeAdminImportControl')) { + $this->li(); + $this->out->checkbox( + 'enabled', _m('Enable Twitter import'), + (bool) $this->value('enabled', 'twitterimport'), + _m('Allow users to import their Twitter friends\' timelines. Requires daemons to be manually configured.') + ); + $this->unli(); + } $this->out->elementEnd('ul'); -- cgit v1.2.3-54-g00ecf From 8120842780319089f47144acf82685163237b8bc Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 2 Aug 2010 16:42:28 -0400 Subject: Fix for #2429 - move OStatus XML writing to XMLStringer --- plugins/OStatus/lib/magicenvelope.php | 28 ++++------- plugins/OStatus/lib/xrd.php | 94 ++++++++++++----------------------- 2 files changed, 44 insertions(+), 78 deletions(-) (limited to 'plugins') diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index f39686b71..3bdf24b31 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -97,24 +97,18 @@ class MagicEnvelope } public function toXML($env) { - $dom = new DOMDocument(); - - $envelope = $dom->createElementNS(MagicEnvelope::NS, 'me:env'); - $envelope->setAttribute('xmlns:me', MagicEnvelope::NS); - $data = $dom->createElementNS(MagicEnvelope::NS, 'me:data', $env['data']); - $data->setAttribute('type', $env['data_type']); - $envelope->appendChild($data); - $enc = $dom->createElementNS(MagicEnvelope::NS, 'me:encoding', $env['encoding']); - $envelope->appendChild($enc); - $alg = $dom->createElementNS(MagicEnvelope::NS, 'me:alg', $env['alg']); - $envelope->appendChild($alg); - $sig = $dom->createElementNS(MagicEnvelope::NS, 'me:sig', $env['sig']); - $envelope->appendChild($sig); - - $dom->appendChild($envelope); + $xs = new XMLStringer(); + $xs->startXML(); + $xs->elementStart('me:env', array('xmlns:me' => MagicEnvelope::NS)); + $xs->element('me:data', array('type' => $env['data_type']), $env['data']); + $xs->element('me:encoding', null, $env['encoding']); + $xs->element('me:alg', null, $env['alg']); + $xs->element('me:sig', null, $env['sig']); + $xs->elementEnd('me:env'); - - return $dom->saveXML(); + $string = $xs->getString(); + common_debug($string); + return $string; } diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php index 34b28790b..a10b9f427 100644 --- a/plugins/OStatus/lib/xrd.php +++ b/plugins/OStatus/lib/xrd.php @@ -106,44 +106,43 @@ class XRD public function toXML() { - $dom = new DOMDocument('1.0', 'UTF-8'); - $dom->formatOutput = true; - - $xrd_dom = $dom->createElementNS(XRD::XRD_NS, 'XRD'); - $dom->appendChild($xrd_dom); + $xs = new XMLStringer(); + + $xs->startXML(); + $xs->elementStart('XRD', array('xmlns' => XRD::XRD_NS)); if ($this->host) { - $host_dom = $dom->createElement('hm:Host', $this->host); - $xrd_dom->setAttributeNS(XRD::XML_NS, 'xmlns:hm', XRD::HOST_META_NS); - $xrd_dom->appendChild($host_dom); + $xs->element('hm:Host', array('xmlns:hm' => XRD::HOST_META_NS), $this->host); } - if ($this->expires) { - $expires_dom = $dom->createElement('Expires', $this->expires); - $xrd_dom->appendChild($expires_dom); - } - - if ($this->subject) { - $subject_dom = $dom->createElement('Subject', $this->subject); - $xrd_dom->appendChild($subject_dom); - } - - foreach ($this->alias as $alias) { - $alias_dom = $dom->createElement('Alias', $alias); - $xrd_dom->appendChild($alias_dom); - } - - foreach ($this->types as $type) { - $type_dom = $dom->createElement('Type', $type); - $xrd_dom->appendChild($type_dom); - } - - foreach ($this->links as $link) { - $link_dom = $this->saveLink($dom, $link); - $xrd_dom->appendChild($link_dom); - } - - return $dom->saveXML(); + 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) @@ -169,32 +168,5 @@ class XRD return $link; } - - function saveLink($doc, $link) - { - $link_element = $doc->createElement('Link'); - if (!empty($link['rel'])) { - $link_element->setAttribute('rel', $link['rel']); - } - if (!empty($link['type'])) { - $link_element->setAttribute('type', $link['type']); - } - if (!empty($link['href'])) { - $link_element->setAttribute('href', $link['href']); - } - if (!empty($link['template'])) { - $link_element->setAttribute('template', $link['template']); - } - - if (!empty($link['title']) && is_array($link['title'])) { - foreach($link['title'] as $title) { - $title = $doc->createElement('Title', $title); - $link_element->appendChild($title); - } - } - - - return $link_element; - } } -- cgit v1.2.3-54-g00ecf From f83171824f835ff9cd24bf0aea26f13c62b806cf Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 3 Aug 2010 15:50:21 -0700 Subject: correctly show for atom feeds --- classes/Notice.php | 48 ++++++++++++++++++++++++++------------- classes/Profile.php | 29 +++++++++++++++++------ plugins/OStatus/OStatusPlugin.php | 12 ++++++++++ 3 files changed, 66 insertions(+), 23 deletions(-) (limited to 'plugins') diff --git a/classes/Notice.php b/classes/Notice.php index f6e9eb585..61844d487 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1215,29 +1215,45 @@ class Notice extends Memcached_DataObject if ($source) { - $xs->elementStart('source'); + $atom_feed = $profile->getAtomFeed(); - $xs->element('id', null, $profile->profileurl); - $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name')); - $xs->element('link', array('href' => $profile->profileurl)); + if (!empty($atom_feed)) { - $user = User::staticGet('id', $profile->id); + $xs->elementStart('source'); + + // XXX: we should store the actual feed ID + + $xs->element('id', null, $atom_feed); + + // XXX: we should store the actual feed title + + $xs->element('title', null, $profile->getBestName()); + + $xs->element('link', array('rel' => 'alternate', + 'type' => 'text/html', + 'href' => $profile->profileurl)); - if (!empty($user)) { - $atom_feed = common_local_url('ApiTimelineUser', - array('format' => 'atom', - 'id' => $profile->nickname)); $xs->element('link', array('rel' => 'self', 'type' => 'application/atom+xml', - 'href' => $profile->profileurl)); - $xs->element('link', array('rel' => 'license', - 'href' => common_config('license', 'url'))); - } + 'href' => $atom_feed)); - $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE)); - $xs->element('updated', null, common_date_w3dtf($this->created)); // FIXME: not true! + $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE)); - $xs->elementEnd('source'); + $notice = $profile->getCurrentNotice(); + + if (!empty($notice)) { + $xs->element('updated', null, common_date_w3dtf($notice->created)); + } + + $user = User::staticGet('id', $profile->id); + + if (!empty($user)) { + $xs->element('link', array('rel' => 'license', + 'href' => common_config('license', 'url'))); + } + + $xs->elementEnd('source'); + } } Event::handle('EndActivitySource', array(&$this, &$xs)); } diff --git a/classes/Profile.php b/classes/Profile.php index a303469e9..abd6eb031 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -152,17 +152,16 @@ class Profile extends Memcached_DataObject * * @return mixed Notice or null */ + function getCurrentNotice() { - $notice = new Notice(); - $notice->profile_id = $this->id; - // @fixme change this to sort on notice.id only when indexes are updated - $notice->orderBy('created DESC, notice.id DESC'); - $notice->limit(1); - if ($notice->find(true)) { + $notice = $this->getNotices(0, 1); + + if ($notice->fetch()) { return $notice; + } else { + return null; } - return null; } function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) @@ -943,4 +942,20 @@ class Profile extends Memcached_DataObject return $result; } + + function getAtomFeed() + { + $feed = null; + + if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) { + $user = User::staticGet('id', $this->id); + if (!empty($user)) { + $feed = common_local_url('ApiTimelineUser', array('id' => $user->id, + 'format' => 'atom')); + } + Event::handle('EndProfileGetAtomFeed', array($this, $feed)); + } + + return $feed; + } } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index c61e2cc5f..4fc9d4108 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -953,4 +953,16 @@ class OStatusPlugin extends Plugin } return false; } + + public function onStartProfileGetAtomFeed($profile, &$feed) + { + $oprofile = Ostatus_profile::staticGet('profile_id', $profile->id); + + if (empty($oprofile)) { + return true; + } + + $feed = $oprofile->feeduri; + return false; + } } -- cgit v1.2.3-54-g00ecf From 422a6ef5185a522ebff03733dd7bfe73c2b68fb5 Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Fri, 6 Aug 2010 22:48:00 -0500 Subject: Fixed PHP 5.3 by & value Cleaned up {}'s --- plugins/Gravatar/GravatarPlugin.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/Gravatar/GravatarPlugin.php b/plugins/Gravatar/GravatarPlugin.php index 580852072..8a9721ea9 100644 --- a/plugins/Gravatar/GravatarPlugin.php +++ b/plugins/Gravatar/GravatarPlugin.php @@ -30,11 +30,13 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class GravatarPlugin extends Plugin { - function onInitializePlugin() { + function onInitializePlugin() + { return true; } - function onStartAvatarFormData($action) { + function onStartAvatarFormData($action) + { $user = common_current_user(); $hasGravatar = $this->hasGravatar($user->id); @@ -43,7 +45,8 @@ class GravatarPlugin extends Plugin } } - function onEndAvatarFormData(&$action) { + function onEndAvatarFormData($action) + { $user = common_current_user(); $hasGravatar = $this->hasGravatar($user->id); @@ -89,7 +92,8 @@ class GravatarPlugin extends Plugin } } - function onStartAvatarSaveForm($action) { + function onStartAvatarSaveForm($action) + { if ($action->arg('add')) { $result = $this->gravatar_save(); @@ -178,7 +182,8 @@ class GravatarPlugin extends Plugin 'success' => true); } - function gravatar_url($email, $size) { + function gravatar_url($email, $size) + { $url = "http://www.gravatar.com/avatar.php?gravatar_id=". md5(strtolower($email)). "&default=".urlencode(Avatar::defaultImage($size)). @@ -197,4 +202,4 @@ class GravatarPlugin extends Plugin return true; } -} +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From edb62db613478d50def223ec1d60ef5863fa3a4b Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Fri, 6 Aug 2010 23:07:34 -0500 Subject: Locale error message, clean up {}, Verified under 1.0.x && php 5.3 --- plugins/Recaptcha/RecaptchaPlugin.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/Recaptcha/RecaptchaPlugin.php b/plugins/Recaptcha/RecaptchaPlugin.php index 7cc34c568..b7a0e92c7 100644 --- a/plugins/Recaptcha/RecaptchaPlugin.php +++ b/plugins/Recaptcha/RecaptchaPlugin.php @@ -41,7 +41,8 @@ class RecaptchaPlugin extends Plugin var $failed; var $ssl; - function onInitializePlugin(){ + function onInitializePlugin() + { if(!isset($this->private_key)) { common_log(LOG_ERR, 'Recaptcha: Must specify private_key in config.php'); } @@ -50,7 +51,8 @@ class RecaptchaPlugin extends Plugin } } - function checkssl(){ + function checkssl() + { if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') { return true; } @@ -102,7 +104,7 @@ class RecaptchaPlugin extends Plugin if($this->display_errors) { $action->showForm ("(reCAPTCHA error: " . $resp->error . ")"); } - $action->showForm("Captcha does not match!"); + $action->showForm(_m("Captcha does not match!")); return false; } } @@ -118,4 +120,4 @@ class RecaptchaPlugin extends Plugin 'captcha to the registration page.')); return true; } -} +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf