summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/AccountManager/AccountManagementControlDocumentAction.php98
-rw-r--r--plugins/AccountManager/AccountManagementSessionStatusAction.php73
-rw-r--r--plugins/AccountManager/AccountManagerPlugin.php115
-rw-r--r--plugins/AccountManager/README16
-rw-r--r--plugins/Aim/AimPlugin.php169
-rw-r--r--plugins/Aim/Fake_Aim.php43
-rw-r--r--plugins/Aim/README27
-rw-r--r--plugins/Aim/aimmanager.php100
-rwxr-xr-xplugins/Aim/extlib/phptoclib/README.txt169
-rwxr-xr-xplugins/Aim/extlib/phptoclib/aimclassw.php2370
-rwxr-xr-xplugins/Aim/extlib/phptoclib/dconnection.php229
-rw-r--r--plugins/BitlyUrl/BitlyUrlPlugin.php2
-rw-r--r--plugins/CasAuthentication/caslogin.php2
-rw-r--r--plugins/CasAuthentication/extlib/CAS.php1397
-rw-r--r--plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php28
-rw-r--r--plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php27
-rw-r--r--plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php29
-rw-r--r--plugins/CasAuthentication/extlib/CAS/client.php861
-rw-r--r--plugins/ClientSideShorten/ClientSideShortenPlugin.php4
-rw-r--r--plugins/ClientSideShorten/shorten.js15
-rw-r--r--plugins/Geonames/GeonamesPlugin.php8
-rw-r--r--plugins/Imap/imapmanager.php8
-rw-r--r--plugins/Irc/ChannelResponseChannel.php61
-rw-r--r--plugins/Irc/Fake_Irc.php47
-rw-r--r--plugins/Irc/IrcPlugin.php396
-rw-r--r--plugins/Irc/Irc_waiting_message.php142
-rw-r--r--plugins/Irc/README45
-rw-r--r--plugins/Irc/extlib/.gitignore2
-rw-r--r--plugins/Irc/extlib/phergie/.gitignore2
-rw-r--r--plugins/Irc/extlib/phergie/LICENSE27
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Autoload.php81
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Bot.php390
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Config.php186
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Config/Exception.php44
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Connection.php401
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php50
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php130
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php301
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Driver/Exception.php59
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Driver/Statusnet.php66
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Driver/Streams.php729
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php62
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Event/Command.php62
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Event/Exception.php38
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Event/Handler.php190
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Event/Request.php468
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Event/Response.php953
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Exception.php33
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Hostmask.php217
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Hostmask/Exception.php37
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php605
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php186
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/AltNick.php95
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/AudioScrobbler.php191
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/AutoJoin.php69
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Beer.php82
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php81
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/BeerScore.php156
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cache.php106
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php69
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php51
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Censor.php120
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail.php71
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail/db.php74
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php146
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie.php69
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php55
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Cron.php147
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Ctcp.php91
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php56
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Encoding.php182
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Exception.php120
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php459
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php501
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php276
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php284
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Http/Response.php281
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php180
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Invisible.php44
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php142
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Join.php56
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php451
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Lart.php303
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php112
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/NickServ.php178
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Part.php55
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Php.php78
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source.php46
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source/Local.php208
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php162
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Pong.php44
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Prioritize.php99
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Puppet.php89
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php54
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php122
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Remind.php378
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php160
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/SpellCheck.php118
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Statusnet.php143
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php69
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php49
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Temperature.php81
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/TerryChay.php94
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php144
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Time.php72
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php148
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php68
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter.php206
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/laconica.class.php41
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/twitter.class.php287
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Url.php638
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Abstract.php105
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Trim.php64
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/UserInfo.php413
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Weather.php149
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Wine.php69
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Wine/db.php53
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php168
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Process/Abstract.php130
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Process/Async.php157
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Process/Exception.php33
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Process/Standard.php57
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/StatusnetBot.php98
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/INSTALL24
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/index.php368
-rwxr-xr-xplugins/Irc/extlib/phergie/Phergie/Tools/README6
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Ui/Abstract.php116
-rw-r--r--plugins/Irc/extlib/phergie/Phergie/Ui/Console.php223
-rw-r--r--plugins/Irc/extlib/phergie/PhergiePackageTask.php110
-rw-r--r--plugins/Irc/extlib/phergie/README11
-rwxr-xr-xplugins/Irc/extlib/phergie/Settings.php.dist98
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/ConnectionTest.php262
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php837
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php151
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/KarmaTest.php335
-rwxr-xr-xplugins/Irc/extlib/phergie/Tests/Phergie/Plugin/Mock.php61
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PingTest.php164
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PongTest.php46
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/SpellCheckTest.php166
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TerryChayTest.php135
-rw-r--r--plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestCase.php435
-rwxr-xr-xplugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestNonInstantiablePluginFromFile.php43
-rw-r--r--plugins/Irc/extlib/phergie/Tests/TestHelper.php26
-rw-r--r--plugins/Irc/extlib/phergie/Tests/phpunit.xml26
-rw-r--r--plugins/Irc/extlib/phergie/build.xml301
-rw-r--r--plugins/Irc/extlib/phergie/phergie.bat14
-rwxr-xr-xplugins/Irc/extlib/phergie/phergie.php54
-rw-r--r--plugins/Irc/ircmanager.php357
-rw-r--r--plugins/LdapCommon/LdapCommon.php4
-rw-r--r--plugins/LilUrl/LilUrlPlugin.php2
-rw-r--r--plugins/Minify/MinifyPlugin.php8
-rw-r--r--plugins/Minify/minify.php4
-rw-r--r--plugins/Msn/MsnPlugin.php216
-rw-r--r--plugins/Msn/README32
-rw-r--r--plugins/Msn/extlib/phpmsnclass/msn.class.php3210
-rwxr-xr-xplugins/Msn/extlib/phpmsnclass/msnbot.php63
-rw-r--r--plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd832
-rw-r--r--plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd567
-rw-r--r--plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl532
-rw-r--r--plugins/Msn/msn_waiting_message.php132
-rw-r--r--plugins/Msn/msnmanager.php275
-rw-r--r--plugins/OStatus/OStatusPlugin.php10
-rw-r--r--plugins/OStatus/lib/xrd.php172
-rw-r--r--plugins/OpenID/OpenIDPlugin.php22
-rw-r--r--plugins/OpenID/openidlogin.php9
-rw-r--r--plugins/PtitUrl/PtitUrlPlugin.php1
-rw-r--r--plugins/Recaptcha/RecaptchaPlugin.php8
-rw-r--r--plugins/SimpleUrl/SimpleUrlPlugin.php2
-rw-r--r--plugins/TightUrl/TightUrlPlugin.php2
-rw-r--r--plugins/UrlShortener/UrlShortenerPlugin.php96
-rw-r--r--plugins/Xmpp/Queued_XMPP.php121
-rw-r--r--plugins/Xmpp/README35
-rw-r--r--plugins/Xmpp/Sharing_XMPP.php (renamed from plugins/OStatus/actions/hostmeta.php)41
-rw-r--r--plugins/Xmpp/XmppPlugin.php433
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/BOSH.php188
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/Exception.php41
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/Log.php119
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/Roster.php163
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/XMLObj.php158
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/XMLStream.php763
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/XMPP.php432
-rw-r--r--plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php114
-rw-r--r--plugins/Xmpp/xmppmanager.php279
183 files changed, 34271 insertions, 1431 deletions
diff --git a/plugins/AccountManager/AccountManagementControlDocumentAction.php b/plugins/AccountManager/AccountManagementControlDocumentAction.php
new file mode 100644
index 000000000..3fcea5af4
--- /dev/null
+++ b/plugins/AccountManager/AccountManagementControlDocumentAction.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Implements the JSON Account Management endpoint
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category AccountManager
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @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);
+}
+
+/**
+ * Implements the JSON Account Management endpoint
+ *
+ * @category AccountManager
+ * @package StatusNet
+ * @author ECraig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class AccountManagementControlDocumentAction extends Action
+{
+ /**
+ * handle the action
+ *
+ * @param array $args unused.
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ header('Content-Type: application/json; charset=utf-8');
+
+ $amcd = array();
+
+ if(Event::handle('StartAccountManagementControlDocument', array(&$amcd))) {
+
+ $amcd['version'] = 1;
+ $amcd['sessionstatus'] = array(
+ 'method' => 'GET',
+ 'path' => common_local_url('AccountManagementSessionStatus')
+ );
+ $amcd['auth-methods'] = array(
+ 'username-password-form' => array(
+ 'connect' => array(
+ 'method' => 'POST',
+ 'path' => common_local_url('login'),
+ 'params' => array(
+ 'username' => 'nickname',
+ 'password' => 'password'
+ )
+ ),
+ 'disconnect' => array(
+ 'method' => 'GET',
+ 'path' => common_local_url('logout')
+ )
+ )
+ );
+
+ Event::handle('EndAccountManagementControlDocument', array(&$amcd));
+ }
+
+ print json_encode($amcd);
+
+ return true;
+ }
+
+ function isReadOnly()
+ {
+ return true;
+ }
+}
diff --git a/plugins/AccountManager/AccountManagementSessionStatusAction.php b/plugins/AccountManager/AccountManagementSessionStatusAction.php
new file mode 100644
index 000000000..48b6034ff
--- /dev/null
+++ b/plugins/AccountManager/AccountManagementSessionStatusAction.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Implements the session status Account Management endpoint
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category AccountManager
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @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);
+}
+
+/**
+ * Implements the session status Account Management endpoint
+ *
+ * @category AccountManager
+ * @package StatusNet
+ * @author ECraig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class AccountManagementSessionStatusAction extends Action
+{
+ /**
+ * handle the action
+ *
+ * @param array $args unused.
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ $cur = common_current_user();
+ if(empty($cur)) {
+ print 'none';
+ } else {
+ //TODO it seems " should be escaped in the name and id, but the spec doesn't seem to indicate how to do that
+ print 'active; name="' . $cur->nickname . '"; id="' . $cur->nickname . '"';
+ }
+
+ return true;
+ }
+
+ function isReadOnly()
+ {
+ return true;
+ }
+}
diff --git a/plugins/AccountManager/AccountManagerPlugin.php b/plugins/AccountManager/AccountManagerPlugin.php
new file mode 100644
index 000000000..52dd64a24
--- /dev/null
+++ b/plugins/AccountManager/AccountManagerPlugin.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to implement the Account Manager specification
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @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 AccountManagerPlugin extends Plugin
+{
+
+ const AM_REL = 'acct-mgmt';
+
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'AccountManagementControlDocumentAction':
+ require_once(INSTALLDIR.'/plugins/AccountManager/AccountManagementControlDocumentAction.php');
+ return false;
+ case 'AccountManagementSessionStatusAction':
+ require_once(INSTALLDIR.'/plugins/AccountManager/AccountManagementSessionStatusAction.php');
+ return false;
+ }
+ }
+
+ /**
+ * Hook for RouterInitialized event.
+ *
+ * @param Net_URL_Mapper $m path-to-action mapper
+ * @return boolean hook return
+ */
+ function onRouterInitialized($m)
+ {
+ // Discovery actions
+ $m->connect('main/amcd.json',
+ array('action' => 'AccountManagementControlDocument'));
+ $m->connect('main/amsessionstatus',
+ array('action' => 'AccountManagementSessionStatus'));
+ return true;
+ }
+
+ function onStartHostMetaLinks(&$links) {
+ $links[] = array('rel' => AccountManagerPlugin::AM_REL,
+ 'href' => common_local_url('AccountManagementControlDocument'));
+ }
+
+ function onStartShowHTML($action)
+ {
+ //Account management discovery link
+ header('Link: <'.common_local_url('AccountManagementControlDocument').'>; rel="'. AccountManagerPlugin::AM_REL.'"; type="application/json"');
+
+ //Account management login status
+ $cur = common_current_user();
+ if(empty($cur)) {
+ header('X-Account-Management-Status: none');
+ } else {
+ //TODO it seems " should be escaped in the name and id, but the spec doesn't seem to indicate how to do that
+ header('X-Account-Management-Status: active; name="' . $cur->nickname . '"; id="' . $cur->nickname . '"');
+ }
+ }
+
+ function onLoginAction($action, &$login) {
+ switch ($action)
+ {
+ case 'AccountManagementControlDocument':
+ $login = true;
+ return false;
+ default:
+ return true;
+ }
+
+ }
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'AccountManager',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:AccountManager',
+ 'rawdescription' =>
+ _m('The Account Manager plugin implements the Account Manager specification.'));
+ return true;
+ }
+}
diff --git a/plugins/AccountManager/README b/plugins/AccountManager/README
new file mode 100644
index 000000000..a0715927b
--- /dev/null
+++ b/plugins/AccountManager/README
@@ -0,0 +1,16 @@
+The Account Manager plugin implements the Account Manager specification to "allow a server to describe an account-based user identification process in terms that a user-agent can understand."
+
+See https://wiki.mozilla.org/Labs/Weave/Identity/Account_Manager/Spec/Latest
+
+Installation
+============
+add "addPlugin('accountManager');"
+to the bottom of your config.php
+
+Settings
+========
+none
+
+Example
+=======
+addPlugin('accountManager');
diff --git a/plugins/Aim/AimPlugin.php b/plugins/Aim/AimPlugin.php
new file mode 100644
index 000000000..3a1799a2d
--- /dev/null
+++ b/plugins/Aim/AimPlugin.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Send and receive notices using the AIM network
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category IM
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @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 <candrews@integralblue.com>
+ * @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 daemonScreenname()
+ {
+ 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 sendMessage($screenname, $body)
+ {
+ $this->fake_aim->sendIm($screenname, $body);
+ $this->enqueueOutgoingRaw($this->fake_aim->would_be_sent);
+ return true;
+ }
+
+ /**
+ * Accept a queued input message.
+ *
+ * @return true if processing completed, false if message should be reprocessed
+ */
+ function receiveRawMessage($message)
+ {
+ $info=Aim::getMessageInfo($message);
+ $from = $info['from'];
+ $user = $this->getUser($from);
+ $notice_text = $info['message'];
+
+ $this->handleIncoming($from, $notice_text);
+
+ return true;
+ }
+
+ 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 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Instead of sending AIM messages, retrieve the raw data that would be sent
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Network
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @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..7d486a036
--- /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
+
+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.
+
+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..8ff7ab7e7
--- /dev/null
+++ b/plugins/Aim/aimmanager.php
@@ -0,0 +1,100 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * 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->enqueueIncomingRaw($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 @@
+<?php
+/*
+* PHPTOCLIB: A library for AIM connectivity through PHP using the TOC protocal.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*/
+/**
+* The version of PHPTOCLIB we are running right now
+*
+* @access private
+* @var int
+*/
+define("PHPTOCLIB_VERSION","1.0.0 RC1");
+
+// Prevents Script from Timing Out
+//set_time_limit(0);
+
+// Constant Declarations
+
+/**
+* Maximum size for a direct connection IM in bytes
+*
+* @access private
+* @var int
+*/
+
+define("MAX_DIM_SIZE",3072); //Default to 3kb
+
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_WARN",74);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_MSG",75);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_UPDATEBUDDY",76);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_SIGNON",77);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_NICK",78);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_ERROR",79);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_CHATJ",80);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_CHATI",81);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_CHATUPDBUD",82);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_CHATINV",83);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_CHATLE",84);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_URL",85);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_NICKSTAT",86);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_PASSSTAT",87);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_RVOUSP",88);
+/**
+* Internally used for message type
+*
+* @access private
+* @var int
+*/
+define("AIM_TYPE_NOT_IMPLEMENTED",666);
+
+
+
+/**
+* Internally used for connection type
+*
+* Internal type for a normal connection
+*
+* @access private
+* @var int
+*/
+define("CONN_TYPE_NORMAL",1);
+/**
+* Internally used for connection type
+*
+* Internal type of a Dirct Connection
+*
+* @access private
+* @var int
+*/
+define("CONN_TYPE_DC",2);
+/**
+* Internally used for connection type
+*
+*Internal type for a file transfer connection
+*
+* @access private
+* @var int
+*/
+define("CONN_TYPE_FT",3);
+/**
+* Internally used for connection type
+*
+*Internal type for a file get connection
+*
+* @access private
+* @var int
+*/
+define("CONN_TYPE_FTG",4);
+
+/**
+* Maximum size for a TOC packet
+*
+* @access private
+* @var int
+*/
+define("MAX_PACKLENGTH",2048);
+
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TYPE_SIGNON",1);
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TYPE_DATA",2);
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TYPE_ERROR",3);
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TYPE_SIGNOFF",4);
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TYPE_KEEPALIVE",5);
+/**
+* TOC packet type
+*
+* @access private
+* @var int
+*/
+define("SFLAP_MAX_LENGTH",1024);
+
+
+
+/**
+* Service UID for a voice connection
+*
+* @access private
+* @var int
+*/
+define('VOICE_UID', '09461341-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for file sending
+*
+* @access private
+* @var int
+*/
+define('FILE_SEND_UID', '09461343-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for file getting
+*
+* @access private
+* @var int
+*/
+define('FILE_GET_UID', '09461348-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for Direct connections
+*
+* @access private
+* @var int
+*/
+define('IMAGE_UID', '09461345-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for Buddy Icons
+*
+* @access private
+* @var int
+*/
+define('BUDDY_ICON_UID', '09461346-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for stocks
+*
+* @access private
+* @var int
+*/
+define('STOCKS_UID', '09461347-4C7F-11D1-8222-444553540000');
+/**
+* Service UID for games
+*
+* @access private
+* @var int
+*/
+define('GAMES_UID', '0946134a-4C7F-11D1-8222-444553540000');
+
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_SUCCESS",0);
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_ERR_UNKNOWN",1);
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_ERR_ARGS",2);
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_ERR_LENGTH",3);
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_ERR_READ",4);
+/**
+* FLAP return code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_ERR_SEND",5);
+
+/**
+* FLAP version number
+*
+* @access private
+* @var int
+*/
+define("SFLAP_FLAP_VERSION",1);
+/**
+* FLAP TLV code
+*
+* @access private
+* @var int
+*/
+define("SFLAP_TLV_TAG",1);
+/**
+* Bytes in a FLAP header
+*
+* @access private
+* @var int
+*/
+define("SFLAP_HEADER_LEN",6);
+
+/**
+ * PHPTocLib AIM Class
+ *
+ * @author Jeremy Bryant <pickleman78@users.sourceforge.net>
+ * @author Rajiv Makhijani <rajiv@blue-tech.org>
+ * @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('&quot;','"', $data);
+ $data = str_replace('&amp;','&', $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("<br>", " ", $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; $i<sizeof($people); $i++)
+ {
+ @list($mode, $name) = explode(":", $people[$i]);
+ switch($mode)
+ {
+ case 'p':
+ $this->myPermitList[] = $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 . "<font size=1 face=tahoma><br><br>Powered by phpTOCLib<br>http://sourceforge.net/projects/phptoclib</font>";
+ }
+
+ $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;$i<sizeof($this->myDirectConnections);$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;$i<sizeof($this->myConnections);$i++)
+ {
+ if ($dcon->sock == $this->myConnections[$i])
+ unset($this->myConnections[$i]);
+ }
+
+ $this->myConnections = array_values($this->myConnections);
+ unset($nary);
+ $nary2 = array();
+
+ for($i = 0;$i<sizeof($this->myDirectConnections);$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;$i<sizeof($this->myDirectConnections);$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;$i<sizeof($ready);$i++)
+ {
+ //Get the type
+ $type = $this->connectionType($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;$j<sizeof($this->myDirectConnections);$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;$i<sizeof($people);$i++)
+ {
+ @list($mode, $name) = explode(":", $people[$i]);
+ switch($mode)
+ {
+ case 'p':
+ $this->myPermitList[] = $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; $i<count($data_array); $i++)
+ {
+ switch($i)
+ {
+ case 0:
+ $cmd = $data_array[$i];
+ break;
+ case 1:
+ $errornum = $data_array[$i];
+ break;
+ case 2:
+ $errargs = $data_array[$i];
+ break;
+ }
+ }
+ eval("\$errorstring=\"\$ERRORS[" . $errornum . "]\";");
+ $string = "\$errorstring=\"\$ERRORS[$errornum]\";";
+ //This is important information! We need
+ // a A different outputter for errors
+ // b Just to echo it
+ //I'm just going to do a straight echo here, becuse we assume that
+ //the user will NEED to see this error. An option to supress it will
+ //come later I think. Perhaps if we did an error reporting level, similar
+ //to PHP's, and we could probably even use PHP's error outputting system
+ //I think that may be an idea....
+
+ $this->log($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 @@
+<?php
+
+//The following class was created June 30th 2004 by Jeremy(pickle)
+//This class is designed to handle a direct connection
+
+class Dconnect
+{
+ var $sock;
+ var $lastReceived;
+ var $lastMessage;
+ var $connected;
+ var $cookie;
+ var $type=2;
+ var $connectedTo;
+
+
+ function Dconnect($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 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/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php
index 93a35b3f3..f4d987489 100644
--- a/plugins/BitlyUrl/BitlyUrlPlugin.php
+++ b/plugins/BitlyUrl/BitlyUrlPlugin.php
@@ -33,8 +33,6 @@ if (!defined('STATUSNET')) {
exit(1);
}
-require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php';
-
class BitlyUrlPlugin extends UrlShortenerPlugin
{
public $shortenerName = 'bit.ly';
diff --git a/plugins/CasAuthentication/caslogin.php b/plugins/CasAuthentication/caslogin.php
index 846774e7c..3301ce582 100644
--- a/plugins/CasAuthentication/caslogin.php
+++ b/plugins/CasAuthentication/caslogin.php
@@ -28,7 +28,7 @@ class CasloginAction extends Action
$this->clientError(_m('Already logged in.'));
} else {
global $casSettings;
- phpCAS::client(CAS_VERSION_2_0,$casSettings['server'],$casSettings['port'],$casSettings['path']);
+ phpCAS::client(CAS_VERSION_2_0,$casSettings['server'],$casSettings['port'],$casSettings['path'],false);
phpCAS::setNoCasServerValidation();
phpCAS::handleLogoutRequests();
phpCAS::forceAuthentication();
diff --git a/plugins/CasAuthentication/extlib/CAS.php b/plugins/CasAuthentication/extlib/CAS.php
index e75437419..62a617579 100644
--- a/plugins/CasAuthentication/extlib/CAS.php
+++ b/plugins/CasAuthentication/extlib/CAS.php
@@ -1,20 +1,46 @@
<?php
-// commented in 0.4.22-RC2 for Sylvain Derosiaux
-// error_reporting(E_ALL ^ E_NOTICE);
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the ESUP-Portail consortium & the JA-SIG
+ * Collaborative nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
//
// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] in IIS
//
if (!$_SERVER['REQUEST_URI']) {
- $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'].'?'.$_SERVER['QUERY_STRING'];
+ $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
}
//
// another one by Vangelis Haniotakis also to make phpCAS work with PHP5
//
-if (version_compare(PHP_VERSION,'5','>=')) {
- require_once(dirname(__FILE__).'/CAS/domxml-php4-to-php5.php');
+if (version_compare(PHP_VERSION, '5', '>=') && !(function_exists('domxml_new_doc'))) {
+ require_once (dirname(__FILE__) . '/CAS/domxml-php4-to-php5.php');
}
/**
@@ -35,24 +61,24 @@ if (version_compare(PHP_VERSION,'5','>=')) {
/**
* phpCAS version. accessible for the user by phpCAS::getVersion().
*/
-define('PHPCAS_VERSION','1.1.0RC6');
+define('PHPCAS_VERSION', '1.1.2');
// ------------------------------------------------------------------------
// CAS VERSIONS
// ------------------------------------------------------------------------
- /**
- * @addtogroup public
- * @{
- */
+/**
+ * @addtogroup public
+ * @{
+ */
/**
* CAS version 1.0
*/
-define("CAS_VERSION_1_0",'1.0');
+define("CAS_VERSION_1_0", '1.0');
/*!
* CAS version 2.0
*/
-define("CAS_VERSION_2_0",'2.0');
+define("CAS_VERSION_2_0", '2.0');
// ------------------------------------------------------------------------
// SAML defines
@@ -71,143 +97,141 @@ define("SAML_XML_HEADER", '<?xml version="1.0" encoding="UTF-8"?>');
/**
* SOAP envelope for SAML POST
*/
-define ("SAML_SOAP_ENV", '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/>');
+define("SAML_SOAP_ENV", '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/>');
/**
* SOAP body for SAML POST
*/
-define ("SAML_SOAP_BODY", '<SOAP-ENV:Body>');
+define("SAML_SOAP_BODY", '<SOAP-ENV:Body>');
/**
* SAMLP request
*/
-define ("SAMLP_REQUEST", '<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" MajorVersion="1" MinorVersion="1" RequestID="_192.168.16.51.1024506224022" IssueInstant="2002-06-19T17:03:44.022Z">');
-define ("SAMLP_REQUEST_CLOSE", '</samlp:Request>');
+define("SAMLP_REQUEST", '<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" MajorVersion="1" MinorVersion="1" RequestID="_192.168.16.51.1024506224022" IssueInstant="2002-06-19T17:03:44.022Z">');
+define("SAMLP_REQUEST_CLOSE", '</samlp:Request>');
/**
* SAMLP artifact tag (for the ticket)
*/
-define ("SAML_ASSERTION_ARTIFACT", '<samlp:AssertionArtifact>');
+define("SAML_ASSERTION_ARTIFACT", '<samlp:AssertionArtifact>');
/**
* SAMLP close
*/
-define ("SAML_ASSERTION_ARTIFACT_CLOSE", '</samlp:AssertionArtifact>');
+define("SAML_ASSERTION_ARTIFACT_CLOSE", '</samlp:AssertionArtifact>');
/**
* SOAP body close
*/
-define ("SAML_SOAP_BODY_CLOSE", '</SOAP-ENV:Body>');
+define("SAML_SOAP_BODY_CLOSE", '</SOAP-ENV:Body>');
/**
* SOAP envelope close
*/
-define ("SAML_SOAP_ENV_CLOSE", '</SOAP-ENV:Envelope>');
+define("SAML_SOAP_ENV_CLOSE", '</SOAP-ENV:Envelope>');
/**
* SAML Attributes
*/
define("SAML_ATTRIBUTES", 'SAMLATTRIBS');
-
-
/** @} */
- /**
- * @addtogroup publicPGTStorage
- * @{
- */
+/**
+ * @addtogroup publicPGTStorage
+ * @{
+ */
// ------------------------------------------------------------------------
// FILE PGT STORAGE
// ------------------------------------------------------------------------
- /**
- * Default path used when storing PGT's to file
- */
-define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH",'/tmp');
+/**
+ * Default path used when storing PGT's to file
+ */
+define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH", '/tmp');
/**
* phpCAS::setPGTStorageFile()'s 2nd parameter to write plain text files
*/
-define("CAS_PGT_STORAGE_FILE_FORMAT_PLAIN",'plain');
+define("CAS_PGT_STORAGE_FILE_FORMAT_PLAIN", 'plain');
/**
* phpCAS::setPGTStorageFile()'s 2nd parameter to write xml files
*/
-define("CAS_PGT_STORAGE_FILE_FORMAT_XML",'xml');
+define("CAS_PGT_STORAGE_FILE_FORMAT_XML", 'xml');
/**
* Default format used when storing PGT's to file
*/
-define("CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT",CAS_PGT_STORAGE_FILE_FORMAT_PLAIN);
+define("CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT", CAS_PGT_STORAGE_FILE_FORMAT_PLAIN);
// ------------------------------------------------------------------------
// DATABASE PGT STORAGE
// ------------------------------------------------------------------------
- /**
- * default database type when storing PGT's to database
- */
-define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE",'mysql');
+/**
+ * default database type when storing PGT's to database
+ */
+define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE", 'mysql');
/**
* default host when storing PGT's to database
*/
-define("CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME",'localhost');
+define("CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME", 'localhost');
/**
* default port when storing PGT's to database
*/
-define("CAS_PGT_STORAGE_DB_DEFAULT_PORT",'');
+define("CAS_PGT_STORAGE_DB_DEFAULT_PORT", '');
/**
* default database when storing PGT's to database
*/
-define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE",'phpCAS');
+define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE", 'phpCAS');
/**
* default table when storing PGT's to database
*/
-define("CAS_PGT_STORAGE_DB_DEFAULT_TABLE",'pgt');
+define("CAS_PGT_STORAGE_DB_DEFAULT_TABLE", 'pgt');
/** @} */
// ------------------------------------------------------------------------
// SERVICE ACCESS ERRORS
// ------------------------------------------------------------------------
- /**
- * @addtogroup publicServices
- * @{
- */
+/**
+ * @addtogroup publicServices
+ * @{
+ */
/**
* phpCAS::service() error code on success
*/
-define("PHPCAS_SERVICE_OK",0);
+define("PHPCAS_SERVICE_OK", 0);
/**
* phpCAS::service() error code when the PT could not retrieve because
* the CAS server did not respond.
*/
-define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE",1);
+define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE", 1);
/**
* phpCAS::service() error code when the PT could not retrieve because
* the response of the CAS server was ill-formed.
*/
-define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE",2);
+define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE", 2);
/**
* phpCAS::service() error code when the PT could not retrieve because
* the CAS server did not want to.
*/
-define("PHPCAS_SERVICE_PT_FAILURE",3);
+define("PHPCAS_SERVICE_PT_FAILURE", 3);
/**
* phpCAS::service() error code when the service was not available.
*/
-define("PHPCAS_SERVICE_NOT AVAILABLE",4);
+define("PHPCAS_SERVICE_NOT AVAILABLE", 4);
/** @} */
// ------------------------------------------------------------------------
// LANGUAGES
// ------------------------------------------------------------------------
- /**
- * @addtogroup publicLang
- * @{
- */
-
-define("PHPCAS_LANG_ENGLISH", 'english');
-define("PHPCAS_LANG_FRENCH", 'french');
-define("PHPCAS_LANG_GREEK", 'greek');
-define("PHPCAS_LANG_GERMAN", 'german');
-define("PHPCAS_LANG_JAPANESE", 'japanese');
-define("PHPCAS_LANG_SPANISH", 'spanish');
-define("PHPCAS_LANG_CATALAN", 'catalan');
+/**
+ * @addtogroup publicLang
+ * @{
+ */
+
+define("PHPCAS_LANG_ENGLISH", 'english');
+define("PHPCAS_LANG_FRENCH", 'french');
+define("PHPCAS_LANG_GREEK", 'greek');
+define("PHPCAS_LANG_GERMAN", 'german');
+define("PHPCAS_LANG_JAPANESE", 'japanese');
+define("PHPCAS_LANG_SPANISH", 'spanish');
+define("PHPCAS_LANG_CATALAN", 'catalan');
/** @} */
@@ -225,31 +249,31 @@ define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH);
// ------------------------------------------------------------------------
// DEBUG
// ------------------------------------------------------------------------
- /**
- * @addtogroup publicDebug
- * @{
- */
+/**
+ * @addtogroup publicDebug
+ * @{
+ */
/**
* The default directory for the debug file under Unix.
*/
-define('DEFAULT_DEBUG_DIR','/tmp/');
+define('DEFAULT_DEBUG_DIR', '/tmp/');
/** @} */
// ------------------------------------------------------------------------
// MISC
// ------------------------------------------------------------------------
- /**
- * @addtogroup internalMisc
- * @{
- */
+/**
+ * @addtogroup internalMisc
+ * @{
+ */
/**
* This global variable is used by the interface class phpCAS.
*
* @hideinitializer
*/
-$GLOBALS['PHPCAS_CLIENT'] = null;
+$GLOBALS['PHPCAS_CLIENT'] = null;
/**
* This global variable is used to store where the initializer is called from
@@ -257,10 +281,12 @@ $GLOBALS['PHPCAS_CLIENT'] = null;
*
* @hideinitializer
*/
-$GLOBALS['PHPCAS_INIT_CALL'] = array('done' => FALSE,
+$GLOBALS['PHPCAS_INIT_CALL'] = array (
+ 'done' => FALSE,
'file' => '?',
'line' => -1,
- 'method' => '?');
+ 'method' => '?'
+);
/**
* This global variable is used to store where the method checking
@@ -268,20 +294,24 @@ $GLOBALS['PHPCAS_INIT_CALL'] = array('done' => FALSE,
*
* @hideinitializer
*/
-$GLOBALS['PHPCAS_AUTH_CHECK_CALL'] = array('done' => FALSE,
+$GLOBALS['PHPCAS_AUTH_CHECK_CALL'] = array (
+ 'done' => FALSE,
'file' => '?',
'line' => -1,
'method' => '?',
- 'result' => FALSE);
+ 'result' => FALSE
+);
/**
* This global variable is used to store phpCAS debug mode.
*
* @hideinitializer
*/
-$GLOBALS['PHPCAS_DEBUG'] = array('filename' => FALSE,
+$GLOBALS['PHPCAS_DEBUG'] = array (
+ 'filename' => FALSE,
'indent' => 0,
- 'unique_id' => '');
+ 'unique_id' => ''
+);
/** @} */
@@ -290,7 +320,7 @@ $GLOBALS['PHPCAS_DEBUG'] = array('filename' => FALSE,
// ########################################################################
// include client class
-include_once(dirname(__FILE__).'/CAS/client.php');
+include_once (dirname(__FILE__) . '/CAS/client.php');
// ########################################################################
// INTERFACE CLASS
@@ -308,20 +338,17 @@ include_once(dirname(__FILE__).'/CAS/client.php');
* at the end of CAS/client.php).
*/
+class phpCAS {
-
-class phpCAS
-{
-
// ########################################################################
// INITIALIZATION
// ########################################################################
-
+
/**
* @addtogroup publicInit
* @{
*/
-
+
/**
* phpCAS client initializer.
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -336,43 +363,41 @@ class phpCAS
*
* @return a newly created CASClient object
*/
- function client($server_version,
- $server_hostname,
- $server_port,
- $server_uri,
- $start_session = true)
- {
+ function client($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {
global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL;
-
- phpCAS::traceBegin();
- if ( is_object($PHPCAS_CLIENT) ) {
- phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')');
+
+ phpCAS :: traceBegin();
+ if (is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error($PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . $PHPCAS_INIT_CALL['file'] . ':' . $PHPCAS_INIT_CALL['line'] . ')');
}
- if ( gettype($server_version) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_version (should be `string\')');
+ if (gettype($server_version) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_version (should be `string\')');
}
- if ( gettype($server_hostname) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')');
+ if (gettype($server_hostname) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_hostname (should be `string\')');
}
- if ( gettype($server_port) != 'integer' ) {
- phpCAS::error('type mismatched for parameter $server_port (should be `integer\')');
+ if (gettype($server_port) != 'integer') {
+ phpCAS :: error('type mismatched for parameter $server_port (should be `integer\')');
}
- if ( gettype($server_uri) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_uri (should be `string\')');
+ if (gettype($server_uri) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_uri (should be `string\')');
}
-
+
// store where the initializer is called from
- $dbg = phpCAS::backtrace();
- $PHPCAS_INIT_CALL = array('done' => TRUE,
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_INIT_CALL = array (
+ 'done' => TRUE,
'file' => $dbg[0]['file'],
'line' => $dbg[0]['line'],
- 'method' => __CLASS__.'::'.__FUNCTION__);
-
+ 'method' => __CLASS__ . '::' . __FUNCTION__
+ );
+
// initialize the global object $PHPCAS_CLIENT
- $PHPCAS_CLIENT = new CASClient($server_version,FALSE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session);
- phpCAS::traceEnd();
- }
-
+ $PHPCAS_CLIENT = new CASClient($server_version, FALSE /*proxy*/
+ , $server_hostname, $server_port, $server_uri, $start_session);
+ phpCAS :: traceEnd();
+ }
+
/**
* phpCAS proxy initializer.
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -387,110 +412,107 @@ class phpCAS
*
* @return a newly created CASClient object
*/
- function proxy($server_version,
- $server_hostname,
- $server_port,
- $server_uri,
- $start_session = true)
- {
+ function proxy($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {
global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL;
-
- phpCAS::traceBegin();
- if ( is_object($PHPCAS_CLIENT) ) {
- phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')');
+
+ phpCAS :: traceBegin();
+ if (is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error($PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . $PHPCAS_INIT_CALL['file'] . ':' . $PHPCAS_INIT_CALL['line'] . ')');
}
- if ( gettype($server_version) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_version (should be `string\')');
+ if (gettype($server_version) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_version (should be `string\')');
}
- if ( gettype($server_hostname) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')');
+ if (gettype($server_hostname) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_hostname (should be `string\')');
}
- if ( gettype($server_port) != 'integer' ) {
- phpCAS::error('type mismatched for parameter $server_port (should be `integer\')');
+ if (gettype($server_port) != 'integer') {
+ phpCAS :: error('type mismatched for parameter $server_port (should be `integer\')');
}
- if ( gettype($server_uri) != 'string' ) {
- phpCAS::error('type mismatched for parameter $server_uri (should be `string\')');
+ if (gettype($server_uri) != 'string') {
+ phpCAS :: error('type mismatched for parameter $server_uri (should be `string\')');
}
-
+
// store where the initialzer is called from
- $dbg = phpCAS::backtrace();
- $PHPCAS_INIT_CALL = array('done' => TRUE,
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_INIT_CALL = array (
+ 'done' => TRUE,
'file' => $dbg[0]['file'],
'line' => $dbg[0]['line'],
- 'method' => __CLASS__.'::'.__FUNCTION__);
-
+ 'method' => __CLASS__ . '::' . __FUNCTION__
+ );
+
// initialize the global object $PHPCAS_CLIENT
- $PHPCAS_CLIENT = new CASClient($server_version,TRUE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session);
- phpCAS::traceEnd();
- }
-
+ $PHPCAS_CLIENT = new CASClient($server_version, TRUE /*proxy*/
+ , $server_hostname, $server_port, $server_uri, $start_session);
+ phpCAS :: traceEnd();
+ }
+
/** @} */
// ########################################################################
// DEBUGGING
// ########################################################################
-
+
/**
* @addtogroup publicDebug
* @{
*/
-
+
/**
* Set/unset debug mode
*
* @param $filename the name of the file used for logging, or FALSE to stop debugging.
*/
- function setDebug($filename='')
- {
+ function setDebug($filename = '') {
global $PHPCAS_DEBUG;
-
- if ( $filename != FALSE && gettype($filename) != 'string' ) {
- phpCAS::error('type mismatched for parameter $dbg (should be FALSE or the name of the log file)');
- }
-
- if ( empty($filename) ) {
- if ( preg_match('/^Win.*/',getenv('OS')) ) {
- if ( isset($_ENV['TMP']) ) {
- $debugDir = $_ENV['TMP'].'/';
- } else if ( isset($_ENV['TEMP']) ) {
- $debugDir = $_ENV['TEMP'].'/';
- } else {
- $debugDir = '';
- }
+
+ if ($filename != FALSE && gettype($filename) != 'string') {
+ phpCAS :: error('type mismatched for parameter $dbg (should be FALSE or the name of the log file)');
+ }
+
+ if (empty ($filename)) {
+ if (preg_match('/^Win.*/', getenv('OS'))) {
+ if (isset ($_ENV['TMP'])) {
+ $debugDir = $_ENV['TMP'] . '/';
+ } else
+ if (isset ($_ENV['TEMP'])) {
+ $debugDir = $_ENV['TEMP'] . '/';
+ } else {
+ $debugDir = '';
+ }
} else {
$debugDir = DEFAULT_DEBUG_DIR;
}
$filename = $debugDir . 'phpCAS.log';
}
-
- if ( empty($PHPCAS_DEBUG['unique_id']) ) {
- $PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))),0,4);
+
+ if (empty ($PHPCAS_DEBUG['unique_id'])) {
+ $PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4);
}
-
+
$PHPCAS_DEBUG['filename'] = $filename;
-
- phpCAS::trace('START ******************');
- }
-
+
+ phpCAS :: trace('START phpCAS-' . PHPCAS_VERSION . ' ******************');
+ }
+
/** @} */
/**
* @addtogroup internalDebug
* @{
*/
-
+
/**
* This method is a wrapper for debug_backtrace() that is not available
* in all PHP versions (>= 4.3.0 only)
*/
- function backtrace()
- {
- if ( function_exists('debug_backtrace') ) {
+ function backtrace() {
+ if (function_exists('debug_backtrace')) {
return debug_backtrace();
} else {
// poor man's hack ... but it does work ...
- return array();
- }
+ return array ();
}
-
+ }
+
/**
* Logs a string in debug mode.
*
@@ -498,20 +520,19 @@ class phpCAS
*
* @private
*/
- function log($str)
- {
+ function log($str) {
$indent_str = ".";
global $PHPCAS_DEBUG;
-
- if ( $PHPCAS_DEBUG['filename'] ) {
- for ($i=0;$i<$PHPCAS_DEBUG['indent'];$i++) {
+
+ if ($PHPCAS_DEBUG['filename']) {
+ for ($i = 0; $i < $PHPCAS_DEBUG['indent']; $i++) {
$indent_str .= '| ';
}
- error_log($PHPCAS_DEBUG['unique_id'].' '.$indent_str.$str."\n",3,$PHPCAS_DEBUG['filename']);
- }
-
+ error_log($PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str . "\n", 3, $PHPCAS_DEBUG['filename']);
}
-
+
+ }
+
/**
* This method is used by interface methods to print an error and where the function
* was originally called from.
@@ -520,16 +541,15 @@ class phpCAS
*
* @private
*/
- function error($msg)
- {
- $dbg = phpCAS::backtrace();
+ function error($msg) {
+ $dbg = phpCAS :: backtrace();
$function = '?';
$file = '?';
$line = '?';
- if ( is_array($dbg) ) {
- for ( $i=1; $i<sizeof($dbg); $i++) {
- if ( is_array($dbg[$i]) ) {
- if ( $dbg[$i]['class'] == __CLASS__ ) {
+ if (is_array($dbg)) {
+ for ($i = 1; $i < sizeof($dbg); $i++) {
+ if (is_array($dbg[$i])) {
+ if ($dbg[$i]['class'] == __CLASS__) {
$function = $dbg[$i]['function'];
$file = $dbg[$i]['file'];
$line = $dbg[$i]['line'];
@@ -537,77 +557,73 @@ class phpCAS
}
}
}
- echo "<br />\n<b>phpCAS error</b>: <font color=\"FF0000\"><b>".__CLASS__."::".$function.'(): '.htmlentities($msg)."</b></font> in <b>".$file."</b> on line <b>".$line."</b><br />\n";
- phpCAS::trace($msg);
- phpCAS::traceExit();
- exit();
- }
-
+ echo "<br />\n<b>phpCAS error</b>: <font color=\"FF0000\"><b>" . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . "</b></font> in <b>" . $file . "</b> on line <b>" . $line . "</b><br />\n";
+ phpCAS :: trace($msg);
+ phpCAS :: traceExit();
+ exit ();
+ }
+
/**
* This method is used to log something in debug mode.
*/
- function trace($str)
- {
- $dbg = phpCAS::backtrace();
- phpCAS::log($str.' ['.basename($dbg[1]['file']).':'.$dbg[1]['line'].']');
- }
-
+ function trace($str) {
+ $dbg = phpCAS :: backtrace();
+ phpCAS :: log($str . ' [' . basename($dbg[1]['file']) . ':' . $dbg[1]['line'] . ']');
+ }
+
/**
* This method is used to indicate the start of the execution of a function in debug mode.
*/
- function traceBegin()
- {
+ function traceBegin() {
global $PHPCAS_DEBUG;
-
- $dbg = phpCAS::backtrace();
+
+ $dbg = phpCAS :: backtrace();
$str = '=> ';
- if ( !empty($dbg[2]['class']) ) {
- $str .= $dbg[2]['class'].'::';
+ if (!empty ($dbg[2]['class'])) {
+ $str .= $dbg[2]['class'] . '::';
}
- $str .= $dbg[2]['function'].'(';
- if ( is_array($dbg[2]['args']) ) {
+ $str .= $dbg[2]['function'] . '(';
+ if (is_array($dbg[2]['args'])) {
foreach ($dbg[2]['args'] as $index => $arg) {
- if ( $index != 0 ) {
+ if ($index != 0) {
$str .= ', ';
}
- $str .= str_replace("\n","",var_export($arg,TRUE));
+ $str .= str_replace("\n", "", var_export($arg, TRUE));
}
}
- $str .= ') ['.basename($dbg[2]['file']).':'.$dbg[2]['line'].']';
- phpCAS::log($str);
- $PHPCAS_DEBUG['indent'] ++;
- }
-
+ $str .= ') [' . basename($dbg[2]['file']) . ':' . $dbg[2]['line'] . ']';
+ phpCAS :: log($str);
+ $PHPCAS_DEBUG['indent']++;
+ }
+
/**
* This method is used to indicate the end of the execution of a function in debug mode.
*
* @param $res the result of the function
*/
- function traceEnd($res='')
- {
+ function traceEnd($res = '') {
global $PHPCAS_DEBUG;
-
- $PHPCAS_DEBUG['indent'] --;
- $dbg = phpCAS::backtrace();
+
+ $PHPCAS_DEBUG['indent']--;
+ $dbg = phpCAS :: backtrace();
$str = '';
- $str .= '<= '.str_replace("\n","",var_export($res,TRUE));
- phpCAS::log($str);
- }
-
+ $str .= '<= ' . str_replace("\n", "", var_export($res, TRUE));
+ phpCAS :: log($str);
+ }
+
/**
* This method is used to indicate the end of the execution of the program
*/
- function traceExit()
- {
+ function traceExit() {
global $PHPCAS_DEBUG;
-
- phpCAS::log('exit()');
- while ( $PHPCAS_DEBUG['indent'] > 0 ) {
- phpCAS::log('-');
- $PHPCAS_DEBUG['indent'] --;
- }
+
+ phpCAS :: log('exit()');
+ while ($PHPCAS_DEBUG['indent'] > 0) {
+ phpCAS :: log('-');
+ $PHPCAS_DEBUG['indent']--;
}
-
+ }
+
/** @} */
// ########################################################################
// INTERNATIONALIZATION
@@ -616,7 +632,7 @@ class phpCAS
* @addtogroup publicLang
* @{
*/
-
+
/**
* This method is used to set the language used by phpCAS.
* @note Can be called only once.
@@ -625,18 +641,17 @@ class phpCAS
*
* @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH
*/
- function setLang($lang)
- {
+ function setLang($lang) {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( gettype($lang) != 'string' ) {
- phpCAS::error('type mismatched for parameter $lang (should be `string\')');
+ if (gettype($lang) != 'string') {
+ phpCAS :: error('type mismatched for parameter $lang (should be `string\')');
}
$PHPCAS_CLIENT->setLang($lang);
- }
-
+ }
+
/** @} */
// ########################################################################
// VERSION
@@ -645,17 +660,16 @@ class phpCAS
* @addtogroup public
* @{
*/
-
+
/**
* This method returns the phpCAS version.
*
* @return the phpCAS version.
*/
- function getVersion()
- {
+ function getVersion() {
return PHPCAS_VERSION;
- }
-
+ }
+
/** @} */
// ########################################################################
// HTML OUTPUT
@@ -664,41 +678,39 @@ class phpCAS
* @addtogroup publicOutput
* @{
*/
-
+
/**
* This method sets the HTML header used for all outputs.
*
* @param $header the HTML header.
*/
- function setHTMLHeader($header)
- {
+ function setHTMLHeader($header) {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( gettype($header) != 'string' ) {
- phpCAS::error('type mismatched for parameter $header (should be `string\')');
+ if (gettype($header) != 'string') {
+ phpCAS :: error('type mismatched for parameter $header (should be `string\')');
}
$PHPCAS_CLIENT->setHTMLHeader($header);
- }
-
+ }
+
/**
* This method sets the HTML footer used for all outputs.
*
* @param $footer the HTML footer.
*/
- function setHTMLFooter($footer)
- {
+ function setHTMLFooter($footer) {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( gettype($footer) != 'string' ) {
- phpCAS::error('type mismatched for parameter $footer (should be `string\')');
+ if (gettype($footer) != 'string') {
+ phpCAS :: error('type mismatched for parameter $footer (should be `string\')');
}
$PHPCAS_CLIENT->setHTMLFooter($footer);
- }
-
+ }
+
/** @} */
// ########################################################################
// PGT STORAGE
@@ -707,7 +719,7 @@ class phpCAS
* @addtogroup publicPGTStorage
* @{
*/
-
+
/**
* This method is used to tell phpCAS to store the response of the
* CAS server to PGT requests onto the filesystem.
@@ -715,31 +727,29 @@ class phpCAS
* @param $format the format used to store the PGT's (`plain' and `xml' allowed)
* @param $path the path where the PGT's should be stored
*/
- function setPGTStorageFile($format='',
- $path='')
- {
- global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
- }
- if ( !$PHPCAS_CLIENT->isProxy() ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ function setPGTStorageFile($format = '', $path = '') {
+ global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')');
+ if (!$PHPCAS_CLIENT->isProxy()) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( gettype($format) != 'string' ) {
- phpCAS::error('type mismatched for parameter $format (should be `string\')');
+ if ($PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called before ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() (called at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ')');
}
- if ( gettype($path) != 'string' ) {
- phpCAS::error('type mismatched for parameter $format (should be `string\')');
+ if (gettype($format) != 'string') {
+ phpCAS :: error('type mismatched for parameter $format (should be `string\')');
}
- $PHPCAS_CLIENT->setPGTStorageFile($format,$path);
- phpCAS::traceEnd();
+ if (gettype($path) != 'string') {
+ phpCAS :: error('type mismatched for parameter $format (should be `string\')');
}
-
+ $PHPCAS_CLIENT->setPGTStorageFile($format, $path);
+ phpCAS :: traceEnd();
+ }
+
/**
* This method is used to tell phpCAS to store the response of the
* CAS server to PGT requests into a database.
@@ -755,51 +765,44 @@ class phpCAS
* @param $database the name of the database
* @param $table the name of the table storing the data
*/
- function setPGTStorageDB($user,
- $password,
- $database_type='',
- $hostname='',
- $port=0,
- $database='',
- $table='')
- {
- global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
- }
- if ( !$PHPCAS_CLIENT->isProxy() ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ function setPGTStorageDB($user, $password, $database_type = '', $hostname = '', $port = 0, $database = '', $table = '') {
+ global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')');
+ if (!$PHPCAS_CLIENT->isProxy()) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( gettype($user) != 'string' ) {
- phpCAS::error('type mismatched for parameter $user (should be `string\')');
+ if ($PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called before ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() (called at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ')');
}
- if ( gettype($password) != 'string' ) {
- phpCAS::error('type mismatched for parameter $password (should be `string\')');
+ if (gettype($user) != 'string') {
+ phpCAS :: error('type mismatched for parameter $user (should be `string\')');
}
- if ( gettype($database_type) != 'string' ) {
- phpCAS::error('type mismatched for parameter $database_type (should be `string\')');
+ if (gettype($password) != 'string') {
+ phpCAS :: error('type mismatched for parameter $password (should be `string\')');
}
- if ( gettype($hostname) != 'string' ) {
- phpCAS::error('type mismatched for parameter $hostname (should be `string\')');
+ if (gettype($database_type) != 'string') {
+ phpCAS :: error('type mismatched for parameter $database_type (should be `string\')');
}
- if ( gettype($port) != 'integer' ) {
- phpCAS::error('type mismatched for parameter $port (should be `integer\')');
+ if (gettype($hostname) != 'string') {
+ phpCAS :: error('type mismatched for parameter $hostname (should be `string\')');
}
- if ( gettype($database) != 'string' ) {
- phpCAS::error('type mismatched for parameter $database (should be `string\')');
+ if (gettype($port) != 'integer') {
+ phpCAS :: error('type mismatched for parameter $port (should be `integer\')');
}
- if ( gettype($table) != 'string' ) {
- phpCAS::error('type mismatched for parameter $table (should be `string\')');
+ if (gettype($database) != 'string') {
+ phpCAS :: error('type mismatched for parameter $database (should be `string\')');
}
- $PHPCAS_CLIENT->setPGTStorageDB($user,$password,$database_type,$hostname,$port,$database,$table);
- phpCAS::traceEnd();
+ if (gettype($table) != 'string') {
+ phpCAS :: error('type mismatched for parameter $table (should be `string\')');
}
-
+ $PHPCAS_CLIENT->setPGTStorageDB($user, $password, $database_type, $hostname, $port, $database, $table);
+ phpCAS :: traceEnd();
+ }
+
/** @} */
// ########################################################################
// ACCESS TO EXTERNAL SERVICES
@@ -808,7 +811,7 @@ class phpCAS
* @addtogroup publicServices
* @{
*/
-
+
/**
* This method is used to access an HTTP[S] service.
*
@@ -822,33 +825,32 @@ class phpCAS
* @return TRUE on success, FALSE otherwise (in this later case, $err_code
* gives the reason why it failed and $output contains an error message).
*/
- function serviceWeb($url,&$err_code,&$output)
- {
+ function serviceWeb($url, & $err_code, & $output) {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_CLIENT->isProxy() ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ if (!$PHPCAS_CLIENT->isProxy()) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()');
+ if (!$PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) {
- phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE');
+ if (!$PHPCAS_AUTH_CHECK_CALL['result']) {
+ phpCAS :: error('authentication was checked (by ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ') but the method returned FALSE');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
-
- $res = $PHPCAS_CLIENT->serviceWeb($url,$err_code,$output);
-
- phpCAS::traceEnd($res);
+
+ $res = $PHPCAS_CLIENT->serviceWeb($url, $err_code, $output);
+
+ phpCAS :: traceEnd($res);
return $res;
- }
-
+ }
+
/**
* This method is used to access an IMAP/POP3/NNTP service.
*
@@ -866,37 +868,36 @@ class phpCAS
* @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code
* gives the reason why it failed and $err_msg contains an error message).
*/
- function serviceMail($url,$service,$flags,&$err_code,&$err_msg,&$pt)
- {
+ function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_CLIENT->isProxy() ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ if (!$PHPCAS_CLIENT->isProxy()) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()');
+ if (!$PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) {
- phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE');
+ if (!$PHPCAS_AUTH_CHECK_CALL['result']) {
+ phpCAS :: error('authentication was checked (by ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ') but the method returned FALSE');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
-
- if ( gettype($flags) != 'integer' ) {
- phpCAS::error('type mismatched for parameter $flags (should be `integer\')');
+
+ if (gettype($flags) != 'integer') {
+ phpCAS :: error('type mismatched for parameter $flags (should be `integer\')');
}
-
- $res = $PHPCAS_CLIENT->serviceMail($url,$service,$flags,$err_code,$err_msg,$pt);
-
- phpCAS::traceEnd($res);
+
+ $res = $PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt);
+
+ phpCAS :: traceEnd($res);
return $res;
- }
-
+ }
+
/** @} */
// ########################################################################
// AUTHENTICATION
@@ -905,7 +906,7 @@ class phpCAS
* @addtogroup publicAuth
* @{
*/
-
+
/**
* Set the times authentication will be cached before really accessing the CAS server in gateway mode:
* - -1: check only once, and then never again (until you pree login)
@@ -914,150 +915,156 @@ class phpCAS
*
* @param $n an integer.
*/
- function setCacheTimesForAuthRecheck($n)
- {
+ function setCacheTimesForAuthRecheck($n) {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( gettype($n) != 'integer' ) {
- phpCAS::error('type mismatched for parameter $header (should be `string\')');
+ if (gettype($n) != 'integer') {
+ phpCAS :: error('type mismatched for parameter $header (should be `string\')');
}
$PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n);
- }
-
+ }
+
/**
* This method is called to check if the user is authenticated (use the gateway feature).
* @return TRUE when the user is authenticated; otherwise FALSE.
*/
- function checkAuthentication()
- {
+ function checkAuthentication() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
-
+
$auth = $PHPCAS_CLIENT->checkAuthentication();
-
+
// store where the authentication has been checked and the result
- $dbg = phpCAS::backtrace();
- $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE,
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_AUTH_CHECK_CALL = array (
+ 'done' => TRUE,
'file' => $dbg[0]['file'],
'line' => $dbg[0]['line'],
- 'method' => __CLASS__.'::'.__FUNCTION__,
- 'result' => $auth );
- phpCAS::traceEnd($auth);
- return $auth;
- }
+ 'method' => __CLASS__ . '::' . __FUNCTION__,
+ 'result' => $auth
+ );
+ phpCAS :: traceEnd($auth);
+ return $auth;
+ }
/**
* This method is called to force authentication if the user was not already
* authenticated. If the user is not authenticated, halt by redirecting to
* the CAS server.
*/
- function forceAuthentication()
- {
+ function forceAuthentication() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
-
+
$auth = $PHPCAS_CLIENT->forceAuthentication();
-
+
// store where the authentication has been checked and the result
- $dbg = phpCAS::backtrace();
- $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE,
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_AUTH_CHECK_CALL = array (
+ 'done' => TRUE,
'file' => $dbg[0]['file'],
'line' => $dbg[0]['line'],
- 'method' => __CLASS__.'::'.__FUNCTION__,
- 'result' => $auth );
-
- if ( !$auth ) {
- phpCAS::trace('user is not authenticated, redirecting to the CAS server');
+ 'method' => __CLASS__ . '::' . __FUNCTION__,
+ 'result' => $auth
+ );
+
+ if (!$auth) {
+ phpCAS :: trace('user is not authenticated, redirecting to the CAS server');
$PHPCAS_CLIENT->forceAuthentication();
} else {
- phpCAS::trace('no need to authenticate (user `'.phpCAS::getUser().'\' is already authenticated)');
+ phpCAS :: trace('no need to authenticate (user `' . phpCAS :: getUser() . '\' is already authenticated)');
}
-
- phpCAS::traceEnd();
- return $auth;
- }
-
+
+ phpCAS :: traceEnd();
+ return $auth;
+ }
+
/**
* This method is called to renew the authentication.
**/
function renewAuthentication() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before'.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
-
+
// store where the authentication has been checked and the result
- $dbg = phpCAS::backtrace();
- $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, 'file' => $dbg[0]['file'], 'line' => $dbg[0]['line'], 'method' => __CLASS__.'::'.__FUNCTION__, 'result' => $auth );
-
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_AUTH_CHECK_CALL = array (
+ 'done' => TRUE,
+ 'file' => $dbg[0]['file'],
+ 'line' => $dbg[0]['line'],
+ 'method' => __CLASS__ . '::' . __FUNCTION__,
+ 'result' => $auth
+ );
+
$PHPCAS_CLIENT->renewAuthentication();
- phpCAS::traceEnd();
+ phpCAS :: traceEnd();
}
/**
* This method has been left from version 0.4.1 for compatibility reasons.
*/
- function authenticate()
- {
- phpCAS::error('this method is deprecated. You should use '.__CLASS__.'::forceAuthentication() instead');
- }
-
+ function authenticate() {
+ phpCAS :: error('this method is deprecated. You should use ' . __CLASS__ . '::forceAuthentication() instead');
+ }
+
/**
* This method is called to check if the user is authenticated (previously or by
* tickets given in the URL).
*
* @return TRUE when the user is authenticated.
*/
- function isAuthenticated()
- {
+ function isAuthenticated() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
-
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
-
+
// call the isAuthenticated method of the global $PHPCAS_CLIENT object
$auth = $PHPCAS_CLIENT->isAuthenticated();
-
+
// store where the authentication has been checked and the result
- $dbg = phpCAS::backtrace();
- $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE,
+ $dbg = phpCAS :: backtrace();
+ $PHPCAS_AUTH_CHECK_CALL = array (
+ 'done' => TRUE,
'file' => $dbg[0]['file'],
'line' => $dbg[0]['line'],
- 'method' => __CLASS__.'::'.__FUNCTION__,
- 'result' => $auth );
- phpCAS::traceEnd($auth);
+ 'method' => __CLASS__ . '::' . __FUNCTION__,
+ 'result' => $auth
+ );
+ phpCAS :: traceEnd($auth);
return $auth;
- }
-
+ }
+
/**
* Checks whether authenticated based on $_SESSION. Useful to avoid
* server calls.
* @return true if authenticated, false otherwise.
* @since 0.4.22 by Brendan Arnold
*/
- function isSessionAuthenticated ()
- {
+ function isSessionAuthenticated() {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
- }
- return($PHPCAS_CLIENT->isSessionAuthenticated());
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
-
+ return ($PHPCAS_CLIENT->isSessionAuthenticated());
+ }
+
/**
* This method returns the CAS user's login name.
* @warning should not be called only after phpCAS::forceAuthentication()
@@ -1065,21 +1072,20 @@ class phpCAS
*
* @return the login name of the authenticated user
*/
- function getUser()
- {
+ function getUser() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
+ if (!$PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) {
- phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE');
+ if (!$PHPCAS_AUTH_CHECK_CALL['result']) {
+ phpCAS :: error('authentication was checked (by ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ') but the method returned FALSE');
}
return $PHPCAS_CLIENT->getUser();
- }
-
+ }
+
/**
* This method returns the CAS user's login name.
* @warning should not be called only after phpCAS::forceAuthentication()
@@ -1087,169 +1093,160 @@ class phpCAS
*
* @return the login name of the authenticated user
*/
- function getAttributes()
- {
+ function getAttributes() {
global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
+ if (!$PHPCAS_AUTH_CHECK_CALL['done']) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
}
- if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) {
- phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE');
+ if (!$PHPCAS_AUTH_CHECK_CALL['result']) {
+ phpCAS :: error('authentication was checked (by ' . $PHPCAS_AUTH_CHECK_CALL['method'] . '() at ' . $PHPCAS_AUTH_CHECK_CALL['file'] . ':' . $PHPCAS_AUTH_CHECK_CALL['line'] . ') but the method returned FALSE');
}
return $PHPCAS_CLIENT->getAttributes();
+ }
+ /**
+ * Handle logout requests.
+ */
+ function handleLogoutRequests($check_client = true, $allowed_clients = false) {
+ global $PHPCAS_CLIENT;
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
- /**
- * Handle logout requests.
- */
- function handleLogoutRequests($check_client=true, $allowed_clients=false)
- {
- global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
- }
- return($PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients));
- }
-
+ return ($PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients));
+ }
+
/**
* This method returns the URL to be used to login.
* or phpCAS::isAuthenticated().
*
* @return the login name of the authenticated user
*/
- function getServerLoginURL()
- {
+ function getServerLoginURL() {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
return $PHPCAS_CLIENT->getServerLoginURL();
- }
-
+ }
+
/**
* Set the login URL of the CAS server.
* @param $url the login URL
* @since 0.4.21 by Wyman Chan
*/
- function setServerLoginURL($url='')
- {
+ function setServerLoginURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after
- '.__CLASS__.'::client()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after
+ ' . __CLASS__ . '::client()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be
- `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be
+ `string\')');
}
$PHPCAS_CLIENT->setServerLoginURL($url);
- phpCAS::traceEnd();
- }
-
-
+ phpCAS :: traceEnd();
+ }
+
/**
* Set the serviceValidate URL of the CAS server.
+ * Used only in CAS 1.0 validations
* @param $url the serviceValidate URL
* @since 1.1.0 by Joachim Fritschi
*/
- function setServerServiceValidateURL($url='')
- {
+ function setServerServiceValidateURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after
- '.__CLASS__.'::client()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after
+ ' . __CLASS__ . '::client()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be
- `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be
+ `string\')');
}
$PHPCAS_CLIENT->setServerServiceValidateURL($url);
- phpCAS::traceEnd();
- }
-
-
- /**
+ phpCAS :: traceEnd();
+ }
+
+ /**
* Set the proxyValidate URL of the CAS server.
+ * Used for all CAS 2.0 validations
* @param $url the proxyValidate URL
* @since 1.1.0 by Joachim Fritschi
*/
- function setServerProxyValidateURL($url='')
- {
+ function setServerProxyValidateURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after
- '.__CLASS__.'::client()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after
+ ' . __CLASS__ . '::client()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be
- `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be
+ `string\')');
}
$PHPCAS_CLIENT->setServerProxyValidateURL($url);
- phpCAS::traceEnd();
- }
-
- /**
+ phpCAS :: traceEnd();
+ }
+
+ /**
* Set the samlValidate URL of the CAS server.
* @param $url the samlValidate URL
* @since 1.1.0 by Joachim Fritschi
*/
- function setServerSamlValidateURL($url='')
- {
+ function setServerSamlValidateURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after
- '.__CLASS__.'::client()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after
+ ' . __CLASS__ . '::client()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be
- `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be
+ `string\')');
}
$PHPCAS_CLIENT->setServerSamlValidateURL($url);
- phpCAS::traceEnd();
- }
-
+ phpCAS :: traceEnd();
+ }
+
/**
* This method returns the URL to be used to login.
* or phpCAS::isAuthenticated().
*
* @return the login name of the authenticated user
*/
- function getServerLogoutURL()
- {
+ function getServerLogoutURL() {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
}
return $PHPCAS_CLIENT->getServerLogoutURL();
- }
-
+ }
+
/**
* Set the logout URL of the CAS server.
* @param $url the logout URL
* @since 0.4.21 by Wyman Chan
*/
- function setServerLogoutURL($url='')
- {
+ function setServerLogoutURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after
- '.__CLASS__.'::client()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after
+ ' . __CLASS__ . '::client()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be
- `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be
+ `string\')');
}
$PHPCAS_CLIENT->setServerLogoutURL($url);
- phpCAS::traceEnd();
- }
-
+ phpCAS :: traceEnd();
+ }
+
/**
* This method is used to logout from CAS.
* @params $params an array that contains the optional url and service parameters that will be passed to the CAS server
@@ -1257,66 +1254,70 @@ class phpCAS
*/
function logout($params = "") {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
+ phpCAS :: traceBegin();
if (!is_object($PHPCAS_CLIENT)) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
- $parsedParams = array();
+ $parsedParams = array ();
if ($params != "") {
if (is_string($params)) {
- phpCAS::error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead');
+ phpCAS :: error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead');
}
if (!is_array($params)) {
- phpCAS::error('type mismatched for parameter $params (should be `array\')');
+ phpCAS :: error('type mismatched for parameter $params (should be `array\')');
}
foreach ($params as $key => $value) {
if ($key != "service" && $key != "url") {
- phpCAS::error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\'');
+ phpCAS :: error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\'');
}
$parsedParams[$key] = $value;
}
}
$PHPCAS_CLIENT->logout($parsedParams);
// never reached
- phpCAS::traceEnd();
+ phpCAS :: traceEnd();
}
-
+
/**
* This method is used to logout from CAS. Halts by redirecting to the CAS server.
* @param $service a URL that will be transmitted to the CAS server
*/
function logoutWithRedirectService($service) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
if (!is_string($service)) {
- phpCAS::error('type mismatched for parameter $service (should be `string\')');
+ phpCAS :: error('type mismatched for parameter $service (should be `string\')');
}
- $PHPCAS_CLIENT->logout(array("service" => $service));
+ $PHPCAS_CLIENT->logout(array (
+ "service" => $service
+ ));
// never reached
- phpCAS::traceEnd();
+ phpCAS :: traceEnd();
}
-
+
/**
* This method is used to logout from CAS. Halts by redirecting to the CAS server.
* @param $url a URL that will be transmitted to the CAS server
*/
function logoutWithUrl($url) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
if (!is_string($url)) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
- $PHPCAS_CLIENT->logout(array("url" => $url));
+ $PHPCAS_CLIENT->logout(array (
+ "url" => $url
+ ));
// never reached
- phpCAS::traceEnd();
+ phpCAS :: traceEnd();
}
-
+
/**
* This method is used to logout from CAS. Halts by redirecting to the CAS server.
* @param $service a URL that will be transmitted to the CAS server
@@ -1324,161 +1325,156 @@ class phpCAS
*/
function logoutWithRedirectServiceAndUrl($service, $url) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
if (!is_string($service)) {
- phpCAS::error('type mismatched for parameter $service (should be `string\')');
+ phpCAS :: error('type mismatched for parameter $service (should be `string\')');
}
if (!is_string($url)) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
- $PHPCAS_CLIENT->logout(array("service" => $service, "url" => $url));
+ $PHPCAS_CLIENT->logout(array (
+ "service" => $service,
+ "url" => $url
+ ));
// never reached
- phpCAS::traceEnd();
+ phpCAS :: traceEnd();
}
-
+
/**
* Set the fixed URL that will be used by the CAS server to transmit the PGT.
* When this method is not called, a phpCAS script uses its own URL for the callback.
*
* @param $url the URL
*/
- function setFixedCallbackURL($url='')
- {
+ function setFixedCallbackURL($url = '') {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( !$PHPCAS_CLIENT->isProxy() ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
+ if (!$PHPCAS_CLIENT->isProxy()) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
$PHPCAS_CLIENT->setCallbackURL($url);
- phpCAS::traceEnd();
- }
-
+ phpCAS :: traceEnd();
+ }
+
/**
* Set the fixed URL that will be set as the CAS service parameter. When this
* method is not called, a phpCAS script uses its own URL.
*
* @param $url the URL
*/
- function setFixedServiceURL($url)
- {
+ function setFixedServiceURL($url) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
- }
- if ( gettype($url) != 'string' ) {
- phpCAS::error('type mismatched for parameter $url (should be `string\')');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- $PHPCAS_CLIENT->setURL($url);
- phpCAS::traceEnd();
+ if (gettype($url) != 'string') {
+ phpCAS :: error('type mismatched for parameter $url (should be `string\')');
}
-
+ $PHPCAS_CLIENT->setURL($url);
+ phpCAS :: traceEnd();
+ }
+
/**
* Get the URL that is set as the CAS service parameter.
*/
- function getServiceURL()
- {
+ function getServiceURL() {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
- }
- return($PHPCAS_CLIENT->getURL());
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
-
+ return ($PHPCAS_CLIENT->getURL());
+ }
+
/**
* Retrieve a Proxy Ticket from the CAS server.
*/
- function retrievePT($target_service,&$err_code,&$err_msg)
- {
+ function retrievePT($target_service, & $err_code, & $err_msg) {
global $PHPCAS_CLIENT;
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()');
- }
- if ( gettype($target_service) != 'string' ) {
- phpCAS::error('type mismatched for parameter $target_service(should be `string\')');
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
}
- return($PHPCAS_CLIENT->retrievePT($target_service,$err_code,$err_msg));
+ if (gettype($target_service) != 'string') {
+ phpCAS :: error('type mismatched for parameter $target_service(should be `string\')');
}
-
+ return ($PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg));
+ }
+
/**
* Set the certificate of the CAS server.
*
* @param $cert the PEM certificate
*/
- function setCasServerCert($cert)
- {
+ function setCasServerCert($cert) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
- }
- if ( gettype($cert) != 'string' ) {
- phpCAS::error('type mismatched for parameter $cert (should be `string\')');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
- $PHPCAS_CLIENT->setCasServerCert($cert);
- phpCAS::traceEnd();
+ if (gettype($cert) != 'string') {
+ phpCAS :: error('type mismatched for parameter $cert (should be `string\')');
}
-
+ $PHPCAS_CLIENT->setCasServerCert($cert);
+ phpCAS :: traceEnd();
+ }
+
/**
* Set the certificate of the CAS server CA.
*
* @param $cert the CA certificate
*/
- function setCasServerCACert($cert)
- {
+ function setCasServerCACert($cert) {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
- }
- if ( gettype($cert) != 'string' ) {
- phpCAS::error('type mismatched for parameter $cert (should be `string\')');
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
- $PHPCAS_CLIENT->setCasServerCACert($cert);
- phpCAS::traceEnd();
+ if (gettype($cert) != 'string') {
+ phpCAS :: error('type mismatched for parameter $cert (should be `string\')');
}
-
+ $PHPCAS_CLIENT->setCasServerCACert($cert);
+ phpCAS :: traceEnd();
+ }
+
/**
* Set no SSL validation for the CAS server.
*/
- function setNoCasServerValidation()
- {
+ function setNoCasServerValidation() {
global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
- }
- $PHPCAS_CLIENT->setNoCasServerValidation();
- phpCAS::traceEnd();
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
-
+ $PHPCAS_CLIENT->setNoCasServerValidation();
+ phpCAS :: traceEnd();
+ }
+
/** @} */
-
- /**
- * Change CURL options.
- * CURL is used to connect through HTTPS to CAS server
- * @param $key the option key
- * @param $value the value to set
- */
- function setExtraCurlOption($key, $value)
- {
- global $PHPCAS_CLIENT;
- phpCAS::traceBegin();
- if ( !is_object($PHPCAS_CLIENT) ) {
- phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()');
- }
- $PHPCAS_CLIENT->setExtraCurlOption($key, $value);
- phpCAS::traceEnd();
+
+ /**
+ * Change CURL options.
+ * CURL is used to connect through HTTPS to CAS server
+ * @param $key the option key
+ * @param $value the value to set
+ */
+ function setExtraCurlOption($key, $value) {
+ global $PHPCAS_CLIENT;
+ phpCAS :: traceBegin();
+ if (!is_object($PHPCAS_CLIENT)) {
+ phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
}
+ $PHPCAS_CLIENT->setExtraCurlOption($key, $value);
+ phpCAS :: traceEnd();
+ }
}
@@ -1525,7 +1521,6 @@ class phpCAS
/** @defgroup publicDebug Debugging
* @ingroup public */
-
/** @defgroup internal Implementation */
/** @defgroup internalAuthentication Authentication
@@ -1579,37 +1574,37 @@ class phpCAS
/**
* @example example_simple.php
*/
- /**
- * @example example_proxy.php
- */
- /**
- * @example example_proxy2.php
- */
- /**
- * @example example_lang.php
- */
- /**
- * @example example_html.php
- */
- /**
- * @example example_file.php
- */
- /**
- * @example example_db.php
- */
- /**
- * @example example_service.php
- */
- /**
- * @example example_session_proxy.php
- */
- /**
- * @example example_session_service.php
- */
- /**
- * @example example_gateway.php
- */
-
-
-
+/**
+ * @example example_proxy.php
+ */
+/**
+ * @example example_proxy2.php
+ */
+/**
+ * @example example_lang.php
+ */
+/**
+ * @example example_html.php
+ */
+/**
+ * @example example_file.php
+ */
+/**
+ * @example example_db.php
+ */
+/**
+ * @example example_service.php
+ */
+/**
+ * @example example_session_proxy.php
+ */
+/**
+ * @example example_session_service.php
+ */
+/**
+ * @example example_gateway.php
+ */
+/**
+ * @example example_custom_urls.php
+ */
?>
diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php
index 5a589e4b2..1e316b6f6 100644
--- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php
+++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php
@@ -1,4 +1,32 @@
<?php
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the ESUP-Portail consortium & the JA-SIG
+ * Collaborative nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
/**
* @file CAS/PGTStorage/pgt-db.php
diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php
index bc07485b8..983e557c5 100644
--- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php
+++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php
@@ -1,5 +1,32 @@
<?php
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the ESUP-Portail consortium & the JA-SIG
+ * Collaborative nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
/**
* @file CAS/PGTStorage/pgt-file.php
* Basic class for PGT file storage
diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php
index cd9b49967..cf4c4ed0f 100644
--- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php
+++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php
@@ -1,5 +1,32 @@
<?php
-
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the ESUP-Portail consortium & the JA-SIG
+ * Collaborative nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
/**
* @file CAS/PGTStorage/pgt-main.php
* Basic class for PGT storage
diff --git a/plugins/CasAuthentication/extlib/CAS/client.php b/plugins/CasAuthentication/extlib/CAS/client.php
index ad5a23f83..d38c24d36 100644
--- a/plugins/CasAuthentication/extlib/CAS/client.php
+++ b/plugins/CasAuthentication/extlib/CAS/client.php
@@ -1,5 +1,34 @@
<?php
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the ESUP-Portail consortium & the JA-SIG
+ * Collaborative nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
/**
* @file CAS/client.php
* Main class of the phpCAS library
@@ -351,8 +380,8 @@ class CASClient
{
return $this->_server['login_url'] = $url;
}
-
-
+
+
/**
* This method sets the serviceValidate URL of the CAS server.
* @param $url the serviceValidate URL
@@ -363,8 +392,8 @@ class CASClient
{
return $this->_server['service_validate_url'] = $url;
}
-
-
+
+
/**
* This method sets the proxyValidate URL of the CAS server.
* @param $url the proxyValidate URL
@@ -375,8 +404,8 @@ class CASClient
{
return $this->_server['proxy_validate_url'] = $url;
}
-
-
+
+
/**
* This method sets the samlValidate URL of the CAS server.
* @param $url the samlValidate URL
@@ -387,7 +416,7 @@ class CASClient
{
return $this->_server['saml_validate_url'] = $url;
}
-
+
/**
* This method is used to retrieve the service validating URL of the CAS server.
@@ -411,24 +440,24 @@ class CASClient
return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL());
}
/**
- * This method is used to retrieve the SAML validating URL of the CAS server.
- * @return a URL.
- * @private
- */
+ * This method is used to retrieve the SAML validating URL of the CAS server.
+ * @return a URL.
+ * @private
+ */
function getServerSamlValidateURL()
- {
- phpCAS::traceBegin();
- // the URL is build only when needed
- if ( empty($this->_server['saml_validate_url']) ) {
- switch ($this->getServerVersion()) {
- case SAML_VERSION_1_1:
- $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
- break;
+ {
+ phpCAS::traceBegin();
+ // the URL is build only when needed
+ if ( empty($this->_server['saml_validate_url']) ) {
+ switch ($this->getServerVersion()) {
+ case SAML_VERSION_1_1:
+ $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
+ break;
}
- }
- phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
- return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
- }
+ }
+ phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
+ return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
+ }
/**
* This method is used to retrieve the proxy validating URL of the CAS server.
* @return a URL.
@@ -496,20 +525,20 @@ class CASClient
{
return $this->_server['logout_url'] = $url;
}
-
+
/**
* An array to store extra curl options.
*/
var $_curl_options = array();
-
+
/**
* This method is used to set additional user curl options.
*/
function setExtraCurlOption($key, $value)
- {
+ {
$this->_curl_options[$key] = $value;
- }
-
+ }
+
/**
* This method checks to see if the request is secured via HTTPS
* @return true if https, false otherwise
@@ -556,45 +585,21 @@ class CASClient
if (version_compare(PHP_VERSION,'5','>=') && ini_get('zend.ze1_compatibility_mode')) {
phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
}
+ $this->_start_session = $start_session;
+
+ if ($this->_start_session && session_id())
+ {
+ phpCAS :: error("Another session was started before phpcas. Either disable the session" .
+ " handling for phpcas in the client() call or modify your application to leave" .
+ " session handling to phpcas");
+ }
// skip Session Handling for logout requests and if don't want it'
- if ($start_session && !$this->isLogoutRequest()) {
- phpCAS::trace("Starting session handling");
- // Check for Tickets from the CAS server
- if (empty($_GET['ticket'])){
- phpCAS::trace("No ticket found");
- // only create a session if necessary
- if (!isset($_SESSION)) {
- phpCAS::trace("No session found, creating new session");
- session_start();
- }
- }else{
- phpCAS::trace("Ticket found");
- // We have to copy any old data before renaming the session
- if (isset($_SESSION)) {
- phpCAS::trace("Old active session found, saving old data and destroying session");
- $old_session = $_SESSION;
- session_destroy();
- }else{
- session_start();
- phpCAS::trace("Starting possible old session to copy variables");
- $old_session = $_SESSION;
- session_destroy();
- }
- // set up a new session, of name based on the ticket
- $session_id = preg_replace('/[^\w]/','',$_GET['ticket']);
- phpCAS::LOG("Session ID: " . $session_id);
- session_id($session_id);
- session_start();
- // restore old session vars
- if(isset($old_session)){
- phpCAS::trace("Restoring old session vars");
- $_SESSION = $old_session;
- }
- }
- }else{
- phpCAS::trace("Skipping session creation");
+ if ($start_session && !$this->isLogoutRequest())
+ {
+ phpCAS :: trace("Starting a new session");
+ session_start();
}
-
+
// are we in proxy mode ?
$this->_proxy = $proxy;
@@ -667,12 +672,8 @@ class CASClient
}
break;
case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
- if (preg_match('/^ST-/', $ticket)) {
- phpCAS::trace('ST \'' . $ticket . '\' found');
- $this->setST($ticket);
- unset ($_GET['ticket']);
- } else if (preg_match('/^PT-/', $ticket)) {
- phpCAS::trace('PT \'' . $ticket . '\' found');
+ if( preg_match('/^[SP]T-/',$ticket) ) {
+ phpCAS::trace('ST or PT \''.$ticket.'\' found');
$this->setPT($ticket);
unset($_GET['ticket']);
} else if ( !empty($ticket) ) {
@@ -682,9 +683,9 @@ class CASClient
break;
case SAML_VERSION_1_1: // SAML just does Service Tickets
if( preg_match('/^[SP]T-/',$ticket) ) {
- phpCAS::trace('SA \''.$ticket.'\' found');
- $this->setSA($ticket);
- unset($_GET['ticket']);
+ phpCAS::trace('SA \''.$ticket.'\' found');
+ $this->setSA($ticket);
+ unset($_GET['ticket']);
} else if ( !empty($ticket) ) {
//ill-formed ticket, halt
phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
@@ -699,6 +700,57 @@ class CASClient
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// XX XX
+ // XX Session Handling XX
+ // XX XX
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+
+ /**
+ * A variable to whether phpcas will use its own session handling. Default = true
+ * @hideinitializer
+ * @private
+ */
+ var $_start_session = true;
+
+ function setStartSession($session)
+ {
+ $this->_start_session = session;
+ }
+
+ function getStartSession($session)
+ {
+ $this->_start_session = session;
+ }
+
+ /**
+ * Renaming the session
+ */
+ function renameSession($ticket)
+ {
+ phpCAS::traceBegin();
+ if($this->_start_session){
+ if (!empty ($this->_user))
+ {
+ $old_session = $_SESSION;
+ session_destroy();
+ // set up a new session, of name based on the ticket
+ $session_id = preg_replace('/[^\w]/', '', $ticket);
+ phpCAS :: trace("Session ID: ".$session_id);
+ session_id($session_id);
+ session_start();
+ phpCAS :: trace("Restoring old session vars");
+ $_SESSION = $old_session;
+ } else
+ {
+ phpCAS :: error('Session should only be renamed after successfull authentication');
+ }
+ }else{
+ phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
+ }
+ phpCAS::traceEnd();
+ }
+
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ // XX XX
// XX AUTHENTICATION XX
// XX XX
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@@ -743,8 +795,8 @@ class CASClient
}
return $this->_user;
}
-
-
+
+
/***********************************************************************************************************************
* Atrributes section
@@ -760,23 +812,23 @@ class CASClient
* @private
*/
var $_attributes = array();
-
+
function setAttributes($attributes)
{ $this->_attributes = $attributes; }
-
+
function getAttributes() {
if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
}
return $this->_attributes;
}
-
+
function hasAttributes()
{ return !empty($this->_attributes); }
-
+
function hasAttribute($key)
{ return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
-
+
function getAttribute($key) {
if($this->hasAttribute($key)) {
return $this->_attributes[$key];
@@ -802,7 +854,7 @@ class CASClient
}
phpCAS::traceEnd();
}
-
+
/**
* This method is called to be sure that the user is authenticated. When not
* authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
@@ -914,66 +966,73 @@ class CASClient
*/
function isAuthenticated()
{
- phpCAS::traceBegin();
- $res = FALSE;
- $validate_url = '';
-
- if ( $this->wasPreviouslyAuthenticated() ) {
+ phpCAS::traceBegin();
+ $res = FALSE;
+ $validate_url = '';
+
+ if ( $this->wasPreviouslyAuthenticated() ) {
+ if($this->hasST() || $this->hasPT() || $this->hasSA()){
+ // User has a additional ticket but was already authenticated
+ phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
+ header('Location: '.$this->getURL());
+ phpCAS::log( "Prepare redirect to remove ticket: ".$this->getURL() );
+ }else{
// the user has already (previously during the session) been
// authenticated, nothing to be done.
phpCAS::trace('user was already authenticated, no need to look for tickets');
- $res = TRUE;
}
- else {
- if ( $this->hasST() ) {
- // if a Service Ticket was given, validate it
- phpCAS::trace('ST `'.$this->getST().'\' is present');
- $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
- phpCAS::trace('ST `'.$this->getST().'\' was validated');
- if ( $this->isProxy() ) {
- $this->validatePGT($validate_url,$text_response,$tree_response); // idem
- phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
- $_SESSION['phpCAS']['pgt'] = $this->getPGT();
- }
- $_SESSION['phpCAS']['user'] = $this->getUser();
- $res = TRUE;
- }
- elseif ( $this->hasPT() ) {
- // if a Proxy Ticket was given, validate it
- phpCAS::trace('PT `'.$this->getPT().'\' is present');
- $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
- phpCAS::trace('PT `'.$this->getPT().'\' was validated');
- if ( $this->isProxy() ) {
- $this->validatePGT($validate_url,$text_response,$tree_response); // idem
- phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
- $_SESSION['phpCAS']['pgt'] = $this->getPGT();
- }
- $_SESSION['phpCAS']['user'] = $this->getUser();
- $res = TRUE;
- }
- elseif ( $this->hasSA() ) {
- // if we have a SAML ticket, validate it.
- phpCAS::trace('SA `'.$this->getSA().'\' is present');
- $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
- phpCAS::trace('SA `'.$this->getSA().'\' was validated');
- $_SESSION['phpCAS']['user'] = $this->getUser();
- $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
- $res = TRUE;
- }
- else {
- // no ticket given, not authenticated
- phpCAS::trace('no ticket found');
+ $res = TRUE;
+ }
+ else {
+ if ( $this->hasST() ) {
+ // if a Service Ticket was given, validate it
+ phpCAS::trace('ST `'.$this->getST().'\' is present');
+ $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
+ phpCAS::trace('ST `'.$this->getST().'\' was validated');
+ if ( $this->isProxy() ) {
+ $this->validatePGT($validate_url,$text_response,$tree_response); // idem
+ phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
+ $_SESSION['phpCAS']['pgt'] = $this->getPGT();
}
- if ($res) {
- // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
- // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
- header('Location: '.$this->getURL());
- phpCAS::log( "Prepare redirect to : ".$this->getURL() );
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $res = TRUE;
+ }
+ elseif ( $this->hasPT() ) {
+ // if a Proxy Ticket was given, validate it
+ phpCAS::trace('PT `'.$this->getPT().'\' is present');
+ $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
+ phpCAS::trace('PT `'.$this->getPT().'\' was validated');
+ if ( $this->isProxy() ) {
+ $this->validatePGT($validate_url,$text_response,$tree_response); // idem
+ phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
+ $_SESSION['phpCAS']['pgt'] = $this->getPGT();
}
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $res = TRUE;
+ }
+ elseif ( $this->hasSA() ) {
+ // if we have a SAML ticket, validate it.
+ phpCAS::trace('SA `'.$this->getSA().'\' is present');
+ $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
+ phpCAS::trace('SA `'.$this->getSA().'\' was validated');
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
+ $res = TRUE;
+ }
+ else {
+ // no ticket given, not authenticated
+ phpCAS::trace('no ticket found');
+ }
+ if ($res) {
+ // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
+ // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
+ header('Location: '.$this->getURL());
+ phpCAS::log( "Prepare redirect to : ".$this->getURL() );
}
-
- phpCAS::traceEnd($res);
- return $res;
+ }
+
+ phpCAS::traceEnd($res);
+ return $res;
}
/**
@@ -1071,30 +1130,7 @@ class CASClient
phpCAS::traceExit();
exit();
}
-
-// /**
-// * This method is used to logout from CAS.
-// * @param $url a URL that will be transmitted to the CAS server (to come back to when logged out)
-// * @public
-// */
-// function logout($url = "") {
-// phpCAS::traceBegin();
-// $cas_url = $this->getServerLogoutURL();
-// // v0.4.14 sebastien.gougeon at univ-rennes1.fr
-// // header('Location: '.$cas_url);
-// if ( $url != "" ) {
-// // Adam Moore 1.0.0RC2
-// $url = '?service=' . $url . '&url=' . $url;
-// }
-// header('Location: '.$cas_url . $url);
-// session_unset();
-// session_destroy();
-// $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
-// printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
-// $this->printHTMLFooter();
-// phpCAS::traceExit();
-// exit();
-// }
+
/**
* This method is used to logout from CAS.
@@ -1114,7 +1150,7 @@ class CASClient
}
header('Location: '.$cas_url);
phpCAS::log( "Prepare redirect to : ".$cas_url );
-
+
session_unset();
session_destroy();
@@ -1156,6 +1192,9 @@ class CASClient
phpCAS::traceEnd();
return;
}
+ if(!$this->_start_session){
+ phpCAS::log("phpCAS can't handle logout requests if it does not manage the session.");
+ }
phpCAS::log("Logout requested");
phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']);
if ($check_client) {
@@ -1177,7 +1216,7 @@ class CASClient
}
if (!$allowed) {
phpCAS::error("Unauthorized logout request from client '".$client."'");
- printf("Unauthorized!");
+ printf("Unauthorized!");
phpCAS::traceExit();
exit();
}
@@ -1191,8 +1230,13 @@ class CASClient
phpCAS::log("Ticket to logout: ".$ticket2logout);
$session_id = preg_replace('/[^\w]/','',$ticket2logout);
phpCAS::log("Session id: ".$session_id);
-
- // fix New session ID
+
+ // destroy a possible application session created before phpcas
+ if(session_id()){
+ session_unset();
+ session_destroy();
+ }
+ // fix session ID
session_id($session_id);
$_COOKIE[session_name()]=$session_id;
$_GET[session_name()]=$session_id;
@@ -1200,8 +1244,8 @@ class CASClient
// Overwrite session
session_start();
session_unset();
- session_destroy();
- printf("Disconnected!");
+ session_destroy();
+ printf("Disconnected!");
phpCAS::traceExit();
exit();
}
@@ -1322,7 +1366,7 @@ class CASClient
* This method is used to validate a ST; halt on failure, and sets $validate_url,
* $text_reponse and $tree_response on success. These parameters are used later
* by CASClient::validatePGT() for CAS proxies.
- *
+ * Used for all CAS 1.0 validations
* @param $validate_url the URL of the request to the CAS server.
* @param $text_response the response of the CAS server, as is (XML text).
* @param $tree_response the response of the CAS server, as a DOM XML tree.
@@ -1338,7 +1382,7 @@ class CASClient
$validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
if ( $this->isProxy() ) {
// pass the callback url for CAS proxies
- $validate_url .= '&pgtUrl='.$this->getCallbackURL();
+ $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
}
// open and read the URL
@@ -1434,156 +1478,160 @@ class CASClient
}
break;
}
+ $this->renameSession($this->getST());
+ // at this step, ST has been validated and $this->_user has been set,
+ phpCAS::traceEnd(TRUE);
+ return TRUE;
+ }
+
+ // ########################################################################
+ // SAML VALIDATION
+ // ########################################################################
+ /**
+ * @addtogroup internalBasic
+ * @{
+ */
+
+ /**
+ * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
+ * $text_reponse and $tree_response on success. These parameters are used later
+ * by CASClient::validatePGT() for CAS proxies.
+ *
+ * @param $validate_url the URL of the request to the CAS server.
+ * @param $text_response the response of the CAS server, as is (XML text).
+ * @param $tree_response the response of the CAS server, as a DOM XML tree.
+ *
+ * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
+ *
+ * @private
+ */
+ function validateSA($validate_url,&$text_response,&$tree_response)
+ {
+ phpCAS::traceBegin();
+
+ // build the URL to validate the ticket
+ $validate_url = $this->getServerSamlValidateURL();
+
+ // open and read the URL
+ if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
+ phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
+ $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
+ }
+
+ phpCAS::trace('server version: '.$this->getServerVersion());
+ // analyze the result depending on the version
+ switch ($this->getServerVersion()) {
+ case SAML_VERSION_1_1:
+
+ // read the response of the CAS server into a DOM object
+ if ( !($dom = domxml_open_mem($text_response))) {
+ phpCAS::trace('domxml_open_mem() failed');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // read the root node of the XML tree
+ if ( !($tree_response = $dom->document_element()) ) {
+ phpCAS::trace('document_element() failed');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // insure that tag name is 'Envelope'
+ if ( $tree_response->node_name() != 'Envelope' ) {
+ phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // check for the NameIdentifier tag in the SAML response
+ if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
+ phpCAS::trace('NameIdentifier found');
+ $user = trim($success_elements[0]->get_content());
+ phpCAS::trace('user = `'.$user.'`');
+ $this->setUser($user);
+ $this->setSessionAttributes($text_response);
+ } else {
+ phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ break;
+ }
+ $this->renameSession($this->getSA());
// at this step, ST has been validated and $this->_user has been set,
phpCAS::traceEnd(TRUE);
return TRUE;
}
-
- // ########################################################################
- // SAML VALIDATION
- // ########################################################################
- /**
- * @addtogroup internalBasic
- * @{
- */
-
- /**
- * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
- * $text_reponse and $tree_response on success. These parameters are used later
- * by CASClient::validatePGT() for CAS proxies.
- *
- * @param $validate_url the URL of the request to the CAS server.
- * @param $text_response the response of the CAS server, as is (XML text).
- * @param $tree_response the response of the CAS server, as a DOM XML tree.
- *
- * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
- *
- * @private
- */
- function validateSA($validate_url,&$text_response,&$tree_response)
- {
- phpCAS::traceBegin();
-
- // build the URL to validate the ticket
- $validate_url = $this->getServerSamlValidateURL();
-
- // open and read the URL
- if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
- phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
- $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
- }
-
- phpCAS::trace('server version: '.$this->getServerVersion());
-
- // analyze the result depending on the version
- switch ($this->getServerVersion()) {
- case SAML_VERSION_1_1:
-
- // read the response of the CAS server into a DOM object
- if ( !($dom = domxml_open_mem($text_response))) {
- phpCAS::trace('domxml_open_mem() failed');
- $this->authError('SA not validated',
- $validate_url,
- FALSE/*$no_response*/,
- TRUE/*$bad_response*/,
- $text_response);
- }
- // read the root node of the XML tree
- if ( !($tree_response = $dom->document_element()) ) {
- phpCAS::trace('document_element() failed');
- $this->authError('SA not validated',
- $validate_url,
- FALSE/*$no_response*/,
- TRUE/*$bad_response*/,
- $text_response);
- }
- // insure that tag name is 'Envelope'
- if ( $tree_response->node_name() != 'Envelope' ) {
- phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
- $this->authError('SA not validated',
- $validate_url,
- FALSE/*$no_response*/,
- TRUE/*$bad_response*/,
- $text_response);
- }
- // check for the NameIdentifier tag in the SAML response
- if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
- phpCAS::trace('NameIdentifier found');
- $user = trim($success_elements[0]->get_content());
- phpCAS::trace('user = `'.$user.'`');
- $this->setUser($user);
- $this->setSessionAttributes($text_response);
- } else {
- phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
- $this->authError('SA not validated',
- $validate_url,
- FALSE/*$no_response*/,
- TRUE/*$bad_response*/,
- $text_response);
- }
- break;
- }
-
- // at this step, ST has been validated and $this->_user has been set,
- phpCAS::traceEnd(TRUE);
- return TRUE;
- }
-
- /**
- * This method will parse the DOM and pull out the attributes from the SAML
- * payload and put them into an array, then put the array into the session.
- *
- * @param $text_response the SAML payload.
- * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
- *
- * @private
- */
- function setSessionAttributes($text_response)
- {
- phpCAS::traceBegin();
-
- $result = FALSE;
-
- if (isset($_SESSION[SAML_ATTRIBUTES])) {
- phpCAS::trace("session attrs already set."); //testbml - do we care?
- }
-
- $attr_array = array();
-
- if (($dom = domxml_open_mem($text_response))) {
- $xPath = $dom->xpath_new_context();
- $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
- $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
- $nodelist = $xPath->xpath_eval("//saml:Attribute");
- $attrs = $nodelist->nodeset;
- phpCAS::trace($text_response);
- foreach($attrs as $attr){
- $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
- $name = $attr->get_attribute("AttributeName");
- $value_array = array();
- foreach($xres->nodeset as $node){
- $value_array[] = $node->get_content();
-
- }
- phpCAS::trace("* " . $name . "=" . $value_array);
- $attr_array[$name] = $value_array;
- }
- $_SESSION[SAML_ATTRIBUTES] = $attr_array;
- // UGent addition...
- foreach($attr_array as $attr_key => $attr_value) {
- if(count($attr_value) > 1) {
- $this->_attributes[$attr_key] = $attr_value;
- }
- else {
- $this->_attributes[$attr_key] = $attr_value[0];
- }
- }
- $result = TRUE;
- }
- phpCAS::traceEnd($result);
- return $result;
- }
+
+ /**
+ * This method will parse the DOM and pull out the attributes from the SAML
+ * payload and put them into an array, then put the array into the session.
+ *
+ * @param $text_response the SAML payload.
+ * @return bool TRUE when successfull and FALSE if no attributes a found
+ *
+ * @private
+ */
+ function setSessionAttributes($text_response)
+ {
+ phpCAS::traceBegin();
+
+ $result = FALSE;
+
+ if (isset($_SESSION[SAML_ATTRIBUTES])) {
+ phpCAS::trace("session attrs already set."); //testbml - do we care?
+ }
+
+ $attr_array = array();
+
+ if (($dom = domxml_open_mem($text_response))) {
+ $xPath = $dom->xpath_new_context();
+ $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
+ $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
+ $nodelist = $xPath->xpath_eval("//saml:Attribute");
+ if($nodelist){
+ $attrs = $nodelist->nodeset;
+ foreach($attrs as $attr){
+ $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
+ $name = $attr->get_attribute("AttributeName");
+ $value_array = array();
+ foreach($xres->nodeset as $node){
+ $value_array[] = $node->get_content();
+ }
+ $attr_array[$name] = $value_array;
+ }
+ $_SESSION[SAML_ATTRIBUTES] = $attr_array;
+ // UGent addition...
+ foreach($attr_array as $attr_key => $attr_value) {
+ if(count($attr_value) > 1) {
+ $this->_attributes[$attr_key] = $attr_value;
+ phpCAS::trace("* " . $attr_key . "=" . $attr_value);
+ }
+ else {
+ $this->_attributes[$attr_key] = $attr_value[0];
+ phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
+ }
+ }
+ $result = TRUE;
+ }else{
+ phpCAS::trace("SAML Attributes are empty");
+ $result = FALSE;
+ }
+ }
+ phpCAS::traceEnd($result);
+ return $result;
+ }
/** @} */
@@ -2118,7 +2166,7 @@ class CASClient
curl_setopt($ch, $key, $value);
}
}
-
+
if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
}
@@ -2150,21 +2198,21 @@ class CASClient
if ( is_array($cookies) ) {
curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
}
- // add extra stuff if SAML
- if ($this->hasSA()) {
- $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
- "cache-control: no-cache",
- "pragma: no-cache",
- "accept: text/xml",
- "connection: keep-alive",
- "content-type: text/xml");
-
- curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
- curl_setopt($ch, CURLOPT_POST, 1);
- $data = $this->buildSAMLPayload();
- //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
- curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
- }
+ // add extra stuff if SAML
+ if ($this->hasSA()) {
+ $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
+ "cache-control: no-cache",
+ "pragma: no-cache",
+ "accept: text/xml",
+ "connection: keep-alive",
+ "content-type: text/xml");
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ $data = $this->buildSAMLPayload();
+ //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ }
// perform the query
$buf = curl_exec ($ch);
//phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
@@ -2185,39 +2233,39 @@ class CASClient
phpCAS::traceEnd($res);
return $res;
- }
-
- /**
- * This method is used to build the SAML POST body sent to /samlValidate URL.
- *
- * @return the SOAP-encased SAMLP artifact (the ticket).
- *
- * @private
- */
- function buildSAMLPayload()
- {
- phpCAS::traceBegin();
-
- //get the ticket
- $sa = $this->getSA();
- //phpCAS::trace("SA: ".$sa);
-
- $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
-
- phpCAS::traceEnd($body);
- return ($body);
- }
-
+ }
+
+ /**
+ * This method is used to build the SAML POST body sent to /samlValidate URL.
+ *
+ * @return the SOAP-encased SAMLP artifact (the ticket).
+ *
+ * @private
+ */
+ function buildSAMLPayload()
+ {
+ phpCAS::traceBegin();
+
+ //get the ticket
+ $sa = $this->getSA();
+ //phpCAS::trace("SA: ".$sa);
+
+ $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
+
+ phpCAS::traceEnd($body);
+ return ($body);
+ }
+
/**
* This method is the callback used by readURL method to request HTTP headers.
*/
var $_curl_headers = array();
function _curl_read_headers($ch, $header)
- {
+ {
$this->_curl_headers[] = $header;
return strlen($header);
- }
-
+ }
+
/**
* This method is used to access an HTTP[S] service.
*
@@ -2236,6 +2284,7 @@ class CASClient
function serviceWeb($url,&$err_code,&$output)
{
phpCAS::traceBegin();
+ $cookies = array();
// at first retrieve a PT
$pt = $this->retrievePT($url,$err_code,$output);
@@ -2248,7 +2297,8 @@ class CASClient
$res = FALSE;
} else {
// add cookies if necessary
- if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
+ if ( isset($_SESSION['phpCAS']['services'][$url]['cookies']) &&
+ is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
$cookies[] = $name.'='.$val;
}
@@ -2400,29 +2450,29 @@ class CASClient
function hasPT()
{ return !empty($this->_pt); }
/**
- * This method returns the SAML Ticket provided in the URL of the request.
- * @return The SAML ticket.
- * @private
- */
- function getSA()
- { return 'ST'.substr($this->_sa, 2); }
-
- /**
- * This method stores the SAML Ticket.
- * @param $sa The SAML Ticket.
- * @private
- */
- function setSA($sa)
- { $this->_sa = $sa; }
-
- /**
- * This method tells if a SAML Ticket was stored.
- * @return TRUE if a SAML Ticket has been stored.
- * @private
- */
- function hasSA()
- { return !empty($this->_sa); }
-
+ * This method returns the SAML Ticket provided in the URL of the request.
+ * @return The SAML ticket.
+ * @private
+ */
+ function getSA()
+ { return 'ST'.substr($this->_sa, 2); }
+
+ /**
+ * This method stores the SAML Ticket.
+ * @param $sa The SAML Ticket.
+ * @private
+ */
+ function setSA($sa)
+ { $this->_sa = $sa; }
+
+ /**
+ * This method tells if a SAML Ticket was stored.
+ * @return TRUE if a SAML Ticket has been stored.
+ * @private
+ */
+ function hasSA()
+ { return !empty($this->_sa); }
+
/** @} */
// ########################################################################
// PT VALIDATION
@@ -2433,8 +2483,8 @@ class CASClient
*/
/**
- * This method is used to validate a PT; halt on failure
- *
+ * This method is used to validate a ST or PT; halt on failure
+ * Used for all CAS 2.0 validations
* @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
*
* @private
@@ -2447,7 +2497,7 @@ class CASClient
if ( $this->isProxy() ) {
// pass the callback url for CAS proxies
- $validate_url .= '&pgtUrl='.$this->getCallbackURL();
+ $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
}
// open and read the URL
@@ -2514,6 +2564,7 @@ class CASClient
$text_response);
}
+ $this->renameSession($this->getPT());
// at this step, PT has been validated and $this->_user has been set,
phpCAS::traceEnd(TRUE);
@@ -2586,25 +2637,43 @@ class CASClient
}
}
- $php_is_for_sissies = split("\?", $_SERVER['REQUEST_URI'], 2);
- $final_uri .= $php_is_for_sissies[0];
- if(sizeof($php_is_for_sissies) > 1){
- $cgi_params = '?' . $php_is_for_sissies[1];
- } else {
- $cgi_params = '?';
+ $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $final_uri .= $request_uri[0];
+
+ if (isset($request_uri[1]) && $request_uri[1])
+ {
+ $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
+
+ // If the query string still has anything left, append it to the final URI
+ if ($query_string !== '')
+ $final_uri .= "?$query_string";
+
}
- // remove the ticket if present in the CGI parameters
- $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params);
- $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params);
- $cgi_params = preg_replace('/\?%26/','?',$cgi_params);
- $cgi_params = preg_replace('/\?&/','?',$cgi_params);
- $cgi_params = preg_replace('/\?$/','',$cgi_params);
- $final_uri .= $cgi_params;
+
+ phpCAS::trace("Final URI: $final_uri");
$this->setURL($final_uri);
}
phpCAS::traceEnd($this->_url);
return $this->_url;
- }
+ }
+
+
+
+ /**
+ * Removes a parameter from a query string
+ *
+ * @param string $parameterName
+ * @param string $queryString
+ * @return string
+ *
+ * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
+ */
+ function removeParameterFromQueryString($parameterName, $queryString)
+ {
+ $parameterName = preg_quote($parameterName);
+ return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
+ }
+
/**
* This method sets the URL of the current request
@@ -2641,7 +2710,7 @@ class CASClient
phpCAS::traceBegin();
$this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
- printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']);
+ printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
phpCAS::trace('CAS URL: '.$cas_url);
phpCAS::trace('Authentication failure: '.$failure);
if ( $no_response ) {
diff --git a/plugins/ClientSideShorten/ClientSideShortenPlugin.php b/plugins/ClientSideShorten/ClientSideShortenPlugin.php
index 27a3a56f7..65e27a037 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<words.length;i++)
+ if(words[i].length > 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',
diff --git a/plugins/Geonames/GeonamesPlugin.php b/plugins/Geonames/GeonamesPlugin.php
index 310641ce6..d88014bb8 100644
--- a/plugins/Geonames/GeonamesPlugin.php
+++ b/plugins/Geonames/GeonamesPlugin.php
@@ -377,7 +377,7 @@ class GeonamesPlugin extends Plugin
function getCache($attrs)
{
- $c = common_memcache();
+ $c = Cache::instance();
if (empty($c)) {
return null;
@@ -392,7 +392,7 @@ class GeonamesPlugin extends Plugin
function setCache($attrs, $loc)
{
- $c = common_memcache();
+ $c = Cache::instance();
if (empty($c)) {
return null;
@@ -409,11 +409,11 @@ class GeonamesPlugin extends Plugin
{
$key = 'geonames:' .
implode(',', array_keys($attrs)) . ':'.
- common_keyize(implode(',', array_values($attrs)));
+ Cache::keyize(implode(',', array_values($attrs)));
if ($this->cachePrefix) {
return $this->cachePrefix . ':' . $key;
} else {
- return common_cache_key($key);
+ return Cache::key($key);
}
}
diff --git a/plugins/Imap/imapmanager.php b/plugins/Imap/imapmanager.php
index a9e531e1a..2d27ac97a 100644
--- a/plugins/Imap/imapmanager.php
+++ b/plugins/Imap/imapmanager.php
@@ -63,12 +63,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/Irc/ChannelResponseChannel.php b/plugins/Irc/ChannelResponseChannel.php
new file mode 100644
index 000000000..9d50b914a
--- /dev/null
+++ b/plugins/Irc/ChannelResponseChannel.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Extend the IMChannel class to allow commands to send messages
+ * to a channel instead of PMing a user
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Network
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @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 ChannelResponseChannel extends IMChannel {
+ protected $ircChannel;
+
+ /**
+ * Construct a ChannelResponseChannel
+ *
+ * @param IMplugin $imPlugin IMPlugin
+ * @param string $ircChannel IRC Channel to reply to
+ * @return ChannelResponseChannel
+ */
+ public function __construct($imPlugin, $ircChannel) {
+ $this->ircChannel = $ircChannel;
+ parent::__construct($imPlugin);
+ }
+
+ /**
+ * Send a message using the plugin
+ *
+ * @param User $user User
+ * @param string $text Message text
+ * @return void
+ */
+ public function output($user, $text) {
+ $text = $user->nickname.': ['.common_config('site', 'name') . '] ' . $text;
+ $this->imPlugin->sendMessage($this->ircChannel, $text);
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/Fake_Irc.php b/plugins/Irc/Fake_Irc.php
new file mode 100644
index 000000000..d95ab0491
--- /dev/null
+++ b/plugins/Irc/Fake_Irc.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Instead of sending IRC messages, retrieve the raw data that would be sent
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Network
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @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_Irc extends Phergie_Driver_Streams {
+ public $would_be_sent = null;
+
+ /**
+ * Store the components for sending a command
+ *
+ * @param string $command Command
+ * @param array $args Arguments
+ * @return void
+ */
+ protected function send($command, $args = '') {
+ $this->would_be_sent = array('command' => $command, 'args' => $args);
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/IrcPlugin.php b/plugins/Irc/IrcPlugin.php
new file mode 100644
index 000000000..7a53e5cbf
--- /dev/null
+++ b/plugins/Irc/IrcPlugin.php
@@ -0,0 +1,396 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Send and receive notices using an IRC network
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category IM
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 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 Phergie library...
+set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phergie');
+
+/**
+ * Plugin for IRC
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class IrcPlugin extends ImPlugin {
+ public $host = null;
+ public $port = null;
+ public $username = null;
+ public $realname = null;
+ public $nick = null;
+ public $password = null;
+ public $nickservidentifyregexp = null;
+ public $nickservpassword = null;
+ public $channels = null;
+ public $transporttype = null;
+ public $encoding = null;
+ public $pinginterval = null;
+
+ public $regcheck = null;
+ public $unregregexp = null;
+ public $regregexp = null;
+
+ public $transport = 'irc';
+ protected $whiteList;
+ protected $fake_irc;
+
+ /**
+ * Get the internationalized/translated display name of this IM service
+ *
+ * @return string Name of service
+ */
+ public function getDisplayName() {
+ return _m('IRC');
+ }
+
+ /**
+ * Normalize a screenname for comparison
+ *
+ * @param string $screenname Screenname to normalize
+ * @return string An equivalent screenname in normalized form
+ */
+ public function normalize($screenname) {
+ $screenname = str_replace(" ","", $screenname);
+ return strtolower($screenname);
+ }
+
+ /**
+ * Get the screenname of the daemon that sends and receives messages
+ *
+ * @return string Screenname
+ */
+ public function daemonScreenname() {
+ return $this->nick;
+ }
+
+ /**
+ * Validate (ensure the validity of) a screenname
+ *
+ * @param string $screenname Screenname to validate
+ * @return boolean true if screenname is valid
+ */
+ public function validate($screenname) {
+ if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/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.
+ */
+ public function onAutoload($cls) {
+ $dir = dirname(__FILE__);
+
+ switch ($cls) {
+ case 'IrcManager':
+ include_once $dir . '/'.strtolower($cls).'.php';
+ return false;
+ case 'Fake_Irc':
+ case 'Irc_waiting_message':
+ case 'ChannelResponseChannel':
+ include_once $dir . '/'. $cls .'.php';
+ return false;
+ default:
+ if (substr($cls, 0, 7) == 'Phergie') {
+ include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /*
+ * Start manager on daemon start
+ *
+ * @param array &$versions Array to insert manager into
+ * @return boolean
+ */
+ public function onStartImDaemonIoManagers(&$classes) {
+ parent::onStartImDaemonIoManagers(&$classes);
+ $classes[] = new IrcManager($this); // handles sending/receiving
+ return true;
+ }
+
+ /**
+ * Ensure the database table is present
+ *
+ */
+ public function onCheckSchema() {
+ $schema = Schema::get();
+
+ // For storing messages while sessions become ready
+ $schema->ensureTable('irc_waiting_message',
+ array(new ColumnDef('id', 'integer', null,
+ false, 'PRI', null, null, true),
+ new ColumnDef('data', 'blob', null, false),
+ new ColumnDef('prioritise', 'tinyint', 1, false),
+ new ColumnDef('attempts', 'integer', null, false),
+ new ColumnDef('created', 'datetime', null, false),
+ new ColumnDef('claimed', 'datetime')));
+
+ return true;
+ }
+
+ /**
+ * Get a microid URI for the given screenname
+ *
+ * @param string $screenname Screenname
+ * @return string microid URI
+ */
+ public function microiduri($screenname) {
+ return 'irc:' . $screenname;
+ }
+
+ /**
+ * Send a message to a given screenname
+ *
+ * @param string $screenname Screenname to send to
+ * @param string $body Text to send
+ * @return boolean true on success
+ */
+ public function sendMessage($screenname, $body) {
+ $lines = explode("\n", $body);
+ foreach ($lines as $line) {
+ $this->fake_irc->doPrivmsg($screenname, $line);
+ $this->enqueueOutgoingRaw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
+ }
+ return true;
+ }
+
+ /**
+ * Accept a queued input message.
+ *
+ * @return boolean true if processing completed, false if message should be reprocessed
+ */
+ public function receiveRawMessage($data) {
+ if (strpos($data['source'], '#') === 0) {
+ $message = $data['message'];
+ $parts = explode(' ', $message, 2);
+ $command = $parts[0];
+ if (in_array($command, $this->whiteList)) {
+ $this->handle_channel_incoming($data['sender'], $data['source'], $message);
+ } else {
+ $this->handleIncoming($data['sender'], $message);
+ }
+ } else {
+ $this->handleIncoming($data['sender'], $data['message']);
+ }
+ return true;
+ }
+
+ /**
+ * Helper for handling incoming messages from a channel requiring response
+ * to the channel instead of via PM
+ *
+ * @param string $nick Screenname the message was sent from
+ * @param string $channel Channel the message originated from
+ * @param string $message Message text
+ * @param boolean true on success
+ */
+ protected function handle_channel_incoming($nick, $channel, $notice_text) {
+ $user = $this->getUser($nick);
+ // For common_current_user to work
+ global $_cur;
+ $_cur = $user;
+
+ if (!$user) {
+ $this->sendFromSite($nick, 'Unknown user; go to ' .
+ common_local_url('imsettings') .
+ ' to add your address to your account');
+ common_log(LOG_WARNING, 'Message from unknown user ' . $nick);
+ return;
+ }
+ if ($this->handle_channel_command($user, $channel, $notice_text)) {
+ common_log(LOG_INFO, "Command message by $nick handled.");
+ return;
+ } else if ($this->isAutoreply($notice_text)) {
+ common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
+ return;
+ } else if ($this->isOtr($notice_text)) {
+ common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
+ return;
+ } else {
+ common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
+ $this->addNotice($nick, $user, $notice_text);
+ }
+
+ $user->free();
+ unset($user);
+ unset($_cur);
+ unset($message);
+ }
+
+ /**
+ * Attempt to handle a message from a channel as a command
+ *
+ * @param User $user User the message is from
+ * @param string $channel Channel the message originated 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_channel_command($user, $channel, $body) {
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $body);
+ if ($cmd) {
+ $chan = new ChannelResponseChannel($this, $channel);
+ $cmd->execute($chan);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 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
+ */
+ public function sendConfirmationCode($screenname, $code, $user, $checked = false) {
+ $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)));
+
+ if ($this->regcheck && !$checked) {
+ return $this->checked_sendConfirmationCode($screenname, $code, $user);
+ } else {
+ return $this->sendMessage($screenname, $body);
+ }
+ }
+
+ /**
+ * Only sends the confirmation message if the nick is
+ * registered
+ *
+ * @param string $screenname Screenname sending to
+ * @param string $code The confirmation code
+ * @param User $user User sending to
+ * @return boolean true on succes
+ */
+ public function checked_sendConfirmationCode($screenname, $code, $user) {
+ $this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
+ $this->enqueueOutgoingRaw(
+ array(
+ 'type' => 'nickcheck',
+ 'prioritise' => 1,
+ 'data' => $this->fake_irc->would_be_sent,
+ 'nickdata' =>
+ array(
+ 'screenname' => $screenname,
+ 'code' => $code,
+ 'user' => $user
+ )
+ )
+ );
+ return true;
+ }
+
+ /**
+ * Initialize plugin
+ *
+ * @return boolean
+ */
+ public function initialize() {
+ if (!isset($this->host)) {
+ throw new Exception('must specify a host');
+ }
+ if (!isset($this->username)) {
+ throw new Exception('must specify a username');
+ }
+ if (!isset($this->realname)) {
+ throw new Exception('must specify a "real name"');
+ }
+ if (!isset($this->nick)) {
+ throw new Exception('must specify a nickname');
+ }
+
+ if (!isset($this->port)) {
+ $this->port = 6667;
+ }
+ if (!isset($this->transporttype)) {
+ $this->transporttype = 'tcp';
+ }
+ if (!isset($this->encoding)) {
+ $this->encoding = 'UTF-8';
+ }
+ if (!isset($this->pinginterval)) {
+ $this->pinginterval = 120;
+ }
+
+ if (!isset($this->regcheck)) {
+ $this->regcheck = true;
+ }
+
+ $this->fake_irc = new Fake_Irc;
+
+ /*
+ * Commands allowed to return output to a channel
+ */
+ $this->whiteList = array('stats', 'last', 'get');
+
+ return true;
+ }
+
+ /**
+ * Get plugin information
+ *
+ * @param array $versions Array to insert information into
+ * @return void
+ */
+ public function onPluginVersion(&$versions) {
+ $versions[] = array('name' => 'IRC',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Luke Fitzgerald',
+ 'homepage' => 'http://status.net/wiki/Plugin:IRC',
+ 'rawdescription' =>
+ _m('The IRC plugin allows users to send and receive notices over an IRC network.'));
+ return true;
+ }
+}
diff --git a/plugins/Irc/Irc_waiting_message.php b/plugins/Irc/Irc_waiting_message.php
new file mode 100644
index 000000000..59eec63d8
--- /dev/null
+++ b/plugins/Irc/Irc_waiting_message.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Table Definition for irc_waiting_message
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Irc_waiting_message extends Memcached_DataObject {
+
+ public $__table = 'irc_waiting_message'; // table name
+ public $id; // int primary_key not_null auto_increment
+ public $data; // blob not_null
+ public $prioritise; // tinyint(1) not_null
+ public $attempts; // int not_null
+ public $created; // datetime() not_null
+ public $claimed; // datetime()
+
+ /* Static get */
+ public function staticGet($k, $v = null) {
+ return Memcached_DataObject::staticGet('Irc_waiting_message', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+ public function table() {
+ return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'data' => DB_DATAOBJECT_BLOB + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'prioritise' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'claimed' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has, since it
+ * won't appear in StatusNet's own keys list. In most cases, this will
+ * simply reference your keyTypes() function.
+ *
+ * @return array list of key field names
+ */
+ public function keys() {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+ public function keyTypes() {
+ return array('id' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+ public function sequenceKey() {
+ return array(false, false, false);
+ }
+
+ /**
+ * Get the next item in the queue
+ *
+ * @return Irc_waiting_message Next message if there is one
+ */
+ public static function top() {
+ $wm = new Irc_waiting_message();
+
+ $wm->orderBy('prioritise DESC, created');
+ $wm->whereAdd('claimed is null');
+
+ $wm->limit(1);
+
+ $cnt = $wm->find(true);
+
+ if ($cnt) {
+ # XXX: potential race condition
+ # can we force it to only update if claimed is still null
+ # (or old)?
+ common_log(LOG_INFO, 'claiming IRC waiting message id = ' . $wm->id);
+ $orig = clone($wm);
+ $wm->claimed = common_sql_now();
+ $result = $wm->update($orig);
+ if ($result) {
+ common_log(LOG_INFO, 'claim succeeded.');
+ return $wm;
+ } else {
+ common_log(LOG_INFO, 'claim failed.');
+ }
+ }
+ $wm = null;
+ return null;
+ }
+
+ /**
+ * Increment the attempts count
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function incAttempts() {
+ $orig = clone($this);
+ $this->attempts++;
+ $result = $this->update($orig);
+
+ if (!$result) {
+ throw Exception(sprintf(_m("Could not increment attempts count for %d"), $this->id));
+ }
+ }
+
+ /**
+ * Release a claimed item.
+ */
+ public function releaseClaim() {
+ // DB_DataObject doesn't let us save nulls right now
+ $sql = sprintf("UPDATE irc_waiting_message SET claimed=NULL WHERE id=%d", $this->id);
+ $this->query($sql);
+
+ $this->claimed = null;
+ $this->encache();
+ }
+}
diff --git a/plugins/Irc/README b/plugins/Irc/README
new file mode 100644
index 000000000..0a5d9ea83
--- /dev/null
+++ b/plugins/Irc/README
@@ -0,0 +1,45 @@
+The IRC plugin allows users to send and receive notices over an IRC network.
+
+Installation
+============
+add "addPlugin('irc',
+ array('setting'=>'value', 'setting2'=>'value2', ...);"
+to the bottom of your config.php
+
+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.
+
+Settings
+========
+host*: Hostname of IRC server
+port: Port of IRC server (defaults to 6667)
+username*: Username of bot
+realname*: Real name of bot
+nick*: Nickname of bot
+password: Password
+nickservpassword: NickServ password for identification
+nickservidentifyregexp: Override existing regexp matching request for identification from NickServ
+channels: Channels for bot to idle in
+transporttype: Set to 'ssl' to enable SSL
+encoding: Set to change encoding
+pinginterval: Set to change the number of seconds between pings (helps keep the connection open)
+ Defaults to 120 seconds
+regcheck: Check user's nicknames are registered, enabled by default, set to false to disable
+regregexp: Override existing regexp matching response from NickServ if nick checked is registered.
+ Must contain a capturing group catching the nick
+unregregexp: Override existing regexp matching response from NickServ if nick checked is unregistered
+ Must contain a capturing group catching the nick
+
+* required
+
+Example
+=======
+addPlugin('irc', array(
+ 'host' => '...',
+ 'username' => '...',
+ 'realname' => '...',
+ 'nick' => '...',
+ 'channels' => array('#channel1', '#channel2')
+));
+
diff --git a/plugins/Irc/extlib/.gitignore b/plugins/Irc/extlib/.gitignore
new file mode 100644
index 000000000..553fe8e25
--- /dev/null
+++ b/plugins/Irc/extlib/.gitignore
@@ -0,0 +1,2 @@
+Settings.php
+*.db
diff --git a/plugins/Irc/extlib/phergie/.gitignore b/plugins/Irc/extlib/phergie/.gitignore
new file mode 100644
index 000000000..553fe8e25
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/.gitignore
@@ -0,0 +1,2 @@
+Settings.php
+*.db
diff --git a/plugins/Irc/extlib/phergie/LICENSE b/plugins/Irc/extlib/phergie/LICENSE
new file mode 100644
index 000000000..d7d23420a
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2010, Phergie Development Team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+Neither the name of the Phergie Development Team nor the names of its
+contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/plugins/Irc/extlib/phergie/Phergie/Autoload.php b/plugins/Irc/extlib/phergie/Phergie/Autoload.php
new file mode 100755
index 000000000..0004f44e2
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Autoload.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Autoloader for Phergie classes.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Autoload
+{
+ /**
+ * Constructor to add the base Phergie path to the include_path.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $path = realpath(dirname(__FILE__) . '/..');
+ $includePath = get_include_path();
+ $includePathList = explode(PATH_SEPARATOR, $includePath);
+ if (!in_array($path, $includePathList)) {
+ self::addPath($path);
+ }
+ }
+
+ /**
+ * Autoload callback for loading class files.
+ *
+ * @param string $class Class to load
+ *
+ * @return void
+ */
+ public function load($class)
+ {
+ include str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+ }
+
+ /**
+ * Registers an instance of this class as an autoloader.
+ *
+ * @return void
+ */
+ public static function registerAutoloader()
+ {
+ spl_autoload_register(array(new self, 'load'));
+ }
+
+ /**
+ * Add a path to the include path.
+ *
+ * @param string $path Path to add
+ *
+ * @return void
+ */
+ public static function addPath($path)
+ {
+ set_include_path($path . PATH_SEPARATOR . get_include_path());
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Bot.php b/plugins/Irc/extlib/phergie/Phergie/Bot.php
new file mode 100755
index 000000000..85e8a00fc
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Bot.php
@@ -0,0 +1,390 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Composite class for other components to represent the bot.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Bot
+{
+ /**
+ * Current version of Phergie
+ */
+ const VERSION = '2.0.1';
+
+ /**
+ * Current driver instance
+ *
+ * @var Phergie_Driver_Abstract
+ */
+ protected $driver;
+
+ /**
+ * Current configuration instance
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Current connection handler instance
+ *
+ * @var Phergie_Connection_Handler
+ */
+ protected $connections;
+
+ /**
+ * Current plugin handler instance
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Current event handler instance
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Current end-user interface instance
+ *
+ * @var Phergie_Ui_Abstract
+ */
+ protected $ui;
+
+ /**
+ * Current processor instance
+ *
+ * @var Phergie_Process_Abstract
+ */
+ protected $processor;
+
+ /**
+ * Returns a driver instance, creating one of the default class if
+ * none has been set.
+ *
+ * @return Phergie_Driver_Abstract
+ */
+ public function getDriver()
+ {
+ if (empty($this->driver)) {
+ // Check if a driver has been defined in the configuration to use
+ // as the default
+ $config = $this->getConfig();
+ if (isset($config['driver'])) {
+ $class = 'Phergie_Driver_' . ucfirst($config['driver']);
+ } else {
+ // Otherwise default to the Streams driver.
+ $class = 'Phergie_Driver_Streams';
+ }
+
+ $this->driver = new $class;
+ }
+ return $this->driver;
+ }
+
+ /**
+ * Sets the driver instance to use.
+ *
+ * @param Phergie_Driver_Abstract $driver Driver instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setDriver(Phergie_Driver_Abstract $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * Sets the configuration to use.
+ *
+ * @param Phergie_Config $config Configuration instance
+ *
+ * @return Phergie_Runner_Abstract Provides a fluent interface
+ */
+ public function setConfig(Phergie_Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Returns the entire configuration in use or the value of a specific
+ * configuration setting.
+ *
+ * @param string $index Optional index of a specific configuration
+ * setting for which the corresponding value should be returned
+ * @param mixed $default Value to return if no match is found for $index
+ *
+ * @return mixed Value corresponding to $index or the entire
+ * configuration if $index is not specified
+ */
+ public function getConfig($index = null, $default = null)
+ {
+ if (empty($this->config)) {
+ $this->config = new Phergie_Config;
+ $this->config->read('Settings.php');
+ }
+ if ($index !== null) {
+ if (isset($this->config[$index])) {
+ return $this->config[$index];
+ } else {
+ return $default;
+ }
+ }
+ return $this->config;
+ }
+
+ /**
+ * Returns a plugin handler instance, creating it if it does not already
+ * exist and using a default class if none has been set.
+ *
+ * @return Phergie_Plugin_Handler
+ */
+ public function getPluginHandler()
+ {
+ if (empty($this->plugins)) {
+ $this->plugins = new Phergie_Plugin_Handler(
+ $this->getConfig(),
+ $this->getEventHandler()
+ );
+ }
+ return $this->plugins;
+ }
+
+ /**
+ * Sets the plugin handler instance to use.
+ *
+ * @param Phergie_Plugin_Handler $handler Plugin handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setPluginHandler(Phergie_Plugin_Handler $handler)
+ {
+ $this->plugins = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns an event handler instance, creating it if it does not already
+ * exist and using a default class if none has been set.
+ *
+ * @return Phergie_Event_Handler
+ */
+ public function getEventHandler()
+ {
+ if (empty($this->events)) {
+ $this->events = new Phergie_Event_Handler;
+ }
+ return $this->events;
+ }
+
+ /**
+ * Sets the event handler instance to use.
+ *
+ * @param Phergie_Event_Handler $handler Event handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setEventHandler(Phergie_Event_Handler $handler)
+ {
+ $this->events = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns a connection handler instance, creating it if it does not
+ * already exist and using a default class if none has been set.
+ *
+ * @return Phergie_Connection_Handler
+ */
+ public function getConnectionHandler()
+ {
+ if (empty($this->connections)) {
+ $this->connections = new Phergie_Connection_Handler;
+ }
+ return $this->connections;
+ }
+
+ /**
+ * Sets the connection handler instance to use.
+ *
+ * @param Phergie_Connection_Handler $handler Connection handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setConnectionHandler(Phergie_Connection_Handler $handler)
+ {
+ $this->connections = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns an end-user interface instance, creating it if it does not
+ * already exist and using a default class if none has been set.
+ *
+ * @return Phergie_Ui_Abstract
+ */
+ public function getUi()
+ {
+ if (empty($this->ui)) {
+ $this->ui = new Phergie_Ui_Console;
+ }
+ return $this->ui;
+ }
+
+ /**
+ * Sets the end-user interface instance to use.
+ *
+ * @param Phergie_Ui_Abstract $ui End-user interface instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setUi(Phergie_Ui_Abstract $ui)
+ {
+ $this->ui = $ui;
+ return $this;
+ }
+
+ /**
+ * Returns a processer instance, creating one if none exists.
+ *
+ * @return Phergie_Process_Abstract
+ */
+ public function getProcessor()
+ {
+ if (empty($this->processor)) {
+ $class = 'Phergie_Process_Standard';
+
+ $type = $this->getConfig('processor');
+ if (!empty($type)) {
+ $class = 'Phergie_Process_' . ucfirst($type);
+ }
+
+ $this->processor = new $class(
+ $this,
+ $this->getConfig('processor.options', array())
+ );
+ }
+ return $this->processor;
+ }
+
+ /**
+ * Sets the processer instance to use.
+ *
+ * @param Phergie_Process_Abstract $processor Processer instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setProcessor(Phergie_Process_Abstract $processor)
+ {
+ $this->processor = $processor;
+ return $this;
+ }
+
+ /**
+ * Loads plugins into the plugin handler.
+ *
+ * @return void
+ */
+ protected function loadPlugins()
+ {
+ $config = $this->getConfig();
+ $plugins = $this->getPluginHandler();
+ $ui = $this->getUi();
+
+ $plugins->setAutoload($config['plugins.autoload']);
+ foreach ($config['plugins'] as $name) {
+ try {
+ $plugin = $plugins->addPlugin($name);
+ $ui->onPluginLoad($name);
+ } catch (Phergie_Plugin_Exception $e) {
+ $ui->onPluginFailure($name, $e->getMessage());
+ if (!empty($plugin)) {
+ $plugins->removePlugin($plugin);
+ }
+ }
+ }
+ }
+
+ /**
+ * Configures and establishes connections to IRC servers.
+ *
+ * @return void
+ */
+ protected function loadConnections()
+ {
+ $config = $this->getConfig();
+ $driver = $this->getDriver();
+ $connections = $this->getConnectionHandler();
+ $plugins = $this->getPluginHandler();
+ $ui = $this->getUi();
+
+ foreach ($config['connections'] as $data) {
+ $connection = new Phergie_Connection($data);
+ $connections->addConnection($connection);
+
+ $ui->onConnect($data['host']);
+ $driver->setConnection($connection)->doConnect();
+ $plugins->setConnection($connection);
+ $plugins->onConnect();
+ }
+ }
+
+ /**
+ * Establishes server connections and initiates an execution loop to
+ * continuously receive and process events.
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function run()
+ {
+ set_time_limit(0);
+
+ $timezone = $this->getConfig('timezone', 'UTC');
+ date_default_timezone_set($timezone);
+
+ $ui = $this->getUi();
+ $ui->setEnabled($this->getConfig('ui.enabled'));
+
+ $this->loadPlugins();
+ $this->loadConnections();
+
+ $processor = $this->getProcessor();
+
+ $connections = $this->getConnectionHandler();
+ while (count($connections)) {
+ $processor->handleEvents();
+ }
+
+ $ui->onShutdown();
+
+ return $this;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Config.php b/plugins/Irc/extlib/phergie/Phergie/Config.php
new file mode 100755
index 000000000..c182f2ac1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Config.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Reads from and writes to PHP configuration files and provides access to
+ * the settings they contain.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Config implements ArrayAccess
+{
+ /**
+ * Mapping of configuration file paths to an array of names of settings
+ * they contain
+ *
+ * @var array
+ */
+ protected $files = array();
+
+ /**
+ * Mapping of setting names to their current corresponding values
+ *
+ * @var array
+ */
+ protected $settings = array();
+
+ /**
+ * Includes a specified PHP configuration file and incorporates its
+ * return value (which should be an associative array) into the current
+ * configuration settings.
+ *
+ * @param string $file Path to the file to read
+ *
+ * @return Phergie_Config Provides a fluent interface
+ * @throws Phergie_Config_Exception
+ */
+ public function read($file)
+ {
+ if (!(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
+ && file_exists($file))
+ && !is_executable($file)
+ ) {
+ throw new Phergie_Config_Exception(
+ 'Path "' . $file . '" does not reference an executable file',
+ Phergie_Config_Exception::ERR_FILE_NOT_EXECUTABLE
+ );
+ }
+
+ $settings = include $file;
+ if (!is_array($settings)) {
+ throw new Phergie_Config_Exception(
+ 'File "' . $file . '" does not return an array',
+ Phergie_Config_Exception::ERR_ARRAY_NOT_RETURNED
+ );
+ }
+
+ $this->files[$file] = array_keys($settings);
+ $this->settings += $settings;
+
+ return $this;
+ }
+
+ /**
+ * Merges an associative array of configuration setting values into the
+ * current configuration settings.
+ *
+ * @param array $settings Associative array of configuration setting
+ * values keyed by setting name
+ *
+ * @return Phergie_Config Provides a fluent interface
+ */
+ public function readArray(array $settings)
+ {
+ $this->settings += $settings;
+
+ return $this;
+ }
+
+ /**
+ * Writes the values of the current configuration settings back to their
+ * originating files.
+ *
+ * @return Phergie_Config Provides a fluent interface
+ */
+ public function write()
+ {
+ foreach ($this->files as $file => &$settings) {
+ $values = array();
+ foreach ($settings as $setting) {
+ $values[$setting] = $this->settings[$setting];
+ }
+ $source = '<?php' . PHP_EOL . PHP_EOL .
+ 'return ' . var_export($value, true) . ';';
+ file_put_contents($file, $source);
+ }
+ }
+
+ /**
+ * Checks to see if a configuration setting is assigned a value.
+ *
+ * @param string $offset Configuration setting name
+ *
+ * @return bool TRUE if the setting has a value, FALSE otherwise
+ * @see ArrayAccess::offsetExists()
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->settings[$offset]);
+ }
+
+ /**
+ * Returns the value of a configuration setting.
+ *
+ * @param string $offset Configuration setting name
+ *
+ * @return mixed Configuration setting value or NULL if it is not
+ * assigned a value
+ * @see ArrayAccess::offsetGet()
+ */
+ public function offsetGet($offset)
+ {
+ if (isset($this->settings[$offset])) {
+ $value = &$this->settings[$offset];
+ } else {
+ $value = null;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Sets the value of a configuration setting.
+ *
+ * @param string $offset Configuration setting name
+ * @param mixed $value New setting value
+ *
+ * @return void
+ * @see ArrayAccess::offsetSet()
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->settings[$offset] = $value;
+ }
+
+ /**
+ * Removes the value set for a configuration setting.
+ *
+ * @param string $offset Configuration setting name
+ *
+ * @return void
+ * @see ArrayAccess::offsetUnset()
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->settings[$offset]);
+
+ foreach ($this->files as $file => $settings) {
+ $key = array_search($offset, $settings);
+ if ($key !== false) {
+ unset($this->files[$file][$key]);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php
new file mode 100644
index 000000000..fb646c10c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to configuration.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Config_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that an attempt was made to read a configuration
+ * file that could not be executed
+ */
+ const ERR_FILE_NOT_EXECUTABLE = 1;
+
+ /**
+ * Error indicating that a read configuration file does not return an
+ * array
+ */
+ const ERR_ARRAY_NOT_RETURNED = 2;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection.php b/plugins/Irc/extlib/phergie/Phergie/Connection.php
new file mode 100755
index 000000000..746dec05f
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Connection.php
@@ -0,0 +1,401 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Data structure for connection metadata.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Connection
+{
+ /**
+ * Host to which the client will connect
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * Port on which the client will connect, defaults to the standard IRC
+ * port
+ *
+ * @var int
+ */
+ protected $port;
+
+ /**
+ * Transport for the connection, defaults to tcp but can be set to ssl
+ * or variations thereof to connect over SSL
+ *
+ * @var string
+ */
+ protected $transport;
+
+ /**
+ * Encoding method for the connection, defaults to ISO-8859-1 but can
+ * be set to UTF8 if necessary
+ *
+ * @var strng
+ */
+ protected $encoding;
+
+ /**
+ * Nick that the client will use
+ *
+ * @var string
+ */
+ protected $nick;
+
+ /**
+ * Username that the client will use
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * Realname that the client will use
+ *
+ * @var string
+ */
+ protected $realname;
+
+ /**
+ * Password that the client will use
+ *
+ * @var string
+ */
+ protected $password;
+
+ /**
+ * Hostmask for the connection
+ *
+ * @var Phergie_Hostmask
+ */
+ protected $hostmask;
+
+ /**
+ * Constructor to initialize instance properties.
+ *
+ * @param array $options Optional associative array of property values
+ * to initialize
+ *
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ $this->transport = 'tcp';
+ $this->encoding = 'ISO-8859-1';
+ // @note this may need changed to something different, for broader support.
+ // @note also may need to make use of http://us.php.net/manual/en/function.stream-encoding.php
+
+ $this->setOptions($options);
+ }
+
+ /**
+ * Emits an error related to a required connection setting does not have
+ * value set for it.
+ *
+ * @param string $setting Name of the setting
+ *
+ * @return void
+ */
+ protected function checkSetting($setting)
+ {
+ if (empty($this->$setting)) {
+ throw new Phergie_Connection_Exception(
+ 'Required connection setting "' . $setting . '" missing',
+ Phergie_Connection_Exception::ERR_REQUIRED_SETTING_MISSING
+ );
+ }
+ }
+
+ /**
+ * Returns a hostmask that uniquely identifies the connection.
+ *
+ * @return string
+ */
+ public function getHostmask()
+ {
+ if (empty($this->hostmask)) {
+ $this->hostmask = new Phergie_Hostmask(
+ $this->getNick(),
+ $this->getUsername(),
+ $this->getHost()
+ );
+ }
+
+ return $this->hostmask;
+ }
+
+ /**
+ * Sets the host to which the client will connect.
+ *
+ * @param string $host Hostname
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setHost($host)
+ {
+ if (empty($this->host)) {
+ $this->host = (string) $host;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the host to which the client will connect if it is set or
+ * emits an error if it is not set.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ $this->checkSetting('host');
+
+ return $this->host;
+ }
+
+ /**
+ * Sets the port on which the client will connect.
+ *
+ * @param int $port Port
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setPort($port)
+ {
+ if (empty($this->port)) {
+ $this->port = (int) $port;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the port on which the client will connect.
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ if (empty($this->port)) {
+ $this->port = 6667;
+ }
+
+ return $this->port;
+ }
+
+ /**
+ * Sets the transport for the connection to use.
+ *
+ * @param string $transport Transport (ex: tcp, ssl, etc.)
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setTransport($transport)
+ {
+ $this->transport = (string) $transport;
+
+ if (!in_array($this->transport, stream_get_transports())) {
+ throw new Phergie_Connection_Exception(
+ 'Transport ' . $this->transport . ' is not supported',
+ Phergie_Connection_Exception::ERR_TRANSPORT_NOT_SUPPORTED
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the transport in use by the connection.
+ *
+ * @return string Transport (ex: tcp, ssl, etc.)
+ */
+ public function getTransport()
+ {
+ return $this->transport;
+ }
+
+ /**
+ * Sets the encoding for the connection to use.
+ *
+ * @param string $encoding Encoding to use (ex: ASCII, ISO-8859-1, UTF8, etc.)
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = (string) $encoding;
+
+ if (!in_array($this->encoding, mb_list_encodings())) {
+ throw new Phergie_Connection_Exception(
+ 'Encoding ' . $this->encoding . ' is not supported',
+ Phergie_Connection_Exception::ERR_ENCODING_NOT_SUPPORTED
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the encoding in use by the connection.
+ *
+ * @return string Encoding (ex: ASCII, ISO-8859-1, UTF8, etc.)
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Sets the nick that the client will use.
+ *
+ * @param string $nick Nickname
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setNick($nick)
+ {
+ if (empty($this->nick)) {
+ $this->nick = (string) $nick;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the nick that the client will use.
+ *
+ * @return string
+ */
+ public function getNick()
+ {
+ $this->checkSetting('nick');
+
+ return $this->nick;
+ }
+
+ /**
+ * Sets the username that the client will use.
+ *
+ * @param string $username Username
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setUsername($username)
+ {
+ if (empty($this->username)) {
+ $this->username = (string) $username;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the username that the client will use.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ $this->checkSetting('username');
+
+ return $this->username;
+ }
+
+ /**
+ * Sets the realname that the client will use.
+ *
+ * @param string $realname Real name
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setRealname($realname)
+ {
+ if (empty($this->realname)) {
+ $this->realname = (string) $realname;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the realname that the client will use.
+ *
+ * @return string
+ */
+ public function getRealname()
+ {
+ $this->checkSetting('realname');
+
+ return $this->realname;
+ }
+
+ /**
+ * Sets the password that the client will use.
+ *
+ * @param string $password Password
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setPassword($password)
+ {
+ if (empty($this->password)) {
+ $this->password = (string) $password;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the password that the client will use.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Sets multiple connection settings using an array.
+ *
+ * @param array $options Associative array of setting names mapped to
+ * corresponding values
+ *
+ * @return Phergie_Connection Provides a fluent interface
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $option => $value) {
+ $method = 'set' . ucfirst($option);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php
new file mode 100644
index 000000000..aec1cd8e0
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to a connection to an IRC server.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Connection_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that an operation was attempted requiring a value
+ * for a specific configuration setting, but none was set
+ */
+ const ERR_REQUIRED_SETTING_MISSING = 1;
+
+ /**
+ * Error indicating that a connection is configured to use a transport,
+ * but that transport is not supported by the current PHP installation
+ */
+ const ERR_TRANSPORT_NOT_SUPPORTED = 2;
+
+ /**
+ * Error indicating that a connection is configured to use an encoding,
+ * but that encoding is not supported by the current PHP installation
+ */
+ const ERR_ENCODING_NOT_SUPPORTED = 3;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php
new file mode 100644
index 000000000..e9aeddcd3
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Handles connections initiated by the bot.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Connection_Handler implements Countable, IteratorAggregate
+{
+ /**
+ * Map of connections indexed by hostmask
+ *
+ * @var array
+ */
+ protected $connections;
+
+ /**
+ * Constructor to initialize storage for connections.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->connections = array();
+ }
+
+ /**
+ * Adds a connection to the connection list.
+ *
+ * @param Phergie_Connection $connection Connection to add
+ *
+ * @return Phergie_Connection_Handler Provides a fluent interface
+ */
+ public function addConnection(Phergie_Connection $connection)
+ {
+ $this->connections[(string) $connection->getHostmask()] = $connection;
+ return $this;
+ }
+
+ /**
+ * Removes a connection from the connection list.
+ *
+ * @param Phergie_Connection|string $connection Instance or hostmask for
+ * the connection to remove
+ *
+ * @return Phergie_Connection_Handler Provides a fluent interface
+ */
+ public function removeConnection($connection)
+ {
+ if ($connection instanceof Phergie_Connection) {
+ $hostmask = (string) $connection->getHostmask();
+ } elseif (is_string($connection)
+ && isset($this->connections[$connection])) {
+ $hostmask = $connection;
+ } else {
+ return $this;
+ }
+ unset($this->connections[$hostmask]);
+ return $this;
+ }
+
+ /**
+ * Returns the number of connections in the list.
+ *
+ * @return int Number of connections
+ */
+ public function count()
+ {
+ return count($this->connections);
+ }
+
+ /**
+ * Returns an iterator for the connection list.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->connections);
+ }
+
+ /**
+ * Returns a list of specified connection objects.
+ *
+ * @param array|string $keys One or more hostmasks identifying the
+ * connections to return
+ *
+ * @return array List of Phergie_Connection objects corresponding to the
+ * specified hostmask(s)
+ */
+ public function getConnections($keys)
+ {
+ $connections = array();
+
+ if (!is_array($keys)) {
+ $keys = array($keys);
+ }
+
+ foreach ($keys as $key) {
+ if (isset($this->connections[$key])) {
+ $connections[] = $this->connections[$key];
+ }
+ }
+
+ return $connections;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php
new file mode 100755
index 000000000..62736620d
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for drivers which handle issuing client commands to the IRC
+ * server and converting responses into usable data objects.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+abstract class Phergie_Driver_Abstract
+{
+ /**
+ * Currently active connection
+ *
+ * @var Phergie_Connection
+ */
+ protected $connection;
+
+ /**
+ * Sets the currently active connection.
+ *
+ * @param Phergie_Connection $connection Active connection
+ *
+ * @return Phergie_Driver_Abstract Provides a fluent interface
+ */
+ public function setConnection(Phergie_Connection $connection)
+ {
+ $this->connection = $connection;
+
+ return $this;
+ }
+
+ /**
+ * Returns the currently active connection.
+ *
+ * @return Phergie_Connection
+ * @throws Phergie_Driver_Exception
+ */
+ public function getConnection()
+ {
+ if (empty($this->connection)) {
+ throw new Phergie_Driver_Exception(
+ 'Operation requires an active connection, but none is set',
+ Phergie_Driver_Exception::ERR_NO_ACTIVE_CONNECTION
+ );
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Returns an event if one has been received from the server.
+ *
+ * @return Phergie_Event_Interface|null Event instance if an event has
+ * been received, NULL otherwise
+ */
+ public abstract function getEvent();
+
+ /**
+ * Initiates a connection with the server.
+ *
+ * @return void
+ */
+ public abstract function doConnect();
+
+ /**
+ * Terminates the connection with the server.
+ *
+ * @param string $reason Reason for connection termination (optional)
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
+ */
+ public abstract function doQuit($reason = null);
+
+ /**
+ * Joins a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to join
+ * @param string $keys Optional comma-delimited list of channel keys
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
+ */
+ public abstract function doJoin($channels, $keys = null);
+
+ /**
+ * Leaves a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to leave
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
+ */
+ public abstract function doPart($channels);
+
+ /**
+ * Invites a user to an invite-only channel.
+ *
+ * @param string $nick Nick of the user to invite
+ * @param string $channel Name of the channel
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
+ */
+ public abstract function doInvite($nick, $channel);
+
+ /**
+ * Obtains a list of nicks of users in specified channels.
+ *
+ * @param string $channels Comma-delimited list of one or more channels
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_5
+ */
+ public abstract function doNames($channels);
+
+ /**
+ * Obtains a list of channel names and topics.
+ *
+ * @param string $channels Comma-delimited list of one or more channels
+ * to which the response should be restricted
+ * (optional)
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_6
+ */
+ public abstract function doList($channels = null);
+
+ /**
+ * Retrieves or changes a channel topic.
+ *
+ * @param string $channel Name of the channel
+ * @param string $topic New topic to assign (optional)
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
+ */
+ public abstract function doTopic($channel, $topic = null);
+
+ /**
+ * Retrieves or changes a channel or user mode.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $mode New mode to assign (optional)
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
+ */
+ public abstract function doMode($target, $mode = null);
+
+ /**
+ * Changes the client nick.
+ *
+ * @param string $nick New nick to assign
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
+ */
+ public abstract function doNick($nick);
+
+ /**
+ * Retrieves information about a nick.
+ *
+ * @param string $nick Nick
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_5_2
+ */
+ public abstract function doWhois($nick);
+
+ /**
+ * Sends a message to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the message to send
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
+ */
+ public abstract function doPrivmsg($target, $text);
+
+ /**
+ * Sends a notice to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the notice to send
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
+ */
+ public abstract function doNotice($target, $text);
+
+ /**
+ * Kicks a user from a channel.
+ *
+ * @param string $nick Nick of the user
+ * @param string $channel Channel name
+ * @param string $reason Reason for the kick (optional)
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
+ */
+ public abstract function doKick($nick, $channel, $reason = null);
+
+ /**
+ * Responds to a server test of client responsiveness.
+ *
+ * @param string $daemon Daemon from which the original request originates
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
+ */
+ public abstract function doPong($daemon);
+
+ /**
+ * Sends a CTCP ACTION (/me) command to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the action to perform
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.4
+ */
+ public abstract function doAction($target, $text);
+
+ /**
+ * Sends a CTCP PING request to a user.
+ *
+ * @param string $nick User nick
+ * @param string $hash Hash to use in the handshake
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.2
+ */
+ public abstract function doPing($nick, $hash);
+
+ /**
+ * Sends a CTCP VERSION request or response to a user.
+ *
+ * @param string $nick User nick
+ * @param string $version Version string to send for a response
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.1
+ */
+ public abstract function doVersion($nick, $version = null);
+
+ /**
+ * Sends a CTCP TIME request to a user.
+ *
+ * @param string $nick User nick
+ * @param string $time Time string to send for a response
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.6
+ */
+ public abstract function doTime($nick, $time = null);
+
+ /**
+ * Sends a CTCP FINGER request to a user.
+ *
+ * @param string $nick User nick
+ * @param string $finger Finger string to send for a response
+ *
+ * @return void
+ * @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html
+ */
+ public abstract function doFinger($nick, $finger = null);
+
+ /**
+ * Sends a raw command to the server.
+ *
+ * @param string $command Command string to send
+ *
+ * @return void
+ */
+ public abstract function doRaw($command);
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php
new file mode 100755
index 000000000..5873b2cb9
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to driver operations.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Driver_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that an operation was requested requiring an active
+ * connection before one had been set
+ */
+ const ERR_NO_ACTIVE_CONNECTION = 1;
+
+ /**
+ * Error indicating that an operation was requested requiring an active
+ * connection where one had been set but not initiated
+ */
+ const ERR_NO_INITIATED_CONNECTION = 2;
+
+ /**
+ * Error indicating that an attempt to initiate a connection failed
+ */
+ const ERR_CONNECTION_ATTEMPT_FAILED = 3;
+
+ /**
+ * Error indicating that an attempt to send data via a connection failed
+ */
+ const ERR_CONNECTION_WRITE_FAILED = 4;
+
+ /**
+ * Error indicating that an attempt to read data via a connection failed
+ */
+ const ERR_CONNECTION_READ_FAILED = 5;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Statusnet.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Statusnet.php
new file mode 100644
index 000000000..84c85a01c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Statusnet.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Extends the Streams driver (Phergie_Driver_Streams) to give external access
+ * to the socket resources and send method
+ *
+ * @category Phergie
+ * @package Phergie_Driver_Statusnet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class Phergie_Driver_Statusnet extends Phergie_Driver_Streams {
+ /**
+ * Handles construction of command strings and their transmission to the
+ * server.
+ *
+ * @param string $command Command to send
+ * @param string|array $args Optional string or array of sequential
+ * arguments
+ *
+ * @return string Command string that was sent
+ * @throws Phergie_Driver_Exception
+ */
+ public function send($command, $args = '') {
+ return parent::send($command, $args);
+ }
+
+ public function forceQuit() {
+ try {
+ // Send a QUIT command to the server
+ $this->send('QUIT', 'Reconnecting');
+ } catch (Phergie_Driver_Exception $e){}
+
+ // Terminate the socket connection
+ fclose($this->socket);
+
+ // Remove the socket from the internal socket list
+ unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
+ }
+
+ /**
+ * Returns the array of sockets
+ *
+ * @return array Array of socket resources
+ */
+ public function getSockets() {
+ return $this->sockets;
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php
new file mode 100755
index 000000000..73c0230c7
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php
@@ -0,0 +1,729 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Driver that uses the sockets wrapper of the streams extension for
+ * communicating with the server and handles formatting and parsing of
+ * events using PHP.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Driver_Streams extends Phergie_Driver_Abstract
+{
+ /**
+ * Socket handlers
+ *
+ * @var array
+ */
+ protected $sockets = array();
+
+ /**
+ * Reference to the currently active socket handler
+ *
+ * @var resource
+ */
+ protected $socket;
+
+ /**
+ * Amount of time in seconds to wait to receive an event each time the
+ * socket is polled
+ *
+ * @var float
+ */
+ protected $timeout = 0.1;
+
+ /**
+ * Handles construction of command strings and their transmission to the
+ * server.
+ *
+ * @param string $command Command to send
+ * @param string|array $args Optional string or array of sequential
+ * arguments
+ *
+ * @return string Command string that was sent
+ * @throws Phergie_Driver_Exception
+ */
+ protected function send($command, $args = '')
+ {
+ $connection = $this->getConnection();
+ $encoding = $connection->getEncoding();
+
+ // Require an open socket connection to continue
+ if (empty($this->socket)) {
+ throw new Phergie_Driver_Exception(
+ 'doConnect() must be called first',
+ Phergie_Driver_Exception::ERR_NO_INITIATED_CONNECTION
+ );
+ }
+
+ // Add the command
+ $buffer = strtoupper($command);
+
+ // Add arguments
+ if (!empty($args)) {
+
+ // Apply formatting if arguments are passed in as an array
+ if (is_array($args)) {
+ $end = count($args) - 1;
+ $args[$end] = ':' . $args[$end];
+ $args = implode(' ', $args);
+ } else {
+ $args = ':' . $args;
+ }
+
+ $buffer .= ' ' . $args;
+ }
+
+ // Transmit the command over the socket connection
+ $attempts = $written = 0;
+ $temp = $buffer . "\r\n";
+ $is_multibyte = !substr($encoding, 0, 8) === 'ISO-8859' && $encoding !== 'ASCII' && $encoding !== 'CP1252';
+ $length = ($is_multibyte) ? mb_strlen($buffer, '8bit') : strlen($buffer);
+ while (true) {
+ $written += (int) fwrite($this->socket, $temp);
+ if ($written < $length) {
+ $temp = substr($temp, $written);
+ $attempts++;
+ if ($attempts == 3) {
+ throw new Phergie_Driver_Exception(
+ 'Unable to write to socket',
+ Phergie_Driver_Exception::ERR_CONNECTION_WRITE_FAILED
+ );
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Return the command string that was transmitted
+ return $buffer;
+ }
+
+ /**
+ * Overrides the parent class to set the currently active socket handler
+ * when the active connection is changed.
+ *
+ * @param Phergie_Connection $connection Active connection
+ *
+ * @return Phergie_Driver_Streams Provides a fluent interface
+ */
+ public function setConnection(Phergie_Connection $connection)
+ {
+ // Set the active socket handler
+ $hostmask = (string) $connection->getHostmask();
+ if (!empty($this->sockets[$hostmask])) {
+ $this->socket = $this->sockets[$hostmask];
+ }
+
+ // Set the active connection
+ return parent::setConnection($connection);
+ }
+
+ /**
+ * Returns a list of hostmasks corresponding to sockets with data to read.
+ *
+ * @param int $sec Length of time to wait for new data (seconds)
+ * @param int $usec Length of time to wait for new data (microseconds)
+ *
+ * @return array List of hostmasks or an empty array if none were found
+ * to have data to read
+ */
+ public function getActiveReadSockets($sec = 0, $usec = 200000)
+ {
+ $read = $this->sockets;
+ $write = null;
+ $error = null;
+ $active = array();
+
+ if (count($this->sockets) > 0) {
+ $number = stream_select($read, $write, $error, $sec, $usec);
+ if ($number > 0) {
+ foreach ($read as $item) {
+ $active[] = array_search($item, $this->sockets);
+ }
+ }
+ }
+
+ return $active;
+ }
+
+ /**
+ * Sets the amount of time to wait for a new event each time the socket
+ * is polled.
+ *
+ * @param float $timeout Amount of time in seconds
+ *
+ * @return Phergie_Driver_Streams Provides a fluent interface
+ */
+ public function setTimeout($timeout)
+ {
+ $timeout = (float) $timeout;
+ if ($timeout) {
+ $this->timeout = $timeout;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the amount of time to wait for a new event each time the
+ * socket is polled.
+ *
+ * @return float Amount of time in seconds
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Supporting method to parse event argument strings where the last
+ * argument may contain a colon.
+ *
+ * @param string $args Argument string to parse
+ * @param int $count Optional maximum number of arguments
+ *
+ * @return array Array of argument values
+ */
+ protected function parseArguments($args, $count = -1)
+ {
+ return preg_split('/ :?/S', $args, $count);
+ }
+
+ /**
+ * Listens for an event on the current connection.
+ *
+ * @return Phergie_Event_Interface|null Event instance if an event was
+ * received, NULL otherwise
+ */
+ public function getEvent()
+ {
+ // Check the socket is still active
+ if (feof($this->socket)) {
+ throw new Phergie_Driver_Exception(
+ 'EOF detected on socket',
+ Phergie_Driver_Exception::ERR_CONNECTION_READ_FAILED
+ );
+ }
+
+ // Check for a new event on the current connection
+ $buffer = fgets($this->socket, 512);
+
+ // If no new event was found, return NULL
+ if (empty($buffer)) {
+ return null;
+ }
+
+ // Strip the trailing newline from the buffer
+ $buffer = rtrim($buffer);
+
+ // If the event is from the server...
+ if (substr($buffer, 0, 1) != ':') {
+
+ // Parse the command and arguments
+ list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null);
+ $hostmask = new Phergie_Hostmask(null, null, $this->connection->getHost());
+
+ } else {
+ // If the event could be from the server or a user...
+
+ // Parse the server hostname or user hostmask, command, and arguments
+ list($prefix, $cmd, $args)
+ = array_pad(explode(' ', ltrim($buffer, ':'), 3), 3, null);
+ if (strpos($prefix, '@') !== false) {
+ $hostmask = Phergie_Hostmask::fromString($prefix);
+ } else {
+ $hostmask = new Phergie_Hostmask(null, null, $prefix);
+ }
+ }
+
+ // Parse the event arguments depending on the event type
+ $cmd = strtolower($cmd);
+ switch ($cmd) {
+ case 'names':
+ case 'nick':
+ case 'quit':
+ case 'ping':
+ case 'join':
+ case 'error':
+ $args = array(ltrim($args, ':'));
+ break;
+
+ case 'privmsg':
+ case 'notice':
+ $args = $this->parseArguments($args, 2);
+ list($source, $ctcp) = $args;
+ if (substr($ctcp, 0, 1) === "\001" && substr($ctcp, -1) === "\001") {
+ $ctcp = substr($ctcp, 1, -1);
+ $reply = ($cmd == 'notice');
+ list($cmd, $args) = array_pad(explode(' ', $ctcp, 2), 2, null);
+ $cmd = strtolower($cmd);
+ switch ($cmd) {
+ case 'version':
+ case 'time':
+ case 'finger':
+ if ($reply) {
+ $args = $ctcp;
+ }
+ break;
+ case 'ping':
+ if ($reply) {
+ $cmd .= 'Response';
+ } else {
+ $cmd = 'ctcpPing';
+ }
+ break;
+ case 'action':
+ $args = array($source, $args);
+ break;
+
+ default:
+ $cmd = 'ctcp';
+ if ($reply) {
+ $cmd .= 'Response';
+ }
+ $args = array($source, $args);
+ break;
+ }
+ }
+ break;
+
+ case 'oper':
+ case 'topic':
+ case 'mode':
+ $args = $this->parseArguments($args);
+ break;
+
+ case 'part':
+ case 'kill':
+ case 'invite':
+ $args = $this->parseArguments($args, 2);
+ break;
+
+ case 'kick':
+ $args = $this->parseArguments($args, 3);
+ break;
+
+ // Remove the target from responses
+ default:
+ $args = substr($args, strpos($args, ' ') + 1);
+ break;
+ }
+
+ // Create, populate, and return an event object
+ if (ctype_digit($cmd)) {
+ $event = new Phergie_Event_Response;
+ $event
+ ->setCode($cmd)
+ ->setDescription($args);
+ } else {
+ $event = new Phergie_Event_Request;
+ $event
+ ->setType($cmd)
+ ->setArguments($args);
+ if (isset($hostmask)) {
+ $event->setHostmask($hostmask);
+ }
+ }
+ $event->setRawData($buffer);
+ return $event;
+ }
+
+ /**
+ * Initiates a connection with the server.
+ *
+ * @return void
+ */
+ public function doConnect()
+ {
+ // Listen for input indefinitely
+ set_time_limit(0);
+
+ // Get connection information
+ $connection = $this->getConnection();
+ $hostname = $connection->getHost();
+ $port = $connection->getPort();
+ $password = $connection->getPassword();
+ $username = $connection->getUsername();
+ $nick = $connection->getNick();
+ $realname = $connection->getRealname();
+ $transport = $connection->getTransport();
+
+ // Establish and configure the socket connection
+ $remote = $transport . '://' . $hostname . ':' . $port;
+ $this->socket = @stream_socket_client($remote, $errno, $errstr);
+ if (!$this->socket) {
+ throw new Phergie_Driver_Exception(
+ 'Unable to connect: socket error ' . $errno . ' ' . $errstr,
+ Phergie_Driver_Exception::ERR_CONNECTION_ATTEMPT_FAILED
+ );
+ }
+
+ $seconds = (int) $this->timeout;
+ $microseconds = ($this->timeout - $seconds) * 1000000;
+ stream_set_timeout($this->socket, $seconds, $microseconds);
+
+ // Send the password if one is specified
+ if (!empty($password)) {
+ $this->send('PASS', $password);
+ }
+
+ // Send user information
+ $this->send(
+ 'USER',
+ array(
+ $username,
+ $hostname,
+ $hostname,
+ $realname
+ )
+ );
+
+ $this->send('NICK', $nick);
+
+ // Add the socket handler to the internal array for socket handlers
+ $this->sockets[(string) $connection->getHostmask()] = $this->socket;
+ }
+
+ /**
+ * Terminates the connection with the server.
+ *
+ * @param string $reason Reason for connection termination (optional)
+ *
+ * @return void
+ */
+ public function doQuit($reason = null)
+ {
+ // Send a QUIT command to the server
+ $this->send('QUIT', $reason);
+
+ // Terminate the socket connection
+ fclose($this->socket);
+
+ // Remove the socket from the internal socket list
+ unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
+ }
+
+ /**
+ * Joins a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to join
+ * @param string $keys Optional comma-delimited list of channel keys
+ *
+ * @return void
+ */
+ public function doJoin($channels, $keys = null)
+ {
+ $args = array($channels);
+
+ if (!empty($keys)) {
+ $args[] = $keys;
+ }
+
+ $this->send('JOIN', $args);
+ }
+
+ /**
+ * Leaves a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to leave
+ *
+ * @return void
+ */
+ public function doPart($channels)
+ {
+ $this->send('PART', $channels);
+ }
+
+ /**
+ * Invites a user to an invite-only channel.
+ *
+ * @param string $nick Nick of the user to invite
+ * @param string $channel Name of the channel
+ *
+ * @return void
+ */
+ public function doInvite($nick, $channel)
+ {
+ $this->send('INVITE', array($nick, $channel));
+ }
+
+ /**
+ * Obtains a list of nicks of usrs in currently joined channels.
+ *
+ * @param string $channels Comma-delimited list of one or more channels
+ *
+ * @return void
+ */
+ public function doNames($channels)
+ {
+ $this->send('NAMES', $channels);
+ }
+
+ /**
+ * Obtains a list of channel names and topics.
+ *
+ * @param string $channels Comma-delimited list of one or more channels
+ * to which the response should be restricted
+ * (optional)
+ *
+ * @return void
+ */
+ public function doList($channels = null)
+ {
+ $this->send('LIST', $channels);
+ }
+
+ /**
+ * Retrieves or changes a channel topic.
+ *
+ * @param string $channel Name of the channel
+ * @param string $topic New topic to assign (optional)
+ *
+ * @return void
+ */
+ public function doTopic($channel, $topic = null)
+ {
+ $args = array($channel);
+
+ if (!empty($topic)) {
+ $args[] = $topic;
+ }
+
+ $this->send('TOPIC', $args);
+ }
+
+ /**
+ * Retrieves or changes a channel or user mode.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $mode New mode to assign (optional)
+ *
+ * @return void
+ */
+ public function doMode($target, $mode = null)
+ {
+ $args = array($target);
+
+ if (!empty($mode)) {
+ $args[] = $mode;
+ }
+
+ $this->send('MODE', $args);
+ }
+
+ /**
+ * Changes the client nick.
+ *
+ * @param string $nick New nick to assign
+ *
+ * @return void
+ */
+ public function doNick($nick)
+ {
+ $this->send('NICK', $nick);
+ }
+
+ /**
+ * Retrieves information about a nick.
+ *
+ * @param string $nick Nick
+ *
+ * @return void
+ */
+ public function doWhois($nick)
+ {
+ $this->send('WHOIS', $nick);
+ }
+
+ /**
+ * Sends a message to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the message to send
+ *
+ * @return void
+ */
+ public function doPrivmsg($target, $text)
+ {
+ $this->send('PRIVMSG', array($target, $text));
+ }
+
+ /**
+ * Sends a notice to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the notice to send
+ *
+ * @return void
+ */
+ public function doNotice($target, $text)
+ {
+ $this->send('NOTICE', array($target, $text));
+ }
+
+ /**
+ * Kicks a user from a channel.
+ *
+ * @param string $nick Nick of the user
+ * @param string $channel Channel name
+ * @param string $reason Reason for the kick (optional)
+ *
+ * @return void
+ */
+ public function doKick($nick, $channel, $reason = null)
+ {
+ $args = array($nick, $channel);
+
+ if (!empty($reason)) {
+ $args[] = $response;
+ }
+
+ $this->send('KICK', $args);
+ }
+
+ /**
+ * Responds to a server test of client responsiveness.
+ *
+ * @param string $daemon Daemon from which the original request originates
+ *
+ * @return void
+ */
+ public function doPong($daemon)
+ {
+ $this->send('PONG', $daemon);
+ }
+
+ /**
+ * Sends a CTCP ACTION (/me) command to a nick or channel.
+ *
+ * @param string $target Channel name or user nick
+ * @param string $text Text of the action to perform
+ *
+ * @return void
+ */
+ public function doAction($target, $text)
+ {
+ $buffer = rtrim('ACTION ' . $text);
+
+ $this->doPrivmsg($target, chr(1) . $buffer . chr(1));
+ }
+
+ /**
+ * Sends a CTCP response to a user.
+ *
+ * @param string $nick User nick
+ * @param string $command Command to send
+ * @param string|array $args String or array of sequential arguments
+ * (optional)
+ *
+ * @return void
+ */
+ protected function doCtcp($nick, $command, $args = null)
+ {
+ if (is_array($args)) {
+ $args = implode(' ', $args);
+ }
+
+ $buffer = rtrim(strtoupper($command) . ' ' . $args);
+
+ $this->doNotice($nick, chr(1) . $buffer . chr(1));
+ }
+
+ /**
+ * Sends a CTCP PING request or response (they are identical) to a user.
+ *
+ * @param string $nick User nick
+ * @param string $hash Hash to use in the handshake
+ *
+ * @return void
+ */
+ public function doPing($nick, $hash)
+ {
+ $this->doCtcp($nick, 'PING', $hash);
+ }
+
+ /**
+ * Sends a CTCP VERSION request or response to a user.
+ *
+ * @param string $nick User nick
+ * @param string $version Version string to send for a response
+ *
+ * @return void
+ */
+ public function doVersion($nick, $version = null)
+ {
+ if ($version) {
+ $this->doCtcp($nick, 'VERSION', $version);
+ } else {
+ $this->doCtcp($nick, 'VERSION');
+ }
+ }
+
+ /**
+ * Sends a CTCP TIME request to a user.
+ *
+ * @param string $nick User nick
+ * @param string $time Time string to send for a response
+ *
+ * @return void
+ */
+ public function doTime($nick, $time = null)
+ {
+ if ($time) {
+ $this->doCtcp($nick, 'TIME', $time);
+ } else {
+ $this->doCtcp($nick, 'TIME');
+ }
+ }
+
+ /**
+ * Sends a CTCP FINGER request to a user.
+ *
+ * @param string $nick User nick
+ * @param string $finger Finger string to send for a response
+ *
+ * @return void
+ */
+ public function doFinger($nick, $finger = null)
+ {
+ if ($finger) {
+ $this->doCtcp($nick, 'FINGER', $finger);
+ } else {
+ $this->doCtcp($nick, 'FINGER');
+ }
+ }
+
+ /**
+ * Sends a raw command to the server.
+ *
+ * @param string $command Command string to send
+ *
+ * @return void
+ */
+ public function doRaw($command)
+ {
+ $this->send('RAW', $command);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php
new file mode 100644
index 000000000..54b035dc0
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for events.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+abstract class Phergie_Event_Abstract
+{
+ /**
+ * Event type, used for determining the callback to execute in response
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * Returns the event type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Sets the event type.
+ *
+ * @param string $type Event type
+ *
+ * @return Phergie_Event_Abstract Implements a fluent interface
+ */
+ public function setType($type)
+ {
+ $this->type = (string) $type;
+ return $this;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Command.php b/plugins/Irc/extlib/phergie/Phergie/Event/Command.php
new file mode 100644
index 000000000..5940636ba
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Command.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Event originating from a plugin for the bot.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Event_Command extends Phergie_Event_Request
+{
+ /**
+ * Reference to the plugin instance that created the event
+ *
+ * @var Phergie_Plugin_Abstract
+ */
+ protected $plugin;
+
+ /**
+ * Stores a reference to the plugin instance that created the event.
+ *
+ * @param Phergie_Plugin_Abstract $plugin Plugin instance
+ *
+ * @return Phergie_Event_Command Provides a fluent interface
+ */
+ public function setPlugin(Phergie_Plugin_Abstract $plugin)
+ {
+ $this->plugin = $plugin;
+ return $this;
+ }
+
+ /**
+ * Returns a reference to the plugin instance that created the event.
+ *
+ * @return Phergie_Plugin_Abstract
+ */
+ public function getPlugin()
+ {
+ return $this->plugin;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php
new file mode 100644
index 000000000..6b094a810
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to outgoing events.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Event_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that an attempt was made to create an event of an
+ * unknown type
+ */
+ const ERR_UNKNOWN_EVENT_TYPE = 1;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php
new file mode 100644
index 000000000..e308df8a5
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Handles events initiated by plugins.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Event_Handler implements IteratorAggregate, Countable
+{
+ /**
+ * Current queue of events
+ *
+ * @var array
+ */
+ protected $events;
+
+ /**
+ * Constructor to initialize the event queue.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->events = array();
+ }
+
+ /**
+ * Adds an event to the queue.
+ *
+ * @param Phergie_Plugin_Abstract $plugin Plugin originating the event
+ * @param string $type Event type, corresponding to a
+ * Phergie_Event_Command::TYPE_* constant
+ * @param array $args Optional event arguments
+ *
+ * @return Phergie_Event_Handler Provides a fluent interface
+ */
+ public function addEvent(Phergie_Plugin_Abstract $plugin, $type,
+ array $args = array()
+ ) {
+ if (!defined('Phergie_Event_Command::TYPE_' . strtoupper($type))) {
+ throw new Phergie_Event_Exception(
+ 'Unknown event type "' . $type . '"',
+ Phergie_Event_Exception::ERR_UNKNOWN_EVENT_TYPE
+ );
+ }
+
+ $event = new Phergie_Event_Command;
+ $event
+ ->setPlugin($plugin)
+ ->setType($type)
+ ->setArguments($args);
+
+ $this->events[] = $event;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current event queue.
+ *
+ * @return array Enumerated array of Phergie_Event_Command objects
+ */
+ public function getEvents()
+ {
+ return $this->events;
+ }
+
+ /**
+ * Clears the event queue.
+ *
+ * @return Phergie_Event_Handler Provides a fluent interface
+ */
+ public function clearEvents()
+ {
+ $this->events = array();
+ return $this;
+ }
+
+ /**
+ * Replaces the current event queue with a given queue of events.
+ *
+ * @param array $events Ordered list of objects of the class
+ * Phergie_Event_Command
+ *
+ * @return Phergie_Event_Handler Provides a fluent interface
+ */
+ public function replaceEvents(array $events)
+ {
+ $this->events = $events;
+ return $this;
+ }
+
+ /**
+ * Returns whether an event of the given type exists in the queue.
+ *
+ * @param string $type Event type from Phergie_Event_Request::TYPE_*
+ * constants
+ *
+ * @return bool TRUE if an event of the specified type exists in the
+ * queue, FALSE otherwise
+ */
+ public function hasEventOfType($type)
+ {
+ foreach ($this->events as $event) {
+ if ($event->getType() == $type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a list of events of a specified type.
+ *
+ * @param string $type Event type from Phergie_Event_Request::TYPE_*
+ * constants
+ *
+ * @return array Array containing event instances of the specified type
+ * or an empty array if no such events were found
+ */
+ public function getEventsOfType($type)
+ {
+ $events = array();
+ foreach ($this->events as $event) {
+ if ($event->getType() == $type) {
+ $events[] = $event;
+ }
+ }
+ return $events;
+ }
+
+ /**
+ * Removes a single event from the event queue.
+ *
+ * @param Phergie_Event_Command $event Event to remove
+ *
+ * @return Phergie_Event_Handler Provides a fluent interface
+ */
+ public function removeEvent(Phergie_Event_Command $event)
+ {
+ $key = array_search($event, $this->events);
+ if ($key !== false) {
+ unset($this->events[$key]);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns an iterator for the current event queue.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->events);
+ }
+
+ /**
+ * Returns the number of events in the event queue
+ *
+ * @return int number of queued events
+ */
+ public function count()
+ {
+ return count($this->events);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Request.php b/plugins/Irc/extlib/phergie/Phergie/Event/Request.php
new file mode 100755
index 000000000..647b5acb8
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Request.php
@@ -0,0 +1,468 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Autonomous event originating from a user or the server.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ * @link http://www.irchelp.org/irchelp/rfc/chapter4.html
+ */
+class Phergie_Event_Request
+ extends Phergie_Event_Abstract
+ implements ArrayAccess
+{
+ /**
+ * Nick message event type
+ */
+ const TYPE_NICK = 'nick';
+
+ /**
+ * Whois message event type
+ */
+ const TYPE_WHOIS = 'whois';
+
+ /**
+ * Quit command event type
+ */
+ const TYPE_QUIT = 'quit';
+
+ /**
+ * Join message event type
+ */
+ const TYPE_JOIN = 'join';
+
+ /**
+ * Kick message event type
+ */
+ const TYPE_KICK = 'kick';
+
+ /**
+ * Part message event type
+ */
+ const TYPE_PART = 'part';
+
+ /**
+ * Invite message event type
+ */
+ const TYPE_INVITE = 'invite';
+
+ /**
+ * Mode message event type
+ */
+ const TYPE_MODE = 'mode';
+
+ /**
+ * Topic message event type
+ */
+ const TYPE_TOPIC = 'topic';
+
+ /**
+ * Private message command event type
+ */
+ const TYPE_PRIVMSG = 'privmsg';
+
+ /**
+ * Notice message event type
+ */
+ const TYPE_NOTICE = 'notice';
+
+ /**
+ * Pong message event type
+ */
+ const TYPE_PONG = 'pong';
+
+ /**
+ * CTCP ACTION command event type
+ */
+ const TYPE_ACTION = 'action';
+
+ /**
+ * CTCP PING command event type
+ */
+ const TYPE_PING = 'ping';
+
+ /**
+ * CTCP TIME command event type
+ */
+ const TYPE_TIME = 'time';
+
+ /**
+ * CTCP VERSION command event type
+ */
+ const TYPE_VERSION = 'version';
+
+ /**
+ * RAW message event type
+ */
+ const TYPE_RAW = 'raw';
+
+ /**
+ * Mapping of event types to their named parameters
+ *
+ * @var array
+ */
+ protected static $map = array(
+
+ self::TYPE_QUIT => array(
+ 'message' => 0
+ ),
+
+ self::TYPE_JOIN => array(
+ 'channel' => 0
+ ),
+
+ self::TYPE_KICK => array(
+ 'channel' => 0,
+ 'user' => 1,
+ 'comment' => 2
+ ),
+
+ self::TYPE_PART => array(
+ 'channel' => 0,
+ 'message' => 1
+ ),
+
+ self::TYPE_INVITE => array(
+ 'nickname' => 0,
+ 'channel' => 1
+ ),
+
+ self::TYPE_MODE => array(
+ 'target' => 0,
+ 'mode' => 1,
+ 'limit' => 2,
+ 'user' => 3,
+ 'banmask' => 4
+ ),
+
+ self::TYPE_TOPIC => array(
+ 'channel' => 0,
+ 'topic' => 1
+ ),
+
+ self::TYPE_PRIVMSG => array(
+ 'receiver' => 0,
+ 'text' => 1
+ ),
+
+ self::TYPE_NOTICE => array(
+ 'nickname' => 0,
+ 'text' => 1
+ ),
+
+ self::TYPE_ACTION => array(
+ 'target' => 0,
+ 'action' => 1
+ ),
+
+ self::TYPE_RAW => array(
+ 'message' => 0
+ )
+
+ );
+
+ /**
+ * Hostmask representing the originating user, if applicable
+ *
+ * @var Phergie_Hostmask
+ */
+ protected $hostmask;
+
+ /**
+ * Arguments included with the message
+ *
+ * @var array
+ */
+ protected $arguments;
+
+ /**
+ * Raw data sent by the server
+ *
+ * @var string
+ */
+ protected $rawData;
+
+ /**
+ * Sets the hostmask representing the originating user.
+ *
+ * @param Phergie_Hostmask $hostmask User hostmask
+ *
+ * @return Phergie_Event_Request Provides a fluent interface
+ */
+ public function setHostmask(Phergie_Hostmask $hostmask)
+ {
+ $this->hostmask = $hostmask;
+ return $this;
+ }
+
+ /**
+ * Returns the hostmask representing the originating user.
+ *
+ * @return Phergie_Event_Request|null Hostmask or NULL if none was set
+ */
+ public function getHostmask()
+ {
+ return $this->hostmask;
+ }
+
+ /**
+ * Sets the arguments for the request.
+ *
+ * @param array $arguments Request arguments
+ *
+ * @return Phergie_Event_Request Provides a fluent interface
+ */
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+ return $this;
+ }
+
+ /**
+ * Sets the value of a single argument for the request.
+ *
+ * @param mixed $argument Integer position (starting from 0) or the
+ * equivalent string name of the argument from self::$map
+ * @param string $value Value to assign to the argument
+ *
+ * @return Phergie_Event_Request Provides a fluent interface
+ */
+ public function setArgument($argument, $value)
+ {
+ $argument = $this->resolveArgument($argument);
+ if ($argument !== null) {
+ $this->arguments[$argument] = (string) $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the arguments for the request.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Resolves an argument specification to an integer position.
+ *
+ * @param mixed $argument Integer position (starting from 0) or the
+ * equivalent string name of the argument from self::$map
+ *
+ * @return int|null Integer position of the argument or NULL if no
+ * corresponding argument was found
+ */
+ protected function resolveArgument($argument)
+ {
+ if (isset($this->arguments[$argument])) {
+ return $argument;
+ } else {
+ $argument = strtolower($argument);
+ if (isset(self::$map[$this->type][$argument])
+ && isset($this->arguments[self::$map[$this->type][$argument]])
+ ) {
+ return self::$map[$this->type][$argument];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a single specified argument for the request.
+ *
+ * @param mixed $argument Integer position (starting from 0) or the
+ * equivalent string name of the argument from self::$map
+ *
+ * @return string|null Argument value or NULL if none is set
+ */
+ public function getArgument($argument)
+ {
+ $argument = $this->resolveArgument($argument);
+ if ($argument !== null) {
+ return $this->arguments[$argument];
+ }
+ return null;
+ }
+
+ /**
+ * Sets the raw buffer for the event.
+ *
+ * @param string $buffer Raw event buffer
+ *
+ * @return Phergie_Event_Request Provides a fluent interface
+ */
+ public function setRawData($buffer)
+ {
+ $this->rawData = $buffer;
+ return $this;
+ }
+
+ /**
+ * Returns the raw buffer sent from the server for the event.
+ *
+ * @return string
+ */
+ public function getRawData()
+ {
+ return $this->rawData;
+ }
+
+ /**
+ * Returns the nick of the user who originated the event.
+ *
+ * @return string
+ */
+ public function getNick()
+ {
+ return $this->hostmask->getNick();
+ }
+
+ /**
+ * Returns the channel name if the event occurred in a channel or the
+ * user nick if the event was a private message directed at the bot by a
+ * user.
+ *
+ * @return string
+ */
+ public function getSource()
+ {
+ if (substr($this->arguments[0], 0, 1) == '#') {
+ return $this->arguments[0];
+ }
+ return $this->hostmask->getNick();
+ }
+
+ /**
+ * Returns whether or not the event occurred within a channel.
+ *
+ * @return TRUE if the event is in a channel, FALSE otherwise
+ */
+ public function isInChannel()
+ {
+ return (substr($this->getSource(), 0, 1) == '#');
+ }
+
+ /**
+ * Returns whether or not the event originated from a user.
+ *
+ * @return TRUE if the event is from a user, FALSE otherwise
+ */
+ public function isFromUser()
+ {
+ if (empty($this->hostmask)) {
+ return false;
+ }
+ $username = $this->hostmask->getUsername();
+ return !empty($username);
+ }
+
+ /**
+ * Returns whether or not the event originated from the server.
+ *
+ * @return TRUE if the event is from the server, FALSE otherwise
+ */
+ public function isFromServer()
+ {
+ $username = $this->hostmask->getUsername();
+ return empty($username);
+ }
+
+ /**
+ * Provides access to named parameters via virtual "getter" methods.
+ *
+ * @param string $name Name of the method called
+ * @param array $arguments Arguments passed to the method (should always
+ * be empty)
+ *
+ * @return mixed Method return value
+ */
+ public function __call($name, array $arguments)
+ {
+ if (!count($arguments) && substr($name, 0, 3) == 'get') {
+ return $this->getArgument(substr($name, 3));
+ }
+ }
+
+ /**
+ * Checks to see if an event argument is assigned a value.
+ *
+ * @param string|int $offset Argument name or position beginning from 0
+ *
+ * @return bool TRUE if the argument has a value, FALSE otherwise
+ * @see ArrayAccess::offsetExists()
+ */
+ public function offsetExists($offset)
+ {
+ return ($this->resolveArgument($offset) !== null);
+ }
+
+ /**
+ * Returns the value of an event argument.
+ *
+ * @param string|int $offset Argument name or position beginning from 0
+ *
+ * @return string|null Argument value or NULL if none is set
+ * @see ArrayAccess::offsetGet()
+ */
+ public function offsetGet($offset)
+ {
+ return $this->getArgument($offset);
+ }
+
+ /**
+ * Sets the value of an event argument.
+ *
+ * @param string|int $offset Argument name or position beginning from 0
+ * @param string $value New argument value
+ *
+ * @return void
+ * @see ArrayAccess::offsetSet()
+ */
+ public function offsetSet($offset, $value)
+ {
+ $offset = $this->resolveArgument($offset);
+ if ($offset !== null) {
+ $this->arguments[$offset] = $value;
+ }
+ }
+
+ /**
+ * Removes the value set for an event argument.
+ *
+ * @param string|int $offset Argument name or position beginning from 0
+ *
+ * @return void
+ * @see ArrayAccess::offsetUnset()
+ */
+ public function offsetUnset($offset)
+ {
+ if ($offset = $this->resolveArgument($offset)) {
+ unset($this->arguments[$offset]);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Response.php b/plugins/Irc/extlib/phergie/Phergie/Event/Response.php
new file mode 100755
index 000000000..097e2535e
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Event/Response.php
@@ -0,0 +1,953 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Event originating from the server in response to an event sent by the
+ * current client.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ * @link http://www.irchelp.org/irchelp/rfc/chapter6.html
+ */
+class Phergie_Event_Response extends Phergie_Event_Abstract
+{
+ /**
+ * <nickname> No such nick/channel
+ *
+ * Used to indicate the nickname parameter supplied to a command is currently
+ * unused.
+ */
+ const ERR_NOSUCHNICK = '401';
+
+ /**
+ * <server name> No such server
+ *
+ * Used to indicate the server name given currently doesn't exist.
+ */
+ const ERR_NOSUCHSERVER = '402';
+
+ /**
+ * <channel name> No such channel
+ *
+ * Used to indicate the given channel name is invalid.
+ */
+ const ERR_NOSUCHCHANNEL = '403';
+
+ /**
+ * <channel name> Cannot send to channel
+ *
+ * Sent to a user who is either (a) not on a channel which is mode +n or (b) not
+ * a chanop (or mode +v) on a channel which has mode +m set and is trying to send
+ * a PRIVMSG message to that channel.
+ */
+ const ERR_CANNOTSENDTOCHAN = '404';
+
+ /**
+ * <channel name> You have joined too many channels
+ *
+ * Sent to a user when they have joined the maximum number of allowed channels
+ * and they try to join another channel.
+ */
+ const ERR_TOOMANYCHANNELS = '405';
+
+ /**
+ * <nickname> There was no such nickname
+ *
+ * Returned by WHOWAS to indicate there is no history information for that
+ * nickname.
+ */
+ const ERR_WASNOSUCHNICK = '406';
+
+ /**
+ * <target> Duplicate recipients. No message delivered
+ *
+ * Returned to a client which is attempting to send PRIVMSG/NOTICE using the
+ * user@host destination format and for a user@host which has several
+ * occurrences.
+ */
+ const ERR_TOOMANYTARGETS = '407';
+
+ /**
+ * No origin specified
+ *
+ * PING or PONG message missing the originator parameter which is required since
+ * these commands must work without valid prefixes.
+ */
+ const ERR_NOORIGIN = '409';
+
+ /**
+ * No recipient given (<command>)
+ */
+ const ERR_NORECIPIENT = '411';
+
+ /**
+ * No text to send
+ */
+ const ERR_NOTEXTTOSEND = '412';
+
+ /**
+ * <mask> No toplevel domain specified
+ */
+ const ERR_NOTOPLEVEL = '413';
+
+ /**
+ * <mask> Wildcard in toplevel domain
+ *
+ * 412 - 414 are returned by PRIVMSG to indicate that the message wasn't
+ * delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
+ * are returned when an invalid use of "PRIVMSG $<server>" or "PRIVMSG #<host>"
+ * is attempted.
+ */
+ const ERR_WILDTOPLEVEL = '414';
+
+ /**
+ * <command> Unknown command
+ *
+ * Returned to a registered client to indicate that the command sent is unknown
+ * by the server.
+ */
+ const ERR_UNKNOWNCOMMAND = '421';
+
+ /**
+ * MOTD File is missing
+ *
+ * Server's MOTD file could not be opened by the server.
+ */
+ const ERR_NOMOTD = '422';
+
+ /**
+ * <server> No administrative info available
+ *
+ * Returned by a server in response to an ADMIN message when there is an error in
+ * finding the appropriate information.
+ */
+ const ERR_NOADMININFO = '423';
+
+ /**
+ * File error doing <file op> on <file>
+ *
+ * Generic error message used to report a failed file operation during the
+ * processing of a message.
+ */
+ const ERR_FILEERROR = '424';
+
+ /**
+ * No nickname given
+ *
+ * Returned when a nickname parameter expected for a command and isn't found.
+ */
+ const ERR_NONICKNAMEGIVEN = '431';
+
+ /**
+ * <nick> Erroneus nickname
+ *
+ * Returned after receiving a NICK message which contains characters which do not
+ * fall in the defined set. See section x.x.x for details on valid nicknames.
+ */
+ const ERR_ERRONEUSNICKNAME = '432';
+
+ /**
+ * <nick> Nickname is already in use
+ *
+ * Returned when a NICK message is processed that results in an attempt to change
+ * to a currently existing nickname.
+ */
+ const ERR_NICKNAMEINUSE = '433';
+
+ /**
+ * <nick> Nickname collision KILL
+ *
+ * Returned by a server to a client when it detects a nickname collision
+ * (registered of a NICK that already exists by another server).
+ */
+ const ERR_NICKCOLLISION = '436';
+
+ /**
+ * <nick> <channel> They aren't on that channel
+ *
+ * Returned by the server to indicate that the target user of the command is not
+ * on the given channel.
+ */
+ const ERR_USERNOTINCHANNEL = '441';
+
+ /**
+ * <channel> You're not on that channel
+ *
+ * Returned by the server whenever a client tries to perform a channel effecting
+ * command for which the client isn't a member.
+ */
+ const ERR_NOTONCHANNEL = '442';
+
+ /**
+ * <user> <channel> is already on channel
+ *
+ * Returned when a client tries to invite a user to a channel they are already
+ * on.
+ */
+ const ERR_USERONCHANNEL = '443';
+
+ /**
+ * <user> User not logged in
+ *
+ * Returned by the summon after a SUMMON command for a user was unable to be
+ * performed since they were not logged in.
+ */
+ const ERR_NOLOGIN = '444';
+
+ /**
+ * SUMMON has been disabled
+ *
+ * Returned as a response to the SUMMON command. Must be returned by any server
+ * which does not implement it.
+ */
+ const ERR_SUMMONDISABLED = '445';
+
+ /**
+ * USERS has been disabled
+ *
+ * Returned as a response to the USERS command. Must be returned by any server
+ * which does not implement it.
+ */
+ const ERR_USERSDISABLED = '446';
+
+ /**
+ * You have not registered
+ *
+ * Returned by the server to indicate that the client must be registered before
+ * the server will allow it to be parsed in detail.
+ */
+ const ERR_NOTREGISTERED = '451';
+
+ /**
+ * <command> Not enough parameters
+ *
+ * Returned by the server by numerous commands to indicate to the client that it
+ * didn't supply enough parameters.
+ */
+ const ERR_NEEDMOREPARAMS = '461';
+
+ /**
+ * You may not reregister
+ *
+ * Returned by the server to any link which tries to change part of the
+ * registered details (such as password or user details from second USER
+ * message).
+ */
+ const ERR_ALREADYREGISTRED = '462';
+
+ /**
+ * Your host isn't among the privileged
+ *
+ * Returned to a client which attempts to register with a server which does not
+ * been setup to allow connections from the host the attempted connection is
+ * tried.
+ */
+ const ERR_NOPERMFORHOST = '463';
+
+ /**
+ * Password incorrect
+ *
+ * Returned to indicate a failed attempt at registering a connection for which a
+ * password was required and was either not given or incorrect.
+ */
+ const ERR_PASSWDMISMATCH = '464';
+
+ /**
+ * You are banned from this server
+ *
+ * Returned after an attempt to connect and register yourself with a server which
+ * has been setup to explicitly deny connections to you.
+ */
+ const ERR_YOUREBANNEDCREEP = '465';
+
+ /**
+ * <channel> Channel key already set
+ */
+ const ERR_KEYSET = '467';
+
+ /**
+ * <channel> Cannot join channel (+l)
+ */
+ const ERR_CHANNELISFULL = '471';
+
+ /**
+ * <char> is unknown mode char to me
+ */
+ const ERR_UNKNOWNMODE = '472';
+
+ /**
+ * <channel> Cannot join channel (+i)
+ */
+ const ERR_INVITEONLYCHAN = '473';
+
+ /**
+ * <channel> Cannot join channel (+b)
+ */
+ const ERR_BANNEDFROMCHAN = '474';
+
+ /**
+ * <channel> Cannot join channel (+k)
+ */
+ const ERR_BADCHANNELKEY = '475';
+
+ /**
+ * Permission Denied- You're not an IRC operator
+ *
+ * Any command requiring operator privileges to operate must return this error to
+ * indicate the attempt was unsuccessful.
+ */
+ const ERR_NOPRIVILEGES = '481';
+
+ /**
+ * <channel> You're not channel operator
+ *
+ * Any command requiring 'chanop' privileges (such as MODE messages) must return
+ * this error if the client making the attempt is not a chanop on the specified
+ * channel.
+ */
+ const ERR_CHANOPRIVSNEEDED = '482';
+
+ /**
+ * You cant kill a server!
+ *
+ * Any attempts to use the KILL command on a server are to be refused and this
+ * error returned directly to the client.
+ */
+ const ERR_CANTKILLSERVER = '483';
+
+ /**
+ * No O-lines for your host
+ *
+ * If a client sends an OPER message and the server has not been configured to
+ * allow connections from the client's host as an operator, this error must be
+ * returned.
+ */
+ const ERR_NOOPERHOST = '491';
+
+ /**
+ * Unknown MODE flag
+ *
+ * Returned by the server to indicate that a MODE message was sent with a
+ * nickname parameter and that the a mode flag sent was not recognized.
+ */
+ const ERR_UMODEUNKNOWNFLAG = '501';
+
+ /**
+ * Cant change mode for other users
+ *
+ * Error sent to any user trying to view or change the user mode for a user other
+ * than themselves.
+ */
+ const ERR_USERSDONTMATCH = '502';
+
+ /**
+ * Dummy reply number. Not used.
+ */
+ const RPL_NONE = '300';
+
+ /**
+ * [<reply>{<space><reply>}]
+ *
+ * Reply format used by USERHOST to list replies to the query list. The reply
+ * string is composed as follows <reply> = <nick>['*'] '=' <'+'|'-'><hostname>
+ * The '*' indicates whether the client has registered as an Operator. The '-' or
+ * '+' characters represent whether the client has set an AWAY message or not
+ * respectively.
+ */
+ const RPL_USERHOST = '302';
+
+ /**
+ * [<nick> {<space><nick>}]
+ *
+ * Reply format used by ISON to list replies to the query list.
+ */
+ const RPL_ISON = '303';
+
+ /**
+ * <nick> <away message>
+ */
+ const RPL_AWAY = '301';
+
+ /**
+ * You are no longer marked as being away
+ */
+ const RPL_UNAWAY = '305';
+
+ /**
+ * You have been marked as being away
+ *
+ * These replies are used with the AWAY command (if allowed). RPL_AWAY is sent to
+ * any client sending a PRIVMSG to a client which is away. RPL_AWAY is only sent
+ * by the server to which the client is connected. Replies RPL_UNAWAY and
+ * RPL_NOWAWAY are sent when the client removes and sets an AWAY message.
+ */
+ const RPL_NOWAWAY = '306';
+
+ /**
+ * <nick> <user> <host> * <real name>
+ */
+ const RPL_WHOISUSER = '311';
+
+ /**
+ * <nick> <server> <server info>
+ */
+ const RPL_WHOISSERVER = '312';
+
+ /**
+ * <nick> is an IRC operator
+ */
+ const RPL_WHOISOPERATOR = '313';
+
+ /**
+ * <nick> <integer> seconds idle
+ */
+ const RPL_WHOISIDLE = '317';
+
+ /**
+ * <nick> End of /WHOIS list
+ */
+ const RPL_ENDOFWHOIS = '318';
+
+ /**
+ * <nick> {[@|+]<channel><space>}
+ *
+ * Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS
+ * message. Given that there are enough parameters present, the answering server
+ * must either formulate a reply out of the above numerics (if the query nick is
+ * found) or return an error reply. The '*' in RPL_WHOISUSER is there as the
+ * literal character and not as a wild card. For each reply set, only
+ * RPL_WHOISCHANNELS may appear more than once (for long lists of channel names).
+ * The '@' and '+' characters next to the channel name indicate whether a client
+ * is a channel operator or has been granted permission to speak on a moderated
+ * channel. The RPL_ENDOFWHOIS reply is used to mark the end of processing a
+ * WHOIS message.
+ */
+ const RPL_WHOISCHANNELS = '319';
+
+ /**
+ * <nick> <user> <host> * <real name>
+ */
+ const RPL_WHOWASUSER = '314';
+
+ /**
+ * <nick> End of WHOWAS
+ *
+ * When replying to a WHOWAS message, a server must use the replies
+ * RPL_WHOWASUSER, RPL_WHOISSERVER or ERR_WASNOSUCHNICK for each nickname in the
+ * presented list. At the end of all reply batches, there must be RPL_ENDOFWHOWAS
+ * (even if there was only one reply and it was an error).
+ */
+ const RPL_ENDOFWHOWAS = '369';
+
+ /**
+ * Channel Users Name
+ */
+ const RPL_LISTSTART = '321';
+
+ /**
+ * <channel> <# visible> <topic>
+ */
+ const RPL_LIST = '322';
+
+ /**
+ * End of /LIST
+ *
+ * Replies RPL_LISTSTART, RPL_LIST, RPL_LISTEND mark the start, actual replies
+ * with data and end of the server's response to a LIST command. If there are no
+ * channels available to return, only the start and end reply must be sent.
+ */
+ const RPL_LISTEND = '323';
+
+ /**
+ * <channel> <mode> <mode params>
+ */
+ const RPL_CHANNELMODEIS = '324';
+
+ /**
+ * <channel> No topic is set
+ */
+ const RPL_NOTOPIC = '331';
+
+ /**
+ * <channel> <topic>
+ *
+ * When sending a TOPIC message to determine the channel topic, one of two
+ * replies is sent. If the topic is set, RPL_TOPIC is sent back else RPL_NOTOPIC.
+ */
+ const RPL_TOPIC = '332';
+
+ /**
+ * <channel> <nick>
+ *
+ * Returned by the server to indicate that the attempted INVITE message was
+ * successful and is being passed onto the end client.
+ */
+ const RPL_INVITING = '341';
+
+ /**
+ * <user> Summoning user to IRC
+ *
+ * Returned by a server answering a SUMMON message to indicate that it is
+ * summoning that user.
+ */
+ const RPL_SUMMONING = '342';
+
+ /**
+ * <version>.<debuglevel> <server> <comments>
+ *
+ * Reply by the server showing its version details. The <version> is the version
+ * of the software being used (including any patchlevel revisions) and the
+ * <debuglevel> is used to indicate if the server is running in "debug mode". The
+ * "comments" field may contain any comments about the version or further version
+ * details.
+ */
+ const RPL_VERSION = '351';
+
+ /**
+ * <channel> <user> <host> <server> <nick> <H|G>[*][@|+] <hopcount> <real name>
+ */
+ const RPL_WHOREPLY = '352';
+
+ /**
+ * <name> End of /WHO list
+ *
+ * The RPL_WHOREPLY and RPL_ENDOFWHO pair are used to answer a WHO message. The
+ * RPL_WHOREPLY is only sent if there is an appropriate match to the WHO query.
+ * If there is a list of parameters supplied with a WHO message, a RPL_ENDOFWHO
+ * must be sent after processing each list item with <name> being the item.
+ */
+ const RPL_ENDOFWHO = '315';
+
+ /**
+ * <channel> [[@|+]<nick> [[@|+]<nick> [...]]]
+ */
+ const RPL_NAMREPLY = '353';
+
+ /**
+ * <channel> End of /NAMES list
+ *
+ * To reply to a NAMES message, a reply pair consisting of RPL_NAMREPLY and
+ * RPL_ENDOFNAMES is sent by the server back to the client. If there is no
+ * channel found as in the query, then only RPL_ENDOFNAMES is returned. The
+ * exception to this is when a NAMES message is sent with no parameters and all
+ * visible channels and contents are sent back in a series of RPL_NAMEREPLY
+ * messages with a RPL_ENDOFNAMES to mark the end.
+ */
+ const RPL_ENDOFNAMES = '366';
+
+ /**
+ * <mask> <server> <hopcount> <server info>
+ */
+ const RPL_LINKS = '364';
+
+ /**
+ * <mask> End of /LINKS list
+ *
+ * In replying to the LINKS message, a server must send replies back using the
+ * RPL_LINKS numeric and mark the end of the list using an RPL_ENDOFLINKS reply.v
+ */
+ const RPL_ENDOFLINKS = '365';
+
+ /**
+ * <channel> <banid>
+ */
+ const RPL_BANLIST = '367';
+
+ /**
+ * <channel> End of channel ban list
+ *
+ * When listing the active 'bans' for a given channel, a server is required to
+ * send the list back using the RPL_BANLIST and RPL_ENDOFBANLIST messages. A
+ * separate RPL_BANLIST is sent for each active banid. After the banids have been
+ * listed (or if none present) a RPL_ENDOFBANLIST must be sent.
+ */
+ const RPL_ENDOFBANLIST = '368';
+
+ /**
+ * <string>
+ */
+ const RPL_INFO = '371';
+
+ /**
+ * End of /INFO list
+ *
+ * A server responding to an INFO message is required to send all its 'info' in a
+ * series of RPL_INFO messages with a RPL_ENDOFINFO reply to indicate the end of
+ * the replies.
+ */
+ const RPL_ENDOFINFO = '374';
+
+ /**
+ * - <server> Message of the day -
+ */
+ const RPL_MOTDSTART = '375';
+
+ /**
+ * - <text>
+ */
+ const RPL_MOTD = '372';
+
+ /**
+ * End of /MOTD command
+ *
+ * When responding to the MOTD message and the MOTD file is found, the file is
+ * displayed line by line, with each line no longer than 80 characters, using
+ * RPL_MOTD format replies. These should be surrounded by a RPL_MOTDSTART (before
+ * the RPL_MOTDs) and an RPL_ENDOFMOTD (after).
+ */
+ const RPL_ENDOFMOTD = '376';
+
+ /**
+ * You are now an IRC operator
+ *
+ * RPL_YOUREOPER is sent back to a client which has just successfully issued an
+ * OPER message and gained operator status.
+ */
+ const RPL_YOUREOPER = '381';
+
+ /**
+ * <config file> Rehashing
+ *
+ * If the REHASH option is used and an operator sends a REHASH message, an
+ * RPL_REHASHING is sent back to the operator.
+ */
+ const RPL_REHASHING = '382';
+
+ /**
+ * <server> <string showing server's local time>
+ *
+ * When replying to the TIME message, a server must send the reply using the
+ * RPL_TIME format above. The string showing the time need only contain the
+ * correct day and time there. There is no further requirement for the time
+ * string.
+ */
+ const RPL_TIME = '391';
+
+ /**
+ * UserID Terminal Host
+ */
+ const RPL_USERSSTART = '392';
+
+ /**
+ * %-8s %-9s %-8s
+ */
+ const RPL_USERS = '393';
+
+ /**
+ * End of users
+ */
+ const RPL_ENDOFUSERS = '394';
+
+ /**
+ * Nobody logged in
+ *
+ * If the USERS message is handled by a server, the replies RPL_USERSTART,
+ * RPL_USERS, RPL_ENDOFUSERS and RPL_NOUSERS are used. RPL_USERSSTART must be
+ * sent first, following by either a sequence of RPL_USERS or a single
+ * RPL_NOUSER. Following this is RPL_ENDOFUSERS.
+ */
+ const RPL_NOUSERS = '395';
+
+ /**
+ * Link <version & debug level> <destination> <next server>
+ */
+ const RPL_TRACELINK = '200';
+
+ /**
+ * Try. <class> <server>
+ */
+ const RPL_TRACECONNECTING = '201';
+
+ /**
+ * H.S. <class> <server>
+ */
+ const RPL_TRACEHANDSHAKE = '202';
+
+ /**
+ * ???? <class> [<client IP address in dot form>]
+ */
+ const RPL_TRACEUNKNOWN = '203';
+
+ /**
+ * Oper <class> <nick>
+ */
+ const RPL_TRACEOPERATOR = '204';
+
+ /**
+ * User <class> <nick>
+ */
+ const RPL_TRACEUSER = '205';
+
+ /**
+ * Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>
+ */
+ const RPL_TRACESERVER = '206';
+
+ /**
+ * <newtype> 0 <client name>
+ */
+ const RPL_TRACENEWTYPE = '208';
+
+ /**
+ * File <logfile> <debug level>
+ *
+ * The RPL_TRACE* are all returned by the server in response to the TRACE
+ * message. How many are returned is dependent on the the TRACE message and
+ * whether it was sent by an operator or not. There is no predefined order for
+ * which occurs first. Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
+ * RPL_TRACEHANDSHAKE are all used for connections which have not been fully
+ * established and are either unknown, still attempting to connect or in the
+ * process of completing the 'server handshake'. RPL_TRACELINK is sent by any
+ * server which handles a TRACE message and has to pass it on to another server.
+ * The list of RPL_TRACELINKs sent in response to a TRACE command traversing the
+ * IRC network should reflect the actual connectivity of the servers themselves
+ * along that path. RPL_TRACENEWTYPE is to be used for any connection which does
+ * not fit in the other categories but is being displayed anyway.
+ */
+ const RPL_TRACELOG = '261';
+
+ /**
+ * <linkname> <sendq> <sent messages> <sent bytes> <received messages> <received
+ * bytes> <time open>
+ */
+ const RPL_STATSLINKINFO = '211';
+
+ /**
+ * <command> <count>
+ */
+ const RPL_STATSCOMMANDS = '212';
+
+ /**
+ * C <host> * <name> <port> <class>
+ */
+ const RPL_STATSCLINE = '213';
+
+ /**
+ * N <host> * <name> <port> <class>
+ */
+ const RPL_STATSNLINE = '214';
+
+ /**
+ * I <host> * <host> <port> <class>
+ */
+ const RPL_STATSILINE = '215';
+
+ /**
+ * K <host> * <username> <port> <class>
+ */
+ const RPL_STATSKLINE = '216';
+
+ /**
+ * Y <class> <ping frequency> <connect frequency> <max sendq>
+ */
+ const RPL_STATSYLINE = '218';
+
+ /**
+ * <stats letter> End of /STATS report
+ */
+ const RPL_ENDOFSTATS = '219';
+
+ /**
+ * L <hostmask> * <servername> <maxdepth>
+ */
+ const RPL_STATSLLINE = '241';
+
+ /**
+ * Server Up %d days %d%02d%02d
+ */
+ const RPL_STATSUPTIME = '242';
+
+ /**
+ * O <hostmask> * <name>
+ */
+ const RPL_STATSOLINE = '243';
+
+ /**
+ * H <hostmask> * <servername>
+ */
+ const RPL_STATSHLINE = '244';
+
+ /**
+ * <user mode string>
+ *
+ * To answer a query about a client's own mode, RPL_UMODEIS is sent back.
+ */
+ const RPL_UMODEIS = '221';
+
+ /**
+ * There are <integer> users and <integer> invisible on <integer> servers
+ */
+ const RPL_LUSERCLIENT = '251';
+
+ /**
+ * <integer> operator(s) online
+ */
+ const RPL_LUSEROP = '252';
+
+ /**
+ * <integer> unknown connection(s)
+ */
+ const RPL_LUSERUNKNOWN = '253';
+
+ /**
+ * <integer> channels formed
+ */
+ const RPL_LUSERCHANNELS = '254';
+
+ /**
+ * I have <integer> clients and <integer> servers
+ *
+ * In processing an LUSERS message, the server sends a set of replies from
+ * RPL_LUSERCLIENT, RPL_LUSEROP, RPL_USERUNKNOWN, RPL_LUSERCHANNELS and
+ * RPL_LUSERME. When replying, a server must send back RPL_LUSERCLIENT and
+ * RPL_LUSERME. The other replies are only sent back if a non-zero count is found
+ * for them.
+ */
+ const RPL_LUSERME = '255';
+
+ /**
+ * <server> Administrative info
+ */
+ const RPL_ADMINME = '256';
+
+ /**
+ * <admin info>
+ */
+ const RPL_ADMINLOC1 = '257';
+
+ /**
+ * <admin info>
+ */
+ const RPL_ADMINLOC2 = '258';
+
+ /**
+ * <admin info>
+ *
+ * When replying to an ADMIN message, a server is expected to use replies
+ * RLP_ADMINME through to RPL_ADMINEMAIL and provide a text message with each.
+ * For RPL_ADMINLOC1 a description of what city, state and country the server is
+ * in is expected, followed by details of the university and department
+ * (RPL_ADMINLOC2) and finally the administrative contact for the server (an
+ * email address here is required) in RPL_ADMINEMAIL.
+ */
+ const RPL_ADMINEMAIL = '259';
+
+ /**
+ * Reply code sent by the server, which can be compared to the ERR_* and
+ * RPL_* constants
+ *
+ * @var string
+ */
+ protected $code;
+
+ /**
+ * Reply code description sent by the server.
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * Raw data sent by the server
+ *
+ * @var string
+ */
+ protected $rawData;
+
+ /**
+ * Event type
+ *
+ * @var string
+ */
+ protected $type = 'response';
+
+ /**
+ * Sets the reply code sent by the server.
+ *
+ * @param string $code Reply code
+ *
+ * @return Phergie_Event_Response Provides a fluent interface
+ */
+ public function setCode($code)
+ {
+ $this->code = $code;
+ return $this;
+ }
+
+ /**
+ * Returns the reply code sent by the server.
+ *
+ * @return string
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Sets the reply code description sent by the server.
+ *
+ * @param string $description Reply code description
+ *
+ * @return Phergie_Event_Response Provides a fluent interface
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+ return $this;
+ }
+
+ /**
+ * Returns the reply code description sent by the server.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Sets the raw buffer for the given event
+ *
+ * @param string $buffer Raw event buffer
+ *
+ * @return Phergie_Event_Response Provides a fluent interface
+ */
+ public function setRawData($buffer)
+ {
+ $this->rawData = $buffer;
+ return $this;
+ }
+
+ /**
+ * Returns the raw buffer that was sent from the server for that event
+ *
+ * @return string
+ */
+ public function getRawData()
+ {
+ return $this->rawData;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Exception.php
new file mode 100755
index 000000000..f4d71e531
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Exception.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for all Phergie-related exceptions.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Exception extends Exception
+{
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Hostmask.php b/plugins/Irc/extlib/phergie/Phergie/Hostmask.php
new file mode 100755
index 000000000..b13842f53
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Hostmask.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Data structure for a hostmask.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Hostmask
+{
+ /**
+ * Host
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * Nick
+ *
+ * @var string
+ */
+ protected $nick;
+
+ /**
+ * Username
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * Regular expression used to parse a hostmask
+ *
+ * @var string
+ */
+ protected static $regex = '/^([^!@]+)!(?:[ni]=)?([^@]+)@([^ ]+)/';
+
+ /**
+ * Constructor to initialize components of the hostmask.
+ *
+ * @param string $nick Nick component
+ * @param string $username Username component
+ * @param string $host Host component
+ *
+ * @return void
+ */
+ public function __construct($nick, $username, $host)
+ {
+ $this->nick = $nick;
+ $this->username = $username;
+ $this->host = $host;
+ }
+
+ /**
+ * Returns whether a given string appears to be a valid hostmask.
+ *
+ * @param string $string Alleged hostmask string
+ *
+ * @return bool TRUE if the string appears to be a valid hostmask, FALSE
+ * otherwise
+ */
+ public static function isValid($string)
+ {
+ return (preg_match(self::$regex, $string) > 0);
+ }
+
+ /**
+ * Parses a string containing the entire hostmask into a new instance of
+ * this class.
+ *
+ * @param string $hostmask Entire hostmask including the nick, username,
+ * and host components
+ *
+ * @return Phergie_Hostmask New instance populated with data parsed from
+ * the provided hostmask string
+ * @throws Phergie_Hostmask_Exception
+ */
+ public static function fromString($hostmask)
+ {
+ if (preg_match(self::$regex, $hostmask, $match)) {
+ list(, $nick, $username, $host) = $match;
+ return new self($nick, $username, $host);
+ }
+
+ throw new Phergie_Hostmask_Exception(
+ 'Invalid hostmask specified: "' . $hostmask . '"',
+ Phergie_Hostmask_Exception::ERR_INVALID_HOSTMASK
+ );
+ }
+
+ /**
+ * Sets the hostname.
+ *
+ * @param string $host Hostname
+ *
+ * @return Phergie_Hostmask Provides a fluent interface
+ */
+ public function setHost($host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ /**
+ * Returns the hostname.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Sets the username of the user.
+ *
+ * @param string $username Username
+ *
+ * @return Phergie_Hostmask Provides a fluent interface
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ /**
+ * Returns the username of the user.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Sets the nick of the user.
+ *
+ * @param string $nick User nick
+ *
+ * @return Phergie_Hostmask Provides a fluent interface
+ */
+ public function setNick($nick)
+ {
+ $this->nick = $nick;
+
+ return $this;
+ }
+
+ /**
+ * Returns the nick of the user.
+ *
+ * @return string
+ */
+ public function getNick()
+ {
+ return $this->nick;
+ }
+
+ /**
+ * Returns the hostmask for the originating server or user.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->nick . '!' . $this->username . '@' . $this->host;
+ }
+
+ /**
+ * Returns whether a given hostmask matches a given pattern.
+ *
+ * @param string $pattern Pattern using conventions of a ban mask where
+ * represents a wildcard
+ * @param string $hostmask Optional hostmask to match against, if not
+ * the current hostmask instance
+ *
+ * @return bool TRUE if the hostmask matches the pattern, FALSE otherwise
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3 Examples
+ */
+ public function matches($pattern, $hostmask = null)
+ {
+ if (!$hostmask) {
+ $hostmask = (string) $this;
+ }
+
+ $pattern = str_replace('*', '.*', $pattern);
+
+ return (preg_match('#^' . $pattern . '$#', $hostmask) > 0);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Hostmask/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Hostmask/Exception.php
new file mode 100644
index 000000000..590f02004
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Hostmask/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to hostmask handling.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Hostmask_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that an invalid hostmask string was specified
+ */
+ const ERR_INVALID_HOSTMASK = 1;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php
new file mode 100755
index 000000000..b7105ecdb
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php
@@ -0,0 +1,605 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for plugins to provide event handler stubs and commonly needed
+ * functionality.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+abstract class Phergie_Plugin_Abstract
+{
+ /**
+ * Current configuration handler
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Plugin handler used to provide access to other plugins
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Current event handler instance for outgoing events
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Current connection instance
+ *
+ * @var Phergie_Connection
+ */
+ protected $connection;
+
+ /**
+ * Current incoming event being handled
+ *
+ * @var Phergie_Event_Request|Phergie_Event_Response
+ */
+ protected $event;
+
+ /**
+ * Plugin short name
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * Returns the short name for the plugin based on its class name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ if (empty($this->name)) {
+ $this->name = substr(strrchr(get_class($this), '_'), 1);
+ }
+ return $this->name;
+ }
+
+ /**
+ * Sets the short name for the plugin.
+ *
+ * @param string $name Plugin short name
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setName($name)
+ {
+ $this->name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Indicates that the plugin failed to load due to an unsatisfied
+ * runtime requirement, such as a missing dependency.
+ *
+ * @param string $message Error message to provide more information
+ * about the reason for the failure
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ * @throws Phergie_Plugin_Exception Always
+ */
+ protected function fail($message)
+ {
+ throw new Phergie_Plugin_Exception(
+ $message,
+ Phergie_Plugin_Exception::ERR_REQUIREMENT_UNSATISFIED
+ );
+ }
+
+ /**
+ * Sets the current configuration handler.
+ *
+ * @param Phergie_Config $config Configuration handler
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setConfig(Phergie_Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Returns the current configuration handler or the value of a single
+ * setting from it.
+ *
+ * @param string $name Optional name of a setting for which the value
+ * should be returned instead of the entire configuration handler
+ * @param mixed $default Optional default value to return if no value
+ * is set for the setting indicated by $name
+ *
+ * @return Phergie_Config|mixed Configuration handler or value of the
+ * setting specified by $name
+ * @throws Phergie_Plugin_Exception No configuration handler has been set
+ */
+ public function getConfig($name = null, $default = null)
+ {
+ if (empty($this->config)) {
+ throw new Phergie_Plugin_Exception(
+ 'Configuration handler cannot be accessed before one is set',
+ Phergie_Plugin_Exception::ERR_NO_CONFIG_HANDLER
+ );
+ }
+ if (!is_null($name)) {
+ if (!isset($this->config[$name])) {
+ return $default;
+ }
+ return $this->config[$name];
+ }
+ return $this->config;
+ }
+
+ /**
+ * Sets the current plugin handler.
+ *
+ * @param Phergie_Plugin_Handler $handler Plugin handler
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setPluginHandler(Phergie_Plugin_Handler $handler)
+ {
+ $this->plugins = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns the current plugin handler.
+ *
+ * @return Phergie_Plugin_Handler
+ * @throws Phergie_Plugin_Exception No plugin handler has been set
+ */
+ public function getPluginHandler()
+ {
+ if (empty($this->plugins)) {
+ throw new Phergie_Plugin_Exception(
+ 'Plugin handler cannot be accessed before one is set',
+ Phergie_Plugin_Exception::ERR_NO_PLUGIN_HANDLER
+ );
+ }
+ return $this->plugins;
+ }
+
+ /**
+ * Sets the current event handler.
+ *
+ * @param Phergie_Event_Handler $handler Event handler
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setEventHandler(Phergie_Event_Handler $handler)
+ {
+ $this->events = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns the current event handler.
+ *
+ * @return Phergie_Event_Handler
+ * @throws Phergie_Plugin_Exception No event handler has been set
+ */
+ public function getEventHandler()
+ {
+ if (empty($this->events)) {
+ throw new Phergie_Plugin_Exception(
+ 'Event handler cannot be accessed before one is set',
+ Phergie_Plugin_Exception::ERR_NO_EVENT_HANDLER
+ );
+ }
+ return $this->events;
+ }
+
+ /**
+ * Sets the current connection.
+ *
+ * @param Phergie_Connection $connection Connection
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setConnection(Phergie_Connection $connection)
+ {
+ $this->connection = $connection;
+ return $this;
+ }
+
+ /**
+ * Returns the current event connection.
+ *
+ * @return Phergie_Connection
+ * @throws Phergie_Plugin_Exception No connection has been set
+ */
+ public function getConnection()
+ {
+ if (empty($this->connection)) {
+ throw new Phergie_Plugin_Exception(
+ 'Connection cannot be accessed before one is set',
+ Phergie_Plugin_Exception::ERR_NO_CONNECTION
+ );
+ }
+ return $this->connection;
+ }
+
+ /**
+ * Sets the current incoming event to be handled.
+ *
+ * @param Phergie_Event_Request|Phergie_Event_Response $event Event
+ *
+ * @return Phergie_Plugin_Abstract Provides a fluent interface
+ */
+ public function setEvent($event)
+ {
+ $this->event = $event;
+ return $this;
+ }
+
+ /**
+ * Returns the current incoming event to be handled.
+ *
+ * @return Phergie_Event_Request|Phergie_Event_Response
+ */
+ public function getEvent()
+ {
+ if (empty($this->event)) {
+ throw new Phergie_Plugin_Exception(
+ 'Event cannot be accessed before one is set',
+ Phergie_Plugin_Exception::ERR_NO_EVENT
+ );
+ }
+ return $this->event;
+ }
+
+ /**
+ * Provides do* methods with signatures identical to those of
+ * Phergie_Driver_Abstract but that queue up events to be dispatched
+ * later.
+ *
+ * @param string $name Name of the method called
+ * @param array $args Arguments passed in the call
+ *
+ * @return mixed
+ */
+ public function __call($name, array $args)
+ {
+ $subcmd = substr($name, 0, 2);
+ if ($subcmd == 'do') {
+ $type = strtolower(substr($name, 2));
+ $this->getEventHandler()->addEvent($this, $type, $args);
+ } else if ($subcmd != 'on') {
+ throw new Phergie_Plugin_Exception(
+ 'Called invalid method ' . $name . ' in ' . get_class($this),
+ Phergie_Plugin_Exception::ERR_INVALID_CALL
+ );
+ }
+ }
+
+ /**
+ * Handler for when the plugin is initially loaded - useful for checking
+ * runtime dependencies or performing any setup necessary for the plugin
+ * to function properly such as initializing a database.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ }
+
+ /**
+ * Handler for when the bot initially connects to a server.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ }
+
+ /**
+ * Handler for each tick, a single iteration of the continuous loop
+ * executed by the bot to receive, handle, and send events - useful for
+ * repeated execution of tasks on a time interval.
+ *
+ * @return void
+ */
+ public function onTick()
+ {
+ }
+
+ /**
+ * Handler for when any event is received but has not yet been dispatched
+ * to the plugin handler method specific to its event type.
+ *
+ * @return bool|null|void FALSE to short-circuit further event
+ * processing, TRUE or NULL otherwise
+ */
+ public function preEvent()
+ {
+ }
+
+ /**
+ * Handler for after plugin processing of an event has concluded but
+ * before any events triggered in response by plugins are sent to the
+ * server - useful for modifying outgoing events before they are sent.
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ }
+
+ /**
+ * Handler for after any events triggered by plugins in response to a
+ * received event are sent to the server.
+ *
+ * @return void
+ */
+ public function postDispatch()
+ {
+ }
+
+ /**
+ * Handler for when the server prompts the client for a nick.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
+ */
+ public function onNick()
+ {
+ }
+
+ /**
+ * Handler for when a user obtains operator privileges.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_5
+ */
+ public function onOper()
+ {
+ }
+
+ /**
+ * Handler for when the client session is about to be terminated.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
+ */
+ public function onQuit()
+ {
+ }
+
+ /**
+ * Handler for when a user joins a channel.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
+ */
+ public function onJoin()
+ {
+ }
+
+ /**
+ * Handler for when a user leaves a channel.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
+ */
+ public function onPart()
+ {
+ }
+
+ /**
+ * Handler for when a user or channel mode is changed.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
+ */
+ public function onMode()
+ {
+ }
+
+ /**
+ * Handler for when a channel topic is viewed or changed.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
+ */
+ public function onTopic()
+ {
+ }
+
+ /**
+ * Handler for when a message is received from a channel or user.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
+ */
+ public function onPrivmsg()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a CTCP ACTION request.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.4
+ */
+ public function onAction()
+ {
+ }
+
+ /**
+ * Handler for when a notice is received.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
+ */
+ public function onNotice()
+ {
+ }
+
+ /**
+ * Handler for when a user is kicked from a channel.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
+ */
+ public function onKick()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a ping event from a server, at
+ * which point it is expected to respond with a pong request within
+ * a short period else the server may terminate its connection.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
+ */
+ public function onPing()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a CTCP TIME request.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.6
+ */
+ public function onTime()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a CTCP VERSION request.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.1
+ */
+ public function onVersion()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a CTCP PING request.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.2
+ */
+ public function onCtcpPing()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a CTCP request of an unknown type.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html
+ */
+ public function onCtcp()
+ {
+ }
+
+ /**
+ * Handler for when a reply is received for a CTCP PING request sent by
+ * the bot.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.2
+ */
+ public function onPingReply()
+ {
+ }
+
+ /**
+ * Handler for when a reply is received for a CTCP TIME request sent by
+ * the bot.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.6
+ */
+ public function onTimeReply()
+ {
+ }
+
+ /**
+ * Handler for when a reply is received for a CTCP VERSION request sent
+ * by the bot.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html#4.1
+ */
+ public function onVersionReply()
+ {
+ }
+
+ /**
+ * Handler for when a reply received for a CTCP request of an unknown
+ * type.
+ *
+ * @return void
+ * @link http://www.invlogic.com/irc/ctcp.html
+ */
+ public function onCtcpReply()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives a kill request from a server.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_1
+ */
+ public function onKill()
+ {
+ }
+
+ /**
+ * Handler for when the bot receives an invitation to join a channel.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
+ */
+ public function onInvite()
+ {
+ }
+
+ /**
+ * Handler for when a server response is received to a command issued by
+ * the bot.
+ *
+ * @return void
+ * @link http://irchelp.org/irchelp/rfc/chapter6.html
+ */
+ public function onResponse()
+ {
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php
new file mode 100755
index 000000000..e209e32e1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Acl
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Acl
+ */
+
+/**
+ * Provides an access control system to limit reponses to events based on
+ * the users who originate them.
+ *
+ * Configuration settings:
+ * acl.whitelist - mapping of user hostmask patterns (optionally by host) to
+ * plugins and methods where those plugins and methods will
+ * only be accessible to those users (i.e. and inaccessible
+ * to other users)
+ * acl.blacklist - mapping of user hostmasks (optionally by host) to plugins
+ * and methods where where those plugins and methods will be
+ * inaccessible to those users but accessible to other users
+ * acl.ops - TRUE to automatically give access to whitelisted plugins
+ * and methods to users with ops for events they initiate in
+ * channels where they have ops
+ *
+ * The whitelist and blacklist settings are formatted like so:
+ * <code>
+ * 'acl.whitelist' => array(
+ * 'hostname1' => array(
+ * 'pattern1' => array(
+ * 'plugins' => array(
+ * 'ShortPluginName'
+ * ),
+ * 'methods' => array(
+ * 'methodName'
+ * )
+ * ),
+ * )
+ * ),
+ * </code>
+ *
+ * The hostname array dimension is optional; if not used, rules will be
+ * applied across all connections. The pattern is a user hostmask pattern
+ * where asterisks (*) are used for wildcards. Plugins and methods do not
+ * need to be set to empty arrays if they are not used; simply exclude them.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Acl
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Acl
+ * @uses Phergie_Plugin_UserInfo pear.phergie.org
+ */
+class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for permission settings and removes the plugin if none are set.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->plugins->getPlugin('UserInfo');
+
+ if (!$this->getConfig('acl.blacklist')
+ && !$this->getConfig('acl.whitelist')
+ ) {
+ $this->plugins->removePlugin($this);
+ }
+ }
+
+ /**
+ * Applies a set of rules to a plugin handler iterator.
+ *
+ * @param Phergie_Plugin_Iterator $iterator Iterator to receive rules
+ * @param array $rules Associate array containing
+ * either a 'plugins' key pointing to an array containing plugin
+ * short names to filter, a 'methods' key pointing to an array
+ * containing method names to filter, or both
+ *
+ * @return void
+ */
+ protected function applyRules(Phergie_Plugin_Iterator $iterator, array $rules)
+ {
+ if (!empty($rules['plugins'])) {
+ $iterator->addPluginFilter($rules['plugins']);
+ }
+ if (!empty($rules['methods'])) {
+ $iterator->addMethodFilter($rules['methods']);
+ }
+ }
+
+ /**
+ * Checks permission settings and short-circuits event processing for
+ * blacklisted users.
+ *
+ * @return void
+ */
+ public function preEvent()
+ {
+ // Ignore server responses
+ if ($this->event instanceof Phergie_Event_Response) {
+ return;
+ }
+
+ // Ignore server-initiated events
+ if (!$this->event->isFromUser()) {
+ return;
+ }
+
+ // Get the iterator used to filter plugins when processing events
+ $iterator = $this->plugins->getIterator();
+
+ // Get configuration setting values
+ $whitelist = $this->getConfig('acl.whitelist', array());
+ $blacklist = $this->getConfig('acl.blacklist', array());
+ $ops = $this->getConfig('acl.ops', false);
+
+ // Support host-specific lists
+ $host = $this->connection->getHost();
+ foreach (array('whitelist', 'blacklist') as $var) {
+ foreach ($$var as $pattern => $rules) {
+ $regex = '/^' . str_replace('*', '.*', $pattern) . '$/i';
+ if (preg_match($regex, $host)) {
+ ${$var} = ${$var}[$pattern];
+ break;
+ }
+ }
+ }
+
+ // Get information on the user initiating the current event
+ $hostmask = $this->event->getHostmask();
+ $isOp = $ops
+ && $this->event->isInChannel()
+ && $this->plugins->userInfo->isOp(
+ $this->event->getNick(),
+ $this->event->getSource()
+ );
+
+ // Filter whitelisted commands if the user is not on the whitelist
+ if (!$isOp) {
+ $whitelisted = false;
+ foreach ($whitelist as $pattern => $rules) {
+ if ($hostmask->matches($pattern)) {
+ $whitelisted = true;
+ }
+ }
+ if (!$whitelisted) {
+ foreach ($whitelist as $pattern => $rules) {
+ $this->applyRules($iterator, $rules);
+ }
+ }
+ }
+
+ // Filter blacklisted commands if the user is on the blacklist
+ $blacklisted = false;
+ foreach ($blacklist as $pattern => $rules) {
+ if ($hostmask->matches($pattern)) {
+ $this->applyRules($iterator, $rules);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Clears filters on the plugin handler iterator.
+ *
+ * @return void
+ */
+ public function postDispatch()
+ {
+ $this->plugins->getIterator()->clearFilters();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/AltNick.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/AltNick.php
new file mode 100755
index 000000000..16d5f9b9c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/AltNick.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AltNick
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AltNick
+ */
+
+/**
+ * Handles switching to alternate nicks in cases where the primary nick is
+ * not available for use.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AltNick
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AltNick
+ * @uses extension spl
+ */
+class Phergie_Plugin_AltNick extends Phergie_Plugin_Abstract
+{
+ /**
+ * Iterator for the alternate nick list
+ *
+ * @var ArrayIterator
+ */
+ protected $iterator;
+
+ /**
+ * Initializes instance variables.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ if ($this->config['altnick.nicks']) {
+ if (is_string($this->config['altnick.nicks'])) {
+ $this->config['altnick.nicks']
+ = array($this->config['altnick.nicks']);
+ }
+ $this->iterator = new ArrayIterator($this->config['altnick.nicks']);
+ }
+ }
+
+ /**
+ * Switches to alternate nicks as needed when nick collisions occur.
+ *
+ * @return void
+ */
+ public function onResponse()
+ {
+ // If no alternate nick list was found, return
+ if (empty($this->iterator)) {
+ return;
+ }
+
+ // If the response event indicates that the nick set is in use...
+ $code = $this->getEvent()->getCode();
+ if ($code == Phergie_Event_Response::ERR_NICKNAMEINUSE) {
+
+ // Attempt to move to the next nick in the alternate nick list
+ $this->iterator->next();
+
+ // If another nick is available...
+ if ($this->iterator->valid()) {
+
+ // Switch to the new nick
+ $altNick = $this->iterator->current();
+ $this->doNick($altNick);
+
+ // Update the connection to reflect the nick change
+ $this->getConnection()->setNick($altNick);
+
+ } else {
+ // If no other nicks are available...
+
+ // Terminate the connection
+ $this->doQuit('All specified alternate nicks are in use');
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/AudioScrobbler.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/AudioScrobbler.php
new file mode 100755
index 000000000..ed4030a28
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/AudioScrobbler.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AudioScrobbler
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AudioScrobbler
+ */
+
+/**
+ * Provides commands to look up information on tracks played by specific
+ * users on the Last.fm and Libre.fm services.
+ *
+ * TODO: Make the "nick-binding" use an SQLite database instead of having them
+ * hard-coded in to the config file.
+ *
+ * Configuration settings:
+ * "audioscrobbler.lastfm_api_key": API given by last.fm (string).
+ * "audioscrobbler.librefm_api_key": API key given by libre.fm (string).
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AudioScrobbler
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AudioScrobbler
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ * @uses extension simplexml
+ */
+class Phergie_Plugin_AudioScrobbler extends Phergie_Plugin_Abstract
+{
+ /**
+ * Last.FM API entry point
+ *
+ * @var string
+ */
+ protected $lastfmUrl = 'http://ws.audioscrobbler.com/2.0/';
+
+ /**
+ * Libre.FM API entry point
+ *
+ * @var string
+ */
+ protected $librefmUrl = 'http://alpha.dev.libre.fm/2.0/';
+
+ /**
+ * Scrobbler query string for user.getRecentTracks
+ *
+ * @var string
+ */
+ protected $query = '?method=user.getrecenttracks&user=%s&api_key=%s';
+
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http;
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('simplexml')) {
+ $this->fail('SimpleXML php extension is required');
+ }
+
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $this->http = $plugins->getPlugin('Http');
+ }
+
+ /**
+ * Command function to get a user's status on last.fm.
+ *
+ * @param string $user User identifier
+ *
+ * @return void
+ */
+ public function onCommandLastfm($user = null)
+ {
+ if ($key = $this->config['audioscrobbler.lastfm_api_key']) {
+ $scrobbled = $this->getScrobbled($user, $this->lastfmUrl, $key);
+ if ($scrobbled) {
+ $this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
+ }
+ }
+ }
+
+ /**
+ * Command function to get a user's status on libre.fm.
+ *
+ * @param string $user User identifier
+ *
+ * @return void
+ */
+ public function onCommandLibrefm($user = null)
+ {
+ if ($key = $this->config['audioscrobbler.librefm_api_key']) {
+ $scrobbled = $this->getScrobbled($user, $this->librefmUrl, $key);
+ if ($scrobbled) {
+ $this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
+ }
+ }
+ }
+
+ /**
+ * Simple Scrobbler API function to get a formatted string of the most
+ * recent track played by a user.
+ *
+ * @param string $user Username to look up
+ * @param string $url Base URL of the scrobbler service
+ * @param string $key Scrobbler service API key
+ *
+ * @return string Formatted string of the most recent track played
+ */
+ public function getScrobbled($user, $url, $key)
+ {
+ $event = $this->getEvent();
+ $user = $user ? $user : $event->getNick();
+ $url = sprintf($url . $this->query, urlencode($user), urlencode($key));
+
+ $response = $this->http->get($url);
+ if ($response->isError()) {
+ $this->doNotice(
+ $event->getNick(),
+ 'Can\'t find status for ' . $user . ': HTTP ' .
+ $response->getCode() . ' ' . $response->getMessage()
+ );
+ return false;
+ }
+
+ $xml = $response->getContent();
+ if ($xml->error) {
+ $this->doNotice(
+ $event->getNick(),
+ 'Can\'t find status for ' . $user . ': API ' . $xml->error
+ );
+ return false;
+ }
+
+ $recenttracks = $xml->recenttracks;
+ $track = $recenttracks->track[0];
+
+ // If the user exists but has not scrobbled anything, the result will
+ // be empty.
+ if (empty($track->name) && empty($track->artist)) {
+ $this->doNotice(
+ $event->getNick(),
+ 'Can\'t find track information for ' . $recenttracks['user']
+ );
+ return false;
+ }
+
+ if (isset($track['nowplaying'])) {
+ $msg = sprintf(
+ '%s is listening to %s by %s',
+ $recenttracks['user'],
+ $track->name,
+ $track->artist
+ );
+ } else {
+ $msg = sprintf(
+ '%s, %s was listening to %s by %s',
+ date('j M Y, H:i', (int) $track->date['uts']),
+ $recenttracks['user'],
+ $track->name,
+ $track->artist
+ );
+ }
+ if ($track->streamable == 1) {
+ $msg .= ' - ' . $track->url;
+ }
+ return $msg;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/AutoJoin.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/AutoJoin.php
new file mode 100755
index 000000000..a6d1adfbb
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/AutoJoin.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AutoJoin
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AutoJoin
+ */
+
+/**
+ * Automates the process of having the bot join one or more channels upon
+ * connection to the server.
+ *
+ * The configuration setting autojoin.channels is used to determine which
+ * channels to join. This setting can point to a comma-delimited string or
+ * enumerated array containing a single list of channels or an associative
+ * array keyed by hostname where each value is a comma-delimited string or
+ * enumerated array containing a list of channels to join on the server
+ * corresponding to that hostname.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_AutoJoin
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_AutoJoin
+ */
+class Phergie_Plugin_AutoJoin extends Phergie_Plugin_Abstract
+{
+ /**
+ * Intercepts the end of the "message of the day" response and responds by
+ * joining the channels specified in the configuration file.
+ *
+ * @return void
+ */
+ public function onResponse()
+ {
+ switch ($this->getEvent()->getCode()) {
+ case Phergie_Event_Response::RPL_ENDOFMOTD:
+ case Phergie_Event_Response::ERR_NOMOTD:
+ if ($channels = $this->config['autojoin.channels']) {
+ if (is_array($channels)) {
+ // Support autojoin.channels being in these formats:
+ // 'hostname' => array('#channel1', '#channel2', ... )
+ $host = $this->getConnection()->getHost();
+ if (isset($channels[$host])) {
+ $channels = $channels[$host];
+ }
+ if (is_array($channels)) {
+ $channels = implode(',', $channels);
+ }
+ }
+ $this->doJoin($channels);
+ }
+ $this->getPluginHandler()->removePlugin($this);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer.php
new file mode 100644
index 000000000..7213cdee8
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Beer
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Beer
+ */
+
+/**
+ * Processes requests to serve users beer.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Beer
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Beer
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Beer extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user a beer.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what beer to serve
+ *
+ * @return void
+ */
+ public function onCommandBeer($request)
+ {
+ $format = $this->getConfig(
+ 'beer.format',
+ 'throws %target% %article% %item%.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Beer/beer.db',
+ 'beer',
+ $format,
+ $request
+ );
+ }
+
+ /**
+ * Adds a "booze" alias for the "beer" command.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what beer to serve
+ *
+ * @return void
+ */
+ public function onCommandBooze($request)
+ {
+ $this->onCommandBeer($request);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php
new file mode 100644
index 000000000..c7921e519
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php
@@ -0,0 +1,81 @@
+<?php
+
+if (!defined('__DIR__')) {
+ define('__DIR__', dirname(__FILE__));
+}
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/beer.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE beer (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX beer_name ON beer (name)');
+$insert = $db->prepare('INSERT INTO beer (name, link) VALUES (:name, :link)');
+
+// Get raw beerme.com data set
+echo 'Downloading beerme.com data set', PHP_EOL;
+$file = __DIR__ . '/beerlist.txt';
+if (!file_exists($file)) {
+ copy('http://beerme.com/beerlist.php', $file);
+}
+$contents = file_get_contents($file);
+
+// Extract data from data set
+echo 'Processing beerme.com data', PHP_EOL;
+$contents = tidy_repair_string($contents);
+libxml_use_internal_errors(true);
+$doc = new DOMDocument;
+$doc->loadHTML($contents);
+libxml_clear_errors();
+$xpath = new DOMXPath($doc);
+$beers = $xpath->query('//table[@class="beerlist"]/tr/td[1]');
+$db->beginTransaction();
+foreach ($beers as $beer) {
+ $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $beer->textContent);
+ $name = preg_replace('/\h*\v+\h*/', '', $name);
+ $link = 'http://beerme.com' . $beer->childNodes->item(1)->getAttribute('href');
+ $insert->execute(array($name, $link));
+}
+$db->commit();
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unlink($file);
+
+// Get and decompress openbeerdb.com data set
+$archive = __DIR__ . '/beers.zip';
+if (!file_exists($archive)) {
+ echo 'Downloading openbeerdb.com data set', PHP_EOL;
+ copy('http://openbeerdb.googlecode.com/files/beers.zip', $archive);
+}
+
+echo 'Decompressing openbeerdb.com data set', PHP_EOL;
+$zip = new ZipArchive;
+$zip->open($archive);
+$zip->extractTo(__DIR__, 'beers/beers.csv');
+$zip->close();
+$file = __DIR__ . '/beers/beers.csv';
+
+// Extract data from data set
+echo 'Processing openbeerdb.com data', PHP_EOL;
+$fp = fopen($file, 'r');
+$columns = fgetcsv($fp, 0, '|');
+$db->beginTransaction();
+while ($line = fgetcsv($fp, 0, '|')) {
+ $line = array_combine($columns, $line);
+ $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $line['name']);
+ $name = preg_replace('/\h*\v+\h*/', '', $name);
+ $link = null;
+ $insert->execute(array($name, $link));
+}
+$db->commit();
+fclose($fp);
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unlink($file);
+unlink($archive);
+rmdir(__DIR__ . '/beers');
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/BeerScore.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/BeerScore.php
new file mode 100644
index 000000000..16c671f68
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/BeerScore.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_BeerScore
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_BeerScore
+ */
+
+/**
+ * Handles incoming requests for beer scores.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_BeerScore
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_BeerScore
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+class Phergie_Plugin_BeerScore extends Phergie_Plugin_Abstract
+{
+ /**
+ * Score result type
+ *
+ * @const string
+ */
+ const TYPE_SCORE = 'SCORE';
+
+ /**
+ * Search result type
+ *
+ * @const string
+ */
+ const TYPE_SEARCH = 'SEARCH';
+
+ /**
+ * Refine result type
+ *
+ * @const type
+ */
+ const TYPE_REFINE = 'REFINE';
+
+ /**
+ * Base API URL
+ *
+ * @const string
+ */
+ const API_BASE_URL = 'http://caedmon.net/beerscore/';
+
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->http = $this->getPluginHandler()->getPlugin('Http');
+ }
+
+ /**
+ * Handles beerscore commands.
+ *
+ * @param string $searchstring String to use in seaching for beer scores
+ *
+ * @return void
+ */
+ public function onCommandBeerscore($searchstring)
+ {
+ $event = $this->getEvent();
+ $target = $event->getNick();
+ $source = $event->getSource();
+
+ $apiurl = self::API_BASE_URL . rawurlencode($searchstring);
+ $response = $this->http->get($apiurl);
+
+ if ($response->isError()) {
+ $this->doNotice($target, 'Score not found (or failed to contact API)');
+ return;
+ }
+
+ $result = $response->getContent();
+ switch ($result->type) {
+ case self::TYPE_SCORE:
+ // small enough number to get scores
+ foreach ($result->beer as $beer) {
+ if ($beer->score === -1) {
+ $score = '(not rated)';
+ } else {
+ $score = $beer->score;
+ }
+ $str
+ = "{$target}: rating for {$beer->name}" .
+ " = {$score} ({$beer->url})";
+ $this->doPrivmsg($source, $str);
+ }
+ break;
+
+ case self::TYPE_SEARCH:
+ // only beer names, no scores
+ $str = '';
+ $found = 0;
+ foreach ($result->beer as $beer) {
+ if (isset($beer->score)) {
+ ++$found;
+ if ($beer->score === -1) {
+ $score = '(not rated)';
+ } else {
+ $score = $beer->score;
+ }
+ $str
+ = "{$target}: rating for {$beer->name}" .
+ " = {$score} ({$beer->url})";
+ $this->doPrivmsg($source, $str);
+ } else {
+ $str .= "({$beer->name} -> {$beer->url}) ";
+ }
+ }
+ $foundnum = $result->num - $found;
+ $more = $found ? 'more ' : '';
+ $str = "{$target}: {$foundnum} {$more}results... {$str}";
+ $this->doPrivmsg($source, $str);
+ break;
+
+ case self::TYPE_REFINE:
+ // Too many results; only output search URL
+ if ($result->num < 100) {
+ $num = $result->num;
+ } else {
+ $num = 'at least 100';
+ }
+ $resultsword = (($result->num > 1) ? 'results' : 'result');
+ $str = "{$target}: {$num} {$resultsword}; {$result->searchurl}";
+ $this->doPrivmsg($source, $str);
+ break;
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cache.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cache.php
new file mode 100644
index 000000000..2b54fab68
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cache.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cache
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cache
+ */
+
+/**
+ * Implements a generic cache to be used by other plugins.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cache
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cache
+ */
+class Phergie_Plugin_Cache extends Phergie_Plugin_Abstract
+{
+ /**
+ * Key-value data storage for the cache
+ *
+ * @var array
+ */
+ protected $cache = array();
+
+ /**
+ * Stores a value in the cache.
+ *
+ * @param string $key Key to associate with the value
+ * @param mixed $data Data to be stored
+ * @param int|null $ttl Time to live in seconds or NULL for forever
+ * @param bool $overwrite TRUE to overwrite any existing value
+ * associated with the specified key
+ *
+ * @return bool
+ */
+ public function store($key, $data, $ttl = 3600, $overwrite = true)
+ {
+ if (!$overwrite && isset($this->cache[$key])) {
+ return false;
+ }
+
+ if ($ttl) {
+ $expires = time()+$ttl;
+ } else {
+ $expires = null;
+ }
+
+ $this->cache[$key] = array('data' => $data, 'expires' => $expires);
+ return true;
+
+ }
+
+ /**
+ * Fetches a previously stored value.
+ *
+ * @param string $key Key associated with the value
+ *
+ * @return mixed Stored value or FALSE if no value or an expired value
+ * is associated with the specified key
+ */
+ public function fetch($key)
+ {
+ if (!isset($this->cache[$key])) {
+ return false;
+ }
+
+ $item = $this->cache[$key];
+ if (!is_null($item['expires']) && $item['expires'] < time()) {
+ $this->expire($key);
+ return false;
+ }
+
+ return $item['data'];
+ }
+
+ /**
+ * Expires a value that has exceeded its time to live.
+ *
+ * @param string $key Key associated with the value to expire
+ *
+ * @return bool
+ */
+ protected function expire($key)
+ {
+ if (!isset($this->cache[$key])) {
+ return false;
+ }
+ unset($this->cache[$key]);
+ return true;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php
new file mode 100644
index 000000000..2b76bd312
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Caffeine
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Caffeine
+ */
+
+/**
+ * Processes requests to serve users caffeinated beverages.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Caffeine
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Caffeine
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Caffeine extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user a caffeinated beverage.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what caffeinated beverage to serve
+ *
+ * @return void
+ */
+ public function onCommandCaffeine($request)
+ {
+ $format = $this->getConfig(
+ 'beer.format',
+ 'throws %target% %article% %item%.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Caffeine/caffeine.db',
+ 'caffeine',
+ $format,
+ $request
+ );
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php
new file mode 100644
index 000000000..cdff52fd8
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php
@@ -0,0 +1,51 @@
+<?php
+
+if (!defined('__DIR__')) {
+ define('__DIR__', dirname(__FILE__));
+}
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/caffeine.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE caffeine (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX caffeine_name ON caffeine (name)');
+$insert = $db->prepare('INSERT INTO caffeine (name, link) VALUES (:name, :link)');
+
+// Get raw energyfiend.com data set
+echo 'Downloading energyfiend.com data set', PHP_EOL;
+$file = __DIR__ . '/the-caffeine-database.html';
+if (!file_exists($file)) {
+ copy('http://www.energyfiend.com/the-caffeine-database', $file);
+}
+$contents = file_get_contents($file);
+
+// Extract data from data set
+echo 'Processing energyfiend.com data', PHP_EOL;
+$contents = tidy_repair_string($contents);
+libxml_use_internal_errors(true);
+$doc = new DOMDocument;
+$doc->loadHTML($contents);
+libxml_clear_errors();
+$xpath = new DOMXPath($doc);
+$caffeine = $xpath->query('//table[@id="caffeinedb"]//tr/td[1]');
+$db->beginTransaction();
+foreach ($caffeine as $drink) {
+ $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $drink->textContent);
+ $name = preg_replace('/\s*\v+\s*/', ' ', $name);
+ if ($drink->firstChild->nodeName == 'a') {
+ $link = 'http://energyfiend.com'
+ . $drink->firstChild->getAttribute('href');
+ } else {
+ $link = null;
+ }
+ $insert->execute(array($name, $link));
+}
+$db->commit();
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unlink($file);
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Censor.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Censor.php
new file mode 100755
index 000000000..99c69d873
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Censor.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Censor
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Censor
+ */
+
+/**
+ * Facilitates censoring of event content or discardment of events
+ * containing potentially offensive phrases depending on the value of the
+ * configuration setting censor.mode ('off', 'censor', 'discard'). Also
+ * provides access to a web service for detecting censored words so that
+ * other plugins may optionally integrate and adjust behavior accordingly to
+ * prevent discardment of events.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Censor
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Censor
+ * @uses extension soap
+ */
+class Phergie_Plugin_Censor extends Phergie_Plugin_Abstract
+{
+ /**
+ * SOAP client to interact with the CDYNE Profanity Filter API
+ *
+ * @var SoapClient
+ */
+ protected $soap;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('soap')) {
+ $this->fail('The PHP soap extension is required');
+ }
+
+ if (!in_array($this->config['censor.mode'], array('censor', 'discard'))) {
+ $this->plugins->removePlugin($this);
+ }
+ }
+
+ /**
+ * Returns a "clean" version of a given string.
+ *
+ * @param string $string String to clean
+ *
+ * @return string Cleaned string
+ */
+ public function cleanString($string)
+ {
+ if (empty($this->soap)) {
+ $this->soap = new SoapClient('http://ws.cdyne.com/ProfanityWS/Profanity.asmx?wsdl');
+ }
+ $params = array('Text' => $string);
+ $attempts = 0;
+ while ($attempts < 3) {
+ try {
+ $response = $this->soap->SimpleProfanityFilter($params);
+ break;
+ } catch (SoapFault $e) {
+ $attempts++;
+ sleep(1);
+ }
+ }
+ if ($attempts == 3) {
+ return $string;
+ }
+ return $response->SimpleProfanityFilterResult->CleanText;
+ }
+
+ /**
+ * Processes events before they are dispatched and either censors their
+ * content or discards them if they contain potentially offensive
+ * content.
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ $events = $this->events->getEvents();
+
+ foreach ($events as $event) {
+ switch ($event->getType()) {
+ case Phergie_Event_Request::TYPE_PRIVMSG:
+ case Phergie_Event_Request::TYPE_ACTION:
+ case Phergie_Event_Request::TYPE_NOTICE:
+ $text = $event->getArgument(1);
+ $clean = $this->cleanString($text);
+ if ($text != $clean) {
+ if ($this->config['censor.mode'] == 'censor') {
+ $event->setArgument(1, $clean);
+ } else {
+ $this->events->removeEvent($event);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail.php
new file mode 100644
index 000000000..eafeb657b
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cocktail
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cocktail
+ */
+
+/**
+ * Processes requests to serve users cocktail.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cocktail
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cocktail
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Cocktail extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user a cocktail.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what cocktail to serve
+ *
+ * @return void
+ */
+ public function onCommandCocktail($request)
+ {
+ $format = $this->getConfig(
+ 'cocktail.format',
+ 'throws %target% %article% %item%.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Cocktail/cocktail.db',
+ 'cocktail',
+ $format,
+ $request,
+ true
+ );
+ }
+}
+
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail/db.php
new file mode 100644
index 000000000..2e61dd0bd
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cocktail/db.php
@@ -0,0 +1,74 @@
+<?php
+
+if (!defined('__DIR__')) {
+ define('__DIR__', dirname(__FILE__));
+}
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/cocktail.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE cocktail (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX cocktail_name ON cocktail (name)');
+$insert = $db->prepare('INSERT INTO cocktail (name, link) VALUES (:name, :link)');
+
+// Get raw webtender.com data set
+echo 'Downloading webtender.com data set', PHP_EOL;
+$start = 1;
+do {
+ $file = __DIR__ . '/' . $start . '.html';
+ if (file_exists($file)) {
+ continue;
+ }
+ copy(
+ 'http://www.webtender.com/db/browse?level=2&dir=drinks&char=%2A&start=' . $start,
+ $file
+ );
+ if (!isset($limit)) {
+ $contents = file_get_contents($file);
+ preg_match('/([0-9]+) found/', $contents, $match);
+ $limit = $match[1] + (150 - ($match[1] % 150));
+ }
+ echo 'Got records ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;
+ $start += 150;
+} while ($start < $limit);
+
+// Extract data from data set
+$start = 1;
+while ($start < $limit) {
+ echo 'Processing ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;
+
+ $file = __DIR__ . '/' . $start . '.html';
+ $contents = file_get_contents($file);
+ $contents = tidy_repair_string($contents);
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument;
+ $doc->loadHTML($contents);
+ libxml_clear_errors();
+ $xpath = new DOMXPath($doc);
+
+ $cocktails = $xpath->query('//li/a');
+ $db->beginTransaction();
+ foreach ($cocktails as $cocktail) {
+ $name = $cocktail->nodeValue;
+ $name = preg_replace('/ The$|^The |\s*\([^)]+\)\s*| #[0-9]+$/', '', $name);
+ $name = html_entity_decode($name);
+ $link = 'http://www.webtender.com' . $cocktail->getAttribute('href');
+ $insert->execute(array($name, $link));
+ }
+ $db->commit();
+
+ $start += 150;
+}
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+$start = 1;
+while ($start < $limit) {
+ $file = __DIR__ . '/' . $start . '.html';
+ unlink($file);
+ $start += 150;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php
new file mode 100644
index 000000000..2058977ed
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Command
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Command
+ */
+
+/**
+ * Handles parsing and execution of commands sent by users via messages sent
+ * to channels in which the bot is present or directly to the bot.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Command
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Command
+ * @uses extension reflection
+ * @uses Phergie_Plugin_Message pear.phergie.org
+ */
+class Phergie_Plugin_Command extends Phergie_Plugin_Abstract
+{
+ /**
+ * Prefix for command method names
+ *
+ * @var string
+ */
+ const METHOD_PREFIX = 'onCommand';
+
+ /**
+ * Cache for command lookups used to confirm that methods exist and
+ * parameter counts match
+ *
+ * @var array
+ */
+ protected $methods = array();
+
+ /**
+ * Load the Message plugin
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Message');
+ }
+
+ /**
+ * Populates the methods cache.
+ *
+ * @return void
+ */
+ public function populateMethodCache()
+ {
+ foreach ($this->getPluginHandler()->getPlugins() as $plugin) {
+ $reflector = new ReflectionClass($plugin);
+ foreach ($reflector->getMethods() as $method) {
+ $name = $method->getName();
+ if (strpos($name, self::METHOD_PREFIX) === 0
+ && !isset($this->methods[$name])
+ ) {
+ $this->methods[$name] = array(
+ 'total' => $method->getNumberOfParameters(),
+ 'required' => $method->getNumberOfRequiredParameters()
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a given message and, if its format corresponds to that of a
+ * defined command, calls the handler method for that command with any
+ * provided parameters.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ // Populate the methods cache if needed
+ if (empty($this->methods)) {
+ $this->populateMethodCache();
+ }
+
+ // Check for a prefixed message
+ $msg = $this->plugins->message->getMessage();
+ if ($msg === false) {
+ return;
+ }
+
+ // Separate the command and arguments
+ $parsed = preg_split('/\s+/', $msg, 2);
+ $command = strtolower(array_shift($parsed));
+ $args = count($parsed) ? array_shift($parsed) : '';
+
+ // Resolve aliases to their corresponding commands
+ $aliases = $this->getConfig('command.aliases', array());
+ $result = preg_grep('/^' . preg_quote($command, '/') . '$/i', array_keys($aliases));
+ if ($result) {
+ $command = $aliases[array_shift($result)];
+ }
+
+ // Check to ensure the command exists
+ $method = self::METHOD_PREFIX . ucfirst($command);
+ if (empty($this->methods[$method])) {
+ return;
+ }
+
+ // If no arguments are passed...
+ if (empty($args)) {
+
+ // If the method requires no arguments, call it
+ if (empty($this->methods[$method]['required'])) {
+ $this->getPluginHandler()->$method();
+ }
+
+ } else {
+ // If arguments are passed...
+
+ // Parse the arguments
+ $args = preg_split('/\s+/', $args, $this->methods[$method]['total']);
+
+ // If the minimum arguments are passed, call the method
+ if ($this->methods[$method]['required'] <= count($args)) {
+ call_user_func_array(
+ array($this->getPluginHandler(), $method),
+ $args
+ );
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie.php
new file mode 100644
index 000000000..4bc2ceecb
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cookie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cookie
+ */
+
+/**
+ * Processes requests to serve users cookies.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cookie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cookie
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Cookie extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user a cookie.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what cookie to serve
+ *
+ * @return void
+ */
+ public function onCommandCookie($request)
+ {
+ $format = $this->getConfig(
+ 'cookie.format',
+ 'throws %target% %article% %item%.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Cookie/cookie.db',
+ 'cookies',
+ $format,
+ $request
+ );
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php
new file mode 100644
index 000000000..27763159c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php
@@ -0,0 +1,55 @@
+<?php
+
+if (!defined('__DIR__')) {
+ define('__DIR__', dirname(__FILE__));
+}
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/cookie.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE cookies (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX cookie_name ON cookies (name)');
+$insert = $db->prepare('INSERT INTO cookies (name, link) VALUES (:name, :link)');
+
+// Get Cookies list from http://en.wikipedia.org/wiki/List_of_cookies
+echo 'Downloading data from Wikipedia', PHP_EOL;
+$file = __DIR__ . '/cookieslist.txt';
+if (!file_exists($file)) {
+ copy('http://en.wikipedia.org/wiki/List_of_cookies', $file);
+}
+$contents = file_get_contents($file);
+
+// Extract data from data set
+echo 'Processing Wikipedia\'s cookies list', PHP_EOL;
+$contents = tidy_repair_string($contents);
+libxml_use_internal_errors(true);
+$doc = new DOMDocument;
+$doc->loadHTML($contents);
+libxml_clear_errors();
+$xpath = new DOMXPath($doc);
+
+$cookies = $xpath->query('//table[@width="90%"]/tr/td[1]/a');
+
+foreach ($cookies as $cookie) {
+ $name = $cookie->textContent;
+ $name = str_replace(
+ array('(',')',"\n", 'cookies'),
+ array('','', ' ', 'cookie'),
+ $name
+ );
+ $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $name);
+ $name = trim($name);
+ $name = rtrim($name, 's');
+
+ $link = 'http://en.wikipedia.org' . $cookie->getAttribute('href');
+ $insert->execute(array($name, $link));
+ echo 'added [' . $name . '] -> '. $link . PHP_EOL;
+}
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unlink($file);
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cron.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cron.php
new file mode 100644
index 000000000..d24910fa3
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cron.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cron
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cron
+ */
+
+/**
+ * Allows callbacks to be registered for asynchronous execution.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Cron
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Cron
+ */
+class Phergie_Plugin_Cron extends Phergie_Plugin_Abstract
+{
+ /**
+ * Array of all registered callbacks with delays and arguments
+ *
+ * @var array
+ */
+ protected $callbacks;
+
+ /**
+ * Returns a human-readable representation of a callback for debugging
+ * purposes.
+ *
+ * @param callback $callback Callback to analyze
+ *
+ * @return string|boolean String representation of the callback or FALSE
+ * if the specified value is not a valid callback
+ */
+ protected function getCallbackString($callback)
+ {
+ if (!is_callable($callback)) {
+ return false;
+ }
+
+ if (is_array($callback)) {
+ $class = is_string($callback[0]) ?
+ $callback[0] : get_class($callback[0]);
+ $method = $class . '::' . $callback[1];
+ return $method;
+ }
+
+ return $callback;
+ }
+
+ /**
+ * Registers a callback for execution sometime after a given delay
+ * relative to now.
+ *
+ * @param callback $callback Callback to be registered
+ * @param int $delay Delay in seconds from now when the callback
+ * will be executed
+ * @param array $arguments Arguments to pass to the callback when
+ * it's executed
+ * @param bool $repeat TRUE to automatically re-register the
+ * callback for the same delay after it's executed, FALSE
+ * otherwise
+ *
+ * @return void
+ */
+ public function registerCallback($callback, $delay,
+ array $arguments = array(), $repeat = false)
+ {
+ $callbackString = $this->getCallbackString($callback);
+ if ($callbackString === false) {
+ echo 'DEBUG(Cron): Invalid callback specified - ',
+ var_export($callback, true), PHP_EOL;
+ return;
+ }
+
+ $registered = time();
+ $scheduled = $registered + $delay;
+
+ $this->callbacks[] = array(
+ 'callback' => $callback,
+ 'delay' => $delay,
+ 'arguments' => $arguments,
+ 'registered' => $registered,
+ 'scheduled' => $scheduled,
+ 'repeat' => $repeat,
+ );
+
+ echo 'DEBUG(Cron): Callback ', $callbackString,
+ ' scheduled for ', date('H:i:s', $scheduled), PHP_EOL;
+ }
+
+ /**
+ * Handles callback execution.
+ *
+ * @return void
+ */
+ public function onTick()
+ {
+ $time = time();
+ foreach ($this->callbacks as $key => &$callback) {
+ $callbackString = $this->getCallbackString($callback);
+
+ $scheduled = $callback['scheduled'];
+ if ($time < $scheduled) {
+ continue;
+ }
+
+ if (empty($callback['arguments'])) {
+ call_user_func($callback['callback']);
+ } else {
+ call_user_func_array(
+ $callback['callback'],
+ $callback['arguments']
+ );
+ }
+
+ echo 'DEBUG(Cron): Callback ', $callbackString,
+ ' scheduled for ', date('H:i:s', $scheduled), ',',
+ ' executed at ', date('H:i:s', $now), PHP_EOL;
+
+ if ($callback['repeat']) {
+ $callback['scheduled'] = $time + $callback['delay'];
+ echo 'DEBUG(Cron): Callback ', $callbackString,
+ ' scheduled for ', date('H:i:s', $callback['scheduled']),
+ PHP_EOL;
+ } else {
+ echo 'DEBUG(Cron): Callback ', $callbackString,
+ ' removed from callback list', PHP_EOL;
+ unset($this->callbacks[$key]);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ctcp.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ctcp.php
new file mode 100755
index 000000000..e79fcf13e
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ctcp.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ctcp
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ctcp
+ */
+
+/**
+ * Responds to various CTCP requests sent by the server and users.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ctcp
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ctcp
+ * @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html
+ */
+class Phergie_Plugin_Ctcp extends Phergie_Plugin_Abstract
+{
+ /**
+ * Responds to a CTCP TIME request from a user with the current local
+ * time.
+ *
+ * @return void
+ */
+ public function onTime()
+ {
+ $source = $this->getEvent()->getSource();
+ $this->doTime($source, strftime('%c %z'));
+ }
+
+ /**
+ * Responds to a CTCP VERSION request from a user with the codebase
+ * version.
+ *
+ * @return void
+ */
+ public function onVersion()
+ {
+ $source = $this->getEvent()->getSource();
+ $msg = 'Phergie ' . Phergie_Bot::VERSION . ' (http://phergie.org)';
+ $this->doVersion($source, $msg);
+ }
+
+ /**
+ * Responds to a CTCP PING request from a user.
+ *
+ * @return void
+ */
+ public function onCtcpPing()
+ {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $handshake = $event->getArgument(1);
+ $this->doPing($source, $handshake);
+ }
+
+ /**
+ * Responds to a CTCP FINGER request from a user.
+ *
+ * @return void
+ */
+ public function onFinger()
+ {
+ $connection = $this->getConnection();
+ $name = $connection->getNick();
+ $realname = $connection->getRealname();
+ $username = $connection->getUsername();
+
+ $finger
+ = (empty($realname) ? $realname : $name) .
+ ' (' . (!empty($username) ? $username : $name) . ')';
+
+ $this->doFinger($source, $finger);
+ }
+}
+?>
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php
new file mode 100644
index 000000000..ed258e10f
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Daddy
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Daddy
+ */
+
+/**
+ * Simply responds to messages addressed to the bot that contain the phrase
+ * "Who's your daddy?" and related variations.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Daddy
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Daddy
+ */
+class Phergie_Plugin_Daddy extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks messages for the question to which it should respond and sends a
+ * response when appropriate
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $config = $this->getConfig();
+ $prefix = $config['command.prefix'];
+ $event = $this->getEvent();
+ $text = $event->getArgument(1);
+ $target = $event->getNick();
+ $source = $event->getSource();
+ $pattern
+ = '/' . preg_quote($prefix) .
+ '\s*?who\'?s y(?:our|a) ([^?]+)\??/iAD';
+ if (preg_match($pattern, $text, $m)) {
+ $msg = 'You\'re my ' . $m[1] . ', ' . $target . '!';
+ $this->doPrivmsg($source, $msg);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Encoding.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Encoding.php
new file mode 100644
index 000000000..419322bd7
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Encoding.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Encoding
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Encoding
+ */
+
+/**
+ * Handles decoding markup entities and converting text between character
+ * encodings.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Encoding
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Encoding
+ */
+class Phergie_Plugin_Encoding extends Phergie_Plugin_Abstract
+{
+ /**
+ * Lookup table for entity conversions not supported by
+ * html_entity_decode()
+ *
+ * @var array
+ * @link http://us.php.net/manual/en/function.get-html-translation-table.php#73409
+ * @link http://us.php.net/manual/en/function.get-html-translation-table.php#73410
+ */
+ protected static $entities = array(
+ '&alpha;' => 913,
+ '&apos;' => 39,
+ '&beta;' => 914,
+ '&bull;' => 149,
+ '&chi;' => 935,
+ '&circ;' => 94,
+ '&delta;' => 916,
+ '&epsilon;' => 917,
+ '&eta;' => 919,
+ '&fnof;' => 402,
+ '&gamma;' => 915,
+ '&iota;' => 921,
+ '&kappa;' => 922,
+ '&lambda;' => 923,
+ '&ldquo;' => 147,
+ '&lsaquo;' => 139,
+ '&lsquo;' => 145,
+ '&mdash;' => 151,
+ '&minus;' => 45,
+ '&mu;' => 924,
+ '&ndash;' => 150,
+ '&nu;' => 925,
+ '&oelig;' => 140,
+ '&omega;' => 937,
+ '&omicron;' => 927,
+ '&phi;' => 934,
+ '&pi;' => 928,
+ '&piv;' => 982,
+ '&psi;' => 936,
+ '&rdquo;' => 148,
+ '&rho;' => 929,
+ '&rsaquo;' => 155,
+ '&rsquo;' => 146,
+ '&scaron;' => 138,
+ '&sigma;' => 931,
+ '&sigmaf;' => 962,
+ '&tau;' => 932,
+ '&theta;' => 920,
+ '&thetasym;' => 977,
+ '&tilde;' => 126,
+ '&trade;' => 153,
+ '&upsih;' => 978,
+ '&upsilon;' => 933,
+ '&xi;' => 926,
+ '&yuml;' => 159,
+ '&zeta;' => 918,
+ );
+
+ /**
+ * Decodes markup entities in a given string.
+ *
+ * @param string $string String containing markup entities
+ * @param string $charset Optional character set name to use in decoding
+ * entities, defaults to UTF-8
+ *
+ * @return string String with markup entities decoded
+ */
+ public function decodeEntities($string, $charset = 'UTF-8')
+ {
+ $string = str_ireplace(
+ array_keys(self::$entities),
+ array_map('chr', self::$entities),
+ $string
+ );
+ $string = html_entity_decode($string, ENT_QUOTES, $charset);
+ $string = preg_replace(
+ array('/&#0*([0-9]+);/me', '/&#x0*([a-f0-9]+);/mei'),
+ array('$this->codeToUtf(\\1)', '$this->codeToUtf(hexdec(\\1))'),
+ $string
+ );
+ return $string;
+ }
+
+ /**
+ * Converts a given unicode to its UTF-8 equivalent.
+ *
+ * @param int $code Code to convert
+ * @return string Character corresponding to code
+ */
+ public function codeToUtf8($code)
+ {
+ $code = (int) $code;
+ switch ($code) {
+ // 1 byte, 7 bits
+ case 0:
+ return chr(0);
+ case ($code & 0x7F):
+ return chr($code);
+
+ // 2 bytes, 11 bits
+ case ($code & 0x7FF):
+ return chr(0xC0 | (($code >> 6) & 0x1F)) .
+ chr(0x80 | ($code & 0x3F));
+
+ // 3 bytes, 16 bits
+ case ($code & 0xFFFF):
+ return chr(0xE0 | (($code >> 12) & 0x0F)) .
+ chr(0x80 | (($code >> 6) & 0x3F)) .
+ chr(0x80 | ($code & 0x3F));
+
+ // 4 bytes, 21 bits
+ case ($code & 0x1FFFFF):
+ return chr(0xF0 | ($code >> 18)) .
+ chr(0x80 | (($code >> 12) & 0x3F)) .
+ chr(0x80 | (($code >> 6) & 0x3F)) .
+ chr(0x80 | ($code & 0x3F));
+ }
+ }
+
+ /**
+ * Transliterates characters in a given string where possible.
+ *
+ * @param string $string String containing characters to
+ * transliterate
+ * @param string $charsetFrom Optional character set of the string,
+ * defaults to UTF-8
+ * @param string $charsetTo Optional character set to which the string
+ * should be converted, defaults to ISO-8859-1
+ *
+ * @return string String with characters transliterated or the original
+ * string if transliteration was not possible
+ */
+ public function transliterate($string, $charsetFrom = 'UTF-8', $charsetTo = 'ISO-8859-1')
+ {
+ // @link http://pecl.php.net/package/translit
+ if (function_exists('transliterate')) {
+ $string = transliterate($string, array('han_transliterate', 'diacritical_remove'), $charsetFrom, $charsetTo);
+ } elseif (function_exists('iconv')) {
+ $string = iconv($charsetFrom, $charsetTo . '//TRANSLIT', $string);
+ } else {
+ // @link http://stackoverflow.com/questions/1284535/php-transliteration/1285491#1285491
+ $string = preg_replace(
+ '~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i',
+ '$1',
+ htmlentities($string, ENT_COMPAT, $charsetFrom)
+ );
+ }
+ return $string;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Exception.php
new file mode 100755
index 000000000..ca4d53fed
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Exception.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to plugin handling.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Plugin_Exception extends Phergie_Exception
+{
+ /**
+ * Error indicating that a path containing plugins was specified, but
+ * did not reference a readable directory
+ */
+ const ERR_DIRECTORY_NOT_READABLE = 1;
+
+ /**
+ * Error indicating that an attempt was made to locate the class for a
+ * specified plugin, but the class could not be found
+ */
+ const ERR_CLASS_NOT_FOUND = 2;
+
+ /**
+ * Error indicating that an attempt was made to locate the class for a
+ * specified plugin, but that the found class did not extend the base
+ * plugin class
+ */
+ const ERR_INCORRECT_BASE_CLASS = 3;
+
+ /**
+ * Error indicating that an attempt was made to locate the class for a
+ * specified plugin, but that the found class cannot be instantiated
+ */
+ const ERR_CLASS_NOT_INSTANTIABLE = 4;
+
+ /**
+ * Error indicating that an attempt was made to access a plugin that had
+ * not been loaded and autoloading was not enabled to load it
+ */
+ const ERR_PLUGIN_NOT_LOADED = 5;
+
+ /**
+ * Error indicating that an attempt was made to access the configuration
+ * handler before one had been set
+ */
+ const ERR_NO_CONFIG_HANDLER = 6;
+
+ /**
+ * Error indicating that an attempt was made to access the plugin
+ * handler before one had been set
+ */
+ const ERR_NO_PLUGIN_HANDLER = 7;
+
+ /**
+ * Error indicating that an attempt was made to access the event
+ * handler before one had been set
+ */
+ const ERR_NO_EVENT_HANDLER = 8;
+
+ /**
+ * Error indicating that an attempt was made to access the connection
+ * before one had been set
+ */
+ const ERR_NO_CONNECTION = 9;
+
+ /**
+ * Error indicating that an attempt was made to access the current
+ * incoming event before one had been set
+ */
+ const ERR_NO_EVENT = 10;
+
+ /**
+ * Error indicating that a dependency of the plugin was unavailable at
+ * the time that an attempt was made to load it
+ */
+ const ERR_REQUIREMENT_UNSATISFIED = 11;
+
+ /**
+ * Error indicating that a call was made to a nonexistent plugin method
+ * and that its __call() implementation did not process that call as an
+ * attempt to trigger an event - this is intended to aid in debugging of
+ * such situations
+ */
+ const ERR_INVALID_CALL = 12;
+
+ /**
+ * Error indicating that a fatal runtime issue was encountered within a
+ * plugin
+ */
+ const ERR_FATAL_ERROR = 13;
+
+ /**
+ * Error indicating that a class specified to be used for iterating
+ * plugins cannot be found by the autoloader or does not extend
+ * FilterIterator
+ */
+ const ERR_INVALID_ITERATOR_CLASS = 14;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php
new file mode 100644
index 000000000..d2a9d4dce
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php
@@ -0,0 +1,459 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Google
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Google
+ */
+
+/**
+ * Provides commands used to access several services offered by Google
+ * including search, translation, weather, maps, and currency and general
+ * value unit conversion.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Google
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Google
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ * @uses Phergie_Plugin_Temperature pear.phergie.org
+ */
+class Phergie_Plugin_Google extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Http');
+ $plugins->getPlugin('Weather');
+ }
+
+ /**
+ * Returns the first result of a Google search.
+ *
+ * @param string $query Search term
+ *
+ * @return void
+ * @todo Implement use of URL shortening here
+ */
+ public function onCommandG($query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/search/web';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $json = $response->getContent()->responseData;
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($json->cursor->estimatedResultCount > 0) {
+ $msg
+ = $nick
+ . ': [ '
+ . $json->results[0]->titleNoFormatting
+ . ' ] - '
+ . $json->results[0]->url
+ . ' - More results: '
+ . $json->cursor->moreResultsUrl;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $msg = $nick . ': No results for this query.';
+ $this->doPrivmsg($source, $msg);
+ }
+ }
+
+ /**
+ * Performs a Google Count search for the given term.
+ *
+ * @param string $query Search term
+ *
+ * @return void
+ */
+ public function onCommandGc($query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/search/web';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $json = $response->getContent()->responseData->cursor;
+ $count = $json->estimatedResultCount;
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($count) {
+ $msg
+ = $nick . ': ' .
+ number_format($count, 0) .
+ ' estimated results for ' . $query;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $this->doPrivmsg($source, $nick . ': No results for this query.');
+ }
+ }
+
+ /**
+ * Performs a Google Translate search for the given term.
+ *
+ * @param string $from Language of the search term
+ * @param string $to Language to which the search term should be
+ * translated
+ * @param string $query Term to translate
+ *
+ * @return void
+ */
+ public function onCommandGt($from, $to, $query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/language/translate';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query,
+ 'langpair' => $from . '|' . $to
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $json = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if (empty($json->responseData->translatedText)) {
+ $this->doPrivmsg($source, $nick . ': ' . $json->responseDetails);
+ } else {
+ $this->doPrivmsg(
+ $source,
+ $nick . ': ' . $json->responseData->translatedText
+ );
+ }
+ }
+
+ /**
+ * Performs a Google Weather search for the given term.
+ *
+ * @param string $location Location to search for
+ * @param int $offset Optional day offset from the current date
+ * between 0 and 3 to get the forecast
+ *
+ * @return void
+ */
+ public function onCommandGw($location, $offset = null)
+ {
+ $url = 'http://www.google.com/ig/api';
+ $params = array(
+ 'weather' => $location,
+ 'hl' => $this->getConfig('google.lang', 'en'),
+ 'oe' => 'UTF-8'
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $xml = $response->getContent()->weather;
+
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $msg = '';
+ if ($event->isInChannel()) {
+ $msg .= $event->getNick() . ': ';
+ }
+
+ if (isset($xml->problem_cause)) {
+ $msg .= $xml->problem_cause->attributes()->data[0];
+ $this->doPrivmsg($source, $msg);
+ return;
+ }
+
+ $temperature = $this->plugins->getPlugin('Temperature');
+
+ $forecast = $xml->forecast_information;
+ $city = $forecast->city->attributes()->data[0];
+ $zip = $forecast->postal_code->attributes()->data[0];
+
+ if ($offset !== null) {
+ $offset = (int) $offset;
+ if ($offset < 0) {
+ $this->doNotice($source, 'Past weather data is not available');
+ return;
+ } elseif ($offset > 3) {
+ $this->doNotice($source, 'Future weather data is limited to 3 days from today');
+ return;
+ }
+
+ $linha = $xml->forecast_conditions[$offset];
+ $low = $linha->low->attributes()->data[0];
+ $high = $linha->high->attributes()->data[0];
+ $units = $forecast->unit_system->attributes()->data[0];
+ $condition = $linha->condition->attributes()->data[0];
+ $day = $linha->day_of_week->attributes()->data[0];
+
+ $date = ($offset == 0) ? time() : strtotime('next ' . $day);
+ $day = ucfirst($day) . ' ' . date('n/j/y', $date);
+
+ if ($units == 'US') {
+ $lowF = $low;
+ $lowC = $temperature->convertFahrenheitToCelsius($low);
+ $highF = $high;
+ $highC = $temperature->convertFahrenheitToCelsius($high);
+ } else {
+ $lowC = $low;
+ $lowF = $temperature->convertCelsiusToFahrenheit($lowC);
+ $highC = $high;
+ $highF = $temperature->convertCelsiusToFahrenheit($high);
+ }
+
+ $msg .= 'Forecast for ' . $city . ' (' . $zip . ')'
+ . ' on ' . $day . ' ::'
+ . ' Low: ' . $lowF . 'F/' . $lowC . 'C,'
+ . ' High: ' . $highF . 'F/' . $highC . 'C,'
+ . ' Conditions: ' . $condition;
+ } else {
+ $conditions = $xml->current_conditions;
+ $condition = $conditions->condition->attributes()->data[0];
+ $tempF = $conditions->temp_f->attributes()->data[0];
+ $tempC = $conditions->temp_c->attributes()->data[0];
+ $humidity = $conditions->humidity->attributes()->data[0];
+ $wind = $conditions->wind_condition->attributes()->data[0];
+ $time = $forecast->current_date_time->attributes()->data[0];
+ $time = date('n/j/y g:i A', strtotime($time)) . ' +0000';
+
+ $hiF = $temperature->getHeatIndex($tempF, $humidity);
+ $hiC = $temperature->convertFahrenheitToCelsius($hiF);
+
+ $msg .= 'Weather for ' . $city . ' (' . $zip . ') -'
+ . ' Temperature: ' . $tempF . 'F/' . $tempC . 'C,'
+ . ' ' . $humidity . ','
+ . ' Heat Index: ' . $hiF . 'F/' . $hiC . 'C,'
+ . ' Conditions: ' . $condition . ','
+ . ' Updated: ' . $time;
+ }
+
+ $this->doPrivmsg($source, $msg);
+ }
+
+ /**
+ * Performs a Google Maps search for the given term.
+ *
+ * @param string $location Location to search for
+ *
+ * @return void
+ */
+ public function onCommandGmap($location)
+ {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+
+ $location = utf8_encode($location);
+ $url = 'http://maps.google.com/maps/geo';
+ $params = array(
+ 'q' => $location,
+ 'output' => 'json',
+ 'gl' => $this->getConfig('google.lang', 'en'),
+ 'sensor' => 'false',
+ 'oe' => 'utf8',
+ 'mrt' => 'all',
+ 'key' => $this->getConfig('google.key')
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $json = $response->getContent();
+ if (!empty($json)) {
+ $qtd = count($json->Placemark);
+ if ($qtd > 1) {
+ if ($qtd <= 3) {
+ foreach ($json->Placemark as $places) {
+ $xy = $places->Point->coordinates;
+ $address = utf8_decode($places->address);
+ $url = 'http://maps.google.com/maps?sll=' . $xy[1] . ','
+ . $xy[0] . '&z=15';
+ $msg = $nick . ' -> ' . $address . ' - ' . $url;
+ $this->doPrivmsg($source, $msg);
+ }
+ } else {
+ $msg
+ = $nick .
+ ', there are a lot of places with that query.' .
+ ' Try to be more specific!';
+ $this->doPrivmsg($source, $msg);
+ }
+ } elseif ($qtd == 1) {
+ $xy = $json->Placemark[0]->Point->coordinates;
+ $address = utf8_decode($json->Placemark[0]->address);
+ $url = 'http://maps.google.com/maps?sll=' . $xy[1] . ',' . $xy[0]
+ . '&z=15';
+ $msg = $nick . ' -> ' . $address . ' - ' . $url;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $this->doPrivmsg($source, $nick . ', I found nothing.');
+ }
+ } else {
+ $this->doPrivmsg($source, $nick . ', we have a problem.');
+ }
+ }
+
+ /**
+ * Perform a Google Convert query to convert a value from one metric to
+ * another.
+ *
+ * @param string $value Value to convert
+ * @param string $from Source metric
+ * @param string $to Destination metric
+ *
+ * @return void
+ */
+ public function onCommandGconvert($value, $from, $to)
+ {
+ $url = 'http://www.google.com/finance/converter';
+ $params = array(
+ 'a' => $value,
+ 'from' => $from,
+ 'to' => $to
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $contents = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($contents) {
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument;
+ $doc->loadHTML($contents);
+ libxml_clear_errors();
+ $xpath = new DOMXPath($doc);
+ $result = $xpath->query('//div[@id="currency_converter_result"]');
+ $div = $result->item(0);
+ $text = rtrim($div->textContent);
+ $this->doPrivmsg($source, $text);
+ }
+ }
+
+ /**
+ * Performs a Google search to convert a value from one unit to another.
+ *
+ * @param string $query Query of the form "[quantity] [unit] to [unit2]"
+ *
+ * @return void
+ *
+ * @pluginCmd [quantity] [unit] to [unit2] Convert a value from one
+ * metric to another
+ */
+ public function onCommandConvert($query)
+ {
+ $url = 'http://www.google.com/search?q=' . urlencode($query);
+ $response = $this->plugins->http->get($url);
+ $contents = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+
+ if ($response->isError()) {
+ $code = $response->getCode();
+ $message = $response->getMessage();
+ $this->doNotice($nick, 'ERROR: ' . $code . ' ' . $message);
+ return;
+ }
+
+ $start = strpos($contents, '<h3 class=r>');
+ if ($start !== false) {
+ $end = strpos($contents, '</b>', $start);
+ $text = strip_tags(substr($contents, $start, $end - $start));
+ $text = str_replace(
+ array(chr(195), chr(151), chr(160)),
+ array('x', '', ' '),
+ $text
+ );
+ }
+
+ if (isset($text)) {
+ $this->doPrivmsg($source, $nick . ': ' . $text);
+ } else {
+ $this->doNotice($nick, 'Sorry I couldn\'t find an answer.');
+ }
+ }
+
+
+ /**
+ * Returns the first definition of a Google Dictionary search.
+ *
+ * @param string $query Word to get the definition
+ *
+ * @return void
+ * @todo Implement use of URL shortening here
+ */
+ public function onCommandDefine($query)
+ {
+ $lang = $this->getConfig('google.lang', 'en');
+ $url = 'http://www.google.com/dictionary/json';
+ $params = array(
+ 'callback' => 'result',
+ 'q' => $query,
+ 'sl' => $lang,
+ 'tl' => $lang,
+ 'restrict' => 'pr,de'
+ );
+ $response = $this->plugins->http->get($url, $params);
+ $json = $response->getContent();
+
+ // Remove some garbage from the JSON and decode it
+ $json = str_replace(array('result(', ',200,null)'), '', $json);
+ $json = str_replace('"', '¿?¿', $json);
+ $json = strip_tags(stripcslashes($json));
+ $json = str_replace('"', "'", $json);
+ $json = str_replace('¿?¿', '"', $json);
+ $json = json_decode($json);
+
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if (!empty($json->webDefinitions)) {
+ $results = 0;
+ foreach ($json->primaries[0]->entries as $entry) {
+ if ($entry->type == 'meaning') {
+ $results++;
+ if (empty($text)) {
+ foreach ($entry->terms as $term) {
+ if ($term->type == 'text') {
+ $text = trim($term->text);
+ }
+ }
+ }
+ }
+ }
+ $more = $results > 1 ? ($results - 1) . ' ' : '';
+ $lang_code = substr($lang, 0, 2);
+ $msg = $nick . ': ' . $text
+ . ' - You can find ' . $more . 'more results at '
+ . 'http://www.google.com/dictionary'
+ . '?aq=f'
+ . '&langpair=' . $lang_code . '%7C' . $lang_code
+ . '&q=' . $query
+ . '&hl=' . $lang_code;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ if ($lang != 'en'){
+ $lang = 'en';
+ $this->onCommandDefine($query);
+ } else {
+ $msg = $nick . ': No results for this query.';
+ $this->doPrivmsg($source, $msg);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php
new file mode 100755
index 000000000..c3086587b
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php
@@ -0,0 +1,501 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Handles on-demand loading of, iteration over, and access to plugins.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Plugin_Handler implements IteratorAggregate, Countable
+{
+ /**
+ * Current list of plugin instances
+ *
+ * @var array
+ */
+ protected $plugins;
+
+ /**
+ * Paths in which to search for plugin class files
+ *
+ * @var array
+ */
+ protected $paths;
+
+ /**
+ * Flag indicating whether plugin classes should be instantiated on
+ * demand if they are requested but no instance currently exists
+ *
+ * @var bool
+ */
+ protected $autoload;
+
+ /**
+ * Phergie_Config instance that should be passed in to any plugin
+ * instantiated within the handler
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Phergie_Event_Handler instance that should be passed in to any plugin
+ * instantiated within the handler
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Name of the class to use for iterating over all currently loaded
+ * plugins
+ *
+ * @var string
+ */
+ protected $iteratorClass = 'Phergie_Plugin_Iterator';
+
+ /**
+ * Constructor to initialize class properties and add the path for core
+ * plugins.
+ *
+ * @param Phergie_Config $config configuration to pass to any
+ * instantiated plugin
+ * @param Phergie_Event_Handler $events event handler to pass to any
+ * instantiated plugin
+ *
+ * @return void
+ */
+ public function __construct(
+ Phergie_Config $config,
+ Phergie_Event_Handler $events
+ ) {
+ $this->config = $config;
+ $this->events = $events;
+
+ $this->plugins = array();
+ $this->paths = array();
+ $this->autoload = false;
+
+ if (!empty($config['plugins.paths'])) {
+ foreach ($config['plugins.paths'] as $dir => $prefix) {
+ $this->addPath($dir, $prefix);
+ }
+ }
+
+ $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ }
+
+
+ /**
+ * Adds a path to search for plugin class files. Paths are searched in
+ * the reverse order in which they are added.
+ *
+ * @param string $path Filesystem directory path
+ * @param string $prefix Optional class name prefix corresponding to the
+ * path
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ * @throws Phergie_Plugin_Exception
+ */
+ public function addPath($path, $prefix = '')
+ {
+ if (!is_readable($path)) {
+ throw new Phergie_Plugin_Exception(
+ 'Path "' . $path . '" does not reference a readable directory',
+ Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
+ );
+ }
+
+ $this->paths[] = array(
+ 'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
+ 'prefix' => $prefix
+ );
+
+ return $this;
+ }
+
+ /**
+ * Returns metadata corresponding to a specified plugin.
+ *
+ * @param string $plugin Short name of the plugin class
+ *
+ * @throws Phergie_Plugin_Exception Class file can't be found
+ *
+ * @return array|boolean Associative array containing the path to the
+ * class file and its containing directory as well as the full
+ * class name
+ */
+ public function getPluginInfo($plugin)
+ {
+ foreach (array_reverse($this->paths) as $path) {
+ $file = $path['path'] . $plugin . '.php';
+ if (file_exists($file)) {
+ $path = array(
+ 'dir' => $path['path'],
+ 'file' => $file,
+ 'class' => $path['prefix'] . $plugin,
+ );
+ return $path;
+ }
+ }
+
+ // If the class can't be found, display an error
+ throw new Phergie_Plugin_Exception(
+ 'Class file for plugin "' . $plugin . '" cannot be found',
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
+ );
+ }
+
+ /**
+ * Adds a plugin instance to the handler.
+ *
+ * @param string|Phergie_Plugin_Abstract $plugin Short name of the
+ * plugin class or a plugin object
+ * @param array $args Optional array of
+ * arguments to pass to the plugin constructor if a short name is
+ * passed for $plugin
+ *
+ * @return Phergie_Plugin_Abstract New plugin instance
+ */
+ public function addPlugin($plugin, array $args = null)
+ {
+ // If a short plugin name is specified...
+ if (is_string($plugin)) {
+ $index = strtolower($plugin);
+ if (isset($this->plugins[$index])) {
+ return $this->plugins[$index];
+ }
+
+ // Attempt to locate and load the class
+ $info = $this->getPluginInfo($plugin);
+ $file = $info['file'];
+ $class = $info['class'];
+ include_once $file;
+ if (!class_exists($class, false)) {
+ throw new Phergie_Plugin_Exception(
+ 'File "' . $file . '" does not contain class "' . $class . '"',
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
+ );
+ }
+
+ // Check to ensure the class is a plugin class
+ if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
+ $msg
+ = 'Class for plugin "' . $plugin .
+ '" does not extend Phergie_Plugin_Abstract';
+ throw new Phergie_Plugin_Exception(
+ $msg,
+ Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
+ );
+ }
+
+ // Check to ensure the class can be instantiated
+ $reflection = new ReflectionClass($class);
+ if (!$reflection->isInstantiable()) {
+ throw new Phergie_Plugin_Exception(
+ 'Class for plugin "' . $plugin . '" cannot be instantiated',
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
+ );
+ }
+
+ // If the class is found, instantiate it
+ if (!empty($args)) {
+ $instance = $reflection->newInstanceArgs($args);
+ } else {
+ $instance = new $class;
+ }
+
+ // Store the instance
+ $this->plugins[$index] = $instance;
+ $plugin = $instance;
+
+ } elseif ($plugin instanceof Phergie_Plugin_Abstract) {
+ // If a plugin instance is specified...
+
+ // Add the plugin instance to the list of plugins
+ $this->plugins[strtolower($plugin->getName())] = $plugin;
+ }
+
+ // Configure and initialize the instance
+ $plugin->setPluginHandler($this);
+ $plugin->setConfig($this->config);
+ $plugin->setEventHandler($this->events);
+ $plugin->onLoad();
+
+ return $plugin;
+ }
+
+ /**
+ * Adds multiple plugin instances to the handler.
+ *
+ * @param array $plugins List of elements where each is of the form
+ * 'ShortPluginName' or array('ShortPluginName', array($arg1,
+ * ..., $argN))
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ */
+ public function addPlugins(array $plugins)
+ {
+ foreach ($plugins as $plugin) {
+ if (is_array($plugin)) {
+ $this->addPlugin($plugin[0], $plugin[1]);
+ } else {
+ $this->addPlugin($plugin);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Removes a plugin instance from the handler.
+ *
+ * @param string|Phergie_Plugin_Abstract $plugin Short name of the
+ * plugin class or a plugin object
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ */
+ public function removePlugin($plugin)
+ {
+ if ($plugin instanceof Phergie_Plugin_Abstract) {
+ $plugin = $plugin->getName();
+ }
+ $plugin = strtolower($plugin);
+
+ unset($this->plugins[$plugin]);
+
+ return $this;
+ }
+
+ /**
+ * Returns the corresponding instance for a specified plugin, loading it
+ * if it is not already loaded and autoloading is enabled.
+ *
+ * @param string $name Short name of the plugin class
+ *
+ * @return Phergie_Plugin_Abstract Plugin instance
+ */
+ public function getPlugin($name)
+ {
+ // If the plugin is loaded, return the instance
+ $lower = strtolower($name);
+ if (isset($this->plugins[$lower])) {
+ return $this->plugins[$lower];
+ }
+
+ // If autoloading is disabled, display an error
+ if (!$this->autoload) {
+ $msg
+ = 'Plugin "' . $name . '" has been requested, ' .
+ 'is not loaded, and autoload is disabled';
+ throw new Phergie_Plugin_Exception(
+ $msg,
+ Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
+ );
+ }
+
+ // If autoloading is enabled, attempt to load the plugin
+ $plugin = $this->addPlugin($name);
+
+ // Return the added plugin
+ return $plugin;
+ }
+
+ /**
+ * Returns the corresponding instances for multiple specified plugins,
+ * loading them if they are not already loaded and autoloading is
+ * enabled.
+ *
+ * @param array $names Optional list of short names of the plugin
+ * classes to which the returned plugin list will be limited,
+ * defaults to all presently loaded plugins
+ *
+ * @return array Associative array mapping lowercased plugin class short
+ * names to corresponding plugin instances
+ */
+ public function getPlugins(array $names = array())
+ {
+ if (empty($names)) {
+ return $this->plugins;
+ }
+
+ $plugins = array();
+ foreach ($names as $name) {
+ $plugins[strtolower($name)] = $this->getPlugin($name);
+ }
+ return $plugins;
+ }
+
+ /**
+ * Returns whether or not at least one instance of a specified plugin
+ * class is loaded.
+ *
+ * @param string $name Short name of the plugin class
+ *
+ * @return bool TRUE if an instance exists, FALSE otherwise
+ */
+ public function hasPlugin($name)
+ {
+ return isset($this->plugins[strtolower($name)]);
+ }
+
+ /**
+ * Sets a flag used to determine whether plugins should be loaded
+ * automatically if they have not been explicitly loaded.
+ *
+ * @param bool $flag TRUE to have plugins autoload (default), FALSE
+ * otherwise
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface.
+ */
+ public function setAutoload($flag = true)
+ {
+ $this->autoload = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of a flag used to determine whether plugins should
+ * be loaded automatically if they have not been explicitly loaded.
+ *
+ * @return bool TRUE if autoloading is enabled, FALSE otherwise
+ */
+ public function getAutoload()
+ {
+ return $this->autoload;
+ }
+
+ /**
+ * Allows plugin instances to be accessed as properties of the handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return Phergie_Plugin_Abstract Requested plugin instance
+ */
+ public function __get($name)
+ {
+ return $this->getPlugin($name);
+ }
+
+ /**
+ * Allows plugin instances to be detected as properties of the handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return bool TRUE if the plugin is loaded, FALSE otherwise
+ */
+ public function __isset($name)
+ {
+ return $this->hasPlugin($name);
+ }
+
+ /**
+ * Allows plugin instances to be removed as properties of handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $this->removePlugin($name);
+ }
+
+ /**
+ * Returns an iterator for all currently loaded plugin instances.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new $this->iteratorClass(
+ new ArrayIterator($this->plugins)
+ );
+ }
+
+ /**
+ * Sets the iterator class used for all currently loaded plugin
+ * instances.
+ *
+ * @param string $class Name of a class that extends FilterIterator
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent API
+ * @throws Phergie_Plugin_Exception Class cannot be found or is not an
+ * FilterIterator-based class
+ */
+ public function setIteratorClass($class)
+ {
+ $valid = true;
+
+ try {
+ $error_reporting = error_reporting(0); // ignore autoloader errors
+ $r = new ReflectionClass($class);
+ error_reporting($error_reporting);
+ if (!$r->isSubclassOf('FilterIterator')) {
+ $message = 'Class ' . $class . ' is not a subclass of FilterIterator';
+ $valid = false;
+ }
+ } catch (ReflectionException $e) {
+ $message = $e->getMessage();
+ $valid = false;
+ }
+
+ if (!$valid) {
+ throw new Phergie_Plugin_Exception(
+ $message,
+ Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS
+ );
+ }
+
+ $this->iteratorClass = $class;
+ }
+
+ /**
+ * Proxies method calls to all plugins containing the called method.
+ *
+ * @param string $name Name of the method called
+ * @param array $args Arguments passed in the method call
+ *
+ * @return void
+ */
+ public function __call($name, array $args)
+ {
+ foreach ($this->getIterator() as $plugin) {
+ call_user_func_array(array($plugin, $name), $args);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the number of plugins contained within the handler.
+ *
+ * @return int Plugin count
+ */
+ public function count()
+ {
+ return count($this->plugins);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php
new file mode 100644
index 000000000..4c2c49b31
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Help
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Help
+ */
+
+/**
+ * Provides access to descriptions of plugins and the commands they provide.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Help
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Help
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Help extends Phergie_Plugin_Abstract
+{
+ /**
+ * Registry of help data indexed by plugin name
+ *
+ * @var array
+ */
+ protected $registry;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Creates a registry of plugin metadata on connect.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ $this->populateRegistry();
+ }
+
+ /**
+ * Creates a registry of plugin metadata.
+ *
+ * @return void
+ */
+ public function populateRegistry()
+ {
+ $this->registry = array();
+
+ foreach ($this->plugins as $plugin) {
+ $class = new ReflectionClass($plugin);
+ $pluginName = strtolower($plugin->getName());
+
+ // Parse the plugin description
+ $docblock = $class->getDocComment();
+ $annotations = $this->getAnnotations($docblock);
+ if (isset($annotations['pluginDesc'])) {
+ $pluginDesc = implode(' ', $annotations['pluginDesc']);
+ } else {
+ $pluginDesc = $this->parseShortDescription($docblock);
+ }
+ $this->registry[$pluginName] = array(
+ 'desc' => $pluginDesc,
+ 'cmds' => array()
+ );
+
+ // Parse command method descriptions
+ $methodPrefix = Phergie_Plugin_Command::METHOD_PREFIX;
+ $methodPrefixLength = strlen($methodPrefix);
+ foreach ($class->getMethods() as $method) {
+ if (strpos($method->getName(), $methodPrefix) !== 0) {
+ continue;
+ }
+
+ $cmd = strtolower(substr($method->getName(), $methodPrefixLength));
+ $docblock = $method->getDocComment();
+ $annotations = $this->getAnnotations($docblock);
+
+ if (isset($annotations['pluginCmd'])) {
+ $cmdDesc = implode(' ', $annotations['pluginCmd']);
+ } else {
+ $cmdDesc = $this->parseShortDescription($docblock);
+ }
+
+ $cmdParams = array();
+ if (!empty($annotations['param'])) {
+ foreach ($annotations['param'] as $param) {
+ $match = null;
+ if (preg_match('/\h+\$([^\h]+)\h+/', $param, $match)) {
+ $cmdParams[] = $match[1];
+ }
+ }
+ }
+
+ $this->registry[$pluginName]['cmds'][$cmd] = array(
+ 'desc' => $cmdDesc,
+ 'params' => $cmdParams
+ );
+ }
+
+ if (empty($this->registry[$pluginName]['cmds'])) {
+ unset($this->registry[$pluginName]);
+ }
+ }
+ }
+
+ /**
+ * Displays a list of plugins with help information available or
+ * commands available for a specific plugin.
+ *
+ * @param string $query Optional short name of a plugin for which commands
+ * should be returned or a command; if unspecified, a list of
+ * plugins with help information available is returned
+ *
+ * @return void
+ */
+ public function onCommandHelp($query = null)
+ {
+ if ($query == 'refresh') {
+ $this->populateRegistry();
+ }
+
+ $nick = $this->getEvent()->getNick();
+ $delay = $this->getConfig('help.delay', 2);
+
+ // Handle requests for a plugin list
+ if (!$query) {
+ $msg = 'These plugins have help information available: '
+ . implode(', ', array_keys($this->registry));
+ $this->doPrivmsg($nick, $msg);
+ return;
+ }
+
+ // Handle requests for plugin information
+ $query = strtolower($query);
+ if (isset($this->registry[$query])
+ && empty($this->registry[$query]['cmds'][$query])) {
+ $msg = $query . ' - ' . $this->registry[$query]['desc'];
+ $this->doPrivmsg($nick, $msg);
+
+ $msg = 'Available commands - '
+ . implode(', ', array_keys($this->registry[$query]['cmds']));
+ $this->doPrivmsg($nick, $msg);
+
+ if ($this->getConfig('command.prefix')) {
+ $msg
+ = 'Note that these commands must be prefixed with "'
+ . $this->getConfig('command.prefix')
+ . '" (without quotes) when issued in a public channel.';
+ $this->doPrivmsg($nick, $msg);
+ }
+
+ return;
+ }
+
+ // Handle requests for command information
+ foreach ($this->registry as $plugin => $data) {
+ if (empty($data['cmds'])) {
+ continue;
+ }
+
+ $result = preg_grep('/^' . $query . '$/i', array_keys($data['cmds']));
+ if (!$result) {
+ continue;
+ }
+
+ $cmd = $data['cmds'][array_shift($result)];
+ $msg = $query;
+ if (!empty($cmd['params'])) {
+ $msg .= ' [' . implode('] [', $cmd['params']) . ']';
+ }
+ $msg .= ' - ' . $cmd['desc'];
+ $this->doPrivmsg($nick, $msg);
+ }
+ }
+
+ /**
+ * Parses and returns the short description from a docblock.
+ *
+ * @param string $docblock Docblock comment code
+ *
+ * @return string Short description (i.e. content from the start of the
+ * docblock up to the first double-newline)
+ */
+ protected function parseShortDescription($docblock)
+ {
+ $desc = preg_replace(
+ array('#^\h*\*\h*#m', '#^/\*\*\h*\v+\h*#', '#(?:\r?\n){2,}.*#s', '#\s*\v+\s*#'),
+ array('', '', '', ' '),
+ $docblock
+ );
+ return $desc;
+ }
+
+ /**
+ * Taken from PHPUnit/Util/Test.php and modified to fix an issue with
+ * tag content spanning multiple lines.
+ *
+ * PHPUnit
+ *
+ * Copyright (c) 2002-2010, Sebastian Bergmann <sb@sebastian-bergmann.de>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Sebastian Bergmann nor the names of his
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @param string $docblock docblock to parse
+ *
+ * @return array
+ */
+ protected function getAnnotations($docblock)
+ {
+ $annotations = array();
+
+ $regex = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?(?:\*\/|\* @)/ms';
+
+ if (preg_match_all($regex, $docblock, $matches)) {
+ $numMatches = count($matches[0]);
+
+ for ($i = 0; $i < $numMatches; ++$i) {
+ $annotation = $matches['value'][$i];
+ $annotation = preg_replace('/\s*\v+\s*\*\s*/', ' ', $annotation);
+ $annotation = rtrim($annotation);
+ $annotations[$matches['name'][$i]][] = $annotation;
+ }
+ }
+
+ return $annotations;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php
new file mode 100644
index 000000000..7c5c8704c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ */
+
+/**
+ * Provides an HTTP client for plugins to use in contacting web services or
+ * retrieving feeds or web pages.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ * @uses extension simplexml optional
+ * @uses extension json optional
+ */
+class Phergie_Plugin_Http extends Phergie_Plugin_Abstract
+{
+ /**
+ * Response to the last executed HTTP request
+ *
+ * @var Phergie_Plugin_Http_Response
+ */
+ protected $response;
+
+ /**
+ * Mapping of content types to handlers for them
+ *
+ * @var array
+ */
+ protected $handlers;
+
+ /**
+ * Initializes the handler lookup table.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->handlers = array(
+ '(?:application|text)/xml(?:;.*)?'
+ => 'simplexml_load_string',
+ '(?:(?:application|text)/(?:x-)?json)|text/javascript.*'
+ => 'json_decode',
+ );
+
+ if (is_array($this->config['http.handlers'])) {
+ $this->handlers = array_merge(
+ $this->handlers,
+ $this->config['http.handlers']
+ );
+ }
+ }
+
+ /**
+ * Sets a handler callback for a content type, which is called when a
+ * response of that content type is received to perform any needed
+ * transformations on the response body content before storing it in the
+ * response object. Note that the calling plugin is responsible for
+ * indicating any dependencies related to specified handler callbacks.
+ *
+ * @param string $type PCRE regular expression (without delimiters) that
+ * matches one or more MIME types
+ * @param callback $callback Callback to execute when a response of a content
+ * type matched by $type is encountered
+ *
+ * @return Phergie_Plugin_Http Provides a fluent interface
+ */
+ public function setHandler($type, $callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Phergie_Plugin_Exception(
+ 'Invalid callback specified',
+ Phergie_Plugin_Exception::ERR_FATAL_ERROR
+ );
+ }
+
+ $this->handlers[$type] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Supporting method that parses the status line of an HTTP response
+ * message.
+ *
+ * @param string $status Status line
+ *
+ * @return array Associative array containing the HTTP version, response
+ * code, and response description
+ */
+ protected function parseStatusLine($status)
+ {
+ $parts = explode(' ', $status, 3);
+ $parsed = array(
+ 'version' => str_replace('HTTP/', '', $parts[0]),
+ 'code' => $parts[1],
+ 'message' => rtrim($parts[2])
+ );
+ return $parsed;
+ }
+
+ /**
+ * Supporting method that acts as an error handler to intercept HTTP
+ * responses resulting in PHP-level errors.
+ *
+ * @param int $errno Level of the error raised
+ * @param string $errstr Error message
+ * @param string $errfile Name of the file in which the error was raised
+ * @param string $errline Line number on which the error was raised
+ *
+ * @return bool Always returns TRUE to allow normal execution to
+ * continue once this method terminates
+ */
+ public function handleError($errno, $errstr, $errfile, $errline)
+ {
+ if ($httperr = strstr($errstr, 'HTTP/')) {
+ $parts = $this->parseStatusLine($httperr);
+ $this->response
+ ->setCode($parts['code'])
+ ->setMessage($parts['message']);
+ }
+
+ return true;
+ }
+
+ /**
+ * Supporting method that executes a request and handles the response.
+ *
+ * @param string $url URL to request
+ * @param array $context Associative array of stream context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Object representing the response
+ * resulting from the request
+ */
+ public function request($url, array $context)
+ {
+ $this->response = new Phergie_Plugin_Http_Response;
+
+ $url = (string) $url;
+ $context = stream_context_create(array('http' => $context));
+
+ set_error_handler(array($this, 'handleError'), E_WARNING);
+ $stream = fopen($url, 'r', false, $context);
+ if ($stream) {
+ $meta = stream_get_meta_data($stream);
+ $status = $this->parseStatusLine($meta['wrapper_data'][0]);
+ $code = $status['code'];
+ $message = $status['message'];
+ $headers = array();
+ foreach (array_slice($meta['wrapper_data'], 1) as $header) {
+ if (!strpos($header, ':')) {
+ continue;
+ }
+ list($name, $value) = explode(': ', $header, 2);
+ $headers[$name] = $value;
+ }
+ unset($meta['wrapper_data']);
+
+ $this->response
+ ->setCode($code)
+ ->setMessage($message)
+ ->setHeaders($headers)
+ ->setMeta($meta);
+
+ $body = stream_get_contents($stream);
+ $type = $this->response->getHeaders('content-type');
+ foreach ($this->handlers as $expr => $handler) {
+ if (preg_match('#^' . $expr . '$#i', $type)) {
+ $handled = call_user_func($handler, $body);
+ if (!empty($handled)) {
+ $body = $handled;
+ }
+ }
+ }
+
+ $this->response->setContent($body);
+ }
+ restore_error_handler();
+
+ return $this->response;
+ }
+
+ /**
+ * Performs a GET request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function get($url, array $query = array(), array $context = array())
+ {
+ if (!empty($query)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'GET';
+
+ return $this->request($url, $context);
+ }
+
+ /**
+ * Performs a HEAD request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function head($url, array $query = array(), array $context = array())
+ {
+ if (!empty($query)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'HEAD';
+
+ return $this->request($url, $context);
+ }
+
+ /**
+ * Performs a POST request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $post Optional associative array of parameters
+ * constituting the POST request body if it is using the
+ * traditional URL-encoded format
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function post($url, array $query = array(),
+ array $post = array(), array $context = array()
+ ) {
+ if (!empty($query)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'POST';
+
+ if (!empty($post)
+ && (!empty($context['header'])
+ xor stripos($context['header'], 'Content-Type'))
+ ) {
+ if (!empty($context['header'])) {
+ $context['header'] .= "\r\n";
+ } else {
+ $context['header'] = '';
+ }
+ $context['header'] .=
+ 'Content-Type: application/x-www-form-urlencoded';
+ $context['content'] = http_build_query($post);
+ }
+
+ return $this->request($url, $context);
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Http/Response.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http/Response.php
new file mode 100644
index 000000000..b9e377cf6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http/Response.php
@@ -0,0 +1,281 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ */
+
+/**
+ * Data structure for HTTP response information.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ */
+class Phergie_Plugin_Http_Response
+{
+ /**
+ * HTTP response code or 0 if no HTTP response was received
+ *
+ * @var string
+ */
+ protected $code;
+
+ /**
+ * HTTP response strings
+ *
+ * @var array
+ */
+ protected static $codeStrings = array(
+ 0 => 'No Response',
+ 100 => 'Continue',
+ 200 => 'OK',
+ 201 => 'Created',
+ 204 => 'No Content',
+ 206 => 'Partial Content',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 408 => 'Request Timeout',
+ 410 => 'Gone',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error',
+ 501 => 'Method Not Implemented',
+ 503 => 'Service Unavailable',
+ 506 => 'Variant Also Negotiates'
+ );
+
+ /**
+ * Description of the HTTP response code or the error message if no HTTP
+ * response was received
+ *
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * Content of the response body, decoded for supported content types
+ *
+ * @var mixed
+ */
+ protected $content;
+
+ /**
+ * Associative array mapping response header names to their values
+ *
+ * @var array
+ */
+ protected $headers;
+
+ /**
+ * Associative array containing other metadata about the response
+ *
+ * @var array
+ */
+ protected $meta;
+
+ /**
+ * Sets the HTTP response code.
+ *
+ * @param string $code Response code
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setCode($code)
+ {
+ $this->code = $code;
+ return $this;
+ }
+
+ /**
+ * Returns the HTTP response code.
+ *
+ * @return string Response code
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Returns the HTTP response code text.
+ *
+ * @return string Response code text
+ */
+ public function getCodeAsString()
+ {
+ $code = $this->code;
+
+ if (!isset(self::$codeStrings[$code])) {
+ return 'Unkown HTTP Status';
+ }
+
+ return self::$codeStrings[$code];
+ }
+
+ /**
+ * Returns whether the response indicates a client- or server-side error.
+ *
+ * @return bool TRUE if the response indicates an error, FALSE otherwise
+ */
+ public function isError()
+ {
+ switch (substr($this->code, 0, 1)) {
+ case '0':
+ case '4':
+ case '5':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Sets the HTTP response description.
+ *
+ * @param string $message Response description
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ return $this;
+ }
+
+ /**
+ * Returns the HTTP response description.
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Sets the content of the response body.
+ *
+ * @param mixed $content Response body content
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+ return $this;
+ }
+
+ /**
+ * Returns the content of the response body.
+ *
+ * @return mixed Response body content, decoded for supported content
+ * types
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the response headers.
+ *
+ * @param array $headers Associative array of response headers indexed
+ * by header name
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setHeaders(array $headers)
+ {
+ $names = array_map('strtolower', array_keys($headers));
+ $values = array_values($headers);
+ $this->headers = array_combine($names, $values);
+ return $this;
+ }
+
+ /**
+ * Returns all response headers or the value of a single specified
+ * response header.
+ *
+ * @param string $name Optional name of a single header for which the
+ * associated value should be returned
+ *
+ * @return array|string Associative array of all header values, a string
+ * containing the value of the header indicated by $name if one
+ * is set, or null if one is not
+ */
+ public function getHeaders($name = null)
+ {
+ if ($name) {
+ $name = strtolower($name);
+ if (empty($this->headers[$name])) {
+ return null;
+ }
+ return $this->headers[$name];
+ }
+ return $this->headers;
+ }
+
+ /**
+ * Sets the response metadata.
+ *
+ * @param array $meta Associative array of response metadata
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setMeta(array $meta)
+ {
+ $this->meta = $meta;
+ return $this;
+ }
+
+ /**
+ * Returns all metadata or the value of a single specified metadatum.
+ *
+ * @param string $name Optional name of a single metadatum for which the
+ * associated value should be returned
+ *
+ * @return array|string|null Associative array of all metadata values, a
+ * string containing the value of the metadatum indicated by
+ * $name if one is set, or null if one is not
+ */
+ public function getMeta($name = null)
+ {
+ if ($name) {
+ if (empty($this->meta[$name])) {
+ return null;
+ }
+ return $this->meta[$name];
+ }
+ return $this->meta;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php
new file mode 100644
index 000000000..3af88dda9
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ideone
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ideone
+ */
+
+/**
+ * Interfaces with ideone.com to execute code and return the result.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ideone
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ideone
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Ideone extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->plugins->getPlugin('Command');
+ }
+
+ /**
+ * Checks a service response for an error, sends a notice to the event
+ * source if an error has occurred, and returns whether an error was found.
+ *
+ * @param array $result Associative array representing the service response
+ *
+ * @return boolean TRUE if an error is found, FALSE otherwise
+ */
+ protected function isError($result)
+ {
+ if ($result['error'] != 'OK') {
+ $this->doNotice($this->event->getNick(), 'ideone error: ' . $result['error']);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Executes a source code sequence in a specified language and returns
+ * the result.
+ *
+ * @param string $language Programming language the source code is in
+ * @param string $code Source code to execute
+ *
+ * @return void
+ */
+ public function onCommandIdeone($language, $code)
+ {
+ $source = $this->event->getSource();
+ $nick = $this->event->getNick();
+
+ // Get authentication credentials
+ $user = $this->getConfig('ideone.user', 'test');
+ $pass = $this->getConfig('ideone.pass', 'test');
+
+ // Normalize the command parameters
+ $language = strtolower($language);
+
+ // Massage PHP code to allow for convenient shorthand
+ if ($language == 'php') {
+ if (!preg_match('/^<\?(?:php)?/', $code)) {
+ $code = '<?php ' . $code;
+ }
+ switch (substr($code, -1)) {
+ case '}':
+ case ';':
+ break;
+ default:
+ $code .= ';';
+ break;
+ }
+ }
+
+ // Identify the language to use
+ $client = new SoapClient('http://ideone.com/api/1/service.wsdl');
+ $response = $client->getLanguages($user, $pass);
+ if ($this->isError($response)) {
+ return;
+ }
+ $languageLength = strlen($language);
+ foreach ($response['languages'] as $languageId => $languageName) {
+ if (strncasecmp($language, $languageName, $languageLength) == 0) {
+ break;
+ }
+ }
+
+ // Send the paste data
+ $response = $client->createSubmission(
+ $user,
+ $pass,
+ $code,
+ $languageId,
+ null, // string input - data from stdin
+ true, // boolean run - TRUE to execute the code
+ false // boolean private - FALSE to make the paste public
+ );
+ if ($this->isError($response)) {
+ return;
+ }
+ $link = $response['link'];
+
+ // Wait until the paste data is processed or the service fails
+ $attempts = $this->getConfig('ideone.attempts', 10);
+ foreach (range(1, $attempts) as $attempt) {
+ $response = $client->getSubmissionStatus($user, $pass, $link);
+ if ($this->isError($response)) {
+ return;
+ }
+ if ($response['status'] == 0) {
+ $result = $response['result'];
+ break;
+ } else {
+ $result = null;
+ sleep(1);
+ }
+ }
+ if ($result == null) {
+ $this->doNotice($nick, 'ideone error: Timed out');
+ return;
+ }
+ if ($result != 15) {
+ $this->doNotice($nick, 'ideone error: Status code ' . $result);
+ return;
+ }
+
+ // Get details for the created paste
+ $response = $client->getSubmissionDetails(
+ $user,
+ $pass,
+ $link,
+ false, // boolean withSource - FALSE to not return the source code
+ false, // boolean withInput - FALSE to not return stdin data
+ true, // boolean withOutput - TRUE to include output
+ true, // boolean withStderr - TRUE to return stderr data
+ false // boolean withCmpinfo - TRUE to return compilation info
+ );
+ if ($this->isError($response)) {
+ return;
+ }
+
+ // Replace the output if it exceeds a specified maximum length
+ $outputLimit = $this->getConfig('ideone.output_limit', 100);
+ var_dump($response);
+ if ($outputLimit && strlen($response['output']) > $outputLimit) {
+ $response['output'] = 'Output is too long to post';
+ }
+
+ // Format the message
+ $msg = $this->getConfig('ideone.format', '%nick%: [ %link% ] %output%');
+ $response['nick'] = $nick;
+ $response['link'] = 'http://ideone.com/' . $link;
+ foreach ($response as $key => $value) {
+ $msg = str_replace('%' . $key . '%', $value, $msg);
+ }
+ $this->doPrivmsg($source, $msg);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Invisible.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Invisible.php
new file mode 100755
index 000000000..622f7d3db
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Invisible.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Invisible
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Invisible
+ */
+
+/**
+ * Marks the bot as invisible when it connects to the server.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Invisible
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Invisible
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3_2
+ */
+class Phergie_Plugin_Invisible extends Phergie_Plugin_Abstract
+{
+ /**
+ * Marks the bot as invisible when it connects to the server.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ $this->doMode($this->getConnection()->getNick(), '+i');
+ $this->getPluginHandler()->removePlugin($this);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php
new file mode 100644
index 000000000..d61096298
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Implements a filtering iterator for limiting executing of methods across
+ * a group of plugins.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Plugin_Iterator extends FilterIterator
+{
+ /**
+ * List of short names of plugins to exclude when iterating
+ *
+ * @var array
+ */
+ protected $plugins = array();
+
+ /**
+ * List of method names where plugins with these methods will be
+ * excluded when iterating
+ *
+ * @var array
+ */
+ protected $methods = array();
+
+ /**
+ * Overrides the parent constructor to reset the internal iterator's
+ * pointer to the current item, which the parent class errantly does not
+ * do.
+ *
+ * @param Iterator $iterator Iterator to filter
+ *
+ * @return void
+ * @link http://bugs.php.net/bug.php?id=52560
+ */
+ public function __construct(Iterator $iterator)
+ {
+ parent::__construct($iterator);
+ $this->rewind();
+ }
+
+ /**
+ * Adds to a list of plugins to exclude when iterating.
+ *
+ * @param mixed $plugins String containing the short name of a single
+ * plugin to exclude or an array of short names of multiple
+ * plugins to exclude
+ *
+ * @return Phergie_Plugin_Iterator Provides a fluent interface
+ */
+ public function addPluginFilter($plugins)
+ {
+ if (is_array($plugins)) {
+ $this->plugins = array_unique(
+ array_merge($this->plugins, $plugins)
+ );
+ } else {
+ $this->plugins[] = $plugins;
+ }
+ return $this;
+ }
+
+ /**
+ * Adds to a list of method names where plugins defining these methods
+ * will be excluded when iterating.
+ *
+ * @param mixed $methods String containing the name of a single method
+ * or an array containing the name of multiple methods
+ *
+ * @return Phergie_Plugin_Iterator Provides a fluent interface
+ */
+ public function addMethodFilter($methods)
+ {
+ if (is_array($methods)) {
+ $this->methods = array_merge($this->methods, $methods);
+ } else {
+ $this->methods[]= $methods;
+ }
+ return $this;
+ }
+
+ /**
+ * Clears any existing plugin and methods filters.
+ *
+ * @return Phergie_Plugin_Iterator Provides a fluent interface
+ */
+ public function clearFilters()
+ {
+ $this->plugins = array();
+ $this->methods = array();
+ }
+
+ /**
+ * Implements FilterIterator::accept().
+ *
+ * @return boolean TRUE to include the current item in those by returned
+ * during iteration, FALSE otherwise
+ */
+ public function accept()
+ {
+ if (!$this->plugins && !$this->methods) {
+ return true;
+ }
+
+ $current = $this->current();
+
+ if (in_array($current->getName(), $this->plugins)) {
+ return false;
+ }
+
+ foreach ($this->methods as $method) {
+ if (method_exists($current, $method)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Join.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Join.php
new file mode 100755
index 000000000..50c3ad8e6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Join.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Join
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Join
+ */
+
+/**
+ * Joins a specified channel on command from a user.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Join
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Join
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Join extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Joins a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to join
+ * @param string $keys Optional comma-delimited list of channel keys
+ *
+ * @return void
+ */
+ public function onCommandJoin($channels, $keys = null)
+ {
+ $this->doJoin($channels, $keys);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php
new file mode 100644
index 000000000..27b4a087d
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php
@@ -0,0 +1,451 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Karma
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Karma
+ */
+
+/**
+ * Handles requests for incrementation or decrementation of a maintained list
+ * of counters for specified terms.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Karma
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Karma
+ * @uses extension PDO
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
+{
+ /**
+ * SQLite object
+ *
+ * @var resource
+ */
+ protected $db = null;
+
+ /**
+ * Prepared statement to add a new karma record
+ *
+ * @var PDOStatement
+ */
+ protected $insertKarma;
+
+ /**
+ * Prepared statement to update an existing karma record
+ *
+ * @var PDOStatement
+ */
+ protected $updateKarma;
+
+ /**
+ * Retrieves an existing karma record
+ *
+ * @var PDOStatement
+ */
+ protected $fetchKarma;
+
+ /**
+ * Retrieves an existing fixed karma record
+ *
+ * @var PDOStatement
+ */
+ protected $fetchFixedKarma;
+
+ /**
+ * Retrieves a positive answer for a karma comparison
+ *
+ * @var PDOStatement
+ */
+ protected $fetchPositiveAnswer;
+
+ /**
+ * Retrieves a negative answer for a karma comparison
+ *
+ * @var PDOStatement
+ */
+ protected $fetchNegativeAnswer;
+
+ /**
+ * Check for dependencies and initializes a database connection and
+ * prepared statements.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $this->getDb();
+ }
+
+ /**
+ * Initializes prepared statements used by the plugin.
+ *
+ * @return void
+ */
+ protected function initializePreparedStatements()
+ {
+ $this->fetchKarma = $this->db->prepare('
+ SELECT karma
+ FROM karmas
+ WHERE term = :term
+ LIMIT 1
+ ');
+
+ $this->insertKarma = $this->db->prepare('
+ INSERT INTO karmas (term, karma)
+ VALUES (:term, :karma)
+ ');
+
+ $this->updateKarma = $this->db->prepare('
+ UPDATE karmas
+ SET karma = :karma
+ WHERE term = :term
+ ');
+
+ $this->fetchFixedKarma = $this->db->prepare('
+ SELECT karma
+ FROM fixed_karmas
+ WHERE term = :term
+ LIMIT 1
+ ');
+
+ $this->fetchPositiveAnswer = $this->db->prepare('
+ SELECT answer
+ FROM positive_answers
+ ORDER BY RANDOM()
+ LIMIT 1
+ ');
+
+ $this->fetchNegativeAnswer = $this->db->prepare('
+ SELECT answer
+ FROM negative_answers
+ ORDER BY RANDOM()
+ LIMIT 1
+ ');
+ }
+
+ /**
+ * Returns a connection to the plugin database, initializing one if none
+ * is explicitly set.
+ *
+ * @return PDO Database connection
+ */
+ public function getDb()
+ {
+ if (empty($this->db)) {
+ $this->db = new PDO('sqlite:' . dirname(__FILE__) . '/Karma/karma.db');
+ $this->initializePreparedStatements();
+ }
+ return $this->db;
+ }
+
+ /**
+ * Sets the connection to the plugin database, mainly intended for unit
+ * testing.
+ *
+ * @param PDO $db Database connection
+ *
+ * @return Phergie_Plugin_Karma Provides a fluent interface
+ */
+ public function setDb(PDO $db)
+ {
+ $this->db = $db;
+ $this->initializePreparedStatements();
+ return $this;
+ }
+
+ /**
+ * Get the canonical form of a given term.
+ *
+ * In the canonical form all sequences of whitespace
+ * are replaced by a single space and all characters
+ * are lowercased.
+ *
+ * @param string $term Term for which a canonical form is required
+ *
+ * @return string Canonical term
+ */
+ protected function getCanonicalTerm($term)
+ {
+ $canonicalTerm = strtolower(preg_replace('|\s+|', ' ', trim($term, '()')));
+ switch ($canonicalTerm) {
+ case 'me':
+ $canonicalTerm = strtolower($this->event->getNick());
+ break;
+ case 'all':
+ case '*':
+ case 'everything':
+ $canonicalTerm = 'everything';
+ break;
+ }
+ return $canonicalTerm;
+ }
+
+ /**
+ * Intercepts a message and processes any contained recognized commands.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $message = $this->getEvent()->getText();
+
+ $termPattern = '\S+?|\([^<>]+?\)+';
+ $actionPattern = '(?P<action>\+\+|--)';
+
+ $modifyPattern = <<<REGEX
+ {^
+ (?J) # allow overwriting capture names
+ \s* # ignore leading whitespace
+
+ (?: # start with ++ or -- before the term
+ $actionPattern
+ (?P<term>$termPattern)
+ | # follow the term with ++ or --
+ (?P<term>$termPattern)
+ $actionPattern # allow no whitespace between the term and the action
+ )
+ $}ix
+REGEX;
+
+ $versusPattern = <<<REGEX
+ {^
+ (?P<term0>$termPattern)
+ \s+(?P<method><|>)\s+
+ (?P<term1>$termPattern)$#
+ $}ix
+REGEX;
+
+ $match = null;
+
+ if (preg_match($modifyPattern, $message, $match)) {
+ $action = $match['action'];
+ $term = $this->getCanonicalTerm($match['term']);
+ $this->modifyKarma($term, $action);
+ } elseif (preg_match($versusPattern, $message, $match)) {
+ $term0 = trim($match['term0']);
+ $term1 = trim($match['term1']);
+ $method = $match['method'];
+ $this->compareKarma($term0, $term1, $method);
+ }
+ }
+
+ /**
+ * Get the karma rating for a given term.
+ *
+ * @param string $term Term for which the karma rating needs to be
+ * retrieved
+ *
+ * @return void
+ */
+ public function onCommandKarma($term)
+ {
+ $source = $this->getEvent()->getSource();
+ $nick = $this->getEvent()->getNick();
+
+ $canonicalTerm = $this->getCanonicalTerm($term);
+
+ $fixedKarma = $this->fetchFixedKarma($canonicalTerm);
+ if ($fixedKarma) {
+ $message = $nick . ': ' . $term . ' ' . $fixedKarma;
+ $this->doPrivmsg($source, $message);
+ return;
+ }
+
+ $karma = $this->fetchKarma($canonicalTerm);
+
+ $message = $nick . ': ';
+
+ if ($term == 'me') {
+ $message .= 'You have';
+ } else {
+ $message .= $term . ' has';
+ }
+
+ $message .= ' ';
+
+ if ($karma) {
+ $message .= 'karma of ' . $karma;
+ } else {
+ $message .= 'neutral karma';
+ }
+
+ $message .= '.';
+
+ $this->doPrivmsg($source, $message);
+ }
+
+ /**
+ * Resets the karma for a term to 0.
+ *
+ * @param string $term Term for which to reset the karma rating
+ *
+ * @return void
+ */
+ public function onCommandReincarnate($term)
+ {
+ $data = array(
+ ':term' => $term,
+ ':karma' => 0
+ );
+ $this->updateKarma->execute($data);
+ }
+
+ /**
+ * Compares the karma between two terms. Optionally increases/decreases
+ * the karma of either term.
+ *
+ * @param string $term0 First term
+ * @param string $term1 Second term
+ * @param string $method Comparison method (< or >)
+ *
+ * @return void
+ */
+ protected function compareKarma($term0, $term1, $method)
+ {
+ $event = $this->getEvent();
+ $nick = $event->getNick();
+ $source = $event->getSource();
+
+ $canonicalTerm0 = $this->getCanonicalTerm($term0);
+ $canonicalTerm1 = $this->getCanonicalTerm($term1);
+
+ $fixedKarma0 = $this->fetchFixedKarma($canonicalTerm0);
+ $fixedKarma1 = $this->fetchFixedKarma($canonicalTerm1);
+
+ if ($fixedKarma0 || $fixedKarma1) {
+ return;
+ }
+
+ if ($canonicalTerm0 == 'everything') {
+ $change = $method == '<' ? '++' : '--';
+ $karma0 = 0;
+ $karma1 = $this->modifyKarma($canonicalTerm1, $change);
+ } elseif ($canonicalTerm1 == 'everything') {
+ $change = $method == '<' ? '--' : '++';
+ $karma0 = $this->modifyKarma($canonicalTerm0, $change);
+ $karma1 = 0;
+ } else {
+ $karma0 = $this->fetchKarma($canonicalTerm0);
+ $karma1 = $this->fetchKarma($canonicalTerm1);
+ }
+
+ // Combining the first and second branches here causes an odd
+ // single-line lapse in code coverage, but the lapse disappears if
+ // they're separated
+ if ($method == '<' && $karma0 < $karma1) {
+ $replies = $this->fetchPositiveAnswer;
+ } elseif ($method == '>' && $karma0 > $karma1) {
+ $replies = $this->fetchPositiveAnswer;
+ } else {
+ $replies = $this->fetchNegativeAnswer;
+ }
+ $replies->execute();
+ $reply = $replies->fetchColumn();
+
+ if (max($karma0, $karma1) == $karma1) {
+ list($canonicalTerm0, $canonicalTerm1) =
+ array($canonicalTerm1, $canonicalTerm0);
+ }
+
+ $message = str_replace(
+ array('%owner%','%owned%'),
+ array($canonicalTerm0, $canonicalTerm1),
+ $reply
+ );
+
+ $this->doPrivmsg($source, $message);
+ }
+
+ /**
+ * Modifes a term's karma.
+ *
+ * @param string $term Term to modify
+ * @param string $action Karma action (either ++ or --)
+ *
+ * @return int Modified karma rating
+ */
+ protected function modifyKarma($term, $action)
+ {
+ $karma = $this->fetchKarma($term);
+ if ($karma !== false) {
+ $statement = $this->updateKarma;
+ } else {
+ $statement = $this->insertKarma;
+ }
+
+ $karma += ($action == '++') ? 1 : -1;
+
+ $args = array(
+ ':term' => $term,
+ ':karma' => $karma
+ );
+ $statement->execute($args);
+
+ return $karma;
+ }
+
+ /**
+ * Returns the karma rating for a specified term for which the karma
+ * rating can be modified.
+ *
+ * @param string $term Term for which to fetch the corresponding karma
+ * rating
+ *
+ * @return integer|boolean Integer value denoting the term's karma or
+ * FALSE if there is the specified term has no associated karma
+ * rating
+ */
+ protected function fetchKarma($term)
+ {
+ $this->fetchKarma->execute(array(':term' => $term));
+ $result = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
+
+ if ($result === false) {
+ return false;
+ }
+
+ return (int) $result['karma'];
+ }
+
+ /**
+ * Returns a phrase describing the karma rating for a specified term for
+ * which the karma rating is fixed.
+ *
+ * @param string $term Term for which to fetch the corresponding karma
+ * rating
+ *
+ * @return string Phrase describing the karma rating, which may be append
+ * to the term to form a complete response
+ */
+ protected function fetchFixedKarma($term)
+ {
+ $this->fetchFixedKarma->execute(array(':term' => $term));
+ $result = $this->fetchFixedKarma->fetch(PDO::FETCH_ASSOC);
+
+ if ($result === false) {
+ return false;
+ }
+
+ return $result['karma'];
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Lart.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Lart.php
new file mode 100644
index 000000000..d00cae0dd
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Lart.php
@@ -0,0 +1,303 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Lart
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Lart
+ */
+
+/**
+ * Accepts terms and corresponding definitions for storage to a local data
+ * source and performs and returns the result of lookups for term definitions
+ * as they are requested.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Lart
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Lart
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses extension PDO
+ * @uses extension pdo_sqlite
+ */
+class Phergie_Plugin_Lart extends Phergie_Plugin_Abstract
+{
+ /**
+ * PDO instance for the database
+ *
+ * @var PDO
+ */
+ protected $db;
+
+ /**
+ * Prepared statement for inserting a new definition
+ *
+ * @var PDOStatement
+ */
+ protected $save;
+
+ /**
+ * Prepared statement for deleting the definition for a given term
+ *
+ * @var PDOStatement
+ */
+ protected $delete;
+
+ /**
+ * Prepared statement for searching for a definition for which the term
+ * matches as a regular expression against a given search string
+ *
+ * @var PDOStatement
+ */
+ protected $process;
+
+ /**
+ * Prepared statement for searching for a definition by its exact term
+ *
+ * @var PDOStatement
+ */
+ protected $select;
+
+ /**
+ * Checks for dependencies and initializes the database.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+
+ $this->plugins->getPlugin('Command');
+
+ $dir = dirname(__FILE__) . '/' . $this->getName();
+ $path = $dir . '/lart.db';
+ $exists = file_exists($path);
+ if (!$exists) {
+ mkdir($dir);
+ }
+
+ try {
+ $this->db = new PDO('sqlite:' . $path);
+ } catch (PDO_Exception $e) {
+ throw new Phergie_Plugin_Exception($e->getMessage());
+ }
+
+ $this->db->sqliteCreateFunction('preg_match', 'preg_match');
+
+ if (!$exists) {
+ $this->db->exec('
+ CREATE TABLE lart (
+ name VARCHAR(255),
+ definition TEXT,
+ hostmask VARCHAR(50),
+ tstamp VARCHAR(19)
+ )
+ ');
+ $this->db->exec('
+ CREATE UNIQUE INDEX lart_name ON lart (name)
+ ');
+ }
+
+ $this->save = $this->db->prepare('
+ REPLACE INTO lart (name, definition, hostmask, tstamp)
+ VALUES (:name, :definition, :hostmask, :tstamp)
+ ');
+
+ $this->process = $this->db->prepare('
+ SELECT *
+ FROM lart
+ WHERE preg_match(name, :name)
+ ');
+
+ $this->select = $this->db->prepare('
+ SELECT *
+ FROM lart
+ WHERE name = :name
+ ');
+
+ $this->delete = $this->db->prepare('
+ DELETE FROM lart
+ WHERE name = :name
+ ');
+ }
+
+ /**
+ * Retrieves the definition for a given term if it exists.
+ *
+ * @param string $term Term to search for
+ *
+ * @return mixed String containing the definition or FALSE if no definition
+ * exists
+ */
+ protected function getLart($term)
+ {
+ $this->process->execute(array(':name' => $term));
+ $row = $this->process->fetchObject();
+ if ($row === false) {
+ return false;
+ }
+ preg_match($row->name, $term, $match);
+ $definition = preg_replace(
+ "/(?:\\\\|\\$)([0-9]+)/e",
+ '$match[\1]',
+ $row->definition
+ );
+ $event = $this->getEvent();
+ $definition = str_replace(
+ array('$source', '$nick'),
+ array($event->getSource(), $event->getNick()),
+ $definition
+ );
+ return $definition;
+ }
+
+ /**
+ * Deletes a given definition.
+ *
+ * @param string $term Term for which the definition should be deleted
+ *
+ * @return boolean TRUE if the definition was found and deleted, FALSE
+ * otherwise
+ */
+ protected function deleteLart($term)
+ {
+ $this->delete->execute(array(':name' => $term));
+ return ($this->delete->rowCount() > 0);
+ }
+
+ /**
+ * Saves a given definition.
+ *
+ * @param string $term Term to trigger a response containing the
+ * corresponding definition, may be a regular expression
+ * @param string $definition Definition corresponding to the term
+ *
+ * @return boolean TRUE if the definition was saved successfully, FALSE
+ * otherwise
+ */
+ protected function saveLart($term, $definition)
+ {
+ $data = array(
+ ':name' => $term,
+ ':definition' => $definition,
+ ':hostmask' => (string) $this->getEvent()->getHostmask(),
+ ':tstamp' => time()
+ );
+ $this->save->execute($data);
+ return ($this->save->rowCount() > 0);
+ }
+
+ /**
+ * Returns information about a definition.
+ *
+ * @param string $term Term about which to return information
+ *
+ * @return void
+ */
+ public function onCommandLartinfo($term)
+ {
+ $this->select->execute(array(':name' => $term));
+ $row = $this->select->fetchObject();
+ $msg = $this->getEvent()->getNick() . ': ';
+ if (!$row) {
+ $msg .= 'Lart not found';
+ } else {
+ $msg .= 'Term: ' . $row->name
+ . ', Definition: ' . $row->definition
+ . ', User: ' . $row->hostmask
+ . ', Added: ' . date('n/j/y g:i A', $row->tstamp);
+ }
+ $this->doNotice($this->getEvent()->getSource(), $msg);
+ }
+
+ /**
+ * Creates a new definition.
+ *
+ * @param string $term Term to add
+ * @param string $definition Definition to add
+ *
+ * @return void
+ */
+ public function onCommandAddlart($term, $definition)
+ {
+ $result = $this->saveLart($term, $definition);
+ if ($result) {
+ $msg = 'Lart saved successfully';
+ } else {
+ $msg = 'Lart could not be saved';
+ }
+ $this->doNotice($this->getEvent()->getSource(), $msg);
+ }
+
+ /**
+ * Removes an existing definition.
+ *
+ * @param string $term Term for which the definition should be removed
+ *
+ * @return void
+ */
+ public function onCommandDeletelart($term)
+ {
+ $source = $this->getEvent()->getSource();
+ if ($this->deleteLart($term)) {
+ $msg = 'Lart deleted successfully';
+ } else {
+ $msg = 'Lart not found';
+ }
+ $this->doNotice($source, $msg);
+ }
+
+ /**
+ * Processes definition triggers in the text of the current event.
+ *
+ * @return void
+ */
+ protected function processLart()
+ {
+ $lart = $this->getLart($this->getEvent()->getText());
+ if ($lart) {
+ if (strpos($lart, '/me') === 0) {
+ $lart = substr($lart, 4);
+ $method = 'doAction';
+ } else {
+ $method = 'doPrivmsg';
+ }
+ $this->$method($this->getEvent()->getSource(), $lart);
+ }
+ }
+
+ /**
+ * Processes definition triggers in messages.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $this->processLart();
+ }
+
+ /**
+ * Processes definition triggers in CTCP actions.
+ *
+ * @return void
+ */
+ public function onAction()
+ {
+ $this->processLart();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php
new file mode 100644
index 000000000..af8fc7287
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Message
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Message
+ */
+
+/**
+ * Generalized plugin providing utility methods for
+ * prefix and bot named based message extraction.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Message
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Message
+ */
+class Phergie_Plugin_Message extends Phergie_Plugin_Abstract
+{
+
+ /**
+ * Check whether a message is specifically targeted at the bot.
+ * This is the case when the message starts with the bot's name
+ * followed by [,:>] or when it is a private message.
+ *
+ * @return boolean true when the message is specifically targeted at the bot,
+ * false otherwise.
+ */
+ public function isTargetedMessage()
+ {
+ $event = $this->getEvent();
+
+ $self = preg_quote($this->connection->getNick());
+
+ $targetPattern = <<<REGEX
+ {^
+ \s*{$self}\s*[:>,].* # expect the bots name, followed by a [:>,]
+ $}ix
+REGEX;
+
+ return !$event->isInChannel()
+ || preg_match($targetPattern, $event->getText()) > 0;
+ }
+
+ /**
+ * Allow for prefix and bot name aware extraction of a message
+ *
+ * @return string|bool $message The message, which is possibly targeted at the
+ * bot or false if a prefix requirement failed
+ */
+ public function getMessage()
+ {
+ $event = $this->getEvent();
+
+ $prefix = preg_quote($this->getConfig('command.prefix'));
+ $self = preg_quote($this->connection->getNick());
+ $message = $event->getText();
+
+ // $prefixPattern matches : Phergie, do command <parameters>
+ // where $prefix = 'do' : do command <parameters>
+ // : Phergie, command <parameters>
+ $prefixPattern = <<<REGEX
+ {^
+ (?:
+ \s*{$self}\s*[:>,]\s* # start with bot name
+ (?:{$prefix})? # which is optionally followed by the prefix
+ |
+ \s*{$prefix} # or start with the prefix
+ )
+ \s*(.*) # always end with the message
+ $}ix
+REGEX;
+
+ // $noPrefixPattern matches : Phergie, command <parameters>
+ // : command <parameters>
+ $noPrefixPattern = <<<REGEX
+ {^
+ \s*(?:{$self}\s*[:>,]\s*)? # optionally start with the bot name
+ (.*?) # always end with the message
+ $}ix
+REGEX;
+
+ $pattern = $noPrefixPattern;
+
+ // If a prefix is set, force it as a requirement
+ if ($prefix && $event->isInChannel()) {
+ $pattern = $prefixPattern;
+ }
+
+ $match = null;
+
+ if (!preg_match($pattern, $message, $match)) {
+ return false;
+ }
+
+ return $match[1];
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/NickServ.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/NickServ.php
new file mode 100644
index 000000000..e97abecf7
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/NickServ.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_NickServ
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_NickServ
+ */
+
+/**
+ * Intercepts and responds to messages from the NickServ agent requesting that
+ * the bot authenticate its identify.
+ *
+ * The password configuration setting should contain the password registered
+ * with NickServ for the nick used by the bot.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_NickServ
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_NickServ
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_NickServ extends Phergie_Plugin_Abstract
+{
+ /**
+ * Nick of the NickServ bot
+ *
+ * @var string
+ */
+ protected $botNick;
+
+ /**
+ * Identify message
+ */
+ protected $identifyMessage;
+
+ /**
+ * Checks for dependencies and required configuration settings.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+
+ // Get the name of the NickServ bot, defaults to NickServ
+ $this->botNick = $this->getConfig('nickserv.botnick', 'NickServ');
+
+ // Get the identify message
+ $this->identifyMessage = $this->getConfig(
+ 'nickserv.identify_message',
+ '/This nickname is registered./'
+ );
+ }
+
+ /**
+ * Checks for a notice from NickServ and responds accordingly if it is an
+ * authentication request or a notice that a ghost connection has been
+ * killed.
+ *
+ * @return void
+ */
+ public function onNotice()
+ {
+ $event = $this->event;
+ if (strtolower($event->getNick()) == strtolower($this->botNick)) {
+ $message = $event->getArgument(1);
+ $nick = $this->connection->getNick();
+ if (preg_match($this->identifyMessage, $message)) {
+ $password = $this->config['nickserv.password'];
+ if (!empty($password)) {
+ $this->doPrivmsg($this->botNick, 'IDENTIFY ' . $password);
+ }
+ unset($password);
+ } elseif (preg_match('/^.*' . $nick . '.* has been killed/', $message)) {
+ $this->doNick($nick);
+ }
+ }
+ }
+
+ /**
+ * Checks to see if the original nick has quit; if so, take the name back.
+ *
+ * @return void
+ */
+ public function onQuit()
+ {
+ $eventNick = $this->event->getNick();
+ $nick = $this->connection->getNick();
+ if ($eventNick == $nick) {
+ $this->doNick($nick);
+ }
+ }
+
+ /**
+ * Changes the in-memory configuration setting for the bot nick if it is
+ * successfully changed.
+ *
+ * @return void
+ */
+ public function onNick()
+ {
+ $event = $this->event;
+ $connection = $this->connection;
+ if ($event->getNick() == $connection->getNick()) {
+ $connection->setNick($event->getArgument(0));
+ }
+ }
+
+ /**
+ * Provides a command to terminate ghost connections.
+ *
+ * @return void
+ */
+ public function onCommandGhostbust()
+ {
+ $event = $this->event;
+ $user = $event->getNick();
+ $conn = $this->connection;
+ $nick = $conn->getNick();
+
+ if ($nick != $this->config['connections'][$conn->getHost()]['nick']) {
+ $password = $this->config['nickserv.password'];
+ if (!empty($password)) {
+ $this->doPrivmsg(
+ $this->event->getSource(),
+ $user . ': Attempting to ghost ' . $nick .'.'
+ );
+ $this->doPrivmsg(
+ $this->botNick,
+ 'GHOST ' . $nick . ' ' . $password,
+ true
+ );
+ }
+ }
+ }
+
+ /**
+ * Automatically send the GHOST command if the bot's nick is in use.
+ *
+ * @return void
+ */
+ public function onResponse()
+ {
+ if ($this->event->getCode() == Phergie_Event_Response::ERR_NICKNAMEINUSE) {
+ $password = $this->config['nickserv.password'];
+ if (!empty($password)) {
+ $this->doPrivmsg(
+ $this->botNick,
+ 'GHOST ' . $this->connection->getNick() . ' ' . $password
+ );
+ }
+ }
+ }
+
+ /**
+ * Handle the server sending a KILL request.
+ *
+ * @return void
+ */
+ public function onKill()
+ {
+ $this->doQuit($this->event->getArgument(1));
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Part.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Part.php
new file mode 100755
index 000000000..c07cdd918
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Part.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Part
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Part
+ */
+
+/**
+ * Parts a specified channel on command from a user.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Part
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Part
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Part extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Parts a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to leave
+ *
+ * @return void
+ */
+ public function onCommandPart($channels)
+ {
+ $this->doPart($channels);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Php.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php.php
new file mode 100644
index 000000000..e10d101e7
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ */
+
+/**
+ * Returns information on PHP functions as requested.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Php extends Phergie_Plugin_Abstract
+{
+ /**
+ * Data source to use
+ *
+ * @var Phergie_Plugin_Php_Source
+ */
+ protected $source;
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ // @todo find a way to move this to Phergie_Plugin_Php_Source_Local
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+
+ $this->getPluginHandler()->getPlugin('Command');
+
+ $this->source = new Phergie_Plugin_Php_Source_Local;
+ }
+
+ /**
+ * Searches the data source for the requested function.
+ *
+ * @param string $functionName Name of the function to search for
+ *
+ * @return void
+ */
+ public function onCommandPhp($functionName)
+ {
+ $nick = $this->event->getNick();
+ if ($function = $this->source->findFunction($functionName)) {
+ $msg = $nick . ': ' . $function['description'];
+ $this->doPrivmsg($this->event->getSource(), $msg);
+ } else {
+ $msg = 'Search for function ' . $functionName . ' returned no results.';
+ $this->doNotice($nick, $msg);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source.php
new file mode 100644
index 000000000..c6cf3261b
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ */
+
+/**
+ * Data source interface for the Php plugin.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+interface Phergie_Plugin_Php_Source
+{
+ /**
+ * Searches for a description of the function.
+ *
+ * @param string $function Search pattern to match against the function
+ * name, wildcards supported using %
+ *
+ * @return array|null Associative array containing the function name and
+ * description or NULL if no results are found
+ */
+ public function findFunction($function);
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source/Local.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source/Local.php
new file mode 100644
index 000000000..9292bea84
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source/Local.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ */
+
+/**
+ * Data source for {@see Phergie_Plugin_Php}. This source reads function
+ * descriptions from a file and stores them in a SQLite database. When a
+ * function description is requested, the function is retrieved from the
+ * local database.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Php_Source_Local implements Phergie_Plugin_Php_Source
+{
+ /**
+ * Local database for storage
+ *
+ * @var PDO
+ */
+ protected $database;
+
+ /**
+ * Source of the PHP function summary
+ *
+ * @var string
+ */
+ protected $url = 'http://cvs.php.net/viewvc.cgi/phpdoc/funcsummary.txt?revision=HEAD';
+
+ /**
+ * Constructor to initialize the data source.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $path = dirname(__FILE__);
+
+ try {
+ $this->database = new PDO('sqlite:' . $path . '/functions.db');
+ $this->buildDatabase();
+ // @todo Modify this to be rethrown as an appropriate
+ // Phergie_Plugin_Exception and handled in Phergie_Plugin_Php
+ } catch (PDOException $e) {
+ echo 'PDO failure: '.$e->getMessage();
+ }
+ }
+
+ /**
+ * Searches for a description of the function.
+ *
+ * @param string $function Search pattern to match against the function
+ * name, wildcards supported using %
+ *
+ * @return array|null Associative array containing the function name and
+ * description or NULL if no results are found
+ */
+ public function findFunction($function)
+ {
+ // Remove possible parentheses
+ $split = preg_split('{\(|\)}', $function);
+ $function = (count($split)) ? array_shift($split) : $function;
+
+ // Prepare the database statement
+ $stmt = $this->database->prepare('SELECT `name`, `description` FROM `functions` WHERE `name` LIKE :function');
+ $stmt->execute(array(':function' => $function));
+
+ // Check the results
+ if (count($stmt) > 0) {
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ /**
+ * @todo add class and function URLS
+ * class methods: http://php.net/manual/en/classname.methodname.php
+ * functions: http://php.net/manual/en/function.functionname.php
+ * where '_' is replaced with '-'
+ */
+ return $result;
+ }
+
+ // No results found, return
+ return null;
+ }
+
+ /**
+ * Build the database and parses the function summary file into it.
+ *
+ * @param bool $rebuild TRUE to force a rebuild of the table used to
+ * house function information, FALSE otherwise, defaults to FALSE
+ *
+ * @return void
+ */
+ protected function buildDatabase($rebuild = false)
+ {
+ // Check to see if the functions table exists
+ $checkstmt = $this->database->query("SELECT COUNT(*) FROM `sqlite_master` WHERE `name` = 'functions'");
+ $checkstmt->execute();
+ $result = $checkstmt->fetch(PDO::FETCH_ASSOC);
+ unset( $checkstmt );
+ $table = $result['COUNT(*)'];
+ unset( $result );
+ // If the table doesn't exist, create it
+ if (!$table) {
+ $this->database->exec('CREATE TABLE `functions` (`name` VARCHAR(255), `description` TEXT)');
+ $this->database->exec('CREATE UNIQUE INDEX `functions_name` ON `functions` (`name`)');
+ }
+
+ // If we created a new table, fill it with data
+ if (!$table || $rebuild) {
+ // Get the contents of the source file
+ // @todo Handle possible error cases better here; the @ operator
+ // shouldn't be needed
+ $contents = @file($this->url, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);
+
+ if (!$contents) {
+ return;
+ }
+
+ // Parse the contents
+ $valid = array();
+ $firstPart = '';
+ $lineNumber = 0;
+ foreach ($contents as $line) {
+ // Clean the current line
+ $line = trim($line);
+
+ // Skip comment lines
+ if (0 === strpos($line, '#')) {
+ // reset the line if the current line is odd
+ if (($lineNumber % 2) !== 0) {
+ $lineNumber--;
+ }
+ continue;
+ }
+
+ /*
+ * If the current line is even, it's the first part of the
+ * complete function description ...
+ */
+ if (($lineNumber % 2) === 0) {
+ $firstPart = $line;
+ } else {
+ // ... it's the last part of the complete function description
+ $completeLine = $firstPart . ' ' . $line;
+ $firstPart = '';
+ if (preg_match('{^([^\s]*)[\s]?([^)]*)\(([^\)]*)\)[\sU]+([\sa-zA-Z0-9\.,\-_()]*)$}', $completeLine, $matches)) {
+ $valid[] = $matches;
+ }
+ }
+ // Up the line number before going to the next line
+ $lineNumber++;
+ }
+ // free up some memory
+ unset($contents);
+
+ // Process the valid matches
+ if (count($valid) > 0) {
+ // Clear the database
+ $this->database->exec('DELETE * FROM `functions`');
+
+ // Prepare the sql statement
+ $stmt = $this->database->prepare('INSERT INTO `functions` (`name`, `description`) VALUES (:name, :description)');
+ $this->database->beginTransaction();
+
+ // Insert the data
+ foreach ($valid as $function) {
+ // Extract function values
+ list( , $retval, $name, $params, $desc) = $function;
+ if (empty($name)) {
+ $name = $retval;
+ $retval = '';
+ }
+ // Reconstruct the complete function line
+ $line = trim($retval . ' ' . $name . '(' . $params . ') - ' . $desc);
+ // Execute the statement
+ $stmt->execute(array(':name' => $name, ':description' => $line));
+ }
+
+ // Commit the changes to the database
+ $this->database->commit();
+ }
+ // free up some more memory
+ unset($valid);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php
new file mode 100755
index 000000000..021670d97
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ping
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ping
+ */
+
+/**
+ * Uses a self CTCP PING to ensure that the client connection has not been
+ * dropped.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ping
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ping
+ */
+class Phergie_Plugin_Ping extends Phergie_Plugin_Abstract
+{
+ /**
+ * Timestamp for the last instance in which an event was received
+ *
+ * @var int
+ */
+ protected $lastEvent;
+
+ /**
+ * Timestamp for the last instance in which a PING was sent
+ *
+ * @var int
+ */
+ protected $lastPing;
+
+ /**
+ * Initialize event timestamps upon connecting to the server.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ $this->lastEvent = time();
+ $this->lastPing = null;
+ }
+
+ /**
+ * Updates the timestamp since the last received event when a new event
+ * arrives.
+ *
+ * @return void
+ */
+ public function preEvent()
+ {
+ $this->lastEvent = time();
+ }
+
+ /**
+ * Clears the ping time if a reply is received.
+ *
+ * @return void
+ */
+ public function onPingResponse()
+ {
+ $this->lastPing = null;
+ }
+
+ /**
+ * Performs a self ping if the event threshold has been exceeded or
+ * issues a termination command if the ping threshold has been exceeded.
+ *
+ * @return void
+ */
+ public function onTick()
+ {
+ $time = time();
+ if (!empty($this->lastPing)) {
+ if ($time - $this->lastPing > $this->getConfig('ping.ping', 20)) {
+ $this->doQuit();
+ }
+ } elseif (
+ $time - $this->lastEvent > $this->getConfig('ping.event', 300)
+ ) {
+ $this->lastPing = $time;
+ $this->doPing($this->getConnection()->getNick(), $this->lastPing);
+ }
+ }
+
+ /**
+ * Gets the last ping time
+ * lastPing needs exposing for things such as unit testing
+ *
+ * @return int timestamp of last ping
+ */
+ public function getLastPing()
+ {
+ return $this->lastPing;
+ }
+
+ /**
+ * Set the last ping time
+ * lastPing needs to be exposed for unit testing
+ *
+ * @param int|null $ping timestamp of last ping
+ *
+ * @return self
+ */
+ public function setLastPing($ping = null)
+ {
+ if (null === $ping) {
+ $ping = time();
+ }
+ if (!is_int($ping)) {
+ throw new InvalidArgumentException('$ping must be an integer or null');
+ }
+ $this->lastPing = $ping;
+ return $this;
+ }
+
+ /**
+ * Gets the last event time
+ * lastEvent needs exposing for things such as unit testing
+ *
+ * @return int timestamp of last ping
+ */
+ public function getLastEvent()
+ {
+ return $this->lastEvent;
+ }
+
+ /**
+ * Set the last event time
+ * lastEvent needs to be exposed for unit testing
+ *
+ * @param int|null $event timestamp of last ping
+ *
+ * @return self
+ */
+ public function setLastEvent($event = null)
+ {
+ if (null === $event) {
+ $event = time();
+ }
+ if (!is_int($event)) {
+ throw new InvalidArgumentException('$ping must be an integer or null');
+ }
+ $this->lastEvent = $event;
+ return $this;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Pong.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Pong.php
new file mode 100755
index 000000000..54e19fc06
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Pong.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Pong
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Pong
+ */
+
+/**
+ * Responds to PING requests from the server.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Pong
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Pong
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
+ */
+class Phergie_Plugin_Pong extends Phergie_Plugin_Abstract
+{
+ /**
+ * Sends a PONG response for each PING request received by the server.
+ *
+ * @return void
+ */
+ public function onPing()
+ {
+ $this->doPong($this->getEvent()->getArgument(0));
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Prioritize.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Prioritize.php
new file mode 100755
index 000000000..2312567fb
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Prioritize.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Prioritize
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Prioritize
+ */
+
+/**
+ * Prioritizes events such that they are executed in order from least to most
+ * destructive.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Prioritize
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Prioritize
+ */
+class Phergie_Plugin_Prioritize extends Phergie_Plugin_Abstract
+{
+ /**
+ * Event types ordered by priority of execution
+ *
+ * @var array
+ */
+ protected $priority = array(
+ 'raw',
+ 'pass',
+ 'user',
+ 'ping',
+ 'pong',
+ 'notice',
+ 'join',
+ 'list',
+ 'names',
+ 'version',
+ 'stats',
+ 'links',
+ 'time',
+ 'trace',
+ 'admin',
+ 'info',
+ 'who',
+ 'whois',
+ 'whowas',
+ 'mode',
+ 'privmsg',
+ 'action',
+ 'nick',
+ 'topic',
+ 'invite',
+ 'kill',
+ 'part',
+ 'quit'
+ );
+
+ /**
+ * Prioritizes events from least to most destructive.
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ $events = $this->getEventHandler();
+
+ // Categorize events by type
+ $categorized = array();
+ foreach ($events as $event) {
+ $type = $event->getType();
+ if (!isset($categorized[$type])) {
+ $categorized[$type] = array();
+ }
+ $categorized[$type][] = $event;
+ }
+
+ // Order events by type from least to most destructive
+ $types = array_intersect($this->priority, array_keys($categorized));
+ $prioritized = array();
+ foreach ($types as $type) {
+ $prioritized = array_merge($prioritized, $categorized[$type]);
+ }
+
+ // Replace the original events array with the prioritized one
+ $events->replaceEvents($prioritized);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Puppet.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Puppet.php
new file mode 100644
index 000000000..bede0be2c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Puppet.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Puppet
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Puppet
+ */
+
+/**
+ * Allows a user to effectively speak and act as the bot.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Puppet
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Puppet
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Puppet extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Handles a request for the bot to repeat a given message in a specified
+ * channel.
+ *
+ * <code>say #chan message</code>
+ *
+ * @param string $channel Name of the channel
+ * @param string $message Message to repeat
+ *
+ * @return void
+ */
+ public function onCommandSay($channel, $message)
+ {
+ $this->doPrivmsg($channel, $message);
+ }
+
+ /**
+ * Handles a request for the bot to repeat a given action in a specified
+ * channel.
+ *
+ * <code>act #chan action</code>
+ *
+ * @param string $channel Name of the channel
+ * @param string $action Action to perform
+ *
+ * @return void
+ */
+ public function onCommandAct($channel, $action)
+ {
+ $this->doAction($channel, $action);
+ }
+
+ /**
+ * Handles a request for the bot to send the server a raw message
+ *
+ * <code>raw message</code>
+ *
+ * @param string $message Message to send
+ *
+ * @return void
+ */
+ public function onCommandRaw($message)
+ {
+ $this->doRaw($message);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php
new file mode 100755
index 000000000..eca22a98e
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Quit
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Quit
+ */
+
+/**
+ * Terminates the current connection upon command.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Quit
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Quit
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Quit extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Issues a quit command when a message is received requesting that the
+ * bot terminate the current connection.
+ *
+ * @return void
+ */
+ public function onCommandQuit()
+ {
+ $this->doQuit('Requested by ' . $this->getEvent()->getNick());
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php
new file mode 100755
index 000000000..430577081
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Reload
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Reload
+ */
+
+/**
+ * Facilitates reloading of individual plugins for development purposes.
+ * Note that, because existing class definitions cannot be removed from
+ * memory, increased memory usage is an expected result of using this plugin.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Reload
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Reload
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Reloads a specified plugin.
+ *
+ * @param string $plugin Short name of the plugin to reload
+ *
+ * @return void
+ */
+ public function onCommandReload($plugin)
+ {
+ $plugin = ucfirst($plugin);
+
+ $evalClass = true;
+ if (strpos($plugin, ' ') !== false) {
+ $args = explode(' ', $plugin);
+ $plugin = $args[0];
+ if (strtolower($args[1]) == 'force') {
+ $evalClass = false;
+ }
+ }
+
+ if (!$this->plugins->hasPlugin($plugin)) {
+ echo 'DEBUG(Reload): ' . ucfirst($plugin) . ' is not loaded yet, loading', PHP_EOL;
+ try {
+ $this->plugins->getPlugin($plugin);
+ $this->plugins->command->populateMethodCache();
+ } catch (Phergie_Plugin_Exception $e) {
+ if ($e->getCode() == Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND) {
+ echo 'DEBUG(Reload): ', $e->getMessage(), PHP_EOL;
+ } else {
+ throw $e;
+ }
+ }
+ return;
+ }
+
+ try {
+ $info = $this->plugins->getPluginInfo($plugin);
+ } catch (Phergie_Plugin_Exception $e) {
+ $source = $this->event->getSource();
+ $nick = $this->event->getNick();
+ $this->doNotice($source, $nick . ': ' . $e->getMessage());
+ return;
+ }
+
+ $class = $info['class'];
+ $contents = file_get_contents($info['file']);
+ $newClass = $class . '_' . sha1($contents);
+
+ if (class_exists($newClass, false)) {
+ if ($evalClass == true) {
+ echo 'DEBUG(Reload): Class ', $class, ' has not changed since last reload', PHP_EOL;
+ return;
+ }
+ } else {
+ $contents = preg_replace(
+ array('/^<\?(?:php)?/', '/class\s+' . $class . '/i'),
+ array('', 'class ' . $newClass),
+ $contents
+ );
+ eval($contents);
+ }
+
+ $instance = new $newClass;
+ $instance->setName($plugin);
+ $instance->setEvent($this->event);
+ $this->plugins
+ ->removePlugin($plugin)
+ ->addPlugin($instance);
+
+ $this->plugins->command->populateMethodCache();
+ if ($this->plugins->hasPlugin('Help')) {
+ $this->plugins->help->populateRegistry();
+ }
+
+ echo 'DEBUG(Reload): Reloaded ', $class, ' to ', $newClass, PHP_EOL;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Remind.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Remind.php
new file mode 100644
index 000000000..42d674c68
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Remind.php
@@ -0,0 +1,378 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Remind
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Remind
+ */
+
+/**
+ * Parses and logs messages that should be relayed to other users the next time
+ * the recipient is active on the same channel.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Remind
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Remind
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Time pear.phergie.org
+ * @uses extension PDO
+ * @uses extension pdo_sqlite
+ */
+class Phergie_Plugin_Remind extends Phergie_Plugin_Abstract
+{
+ /**
+ * Number of reminders to show in public.
+ */
+ protected $publicReminders = 3;
+
+ /**
+ * PDO resource for a SQLite database containing the reminders.
+ *
+ * @var resource
+ */
+ protected $db;
+
+ /**
+ * Flag that indicates whether or not to use an in-memory reminder list.
+ *
+ * @var bool
+ */
+ protected $keepListInMemory = true;
+
+ /**
+ * In-memory store for pending reminders.
+ */
+ protected $msgStorage = array();
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Time');
+
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+
+ $dir = dirname(__FILE__) . '/' . $this->getName();
+ $path = $dir . '/reminder.db';
+ if (!file_exists($dir)) {
+ mkdir($dir);
+ }
+
+ if (isset($this->config['remind.use_memory'])) {
+ $this->keepListInMemory = (bool) $this->config['remind.use_memory'];
+ }
+
+ if (isset($this->config['remind.public_reminders'])) {
+ $this->publicReminders = (int) $this->config['remind.public_reminders'];
+ $this->publicReminders = max($this->publicReminders, 0);
+ }
+
+ try {
+ $this->db = new PDO('sqlite:' . $path);
+ $this->createTables();
+ } catch (PDO_Exception $e) {
+ throw new Phergie_Plugin_Exception($e->getMessage());
+ }
+ }
+
+ /**
+ * Intercepts a message and processes any contained recognized commands.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $source = $this->getEvent()->getSource();
+ $nick = $this->getEvent()->getNick();
+
+ $this->deliverReminders($source, $nick);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandTell($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandAsk($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandRemind($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handles the tell/remind command (stores the message)
+ *
+ * @param string $recipient name of the recipient
+ * @param string $message message to store
+ *
+ * @return void
+ */
+ protected function handleRemind($recipient, $message)
+ {
+ $source = $this->getEvent()->getSource();
+ $nick = $this->getEvent()->getNick();
+
+ if (!$this->getEvent()->isInChannel()) {
+ $this->doPrivmsg($source, 'Reminders must be requested in-channel.');
+ return;
+ }
+
+ $q = $this->db->prepare(
+ 'INSERT INTO remind
+ (
+ time,
+ channel,
+ recipient,
+ sender,
+ message
+ )
+ VALUES
+ (
+ :time,
+ :channel,
+ :recipient,
+ :sender,
+ :message
+ )'
+ );
+ try {
+ $q->execute(
+ array(
+ 'time' => date(DATE_RFC822),
+ 'channel' => $source,
+ 'recipient' => strtolower($recipient),
+ 'sender' => strtolower($nick),
+ 'message' => $message
+ )
+ );
+ } catch (PDOException $e) {
+ }
+
+ if ($rowid = $this->db->lastInsertId()) {
+ $this->doPrivmsg($source, 'ok, ' . $nick . ', message stored');
+ } else {
+ $this->doPrivmsg(
+ $source,
+ $nick . ': bad things happened. Message not saved.'
+ );
+ return;
+ }
+
+ if ($this->keepListInMemory) {
+ $this->msgStorage[$source][strtolower($recipient)] = $rowid;
+ }
+ }
+
+ /**
+ * Determines if the user has pending reminders, and if so, delivers them.
+ *
+ * @param string $channel channel to check
+ * @param string $nick nick to check
+ *
+ * @return void
+ */
+ protected function deliverReminders($channel, $nick)
+ {
+ if ($channel[0] != '#') {
+ // private message, not a channel, so don't check
+ return;
+ }
+
+ // short circuit if there's no message in memory (if allowed)
+ if ($this->keepListInMemory
+ && !isset($this->msgStorage[$channel][strtolower($nick)])
+ ) {
+ return;
+ }
+
+ // fetch and deliver messages
+ $reminders = $this->fetchMessages($channel, $nick);
+ if (count($reminders) > $this->publicReminders) {
+ $msgs = array_slice($reminders, 0, $this->publicReminders);
+ $privmsgs = array_slice($reminders, $this->publicReminders);
+ } else {
+ $msgs = $reminders;
+ $privmsgs = false;
+ }
+
+ foreach ($msgs as $msg) {
+ $ts = $this->plugins->time->getCountdown($msg['time']);
+ $formatted = sprintf(
+ '%s: (from %s, %s ago) %s',
+ $nick, $msg['sender'], $ts, $msg['message']
+ );
+ $this->doPrivmsg($channel, $formatted);
+ $this->deleteMessage($msg['rowid'], $channel, $nick);
+ }
+
+ if ($privmsgs) {
+ foreach ($privmsgs as $msg) {
+ $ts = $this->plugins->time->getCountdown($msg['time']);
+ $formatted = sprintf(
+ 'from %s, %s ago: %s',
+ $msg['sender'], $ts, $msg['message']
+ );
+ $this->doPrivmsg($nick, $formatted);
+ $this->deleteMessage($msg['rowid'], $channel, $nick);
+ }
+ $formatted = sprintf(
+ '%s: (%d more messages sent in private.)',
+ $nick, count($privmsgs)
+ );
+ $this->doPrivmsg($channel, $formatted);
+ }
+ }
+
+ /**
+ * Get pending messages (for a specific channel/recipient)
+ *
+ * @param string $channel channel on which to check for pending messages
+ * @param string $recipient user for which to check pending messages
+ *
+ * @return array of records
+ */
+ protected function fetchMessages($channel = null, $recipient = null)
+ {
+ if ($channel) {
+ $qClause = 'WHERE channel = :channel AND recipient LIKE :recipient';
+ $params = compact('channel', 'recipient');
+ } else {
+ $qClause = '';
+ $params = array();
+ }
+ $q = $this->db->prepare(
+ 'SELECT rowid, channel, sender, recipient, time, message
+ FROM remind ' . $qClause
+ );
+ $q->execute($params);
+ return $q->fetchAll();
+ }
+
+ /**
+ * Deletes a delivered message
+ *
+ * @param int $rowid ID of the message to delete
+ * @param string $channel message's channel
+ * @param string $nick message's recipient
+ *
+ * @return void
+ */
+ protected function deleteMessage($rowid, $channel, $nick)
+ {
+ $nick = strtolower($nick);
+ $q = $this->db->prepare('DELETE FROM remind WHERE rowid = :rowid');
+ $q->execute(array('rowid' => $rowid));
+
+ if ($this->keepListInMemory) {
+ if (isset($this->msgStorage[$channel][$nick])
+ && $this->msgStorage[$channel][$nick] == $rowid
+ ) {
+ unset($this->msgStorage[$channel][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Determines if a table exists
+ *
+ * @param string $name Table name
+ *
+ * @return bool
+ */
+ protected function haveTable($name)
+ {
+ $sql = 'SELECT COUNT(*) FROM sqlite_master WHERE name = '
+ . $this->db->quote($name);
+ return (bool) $this->db->query($sql)->fetchColumn();
+ }
+
+ /**
+ * Creates the database table(s) (if they don't exist)
+ *
+ * @return void
+ */
+ protected function createTables()
+ {
+ if (!$this->haveTable('remind')) {
+ $this->db->exec(
+ 'CREATE TABLE
+ remind
+ (
+ time INTEGER,
+ channel TEXT,
+ recipient TEXT,
+ sender TEXT,
+ message TEXT
+ )'
+ );
+ }
+ }
+
+ /**
+ * Populates the in-memory cache of pending reminders
+ *
+ * @return void
+ */
+ protected function populateMemory()
+ {
+ if (!$this->keepListInMemory) {
+ return;
+ }
+ foreach ($this->fetchMessages() as $msg) {
+ $this->msgStorage[$msg['channel']][$msg['recipient']] = $msg['rowid'];
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php
new file mode 100755
index 000000000..cdb8f7f91
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Serve
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Serve
+ */
+
+/**
+ * Processes requests to serve a user something from a database.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Serve
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Serve
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ */
+class Phergie_Plugin_Serve extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+ }
+
+ /**
+ * Retrieves a random item from the database table.
+ *
+ * @param string $database Path to the SQLite database file
+ * @param string $table Name of the database table
+ * @param array $request Parsed request
+ *
+ * @return object Retrieved item
+ */
+ protected function getItem($database, $table, array $request)
+ {
+ $db = new PDO('sqlite:' . $database);
+ if (!empty($request['suggestion'])) {
+ $query = 'SELECT * FROM ' . $table . ' WHERE name LIKE ? ORDER BY RANDOM() LIMIT 1';
+ $stmt = $db->prepare($query);
+ $stmt->execute(array('%' . $request['suggestion'] . '%'));
+ $item = $stmt->fetchObject();
+ if (!$item) {
+ $item = new stdClass;
+ $item->name = $request['suggestion'];
+ $item->link = null;
+ }
+ } else {
+ $query = 'SELECT * FROM ' . $table . ' ORDER BY RANDOM() LIMIT 1';
+ $stmt = $db->query($query);
+ $item = $stmt->fetchObject();
+ }
+ return $item;
+ }
+
+ /**
+ * Processes a request to serve a user something.
+ *
+ * @param string $database Path to the SQLite database file
+ * @param string $table Name of the database table
+ * @param string $format Format of the response where %target%,
+ * %item%, %article%', and %link will be replaced with their
+ * respective data
+ * @param string $request Request string including the target and an
+ * optional suggestion of the item to fetch
+ * @param boolean $censor TRUE to integrate with the Censor plugin,
+ * defaults to FALSE
+ *
+ * @return boolean TRUE if the request was processed successfully, FALSE
+ * otherwise
+ */
+ public function serve($database, $table, $format, $request, $censor = false)
+ {
+ // Parse the request
+ $result = preg_match(
+ '/(?P<target>[^\s]+)(\s+an?\s+)?(?P<suggestion>.*)?/',
+ $request,
+ $match
+ );
+
+ if (!$result) {
+ return false;
+ }
+
+ // Resolve the target
+ $target = $match['target'];
+ if ($target == 'me') {
+ $target = $this->event->getNick();
+ }
+
+ // Process the request
+ $item = $this->getItem($database, $table, $match);
+
+ // Reprocess the request for censorship if required
+ if ($this->plugins->hasPlugin('Censor')) {
+ $plugin = $this->plugins->getPlugin('Censor');
+ $attempts = 0;
+ while ($censor && $attempts < 3) {
+ $clean = $plugin->cleanString($item->name);
+ if ($item->name != $clean) {
+ $attempts++;
+ $item = $this->getItem($database, $table, $match);
+ } else {
+ $censor = false;
+ }
+ }
+ if ($censor && $attempts == 3) {
+ $this->doAction($this->event->getSource(), 'shrugs.');
+ }
+ }
+
+ // Derive the proper article for the item
+ if (preg_match('/^[aeiou]/i', $item->name)) {
+ $article = 'an';
+ } else {
+ $article = 'a';
+ }
+
+ // Format the message
+ $replacements = array(
+ 'target' => $target,
+ 'item' => $item->name,
+ 'link' => $item->link,
+ 'article' => $article
+ );
+
+ $msg = $format;
+ foreach ($replacements as $placeholder => $value) {
+ $msg = str_replace(
+ '%' . $placeholder . '%',
+ $value,
+ $msg
+ );
+ }
+
+ // Send the message
+ $this->doAction($this->event->getSource(), $msg);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/SpellCheck.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/SpellCheck.php
new file mode 100644
index 000000000..b731cffc8
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/SpellCheck.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TerryChay
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TerryChay
+ */
+
+/**
+ * Handles requests for checking spelling of specified words and returning
+ * either confirmation of correctly spelled words or potential correct
+ * spellings for misspelled words.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_SpellCheck
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TerryChay
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses extension pspell
+ */
+class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
+{
+ /**
+ * Spell check dictionary handler
+ *
+ * @var resource
+ */
+ protected $pspell;
+
+ /**
+ * Limit on the number of potential correct spellings returned
+ *
+ * @var int
+ */
+ protected $limit;
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('pspell')) {
+ $this->fail('pspell php extension is required');
+ }
+
+ if (!$this->getConfig('spellcheck.lang')) {
+ $this->fail('Setting spellcheck.lang must be filled-in');
+ }
+
+ $this->plugins->getPlugin('Command');
+
+ set_error_handler(array($this, 'loadDictionaryError'));
+ $this->pspell = pspell_new($this->getConfig('spellcheck.lang'));
+ restore_error_handler();
+
+ $this->limit = $this->getConfig('spellcheck.limit', 5);
+ }
+
+ /**
+ * Intercepts and handles requests for spell checks.
+ *
+ * @param string $word the string to perform checks against
+ *
+ * @return void
+ */
+ public function onCommandSpell($word)
+ {
+ $source = $this->event->getSource();
+ $target = $this->event->getNick();
+
+ $message = $target . ': The word "' . $word;
+ $message .= '" seems to be spelled correctly.';
+ if (!pspell_check($this->pspell, $word)) {
+ $suggestions = pspell_suggest($this->pspell, $word);
+
+ $message = $target;
+ $message .= ': I could not find any suggestions for "' . $word . '".';
+ if (!empty($suggestions)) {
+ $suggestions = array_splice($suggestions, 0, $this->limit);
+ $message = $target . ': Suggestions for "';
+ $message .= $word . '": ' . implode(', ', $suggestions) . '.';
+ }
+ }
+
+ $this->doPrivmsg($source, $message);
+ }
+
+ /**
+ * Handle any errors from loading dictionary
+ *
+ * @param integer $errno Error code
+ * @param string $errstr Error message
+ * @param string $errfile File that errored
+ * @param integer $errline Line where the error happened
+ *
+ * @return void
+ */
+ protected function loadDictionaryError($errno, $errstr, $errfile, $errline)
+ {
+ $this->fail($errstr);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Statusnet.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Statusnet.php
new file mode 100644
index 000000000..dc2680a6d
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Statusnet.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Talks to the Statusnet IM architecture to enqueue incoming message messages
+ * and notify result of nickname registration checks
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Statusnet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class Phergie_Plugin_Statusnet extends Phergie_Plugin_Abstract {
+ /**
+ * Message callback details
+ *
+ * @var array
+ */
+ protected $messageCallback;
+
+ /**
+ * Registration check callback details
+ *
+ * @var array
+ */
+ protected $regCallback;
+
+ /**
+ * Connection established callback details
+ *
+ * @var array
+ */
+ protected $connectedCallback;
+
+ /**
+ * Load callback from config
+ */
+ public function onLoad() {
+ $messageCallback = $this->config['statusnet.messagecallback'];
+ if (is_callable($messageCallback)) {
+ $this->messageCallback = $messageCallback;
+ } else {
+ $this->messageCallback = NULL;
+ }
+
+ $regCallback = $this->config['statusnet.regcallback'];
+ if (is_callable($regCallback)) {
+ $this->regCallback = $regCallback;
+ } else {
+ $this->regCallback = NULL;
+ }
+
+ $connectedCallback = $this->config['statusnet.connectedcallback'];
+ if (is_callable($connectedCallback)) {
+ $this->connectedCallback = $connectedCallback;
+ } else {
+ $this->connectedCallback = NULL;
+ }
+
+ $this->unregRegexp = $this->getConfig('statusnet.unregregexp', '/\x02(.*?)\x02 (?:isn\'t|is not) registered/i');
+ $this->regRegexp = $this->getConfig('statusnet.regregexp', '/(?:\A|\x02)(\w+?)\x02? (?:\(account|is \w+?\z)/i');
+ }
+
+ /**
+ * Passes incoming messages to StatusNet
+ *
+ * @return void
+ */
+ public function onPrivmsg() {
+ if ($this->messageCallback !== NULL) {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $sender = $event->getNick();
+ $message = trim($event->getText());
+
+ if (strpos($source, '#') === 0) {
+ $botNick = $this->getConnection()->getNick();
+ $nickPos = strpos($message, $botNick);
+ $nickLen = strlen($botNick);
+ $colonPos = strpos($message, ':', $nickLen);
+ $commandStr = trim(substr($message, $colonPos+1));
+ if ($nickPos === 0 && $colonPos == $nickLen && !empty($commandStr)) {
+ call_user_func($this->messageCallback, array('source' => $source, 'sender' => $sender, 'message' => $commandStr));
+ }
+ } else {
+ call_user_func($this->messageCallback, array('source' => $source, 'sender' => $sender, 'message' => $message));
+ }
+ }
+ }
+
+ /**
+ * Catches the response from NickServ
+ *
+ * @return void
+ */
+ public function onNotice() {
+ if ($this->regCallback !== NULL) {
+ $event = $this->getEvent();
+ if ($event->getNick() == 'NickServ') {
+ $message = $event->getArgument(1);
+ if (preg_match($this->unregRegexp, $message, $groups)) {
+ $screenname = $groups[1];
+ call_user_func($this->regCallback, array('screenname' => $screenname, 'registered' => false));
+ } elseif (preg_match($this->regRegexp, $message, $groups)) {
+ $screenname = $groups[1];
+ call_user_func($this->regCallback, array('screenname' => $screenname, 'registered' => true));
+ }
+ }
+ }
+ }
+
+ /**
+ * Intercepts the end of the "message of the day" response and tells
+ * StatusNet we're connected
+ *
+ * @return void
+ */
+ public function onResponse() {
+ switch ($this->getEvent()->getCode()) {
+ case Phergie_Event_Response::RPL_ENDOFMOTD:
+ case Phergie_Event_Response::ERR_NOMOTD:
+ if ($this->connectedCallback !== NULL) {
+ call_user_func($this->connectedCallback);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php
new file mode 100644
index 000000000..c6453566c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Tea
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Tea
+ */
+
+/**
+ * Processes requests to serve users tea.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Tea
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Tea
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Tea extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user tea.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what tea to serve
+ *
+ * @return void
+ */
+ public function onCommandTea($request)
+ {
+ $format = $this->getConfig(
+ 'tea.format',
+ 'serves %target% a cup of %item% tea.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Tea/tea.db',
+ 'tea',
+ $format,
+ $request
+ );
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php
new file mode 100644
index 000000000..21fe1950d
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php
@@ -0,0 +1,49 @@
+<?php
+
+if (!defined('__DIR__')) {
+ define('__DIR__', dirname(__FILE__));
+}
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/tea.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE tea (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX tea_name ON tea (name)');
+$insert = $db->prepare('INSERT INTO tea (name, link) VALUES (:name, :link)');
+
+// Get raw teacuppa.com data set
+echo 'Downloading teacuppa.com data set', PHP_EOL;
+$file = __DIR__ . '/tea-list.html';
+if (!file_exists($file)) {
+ copy('http://www.teacuppa.com/tea-list.asp', $file);
+}
+$contents = file_get_contents($file);
+
+// Extract data from data set
+echo 'Processing teacuppa.com data', PHP_EOL;
+$contents = tidy_repair_string($contents);
+libxml_use_internal_errors(true);
+$doc = new DOMDocument;
+$doc->loadHTML($contents);
+libxml_clear_errors();
+$xpath = new DOMXPath($doc);
+$teas = $xpath->query('//p[@class="page_title"]/following-sibling::table//a');
+$db->beginTransaction();
+foreach ($teas as $tea) {
+ $name = preg_replace(
+ array('/\s*\v+\s*/', '/\s+tea\s*$/i'),
+ array(' ', ''),
+ $tea->textContent
+ );
+ $link = 'http://teacuppa.com/' . $tea->getAttribute('href');
+ $insert->execute(array($name, $link));
+}
+$db->commit();
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unlink($file);
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Temperature.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Temperature.php
new file mode 100644
index 000000000..541fd85cf
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Temperature.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Temperature
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Temperature
+ */
+
+/**
+ * Performs temperature calculations for other plugins.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Temperature
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Temperature
+ */
+class Phergie_Plugin_Temperature extends Phergie_Plugin_Abstract
+{
+ /**
+ * Converts a temperature in Celsius to Fahrenheit.
+ *
+ * @param int $temp Temperature in Celsius
+ *
+ * @return int Temperature converted to Fahrenheit
+ */
+ public function convertCelsiusToFahrenheit($temp)
+ {
+ return round(((((int) $temp * 9) / 5) + 32));
+ }
+
+ /**
+ * Converts a temperature in Fahrenheit to Celsius.
+ *
+ * @param int $temp Temperature in Fahrenheit
+ *
+ * @return int Temperature converted to Celsius
+ */
+ public function convertFahrenheitToCelsius($temp)
+ {
+ return round(((((int) $temp - 32) * 5) / 9));
+ }
+
+ /**
+ * Calculates the heat index (i.e. "feels like" temperature) based on
+ * temperature and relative humidity.
+ *
+ * @param int $temperature Temperature in degrees Fahrenheit
+ * @param int $humidity Relative humidity (ex: 68)
+ * @return int Heat index in degrees Fahrenheit
+ */
+ public function getHeatIndex($temperature, $humidity)
+ {
+ $temperature2 = $temperature * $temperature;
+ $humidity2 = $humidity * $humidity;
+ return round(
+ -42.379 +
+ (2.04901523 * $temperature) +
+ (10.14333127 * $humidity) -
+ (0.22475541 * $temperature * $humidity) -
+ (0.00683783 * $temperature2) -
+ (0.05481717 * $humidity2) +
+ (0.00122874 * $temperature2 * $humidity) +
+ (0.00085282 * $temperature * $humidity2) -
+ (0.00000199 * $temperature2 * $humidity2)
+ );
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/TerryChay.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/TerryChay.php
new file mode 100644
index 000000000..246cfc398
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/TerryChay.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TerryChay
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TerryChay
+ */
+
+/**
+ * Parses incoming messages for the words "Terry Chay" or tychay and responds
+ * with a random Terry fact retrieved from the Chayism web service.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TerryChay
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TerryChay
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+class Phergie_Plugin_TerryChay extends Phergie_Plugin_Abstract
+{
+ /**
+ * URL to the web service
+ *
+ * @const string
+ */
+ const URL = 'http://phpdoc.info/chayism/';
+
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Http');
+ }
+
+ /**
+ * Fetches a chayism.
+ *
+ * @return string|bool Fetched chayism or FALSE if the operation failed
+ */
+ public function getChayism()
+ {
+ return $this
+ ->getPluginHandler()
+ ->getPlugin('Http')
+ ->get(self::URL)
+ ->getContent();
+ }
+
+ /**
+ * Parses incoming messages for "Terry Chay" and related variations and
+ * responds with a chayism.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $message = $event->getText();
+ $pattern
+ = '{^(' . preg_quote($this->getConfig('command.prefix')) .
+ '\s*)?.*(terry\s+chay|tychay)}ix';
+
+ if (preg_match($pattern, $message)) {
+ if($fact = $this->getChayism()) {
+ $this->doPrivmsg($source, 'Fact: ' . $fact);
+ }
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php
new file mode 100644
index 000000000..8559426b6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TheFuckingWeather
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TheFuckingWeather
+ */
+
+/**
+ * Detects and responds to requests for current weather conditions in a
+ * particular location using data from a web service.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TheFuckingWeather
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TheFuckingWeather
+ * @link http://thefuckingweather.com
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+
+class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract
+{
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http = null;
+
+ /**
+ * Base API URL
+ *
+ * @var string
+ */
+ protected $url = 'http://www.thefuckingweather.com/?zipcode=';
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $pluginHandler = $this->getPluginHandler();
+ $pluginHandler->getPlugin('Command');
+ $this->http = $pluginHandler->getPlugin('Http');
+ }
+
+ /**
+ * Returns the weather from the specified location.
+ *
+ * @param string $location Location term
+ *
+ * @return void
+ * @todo Implement use of URL shortening here
+ */
+ public function onCommandThefuckingweather($location)
+ {
+ $source = $this->getEvent()->getSource();
+ $target = $this->getEvent()->getNick();
+ $out = $this->getWeather($location);
+ if (!$out) {
+ $this->doNotice($source, $out);
+ } else {
+ $this->doPrivmsg($source, $target . ': ' . $out);
+ }
+ }
+
+ /**
+ * Alias for TheFuckingWeather command.
+ *
+ * @param string $location Location term
+ *
+ * @return void
+ */
+ public function onCommandTfw($location)
+ {
+ $this->onCommandThefuckingweather($location);
+ }
+
+ /**
+ * Get the necessary content and returns the search result.
+ *
+ * @param string $location Location term
+ *
+ * @return string|bool Search result or FALSE if none is found
+ * @todo Try to optimize pregs
+ */
+ protected function getWeather($location)
+ {
+ $url = $this->url . urlencode($location);
+ $response = $this->http->get($url);
+ $content = $response->getContent();
+
+ preg_match_all(
+ '#<div><span class="small">(.*?)<\/span><\/div>#im',
+ $content, $matches
+ );
+ $location = $matches[1][0];
+
+ if (!empty($location)) {
+ preg_match_all(
+ '#<div class="large" >(.*?)<br \/>#im',
+ $content, $matches
+ );
+ $temp_numb = (int) $matches[1][0];
+ $temp_numb .= ' F / ' . round(($temp_numb - 32) / 1.8, 0) . ' C?!';
+
+ preg_match_all(
+ '#<br \/>(.*?)<\/div><div id="remark"><br \/>#im',
+ $content, $matches
+ );
+ $temp_desc = $matches[1][0];
+
+ preg_match_all(
+ '#<div id="remark"><br \/>\n<span>(.*?)<\/span><\/div>#im',
+ $content, $matches
+ );
+ $remark = $matches[1][0];
+
+ $result = "{$location}: {$temp_numb} {$temp_desc} ({$remark})";
+ $result = preg_replace('/</', ' <', $result);
+ $result = strip_tags($result);
+ return html_entity_decode($result);
+ } else {
+ return 'No fucking clue where that is.';
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Time.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Time.php
new file mode 100644
index 000000000..0d90bd8a6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Time.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Time
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Time
+ */
+
+/**
+ * Helper plugin to assist other plugins with time manipulation, display.
+ *
+ * Any shared time-related code should go into this class.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Time
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Time
+ */
+class Phergie_Plugin_Time extends Phergie_Plugin_Abstract
+{
+ /**
+ * Returns the time interval between the current time and a given
+ * timestamp.
+ *
+ * @param string $timestamp Timestamp compatible with strtotime()
+ *
+ * @return string
+ */
+ public function getCountdown($timestamp)
+ {
+ $time = time() - strtotime($timestamp);
+ $return = array();
+
+ $days = floor($time / 86400);
+ if ($days > 0) {
+ $return[] = $days . 'd';
+ $time %= 86400;
+ }
+
+ $hours = floor($time / 3600);
+ if ($hours > 0) {
+ $return[] = $hours . 'h';
+ $time %= 3600;
+ }
+
+ $minutes = floor($time / 60);
+ if ($minutes > 0) {
+ $return[] = $minutes . 'm';
+ $time %= 60;
+ }
+
+ if ($time > 0 || count($return) <= 0) {
+ $return[] = ($time > 0 ? $time : '0') . 's';
+ }
+
+ return implode(' ', $return);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php
new file mode 100644
index 000000000..d7d64a471
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+
+/**
+ * Responds to a request for a TLD (formatted as .tld where tld is the TLD to
+ * be looked up) with its corresponding description.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Tld
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Tld
+ * @uses extension PDO
+ * @uses extension pdo_sqlite
+ */
+class Phergie_Plugin_Tld extends Phergie_Plugin_Abstract
+{
+ /**
+ * Connection to the database
+ *
+ * @var PDO
+ */
+ protected $db;
+
+ /**
+ * Prepared statement for selecting a single TLD
+ *
+ * @var PDOStatement
+ */
+ protected $select;
+
+ /**
+ * Prepared statement for selecting all TLDs
+ *
+ * @var PDOStatement
+ */
+ protected $selectAll;
+
+ /**
+ * Checks for dependencies and sets up the database and hard-coded values.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+
+ $dbFile = dirname(__FILE__) . '/Tld/tld.db';
+ try {
+ $this->db = new PDO('sqlite:' . $dbFile);
+
+ $this->select = $this->db->prepare('
+ SELECT type, description
+ FROM tld
+ WHERE LOWER(tld) = LOWER(:tld)
+ ');
+
+ $this->selectAll = $this->db->prepare('
+ SELECT tld, type, description
+ FROM btld
+ ');
+ } catch (PDOException $e) {
+ $this->getPluginHandler()->removePlugin($this);
+ }
+ }
+
+ /**
+ * takes a tld in the format '.tld' and returns its related data
+ *
+ * @param string $tld tld to process
+ *
+ * @return null
+ */
+ public function onCommandTld($tld)
+ {
+ $tld = ltrim($tld, '.');
+ $description = $this->getTld($tld);
+ $this->doPrivmsg(
+ $this->event->getSource(),
+ "{$this->getEvent()->getNick()}: .{$tld} -> "
+ . ($description ? $description : 'Unknown TLD')
+ );
+ }
+
+ /**
+ * Retrieves the definition for a given TLD if it exists
+ *
+ * @param string $tld TLD to search for
+ *
+ * @return mixed Definition of the given TLD as a string or false if unknown
+ */
+ public function getTld($tld)
+ {
+ $tld = trim(strtolower($tld));
+ if ($this->select->execute(array('tld' => $tld))) {
+ $tlds = $this->select->fetch();
+ if (is_array($tlds)) {
+ return '(' . $tlds['type'] . ') ' . $tlds['description'];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a list of all the TLDs and their definitions
+ *
+ * @return mixed Array of all the TLDs and their definitions or FALSE on
+ * failure
+ */
+ public function getTlds()
+ {
+ if ($this->selectAll->execute()) {
+ $tlds = $this->selectAll->fetchAll();
+ if (is_array($tlds)) {
+ $tldinfo = array();
+ foreach ($tlds as $key => $tld) {
+ if (!empty($tld['tld'])) {
+ $tldinfo[$tld['tld']] = "({$tld['type']}) "
+ . $tld['description'];
+ }
+ }
+ return $tldinfo;
+ }
+ }
+ return false;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php
new file mode 100644
index 000000000..28f963ad1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php
@@ -0,0 +1,68 @@
+<?php
+
+$dbFile = 'tld.db';
+
+if (file_exists($dbFile)) {
+ exit;
+}
+
+$db = new PDO('sqlite:' . dirname(__FILE__) . '/' . $dbFile);
+
+$query = '
+ CREATE TABLE tld (
+ tld VARCHAR(20),
+ type VARCHAR(20),
+ description VARCHAR(255)
+ )
+';
+$db->exec($query);
+
+$insert = $db->prepare('
+ INSERT INTO tld (tld, type, description)
+ VALUES (:tld, :type, :description)
+');
+
+$contents = file_get_contents(
+ 'http://www.iana.org/domains/root/db/'
+);
+
+libxml_use_internal_errors(true);
+$doc = new DOMDocument;
+$doc->loadHTML($contents);
+libxml_clear_errors();
+
+$descriptions = array(
+ 'com' => 'Commercial',
+ 'info' => 'Information',
+ 'net' => 'Network',
+ 'org' => 'Organization',
+ 'edu' => 'Educational',
+ 'name' => 'Individuals, by name'
+);
+
+$xpath = new DOMXPath($doc);
+$rows = $xpath->query('//tr[contains(@class, "iana-group")]');
+foreach (range(0, $rows->length - 1) as $index) {
+ $row = $rows->item($index);
+ $tld = strtolower(ltrim($row->childNodes->item(0)->textContent, '.'));
+ $type = $row->childNodes->item(1)->nodeValue;
+ if (isset($descriptions[$tld])) {
+ $description = $descriptions[$tld];
+ } else {
+ $description = $row->childNodes->item(2)->textContent;
+ $regex = '{(^(?:Reserved|Restricted)\s*(?:exclusively\s*)?'
+ . '(?:for|to)\s*(?:members of\s*)?(?:the|support)?'
+ . '\s*|\s*as advised.*$)}i';
+ $description = preg_replace($regex, '', $description);
+ $description = ucfirst(trim($description));
+ }
+ $data = array_map(
+ 'html_entity_decode',
+ array(
+ 'tld' => $tld,
+ 'type' => $type,
+ 'description' => $description
+ )
+ );
+ $insert->execute($data);
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter.php
new file mode 100644
index 000000000..4a77d1e41
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter.php
@@ -0,0 +1,206 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Twitter
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Twitter
+ */
+
+/**
+ * These requires are for library code, so they don't fit Autoload's normal
+ * conventions.
+ *
+ * @link http://github.com/scoates/simpletweet
+ */
+require dirname(__FILE__) . '/Twitter/twitter.class.php';
+require dirname(__FILE__) . '/Twitter/laconica.class.php';
+
+/**
+ * Twitter plugin; Allows tweet (if configured) and twitter commands
+ *
+ * Usage:
+ * tweet text to tweet
+ * (sends a message to twitter and Phergie will give you the link)
+ * twitter username
+ * (fetches and displays the last tweet by @username)
+ * twitter username 3
+ * (fetches and displays the third last tweet by @username)
+ * twitter 1234567
+ * (fetches and displays tweet number 1234567)
+ * http://twitter.com/username/statuses/1234567
+ * (same as `twitter 1234567`)
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Twitter
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Twitter
+ * @uses Phergie_Plugin_Time pear.phergie.org
+ */
+class Phergie_Plugin_Twitter extends Phergie_Plugin_Abstract
+{
+ /**
+ * Twitter object (from Simpletweet)
+ */
+ protected $twitter;
+
+ /**
+ * Twitter user
+ */
+ protected $twitteruser = null;
+
+ /**
+ * Password
+ */
+ protected $twitterpassword = null;
+
+ /**
+ * Register with the URL plugin, if possible
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ if ($url = $this->getPluginHandler()->getPlugin('Url')) {
+ $url->registerRenderer($this);
+ }
+ }
+
+ /**
+ * Initialize (set up configuration vars)
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ if (!isset($this->config['twitter.class'])
+ || !$twitterClass = $this->config['twitter.class']
+ ) {
+ $twitterClass = 'Twitter';
+ }
+
+ $this->twitteruser = $this->config['twitter.user'];
+ $this->twitterpassword = $this->config['twitter.password'];
+ $url = $this->config['twitter.url'];
+
+ $this->twitter = new $twitterClass(
+ $this->twitteruser,
+ $this->twitterpassword,
+ $url
+ );
+
+ }
+
+ /**
+ * Fetches the associated tweet and relays it to the channel
+ *
+ * @param string $tweeter if numeric the tweet number/id, otherwise the
+ * twitter user name (optionally prefixed with @)
+ * @param int $num optional tweet number for this user (number of
+ * tweets ago)
+ *
+ * @return void
+ */
+ public function onCommandTwitter($tweeter = null, $num = 1)
+ {
+ $source = $this->getEvent()->getSource();
+ if (is_numeric($tweeter)) {
+ $tweet = $this->twitter->getTweetByNum($tweeter);
+ } else if (is_null($tweeter) && $this->twitteruser) {
+ $tweet = $this->twitter->getLastTweet($this->twitteruser, 1);
+ } else {
+ $tweet = $this->twitter->getLastTweet(ltrim($tweeter, '@'), $num);
+ }
+ if ($tweet) {
+ $this->doPrivmsg($source, $this->formatTweet($tweet));
+ }
+ }
+
+ /**
+ * Sends a tweet to Twitter as the configured user
+ *
+ * @param string $txt the text to tweet
+ *
+ * @return void
+ */
+ public function onCommandTweet($txt)
+ {
+ $nick = $this->getEvent()->getNick();
+ if (!$this->twitteruser) {
+ return;
+ }
+ $source = $this->getEvent()->getSource();
+ if ($tweet = $this->twitter->sendTweet($txt)) {
+ $this->doPrivmsg(
+ $source, 'Tweeted: '
+ . $this->twitter->getUrlOutputStatus($tweet)
+ );
+ } else {
+ $this->doNotice($nick, 'Tweet failed');
+ }
+ }
+
+ /**
+ * Formats a Tweet into a message suitable for output
+ *
+ * @param object $tweet JSON-decoded tweet object from Twitter
+ * @param bool $includeUrl whether or not to include the URL in the
+ * formatted output
+ *
+ * @return string
+ */
+ protected function formatTweet(StdClass $tweet, $includeUrl = true)
+ {
+ $ts = $this->plugins->time->getCountDown($tweet->created_at);
+ $out = '<@' . $tweet->user->screen_name .'> '. $tweet->text
+ . ' - ' . $ts . ' ago';
+ if ($includeUrl) {
+ $out .= ' (' . $this->twitter->getUrlOutputStatus($tweet) . ')';
+ }
+ return $out;
+ }
+
+ /**
+ * Renders a URL
+ *
+ * @param array $parsed parse_url() output for the URL to render
+ *
+ * @return bool
+ */
+ public function renderUrl(array $parsed)
+ {
+ if ($parsed['host'] != 'twitter.com'
+ && $parsed['host'] != 'www.twitter.com'
+ ) {
+ // unable to render non-twitter URLs
+ return false;
+ }
+
+ $source = $this->getEvent()->getSource();
+
+ if (preg_match('#^/(.*?)/status(es)?/([0-9]+)$#', $parsed['path'], $matches)
+ ) {
+ $tweet = $this->twitter->getTweetByNum($matches[3]);
+ if ($tweet) {
+ $this->doPrivmsg($source, $this->formatTweet($tweet, false));
+ }
+ return true;
+ }
+
+ // if we get this far, we haven't satisfied the URL, so bail:
+ return false;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/laconica.class.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/laconica.class.php
new file mode 100644
index 000000000..e411991db
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/laconica.class.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Sean's Simple Twitter Library - Laconica extension
+ *
+ * Copyright 2008, Sean Coates
+ * Usage of the works is permitted provided that this instrument is retained
+ * with the works, so that any entity that uses the works is notified of this
+ * instrument.
+ * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
+ * ( Fair License - http://www.opensource.org/licenses/fair.php )
+ * Short license: do whatever you like with this.
+ *
+ */
+class Twitter_Laconica extends Twitter {
+
+ /**
+ * Constructor; sets up configuration.
+ *
+ * @param string $user Laconica user name; null for limited read-only access
+ * @param string $pass Laconica password; null for limited read-only access
+ * @param string $baseUrl Base URL of Laconica install. Defaults to identi.ca
+ */
+ public function __construct($user=null, $pass=null, $baseUrl = 'http://identi.ca/') {
+ $this->baseUrl = $baseUrl;
+ parent::__construct($user, $pass);
+ }
+
+ /**
+ * Returns the base API URL
+ */
+ protected function getUrlApi() {
+ return $this->baseUrlFull . 'api/';
+ }
+
+ /**
+ * Output URL: status
+ */
+ public function getUrlOutputStatus(StdClass $tweet) {
+ return $this->baseUrl . 'notice/' . urlencode($tweet->id);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/twitter.class.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/twitter.class.php
new file mode 100644
index 000000000..31173a6d0
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/twitter.class.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * Sean's Simple Twitter Library
+ *
+ * Probably a little more or a little less than you need.
+ *
+ * Copyright 2008, Sean Coates
+ * Usage of the works is permitted provided that this instrument is retained
+ * with the works, so that any entity that uses the works is notified of this
+ * instrument.
+ * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
+ * ( Fair License - http://www.opensource.org/licenses/fair.php )
+ * Short license: do whatever you like with this.
+ *
+ * komode: le=unix language=php codepage=utf8 tab=4 notabs indent=4
+ */
+class Twitter {
+
+ /**
+ * Base URL for Twitter API
+ *
+ * Do not specify user/password in URL
+ */
+ protected $baseUrl = 'http://twitter.com/';
+
+ /**
+ * Full base URL (includes user/pass)
+ *
+ * (created in Init)
+ */
+ protected $baseUrlFull = null;
+
+ /**
+ * Twitter API user
+ */
+ protected $user;
+
+ /**
+ * Twitter API password
+ */
+ protected $pass;
+
+ /**
+ * Constructor; sets up configuration.
+ *
+ * @param string $user Twitter user name; null for limited read-only access
+ * @param string $pass Twitter password; null for limited read-only access
+ */
+ public function __construct($user=null, $pass=null) {
+ $this->baseUrlFull = $this->baseUrl;
+ if (null !== $user) {
+ // user is defined, so use it in the URL
+ $this->user = $user;
+ $this->pass = $pass;
+ $parsed = parse_url($this->baseUrl);
+ $this->baseUrlFull = $parsed['scheme'] . '://' . $this->user . ':' .
+ $this->pass . '@' . $parsed['host'];
+ // port (optional)
+ if (isset($parsed['port']) && is_numeric($parsed['port'])) {
+ $this->baseUrlFull .= ':' . $parsed['port'];
+ }
+ // append path (default: /)
+ if (isset($parsed['path'])) {
+ $this->baseUrlFull .= $parsed['path'];
+ } else {
+ $this->baseUrlFull .= '/';
+ }
+ }
+ }
+
+ /**
+ * Fetches a tweet by its number/id
+ *
+ * @param int $num the tweet id/number
+ * @return string (null on failure)
+ */
+ public function getTweetByNum($num) {
+ if (!is_numeric($num)) {
+ return;
+ }
+ $tweet = json_decode(file_get_contents($this->getUrlStatus($num)));
+ return $tweet;
+ }
+
+ /**
+ * Reads [last] tweet from user
+ *
+ * @param string $tweeter the tweeter username
+ * @param int $num this many tweets ago (1 = current tweet)
+ * @return string (false on failure)
+ */
+ public function getLastTweet($tweeter, $num = 1)
+ {
+ $source = json_decode(file_get_contents($this->getUrlUserTimeline($tweeter)));
+ if ($num > count($source)) {
+ return false;
+ }
+ $tweet = $source[$num - 1];
+ if (!isset($tweet->user->screen_name) || !$tweet->user->screen_name) {
+ return false;
+ }
+ return $tweet;
+ }
+
+ /**
+ * fetches mentions for a user
+ */
+ public function getMentions($sinceId=null, $count=20) {
+ return json_decode(file_get_contents($this->getUrlMentions($sinceId, $count)));
+ }
+
+ /**
+ * Fetches followers for a user
+ */
+ public function getFollowers($cursor=-1) {
+ return json_decode(file_get_contents($this->getUrlFollowers($cursor)));
+ }
+
+ /**
+ * Follow a userid
+ */
+ public function follow($userId) {
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => array(),
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlFollow($userId), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * fetches DMs for a user
+ */
+ public function getDMs($sinceId=null, $count=20, $page=1) {
+ return json_decode(file_get_contents($this->getUrlDMs($sinceId, $count, $page)));
+ }
+
+ /**
+ * Send DM
+ */
+ public function sendDM($screenName, $text) {
+ $data = http_build_query(array('screen_name'=>$screenName, 'text'=>$text));
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => $data,
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlSendDM(), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * Sends a tweet
+ *
+ * @param string $txt the tweet text to send
+ * @return string URL of tweet (or false on failure)
+ */
+ public function sendTweet($txt, $limit=true) {
+ if ($limit) {
+ $txt = substr($txt, 0, 140); // twitter message size limit
+ }
+ $data = 'status=' . urlencode($txt);
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => $data,
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlTweetPost(), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * Returns the base API URL
+ */
+ protected function getUrlApi() {
+ return $this->baseUrlFull;
+ }
+
+ /**
+ * Returns the status URL
+ *
+ * @param int $num the tweet number
+ */
+ protected function getUrlStatus($num) {
+ return $this->getUrlApi() . 'statuses/show/'. urlencode($num) .'.json';
+ }
+
+ /**
+ * Returns the user timeline URL
+ */
+ protected function getUrlUserTimeline($user) {
+ return $this->getUrlApi() . 'statuses/user_timeline/'. urlencode($user) .'.json';
+ }
+
+ /**
+ * Returns the tweet posting URL
+ */
+ protected function getUrlTweetPost() {
+ return $this->getUrlApi() . 'statuses/update.json';
+ }
+
+ /**
+ * Output URL: status
+ */
+ public function getUrlOutputStatus(StdClass $tweet) {
+ return $this->baseUrl . urlencode($tweet->user->screen_name) . '/statuses/' . urlencode($tweet->id);
+ }
+
+ /**
+ * Return mentions URL
+ */
+ public function getUrlMentions($sinceId=null, $count=20) {
+ $url = $this->baseUrlFull . 'statuses/mentions.json?count=' . urlencode($count);
+ if ($sinceId !== null) {
+ $url .= '&since_id=' . urlencode($sinceId);
+ }
+ return $url;
+ }
+
+ /**
+ * Returns the followers URL
+ */
+ public function getUrlFollowers($cursor=-1) {
+ return $this->baseUrlFull . 'statuses/followers.json?cursor=' . ((int)$cursor);
+ }
+
+ /**
+ * Returns the follow-user URL
+ */
+ public function getUrlFollow($userid) {
+ return $this->baseUrlFull . 'friendships/create/' . ((int) $userid) . '.json';
+ }
+
+ /**
+ * Returns the get DMs URL
+ */
+ public function getUrlDMs($sinceId=null, $count=20, $page=1) {
+ $url = $this->baseUrlFull . 'direct_messages.json?';
+ if ($sinceId !== null) {
+ $url .= 'since_id=' . urlencode($sinceId);
+ }
+ $url .= "&page={$page}";
+ $url .= "&count={$count}";
+ return $url;
+ }
+
+ /**
+ * Returns the send DM URL
+ */
+ public function getURLSendDM() {
+ return $this->baseUrlFull . 'direct_messages/new.json';
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Url.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url.php
new file mode 100644
index 000000000..bac115b1b
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url.php
@@ -0,0 +1,638 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+
+/**
+ * Monitors incoming messages for instances of URLs and responds with messages
+ * containing relevant information about detected URLs.
+ *
+ * Has an utility method accessible via
+ * $this->getPlugin('Url')->getTitle('http://foo..').
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ * @uses Phergie_Plugin_Encoding pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ * @uses Phergie_Plugin_Tld pear.phergie.org
+ */
+class Phergie_Plugin_Url extends Phergie_Plugin_Abstract
+{
+ /**
+ * Links output format
+ *
+ * Can use the variables %nick%, %title% and %link% in it to display
+ * page titles and links
+ *
+ * @var string
+ */
+ protected $baseFormat = '%message%';
+ protected $messageFormat = '[ %link% ] %title%';
+
+ /**
+ * Flag indicating whether a single response should be sent for a single
+ * message containing multiple links
+ *
+ * @var bool
+ */
+ protected $mergeLinks = true;
+
+ /**
+ * Max length of the fetched URL title
+ *
+ * @var int
+ */
+ protected $titleLength = 40;
+
+ /**
+ * Url cache to prevent spamming, especially with multiple bots on the
+ * same channel
+ *
+ * @var array
+ */
+ protected $urlCache = array();
+ protected $shortCache = array();
+
+ /**
+ * Time in seconds to store the cached entries
+ *
+ * Setting it to 0 or below disables the cache expiration
+ *
+ * @var int
+ */
+ protected $expire = 1800;
+
+ /**
+ * Number of entries to keep in the cache at one time per channel
+ *
+ * Setting it to 0 or below disables the cache limit
+ *
+ * @var int
+ */
+ protected $limit = 10;
+
+ /**
+ * Flag that determines if the plugin will fall back to using an HTTP
+ * stream when a URL using SSL is detected and OpenSSL support isn't
+ * available in the PHP installation in use
+ *
+ * @var bool
+ */
+ protected $sslFallback = true;
+
+ /**
+ * Flag that is set to true by the custom error handler if an HTTP error
+ * code has been received
+ *
+ * @var boolean
+ */
+ protected $errorStatus = false;
+ protected $errorMessage = null;
+
+ /**
+ * Flag indicating whether or not to display error messages as the title
+ * if a link posted encounters an error
+ *
+ * @var boolean
+ */
+ protected $showErrors = true;
+
+ /**
+ * Flag indicating whether to detect schemeless URLS (i.e. "example.com")
+ *
+ * @var boolean
+ */
+ protected $detectSchemeless = false;
+
+ /**
+ * Shortener object
+ */
+ protected $shortener;
+
+ /**
+ * Array of renderers
+ */
+ protected $renderers = array();
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Encoding');
+ $plugins->getPlugin('Http');
+ $plugins->getPlugin('Tld');
+
+ // make the shortener configurable
+ $shortener = $this->getConfig('url.shortener', 'Trim');
+ $shortener = "Phergie_Plugin_Url_Shorten_{$shortener}";
+ $this->shortener = new $shortener($this->plugins->getPlugin('Http'));
+
+ if (!$this->shortener instanceof Phergie_Plugin_Url_Shorten_Abstract) {
+ $this->fail("Declared shortener class {$shortener} is not of proper ancestry");
+ }
+
+ // load config (a bit ugly, but focusing on porting):
+ foreach (
+ array(
+ 'detect_schemeless' => 'detectSchemeless',
+ 'base_format' => 'baseFormat',
+ 'message_format' => 'messageFormat',
+ 'merge_links' => 'mergeLinks',
+ 'title_length' => 'titleLength',
+ 'show_errors' => 'showErrors',
+ 'expire' => 'expire',
+ ) as $config => $local) {
+ if (isset($this->config["url.{$config}"])) {
+ $this->$local = $this->config["uri.{$config}"];
+ }
+ }
+ }
+
+ /**
+ * Checks an incoming message for the presence of a URL and, if one is
+ * found, responds with its title if it is an HTML document and the
+ * shortened equivalent of its original URL if it meets length requirements.
+ *
+ * @todo Update this to pull configuration settings from $this->config
+ * rather than caching them as class properties
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $this->handleMsg();
+ }
+
+ /**
+ * Checks an incoming message for the presence of a URL and, if one is
+ * found, responds with its title if it is an HTML document and the
+ * shortened equivalent of its original URL if it meets length requirements.
+ *
+ * @todo Update this to pull configuration settings from $this->config
+ * rather than caching them as class properties
+ * @return void
+ */
+ public function onAction()
+ {
+ $this->handleMsg();
+ }
+
+ /**
+ * Handles message events and responds with url titles.
+ *
+ * @return void
+ */
+ protected function handleMsg()
+ {
+ $source = $this->getEvent()->getSource();
+ $user = $this->getEvent()->getNick();
+
+ $responses = array();
+ $urls = $this->findUrls($this->getEvent()->getArgument(1));
+
+ foreach ($urls as $parsed) {
+ $url = $parsed['glued'];
+
+ // allow out-of-class renderers to handle this URL
+ foreach ($this->renderers as $renderer) {
+ if ($renderer->renderUrl($parsed) === true) {
+ // renderers should return true if they've fully
+ // rendered the passed URL (they're responsible
+ // for their own output)
+ $this->debug('Handled by renderer: ' . get_class($renderer));
+ continue 2;
+ }
+ }
+
+ // Convert url
+ $shortenedUrl = $this->shortener->shorten($url);
+ if (!$shortenedUrl) {
+ $this->debug('Invalid Url: Unable to shorten. (' . $url . ')');
+ $shortenedUrl = $url;
+ }
+
+ // Prevent spamfest
+ if ($this->checkUrlCache($url, $shortenedUrl)) {
+ $this->debug('Invalid Url: URL is in the cache. (' . $url . ')');
+ continue;
+ }
+
+ $title = $this->getTitle($url);
+ if (!empty($title)) {
+ $responses[] = str_replace(
+ array(
+ '%title%',
+ '%link%',
+ '%nick%'
+ ), array(
+ $title,
+ $shortenedUrl,
+ $user
+ ), $this->messageFormat
+ );
+ }
+
+ // Update cache
+ $this->updateUrlCache($url, $shortenedUrl);
+ unset($title, $shortenedUrl, $title);
+ }
+
+ // Check to see if there were any URL responses, format them and handle if they
+ // get merged into one message or not
+ if (count($responses) > 0) {
+ if ($this->mergeLinks) {
+ $message = str_replace(
+ array(
+ '%message%',
+ '%nick%'
+ ), array(
+ implode('; ', $responses),
+ $user
+ ), $this->baseFormat
+ );
+ $this->doPrivmsg($source, $message);
+ } else {
+ foreach ($responses as $response) {
+ $message = str_replace(
+ array(
+ '%message%',
+ '%nick%'
+ ), array(
+ implode('; ', $responses),
+ $user
+ ), $this->baseFormat
+ );
+ $this->doPrivmsg($source, $message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Detect URLs in a given string.
+ *
+ * @param string $message the string to detect urls in
+ *
+ * @return array the array of urls found
+ */
+ public function findUrls($message)
+ {
+ $pattern = '#'.($this->detectSchemeless ? '' : 'https?://').'(?:([0-9]{1,3}(?:\.[0-9]{1,3}){3})(?![^/]) | ('
+ .($this->detectSchemeless ? '(?<!http:/|https:/)[@/\\\]' : '').')?(?:(?:[a-z0-9_-]+\.?)+\.[a-z0-9]{1,6}))[^\s]*#xis';
+ $urls = array();
+
+ // URL Match
+ if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $m) {
+ $url = trim(rtrim($m[0], ', ].?!;'));
+
+ // Check to see if the URL was from an email address, is a directory, etc
+ if (!empty($m[2])) {
+ $this->debug('Invalid Url: URL is either an email or a directory path. (' . $url . ')');
+ continue;
+ }
+
+ // Parse the given URL
+ if (!$parsed = $this->parseUrl($url)) {
+ $this->debug('Invalid Url: Could not parse the URL. (' . $url . ')');
+ continue;
+ }
+
+ // Check to see if the given IP/Host is valid
+ if (!empty($m[1]) and !$this->checkValidIP($m[1])) {
+ $this->debug('Invalid Url: ' . $m[1] . ' is not a valid IP address. (' . $url . ')');
+ continue;
+ }
+
+ // Process TLD if it's not an IP
+ if (empty($m[1])) {
+ // Get the TLD from the host
+ $pos = strrpos($parsed['host'], '.');
+ $parsed['tld'] = ($pos !== false ? substr($parsed['host'], ($pos+1)) : '');
+
+ // Check to see if the URL has a valid TLD
+ if ($this->plugins->tld->getTld($parsed['tld']) === false) {
+ $this->debug('Invalid Url: ' . $parsed['tld'] . ' is not a supported TLD. (' . $url . ')');
+ continue;
+ }
+ }
+
+ // Check to see if the URL is to a secured site or not and handle it accordingly
+ if ($parsed['scheme'] == 'https' && !extension_loaded('openssl')) {
+ if (!$this->sslFallback) {
+ $this->debug('Invalid Url: HTTPS is an invalid scheme, OpenSSL isn\'t available. (' . $url . ')');
+ continue;
+ } else {
+ $parsed['scheme'] = 'http';
+ }
+ }
+
+ if (!in_array($parsed['scheme'], array('http', 'https'))) {
+ $this->debug('Invalid Url: ' . $parsed['scheme'] . ' is not a supported scheme. (' . $url . ')');
+ continue;
+ }
+
+ $urls[] = $parsed + array('glued' => $this->glueURL($parsed));
+ }
+ }
+
+ return $urls;
+ }
+
+ /**
+ * Checks a given URL (+shortened) against the cache to verify if they were
+ * previously posted on the channel.
+ *
+ * @param string $url The URL to check against
+ * @param string $shortenedUrl The shortened URL to check against
+ *
+ * @return bool
+ */
+ protected function checkUrlCache($url, $shortenedUrl)
+ {
+ $source = $this->getEvent()->getSource();
+
+ /**
+ * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
+ * and minimize the size of the cache for less cache bloat.
+ */
+ $url = $this->getUrlChecksum($url);
+ $shortenedUrl = $this->getUrlChecksum($shortenedUrl);
+
+ $cache = array(
+ 'url' => isset($this->urlCache[$source][$url]) ? $this->urlCache[$source][$url] : null,
+ 'shortened' => isset($this->shortCache[$source][$shortenedUrl]) ? $this->shortCache[$source][$shortenedUrl] : null
+ );
+
+ $expire = $this->expire;
+ $this->debug("Cache expire: {$expire}");
+ /**
+ * If cache expiration is enabled, check to see if the given url has expired in the cache
+ * If expire is disabled, simply check to see if the url is listed
+ */
+ if (($expire > 0 && (($cache['url'] + $expire) > time() || ($cache['shortened'] + $expire) > time()))
+ || ($expire <= 0 && (isset($cache['url']) || isset($cache['shortened'])))
+ ) {
+ unset($cache, $url, $shortenedUrl, $expire);
+ return true;
+ }
+ unset($cache, $url, $shortenedUrl, $expire);
+ return false;
+ }
+
+ /**
+ * Updates the cache and adds the given URL (+shortened) to the cache. It
+ * also handles cleaning the cache of old entries as well.
+ *
+ * @param string $url The URL to add to the cache
+ * @param string $shortenedUrl The shortened to add to the cache
+ *
+ * @return bool
+ */
+ protected function updateUrlCache($url, $shortenedUrl)
+ {
+ $source = $this->getEvent()->getSource();
+
+ /**
+ * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
+ * and minimize the size of the cache for less cache bloat.
+ */
+ $url = $this->getUrlChecksum($url);
+ $shortenedUrl = $this->getUrlChecksum($shortenedUrl);
+ $time = time();
+
+ // Handle the URL cache and remove old entries that surpass the limit if enabled
+ $this->urlCache[$source][$url] = $time;
+ if ($this->limit > 0 && count($this->urlCache[$source]) > $this->limit) {
+ asort($this->urlCache[$source], SORT_NUMERIC);
+ array_shift($this->urlCache[$source]);
+ }
+
+ // Handle the shortened cache and remove old entries that surpass the limit if enabled
+ $this->shortCache[$source][$shortenedUrl] = $time;
+ if ($this->limit > 0 && count($this->shortCache[$source]) > $this->limit) {
+ asort($this->shortCache[$source], SORT_NUMERIC);
+ array_shift($this->shortCache[$source]);
+ }
+ unset($url, $shortenedUrl, $time);
+ }
+
+ /**
+ * Transliterates a UTF-8 string into corresponding ASCII characters and
+ * truncates and appends an ellipsis to the string if it exceeds a given
+ * length.
+ *
+ * @param string $str String to decode
+ * @param int $trim Maximum string length, optional
+ *
+ * @return string
+ */
+ protected function decode($str, $trim = null)
+ {
+ $out = $this->plugins->encoding->transliterate($str);
+ if ($trim > 0) {
+ $out = substr($out, 0, $trim) . (strlen($out) > $trim ? '...' : '');
+ }
+ return $out;
+ }
+
+ /**
+ * Takes a url, parses and cleans the URL without of all the junk
+ * and then return the hex checksum of the url.
+ *
+ * @param string $url url to checksum
+ *
+ * @return string the hex checksum of the cleaned url
+ */
+ protected function getUrlChecksum($url)
+ {
+ $checksum = strtolower(urldecode($this->glueUrl($url, true)));
+ $checksum = preg_replace('#\s#', '', $this->plugins->encoding->transliterate($checksum));
+ return dechex(crc32($checksum));
+ }
+
+ /**
+ * Parses a given URI and procceses the output to remove redundant
+ * or missing values.
+ *
+ * @param string $url the url to parse
+ *
+ * @return array the url components
+ */
+ protected function parseUrl($url)
+ {
+ if (is_array($url)) return $url;
+
+ $url = trim(ltrim($url, ' /@\\'));
+ if (!preg_match('&^(?:([a-z][-+.a-z0-9]*):)&xis', $url, $matches)) {
+ $url = 'http://' . $url;
+ }
+ $parsed = parse_url($url);
+
+ if (!isset($parsed['scheme'])) {
+ $parsed['scheme'] = 'http';
+ }
+ $parsed['scheme'] = strtolower($parsed['scheme']);
+
+ if (isset($parsed['path']) && !isset($parsed['host'])) {
+ $host = $parsed['path'];
+ $path = '';
+ if (strpos($parsed['path'], '/') !== false) {
+ list($host, $path) = array_pad(explode('/', $parsed['path'], 2), 2, null);
+ }
+ $parsed['host'] = $host;
+ $parsed['path'] = $path;
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * Parses a given URI and then glues it back together in the proper format.
+ * If base is set, then it chops off the scheme, user and pass and fragment
+ * information to return a more unique base URI.
+ *
+ * @param string $uri uri to rebuild
+ * @param string $base set to true to only return the base components
+ *
+ * @return string the rebuilt uri
+ */
+ protected function glueUrl($uri, $base = false)
+ {
+ $parsed = $uri;
+ if (!is_array($parsed)) {
+ $parsed = $this->parseUrl($parsed);
+ }
+
+ if (is_array($parsed)) {
+ $uri = '';
+ if (!$base) {
+ $uri .= (!empty($parsed['scheme']) ? $parsed['scheme'] . ':' .
+ ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '');
+ $uri .= (!empty($parsed['user']) ? $parsed['user'] .
+ (!empty($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '');
+ }
+ if ($base && !empty($parsed['host'])) {
+ $parsed['host'] = trim($parsed['host']);
+ if (substr($parsed['host'], 0, 4) == 'www.') {
+ $parsed['host'] = substr($parsed['host'], 4);
+ }
+ }
+ $uri .= (!empty($parsed['host']) ? $parsed['host'] : '');
+ if (!empty($parsed['port'])
+ && (($parsed['scheme'] == 'http' && $parsed['port'] == 80)
+ || ($parsed['scheme'] == 'https' && $parsed['port'] == 443))
+ ) {
+ unset($parsed['port']);
+ }
+ $uri .= (!empty($parsed['port']) ? ':' . $parsed['port'] : '');
+ if (!empty($parsed['path']) && (!$base || $base && $parsed['path'] != '/')) {
+ $uri .= (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : ('/' . $parsed['path']);
+ }
+ $uri .= (!empty($parsed['query']) ? '?' . $parsed['query'] : '');
+ if (!$base) {
+ $uri .= (!empty($parsed['fragment']) ? '#' . $parsed['fragment'] : '');
+ }
+ }
+ return $uri;
+ }
+
+ /**
+ * Checks the given string to see if its a valid IP4 address
+ *
+ * @param string $ip the ip to validate
+ *
+ * @return bool
+ */
+ protected function checkValidIP($ip)
+ {
+ return long2ip(ip2long($ip)) === $ip;
+ }
+
+ /**
+ * Returns the title of the given page
+ *
+ * @param string $url url to the page
+ *
+ * @return string title
+ */
+ public function getTitle($url)
+ {
+ $http = $this->plugins->getPlugin('Http');
+ $options = array(
+ 'timeout' => 3.5,
+ 'user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12'
+ );
+
+ $response = $http->head($url, array(), $options);
+ $header = $response->getHeaders('Content-Type');
+
+ if (!preg_match('#^(text/x?html|application/xhtml+xml)(?:;.*)?$#', $header)) {
+ $title = $header;
+ } else {
+ $response = $http->get($url, array(), $options);
+ $content = $response->getContent();
+ if (preg_match('#<title[^>]*>(.*?)</title>#is', $content, $match)) {
+ $title = preg_replace('/[\s\v]+/', ' ', trim($match[1]));
+ }
+ }
+ $encoding = $this->plugins->getPlugin('Encoding');
+ $title = $encoding->decodeEntities($title);
+
+ if (empty($title)) {
+ if ($response->isError()) {
+ $title = $response->getCodeAsString();
+ } else {
+ $title = 'No Title';
+ }
+ }
+
+ return $title;
+ }
+
+ /**
+ * Output a debug message
+ *
+ * @param string $msg the message to output
+ *
+ * @return void
+ */
+ protected function debug($msg)
+ {
+ echo "(DEBUG:Url) $msg\n";
+ }
+
+ /**
+ * Add a renderer to the stack
+ *
+ * @param object $obj the renderer to add
+ *
+ * @return void
+ */
+ public function registerRenderer($obj)
+ {
+ $this->renderers[spl_object_hash($obj)] = $obj;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Abstract.php
new file mode 100644
index 000000000..607d1654c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Abstract.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ */
+
+/**
+ * URL shortener abstract class
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+abstract class Phergie_Plugin_Url_Shorten_Abstract
+{
+ protected $http;
+
+ /**
+ * Constructor
+ *
+ * @param Phergie_Plugin_Http $http instance of the http plugin
+ */
+ public function __construct(Phergie_Plugin_Http $http)
+ {
+ $this->http = $http;
+ }
+
+ /**
+ * Returns an array of request parameters given a url to shorten. The
+ * following keys are valid request parameters:
+ *
+ * * 'uri': the URI for the request (required)
+ * * 'query': an array of key-value pairs sent in a GET request
+ * * 'post': an array of key-value pairs sent in a POST request
+ * * 'callback': to be called after the request is finished. Should accept
+ * a Phergie_Plugin_Http_Response object and return either the shortened
+ * url or false if an error has occured.
+ *
+ * If the 'post' key is present a POST request shall be made; otherwise
+ * a GET request will be made. The 'post' key can be an empty array and
+ * a post request will still be made.
+ *
+ * If no callback is provided the contents of the response will be returned.
+ *
+ * @param string $url the url to shorten
+ *
+ * @return array the request parameters
+ */
+ protected abstract function getRequestParams($url);
+
+ /**
+ * Shortens a given url.
+ *
+ * @param string $url the url to shorten
+ *
+ * @return string the shortened url or false on a failure
+ */
+ public function shorten($url)
+ {
+ $defaults = array('get' => array(), 'post' => array(), 'callback' => null);
+ $options = array('timeout' => 2);
+ $params = $this->getRequestParams($url) + $defaults;
+
+ // Should some kind of notice be thrown? Maybe just if getRequestParams does not return an array?
+ if (!is_array($params) || empty($params['uri'])) {
+ return $url;
+ }
+
+ if (!empty($params['post'])) {
+ $response = $this->http->post($params['uri'], $params['get'], $params['post'], $options);
+ } else {
+ $response = $this->http->get($params['uri'], $params['get'], $options);
+ }
+
+ if (is_callable($params['callback'])) {
+ return call_user_func($params['callback'], $response);
+ }
+
+ $code = $response->getCode();
+ $content = trim($response->getContent);
+ if ($code < 200 || $code >= 300 || empty($content)) {
+ return false;
+ }
+
+ return $response->getContent();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Trim.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Trim.php
new file mode 100644
index 000000000..af7e8a514
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Trim.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ */
+
+/**
+ * Shortens urls via the tr.im service
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+class Phergie_Plugin_Url_Shorten_Trim extends Phergie_Plugin_Url_Shorten_Abstract
+{
+ /**
+ * Returns an array of request parameters given a url to shorten. The
+ * following keys are valid request parameters:
+ *
+ * @param string $url the url to shorten
+ *
+ * @return array the request parameters
+ */
+ protected function getRequestParams($url)
+ {
+ return array(
+ 'uri' => 'http://api.tr.im/v1/trim_simple?url=' . rawurlencode($url),
+ 'callback' => array($this, 'onComplete')
+ );
+ }
+
+ /**
+ * Callback for when the URL has been shortened. Checks for error messages.
+ *
+ * @param Phergie_Plugin_Http_Response $response the response object
+ *
+ * @return string|bool the shortened url or false on failure
+ */
+ protected function onComplete($response)
+ {
+ if (strpos($response->getContent(), 'Error: ') === 0) {
+ return false;
+ }
+
+ return $response->getContent();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/UserInfo.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/UserInfo.php
new file mode 100644
index 000000000..9437073d8
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/UserInfo.php
@@ -0,0 +1,413 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_UserInfo
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_UserInfo
+ */
+
+/**
+ * Provides an API for querying information on users.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_UserInfo
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_UserInfo
+ */
+class Phergie_Plugin_UserInfo extends Phergie_Plugin_Abstract
+{
+ const REGULAR = 1;
+ const VOICE = 2;
+ const HALFOP = 4;
+ const OP = 8;
+ const ADMIN = 16;
+ const OWNER = 32;
+
+ /**
+ * An array containing all the user information for a given channel
+ *
+ * @var array
+ */
+ protected $store = array();
+
+ /**
+ * Tracks mode changes
+ *
+ * @return void
+ */
+ public function onMode()
+ {
+ $args = $this->event->getArguments();
+
+ if (count($args) != 3) {
+ return;
+ }
+
+ list($chan, $modes, $nicks) = $args;
+
+ if (!preg_match('/(?:\+|-)[hovaq+-]+/i', $modes)) {
+ return;
+ }
+
+ $chan = trim(strtolower($chan));
+ $modes = str_split(trim(strtolower($modes)), 1);
+ $nicks = explode(' ', trim(strtolower($nicks)));
+ $operation = array_shift($modes); // + or -
+
+ while ($char = array_shift($modes)) {
+ $nick = array_shift($nicks);
+ $mode = null;
+
+ switch ($char) {
+ case 'q':
+ $mode = self::OWNER;
+ break;
+ case 'a':
+ $mode = self::ADMIN;
+ break;
+ case 'o':
+ $mode = self::OP;
+ break;
+ case 'h':
+ $mode = self::HALFOP;
+ break;
+ case 'v':
+ $mode = self::VOICE;
+ break;
+ }
+
+ if (!empty($mode)) {
+ if ($operation == '+') {
+ $this->store[$chan][$nick] |= $mode;
+ } else if ($operation == '-') {
+ $this->store[$chan][$nick] ^= $mode;
+ }
+ }
+ }
+ }
+
+ /**
+ * Tracks users joining a channel
+ *
+ * @return void
+ */
+ public function onJoin()
+ {
+ $chan = trim(strtolower($this->event->getArgument(0)));
+ $nick = trim(strtolower($this->event->getNick()));
+
+ $this->store[$chan][$nick] = self::REGULAR;
+ }
+
+ /**
+ * Tracks users leaving a channel
+ *
+ * @return void
+ */
+ public function onPart()
+ {
+ $chan = trim(strtolower($this->event->getArgument(0)));
+ $nick = trim(strtolower($this->event->getNick()));
+
+ if (isset($this->store[$chan][$nick])) {
+ unset($this->store[$chan][$nick]);
+ }
+ }
+
+ /**
+ * Tracks users quitting a server
+ *
+ * @return void
+ */
+ public function onQuit()
+ {
+ $nick = trim(strtolower($this->event->getNick()));
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ unset($this->store[$chan][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Tracks users changing nicks
+ *
+ * @return void
+ */
+ public function onNick()
+ {
+ $nick = trim(strtolower($this->event->getNick()));
+ $newNick = trim(strtolower($this->event->getArgument(0)));
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ $this->store[$chan][$newNick] = $store[$nick];
+ unset($this->store[$chan][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Populates the internal user listing for a channel when the bot joins it.
+ *
+ * @return void
+ */
+ public function onResponse()
+ {
+ if ($this->event->getCode() != Phergie_Event_Response::RPL_NAMREPLY) {
+ return;
+ }
+
+ $desc = preg_split('/[@*=]\s*/', $this->event->getDescription(), 2);
+ list($chan, $users) = array_pad(explode(' :', trim($desc[1])), 2, null);
+ $users = explode(' ', trim($users));
+
+ $chan = trim(strtolower($chan));
+
+ foreach ($users as $user) {
+ if (empty($user)) {
+ continue;
+ }
+
+ $user = trim(strtolower($user));
+ $flag = self::REGULAR;
+
+ if ($user[0] == '~') {
+ $flag |= self::OWNER;
+ } else if ($user[0] == '&') {
+ $flag |= self::ADMIN;
+ } else if ($user[0] == '@') {
+ $flag |= self::OP;
+ } else if ($user[0] == '%') {
+ $flag |= self::HALFOP;
+ } else if ($user[0] == '+') {
+ $flag |= self::VOICE;
+ }
+
+ if ($flag != self::REGULAR) {
+ $user = substr($user, 1);
+ }
+
+ $this->store[$chan][$user] = $flag;
+ }
+ }
+
+ /**
+ * Debugging function
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ if ($this->getConfig('debug', false) == false) {
+ return;
+ }
+
+ list($target, $msg) = array_pad($this->event->getArguments(), 2, null);
+
+ if (preg_match('#^ishere (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isIn($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isowner (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isOwner($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isadmin (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isAdmin($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isop (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isOp($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^ishop (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isHalfop($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isvoice (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isVoice($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^channels (\S+)$#', $msg, $m)) {
+ $channels = $this->getChannels($m[1]);
+ $this->doPrivmsg($target, $channels ? join(', ', $channels) : 'unable to find nick');
+ } elseif (preg_match('#^users (\S+)$#', $msg, $m)) {
+ $nicks = $this->getUsers($m[1]);
+ $this->doPrivmsg($target, $nicks ? join(', ', $nicks) : 'unable to find channel');
+ } elseif (preg_match('#^random (\S+)$#', $msg, $m)) {
+ $nick = $this->getrandomuser($m[1]);
+ $this->doPrivmsg($target, $nick ? $nick : 'unable to find channel');
+ }
+ }
+
+ /**
+ * Checks whether or not a given user has a mode
+ *
+ * @param int $mode A numeric mode (identified by the class constants)
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function is($mode, $nick, $chan)
+ {
+ $chan = trim(strtolower($chan));
+ $nick = trim(strtolower($nick));
+
+ if (!isset($this->store[$chan][$nick])) {
+ return false;
+ }
+
+ return ($this->store[$chan][$nick] & $mode) != 0;
+ }
+
+ /**
+ * Checks whether or not a given user has owner (~) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isOwner($nick, $chan)
+ {
+ return $this->is(self::OWNER, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has admin (&) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isAdmin($nick, $chan)
+ {
+ return $this->is(self::ADMIN, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has operator (@) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isOp($nick, $chan)
+ {
+ return $this->is(self::OP, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has halfop (%) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isHalfop($nick, $chan)
+ {
+ return $this->is(self::HALFOP, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has voice (+) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isVoice($nick, $chan)
+ {
+ return $this->is(self::VOICE, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user is in a channel
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isIn($nick, $chan)
+ {
+ return $this->is(self::REGULAR, $nick, $chan);
+ }
+
+ /**
+ * Returns the entire user list for a channel or false if the bot is not
+ * in the channel.
+ *
+ * @param string $chan The channel name
+ *
+ * @return array|bool
+ */
+ public function getUsers($chan)
+ {
+ $chan = trim(strtolower($chan));
+ if (isset($this->store[$chan])) {
+ return array_keys($this->store[$chan]);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the nick of a random user present in a given channel or false
+ * if the bot is not present in the channel.
+ *
+ * @param string $chan The channel name
+ *
+ * @return array|bool
+ */
+ public function getRandomUser($chan)
+ {
+ $chan = trim(strtolower($chan));
+
+ if (isset($this->store[$chan])) {
+ $ignore = array('chanserv', 'q', 'l', 's');
+
+ do {
+ $nick = array_rand($this->store[$chan], 1);
+ } while (in_array($nick, $ignore));
+
+ return $nick;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of channels in which a given user is present.
+ *
+ * @param string $nick Nick of the user (optional, defaults to the bot's
+ * nick)
+ *
+ * @return array|bool
+ */
+ public function getChannels($nick = null)
+ {
+ if (empty($nick)) {
+ $nick = $this->connection->getNick();
+ }
+
+ $nick = trim(strtolower($nick));
+ $channels = array();
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ $channels[] = $chan;
+ }
+ }
+
+ return $channels;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Weather.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Weather.php
new file mode 100644
index 000000000..7d4f85a19
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Weather.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Weather
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Weather
+ */
+
+/**
+ * Detects and responds to requests for current weather conditions in a
+ * particular location using data from a web service. Requires registering
+ * with weather.com to obtain authentication credentials, which must be
+ * stored in the configuration settings weather.partner_id and
+ * weather.license_key for the plugin to function.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Weather
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Weather
+ * @link http://www.weather.com/services/xmloap.html
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ * @uses Phergie_Plugin_Temperature pear.phergie.org
+ * @uses extension SimpleXML
+ */
+class Phergie_Plugin_Weather extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Http');
+ $plugins->getPlugin('Temperature');
+
+ if (empty($this->config['weather.partner_id'])
+ || empty($this->config['weather.license_key'])) {
+ $this->fail('weather.partner_id and weather.license_key must be specified');
+ }
+ }
+
+ /**
+ * Returns a weather report for a specified location.
+ *
+ * @param string $location Zip code or city/state/country specification
+ *
+ * @return void
+ */
+ public function onCommandWeather($location)
+ {
+ $response = $this->plugins->http->get(
+ 'http://xoap.weather.com/search/search',
+ array('where' => $location)
+ );
+
+ if ($response->isError()) {
+ $this->doNotice(
+ $this->event->getNick(),
+ 'ERROR: ' . $response->getCode() . ' ' . $response->getMessage()
+ );
+ return;
+ }
+
+ $nick = $this->event->getNick();
+
+ $xml = $response->getContent();
+ if (count($xml->loc) == 0) {
+ $this->doNotice($nick, 'No results for that location.');
+ return;
+ }
+
+ $where = (string) $xml->loc[0]['id'];
+ $response = $this->plugins->http->get(
+ 'http://xoap.weather.com/weather/local/' . $where,
+ array(
+ 'cc' => '*',
+ 'link' => 'xoap',
+ 'prod' => 'xoap',
+ 'par' => $this->config['weather.partner_id'],
+ 'key' => $this->config['weather.license_key'],
+ )
+ );
+
+ if ($response->isError()) {
+ $this->doNotice(
+ $this->event->getNick(),
+ 'ERROR: ' . $response->getCode() . ' ' . $response->getMessage()
+ );
+ return;
+ }
+
+ $temperature = $this->plugins->getPlugin('Temperature');
+ $xml = $response->getContent();
+ $weather = 'Weather for ' . (string) $xml->loc->dnam . ' - ';
+ switch ($xml->head->ut) {
+ case 'F':
+ $tempF = $xml->cc->tmp;
+ $tempC = $temperature->convertFahrenheitToCelsius($tempF);
+ break;
+ case 'C':
+ $tempC = $xml->cc->tmp;
+ $tempF = $temperature->convertCelsiusToFahrenheit($tempC);
+ break;
+ default:
+ $this->doNotice(
+ $this->event->getNick(),
+ 'ERROR: No scale information given.');
+ break;
+ }
+ $r = $xml->cc->hmid;
+ $hiF = $temperature->getHeatIndex($tempF, $r);
+ $hiC = $temperature->convertFahrenheitToCelsius($hiF);
+ $weather .= 'Temperature: ' . $tempF . 'F/' . $tempC . 'C';
+ $weather .= ', Humidity: ' . (string) $xml->cc->hmid . '%';
+ if ($hiF > $tempF || $hiC > $tempC) {
+ $weather .= ', Heat Index: ' . $hiF . 'F/' . $hiC . 'C';
+ }
+ $weather .=
+ ', Conditions: ' . (string) $xml->cc->t .
+ ', Updated: ' . (string) $xml->cc->lsup .
+ ' [ http://weather.com/weather/today/' .
+ str_replace(
+ array('(', ')', ',', ' '),
+ array('', '', '', '+'),
+ (string) $xml->loc->dnam
+ ) .
+ ' ]';
+
+ $this->doPrivmsg($this->event->getSource(), $nick . ': ' . $weather);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine.php
new file mode 100644
index 000000000..9aa0845a6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Wine
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Wine
+ */
+
+/**
+ * Processes requests to serve users wine.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Wine
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Wine
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Serve pear.phergie.org
+ */
+class Phergie_Plugin_Wine extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->plugins;
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Serve');
+ }
+
+ /**
+ * Processes requests to serve a user a wine.
+ *
+ * @param string $request Request including the target and an optional
+ * suggestion of what wine to serve
+ *
+ * @return void
+ */
+ public function onCommandWine($request)
+ {
+ $format = $this->getConfig(
+ 'wine.format',
+ 'serves %target% a glass of %item%.'
+ );
+
+ $this->plugins->getPlugin('Serve')->serve(
+ dirname(__FILE__) . '/Wine/wine.db',
+ 'wine',
+ $format,
+ $request
+ );
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine/db.php
new file mode 100644
index 000000000..ce01c2d98
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Wine/db.php
@@ -0,0 +1,53 @@
+<?php
+
+// Create database schema
+echo 'Creating database', PHP_EOL;
+$file = __DIR__ . '/wine.db';
+if (file_exists($file)) {
+ unlink($file);
+}
+$db = new PDO('sqlite:' . $file);
+$db->exec('CREATE TABLE wine (name VARCHAR(255), link VARCHAR(255))');
+$db->exec('CREATE UNIQUE INDEX wine_name ON wine (name)');
+$insert = $db->prepare('INSERT INTO wine (name, link) VALUES (:name, :link)');
+
+// Get and decompress lcboapi.com data set
+$outer = __DIR__ . '/current.zip';
+if (!file_exists($outer)) {
+ echo 'Downloading lcboapi.com data set', PHP_EOL;
+ copy('http://lcboapi.com/download/current.zip', $outer);
+}
+
+echo 'Decompressing lcboapi.com data set', PHP_EOL;
+$zip = new ZipArchive;
+$zip->open($outer);
+$stat = $zip->statIndex(0);
+$inner = __DIR__ . '/' . $stat['name'];
+$zip->extractTo(__DIR__);
+$zip->close();
+$zip = new ZipArchive;
+$zip->open($inner);
+$stat = $zip->statIndex(0);
+$file = __DIR__ . '/' . $stat['name'];
+$zip->extractTo(__DIR__);
+$zip->close();
+
+// Aggregate data set into the database
+$lcbo = new PDO('sqlite:' . $file);
+$result = $lcbo->query('SELECT product_no, name FROM products WHERE primary_category = "Wine"');
+$wines = $result->fetchAll();
+echo 'Processing lcboapi.com data - ', number_format(count($wines), 0), ' records', PHP_EOL;
+$db->beginTransaction();
+foreach ($wines as $wine) {
+ $name = $wine['name'];
+ $link = 'http://lcboapi.com/products/' . $wine['product_no'];
+ $insert->execute(array($name, $link));
+}
+$db->commit();
+
+// Clean up
+echo 'Cleaning up', PHP_EOL;
+unset($lcbo);
+unlink($outer);
+unlink($inner);
+unlink($file);
diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php
new file mode 100644
index 000000000..e06ea5640
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Youtube
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Youtube
+ */
+
+/**
+ * Provides commands used to access several services offered by Google
+ * including search, translation, weather, maps, and currency and general
+ * value unit conversion.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Youtube
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Youtube
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+class Phergie_Plugin_Youtube extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $plugins->getPlugin('Http');
+ if ($url = $plugins->getPlugin('Url')) {
+ $url->registerRenderer($this);
+ }
+ }
+
+ /**
+ * Queries the YouTube video search web service, processes the first
+ * result, and sends a message back to the current event source.
+ *
+ * @param string $query Search term
+ *
+ * @return object YouTube result object
+ */
+ protected function queryYoutube($query)
+ {
+ $url = 'http://gdata.youtube.com/feeds/api/videos';
+ $params = array(
+ 'max-results' => '1',
+ 'alt' => 'json',
+ 'q' => $query
+ );
+ $http = $this->plugins->getPlugin('Http');
+ $response = $http->get($url, $params);
+ $json = $response->getContent();
+
+ $entries = $json->feed->entry;
+ if (!$entries) {
+ $this->doNotice($this->event->getNick(), 'Query returned no results');
+ return;
+ }
+ $entry = reset($entries);
+
+ $nick = $this->event->getNick();
+ $link = $entry->link[0]->href;
+ $title = $entry->title->{'$t'};
+ $author = $entry->author[0]->name->{'$t'};
+ $seconds = $entry->{'media$group'}->{'yt$duration'}->seconds;
+ $published = $entry->published->{'$t'};
+ $views = $entry->{'yt$statistics'}->viewCount;
+ $rating = $entry->{'gd$rating'}->average;
+
+ $minutes = floor($seconds / 60);
+ $seconds = str_pad($seconds % 60, 2, '0', STR_PAD_LEFT);
+ $parsed_link = parse_url($link);
+ parse_str($parsed_link['query'], $parsed_query);
+ $link = 'http://youtu.be/' . $parsed_query['v'];
+ $published = date('n/j/y g:i A', strtotime($published));
+ $views = number_format($views, 0);
+ $rating = round($rating, 2);
+
+ $format = $this->getConfig('youtube.format');
+ if (!$format) {
+ $format = '%nick%:'
+ . ' [ %link% ]'
+ . ' "%title%" by %author%,'
+ . ' Length %minutes%:%seconds%,'
+ . ' Published %published%,'
+ . ' Views %views%,'
+ . ' Rating %rating%';
+ }
+
+ $replacements = array(
+ 'nick' => $nick,
+ 'link' => $link,
+ 'title' => $title,
+ 'author' => $author,
+ 'minutes' => $minutes,
+ 'seconds' => $seconds,
+ 'published' => $published,
+ 'views' => $views,
+ 'rating' => $rating
+ );
+
+ $msg = $format;
+ foreach ($replacements as $from => $to) {
+ $msg = str_replace('%' . $from . '%', $to, $msg);
+ }
+ $this->doPrivmsg($this->event->getSource(), $msg);
+ }
+
+ /**
+ * Returns the first result of a YouTube search.
+ *
+ * @param string $query Search query
+ *
+ * @return void
+ */
+ public function onCommandYoutube($query)
+ {
+ $this->queryYoutube($query);
+ }
+
+ /**
+ * Renders YouTube URLs.
+ *
+ * @param array $parsed parse_url() output for the URL to render
+ *
+ * @return boolean TRUE if the URL was rendered successfully, FALSE
+ * otherwise
+ */
+ public function renderUrl(array $parsed)
+ {
+ switch ($parsed['host']) {
+ case 'youtu.be':
+ $v = ltrim($parsed['path'], '/');
+ break;
+ case 'youtube.com':
+ case 'www.youtube.com':
+ parse_str($parsed['query'], $parsed_query);
+ if (!empty($parsed_query['v'])) {
+ $v = $parsed_query['v'];
+ break;
+ }
+ default:
+ return false;
+ }
+
+ $this->queryYoutube($v);
+
+ return true;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php
new file mode 100755
index 000000000..0ec3569fe
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for obtaining and processing incoming events.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+abstract class Phergie_Process_Abstract
+{
+ /**
+ * Current driver instance
+ *
+ * @var Phergie_Driver_Abstract
+ */
+ protected $driver;
+
+ /**
+ * Current connection handler instance
+ *
+ * @var Phergie_Connection_Handler
+ */
+ protected $connections;
+
+ /**
+ * Current plugin handler instance
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Current event handler instance
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Current end-user interface instance
+ *
+ * @var Phergie_Ui_Abstract
+ */
+ protected $ui;
+
+ /**
+ * List of arguments for use within the instance
+ *
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * Gets the required class refences from Phergie_Bot.
+ *
+ * @param Phergie_Bot $bot Current bot instance in use
+ * @param array $options Optional processor arguments
+ *
+ * @return void
+ */
+ public function __construct(Phergie_Bot $bot, array $options = array())
+ {
+ $this->driver = $bot->getDriver();
+ $this->plugins = $bot->getPluginHandler();
+ $this->connections = $bot->getConnectionHandler();
+ $this->events = $bot->getEventHandler();
+ $this->ui = $bot->getUi();
+ $this->options = $options;
+ }
+
+ /**
+ * Sends resulting outgoing events from ealier processing in handleEvents.
+ *
+ * @param Phergie_Connection $connection Active connection
+ *
+ * @return void
+ */
+ protected function processEvents(Phergie_Connection $connection)
+ {
+ $this->plugins->preDispatch();
+ if (count($this->events)) {
+ foreach ($this->events as $event) {
+ $this->ui->onCommand($event, $connection);
+
+ $method = 'do' . ucfirst(strtolower($event->getType()));
+ call_user_func_array(
+ array($this->driver, $method),
+ $event->getArguments()
+ );
+ }
+ }
+ $this->plugins->postDispatch();
+
+ if ($this->events->hasEventOfType(Phergie_Event_Request::TYPE_QUIT)) {
+ $this->ui->onQuit($connection);
+ $this->connections->removeConnection($connection);
+ }
+
+ $this->events->clearEvents();
+ }
+
+ /**
+ * Obtains and processes incoming events.
+ *
+ * @return void
+ */
+ public abstract function handleEvents();
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Async.php b/plugins/Irc/extlib/phergie/Phergie/Process/Async.php
new file mode 100644
index 000000000..78d595914
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Process/Async.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Connection data processor which polls to handle input in an
+ * asynchronous manner. Will also cause the application tick at
+ * the user-defined wait time.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Process_Async extends Phergie_Process_Abstract
+{
+ /**
+ * Length of time to poll for stream activity (seconds)
+ *
+ * @var int
+ */
+ protected $sec = 0;
+
+ /**
+ * Length of time to poll for stream activity (microseconds)
+ *
+ * @var int
+ */
+ protected $usec = 200000;
+
+ /**
+ * Length of time to wait between ticks.
+ *
+ * @var int
+ */
+ protected $wait = 0;
+
+ /**
+ * Records when the application last performed a tick
+ *
+ * @var int
+ */
+ protected $lastTick = 0;
+
+ /**
+ * Overrides the parent class to set the poll time.
+ *
+ * @param Phergie_Bot $bot Main bot class
+ * @param array $options Processor arguments
+ *
+ * @return void
+ */
+ public function __construct(Phergie_Bot $bot, array $options)
+ {
+ if (!$bot->getDriver() instanceof Phergie_Driver_Streams) {
+ throw new Phergie_Process_Exception(
+ 'The Async event processor requires the Streams driver'
+ );
+ }
+
+ foreach (array('sec', 'usec') as $var) {
+ if (isset($options[$var])) {
+ if (!is_int($options[$var])) {
+ throw new Phergie_Process_Exception(
+ 'Processor option "' . $var . '" must be an integer'
+ );
+ }
+ $this->$var = $options[$var];
+ }
+ }
+
+ if (!isset($this->sec) && !isset($this->usec)) {
+ throw new Phergie_Process_Exception(
+ 'One of the processor options "sec" or "usec" must be specified'
+ );
+ }
+
+ parent::__construct($bot, $options);
+ }
+
+ /**
+ * Waits for stream activity and performs event processing on
+ * connections with data to read.
+ *
+ * @return void
+ */
+ protected function handleEventsAsync()
+ {
+ $hostmasks = $this->driver->getActiveReadSockets($this->sec, $this->usec);
+ if (!$hostmasks) {
+ return;
+ }
+ $connections = $this->connections->getConnections($hostmasks);
+ foreach ($connections as $connection) {
+ $this->driver->setConnection($connection);
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+
+ if ($event = $this->driver->getEvent()) {
+ $this->ui->onEvent($event, $connection);
+ $this->plugins->setEvent($event);
+ $this->plugins->preEvent();
+ $this->plugins->{'on' . ucfirst($event->getType())}();
+ }
+
+ $this->processEvents($connection);
+ }
+ }
+
+ /**
+ * Perform application tick event on all plugins and connections.
+ *
+ * @return void
+ */
+ protected function doTick()
+ {
+ foreach ($this->connections as $connection) {
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+ $this->processEvents($connection);
+ }
+ }
+
+ /**
+ * Obtains and processes incoming events, then sends resulting outgoing
+ * events.
+ *
+ * @return void
+ */
+ public function handleEvents()
+ {
+ $time = time();
+ if ($this->lastTick == 0 || ($this->lastTick + $this->wait <= $time)) {
+ $this->doTick();
+ $this->lastTick = $time;
+ }
+ $this->handleEventsAsync();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Process/Exception.php
new file mode 100755
index 000000000..f964443c6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Process/Exception.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Exception related to event processor operations.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Process_Exception extends Phergie_Exception
+{
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php b/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php
new file mode 100644
index 000000000..24adbaf99
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Connection data processor which reads all connections looking
+ * for a response.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Process_Standard extends Phergie_Process_Abstract
+{
+ /**
+ * Obtains and processes incoming events, then sends resulting outgoing
+ * events.
+ *
+ * @return void
+ */
+ public function handleEvents()
+ {
+ foreach ($this->connections as $connection) {
+ $this->driver->setConnection($connection);
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+
+ if ($event = $this->driver->getEvent()) {
+ $this->ui->onEvent($event, $connection);
+ $this->plugins->setEvent($event);
+ $this->plugins->preEvent();
+ $this->plugins->{'on' . ucfirst($event->getType())}();
+ }
+
+ $this->processEvents($connection);
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/StatusnetBot.php b/plugins/Irc/extlib/phergie/Phergie/StatusnetBot.php
new file mode 100644
index 000000000..9c1b3a6f1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/StatusnetBot.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Extends the bot class (Phergie_Bot) to allow connection and access to
+ * sockets and to allow StatusNet to 'drive' the bot
+ *
+ * @category Phergie
+ * @package Phergie_StatusnetBot
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+class Phergie_StatusnetBot extends Phergie_Bot {
+ /**
+ * Set up bot and connect to servers
+ *
+ * @return void
+ */
+ public function connect() {
+ $ui = $this->getUi();
+ $ui->setEnabled($this->getConfig('ui.enabled'));
+
+ $this->loadPlugins();
+ $this->loadConnections();
+ }
+
+ /**
+ * Transmit raw command to server using driver
+ *
+ * Handles construction of command strings and their transmission to the
+ * server.
+ *
+ * @param string $command Command to send
+ * @param string|array $args Optional string or array of sequential
+ * arguments
+ *
+ * @return string Command string that was sent
+ * @throws Phergie_Driver_Exception
+ */
+ public function send($command, $args = '') {
+ return $this->getDriver()->send($command, $args);
+ }
+
+ /**
+ * Handle incoming data on the socket using the handleEvents
+ * method of the Processor
+ *
+ * @return void
+ */
+ public function handleEvents() {
+ $this->getProcessor()->handleEvents();
+ }
+
+ /**
+ * Close the current connection and reconnect to the server
+ *
+ * @return void
+ */
+ public function reconnect() {
+ $driver = $this->getDriver();
+ $sockets = $driver->getSockets();
+
+ // Close any existing connections
+ try {
+ $driver->forceQuit();
+ } catch (Phergie_Driver_Exception $e){}
+ try {
+ $driver->doConnect();
+ } catch (Phergie_Driver_Exception $e){
+ $driver->forceQuit();
+ throw $e;
+ }
+ }
+
+ /**
+ * Get the sockets used by the bot
+ *
+ * @return array Array of socket resources
+ */
+ public function getSockets() {
+ return $this->getDriver()->getSockets();
+ }
+} \ No newline at end of file
diff --git a/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/INSTALL b/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/INSTALL
new file mode 100755
index 000000000..dfc985756
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/INSTALL
@@ -0,0 +1,24 @@
+-----------------------------------------------------------------------------
+-- About LogViewer
+-----------------------------------------------------------------------------
+
+The purpose of this tool is to make an HTML display of the Logs made by the
+Logging plugin for Phergie stored in SQLite database.
+
+-----------------------------------------------------------------------------
+-- Installation
+-----------------------------------------------------------------------------
+
+To install this, simply copy the contents of this directory into whatever
+directory you want this to run under.
+
+-----------------------------------------------------------------------------
+-- Configuration
+-----------------------------------------------------------------------------
+
+The only configuration needed at this point in time is to edit the $database
+variable in the top of the index.php to point to the appropriate Phergie
+log file.
+
+Stuff may get more complicated in the future ;) But this is meant to be
+simple for now. \ No newline at end of file
diff --git a/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/index.php b/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/index.php
new file mode 100755
index 000000000..fc27fa294
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/index.php
@@ -0,0 +1,368 @@
+<?php
+// Phergie Log Viewer ... Currently designed as a single PHP file in order to make it easy to
+// 'install' this. Just drop the index.php (or whatever name you wish to rename it to) wherever
+// you wish, and it will simply work. Sure, it would be nice to structure some of this stuff into
+// various include files/etc. But right now this is simple enough of a quick log viewer, that it's
+// just one file.
+
+
+/********** SETUP **********/
+
+// (Change any of these if/as needed for your setup)
+ini_set('default_charset', 'UTF-8');
+date_default_timezone_set('UTC');
+$log = "/PATH/AND/FILENAME/TO/YOUR/LOGFILE/PLEASE.db";
+
+
+/********** PREPARATION **********/
+
+$db = new PDO('sqlite:' . $log);
+if (!is_object($db)) {
+ // Failure, can't access Phergie Log. Bail with an error message, not pretty, but works:
+ echo "ERROR: Cannot access Phergie Log File, please check the configuration & access privileges";
+ exit();
+}
+
+
+/********** DETECTION **********/
+
+// Determine the mode of the application and call the appropriate handler function
+$mode = empty($_GET['m']) ? '' : $_GET['m'];
+switch ($mode) {
+ case 'channel':
+ show_days($db);
+ break;
+ case 'day':
+ show_log($db);
+ break;
+ default:
+ show_channels($db);
+}
+
+// Exit not really needed here, but reminds us that everything below is support functions:
+exit();
+
+
+/********** MODES **********/
+
+/**
+ * show_channels
+ *
+ * Provide a list of all channel's that we are logging information for:
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_channels(PDO $db) {
+ // Begin the HTML page:
+ template_header('Channels');
+ echo "\nChannels:\n<ul>\n";
+
+ // Loop through the database reading in each channel, and echoing out a <li> for it.
+ // only grab actual channels that start with # ... also pre-lowercase everything.
+ // this allows us to 'deal' with variable caps in how the channels were logged.
+ $channels = $db->query("
+ select distinct lower(chan) as c
+ from logs
+ where chan like '#%'
+ ");
+ foreach ($channels as $row) {
+ $html = utf8specialchars($row['c']);
+ $url = urlencode($row['c']);
+ echo "<li><a href=\"?m=channel&w={$url}\">{$html}</a></li>\n";
+ }
+
+ // Finish off the page:
+ echo "\n</ul>\n";
+ template_footer();
+}
+
+/**
+ * show_days
+ *
+ * Create a calendar view of all days available for this particular channel
+ *
+ * NOTE: May get unwieldy if large log files. Perhaps consider in the future
+ * making a paginated version of this? by year? Or a separate 'which year' page
+ * before this? Not to worry about now.
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_days(PDO $db) {
+ $channel = $_GET['w'];
+ $url = urlencode($channel);
+
+ // Begin the HTML page:
+ template_header('Daily Logs for Channel: ' . utf8specialchars($channel));
+ echo "\n<ul>\n";
+
+ // Query the database to discover all days that are available for this channel:
+ $data = array();
+ $prepared = $db->prepare("
+ select distinct date(tstamp) as day
+ from logs
+ where lower(chan) = :chan
+ ");
+ $prepared->execute(array(':chan' => $channel));
+ foreach ($prepared as $row) {
+ list($y, $m, $d) = explode('-', $row['day']);
+ $data[$y][$m][$d] = "{$y}-{$m}-{$d}";
+ }
+
+ // For now, just loop over them all and provide a list:
+ ksort($data);
+ foreach ($data as $year => $months) {
+ ksort($months);
+ foreach ($months as $month => $days) {
+ // Figure out a few facts about this month:
+ $stamp = mktime(0, 0, 0, $month, 1, $year);
+ $first_weekday = idate('w', $stamp);
+ $days_in_month = idate('t', $stamp);
+ $name = date('F', $stamp);
+
+ // We have a month ... start a new table:
+ echo <<<EOTABLE
+<div class="month">
+ <table>
+ <caption>{$name} {$year}</caption>
+ <tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th></tr>
+EOTABLE;
+ // Now we need to start looping through the days in this month:
+ echo '<tr>';
+ $rowmod = 0;
+ // Loop through all day entries, no matter how many blanks we need:
+ for ($d = (-$first_weekday + 1); $d < $days_in_month + 1; $d++) {
+ if (!($rowmod++ % 7)) {
+ // Stop/start a new row:
+ echo '</tr><tr>';
+ }
+ echo '<td>';
+ // If this day is pre or post actual month days, make it blank:
+ if (($d < 1) || ($d > $days_in_month)) {
+ echo '&nbsp;';
+ } elseif (isset($days[$d])) {
+ // Make a link to the day's log:
+ echo "<a href=\"?m=day&w={$url}&d={$days[$d]}\">{$d}</a>";
+ } else {
+ // Just a dead number:
+ echo $d;
+ }
+ echo '</td>';
+ }
+ // Finish off any blanks needed for a complete table row:
+ while ($rowmod++ % 7) {
+ echo '<td>&nbsp;</td>';
+ }
+ echo "</tr></table></div>\n";
+ }
+ }
+
+ // Finish off the page:
+ echo "\n</ul>\n";
+ template_footer();
+}
+
+/**
+ * show_log
+ *
+ * Actually show the log for this specific day
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_log(PDO $db) {
+ $channel = $_GET['w'];
+ $day = $_GET['d'];
+ $parts = explode('-', $day);
+ $formatted_date = "{$parts[0]}-{$parts[1]}-{$parts[2]}";
+
+ // Begin the HTML page:
+ template_header('Date: ' . utf8specialchars($formatted_date) .
+ ' - Channel: ' . utf8specialchars($channel));
+
+ // Query the database to get all log lines for this date:
+ $prepared = $db->prepare("
+ select time(tstamp) as t, type, nick, message
+ from logs
+ where lower(chan) = :chan and date(tstamp) = :day
+ order by tstamp asc
+ ");
+ $prepared->execute(array(
+ ':chan' => $channel,
+ ':day' => $day,
+ ));
+
+ // Loop through each line,
+ foreach ($prepared as $row) {
+ // Prepare some basic details for output:
+ $color = nick_color($row['nick']);
+ $time = utf8specialchars($row['t']);
+ $msg = utf8specialchars($row['message']);
+ $nick = utf8specialchars($row['nick']);
+ $type = false;
+
+ // Now change the format of the line based upon the type:
+ switch ($row['type']) {
+ case 4: // PRIVMSG (A Regular Message)
+ echo "[$time] <span style=\"color:#{$color};\">&lt;{$nick}&gt;</span> {$msg}<br />\n";
+ break;
+ case 5: // ACTION (emote)
+ echo "[$time] <span style=\"color:#{$color};\">*{$nick} {$msg}</span><br />\n";
+ break;
+ case 1: // JOIN
+ echo "[$time] -> {$nick} joined the room.<br />\n";
+ break;
+ case 2: // PART (leaves channel)
+ echo "[$time] -> {$nick} left the room: {$msg}<br />\n";
+ break;
+ case 3: // QUIT (quits the server)
+ echo "[$time] -> {$nick} left the server: {$msg}<br />\n";
+ break;
+ case 6: // NICK (changes their nickname)
+ echo "[$time] -> {$nick} is now known as: {$msg}<br />\n";
+ break;
+ case 7: // KICK (booted)
+ echo "[$time] -> {$nick} boots {$msg} from the room.<br />\n";
+ break;
+ case 8: // MODE (changed their mode)
+ $type = 'MODE';
+ case 9: // TOPIC (changed the topic)
+ $type = $type ? $type : 'TOPIC';
+ echo "[$time] -> {$nick}: :{$type}: {$msg}<br />\n";
+ }
+ }
+
+ // Finish up the page:
+ template_footer();
+}
+
+/**
+ * nick_color
+ *
+ * Uses a silly little algorithm to pick a consistent but unique(ish) color for
+ * any given username. NOTE: Augment this in the future to make it not generate
+ * 'close to white' ones, also maybe to ensure uniqueness? (Not allow two to have
+ * colors that are close to each other?)
+ *
+ * @return string A CSS valid hex color string
+ * @author Eli White <eli@eliw.com>
+ **/
+function nick_color($user) {
+ static $colors = array();
+
+ if (!isset($colors[$user])) {
+ $colors[$user] = substr(md5($user), 0, 6);
+ }
+
+ return $colors[$user];
+}
+
+/**
+ * utf8specialchars
+ *
+ * Just a quick wrapper around htmlspecialchars
+ *
+ * @param string The UTF8 string to escape
+ * @return string An escaped and ready for HTML use string
+ * @author Eli White <eli@eliw.com>
+ **/
+function utf8specialchars($string) {
+ return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
+}
+
+
+/********** TEMPLATES **********/
+
+/**
+ * template_header
+ *
+ * Echo out the header for each HTML page
+ *
+ * @param $title string The title to be used for this page.
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_header($title) {
+ $css = template_css();
+ echo <<<EOHTML
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title>Phergie LogViewer - {$title}</title>
+ <style type="text/css" media="all">{$css}</style>
+ </head>
+ <body>
+ <h2>Phergie LogViewer - {$title}</h2>
+EOHTML;
+}
+
+/**
+ * template_footer
+ *
+ * Echo out the bottom of each HTML page
+ *
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_footer() {
+ echo <<<EOHTML
+ </body>
+</html>
+EOHTML;
+}
+
+/**
+ * template_css
+ *
+ * Generate the CSS used by these HTML pages & return it.
+ *
+ * @return string The CSS in question:
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_css() {
+ return <<<EOCSS
+ div.month {
+ float: left;
+ height: 15em;
+ }
+
+ div.month table {
+ border-collapse: collapse;
+ border: 2px solid black;
+ margin-right: 2em;
+ }
+
+ div.month td, div.month th {
+ text-align: center;
+ vertical-align: bottom;
+ border: 1px solid gray;
+ width: 2em;
+ height: 1.7em;
+ padding: 1px;
+ margin: 0px;
+ }
+
+ div.month th {
+ text-decoration: bold;
+ border: 2px solid black;
+ }
+
+ div.month a {
+ text-decoration: none;
+ }
+
+ a:visited, a:link {
+ color: blue;
+ }
+
+ a:active, a:hover {
+ color: red;
+ }
+EOCSS;
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Tools/README b/plugins/Irc/extlib/phergie/Phergie/Tools/README
new file mode 100755
index 000000000..ed8fe2946
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Tools/README
@@ -0,0 +1,6 @@
+This directory holds any assorted tools that work with Phergie but that are
+not part of Phergie itself. Such as tools to examine data that Phergie is
+capturing/logging.
+
+Each should live inside of it's own directory, and be completely self-contained
+with instructions inside on how to use it.
diff --git a/plugins/Irc/extlib/phergie/Phergie/Ui/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Ui/Abstract.php
new file mode 100644
index 000000000..e2e2ce2f1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Ui/Abstract.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * Base class for end-user interfaces.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+abstract class Phergie_Ui_Abstract
+{
+ /**
+ * Handler for when a server connection is attempted.
+ *
+ * @param string $host Server hostname
+ *
+ * @return void
+ */
+ public function onConnect($host)
+ {
+ }
+
+ /**
+ * Handler for when an attempt is made to load a plugin.
+ *
+ * @param string $plugin Short name of the plugin
+ *
+ * @return void
+ */
+ public function onPluginLoad($plugin)
+ {
+ }
+
+ /**
+ * Handler for when a plugin fails to load.
+ *
+ * @param string $plugin Short name of the plugin
+ * @param string $message Message describing the reason for the failure
+ *
+ * @return void
+ */
+ public function onPluginFailure($plugin, $message)
+ {
+ }
+
+ /**
+ * Handler for when the bot receives an IRC event.
+ *
+ * @param Phergie_Event_Abstract $event Received event
+ * @param Phergie_Connection $connection Connection on which the event
+ * was received
+ *
+ * @return void
+ */
+ public function onEvent(Phergie_Event_Abstract $event,
+ Phergie_Connection $connection
+ ) {
+ }
+
+ /**
+ * Handler for when the bot sends a command to a server.
+ *
+ * @param Phergie_Event_Command $event Event representing the command
+ * being sent
+ * @param Phergie_Connection $connection Connection on which the command
+ * is being sent
+ *
+ * @return void
+ */
+ public function onCommand(Phergie_Event_Command $event,
+ Phergie_Connection $connection
+ ) {
+ }
+
+ /**
+ * Handler for when the bot terminates a connection to a server.
+ *
+ * @param Phergie_Connection $connection Terminated connection
+ *
+ * @return void
+ */
+ public function onQuit(Phergie_Connection $connection)
+ {
+ }
+
+ /**
+ * Handler for when the bot shuts down after terminating all server
+ * connections.
+ *
+ * @return void
+ */
+ public function onShutdown()
+ {
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Phergie/Ui/Console.php b/plugins/Irc/extlib/phergie/Phergie/Ui/Console.php
new file mode 100644
index 000000000..a0a528b3f
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Phergie/Ui/Console.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * End-user interface that produces console output when running the bot from
+ * a shell.
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+class Phergie_Ui_Console extends Phergie_Ui_Abstract
+{
+ /**
+ * Flag that toggles all console output
+ *
+ * @var bool
+ */
+ protected $enabled;
+
+ /**
+ * Format for timestamps included in console output
+ *
+ * @var string
+ * @link http://php.net/date
+ */
+ protected $format;
+
+ /**
+ * Constructor to initialize object properties.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->enabled = true;
+ $this->format = 'H:i:s';
+ }
+
+ /**
+ * Outputs a timestamped line to the console if console output is enabled.
+ *
+ * @param string $line Line to output
+ *
+ * @return void
+ */
+ protected function console($line)
+ {
+ if ($this->enabled) {
+ echo date($this->format), ' ', $line, PHP_EOL;
+ }
+ }
+
+ /**
+ * Returns whether console output is enabled.
+ *
+ * @return bool TRUE if console output is enabled, FALSE otherwise
+ */
+ public function isEnabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Sets whether console output is enabled.
+ *
+ * @param bool $enabled TRUE to enable console output, FALSE otherwise,
+ * defaults to TRUE
+ *
+ * @return Phergie_Ui_Console Provides a fluent interface
+ */
+ public function setEnabled($enabled = true)
+ {
+ $this->enabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns the format used for timestamps in console output.
+ *
+ * @return string
+ * @link http://php.net/date
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Sets the format used for timestamps in console output, overwriting
+ * any previous format used.
+ *
+ * @param string $format Timestamp format
+ *
+ * @return Phergie_Ui_Console Provides a fluent interface
+ * @link http://php.net/date
+ */
+ public function setFormat($format)
+ {
+ $this->format = (string) $format;
+ return $this;
+ }
+
+ /**
+ * Outputs a prompt when a server connection is attempted.
+ *
+ * @param string $host Server hostname
+ *
+ * @return void
+ */
+ public function onConnect($host)
+ {
+ $this->console('Connecting to ' . $host);
+ }
+
+ /**
+ * Outputs a prompt when a plugin is loaded successfully.
+ *
+ * @param string $plugin Short name of the plugin
+ *
+ * @return void
+ */
+ public function onPluginLoad($plugin)
+ {
+ $this->console('Loaded plugin ' . $plugin);
+ }
+
+ /**
+ * Outputs a prompt when a plugin fails to load.
+ *
+ * @param string $plugin Short name of the plugin
+ * @param string $message Message describing the reason for the failure
+ *
+ * @return void
+ */
+ public function onPluginFailure($plugin, $message)
+ {
+ $this->console('Unable to load plugin ' . $plugin . ' - ' . $message);
+ }
+
+ /**
+ * Outputs a prompt when the bot receives an IRC event.
+ *
+ * @param Phergie_Event_Abstract $event Received event
+ * @param Phergie_Connection $connection Connection on which the
+ * event was received
+ *
+ * @return void
+ */
+ public function onEvent(Phergie_Event_Abstract $event,
+ Phergie_Connection $connection
+ ) {
+ $host = $connection->getHostmask()->getHost();
+ $this->console($host . ' <- ' . $event->getRawData());
+ }
+
+ /**
+ * Outputs a prompt when the bot sends a command to a server.
+ *
+ * @param Phergie_Event_Command $event Event representing the
+ * command being sent
+ * @param Phergie_Connection $connection Connection on which the
+ * command is being sent
+ *
+ * @return void
+ */
+ public function onCommand(Phergie_Event_Command $event,
+ Phergie_Connection $connection
+ ) {
+ $plugin = $event->getPlugin()->getName();
+ $host = $connection->getHostmask()->getHost();
+ $type = strtoupper($event->getType());
+ $args = implode(' ', $event->getArguments());
+ $this->console(
+ $plugin . ' plugin: ' .
+ $host . ' -> ' . $type . ' ' . $args
+ );
+ }
+
+ /**
+ * Outputs a prompt when the bot terminates a connection to a server.
+ *
+ * @param Phergie_Connection $connection Terminated connection
+ *
+ * @return void
+ */
+ public function onQuit(Phergie_Connection $connection)
+ {
+ $host = $connection->getHostmask()->getHost();
+ $this->console('Disconnecting from ' . $host);
+ }
+
+ /**
+ * Outputs a prompt when the bot shuts down after terminating all server
+ * connections.
+ *
+ * @return void
+ */
+ public function onShutdown()
+ {
+ $this->console('Shutting down');
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/PhergiePackageTask.php b/plugins/Irc/extlib/phergie/PhergiePackageTask.php
new file mode 100644
index 000000000..dfc760a16
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/PhergiePackageTask.php
@@ -0,0 +1,110 @@
+<?php
+
+require_once 'phing/tasks/ext/PearPackage2Task.php';
+
+class PhergiePackageTask extends PearPackage2Task
+{
+ protected function setOptions()
+ {
+ $this->pkg->addMaintainer('lead', 'team', 'Phergie Development Team', 'team@phergie.org');
+
+ $path = str_replace('_', '/', $this->package) . '.php';
+ if (file_exists($path)) {
+ $contents = file_get_contents($path);
+ preg_match_all('#/\*\*(.*)\*/#Ums', $contents, $matches, PREG_SET_ORDER);
+ $doc = $matches[1][1];
+
+ $have_summary = false;
+ $have_description = false;
+ foreach ($this->options as $option) {
+ switch ($option->getName()) {
+ case 'summary':
+ $have_summary = true;
+ break;
+ case 'description':
+ $have_descripion = true;
+ break;
+ }
+ }
+
+ if (!$have_summary || !$have_description) {
+ $description = substr($doc, 0, strpos($doc, '@'));
+ $description = trim(preg_replace(array('#^[\h*]*|[\h*]*$#m', '#[\h]+#m'), array('', ' '), $description));
+ $split = preg_split('/\v\v+/', $description);
+ $summary = trim(array_shift($split));
+ if (!$have_summary) {
+ $this->pkg->setSummary(htmlentities($summary, ENT_QUOTES));
+ }
+ if (!$have_description) {
+ $this->pkg->setDescription(htmlentities($description, ENT_QUOTES));
+ }
+ }
+
+ $doc = preg_split('/\v+/', $doc);
+ $doc = preg_grep('/@uses/', $doc);
+ $doc = preg_replace('/\s*\* @uses\s+|\s+$/', '', $doc);
+ foreach ($doc as $line) {
+ if (strpos($line, 'extension') === 0) {
+ $line = explode(' ', $line);
+ $name = $line[1];
+ $optional = 'required';
+ if (isset($line[2])) {
+ $optional = $line[2];
+ }
+ $this->pkg->addExtensionDep(
+ $optional,
+ $name
+ );
+ } else {
+ $line = explode(' ', $line);
+ $name = $line[0];
+ $channel = $line[1];
+ $optional = 'required';
+ if (isset($line[2])) {
+ $optional = $line[2];
+ }
+ $this->pkg->addPackageDepWithChannel(
+ $optional,
+ $name,
+ $channel
+ );
+ }
+ }
+ }
+
+ $newmap = array();
+ foreach ($this->mappings as $key => $map) {
+ switch ($map->getName()) {
+ case 'releases':
+ $releases = $map->getValue();
+ foreach ($releases as $release) {
+ $this->pkg->addRelease();
+ if (isset($release['installconditions'])) {
+ if (isset($release['installconditions']['os'])) {
+ $this->pkg->setOsInstallCondition($release['installconditions']['os']);
+ }
+ }
+ if (isset($release['filelist'])) {
+ if (isset($release['filelist']['install'])) {
+ foreach ($release['filelist']['install'] as $file => $as) {
+ $this->pkg->addInstallAs($file, $as);
+ }
+ }
+ if (isset($release['filelist']['ignore'])) {
+ foreach ($release['filelist']['ignore'] as $file) {
+ $this->pkg->addIgnoreToRelease($file);
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ $newmap[] = $map;
+ }
+ }
+ $this->mappings = $newmap;
+
+ parent::setOptions();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/README b/plugins/Irc/extlib/phergie/README
new file mode 100644
index 000000000..d97ce0524
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/README
@@ -0,0 +1,11 @@
+Phergie is an IRC bot written for PHP 5.2.
+
+Main project web site: http://phergie.org
+
+Instructions for running your own instance of Phergie: http://phergie.org/users/
+
+Architectural overview for plugin developers: http://phergie.org/developers/
+
+Support: http://phergie.org/support/
+
+Bug reports/feature requests: http://github.com/elazar/phergie/issues
diff --git a/plugins/Irc/extlib/phergie/Settings.php.dist b/plugins/Irc/extlib/phergie/Settings.php.dist
new file mode 100755
index 000000000..87b4a95c6
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Settings.php.dist
@@ -0,0 +1,98 @@
+<?php
+
+return array(
+
+ // One array per connection, pretty self-explanatory
+ 'connections' => array(
+ // Ex: All connection info for the Freenode network
+ array(
+ 'host' => 'irc.freenode.net',
+ 'port' => 6667,
+ 'username' => 'Elazar',
+ 'realname' => 'Matthew Turland',
+ 'nick' => 'Phergie2',
+ // 'password' => 'password goes here if needed',
+ // 'transport' => 'ssl', // uncomment to connect using SSL
+ // 'encoding' => 'UTF8', // uncomment if using UTF8
+ )
+ ),
+
+ 'processor' => 'async',
+ 'processor.options' => array('usec' => 200000),
+ // Time zone. See: http://www.php.net/manual/en/timezones.php
+ 'timezone' => 'UTC',
+
+ // Whitelist of plugins to load
+ 'plugins' => array(
+ // To enable a plugin, simply add a string to this array containing
+ // the short name of the plugin as shown below.
+
+ // 'ShortPluginName',
+
+ // Below is an example of enabling the AutoJoin plugin, for which
+ // the corresponding PEAR package is Phergie_Plugin_AutoJoin. This
+ // plugin allows you to set a list of channels in this configuration
+ // file that the bot will automatically join when it connects to a
+ // server. If you'd like to enable this plugin, simply install it,
+ // uncomment the line below, and set a value for the setting
+ // autojoin.channels (examples for which are located further down in
+ // this file).
+
+ // 'AutoJoin',
+
+ // A few other recommended plugins:
+
+ // Servers randomly send PING events to clients to ensure that
+ // they're still connected and will eventually terminate the
+
+ // connection if a PONG response is not received. The Pong plugin
+ // handles sending these responses.
+
+ // 'Pong',
+
+ // It's sometimes difficult to distinguish between a lack of
+ // activity on a server and the client not receiving data even
+ // though a connection remains open. The Ping plugin performs a self
+ // CTCP PING sporadically to ensure that its connection is still
+ // functioning and, if not, terminates the bot.
+
+ // 'Ping',
+
+ // Sometimes it's desirable to have the bot disconnect gracefully
+ // when issued a command to do so via a PRIVMSG event. The Quit
+ // plugin implements this using the Command plugin to intercept the
+ // command.
+
+ // 'Quit',
+ ),
+
+ // If set to true, this allows any plugin dependencies for plugins
+ // listed in the 'plugins' option to be loaded even if they are not
+ // explicitly included in that list
+ 'plugins.autoload' => true,
+
+ // Enables shell output describing bot events via Phergie_Ui_Console
+ 'ui.enabled' => true,
+
+ // Examples of supported values for autojoins.channel:
+ // 'autojoin.channels' => '#channel1,#channel2',
+ // 'autojoin.channels' => array('#channel1', '#channel2'),
+ // 'autojoin.channels' => array(
+ // 'host1' => '#channel1,#channel2',
+ // 'host2' => array('#channel3', '#channel4')
+ // ),
+
+ // Examples of setting values for Ping plugin settings
+
+ // This is the amount of time in seconds that the Ping plugin will wait
+ // to receive an event from the server before it initiates a self-ping
+
+ // 'ping.event' => 300, // 5 minutes
+
+ // This is the amount of time in seconds that the Ping plugin will wait
+ // following a self-ping attempt before it assumes that a response will
+ // never be received and terminates the connection
+
+ // 'ping.ping' => 10, // 10 seconds
+
+);
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/ConnectionTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/ConnectionTest.php
new file mode 100644
index 000000000..ba94cd0ea
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/ConnectionTest.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Connection.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_ConnectionTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Associative array containing an option-to-value mapping
+ *
+ * @var array
+ */
+ private $options = array(
+ 'host' => 'example.com',
+ 'port' => 4080,
+ 'transport' => 'udp',
+ 'encoding' => 'ASCII',
+ 'nick' => 'MyNick',
+ 'username' => 'MyUsername',
+ 'realname' => 'MyRealName',
+ 'password' => 'MyPassword',
+ );
+
+ /**
+ * Data provider for testGetOptionReturnsDefault().
+ *
+ * @return array Enumerated array of enumerated arrays each containing a
+ * set of parameters for a single call to
+ * testGetOptionReturnsDefault()
+ */
+ public function dataProviderTestGetOptionReturnsDefault()
+ {
+ return array(
+ array('transport', 'tcp'),
+ array('encoding', 'ISO-8859-1'),
+ array('port', 6667),
+ array('password', null),
+ );
+ }
+
+ /**
+ * Tests that a default values are used for some options.
+ *
+ * @param string $option Name of the option with a default value
+ * @param mixed $value Default value of the option
+ *
+ * @return void
+ * @dataProvider dataProviderTestGetOptionReturnsDefault
+ */
+ public function testGetOptionReturnsDefault($option, $value)
+ {
+ $connection = new Phergie_Connection;
+ $this->assertEquals($value, $connection->{'get' . ucfirst($option)}());
+ }
+
+ /**
+ * Tests that a default encoding is used if one isn't specified.
+ *
+ * @return void
+ */
+ public function testGetEncodingReturnsDefault()
+ {
+ $connection = new Phergie_Connection;
+ $this->assertEquals('ISO-8859-1', $connection->getEncoding());
+ }
+
+ /**
+ * Tests that options can be set via the constructor.
+ *
+ * @return void
+ */
+ public function testSetOptionsViaConstructor()
+ {
+ $connection = new Phergie_Connection($this->options);
+ foreach ($this->options as $key => $value) {
+ $this->assertEquals($value, $connection->{'get' . ucfirst($key)}());
+ }
+ }
+
+ /**
+ * Data provider for testGetHostmaskMissingDataGeneratesException().
+ *
+ * @return array Enumerated array of enumerated arrays each containing a
+ * set of parameters for a single call to
+ * testGetHostmaskMissingDataGeneratesException()
+ */
+ public function dataProviderTestGetHostmaskMissingDataGeneratesException()
+ {
+ return array(
+ array(null, $this->options['username'], $this->options['host']),
+ array($this->options['nick'], null, $this->options['host']),
+ array($this->options['nick'], $this->options['username'], null),
+ );
+ }
+
+ /**
+ * Tests that attempting to retrieve a hostmask without option values
+ * for all of its constituents generates an exception.
+ *
+ * @param string $nick Bot nick
+ * @param string $username Bot username
+ * @param string $host Server hostname
+ *
+ * @return void
+ * @dataProvider dataProviderTestGetHostmaskMissingDataGeneratesException
+ */
+ public function testGetHostmaskMissingDataGeneratesException($nick, $username, $host)
+ {
+ $options = array(
+ 'nick' => $nick,
+ 'username' => $username,
+ 'host' => $host,
+ );
+
+ $connection = new Phergie_Connection($options);
+
+ try {
+ $hostmask = $connection->getHostmask();
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Connection_Exception $e) {
+ return;
+ } catch (Exception $e) {
+ $this->fail('Unexpected exception was thrown');
+ }
+ }
+
+ /**
+ * Tests that attempting to retrieve a hostmask with all required
+ * options is successful.
+ *
+ * @return void
+ */
+ public function testGetHostmaskWithValidData()
+ {
+ $options = array(
+ 'nick' => 'MyNick',
+ 'username' => 'MyUsername',
+ 'host' => 'example.com'
+ );
+
+ $connection = new Phergie_Connection($options);
+ $hostmask = $connection->getHostmask();
+ $this->assertType('Phergie_Hostmask', $hostmask);
+ }
+
+ /**
+ * Data provider for testGetRequiredOptionsWithoutValuesSet().
+ *
+ * @return array Enumerated array of enumerated arrays each containing a
+ * set of parameters for a single call to
+ * testGetRequiredOptionsWithoutValuesSet()
+ */
+ public function dataProviderTestGetRequiredOptionsWithoutValuesSet()
+ {
+ return array(
+ array('host'),
+ array('nick'),
+ array('username'),
+ array('realname'),
+ );
+ }
+
+ /**
+ * Tests that attempting to retrieve values of required options when no
+ * values are set results in an exception.
+ *
+ * @param string $option Option name
+ *
+ * @return void
+ * @dataProvider dataProviderTestGetRequiredOptionsWithoutValuesSet
+ */
+ public function testGetRequiredOptionsWithoutValuesSet($option)
+ {
+ try {
+ $connection = new Phergie_Connection;
+ $value = $connection->{'get' . ucfirst($option)}();
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Connection_Exception $e) {
+ return;
+ } catch (Exception $e) {
+ $this->fail('Unexpected exception was thrown');
+ }
+ }
+
+ /**
+ * Tests that attempting to set an invalid value for the transport
+ * results in an exception.
+ *
+ * @return void
+ */
+ public function testSetTransportWithInvalidValue()
+ {
+ $connection = new Phergie_Connection;
+ try {
+ $connection->setTransport('blah');
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Connection_Exception $e) {
+ return;
+ } catch (Exception $e) {
+ $this->fail('Unexpected exception was thrown');
+ }
+ }
+
+ /**
+ * Tests that attempting to set an invalid value for the encoding
+ * results in an exception.
+ *
+ * @return void
+ */
+ public function testSetEncodingWithInvalidValue()
+ {
+ $connection = new Phergie_Connection;
+ try {
+ $connection->setEncoding('blah');
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Connection_Exception $e) {
+ return;
+ } catch (Exception $e) {
+ $this->fail('Unexpected exception was thrown');
+ }
+ }
+
+ /**
+ * Tests that options can be set collectively after the connection is
+ * instantiated.
+ *
+ * @return void
+ */
+ public function testSetOptions()
+ {
+ $connection = new Phergie_Connection;
+ $connection->setOptions($this->options);
+ foreach ($this->options as $key => $value) {
+ $this->assertEquals($value, $connection->{'get' . ucfirst($key)}());
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php
new file mode 100644
index 000000000..9ecdd327a
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php
@@ -0,0 +1,837 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Handler.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Plugin handler instance being tested
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $handler;
+
+ /**
+ * Mock Phergie_Config instance passed to the plugin handler constructor
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Mock Phergie_Event_Handler instance passed to the plugin handler
+ * constructor
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Returns a mock plugin instance.
+ *
+ * @param string $name Optional short name for the mock plugin, defaults
+ * to 'TestPlugin'
+ * @param array $methods Optional list of methods to override
+ *
+ * @return Phergie_Plugin_Abstract
+ */
+ protected function getMockPlugin($name = 'TestPlugin', array $methods = array())
+ {
+ $methods[] = 'getName';
+ $plugin = $this->getMock('Phergie_Plugin_Abstract', $methods);
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue($name));
+ return $plugin;
+ }
+
+ /**
+ * Sets up a new handler instance before each test.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ $this->config = $this->getMock('Phergie_Config');
+ $this->events = $this->getMock('Phergie_Event_Handler');
+ $this->handler = new Phergie_Plugin_Handler(
+ $this->config,
+ $this->events
+ );
+ }
+
+ /**
+ * Tests iterability of the plugin handler.
+ *
+ * @return void
+ */
+ public function testImplementsIteratorAggregate()
+ {
+ $reflection = new ReflectionObject($this->handler);
+
+ $this->assertTrue(
+ $reflection->implementsInterface('IteratorAggregate'),
+ 'Handler does not implement IteratorAggregate'
+ );
+
+ $this->assertType(
+ 'Iterator',
+ $this->handler->getIterator(),
+ 'getIterator() must return an iterator'
+ );
+ }
+
+ /**
+ * Tests that a default iterator is returned if none is explicitly set.
+ *
+ * @return void
+ */
+ public function testGetIteratorReturnsDefault()
+ {
+ $this->assertType(
+ 'Phergie_Plugin_Iterator',
+ $this->handler->getIterator()
+ );
+ }
+
+ /**
+ * Tests the ability to change the handler's iterator class when a valid
+ * class is specified.
+ *
+ * @return void
+ */
+ public function testSetIteratorClassWithValidClass()
+ {
+ eval('
+ class DummyIterator extends FilterIterator {
+ public function accept() {
+ return true;
+ }
+ }
+ ');
+
+ $this->handler->setIteratorClass('DummyIterator');
+
+ $this->assertType(
+ 'DummyIterator',
+ $this->handler->getIterator()
+ );
+ }
+
+ /**
+ * Tests that a failure occurs when a nonexistent iterator class is
+ * specified.
+ *
+ * @return void
+ */
+ public function testSetIteratorClassWithNonexistentClass()
+ {
+ try {
+ $this->handler->setIteratorClass('FooIterator');
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Plugin_Exception $e) {
+ return;
+ }
+ $this->fail('Unexpected exception was thrown');
+ }
+
+ /**
+ * Tests that a failure occurs when a class that is not a subclass of
+ * FilterIterator is specified.
+ *
+ * @return void
+ */
+ public function testSetIteratorClassWithNonFilterIteratorClass()
+ {
+ try {
+ $this->handler->setIteratorClass('ArrayIterator');
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Plugin_Exception $e) {
+ return;
+ }
+ $this->fail('Unexpected exception was thrown');
+ }
+
+ /**
+ * Tests countability of the plugin handler.
+ *
+ * @return void
+ */
+ public function testImplementsCountable()
+ {
+ $reflection = new ReflectionObject($this->handler);
+
+ $this->assertTrue(
+ $reflection->implementsInterface('Countable'),
+ 'Handler does not implement Countable'
+ );
+
+ $this->assertType(
+ 'int',
+ count($this->handler),
+ 'count() must return an integer'
+ );
+ }
+
+ /**
+ * Tests the plugin handler exposing added plugins as instance
+ * properties of the handler via isset().
+ *
+ * @return void
+ */
+ public function testImplementsIsset()
+ {
+ $pluginName = 'TestPlugin';
+ $this->assertFalse(isset($this->handler->{$pluginName}));
+ $plugin = $this->getMockPlugin($pluginName);
+ $this->handler->addPlugin($plugin);
+ $this->assertTrue(isset($this->handler->{$pluginName}));
+ }
+
+ /**
+ * Tests the plugin handler exposing added plugins as instance
+ * properties of the handler.
+ *
+ * @depends testImplementsIsset
+ * @return void
+ */
+ public function testImplementsGet()
+ {
+ $plugin = $this->getMockPlugin();
+ $this->handler->addPlugin($plugin);
+ $name = $plugin->getName();
+ $getPlugin = $this->handler->getPlugin($name);
+ $this->assertTrue(isset($this->handler->$name));
+ $get = $this->handler->$name;
+ $this->assertSame($getPlugin, $get);
+ }
+
+ /**
+ * Tests the plugin handler allowing for plugin removal via unset().
+ *
+ * @depends testImplementsGet
+ * @return void
+ */
+ public function testImplementsUnset()
+ {
+ $plugin = $this->getMockPlugin();
+ $this->handler->addPlugin($plugin);
+ unset($this->handler->{$plugin->getName()});
+ $this->assertFalse($this->handler->hasPlugin($plugin->getName()));
+ }
+
+ /**
+ * Tests the plugin handler executing a callback on all contained
+ * plugins.
+ *
+ * @return void
+ */
+ public function testImplementsCall()
+ {
+ foreach (range(1, 2) as $index) {
+ $plugin = $this->getMockPlugin('TestPlugin' . $index, array('callback'));
+ $plugin
+ ->expects($this->once())
+ ->method('callback');
+ $this->handler->addPlugin($plugin);
+ }
+
+ $this->assertTrue($this->handler->callback());
+ }
+
+ /**
+ * Tests a newly instantiated handler not having plugins associated with
+ * it.
+ *
+ * @depends testImplementsCountable
+ * @return void
+ */
+ public function testEmptyHandlerHasNoPlugins()
+ {
+ $this->assertEquals(0, count($this->handler));
+ }
+
+ /**
+ * Tests a newly instantiated handler not having autoloading enabled by
+ * default.
+ *
+ * @return void
+ */
+ public function testGetAutoloadDefaultsToNotAutoload()
+ {
+ $this->assertFalse($this->handler->getAutoload());
+ }
+
+ /**
+ * Tests setAutoload().
+ *
+ * @depends testGetAutoloadDefaultsToNotAutoload
+ * @return void
+ */
+ public function testSetAutoload()
+ {
+ $this->assertSame(
+ $this->handler->setAutoload(true),
+ $this->handler,
+ 'setAutoload() does not provide a fluent interface'
+ );
+
+ $this->assertTrue(
+ $this->handler->getAutoload(),
+ 'setAutoload() had no effect on getAutoload()'
+ );
+ }
+
+ /**
+ * Tests addPath() providing a fluent interface.
+ *
+ * @return void
+ */
+ public function testAddPathProvidesFluentInterface()
+ {
+ $handler = $this->handler->addPath(dirname(__FILE__));
+ $this->assertSame($this->handler, $handler);
+ }
+
+ /**
+ * Tests addPath() throwing an exception when it cannot read the
+ * directory.
+ *
+ * @return void
+ */
+ public function testAddPathThrowsExceptionOnUnreadableDirectory()
+ {
+ try {
+ $this->handler->addPath('/an/unreadable/directory/path');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised');
+ }
+
+ /**
+ * Tests adding a path to the plugin handler.
+ *
+ * @return void
+ */
+ public function testAddPath()
+ {
+ $pluginName = 'Mock';
+
+ try {
+ $this->handler->addPlugin($pluginName);
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND,
+ $e->getCode()
+ );
+ }
+
+ if (!isset($e)) {
+ $this->fail('Plugin loaded, path was already present');
+ }
+
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ try {
+ $this->handler->addPlugin($pluginName);
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->fail('Added path, plugin still not found');
+ }
+ }
+
+ /**
+ * Tests addPlugin() returning an added plugin instance.
+ *
+ * @return void
+ */
+ public function testAddPluginByInstanceReturnsPluginInstance()
+ {
+ $plugin = $this->getMockPlugin();
+ $returnedPlugin = $this->handler->addPlugin($plugin);
+ $this->assertSame(
+ $returnedPlugin,
+ $plugin,
+ 'addPlugin() does not return the instance passed to it'
+ );
+ }
+
+ /**
+ * Tests adding a plugin to the handler using the plugin's short name.
+ *
+ * @return void
+ */
+ public function testAddPluginByShortName()
+ {
+ $pluginName = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $returnedPlugin = $this->handler->addPlugin($pluginName);
+ $this->assertTrue($this->handler->hasPlugin($pluginName));
+
+ $this->assertType(
+ 'Phergie_Plugin_Mock',
+ $this->handler->getPlugin($pluginName)
+ );
+
+ $this->assertSame(
+ $this->handler->getPlugin($pluginName),
+ $returnedPlugin,
+ 'Handler does not contain added plugin'
+ );
+ }
+
+
+ /**
+ * Tests adding a plugin instance to the handler.
+ *
+ * @return void
+ */
+ public function testAddPluginByInstance()
+ {
+ $plugin = $this->getMockPlugin();
+ $returnedPlugin = $this->handler->addPlugin($plugin);
+ $this->assertTrue($this->handler->hasPlugin('TestPlugin'));
+
+ $this->assertSame(
+ $plugin,
+ $returnedPlugin,
+ 'addPlugin() does not return added plugin instance'
+ );
+
+ $this->assertSame(
+ $plugin,
+ $this->handler->getPlugin('TestPlugin'),
+ 'getPlugin() does not return added plugin instance'
+ );
+ }
+
+ /**
+ * Tests addPlugin() throwing an exception when the plugin class file
+ * can't be found.
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionWhenPluginFileNotFound()
+ {
+ try {
+ $this->handler->addPlugin('TestPlugin');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised');
+ }
+
+ /**
+ * Recursively removes all files and subdirectories in a directory.
+ *
+ * @param string $path Directory path
+ * @return void
+ */
+ private function removeDirectory($path)
+ {
+ if (file_exists($path)) {
+ $it = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($path),
+ RecursiveIteratorIterator::CHILD_FIRST
+ );
+ foreach ($it as $entry) {
+ if ($it->isDot()) {
+ continue;
+ }
+ if ($entry->isDir()) {
+ rmdir($entry->getPathname());
+ } else {
+ unlink($entry->getPathname());
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests addPlugin() throwing an exception when the plugin class file is
+ * found, but does not contain the plugin class as expected.
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionWhenPluginClassNotFound()
+ {
+ $path = sys_get_temp_dir() . '/Phergie/Plugin';
+ $this->removeDirectory(dirname($path));
+ mkdir($path, 0777, true);
+ touch($path . '/TestPlugin.php');
+ $this->handler->addPath($path, 'Phergie_Plugin_');
+
+ try {
+ $this->handler->addPlugin('TestPlugin');
+ } catch(Phergie_Plugin_Exception $e) { }
+
+ if (isset($e)) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND,
+ $e->getCode()
+ );
+ } else {
+ $this->fail('An expected exception has not been raised');
+ }
+
+ $this->removeDirectory(dirname($path));
+ }
+
+ /**
+ * Tests addPlugin() throwing an exception when trying to instantiate a
+ * class that doesn't extend Phergie_Plugin_Abstract.
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionIfRequestingNonPlugin()
+ {
+ try {
+ $this->handler->addPlugin('Handler');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised');
+ }
+
+ /**
+ * Tests addPlugin() throwing an exception when trying to instantiate a
+ * class that can't be instantiated.
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionIfPluginNotInstantiable()
+ {
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ try {
+ $this->handler->addPlugin('TestNonInstantiablePluginFromFile');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised');
+ }
+
+ /**
+ * Tests adding a plugin by its short name with arguments passed to the
+ * plugin constructor.
+ *
+ * @return void
+ */
+ public function testAddPluginShortNamePassesArgsToConstructor()
+ {
+ $pluginName = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $arguments = array('a', 'b', 'c');
+ $plugin = $this->handler->addPlugin($pluginName, $arguments);
+
+ $this->assertAttributeSame(
+ $arguments,
+ 'arguments',
+ $plugin,
+ 'Arguments do not match'
+ );
+ }
+
+ /**
+ * Tests addPlugin() passing Phergie_Config to an instantiated plugin.
+ *
+ * @return void
+ */
+ public function testAddPluginPassesConstructorArguments()
+ {
+ $pluginName = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ $plugin = $this->handler->addPlugin($pluginName);
+
+ $this->assertSame(
+ $this->config,
+ $plugin->getConfig(),
+ 'Phergie_Config instances do not match'
+ );
+
+ $this->assertSame(
+ $this->events,
+ $plugin->getEventHandler(),
+ 'Phergie_Event_Handler instances do not match'
+ );
+ }
+
+ /**
+ * Tests addPlugin() calling onLoad() on an instantiated plugin.
+ *
+ * @return void
+ */
+ public function testAddPluginCallsOnLoadOnInstantiatedPlugin()
+ {
+ $plugin = $this->getMockPlugin(null, array('onLoad'));
+ $plugin
+ ->expects($this->once())
+ ->method('onLoad');
+ $this->handler->addPlugin($plugin);
+ }
+
+ /**
+ * Tests addPlugin() returning the same plugin when called twice.
+ *
+ * @return void
+ */
+ public function testAddPluginReturnsSamePluginWhenAskedTwice()
+ {
+ $pluginName = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ $plugin1 = $this->handler->addPlugin($pluginName);
+ $plugin2 = $this->handler->addPlugin($pluginName);
+ $this->assertSame($plugin1, $plugin2);
+ }
+
+ /**
+ * Tests getPlugin() throwing an exception when trying to get an
+ * unloaded plugin with autoload disabled.
+ *
+ * @depends testGetAutoloadDefaultsToNotAutoload
+ * @return void
+ */
+ public function testExceptionThrownWhenLoadingPluginWithoutAutoload()
+ {
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ try {
+ $this->handler->getPlugin('Mock');
+ } catch (Phergie_Plugin_Exception $expected) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED,
+ $expected->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised');
+ }
+
+ /**
+ * Tests addPlugins() with a plugin short name and no plugin constructor
+ * arguments.
+ *
+ * @depends testAddPluginByShortName
+ * @depends testAddPluginByInstance
+ * @return void
+ */
+ public function testAddPluginsWithoutArguments()
+ {
+ $prefix = 'Phergie_Plugin_';
+ $this->handler->addPath(dirname(__FILE__), $prefix);
+
+ $plugin = 'Mock';
+ $this->handler->addPlugins(array($plugin));
+ $returnedPlugin = $this->handler->getPlugin($plugin);
+ $this->assertContains(
+ get_class($returnedPlugin),
+ $prefix . $plugin,
+ 'Short name plugin not of expected class'
+ );
+ }
+
+ /**
+ * Tests addPlugins() with a plugin short name and plugin constructor
+ * arguments.
+ *
+ * @depends testAddPluginByShortName
+ * @depends testAddPluginByInstance
+ * @return void
+ */
+ public function testAddPluginsWithArguments()
+ {
+ $prefix = 'Phergie_Plugin_';
+ $this->handler->addPath(dirname(__FILE__), $prefix);
+
+ $arguments = array(1, 2, 3);
+ $plugin = array('Mock', $arguments);
+ $this->handler->addPlugins(array($plugin));
+ $returnedPlugin = $this->handler->getPlugin('Mock');
+ $this->assertEquals(
+ $arguments,
+ $returnedPlugin->getArguments(),
+ 'Constructor arguments for instance plugin do not match'
+ );
+ }
+
+ /**
+ * Tests removePlugin() with a plugin instance.
+ *
+ * @depends testAddPluginByInstance
+ * @return void
+ */
+ public function testRemovePluginByInstance()
+ {
+ $plugin = $this->getMockPlugin();
+ $this->handler->addPlugin($plugin);
+ $this->handler->removePlugin($plugin);
+ $this->assertFalse(
+ $this->handler->hasPlugin($plugin->getName()),
+ 'Plugin was not removed'
+ );
+ }
+
+ /**
+ * Tests removePlugin() with a plugin short name.
+ *
+ * @depends testAddPluginByShortName
+ * @return void
+ */
+ public function testRemovePluginByShortName()
+ {
+ $plugin = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $this->handler->addPlugin($plugin);
+ $this->handler->removePlugin($plugin);
+ $this->assertFalse(
+ $this->handler->hasPlugin($plugin),
+ 'Plugin was not removed'
+ );
+ }
+
+ /**
+ * Tests getPlugin() when the plugin is not already loaded and
+ * autoloading is disabled.
+ *
+ * @depends testSetAutoload
+ * @return void
+ */
+ public function testGetPluginWithAutoloadEnabled()
+ {
+ $this->handler->setAutoload(true);
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ $plugin = $this->handler->getPlugin('Mock');
+ $this->assertType(
+ 'Phergie_Plugin_Mock',
+ $plugin,
+ 'Retrieved plugin not of expected class'
+ );
+ }
+
+ /**
+ * Tests getPlugins().
+ *
+ * @depends testGetPluginWithAutoloadEnabled
+ * @return void
+ */
+ public function testGetPlugins()
+ {
+ $plugin1 = $this->getMockPlugin('TestPlugin1');
+ $this->handler->addPlugin($plugin1);
+
+ $plugin2 = $this->getMockPlugin('TestPlugin2');
+ $this->handler->addPlugin($plugin2);
+
+ $expected = array(
+ 'testplugin1' => $plugin1,
+ 'testplugin2' => $plugin2,
+ );
+
+ $actual = $this->handler->getPlugins();
+ $this->assertEquals($expected, $actual);
+
+ $actual = $this->handler->getPlugins(array('testplugin1', 'testplugin2'));
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that multiple plugin iterators can be used concurrently.
+ *
+ * @return void
+ */
+ public function testUseMultiplePluginIteratorsConcurrently()
+ {
+ $plugin1 = $this->getMockPlugin('TestPlugin1');
+ $this->handler->addPlugin($plugin1);
+
+ $plugin2 = $this->getMockPlugin('TestPlugin2');
+ $this->handler->addPlugin($plugin2);
+
+ $iterator1 = $this->handler->getIterator();
+ $iterator1->next();
+ $this->assertSame($plugin2, $iterator1->current());
+
+ $iterator2 = $this->handler->getIterator();
+ $this->assertSame($plugin1, $iterator2->current());
+ }
+
+ /**
+ * Tests adding plugin paths via configuration.
+ *
+ * @return void
+ */
+ public function testAddPluginPathsViaConfiguration()
+ {
+ $dir = dirname(__FILE__);
+ $prefix = 'Phergie_Plugin_';
+ $paths = array($dir => $prefix);
+ $this->config
+ ->expects($this->any())
+ ->method('offsetExists')
+ ->will($this->returnValue(true));
+ $this->config
+ ->expects($this->any())
+ ->method('offsetGet')
+ ->will($this->returnValue($paths));
+
+ // Reinitialize the handler so the configuration change takes effect
+ // within the constructor
+ $this->handler = new Phergie_Plugin_Handler(
+ $this->config,
+ $this->events
+ );
+
+ $this->handler->setAutoload(true);
+ $this->handler->getPlugin('Mock');
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php
new file mode 100644
index 000000000..336b25d35
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Iterator.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_IteratorTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Iterator instance being tested
+ *
+ * @var Phergie_Plugin_Iterator
+ */
+ protected $iterator;
+
+ /**
+ * List of mock plugin instances to be iterated
+ *
+ * @var array
+ */
+ protected $plugins;
+
+ /**
+ * Initializes the iterator instance being tested.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ $this->plugins = array();
+ foreach (range(0, 4) as $index) {
+ $plugin = $this->getMock('Phergie_Plugin_Abstract');
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue($index));
+ $this->plugins[] = $plugin;
+ }
+
+ $this->iterator = new Phergie_Plugin_Iterator(
+ new ArrayIterator($this->plugins)
+ );
+ }
+
+ /**
+ * Tests that all plugins are iterated when no filters are applied.
+ */
+ public function testIteratesAllPluginsWithNoFilters()
+ {
+ $expected = range(0, 4);
+ $actual = array();
+ foreach ($this->iterator as $plugin) {
+ $actual[] = $plugin->getName();
+ }
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that appropriate plugins are iterated when plugin name filters
+ * are applied.
+ */
+ public function testIteratesPluginsWithNameFilters()
+ {
+ // Test acceptance of strings and fluent interface implementation
+ $returned = $this->iterator->addPluginFilter('0');
+ $this->assertSame($this->iterator, $returned);
+
+ // Test acceptance of arrays
+ $this->iterator->addPluginFilter(array('1', '3'));
+
+ // Test application of filters to iteration
+ $expected = array('2', '4');
+ $actual = array();
+ foreach ($this->iterator as $plugin) {
+ $actual[] = $plugin->getName();
+ }
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that appropriate plugins are iterated when method name filters
+ * are applied.
+ *
+ * The same method name is used in all cases here because mocked methods
+ * of mock objects do not appear to be detected by method_exists() or
+ * ReflectionClass, so filtering by a method defined in the base plugin
+ * class seems the easiest way to test that method filtering really
+ * works.
+ */
+ public function testIteratesPluginsWithMethodFilters()
+ {
+ // Tests acceptance of strings and fluent interface implementation
+ $returned = $this->iterator->addMethodFilter('getName');
+ $this->assertSame($this->iterator, $returned);
+
+ // Test acceptance of arrays
+ $this->iterator->addMethodFilter(array('getName', 'getName'));
+
+ // Test application of filters to iteration
+ $expected = array();
+ $actual = array();
+ foreach ($this->iterator as $plugin) {
+ $actual[] = $plugin->getName();
+ }
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that all plugins are iterated after filters are cleared.
+ *
+ * @depends testIteratesPluginsWithNameFilters
+ * @depends testIteratesPluginsWithMethodFilters
+ */
+ public function testIteratesPluginsAfterClearingFilters()
+ {
+ $this->iterator->addPluginFilter('0');
+ $this->iterator->addMethodFilter('method1');
+ $this->iterator->clearFilters();
+
+ $expected = range(0, 4);
+ $actual = array();
+ foreach ($this->iterator as $plugin) {
+ $actual[] = $plugin->getName();
+ }
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/KarmaTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/KarmaTest.php
new file mode 100644
index 000000000..6b7231674
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/KarmaTest.php
@@ -0,0 +1,335 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Karma.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_KarmaTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Skips tests if the SQLite PDO driver is not available.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->markTestSkipped('PDO or pdo_sqlite extension is required');
+ }
+
+ parent::setUp();
+ }
+
+ /**
+ * Configures the plugin to use a temporary copy of the database.
+ *
+ * @return PDO Connection to the temporary database
+ */
+ private function createMockDatabase()
+ {
+ $dbPath = $this->getPluginsPath('Karma/karma.db');
+ $db = $this->getMockDatabase($dbPath);
+ $this->plugin->setDb($db);
+ return $db;
+ }
+
+ /**
+ * Tests the requirement of the Command plugin.
+ *
+ * @return void
+ */
+ public function testRequiresCommandPlugin()
+ {
+ $this->assertRequiresPlugin('Command');
+ $this->plugin->onLoad();
+ }
+
+ /**
+ * Initiates a karma event with a specified term.
+ *
+ * @param string $term Karma term
+ *
+ * @return Phergie_Event_Request Initiated mock event
+ */
+ private function initiateKarmaEvent($term)
+ {
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => 'karma ' . $term
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+ return $event;
+ }
+
+ /**
+ * Checks for an expected karma response.
+ *
+ * @param Phergie_Event_Request $event Event containing the karma
+ * request
+ * @param string $term Karma term
+ * @param string $response Portion of the response
+ * message following the term
+ * from the original event
+ *
+ * @return void
+ */
+ private function checkForKarmaResponse($event, $term, $response)
+ {
+ $text = $event->getNick() . ': ' . $response;
+ $this->assertEmitsEvent('privmsg', array($event->getSource(), $text));
+ $this->plugin->onCommandKarma($term);
+ }
+
+ /**
+ * Tests that a default database is used when none is specified.
+ *
+ * @return void
+ */
+ public function testGetDb()
+ {
+ $db = $this->plugin->getDb();
+ $this->assertType('PDO', $db);
+ }
+
+ /**
+ * Tests specifying a custom database for the plugin to use.
+ *
+ * @return void
+ */
+ public function testSetDb()
+ {
+ $db = $this->createMockDatabase();
+ $this->assertSame($db, $this->plugin->getDb());
+ }
+
+ /**
+ * Tests that issuing the karma command with an unknown term returns a
+ * neutral rating.
+ *
+ * @return void
+ */
+ public function testKarmaCommandOnUnknownTerm()
+ {
+ $term = 'foo';
+ $this->createMockDatabase();
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, $term . ' has neutral karma.');
+ }
+
+ /**
+ * Tests that issuing the karma command with the term "me" returns the
+ * the karma rating for the initiating user.
+ *
+ * @return void
+ */
+ public function testKarmaCommandOnUser()
+ {
+ $term = 'me';
+ $this->createMockDatabase();
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, 'You have neutral karma.');
+ }
+
+ /**
+ * Tests that issuing the karma command with a term that has a fixed
+ * karma rating results in that rating being returned.
+ *
+ * @return void
+ */
+ public function testKarmaCommandWithFixedKarmaTerm()
+ {
+ $term = 'phergie';
+ $this->createMockDatabase();
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, 'phergie has karma of awesome.');
+ }
+
+ /**
+ * Supporting method that tests the result of a karma term rating change.
+ *
+ * @param string $term Karma term for which the rating is being
+ * changed
+ * @param string $operation ++ or --
+ * @param int $karma Expected karma rating after the change is
+ * applied
+ */
+ private function checkForKarmaRatingChange($term, $operation, $karma)
+ {
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => $term . $operation
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+ $this->plugin->onPrivmsg();
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, $term . ' has karma of ' . $karma . '.');
+ }
+
+ /**
+ * Tests incrementing the karma rating of a new term.
+ *
+ * @return void
+ */
+ public function testIncrementingKarmaRating()
+ {
+ $this->createMockDatabase();
+ $this->checkForKarmaRatingChange('foo', '++', 1);
+ }
+
+ /**
+ * Tests decrementing the karma rating of a new term.
+ *
+ * @return void
+ */
+ public function testDecrementingKarmaRating()
+ {
+ $this->createMockDatabase();
+ $this->checkForKarmaRatingChange('foo', '--', -1);
+ }
+
+ /**
+ * Tests modifying the karma rating of an existing term.
+ *
+ * @return void
+ */
+ public function testChangingExistingKarmaRating()
+ {
+ $term = 'foo';
+ $this->createMockDatabase();
+ $this->checkForKarmaRatingChange($term, '++', 1);
+ $this->checkForKarmaRatingChange($term, '++', 2);
+ }
+
+ /**
+ * Tests resetting the karma rating of an existing term to 0.
+ *
+ * @return void
+ */
+ public function testResettingExistingKarmaRating()
+ {
+ $term = 'foo';
+ $this->createMockDatabase();
+ $this->checkForKarmaRatingChange($term, '++', 1);
+ $this->plugin->onCommandReincarnate($term);
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, $term . ' has neutral karma.');
+ }
+
+ /**
+ * Data provider for testKarmaComparisons().
+ *
+ * @return array Enumerated array of enumerated arrays each containing a
+ * set of parameter values for a single call to
+ * testKarmaComparisons()
+ */
+ public function dataProviderTestKarmaComparisons()
+ {
+ $term1 = 'foo';
+ $term2 = 'bar';
+
+ $positive = 'True that.';
+ $negative = 'No sir, not at all.';
+
+ return array(
+ array($term1, $term2, 1, 0, '>', $positive),
+ array($term1, $term2, 0, 1, '>', $negative),
+ array($term1, $term2, 1, 1, '>', $negative),
+ array($term1, $term2, 1, 0, '<', $negative),
+ array($term1, $term2, 0, 1, '<', $positive),
+ array($term1, $term2, 1, 1, '<', $negative),
+ array($term1, 'phergie', 1, 0, '>', $positive),
+ array('phergie', $term2, 0, 1, '<', $positive),
+ array($term1, 'everything', 0, 0, '>', $positive),
+ array('everything', $term2, 0, 0, '>', $positive),
+ );
+ }
+
+ /**
+ * Tests comparing the karma ratings of two terms.
+ *
+ * @param string $term1 First term
+ * @param string $term2 Second term
+ * @param int $karma1 Karma rating of the first time, 0 or 1
+ * @param int $karma2 Karma rating of the second term, 0 or 1
+ * @param string $operator Comparison operator, > or <
+ * @param string $response Response to check for
+ *
+ * @return void
+ * @dataProvider dataProviderTestKarmaComparisons
+ */
+ public function testKarmaComparisons($term1, $term2, $karma1, $karma2,
+ $operator, $response
+ ) {
+ $db = $this->createMockDatabase();
+
+ // Reduce answer tables to expected response
+ $stmt = $db->prepare('DELETE FROM positive_answers WHERE answer != ?');
+ $stmt->execute(array($response));
+ $stmt = $db->prepare('DELETE FROM negative_answers WHERE answer != ?');
+ $stmt->execute(array($response));
+
+ if ($karma1) {
+ $this->checkForKarmaRatingChange($term1, '++', 1);
+ }
+
+ if ($karma2) {
+ $this->checkForKarmaRatingChange($term2, '++', 1);
+ }
+
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => $term1 . ' ' . $operator . ' ' . $term2
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+
+ // Test lack of a response for terms with fixed karma ratings
+ if ($term1 == 'phergie' || $term2 == 'phergie') {
+ $callback = 'assertDoesNotEmitEvent';
+ } else {
+ $callback = 'assertEmitsEvent';
+ }
+
+ $this->$callback('privmsg', array($event->getSource(), $response));
+ $this->plugin->onPrivmsg();
+
+ // Test for karma changes when one term is "everything"
+ if ($term1 == 'everything' || $term2 == 'everything') {
+ if ($term1 == 'everything') {
+ $term = $term2;
+ $karma = ($operator == '>') ? -1 : 1;
+ } else {
+ $term = $term1;
+ $karma = ($operator == '>') ? 1 : -1;
+ }
+ $event = $this->initiateKarmaEvent($term);
+ $this->checkForKarmaResponse($event, $term, $term . ' has karma of ' . $karma . '.');
+ }
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/Mock.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/Mock.php
new file mode 100755
index 000000000..44a5d11c7
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/Mock.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Creates a plugin on the filesystem that can be used by
+ * Phergie_Plugin_Handler::addPath() to be located and loaded.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_Mock extends Phergie_Plugin_Abstract
+{
+ /**
+ * Arguments passed to the constructor
+ *
+ * @var array
+ */
+ protected $arguments;
+
+ /**
+ * Stores all arguments for later use.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->arguments = func_get_args();
+ }
+
+ /**
+ * Returns all constructor arguments.
+ *
+ * @return array Enumerated array containing the arguments passed to the
+ * constructor in order
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PingTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PingTest.php
new file mode 100644
index 000000000..ac30d46ff
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PingTest.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Ping.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_PingTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Tests that the last ping and event are initialized on connection to
+ * the server.
+ *
+ * @return void
+ */
+ public function testOnConnect()
+ {
+ $this->plugin->onConnect();
+
+ $expected = time();
+ $actual = $this->plugin->getLastEvent();
+ $this->assertEquals($expected, $actual);
+
+ $expected = null;
+ $actual = $this->plugin->getLastPing();
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that the last event is reset when an event occurs.
+ *
+ * @return void
+ */
+ public function testPreEvent()
+ {
+ $this->plugin->preEvent();
+
+ $expected = time();
+ $actual = $this->plugin->getLastEvent();
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that the last ping is reset when a ping is received.
+ *
+ * @return void
+ */
+ public function testOnPingResponse()
+ {
+ $this->plugin->onPingResponse();
+
+ $expected = null;
+ $actual = $this->plugin->getLastPing();
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that the test suite is able to manipulate the value of the last
+ * event.
+ *
+ * @return void
+ */
+ public function testSetLastEvent()
+ {
+ $expected = time() + 1;
+ $this->plugin->setLastEvent($expected);
+ $actual = $this->plugin->getLastEvent();
+ $this->assertEquals($expected, $actual);
+
+ $this->plugin->setLastEvent();
+ $expected = time();
+ $actual = $this->plugin->getLastEvent();
+ $this->assertEquals($expected, $actual);
+
+ try {
+ $this->plugin->setLastEvent('foo');
+ $this->fail('Expected exception was not thrown');
+ } catch (Exception $e) { }
+ }
+
+ /**
+ * Tests that the test suite is able to manipulate the value of the last
+ * ping.
+ *
+ * @return void
+ */
+ public function testSetLastPing()
+ {
+ $expected = time() + 1;
+ $this->plugin->setLastPing($expected);
+ $actual = $this->plugin->getLastPing();
+ $this->assertEquals($expected, $actual);
+
+ $this->plugin->setLastPing();
+ $expected = time();
+ $actual = $this->plugin->getLastPing();
+ $this->assertEquals($expected, $actual);
+
+ try {
+ $this->plugin->setLastPing('foo');
+ $this->fail('Expected exception was not thrown');
+ } catch (Exception $e) { }
+ }
+
+ /**
+ * Tests that a ping event is sent after the appropriate time period has
+ * lapsed since receiving an event.
+ *
+ * @depends testSetLastEvent
+ * @return void
+ */
+ public function testPing()
+ {
+ $pingEvent = 10;
+ $this->setConfig('ping.event', $pingEvent);
+ $lastEvent = time() - ($pingEvent + 1);
+ $this->plugin->setLastEvent($lastEvent);
+ $expected = time();
+ $this->assertEmitsEvent('ping', array($this->nick, $expected));
+ $this->plugin->onTick();
+ $actual = $this->plugin->getLastPing();
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that a quit event is sent after the appropriate time period has
+ * lapsed since sending a ping event.
+ *
+ * @depends testPing
+ * @return void
+ */
+ public function testQuit()
+ {
+ $pingPing = 10;
+ $this->setConfig('ping.ping', $pingPing);
+ $lastPing = time() - ($pingPing + 1);
+ $this->plugin->setLastPing($lastPing);
+ $this->assertEmitsEvent('quit');
+ $this->plugin->onTick();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PongTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PongTest.php
new file mode 100644
index 000000000..e8351fef2
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PongTest.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Pong.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_PongTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Test that a pong event is sent when a ping event is received.
+ *
+ * @return void
+ */
+ public function testPong()
+ {
+ $expected = 'irc.freenode.net';
+ $event = $this->getMockEvent('ping', array($expected));
+ $this->plugin->setEvent($event);
+ $this->assertEmitsEvent('pong', array($expected));
+ $this->plugin->onPing();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/SpellCheckTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/SpellCheckTest.php
new file mode 100644
index 000000000..369a0c644
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/SpellCheckTest.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_SpellCheck.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_SpellCheckTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Checks for the pspell extension.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ if (!extension_loaded('pspell')) {
+ $this->markTestSkipped('pspell extension not available');
+ }
+ }
+
+ /**
+ * Tests for the plugin failing to load when the language setting is not
+ * specified.
+ *
+ * @return void
+ */
+ public function testLanguageSettingNotSet()
+ {
+ try {
+ $this->plugin->onLoad();
+ $this->fail('Expected exception was not thrown');
+ } catch (Phergie_Plugin_Exception $e) {
+ return;
+ }
+ $this->fail('Unexpected exception was thrown');
+ }
+
+ /**
+ * Tests for the plugin requiring the Command plugin as a dependency.
+ *
+ * @return void
+ */
+ public function testRequiresCommandPlugin()
+ {
+ $this->setConfig('spellcheck.lang', 'en');
+ $this->assertRequiresPlugin('Command');
+ $this->plugin->onLoad();
+ }
+
+ /**
+ * Tests for the plugin failing to load because of a dictionary error.
+ *
+ * @return void
+ */
+ public function testLoadDictionaryError()
+ {
+ $this->setConfig('spellcheck.lang', 'foo');
+ try {
+ $this->plugin->onLoad();
+ $this->fail('Expected exception not thrown');
+ } catch (Phergie_Plugin_Exception $e) {
+ return;
+ }
+ $this->fail('Unexpected exception was thrown');
+ }
+
+ /**
+ * Initializes a spell check event.
+ *
+ * @param string $word Word to be checked
+ *
+ * @return void
+ */
+ private function initializeSpellCheckEvent($word)
+ {
+ $this->setConfig('spellcheck.lang', 'en');
+ $this->plugin->onLoad();
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => 'spell ' . $word
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+ }
+
+ /**
+ * Checks for a specified response to a spell check event.
+ *
+ * @param string $word Work being checked
+ * @param string $response Expected response
+ *
+ * @return void
+ */
+ private function checkForSpellCheckResponse($word, $response)
+ {
+ $this->assertEmitsEvent('privmsg', array($this->source, $response));
+ $this->plugin->onCommandSpell($word);
+ }
+
+ /**
+ * Tests for the plugin returning a response for a correctly spelled word.
+ *
+ * @return void
+ */
+ public function testRespondsForCorrectlySpelledWord()
+ {
+ $word = 'test';
+ $this->initializeSpellCheckEvent($word);
+ $response = $this->nick . ': The word "' . $word . '" seems to be spelled correctly.';
+ $this->checkForSpellCheckResponse($word, $response);
+ }
+
+ /**
+ * Tests for the plugin returning a response when it can't find any
+ * suggestions for a word.
+ *
+ * @return void
+ */
+ public function testRespondsWithoutSuggestions()
+ {
+ $word = 'kjlfljlkjljkljlj';
+ $this->initializeSpellCheckEvent($word);
+ $response = $this->nick . ': I could not find any suggestions for "' . $word . '".';
+ $this->checkForSpellCheckResponse($word, $response);
+ }
+
+ /**
+ * Tests for the plugin returning a response when it is able to find
+ * suggestions for a word.
+ *
+ * @return void
+ */
+ public function testRespondsWithSuggestions()
+ {
+ $word = 'teh';
+ $this->initializeSpellCheckEvent($word);
+ $response = $this->nick . ': Suggestions for "' . $word . '": the, Te, tech, Th, eh.';
+ $this->checkForSpellCheckResponse($word, $response);
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TerryChayTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TerryChayTest.php
new file mode 100644
index 000000000..e58ac6f29
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TerryChayTest.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_TerryChay.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_TerryChayTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Chayism used as a consistent response when related events are
+ * triggered
+ *
+ * @var string
+ */
+ private $chayism = 'Terry Chay doesn\'t need a framework; he already knows everyone\'s code';
+
+ /**
+ * Configures the mock plugin handler to return a mock Http plugin with
+ * a mock response object populated with predetermined content.
+ *
+ * @return void
+ */
+ public function setUpHttpClient()
+ {
+ $response = $this->getMock('Phergie_Plugin_Http_Response');
+ $response
+ ->expects($this->any())
+ ->method('getContent')
+ ->will($this->returnValue($this->chayism));
+
+ $plugin = $this->getMock('Phergie_Plugin_Http');
+ $plugin
+ ->expects($this->any())
+ ->method('get')
+ ->will($this->returnValue($response));
+
+ $this->getMockPluginHandler()
+ ->expects($this->any())
+ ->method('getPlugin')
+ ->with('Http')
+ ->will($this->returnValue($plugin));
+ }
+
+ /**
+ * Tests that the plugin requires the Http plugin as a dependency.
+ *
+ * @return void
+ */
+ public function testRequiresHttpPlugin()
+ {
+ $this->assertRequiresPlugin('Http');
+ $this->plugin->onLoad();
+ }
+
+ /**
+ * Data provider for testPrivmsgTriggerReturnsChayism().
+ *
+ * @return array Enumerated array of enumerated arrays each containing
+ * a set of parameters for a single call to
+ * testPrivmsgTriggerReturnsChayism()
+ */
+ public function dataProviderTestPrivmsgTriggerReturnsChayism()
+ {
+ return array(
+ array('terry chay'),
+ array('terry chay'),
+ array('tychay'),
+ array('!tychay'),
+ array('! tychay'),
+ array('foo tychay bar'),
+ );
+ }
+
+ /**
+ * Tests that appropriate triggers result in a response with a Chayism.
+ *
+ * @return void
+ * @dataProvider dataProviderTestPrivmsgTriggerReturnsChayism
+ */
+ public function testPrivmsgTriggerReturnsChayism($trigger)
+ {
+ $this->setConfig('command.prefix', '!');
+ $this->setUpHttpClient();
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => $trigger
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+ $this->assertEmitsEvent('privmsg', array($this->source, 'Fact: ' . $this->chayism));
+ $this->plugin->onPrivmsg();
+ }
+
+ /**
+ * Tests that lack of an appropriate trigger results in no response with
+ * a Chayism.
+ *
+ * @return void
+ */
+ public function testNoPrivmsgTriggerDoesNotReturnChayism()
+ {
+ $args = array(
+ 'receiver' => $this->source,
+ 'text' => 'foo bar baz'
+ );
+ $event = $this->getMockEvent('privmsg', $args);
+ $this->plugin->setEvent($event);
+ $this->assertDoesNotEmitEvent('privmsg', array($this->source, 'Fact: ' . $this->chayism));
+ $this->plugin->onPrivmsg();
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestCase.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestCase.php
new file mode 100644
index 000000000..941e7cb41
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestCase.php
@@ -0,0 +1,435 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Unit test suite for plugin classes.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+abstract class Phergie_Plugin_TestCase extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Mock configuration
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Associative array for configuration setting values, accessed by the
+ * mock configuration object using a callback
+ *
+ * @var array
+ */
+ protected $settings = array();
+
+ /**
+ * Mock connection
+ *
+ * @var Phergie_Connection
+ */
+ protected $connection;
+
+ /**
+ * Mock event handler
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Mock plugin handler
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Plugin instance being tested
+ *
+ * @var Phergie_Plugin_Abstract
+ */
+ protected $plugin;
+
+ /**
+ * Full name of the plugin class being tested, may be explicitly
+ * specified in subclasses but is otherwise automatically derived from
+ * the test case class name
+ *
+ * @var string
+ */
+ protected $pluginClass;
+
+ /**
+ * User nick used in any events requiring one
+ *
+ * @var string
+ */
+ protected $nick = 'nick';
+
+ /**
+ * Event source used in any events requiring one
+ *
+ * @var string
+ */
+ protected $source = '#channel';
+
+ /**
+ * Initializes instance properties.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ if (empty($this->pluginClass)) {
+ $this->pluginClass = preg_replace('/Test$/', '', get_class($this));
+ }
+
+ if (empty($this->plugin)) {
+ $this->plugin = new $this->pluginClass;
+ }
+
+ $this->plugin->setConfig($this->getMockConfig());
+ $this->plugin->setConnection($this->getMockConnection());
+ $this->plugin->setEventHandler($this->getMockEventHandler());
+ $this->plugin->setPluginHandler($this->getMockPluginHandler());
+ }
+
+ /**
+ * Destroys all initialized instance properties.
+ *
+ * @return void
+ */
+ public function tearDown()
+ {
+ unset(
+ $this->plugins,
+ $this->events,
+ $this->connection,
+ $this->config,
+ $this->plugin
+ );
+ }
+
+ /**
+ * Returns a mock configuration object.
+ *
+ * @return Phergie_Config
+ */
+ protected function getMockConfig()
+ {
+ if (empty($this->config)) {
+ $this->config = $this->getMock('Phergie_Config', array('offsetExists', 'offsetGet'));
+ $this->config
+ ->expects($this->any())
+ ->method('offsetExists')
+ ->will($this->returnCallback(array($this, 'configOffsetExists')));
+ $this->config
+ ->expects($this->any())
+ ->method('offsetGet')
+ ->will($this->returnCallback(array($this, 'configOffsetGet')));
+ }
+ return $this->config;
+ }
+
+ /**
+ * Returns whether a specific configuration setting has a value. Only
+ * intended for use by this class, but must be public for PHPUnit to
+ * call them.
+ *
+ * @param string $name Name of the setting
+ *
+ * @return boolean TRUE if the setting has a value, FALSE otherwise
+ */
+ public function configOffsetExists($name)
+ {
+ return isset($this->settings[$name]);
+ }
+
+ /**
+ * Returns the value of a specific configuration setting. Only intended
+ * for use by this class, but must be public for PHPUnit to call them.
+ *
+ * @param string $name Name of the setting
+ *
+ * @return mixed Value of the setting
+ */
+ public function configOffsetGet($name)
+ {
+ return $this->settings[$name];
+ }
+
+ /**
+ * Returns a mock connection object.
+ *
+ * @return Phergie_Connection
+ */
+ protected function getMockConnection()
+ {
+ if (empty($this->connection)) {
+ $this->connection = $this->getMock('Phergie_Connection');
+ $this->connection
+ ->expects($this->any())
+ ->method('getNick')
+ ->will($this->returnValue($this->nick));
+ }
+ return $this->connection;
+ }
+
+ /**
+ * Returns a mock event handler object.
+ *
+ * @return Phergie_Event_Handler
+ */
+ protected function getMockEventHandler()
+ {
+ if (empty($this->events)) {
+ $this->events = $this->getMock('Phergie_Event_Handler', array('addEvent'));
+ }
+ return $this->events;
+ }
+
+ /**
+ * Returns a mock plugin handler object.
+ *
+ * @return Phergie_Plugin_Handler
+ */
+ protected function getMockPluginHandler()
+ {
+ if (empty($this->plugins)) {
+ $config = $this->getMockConfig();
+ $events = $this->getMockEventHandler();
+ $this->plugins = $this->getMock(
+ 'Phergie_Plugin_Handler',
+ array(), // mock everything
+ array($config, $events)
+ );
+ }
+ return $this->plugins;
+ }
+
+ /**
+ * Returns a mock event object.
+ *
+ * @param string $type Event type
+ * @param array $args Optional associative array of event arguments
+ * @param string $nick Optional user nick to associate with the event
+ * @param string $source Optional user nick or channel name to associate
+ * with the event as its source
+ *
+ * @return Phergie_Event_Request
+ */
+ protected function getMockEvent($type, array $args = array(),
+ $nick = null, $source = null
+ ) {
+ $methods = array('getNick', 'getSource');
+ foreach (array_keys($args) as $arg) {
+ if (is_int($arg) || ctype_digit($arg)) {
+ $methods[] = 'getArgument';
+ } else {
+ $methods[] = 'get' . ucfirst($arg);
+ }
+ }
+
+ $event = $this->getMock(
+ 'Phergie_Event_Request',
+ $methods
+ );
+
+ $nick = $nick ? $nick : $this->nick;
+ $event
+ ->expects($this->any())
+ ->method('getNick')
+ ->will($this->returnValue($nick));
+
+ $source = $source ? $source : $this->source;
+ $event
+ ->expects($this->any())
+ ->method('getSource')
+ ->will($this->returnValue($source));
+
+ foreach ($args as $key => $value) {
+ if (is_int($key) || ctype_digit($key)) {
+ $event
+ ->expects($this->any())
+ ->method('getArgument')
+ ->with($key)
+ ->will($this->returnValue($value));
+ } else {
+ $event
+ ->expects($this->any())
+ ->method('get' . ucfirst($key))
+ ->will($this->returnValue($value));
+ }
+ }
+
+ return $event;
+ }
+
+ /**
+ * Sets the value of a configuration setting.
+ *
+ * @param string $setting Name of the setting
+ * @param mixed $value Value for the setting
+ *
+ * @return void
+ */
+ protected function setConfig($setting, $value)
+ {
+ $this->settings[$setting] = $value;
+ }
+
+ /**
+ * Returns the absolute path to the Phergie/Plugin directory. Useful in
+ * conjunction with getMockDatabase().
+ *
+ * @param string $subpath Optional path to append to the directory path
+ *
+ * @return string Directory path
+ */
+ protected function getPluginsPath($subpath = null)
+ {
+ $path = realpath(dirname(__FILE__) . '/../../../Phergie/Plugin');
+ if (!empty($subpath)) {
+ $path .= '/' . ltrim($subpath, '/');
+ }
+ return $path;
+ }
+
+ /**
+ * Modifies the event handler to include an expectation of an event
+ * being added by the plugin being tested. Note that this must be called
+ * BEFORE executing the plugin code intended to initiate the event.
+ *
+ * @param string $type Event type
+ * @param array $args Optional enumerated array of event arguments
+ *
+ * @return void
+ */
+ protected function assertEmitsEvent($type, array $args = array())
+ {
+ $this->events
+ ->expects($this->at(0))
+ ->method('addEvent')
+ ->with($this->plugin, $type, $args);
+ }
+
+ /**
+ * Modifies the event handler to include an expectation of an event NOT
+ * being added by the plugin being tested. Note that this must be called
+ * BEFORE executing plugin code that may initiate the event.
+ *
+ * @param string $type Event type
+ * @param array $args Optional enumerated array of event arguments
+ *
+ * @return void
+ */
+ protected function assertDoesNotEmitEvent($type, array $args = array())
+ {
+ // Ugly hack to get around an issue in PHPUnit
+ // @link http://github.com/sebastianbergmann/phpunit-mock-objects/issues/issue/5#issue/5/comment/343524
+ $callback = create_function(
+ '$plugin, $type, $args',
+ 'if (get_class($plugin) == "' . $this->pluginClass . '"
+ && $type == "' . $type . '"
+ && $args == "' . var_export($args, true) . '") {
+ trigger_error("Instance of ' . $this->pluginClass
+ . ' unexpectedly emitted event of type ' . $type
+ . '", E_USER_ERROR);
+ }'
+ );
+
+ $this->events
+ ->expects($this->any())
+ ->method('addEvent')
+ ->will($this->returnCallback($callback));
+ }
+
+ /**
+ * Modifies the plugin handler to include an expectation of a plugin
+ * being retrieved, indicating a dependency. Note that this must be
+ * called BEFORE executing the plugin code that may load that plugin
+ * dependency, which is usually located in onLoad().
+ *
+ * @param string $name Short name of the plugin required as a dependency
+ *
+ * @return void
+ */
+ public function assertRequiresPlugin($name)
+ {
+ $this->plugins
+ ->expects($this->atLeastOnce())
+ ->method('getPlugin')
+ ->with($name);
+ }
+
+ /**
+ * Creates an in-memory copy of a specified SQLite database file and
+ * returns a connection to it.
+ *
+ * @param string $path Path to the SQLite file to copy
+ *
+ * @return PDO Connection to the database copy
+ */
+ public function getMockDatabase($path)
+ {
+ $original = new PDO('sqlite:' . $path);
+ $copy = new PDO('sqlite::memory:');
+
+ $result = $original->query('SELECT sql FROM sqlite_master');
+ while ($sql = $result->fetchColumn()) {
+ $copy->exec($sql);
+ }
+
+ $tables = array();
+ $result = $original->query('SELECT name FROM sqlite_master WHERE type = "table"');
+ while ($table = $result->fetchColumn()) {
+ $tables[] = $table;
+ }
+
+ foreach ($tables as $table) {
+ $result = $original->query('SELECT * FROM ' . $table);
+ $insert = null;
+ $copy->beginTransaction();
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $columns = array_keys($row);
+ if (empty($insert)) {
+ $insert = $copy->prepare(
+ 'INSERT INTO "' . $table . '" (' .
+ '"' . implode('", "', $columns) . '"' .
+ ') VALUES (' .
+ ':' . implode(', :', $columns) .
+ ')'
+ );
+ }
+ $insert->execute($row);
+ }
+ $copy->commit();
+ unset($insert);
+ }
+
+ return $copy;
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestNonInstantiablePluginFromFile.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestNonInstantiablePluginFromFile.php
new file mode 100755
index 000000000..f9bddd151
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestNonInstantiablePluginFromFile.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+/**
+ * Creates a plugin on the filesystem that can be used by
+ * Phergie_Plugin_Handler's addPath utility to be located and loaded.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_TestNonInstantiablePluginFromFile
+extends Phergie_Plugin_Abstract
+{
+ /**
+ * Private constructor to ensure that this class is not instantiable.
+ *
+ * @return void
+ */
+ private function __construct()
+ {
+ }
+}
diff --git a/plugins/Irc/extlib/phergie/Tests/TestHelper.php b/plugins/Irc/extlib/phergie/Tests/TestHelper.php
new file mode 100644
index 000000000..e70af4456
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/TestHelper.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+
+error_reporting(E_ALL | E_STRICT);
+
+// Phergie components require Phergie_Autoload to function correctly.
+require_once dirname(__FILE__) . '/../Phergie/Autoload.php';
+Phergie_Autoload::registerAutoloader();
diff --git a/plugins/Irc/extlib/phergie/Tests/phpunit.xml b/plugins/Irc/extlib/phergie/Tests/phpunit.xml
new file mode 100644
index 000000000..b96589e4a
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/Tests/phpunit.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+-->
+<phpunit colors="true" bootstrap="./TestHelper.php">
+ <testsuite name="Phergie Test Suite">
+ <directory>./</directory>
+ </testsuite>
+</phpunit>
diff --git a/plugins/Irc/extlib/phergie/build.xml b/plugins/Irc/extlib/phergie/build.xml
new file mode 100644
index 000000000..7510c75a1
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/build.xml
@@ -0,0 +1,301 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="Phergie" default="core">
+
+ <tstamp>
+ <format property="DSTAMP" pattern="%Y-%m-%d" />
+ </tstamp>
+
+ <taskdef name="phergiepkg" classname="PhergiePackageTask" />
+ <taskdef name="phpdocumentor" classname="phing.tasks.ext.phpdoc.PhpDocumentorTask" />
+
+ <available file="./build.properties" property="have_properties_file" />
+
+ <property name="clean" value="true" />
+
+ <target name="input">
+
+ <if>
+ <equals arg1="${have_properties_file}" arg2="true" />
+ <then>
+ <property file="./build.properties" />
+ </then>
+ <else>
+ <input propertyname="build.srcdir" defaultvalue="./" message="Source directory" />
+ <input propertyname="build.dstdir" defaultvalue="./" message="Destination directory" />
+ <input propertyname="build.version.release" message="Release version" />
+ <input propertyname="build.version.api" message="API version" />
+ <input propertyname="build.stability.release" defaultvalue="stable" message="Release stability" validArgs="snapshot,devel,alpha,beta,stable" />
+ <input propertyname="build.stability.api" defaultvalue="stable" message="API stability" validArgs="snapshot,devel,alpha,beta,stable" />
+ <input propertyname="build.notes" message="Release notes" />
+ <input propertyname="build.phpdep" defaultvalue="5.2.0" message="PHP version required" />
+ <input propertyname="build.pearinstallerdep" defaultvalue="1.9.0" message="PEAR installer version required" />
+ </else>
+ </if>
+
+ <fileset dir="${build.srcdir}" id="core">
+ <include name="phergie.php" />
+ <include name="phergie.bat" />
+ <include name="LICENSE" />
+ <include name="Settings.php.dist" />
+ <include name="Phergie/Autoload.php" />
+ <include name="Phergie/Bot.php" />
+ <include name="Phergie/Config/Exception.php" />
+ <include name="Phergie/Config.php" />
+ <include name="Phergie/Connection/Exception.php" />
+ <include name="Phergie/Connection/Handler.php" />
+ <include name="Phergie/Connection.php" />
+ <include name="Phergie/Db/Exception.php" />
+ <include name="Phergie/Db/Manager.php" />
+ <include name="Phergie/Db/Sqlite.php" />
+ <include name="Phergie/Driver/Abstract.php" />
+ <include name="Phergie/Driver/Exception.php" />
+ <include name="Phergie/Driver/Streams.php" />
+ <include name="Phergie/Event/Abstract.php" />
+ <include name="Phergie/Event/Command.php" />
+ <include name="Phergie/Event/Exception.php" />
+ <include name="Phergie/Event/Handler.php" />
+ <include name="Phergie/Event/Request.php" />
+ <include name="Phergie/Event/Response.php" />
+ <include name="Phergie/Exception.php" />
+ <include name="Phergie/Hostmask/Exception.php" />
+ <include name="Phergie/Hostmask.php" />
+ <include name="Phergie/Plugin/Abstract.php" />
+ <include name="Phergie/Plugin/Exception.php" />
+ <include name="Phergie/Plugin/Handler.php" />
+ <include name="Phergie/Ui/Abstract.php" />
+ <include name="Phergie/Ui/Console.php" />
+ </fileset>
+
+ </target>
+
+ <target name="core" depends="input">
+
+ <property name="build.tmpdir" value="Phergie-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <copy todir="${build.tmpdir}">
+ <fileset refid="core" />
+ </copy>
+
+ <reflexive file="${build.tmpdir}/Phergie/Bot.php">
+ <filterchain>
+ <replaceregexp>
+ <regexp
+ pattern="const VERSION = '[^']+';"
+ replace="const VERSION = '${build.version.release}';"
+ />
+ </replaceregexp>
+ </filterchain>
+ </reflexive>
+
+ <phergiepkg name="Phergie" dir="${build.tmpdir}">
+ <fileset refid="core" />
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org" />
+ <option name="summary" value="Phergie core library" />
+ <option name="description" value="The Phergie package provides all files necessary to run a basic IRC bot." />
+ <option name="apiversion" value="${build.version.api}" />
+ <option name="apistability" value="${build.stability.api}" />
+ <option name="releaseversion" value="${build.version.release}" />
+ <option name="releasestability" value="${build.stability.release}" />
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License" />
+ <option name="packagetype" value="php" />
+ <option name="notes" value="${build.notes}" />
+ <mapping name="replacements">
+ <element>
+ <element key="path" value="phergie.php" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="/usr/bin/env php" />
+ <element key="to" value="php_bin" />
+ </element>
+ <element>
+ <element key="path" value="phergie.bat" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="@php_bin@" />
+ <element key="to" value="php_bin" />
+ </element>
+ <element>
+ <element key="path" value="phergie.bat" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="@bin_dir@" />
+ <element key="to" value="bin_dir" />
+ </element>
+ </mapping>
+ <mapping name="exceptions">
+ <element key="phergie.php" value="script" />
+ <element key="phergie.bat" value="script" />
+ </mapping>
+ <mapping name="releases">
+ <element>
+ <element key="installconditions">
+ <element key="os" value="windows" />
+ </element>
+ <element key="filelist">
+ <element key="install">
+ <element key="phergie.php" value="phergie" />
+ </element>
+ </element>
+ </element>
+ <element>
+ <element key="filelist">
+ <element key="install">
+ <element key="phergie.php" value="phergie" />
+ </element>
+ <element key="ignore">
+ <element value="phergie.bat" />
+ </element>
+ </element>
+ </element>
+ </mapping>
+ <mapping name="deps">
+ <element>
+ <element key="type" value="ext" />
+ <element key="name" value="pcre" />
+ </element>
+ <element>
+ <element key="type" value="ext" />
+ <element key="name" value="reflection" />
+ </element>
+ </mapping>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="plugin" depends="input">
+
+ <if>
+ <equals arg1="${have_properties_file}" arg2="true" />
+ <then>
+ <property file="./build.properties" />
+ </then>
+ <else>
+ <input propertyname="build.plugin" message="Short plugin name" />
+ <input propertyname="build.summary" message="Plugin summary" />
+ <input propertyname="build.description" message="Plugin description" />
+ </else>
+ </if>
+
+ <property name="build.class" value="Phergie_Plugin_${build.plugin}" />
+ <property name="build.tmpdir" value="${build.class}-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <fileset dir="${build.srcdir}" id="plugin">
+ <include name="Phergie/Plugin/${build.plugin}.php" />
+ <include name="Phergie/Plugin/${build.plugin}/**" />
+ </fileset>
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <copy todir="${build.tmpdir}">
+ <fileset refid="plugin" />
+ </copy>
+
+ <phergiepkg name="${build.class}" dir="${build.tmpdir}">
+ <fileset refid="plugin" />
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org"/>
+ <option name="summary" value="${build.summary}"/>
+ <option name="description" value="${build.description}"/>
+ <option name="apiversion" value="${build.version.api}"/>
+ <option name="apistability" value="${build.stability.api}"/>
+ <option name="releaseversion" value="${build.version.release}"/>
+ <option name="releasestability" value="${build.stability.release}"/>
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License"/>
+ <option name="packagetype" value="php"/>
+ <option name="notes" value="${build.notes}"/>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="docs" depends="input">
+
+ <property name="build.tmpdir" value="Phergie_Docs-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <phpdocumentor title="API Documentation"
+ destdir="${build.tmpdir}/api"
+ output="HTML:Smarty:PHP">
+ <fileset refid="core" />
+ <projdocfileset dir=".">
+ <include name="LICENSE" />
+ </projdocfileset>
+ </phpdocumentor>
+
+ <phergiepkg name="Phergie_Docs" dir="${build.tmpdir}">
+ <fileset dir="${build.tmpdir}">
+ <include name="api**" />
+ </fileset>
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org" />
+ <option name="summary" value="Phergie core library documentation" />
+ <option name="description" value="The Phergie_Docs package provides documentation for the Phergie core libraries." />
+ <option name="apiversion" value="${build.version.api}" />
+ <option name="apistability" value="${build.stability.api}" />
+ <option name="releaseversion" value="${build.version.release}" />
+ <option name="releasestability" value="${build.stability.release}" />
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License" />
+ <option name="packagetype" value="php" />
+ <option name="notes" value="${build.notes}" />
+ <mapping name="exceptions">
+ <element key="api" value="doc" />
+ </mapping>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="build">
+
+ <tar destfile="${build.tarball}" compression="gzip">
+ <fileset dir="${build.dstdir}">
+ <include name="${build.tmpdir}**" />
+ <include name="package.xml" />
+ </fileset>
+ </tar>
+
+ </target>
+
+ <target name="clean">
+
+ <if>
+ <istrue value="${clean}" />
+ <then>
+ <delete dir="${build.tmpdir}" />
+ <delete file="${build.dstdir}package.xml" />
+ </then>
+ </if>
+
+ </target>
+
+</project>
diff --git a/plugins/Irc/extlib/phergie/phergie.bat b/plugins/Irc/extlib/phergie/phergie.bat
new file mode 100644
index 000000000..4eec11d5c
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/phergie.bat
@@ -0,0 +1,14 @@
+@echo off
+REM Phergie
+REM
+REM PHP version 5
+REM
+REM LICENSE
+REM
+REM This source file is subject to the new BSD license that is bundled
+REM with this package in the file LICENSE.
+REM It is also available through the world-wide-web at this URL:
+REM http://phergie.org/license
+
+set PHPBIN="@php_bin@"
+%PHPBIN% "@bin_dir@\phergie" %*
diff --git a/plugins/Irc/extlib/phergie/phergie.php b/plugins/Irc/extlib/phergie/phergie.php
new file mode 100755
index 000000000..f0b9f6ced
--- /dev/null
+++ b/plugins/Irc/extlib/phergie/phergie.php
@@ -0,0 +1,54 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Phergie
+ *
+ * PHP version 5
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.
+ * It is also available through the world-wide-web at this URL:
+ * http://phergie.org/license
+ *
+ * @category Phergie
+ * @package Phergie
+ * @author Phergie Development Team <team@phergie.org>
+ * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie
+ */
+
+/**
+ * @see Phergie_Autoload
+ */
+require 'Phergie/Autoload.php';
+Phergie_Autoload::registerAutoloader();
+
+$bot = new Phergie_Bot;
+
+if (!isset($argc)) {
+ echo
+ 'The PHP setting register_argc_argv must be enabled for Phergie ',
+ 'configuration files to be specified using command line arguments; ',
+ 'defaulting to Settings.php in the current working directory',
+ PHP_EOL;
+} else if ($argc > 0) {
+ // Skip the current file for manual installations
+ // ex: php phergie.php Settings.php
+ if (realpath($argv[0]) == __FILE__) {
+ array_shift($argv);
+ }
+
+ // If configuration files were specified, override default behavior
+ if (count($argv) > 0) {
+ $config = new Phergie_Config;
+ foreach ($argv as $file) {
+ $config->read($file);
+ }
+ $bot->setConfig($config);
+ }
+}
+
+$bot->run();
diff --git a/plugins/Irc/ircmanager.php b/plugins/Irc/ircmanager.php
new file mode 100644
index 000000000..606629331
--- /dev/null
+++ b/plugins/Irc/ircmanager.php
@@ -0,0 +1,357 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * IRC background connection manager for IRC-using queue handlers,
+ * allowing them to send outgoing messages on the right connection.
+ *
+ * Input is handled during socket select loop, 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 IRC enabled.
+ */
+
+class IrcManager extends ImManager {
+ protected $conn = null;
+ protected $lastPing = null;
+ protected $messageWaiting = true;
+ protected $lastMessage = null;
+
+ protected $regChecks = array();
+ protected $regChecksLookup = array();
+
+ protected $connected = false;
+
+ /**
+ * Initialize connection to server.
+ *
+ * @return boolean true on success
+ */
+ public function start($master) {
+ if (parent::start($master)) {
+ $this->connect();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return any open sockets that the run loop should listen
+ * for input on.
+ *
+ * @return array Array of socket resources
+ */
+ public function getSockets() {
+ $this->connect();
+ if ($this->conn) {
+ return $this->conn->getSockets();
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Request a maximum timeout for listeners before the next idle period.
+ *
+ * @return integer Maximum timeout
+ */
+ public function timeout() {
+ if ($this->messageWaiting) {
+ return 1;
+ } else {
+ return $this->plugin->pinginterval;
+ }
+ }
+
+ /**
+ * Idle processing for io manager's execution loop.
+ *
+ * @return void
+ */
+ public function idle() {
+ // Send a ping if necessary
+ if (empty($this->lastPing) || time() - $this->lastPing > $this->plugin->pinginterval) {
+ $this->sendPing();
+ }
+
+ if ($this->connected) {
+ // Send a waiting message if appropriate
+ if ($this->messageWaiting && time() - $this->lastMessage > 1) {
+ $wm = Irc_waiting_message::top();
+ if ($wm === NULL) {
+ $this->messageWaiting = false;
+ return;
+ }
+
+ $data = unserialize($wm->data);
+ $wm->incAttempts();
+
+ if ($this->send_raw_message($data)) {
+ $wm->delete();
+ } else {
+ if ($wm->attempts <= common_config('queue', 'max_retries')) {
+ // Try again next idle
+ $wm->releaseClaim();
+ } else {
+ // Exceeded the maximum number of retries
+ $wm->delete();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Process IRC events that have come in over the wire.
+ *
+ * @param resource $socket Socket to handle input on
+ * @return void
+ */
+ public function handleInput($socket) {
+ common_log(LOG_DEBUG, 'Servicing the IRC queue.');
+ $this->stats('irc_process');
+
+ try {
+ $this->conn->handleEvents();
+ } catch (Phergie_Driver_Exception $e) {
+ $this->connected = false;
+ $this->conn->reconnect();
+ }
+ }
+
+ /**
+ * Initiate connection
+ *
+ * @return void
+ */
+ public function connect() {
+ if (!$this->conn) {
+ $this->conn = new Phergie_StatusnetBot;
+
+ $config = new Phergie_Config;
+ $config->readArray(
+ array(
+ 'connections' => array(
+ array(
+ 'host' => $this->plugin->host,
+ 'port' => $this->plugin->port,
+ 'username' => $this->plugin->username,
+ 'realname' => $this->plugin->realname,
+ 'nick' => $this->plugin->nick,
+ 'password' => $this->plugin->password,
+ 'transport' => $this->plugin->transporttype,
+ 'encoding' => $this->plugin->encoding
+ )
+ ),
+
+ 'driver' => 'statusnet',
+
+ 'processor' => 'async',
+ 'processor.options' => array('sec' => 0, 'usec' => 0),
+
+ 'plugins' => array(
+ 'Pong',
+ 'NickServ',
+ 'AutoJoin',
+ 'Statusnet',
+ ),
+
+ 'plugins.autoload' => true,
+
+ // Uncomment to enable debugging output
+ //'ui.enabled' => true,
+
+ 'nickserv.password' => $this->plugin->nickservpassword,
+ 'nickserv.identify_message' => $this->plugin->nickservidentifyregexp,
+
+ 'autojoin.channels' => $this->plugin->channels,
+
+ 'statusnet.messagecallback' => array($this, 'handle_irc_message'),
+ 'statusnet.regcallback' => array($this, 'handle_reg_response'),
+ 'statusnet.connectedcallback' => array($this, 'handle_connected'),
+ 'statusnet.unregregexp' => $this->plugin->unregregexp,
+ 'statusnet.regregexp' => $this->plugin->regregexp
+ )
+ );
+
+ $this->conn->setConfig($config);
+ $this->conn->connect();
+ $this->lastPing = time();
+ $this->lastMessage = time();
+ }
+ return $this->conn;
+ }
+
+ /**
+ * Called via a callback when a message is received
+ * Passes it back to the queuing system
+ *
+ * @param array $data Data
+ * @return boolean
+ */
+ public function handle_irc_message($data) {
+ $this->plugin->enqueueIncomingRaw($data);
+ return true;
+ }
+
+ /**
+ * Called via a callback when NickServ responds to
+ * the bots query asking if a nick is registered
+ *
+ * @param array $data Data
+ * @return void
+ */
+ public function handle_reg_response($data) {
+ // Retrieve data
+ $screenname = $data['screenname'];
+ $nickdata = $this->regChecks[$screenname];
+ $usernick = $nickdata['user']->nickname;
+
+ if (isset($this->regChecksLookup[$usernick])) {
+ if ($data['registered']) {
+ // Send message
+ $this->plugin->sendConfirmationCode($screenname, $nickdata['code'], $nickdata['user'], true);
+ } else {
+ $this->plugin->sendMessage($screenname, _m('Your nickname is not registered so IRC connectivity cannot be enabled'));
+
+ $confirm = new Confirm_address();
+
+ $confirm->user_id = $user->id;
+ $confirm->address_type = $this->plugin->transport;
+
+ if ($confirm->find(true)) {
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ // TRANS: Server error thrown on database error canceling IM address confirmation.
+ $this->serverError(_('Couldn\'t delete confirmation.'));
+ return;
+ }
+ }
+ }
+
+ // Unset lookup value
+ unset($this->regChecksLookup[$usernick]);
+
+ // Unset data
+ unset($this->regChecks[$screename]);
+ }
+ }
+
+ /**
+ * Called when the connection is established
+ *
+ * @return void
+ */
+ public function handle_connected() {
+ $this->connected = true;
+ }
+
+ /**
+ * Enters a message into the database for sending when ready
+ *
+ * @param string $command Command
+ * @param array $args Arguments
+ * @return boolean
+ */
+ protected function enqueue_waiting_message($data) {
+ $wm = new Irc_waiting_message();
+
+ $wm->data = serialize($data);
+ $wm->prioritise = $data['prioritise'];
+ $wm->attempts = 0;
+ $wm->created = common_sql_now();
+ $result = $wm->insert();
+
+ if (!$result) {
+ common_log_db_error($wm, 'INSERT', __FILE__);
+ throw new ServerException('DB error inserting IRC waiting queue item');
+ }
+
+ return true;
+ }
+
+ /**
+ * Send a message using the daemon
+ *
+ * @param $data Message data
+ * @return boolean true on success
+ */
+ public function send_raw_message($data) {
+ $this->connect();
+ if (!$this->conn) {
+ return false;
+ }
+
+ if ($data['type'] != 'delayedmessage') {
+ if ($data['type'] != 'message') {
+ // Nick checking
+ $nickdata = $data['nickdata'];
+ $usernick = $nickdata['user']->nickname;
+ $screenname = $nickdata['screenname'];
+
+ // Cancel any existing checks for this user
+ if (isset($this->regChecksLookup[$usernick])) {
+ unset($this->regChecks[$this->regChecksLookup[$usernick]]);
+ }
+
+ $this->regChecks[$screenname] = $nickdata;
+ $this->regChecksLookup[$usernick] = $screenname;
+ }
+
+ // If there is a backlog or we need to wait, queue the message
+ if ($this->messageWaiting || time() - $this->lastMessage < 1) {
+ $this->enqueue_waiting_message(
+ array(
+ 'type' => 'delayedmessage',
+ 'prioritise' => $data['prioritise'],
+ 'data' => $data['data']
+ )
+ );
+ $this->messageWaiting = true;
+ return true;
+ }
+ }
+
+ try {
+ $this->conn->send($data['data']['command'], $data['data']['args']);
+ } catch (Phergie_Driver_Exception $e) {
+ $this->connected = false;
+ $this->conn->reconnect();
+ return false;
+ }
+
+ $this->lastMessage = time();
+ return true;
+ }
+
+ /**
+ * Sends a ping
+ *
+ * @return void
+ */
+ protected function sendPing() {
+ $this->lastPing = time();
+ $this->conn->send('PING', $this->lastPing);
+ }
+}
diff --git a/plugins/LdapCommon/LdapCommon.php b/plugins/LdapCommon/LdapCommon.php
index 159b2d265..579fe4b64 100644
--- a/plugins/LdapCommon/LdapCommon.php
+++ b/plugins/LdapCommon/LdapCommon.php
@@ -126,11 +126,11 @@ class LdapCommon
}
throw new Exception('Could not connect to LDAP server: '.$err->getMessage());
}
- $c = common_memcache();
+ $c = Cache::instance();
if (!empty($c)) {
$cacheObj = new MemcacheSchemaCache(
array('c'=>$c,
- 'cacheKey' => common_cache_key('ldap_schema:' . $config_id)));
+ 'cacheKey' => Cache::key('ldap_schema:' . $config_id)));
$ldap->registerSchemaCache($cacheObj);
}
self::$ldap_connections[$config_id] = $ldap;
diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php
index bd98026fe..b63cc8a55 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/Minify/MinifyPlugin.php b/plugins/Minify/MinifyPlugin.php
index b37531165..cfed0779b 100644
--- a/plugins/Minify/MinifyPlugin.php
+++ b/plugins/Minify/MinifyPlugin.php
@@ -118,9 +118,9 @@ class MinifyPlugin extends Plugin
function onStartInlineScriptElement($action,&$code,&$type)
{
if($this->minifyInlineJs && $type=='text/javascript'){
- $c = common_memcache();
+ $c = Cache::instance();
if (!empty($c)) {
- $cacheKey = common_cache_key(self::cacheKey . ':' . crc32($code));
+ $cacheKey = Cache::key(self::cacheKey . ':' . crc32($code));
$out = $c->get($cacheKey);
}
if(empty($out)) {
@@ -138,9 +138,9 @@ class MinifyPlugin extends Plugin
function onStartStyleElement($action,&$code,&$type,&$media)
{
if($this->minifyInlineCss && $type=='text/css'){
- $c = common_memcache();
+ $c = Cache::instance();
if (!empty($c)) {
- $cacheKey = common_cache_key(self::cacheKey . ':' . crc32($code));
+ $cacheKey = Cache::key(self::cacheKey . ':' . crc32($code));
$out = $c->get($cacheKey);
}
if(empty($out)) {
diff --git a/plugins/Minify/minify.php b/plugins/Minify/minify.php
index 9a59c4223..e012a4027 100644
--- a/plugins/Minify/minify.php
+++ b/plugins/Minify/minify.php
@@ -74,9 +74,9 @@ class MinifyAction extends Action
{
parent::handle($args);
- $c = common_memcache();
+ $c = Cache::instance();
if (!empty($c)) {
- $cacheKey = common_cache_key(MinifyPlugin::cacheKey . ':' . $this->file . '?v=' . empty($this->v)?'':$this->v);
+ $cacheKey = Cache::key(MinifyPlugin::cacheKey . ':' . $this->file . '?v=' . empty($this->v)?'':$this->v);
$out = $c->get($cacheKey);
}
if(empty($out)) {
diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php
new file mode 100644
index 000000000..187486eed
--- /dev/null
+++ b/plugins/Msn/MsnPlugin.php
@@ -0,0 +1,216 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Send and receive notices using the MSN network
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category IM
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 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 phpmsnclass library...
+set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phpmsnclass');
+
+/**
+ * Plugin for MSN
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class MsnPlugin extends ImPlugin {
+ public $user = null;
+ public $password = null;
+ public $nickname = null;
+ public $transport = 'msn';
+
+ /**
+ * Get the internationalized/translated display name of this IM service
+ *
+ * @return string Name of service
+ */
+ public function getDisplayName() {
+ return _m('MSN');
+ }
+
+ /**
+ * Normalize a screenname for comparison
+ *
+ * @param string $screenname screenname to normalize
+ * @return string an equivalent screenname in normalized form
+ */
+ public function normalize($screenname) {
+ $screenname = str_replace(" ","", $screenname);
+ return strtolower($screenname);
+ }
+
+ /**
+ * Get the screenname of the daemon that sends and receives messages
+ *
+ * @return string Screenname
+ */
+ public function daemonScreenname() {
+ return $this->user;
+ }
+
+ /**
+ * Validate (ensure the validity of) a screenname
+ *
+ * @param string $screenname screenname to validate
+ * @return boolean
+ */
+ public function validate($screenname) {
+ return Validate::email($screenname, common_config('email', 'check_domain'));
+ }
+
+ /**
+ * 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.
+ */
+ public function onAutoload($cls) {
+ $dir = dirname(__FILE__);
+
+ switch ($cls) {
+ case 'MSN':
+ require_once(INSTALLDIR.'/plugins/Msn/extlib/phpmsnclass/msn.class.php');
+ return false;
+ case 'MsnManager':
+ case 'Msn_waiting_message':
+ include_once $dir . '/'.strtolower($cls).'.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /*
+ * Start manager on daemon start
+ *
+ * @return boolean
+ */
+ public function onStartImDaemonIoManagers(&$classes) {
+ parent::onStartImDaemonIoManagers(&$classes);
+ $classes[] = new MsnManager($this); // handles sending/receiving
+ return true;
+ }
+
+ /**
+ * Ensure the database table is present
+ *
+ */
+ public function onCheckSchema() {
+ $schema = Schema::get();
+
+ // For storing messages while sessions become ready
+ $schema->ensureTable('msn_waiting_message',
+ array(new ColumnDef('id', 'integer', null,
+ false, 'PRI', null, null, true),
+ new ColumnDef('screenname', 'varchar', 255, false),
+ new ColumnDef('message', 'text', null, false),
+ new ColumnDef('created', 'datetime', null, false),
+ new ColumnDef('claimed', 'datetime')));
+
+ return true;
+ }
+
+ /**
+ * Get a microid URI for the given screenname
+ *
+ * @param string $screenname
+ * @return string microid URI
+ */
+ public function microiduri($screenname) {
+ return 'msnim:' . $screenname;
+ }
+
+ /**
+ * Send a message to a given screenname
+ *
+ * @param string $screenname Screenname to send to
+ * @param string $body Text to send
+ * @return boolean success value
+ */
+ public function sendMessage($screenname, $body) {
+ $this->enqueueOutgoingRaw(array('to' => $screenname, 'message' => $body));
+ return true;
+ }
+
+ /**
+ * Accept a queued input message.
+ *
+ * @param array $data Data
+ * @return true if processing completed, false if message should be reprocessed
+ */
+ public function receiveRawMessage($data) {
+ $this->handleIncoming($data['sender'], $data['message']);
+ return true;
+ }
+
+ /**
+ * Initialize plugin
+ *
+ * @return boolean
+ */
+ public function initialize() {
+ if (!isset($this->user)) {
+ throw new Exception("Must specify a user");
+ }
+ if (!isset($this->password)) {
+ throw new Exception("Must specify a password");
+ }
+ if (!isset($this->nickname)) {
+ throw new Exception("Must specify a nickname");
+ }
+
+ return true;
+ }
+
+ /**
+ * Get plugin information
+ *
+ * @param array $versions array to insert information into
+ * @return void
+ */
+ public function onPluginVersion(&$versions) {
+ $versions[] = array(
+ 'name' => 'MSN',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Luke Fitzgerald',
+ 'homepage' => 'http://status.net/wiki/Plugin:MSN',
+ 'rawdescription' =>
+ _m('The MSN plugin allows users to send and receive notices over the MSN network.')
+ );
+ return true;
+ }
+}
diff --git a/plugins/Msn/README b/plugins/Msn/README
new file mode 100644
index 000000000..fa7bc2fb2
--- /dev/null
+++ b/plugins/Msn/README
@@ -0,0 +1,32 @@
+The MSN plugin allows users to send and receive notices over the MSN network.
+
+Required PHP extensions:
+curl pcre mhash mcrypt bcmath
+
+Installation
+============
+add "addPlugin('msn',
+ array('setting'=>'value', 'setting2'=>'value2', ...);"
+to the bottom of your config.php
+
+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.
+
+Settings
+========
+user*: username (screenname) to use when logging into MSN
+password*: password for that user
+nickname*: nickname for the bot
+
+* required
+default values are in (parenthesis)
+
+Example
+=======
+addPlugin('msn', array(
+ 'user' => '...',
+ 'password' => '...',
+ 'nickname' => '...'
+));
+
diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php
new file mode 100644
index 000000000..996c5571c
--- /dev/null
+++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php
@@ -0,0 +1,3210 @@
+<?php
+/*
+
+phpmsnclass ver 2.0s
+Luke Fitzgerald <lw.fitzgerald@googlemail.com>
+
+Based on MSN class ver 2.0 by Tommy Wu, Ricky Su
+License: GPL
+
+Documentation on the MSN protocol can be found at: http://msnpiki.msnfanatic.com/index.php/Main_Page
+
+This class uses MSNP15.
+
+In addition to PHP5, the additional php modules required are:
+curl pcre mcrypt bcmath
+
+*/
+
+class MSN {
+ const PROTOCOL = 'MSNP15';
+ const PASSPORT_URL = 'https://login.live.com/RST.srf';
+ const BUILDVER = '8.1.0178';
+ const PROD_KEY = 'PK}_A_0N_K%O?A9S';
+ const PROD_ID = 'PROD0114ES4Z%Q5W';
+ const LOGIN_METHOD = 'SSO';
+
+ const OIM_SEND_URL = 'https://ows.messenger.msn.com/OimWS/oim.asmx';
+ const OIM_SEND_SOAP = 'http://messenger.live.com/ws/2006/09/oim/Store2';
+
+ const OIM_MAILDATA_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+ const OIM_MAILDATA_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata';
+ const OIM_READ_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+ const OIM_READ_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage';
+ const OIM_DEL_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+ const OIM_DEL_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages';
+
+ const MEMBERSHIP_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';
+ const MEMBERSHIP_SOAP = 'http://www.msn.com/webservices/AddressBook/FindMembership';
+
+ const ADDMEMBER_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';
+ const ADDMEMBER_SOAP = 'http://www.msn.com/webservices/AddressBook/AddMember';
+
+ const DELMEMBER_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';
+ const DELMEMBER_SOAP = 'http://www.msn.com/webservices/AddressBook/DeleteMember';
+
+ // the message length (include header) is limited (maybe since WLM 8.5 released)
+ // for WLM: 1664 bytes
+ // for YIM: 518 bytes
+ const MAX_MSN_MESSAGE_LEN = 1664;
+ const MAX_YAHOO_MESSAGE_LEN = 518;
+
+ private $debug;
+ private $timeout;
+
+ private $id;
+ private $ticket;
+ private $user = '';
+ private $password = '';
+ private $NSfp = false;
+ private $passport_policy = '';
+ private $alias;
+ private $psm;
+ private $retry_wait;
+ private $update_pending;
+ private $PhotoStickerFile = false;
+ private $Emotions = false;
+ private $XFRReqTimeout = 60;
+ private $SBStreamTimeout = 2;
+ private $MsnObjArray = array();
+ private $MsnObjMap = array();
+ private $ABAuthHeader;
+ private $ABService;
+ private $Contacts;
+
+ private $server = 'messenger.hotmail.com';
+ private $port = 1863;
+
+ private $clientid = '';
+
+ private $error = '';
+
+ private $authed = false;
+
+ private $oim_try = 3;
+
+ private $font_fn = 'Arial';
+ private $font_co = '333333';
+ private $font_ef = '';
+
+ // Begin added for StatusNet
+
+ private $aContactList = array();
+ private $aADL = array();
+
+ /**
+ * Holds session information indexed by screenname if
+ * session has no socket or socket if socket present
+ *
+ * @var array
+ */
+ private $switchBoardSessions = array();
+
+ /**
+ * Holds sockets indexed by screenname
+ *
+ * @var array
+ */
+ private $switchBoardSessionLookup = array();
+
+ /**
+ * Holds references to sessions waiting for XFR
+ *
+ * @var array
+ */
+ private $waitingForXFR = array();
+
+ /**
+ * Event Handler Functions
+ */
+ private $myEventHandlers = array();
+
+ // End added for StatusNet
+
+ /**
+ * Constructor method
+ *
+ * @param array $Configs Array of configuration options
+ * 'user' - Username
+ * 'password' - Password
+ * 'alias' - Bot nickname
+ * 'psm' - Bot personal status message
+ * 'retry_wait' - Time to wait before trying to reconnect
+ * 'update_pending' - Whether to update pending contacts
+ * 'PhotoSticker' - Photo file to use (?)
+ * 'debug' - Enable/Disable debugging mode
+ * @param integer $timeout Connection timeout
+ * @param integer $client_id Client id (hexadecimal)
+ * @return MSN
+ */
+ public function __construct ($Configs = array(), $timeout = 15, $client_id = 0x7000800C) {
+ $this->user = $Configs['user'];
+ $this->password = $Configs['password'];
+ $this->alias = isset($Configs['alias']) ? $Configs['alias'] : '';
+ $this->psm = isset($Configs['psm']) ? $Configs['psm'] : '';
+ $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30;
+ $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true;
+ $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false;
+
+ if ($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) {
+ foreach($this->Emotions as $EmotionFilePath)
+ $this->MsnObj($EmotionFilePath,$Type=2);
+ }
+ $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false;
+ $this->timeout = $timeout;
+
+ // Check support
+ if (!function_exists('curl_init')) throw new Exception("curl module not found!\n");
+ if (!function_exists('preg_match')) throw new Exception("pcre module not found!\n");
+ if (!function_exists('mcrypt_cbc')) throw new Exception("mcrypt module not found!\n");
+ if (!function_exists('bcmod')) throw new Exception("bcmath module not found!\n");
+
+ /*
+ http://msnpiki.msnfanatic.com/index.php/Client_ID
+ Client ID for MSN:
+ normal MSN 8.1 clientid is:
+ 01110110 01001100 11000000 00101100
+ = 0x764CC02C
+
+ we just use following:
+ * 0x04: Your client can send/receive Ink (GIF format)
+ * 0x08: Your client can send/recieve Ink (ISF format)
+ * 0x8000: This means you support Winks receiving (If not set the official Client will warn with 'contact has an older client and is not capable of receiving Winks')
+ * 0x70000000: This is the value for MSNC7 (WL Msgr 8.1)
+ = 0x7000800C;
+ */
+ $this->clientid = $client_id;
+ $this->ABService = new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl', array('trace' => 1));
+ }
+
+ /**
+ * Signon methods
+ */
+
+ /**
+ * Connect to the NS server
+ *
+ * @param String $user Username
+ * @param String $password Password
+ * @param String $redirect_server Redirect server
+ * @param Integer $redirect_port Redirect port
+ * @return Boolean Returns true if successful
+ */
+ private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) {
+ $this->id = 1;
+ if ($redirect_server === '') {
+ $this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
+ if (!$this->NSfp) {
+ $this->error = "!!! Could not connect to $this->server:$this->port, error => $errno, $errstr";
+ return false;
+ }
+ }
+ else {
+ $this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, $this->timeout);
+ if (!$this->NSfp) {
+ $this->error = "!!! Could not connect to $redirect_server:$redirect_port, error => $errno, $errstr";
+ return false;
+ }
+ }
+ $this->authed = false;
+ // MSNP9
+ // NS: >> VER {id} MSNP9 CVR0
+ // MSNP15
+ // NS: >>> VER {id} MSNP15 CVR0
+ $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0');
+
+ $start_tm = time();
+ while (!self::socketcheck($this->NSfp)) {
+ $data = $this->ns_readln();
+ // no data?
+ if ($data === false) {
+ // logout now
+ // NS: >>> OUT
+ $this->ns_writeln("OUT");
+ @fclose($this->NSfp);
+ $this->error = 'Timeout, maybe protocol changed!';
+ return false;
+ }
+
+ $code = substr($data, 0, 3);
+ $start_tm = time();
+
+ switch ($code) {
+ case 'VER':
+ // MSNP9
+ // NS: <<< VER {id} MSNP9 CVR0
+ // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 6.0.0602 msmsgs {user}
+ // MSNP15
+ // NS: <<< VER {id} MSNP15 CVR0
+ // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user}
+ $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS ".self::BUILDVER." msmsgs $user");
+ break;
+
+ case 'CVR':
+ // MSNP9
+ // NS: <<< CVR {id} {ver_list} {download_serve} ....
+ // NS: >>> USR {id} TWN I {user}
+ // MSNP15
+ // NS: <<< CVR {id} {ver_list} {download_serve} ....
+ // NS: >>> USR {id} SSO I {user}
+ $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." I $user");
+ break;
+
+ case 'USR':
+ // already login for passport site, finish the login process now.
+ // NS: <<< USR {id} OK {user} {verify} 0
+ if ($this->authed) return true;
+ // max. 16 digits for password
+ if (strlen($password) > 16)
+ $password = substr($password, 0, 16);
+
+ $this->user = $user;
+ $this->password = $password;
+ // NS: <<< USR {id} SSO S {policy} {nonce}
+ @list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce) = @explode(' ', $data);
+
+ $this->passport_policy = $policy;
+ $aTickets = $this->get_passport_ticket();
+ if (!$aTickets || !is_array($aTickets)) {
+ // logout now
+ // NS: >>> OUT
+ $this->ns_writeln("OUT");
+ @fclose($this->NSfp);
+ $this->error = 'Passport authentication failed!';
+ return false;
+ }
+
+ $ticket = $aTickets['ticket'];
+ $secret = $aTickets['secret'];
+ $this->ticket = $aTickets;
+ $login_code = $this->generateLoginBLOB($secret, $nonce);
+
+ // NS: >>> USR {id} SSO S {ticket} {login_code}
+ $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." S $ticket $login_code");
+ $this->authed = true;
+ break;
+
+ case 'XFR':
+ // main login server will redirect to anther NS after USR command
+ // MSNP9
+ // NS: <<< XFR {id} NS {server} 0 {server}
+ // MSNP15
+ // NS: <<< XFR {id} NS {server} U D
+ @list(/* XFR */, /* id */, $Type, $server) = @explode(' ', $data);
+ if ($Type!='NS') break;
+ @list($ip, $port) = @explode(':', $server);
+ // this connection will close after XFR
+ @fclose($this->NSfp);
+
+ $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);
+ if (!$this->NSfp) {
+ $this->error = "Can't connect to $ip:$port, error => $errno, $errstr";
+ return false;
+ }
+
+ // MSNP9
+ // NS: >> VER {id} MSNP9 CVR0
+ // MSNP15
+ // NS: >>> VER {id} MSNP15 CVR0
+ $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0');
+ break;
+
+ case 'GCF':
+ // return some policy data after 'USR {id} SSO I {user}' command
+ // NS: <<< GCF 0 {size}
+ @list(/* GCF */, /* 0 */, $size) = @explode(' ', $data);
+ // we don't need the data, just read it and drop
+ if (is_numeric($size) && $size > 0)
+ $this->ns_readdata($size);
+ break;
+
+ default:
+ // we'll quit if got any error
+ if (is_numeric($code)) {
+ // logout now
+ // NS: >>> OUT
+ $this->ns_writeln("OUT");
+ @fclose($this->NSfp);
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+ return false;
+ }
+ // unknown response from server, just ignore it
+ break;
+ }
+ }
+ // never goto here
+ }
+
+ /**
+ * Sign onto the NS server and retrieve the address book
+ *
+ * @return void
+ */
+ public function signon() {
+ /* FIXME Don't implement the signon as a loop or we could hang
+ * the queue handler! */
+ $this->debug_message('*** Trying to connect to MSN network');
+
+ // Remove any remaining switchboard sessions
+ $this->switchBoardSessions = array();
+ $this->switchBoardSessionLookup = array();
+
+ while (true) {
+ // Connect
+ if (!$this->connect($this->user, $this->password)) {
+ $this->signonFailure("!!! Could not connect to server: $this->error");
+ continue;
+ }
+
+ // Update contacts
+ if ($this->UpdateContacts() === false) {
+ $this->signonFailure('');
+ continue;
+ }
+
+ // Get membership lists
+ if (($this->aContactList = $this->getMembershipList()) === false) {
+ $this->signonFailure('!!! Get membership list failed');
+ continue;
+ }
+
+ if ($this->update_pending) {
+ if (is_array($this->aContactList)) {
+ $pending = 'Pending';
+ foreach ($this->aContactList as $u_domain => $aUserList) {
+ foreach ($aUserList as $u_name => $aNetworks) {
+ foreach ($aNetworks as $network => $aData) {
+ if (isset($aData[$pending])) {
+ // pending list
+ $cnt = 0;
+ foreach (array('Allow', 'Reverse') as $list) {
+ if (isset($aData[$list]))
+ $cnt++;
+ else {
+ if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;
+ $cnt++;
+ }
+ }
+ }
+ if ($cnt >= 2) {
+ $id = $aData[$pending];
+ // we can delete it from pending now
+ if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))
+ unset($this->aContactList[$u_domain][$u_name][$network][$pending]);
+ }
+ }
+ else {
+ // sync list
+ foreach (array('Allow', 'Reverse') as $list) {
+ if (!isset($aData[$list])) {
+ if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ $n = 0;
+ $sList = '';
+ $len = 0;
+ if (is_array($this->aContactList)) {
+ foreach ($this->aContactList as $u_domain => $aUserList) {
+ $str = '<d n="'.$u_domain.'">';
+ $len += strlen($str);
+ if ($len > 7400) {
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+ $n++;
+ $sList = '';
+ $len = strlen($str);
+ }
+ $sList .= $str;
+ foreach ($aUserList as $u_name => $aNetworks) {
+ foreach ($aNetworks as $network => $status) {
+ $str = '<c n="'.$u_name.'" l="3" t="'.$network.'" />';
+ $len += strlen($str);
+ // max: 7500, but <ml l="1"></d></ml> is 19,
+ // so we use 7475
+ if ($len > 7475) {
+ $sList .= '</d>';
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+ $n++;
+ $sList = '<d n="'.$u_domain.'">'.$str;
+ $len = strlen($sList);
+ }
+ else
+ $sList .= $str;
+ }
+ }
+ $sList .= '</d>';
+ }
+ }
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+ // NS: >>> BLP {id} BL
+ $this->ns_writeln("BLP $this->id BL");
+ foreach ($this->aADL as $str) {
+ $len = strlen($str);
+ // NS: >>> ADL {id} {size}
+ $this->ns_writeln("ADL $this->id $len");
+ $this->ns_writedata($str);
+ }
+ // NS: >>> PRP {id} MFN name
+ if ($this->alias == '') $this->alias = $user;
+ $aliasname = rawurlencode($this->alias);
+ $this->ns_writeln("PRP $this->id MFN $aliasname");
+ //設定個人大頭貼
+ //$MsnObj=$this->PhotoStckObj();
+ // NS: >>> CHG {id} {status} {clientid} {msnobj}
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");
+ if ($this->PhotoStickerFile !== false)
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
+ // NS: >>> UUX {id} length
+ $str = '<Data><PSM>'.htmlspecialchars($this->psm).'</PSM><CurrentMedia></CurrentMedia><MachineGuid></MachineGuid></Data>';
+ $len = strlen($str);
+ $this->ns_writeln("UUX $this->id $len");
+ $this->ns_writedata($str);
+ if (!self::socketcheck($this->NSfp)) {
+ $this->debug_message('*** Connected, waiting for commands');
+ break;
+ } else {
+ $this->NSRetryWait($this->retry_wait);
+ }
+ }
+ }
+
+ /**
+ * Called if there is an error during signon
+ *
+ * @param string $message Error message to log
+ * @return void
+ */
+ private function signonFailure($message) {
+ if(!empty($message)) {
+ $this->debug_message($message);
+ }
+ $this->callHandler('ConnectFailed', $message);
+ $this->NSRetryWait($this->retry_wait);
+ }
+
+ /**
+ * Log out and close the NS connection
+ *
+ * @return void
+ */
+ private function nsLogout() {
+ if (is_resource($this->NSfp) && !feof($this->NSfp)) {
+ // logout now
+ // NS: >>> OUT
+ $this->ns_writeln("OUT");
+ fclose($this->NSfp);
+ $this->NSfp = false;
+ $this->debug_message("*** Logged out");
+ }
+ }
+
+ /**
+ * NS and SB command handling methods
+ */
+
+ /**
+ * Read and handle incoming command from NS
+ *
+ * @return void
+ */
+ private function nsReceive() {
+ // Sign in again if not signed in or socket failed
+ if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) {
+ $this->callHandler('Reconnect');
+ $this->NSRetryWait($this->retry_wait);
+ $this->signon();
+ return;
+ }
+
+ $data = $this->ns_readln();
+ if ($data === false) {
+ // There was no data / an error when reading from the socket so reconnect
+ $this->callHandler('Reconnect');
+ $this->NSRetryWait($this->retry_wait);
+ $this->signon();
+ return;
+ }
+
+ switch (substr($data, 0, 3)) {
+ case 'SBS':
+ // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us
+ // NS: <<< SBS 0 null
+ break;
+
+ case 'RFS':
+ // FIXME:
+ // NS: <<< RFS ???
+ // refresh ADL, so we re-send it again
+ if (is_array($this->aADL)) {
+ foreach ($this->aADL as $str) {
+ $len = strlen($str);
+ // NS: >>> ADL {id} {size}
+ $this->ns_writeln("ADL $this->id $len");
+ $this->ns_writedata($str);
+ }
+ }
+ break;
+
+ case 'LST':
+ // NS: <<< LST {email} {alias} 11 0
+ @list(/* LST */, $email) = @explode(' ', $data);
+ @list($u_name, $u_domain) = @explode('@', $email);
+ if (!isset($this->aContactList[$u_domain][$u_name][1])) {
+ $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';
+ $this->debug_message("*** Added to contact list: $u_name@$u_domain");
+ }
+ break;
+
+ case 'ADL':
+ // randomly, we get ADL command, someone add us to their contact list for MSNP15
+ // NS: <<< ADL 0 {size}
+ @list(/* ADL */, /* 0 */, $size) = @explode(' ', $data);
+ if (is_numeric($size) && $size > 0) {
+ $data = $this->ns_readdata($size);
+ preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);
+ if (is_array($matches) && count($matches) > 0) {
+ $u_domain = $matches[1];
+ $u_name = $matches[2];
+ $network = $matches[4];
+ if (isset($this->aContactList[$u_domain][$u_name][$network]))
+ $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain");
+ else {
+ $re_login = false;
+ $cnt = 0;
+ foreach (array('Allow', 'Reverse') as $list) {
+ if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+ if ($re_login) {
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");
+ continue;
+ }
+ $aTickets = $this->get_passport_ticket();
+ if (!$aTickets || !is_array($aTickets)) {
+ // failed to login? ignore it
+ $this->debug_message("*** Could not re-login, something wrong here");
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");
+ continue;
+ }
+ $re_login = true;
+ $this->ticket = $aTickets;
+ $this->debug_message("**** Got new ticket, trying again");
+ if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");
+ continue;
+ }
+ }
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;
+ $cnt++;
+ }
+ $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain");
+ }
+ $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="3" t="'.$network.'" /></d></ml>';
+ $len = strlen($str);
+
+ $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));
+ }
+ else
+ $this->debug_message("*** Someone added us to their list: $data");
+ }
+ break;
+
+ case 'RML':
+ // randomly, we get RML command, someome remove us to their contact list for MSNP15
+ // NS: <<< RML 0 {size}
+ @list(/* RML */, /* 0 */, $size) = @explode(' ', $data);
+ if (is_numeric($size) && $size > 0) {
+ $data = $this->ns_readdata($size);
+ preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);
+ if (is_array($matches) && count($matches) > 0) {
+ $u_domain = $matches[1];
+ $u_name = $matches[2];
+ $network = $matches[4];
+ if (isset($this->aContactList[$u_domain][$u_name][$network])) {
+ $aData = $this->aContactList[$u_domain][$u_name][$network];
+
+ foreach ($aData as $list => $id)
+ $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list);
+
+ unset($this->aContactList[$u_domain][$u_name][$network]);
+ $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain");
+ }
+ else
+ $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain");
+
+ $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));
+ }
+ else
+ $this->debug_message("*** Someone removed us from their list: $data");
+ }
+ break;
+
+ case 'MSG':
+ // randomly, we get MSG notification from server
+ // NS: <<< MSG Hotmail Hotmail {size}
+ @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size) = @explode(' ', $data);
+ if (is_numeric($size) && $size > 0) {
+ $data = $this->ns_readdata($size);
+ $aLines = @explode("\n", $data);
+ $header = true;
+ $ignore = false;
+ $maildata = '';
+ foreach ($aLines as $line) {
+ $line = rtrim($line);
+ if ($header) {
+ if ($line === '') {
+ $header = false;
+ continue;
+ }
+ if (strncasecmp($line, 'Content-Type:', 13) == 0) {
+ if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) {
+ // we just need text/x-msmsgsinitialmdatanotification
+ // or text/x-msmsgsoimnotification
+ $ignore = true;
+ break;
+ }
+ }
+ continue;
+ }
+ if (strncasecmp($line, 'Mail-Data:', 10) == 0) {
+ $maildata = trim(substr($line, 10));
+ break;
+ }
+ }
+ if ($ignore) {
+ $this->debug_message("*** Ignoring MSG for: $line");
+ break;
+ }
+ if ($maildata == '') {
+ $this->debug_message("*** Ignoring MSG not for OIM");
+ break;
+ }
+ $re_login = false;
+ if (strcasecmp($maildata, 'too-large') == 0) {
+ $this->debug_message("*** Large mail-data, need to get the data via SOAP");
+ $maildata = $this->getOIM_maildata();
+ if ($maildata === false) {
+ $this->debug_message("*** Could not get mail-data via SOAP");
+
+ // maybe we need to re-login again
+ $aTickets = $this->get_passport_ticket();
+ if (!$aTickets || !is_array($aTickets)) {
+ // failed to login? ignore it
+ $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");
+ break;
+ }
+ $re_login = true;
+ $this->ticket = $aTickets;
+ $this->debug_message("*** Got new ticket, trying again");
+ $maildata = $this->getOIM_maildata();
+ if ($maildata === false) {
+ $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM");
+ break;
+ }
+ }
+ }
+ // could be a lots of <M>...</M>, so we can't use preg_match here
+ $p = $maildata;
+ $aOIMs = array();
+ while (1) {
+ $start = strpos($p, '<M>');
+ $end = strpos($p, '</M>');
+ if ($start === false || $end === false || $start > $end) break;
+ $end += 4;
+ $sOIM = substr($p, $start, $end - $start);
+ $aOIMs[] = $sOIM;
+ $p = substr($p, $end);
+ }
+ if (count($aOIMs) == 0) {
+ $this->debug_message("*** Ignoring empty OIM");
+ break;
+ }
+ foreach ($aOIMs as $maildata) {
+ // T: 11 for MSN, 13 for Yahoo
+ // S: 6 for MSN, 7 for Yahoo
+ // RT: the datetime received by server
+ // RS: already read or not
+ // SZ: size of message
+ // E: sender
+ // I: msgid
+ // F: always 00000000-0000-0000-0000-000000000009
+ // N: sender alias
+ preg_match('#<T>(.*)</T>#', $maildata, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message("*** Ignoring OIM maildata without <T>type</T>");
+ continue;
+ }
+ $oim_type = $matches[1];
+ if ($oim_type = 13)
+ $network = 32;
+ else
+ $network = 1;
+ preg_match('#<E>(.*)</E>#', $maildata, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message("*** Ignoring OIM maildata without <E>sender</E>");
+ continue;
+ }
+ $oim_sender = $matches[1];
+ preg_match('#<I>(.*)</I>#', $maildata, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message("*** Ignoring OIM maildata without <I>msgid</I>");
+ continue;
+ }
+ $oim_msgid = $matches[1];
+ preg_match('#<SZ>(.*)</SZ>#', $maildata, $matches);
+ $oim_size = (count($matches) == 0) ? 0 : $matches[1];
+ preg_match('#<RT>(.*)</RT>#', $maildata, $matches);
+ $oim_time = (count($matches) == 0) ? 0 : $matches[1];
+ $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");
+ $sMsg = $this->getOIM_message($oim_msgid);
+ if ($sMsg === false) {
+ $this->debug_message("*** Could not get OIM, msgid = $oim_msgid");
+ if ($re_login) {
+ $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");
+ continue;
+ }
+ $aTickets = $this->get_passport_ticket();
+ if (!$aTickets || !is_array($aTickets)) {
+ // failed to login? ignore it
+ $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");
+ continue;
+ }
+ $re_login = true;
+ $this->ticket = $aTickets;
+ $this->debug_message("*** get new ticket, try it again");
+ $sMsg = $this->getOIM_message($oim_msgid);
+ if ($sMsg === false) {
+ $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");
+ continue;
+ }
+ }
+ $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");
+ $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true));
+ }
+ }
+ break;
+
+ case 'UBM':
+ // randomly, we get UBM, this is the message from other network, like Yahoo!
+ // NS: <<< UBM {email} $network $type {size}
+ @list(/* UBM */, $from_email, $network, $type, $size) = @explode(' ', $data);
+ if (is_numeric($size) && $size > 0) {
+ $data = $this->ns_readdata($size);
+ $aLines = @explode("\n", $data);
+ $header = true;
+ $ignore = false;
+ $sMsg = '';
+ foreach ($aLines as $line) {
+ $line = rtrim($line);
+ if ($header) {
+ if ($line === '') {
+ $header = false;
+ continue;
+ }
+ if (strncasecmp($line, 'TypingUser:', 11) == 0) {
+ $ignore = true;
+ break;
+ }
+ continue;
+ }
+ $aSubLines = @explode("\r", $line);
+ foreach ($aSubLines as $str) {
+ if ($sMsg !== '')
+ $sMsg .= "\n";
+ $sMsg .= $str;
+ }
+ }
+ if ($ignore) {
+ $this->debug_message("*** Ignoring message from $from_email: $line");
+ break;
+ }
+ $this->debug_message("*** MSG from $from_email (network: $network): $sMsg");
+ $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false));
+ }
+ break;
+
+ case 'UBX':
+ // randomly, we get UBX notification from server
+ // NS: <<< UBX email {network} {size}
+ @list(/* UBX */, /* email */, /* network */, $size) = @explode(' ', $data);
+ // we don't need the notification data, so just ignore it
+ if (is_numeric($size) && $size > 0)
+ $this->ns_readdata($size);
+ break;
+
+ case 'CHL':
+ // randomly, we'll get challenge from server
+ // NS: <<< CHL 0 {code}
+ @list(/* CHL */, /* 0 */, $chl_code) = @explode(' ', $data);
+ $fingerprint = $this->getChallenge($chl_code);
+ // NS: >>> QRY {id} {product_id} 32
+ // NS: >>> fingerprint
+ $this->ns_writeln("QRY $this->id ".self::PROD_ID.' 32');
+ $this->ns_writedata($fingerprint);
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");
+ if ($this->PhotoStickerFile !== false)
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
+ break;
+ case 'CHG':
+ // NS: <<< CHG {id} {status} {code}
+ // ignore it
+ // change our status to online first
+ break;
+
+ case 'XFR':
+ // sometimes, NS will redirect to another NS
+ // MSNP9
+ // NS: <<< XFR {id} NS {server} 0 {server}
+ // MSNP15
+ // NS: <<< XFR {id} NS {server} U D
+ // for normal switchboard XFR
+ // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0
+ @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code) = @explode(' ', $data);
+ @list($ip, $port) = @explode(':', $server);
+ if ($server_type != 'SB') {
+ // maybe exit?
+ // this connection will close after XFR
+ $this->nsLogout();
+ continue;
+ }
+
+ $this->debug_message("NS: <<< XFR SB");
+ $session = array_shift($this->waitingForXFR);
+ $this->connectToSBSession('Active', $ip, $port, $session['to'], array('cki' => $cki_code));
+ break;
+ case 'QNG':
+ // NS: <<< QNG {time}
+ @list(/* QNG */, $ping_wait) = @explode(' ', $data);
+ $this->callHandler('Pong', $ping_wait);
+ break;
+
+ case 'RNG':
+ if ($this->PhotoStickerFile !== false)
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
+ else
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");
+ // someone is trying to talk to us
+ // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0
+ $this->debug_message("NS: <<< RNG $data");
+ @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name) = @explode(' ', $data);
+ @list($sb_ip, $sb_port) = @explode(':', $server);
+ $this->debug_message("*** RING from $email, $sb_ip:$sb_port");
+ $this->addContact($email, 1, $email, true);
+ $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket));
+ break;
+
+ case 'NLN':
+ // NS: <<< NLN {status} {email} {networkid} {nickname} {clientid} {dpobj}
+ @list(/* NLN */, $status, $email, $network, $nickname) = @explode(' ', $data);
+ $this->callHandler('StatusChange', array('screenname' => $email, 'status' => $status, 'network' => $network, 'nickname' => $nickname));
+ break;
+
+ case 'OUT':
+ // force logout from NS
+ // NS: <<< OUT xxx
+ $this->debug_message("*** LOGOUT from NS");
+ return $this->nsLogout();
+
+ default:
+ $code = substr($data,0,3);
+ if (is_numeric($code)) {
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+ $this->debug_message("*** NS: $this->error");
+
+ return $this->nsLogout();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Read and handle incoming command/message from
+ * a switchboard session socket
+ */
+ private function sbReceive($socket) {
+ $intsocket = (int) $socket;
+ $session = &$this->switchBoardSessions[$intsocket];
+
+ if (feof($socket)) {
+ // Unset session lookup value
+ unset($this->switchBoardSessionLookup[$session['to']]);
+
+ // Unset session itself
+ unset($this->switchBoardSessions[$intsocket]);
+ return;
+ }
+
+ $id = &$session['id'];
+
+ $data = $this->sb_readln($socket);
+ $code = substr($data, 0, 3);
+ switch($code) {
+ case 'IRO':
+ // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}
+ @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);
+ $this->debug_message("*** $email joined session");
+ if ($email == $session['to']) {
+ $session['joined'] = true;
+ $this->callHandler('SessionReady', array('to' => $email));
+ }
+ break;
+ case 'BYE':
+ $this->debug_message("*** Quit for BYE");
+ $this->endSBSession($socket);
+ break;
+ case 'USR':
+ // SB: <<< USR {id} OK {user} {alias}
+ // we don't need the data, just ignore it
+ // request user to join this switchboard
+ // SB: >>> CAL {id} {user}
+ $this->sb_writeln($socket, $id, "CAL $id ".$session['to']);
+ break;
+ case 'CAL':
+ // SB: <<< CAL {id} RINGING {?}
+ // we don't need this, just ignore, and wait for other response
+ $session['id']++;
+ break;
+ case 'JOI':
+ // SB: <<< JOI {user} {alias} {clientid?}
+ // someone join us
+ @list(/* JOI */, $email) = @explode(' ', $data);
+ if ($email == $session['to']) {
+ $session['joined'] = true;
+ $this->callHandler('SessionReady', array('to' => $email));
+ }
+ break;
+ case 'MSG':
+ // SB: <<< MSG {email} {alias} {len}
+ @list(/* MSG */, $from_email, /* alias */, $len) = @explode(' ', $data);
+ $len = trim($len);
+ $data = $this->sb_readdata($socket, $len);
+ $aLines = @explode("\n", $data);
+ $header = true;
+ $ignore = false;
+ $is_p2p = false;
+ $sMsg = '';
+ foreach ($aLines as $line) {
+ $line = rtrim($line);
+ if ($header) {
+ if ($line === '') {
+ $header = false;
+ continue;
+ }
+ if (strncasecmp($line, 'TypingUser:', 11) == 0) {
+ // typing notification, just ignore
+ $ignore = true;
+ break;
+ }
+ if (strncasecmp($line, 'Chunk:', 6) == 0) {
+ // we don't handle any split message, just ignore
+ $ignore = true;
+ break;
+ }
+ if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) {
+ // p2p message, ignore it, but we need to send acknowledgement for it...
+ $is_p2p = true;
+ $p = strstr($data, "\n\n");
+ $sMsg = '';
+ if ($p === false) {
+ $p = strstr($data, "\r\n\r\n");
+ if ($p !== false)
+ $sMsg = substr($p, 4);
+ }
+ else
+ $sMsg = substr($p, 2);
+ break;
+ }
+ if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) {
+ // ignore all application/x-... message
+ // for example:
+ // application/x-ms-ink => ink message
+ $ignore = true;
+ break;
+ }
+ if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) {
+ // ignore all text/x-... message
+ // for example:
+ // text/x-msnmsgr-datacast => nudge, voice clip....
+ // text/x-mms-animemoticon => customized animemotion word
+ $ignore = true;
+ break;
+ }
+ continue;
+ }
+ if ($sMsg !== '')
+ $sMsg .= "\n";
+ $sMsg .= $line;
+ }
+ if ($ignore) {
+ $this->debug_message("*** Ignoring SB data from $from_email: $line");
+ break;
+ }
+ if ($is_p2p) {
+ // we will ignore any p2p message after sending acknowledgement
+ $ignore = true;
+ $len = strlen($sMsg);
+ $this->debug_message("*** p2p message from $from_email, size $len");
+ // header = 48 bytes
+ // content >= 0 bytes
+ // footer = 4 bytes
+ // so it need to >= 52 bytes
+ /*if ($len < 52) {
+ $this->debug_message("*** p2p: size error, less than 52!");
+ break;
+ }*/
+ $aDwords = @unpack("V12dword", $sMsg);
+ if (!is_array($aDwords)) {
+ $this->debug_message("*** p2p: header unpack error!");
+ break;
+ }
+ $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg));
+ $hdr_SessionID = $aDwords['dword1'];
+ $hdr_Identifier = $aDwords['dword2'];
+ $hdr_DataOffsetLow = $aDwords['dword3'];
+ $hdr_DataOffsetHigh = $aDwords['dword4'];
+ $hdr_TotalDataSizeLow = $aDwords['dword5'];
+ $hdr_TotalDataSizeHigh = $aDwords['dword6'];
+ $hdr_MessageLength = $aDwords['dword7'];
+ $hdr_Flag = $aDwords['dword8'];
+ $hdr_AckID = $aDwords['dword9'];
+ $hdr_AckUID = $aDwords['dword10'];
+ $hdr_AckSizeLow = $aDwords['dword11'];
+ $hdr_AckSizeHigh = $aDwords['dword12'];
+ $this->debug_message("*** p2p: header SessionID = $hdr_SessionID");
+ $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier");
+ $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow");
+ $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh");
+ $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow");
+ $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh");
+ $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength");
+ $this->debug_message("*** p2p: header Flag = $hdr_Flag");
+ $this->debug_message("*** p2p: header AckID = $hdr_AckID");
+ $this->debug_message("*** p2p: header AckUID = $hdr_AckUID");
+ $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow");
+ $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh");
+ if ($hdr_Flag == 2) {
+ //This is an ACK from SB ignore....
+ $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n");
+ break;
+ }
+ $MsgBody = $this->linetoArray(substr($sMsg, 48, -4));
+ $this->debug_message("*** p2p: body".print_r($MsgBody, true));
+ if (($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) {
+ while (true) {
+ if ($this->sb_readln($socket) === false) break;
+ }
+ $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg, 0, 48)));
+ preg_match('/{([0-9A-F\-]*)}/i', $MsgBody['Via'], $Matches);
+ $BranchGUID = $Matches[1];
+ //it's an invite to send a display picture.
+ $new_id = ~$hdr_Identifier;
+ $hdr = pack(
+ "LLLLLLLLLLLL", $hdr_SessionID,
+ $new_id,
+ 0, 0,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
+ 0,
+ 2,
+ $hdr_Identifier,
+ $hdr_AckID,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh
+ );
+ $footer = pack("L", 0);
+ $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";
+ $len = strlen($message);
+ $this->sb_writeln($socket, $id, "MSG $id D $len");
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID");
+ $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message));
+ $this->sb_readln($socket); // Read ACK;
+ $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr));
+ $new_id -= 3;
+ //Send 200 OK message
+ $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0);
+ $MessagePayload=
+ "MSNSLP/1.0 200 OK\r\n".
+ "To: <msnmsgr:".$from_email.">\r\n".
+ "From: <msnmsgr:".$this->user.">\r\n".
+ "Via: ".$MsgBody['Via']."\r\n".
+ "CSeq: ".($MsgBody['CSeq']+1)."\r\n".
+ "Call-ID: ".$MsgBody['Call-ID']."\r\n".
+ "Max-Forwards: 0\r\n".
+ "Content-Type: application/x-msnmsgr-sessionreqbody\r\n".
+ "Content-Length: ".strlen($MessageContent)."\r\n\r\n".
+ $MessageContent;
+ $hdr_TotalDataSizeLow=strlen($MessagePayload);
+ $hdr_TotalDataSizeHigh=0;
+ $hdr = pack(
+ "LLLLLLLLLLLL", $hdr_SessionID,
+ $new_id,
+ 0, 0,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
+ strlen($MessagePayload),
+ 0,
+ rand(),
+ 0,
+ 0, 0
+ );
+
+ $message =
+ "MIME-Version: 1.0\r\n".
+ "Content-Type: application/x-msnmsgrp2p\r\n".
+ "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message));
+ $this->sb_readln($socket); // Read ACK;
+
+ $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr));
+ // send data preparation message
+ // send 4 null bytes as data
+ $hdr_TotalDataSizeLow = 4;
+ $hdr_TotalDataSizeHigh = 0 ;
+ $new_id++;
+ $hdr = pack(
+ "LLLLLLLLLLLL",
+ $MsgBody['SessionID'],
+ $new_id,
+ 0, 0,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
+ $hdr_TotalDataSizeLow,
+ 0,
+ rand(),
+ 0,
+ 0, 0
+ );
+ $message =
+ "MIME-Version: 1.0\r\n".
+ "Content-Type: application/x-msnmsgrp2p\r\n".
+ "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L', 0)."$footer";
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message));
+ $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr));
+ $this->sb_readln($socket); // Read ACK;
+
+ // send Data Content..
+ $footer=pack('N',1);
+ $new_id++;
+ $FileSize=filesize($PictureFilePath);
+ if ($hTitle=fopen($PictureFilePath,'rb')) {
+ $Offset = 0;
+ //$new_id++;
+ while (!feof($hTitle)) {
+ $FileContent = fread($hTitle, 1024);
+ $FileContentSize = strlen($FileContent);
+ $hdr = pack(
+ "LLLLLLLLLLLL",
+ $MsgBody['SessionID'],
+ $new_id,
+ $Offset, 0,
+ $FileSize, 0,
+ $FileContentSize,
+ 0x20,
+ rand(),
+ 0,
+ 0, 0
+ );
+ $message =
+ "MIME-Version: 1.0\r\n".
+ "Content-Type: application/x-msnmsgrp2p\r\n".
+ "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer";
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message));
+ $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr));
+ //$this->SB_readln($socket);//Read ACK;
+ $Offset += $FileContentSize;
+ }
+ }
+ //Send Bye
+ /*
+ $MessageContent="\r\n".pack("C", 0);
+ $MessagePayload=
+ "BYE MSNMSGR:MSNSLP/1.0\r\n".
+ "To: <msnmsgr:$from_email>\r\n".
+ "From: <msnmsgr:".$this->user.">\r\n".
+ "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n".
+ "CSeq: 0\r\n".
+ "Call-ID: ".$MsgBody['Call-ID']."\r\n".
+ "Max-Forwards: 0\r\n".
+ "Content-Type: application/x-msnmsgr-sessionclosebody\r\n".
+ "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent;
+ $footer=pack('N',0);
+ $hdr_TotalDataSizeLow=strlen($MessagePayload);
+ $hdr_TotalDataSizeHigh=0;
+ $new_id++;
+ $hdr = pack("LLLLLLLLLLLL",
+ 0,
+ $new_id,
+ 0, 0,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
+ 0,
+ 0,
+ rand(),
+ 0,
+ 0,0);
+ $message =
+ "MIME-Version: 1.0\r\n".
+ "Content-Type: application/x-msnmsgrp2p\r\n".
+ "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));
+ $id++;
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message));
+ */
+ break;
+ }
+ //TODO:
+ //if ($hdr_Flag == 2) {
+ // just send ACK...
+ // $this->sb_writeln($socket, $id, "ACK $id");
+ // break;
+ //}
+ if ($hdr_SessionID == 4) {
+ // ignore?
+ $this->debug_message("*** p2p: ignore flag 4");
+ break;
+ }
+ $finished = false;
+ if ($hdr_TotalDataSizeHigh == 0) {
+ // only 32 bites size
+ if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow)
+ $finished = true;
+ }
+ else {
+ // we won't accept any file transfer
+ // so I think we won't get any message size need to use 64 bits
+ // 64 bits size here, can't count directly...
+ $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10);
+ $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10);
+ $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10);
+ $now_size = bcadd($dataoffset, $messagelength);
+ if (bccomp($now_size, $totalsize) >= 0)
+ $finished = true;
+ }
+ if (!$finished) {
+ // ignore not finished split packet
+ $this->debug_message("*** p2p: ignore split packet, not finished");
+ break;
+ }
+ //$new_id = ~$hdr_Identifier;
+ /*
+ $new_id++;
+ $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,
+ $new_id,
+ 0, 0,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
+ 0,
+ 2,
+ $hdr_Identifier,
+ $hdr_AckID,
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);
+ $footer = pack("L", 0);
+ $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";
+ $len = strlen($message);
+ $this->sb_writeln($socket, $id, "MSG $id D $len");
+ $id++;
+ $this->sb_writedata($socket, $message);
+ $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID");
+ $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer));
+ */
+ break;
+ }
+ $this->debug_message("*** MSG from $from_email: $sMsg");
+ $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => 1, 'offline' => false));
+ break;
+ case '217':
+ $this->debug_message('*** User '.$session['to'].' is offline. Trying OIM.');
+ $session['offline'] = true;
+ break;
+ default:
+ if (is_numeric($code)) {
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+ $this->debug_message("*** SB: $this->error");
+ }
+ break;
+ }
+ }
+
+ /**
+ * Checks for new data and calls appropriate methods
+ *
+ * This method is usually called in an infinite loop to keep checking for new data
+ *
+ * @return void
+ */
+ public function receive() {
+ // First, get an array of sockets that have data that is ready to be read
+ $ready = array();
+ $ready = $this->getSockets();
+ $numrdy = stream_select($ready, $w = NULL, $x = NULL, NULL);
+
+ // Now that we've waited for something, go through the $ready
+ // array and read appropriately
+
+ foreach ($ready as $socket) {
+ if ($socket == $this->NSfp) {
+ $this->nsReceive();
+ } else {
+ $this->sbReceive($socket);
+ }
+ }
+ }
+
+ /**
+ * Switchboard related methods
+ */
+
+ /**
+ * Send a request for a switchboard session
+ *
+ * @param string $to Target email for switchboard session
+ */
+ private function reqSBSession($to) {
+ $this->debug_message("*** Request SB for $to");
+ $this->ns_writeln("XFR $this->id SB");
+
+ // Add to the queue of those waiting for a switchboard session reponse
+ $this->switchBoardSessions[$to] = array(
+ 'to' => $to,
+ 'socket' => NULL,
+ 'id' => 1,
+ 'joined' => false,
+ 'offline' => false,
+ 'XFRReqTime' => time()
+ );
+ $this->waitingForXFR[$to] = &$this->switchBoardSessions[$to];
+ }
+
+ /**
+ * Following an XFR or RNG, connect to the switchboard session
+ *
+ * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case of RNG)
+ * @param string $ip IP of Switchboard
+ * @param integer $port Port of Switchboard
+ * @param string $to User on other end of Switchboard
+ * @param array $param Array of parameters - 'cki', 'ticket', 'sid'
+ * @return boolean true if successful
+ */
+ private function connectToSBSession($mode, $ip, $port, $to, $param) {
+ $this->debug_message("*** SB: Trying to connect to switchboard server $ip:$port");
+
+ $socket = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);
+ if (!$socket) {
+ $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr");
+ return false;
+ }
+
+ // Store the socket in the lookup array
+ $this->switchBoardSessionLookup[$to] = $socket;
+
+ // Store the socket in the sessions array
+ $this->switchBoardSessions[$to] = array(
+ 'to' => $to,
+ 'socket' => $socket,
+ 'id' => 1,
+ 'joined' => false,
+ 'offline' => false,
+ 'XFRReqTime' => time()
+ );
+
+ // Change the index of the session to the socket
+ $intsocket = (int) $socket;
+ $this->switchBoardSessions[$intsocket] = $this->switchBoardSessions[$to];
+ unset($this->switchBoardSessions[$to]);
+
+ $id = &$this->switchBoardSessions[$intsocket]['id'];
+
+ if ($mode == 'Active') {
+ $cki_code = $param['cki'];
+
+ // SB: >>> USR {id} {user} {cki}
+ $this->sb_writeln($socket, $id, "USR $id $this->user $cki_code");
+ } else {
+ // Passive
+ $ticket = $param['ticket'];
+ $sid = $param['sid'];
+
+ // SB: >>> ANS {id} {user} {ticket} {session_id}
+ $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid");
+ }
+ }
+
+ /**
+ * Called when we want to end a switchboard session
+ * or a switchboard session ends
+ *
+ * @param resource $socket Socket
+ * @param boolean $killsession Whether to delete the session
+ * @return void
+ */
+ private function endSBSession($socket) {
+ if (!self::socketcheck($socket)) {
+ $this->sb_writeln($socket, $fake = 0, 'OUT');
+ }
+ @fclose($socket);
+
+ // Unset session lookup value
+ $intsocket = (int) $socket;
+ unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]);
+
+ // Unset session itself
+ unset($this->switchBoardSessions[$intsocket]);
+ }
+
+ /**
+ * Send a message via an existing SB session
+ *
+ * @param string $to Recipient for message
+ * @param string $message Message
+ * @return boolean true on success
+ */
+ private function sendMessageViaSB($to, $message) {
+ $socket = $this->switchBoardSessionLookup[$to];
+ if (self::socketcheck($socket)) {
+ return false;
+ }
+
+ $id = &$this->switchBoardSessions[(int) $socket]['id'];
+
+ $aMessage = $this->getMessage($message);
+ // CheckEmotion...
+ $MsnObjDefine = $this->GetMsnObjDefine($aMessage);
+ if ($MsnObjDefine !== '') {
+ $SendString = "MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine";
+ $len = strlen($SendString);
+
+ if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||
+ $this->sb_writedata($socket, $SendString) === false) {
+ $this->endSBSession($socket);
+ return false;
+ }
+ }
+ $len = strlen($aMessage);
+
+ if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||
+ $this->sb_writedata($socket, $aMessage) === false) {
+ $this->endSBSession($socket);
+ return false;
+ }
+
+ // Don't close the SB session, we might as well leave it open
+ return true;
+ }
+
+ /**
+ * Send a message to a user on another network
+ *
+ * @param string $to Intended recipient
+ * @param string $message Message
+ * @param integer $network Network
+ * @return void
+ */
+ private function sendOtherNetworkMessage($to, $message, $network) {
+ $message = $this->getMessage($message, $network);
+ $len = strlen($message);
+ if ($this->ns_writeln("UUM $this->id $to $network 1 $len") === false ||
+ $this->ns_writedata($Message) === false) {
+ return false;
+ }
+ $this->debug_message("*** Sent to $to (network: $network):\n$Message");
+ return true;
+ }
+
+ /**
+ * Send a message
+ *
+ * @param string $to To address in form user@host.com(@network)
+ * where network is 1 for MSN, 32 for Yahoo
+ * and 'Offline' for offline messages
+ * @param string $message Message
+ * @param boolean &$waitForSession Boolean passed by reference,
+ * if set to true on return, message
+ * did not fail to send but is
+ * waiting for a valid session
+ *
+ * @return boolean true on success
+ */
+ public function sendMessage($to, $message, &$waitForSession) {
+ if ($message != '') {
+ $toParts = explode('@', $to);
+ if(count($toParts) < 3) {
+ list($name, $host) = $toParts;
+ $network = 1;
+ } else {
+ list($name, $host, $network) = $toParts;
+ }
+
+ $recipient = $name.'@'.$host;
+
+ if ($network === 1) {
+ if (!isset($this->switchBoardSessionLookup[$recipient])) {
+ if (!isset($this->switchBoardSessions[$recipient]) || time() - $this->switchBoardSessions[$recipient]['XFRReqTime'] > $this->XFRReqTimeout) {
+ $this->debug_message("*** No existing SB session or request has timed out");
+ $this->reqSBSession($recipient);
+ }
+
+ $waitForSession = true;
+ return false;
+ } else {
+ $socket = $this->switchBoardSessionLookup[$recipient];
+ $intsocket = (int) $socket;
+ if ($this->switchBoardSessions[$intsocket]['offline']) {
+ $this->debug_message("*** Contact ($recipient) offline, sending OIM");
+ $this->endSBSession($socket);
+ $waitForSession = false;
+ return $this->sendMessage($recipient.'@Offline', $message);
+ } else {
+ if ($this->switchBoardSessions[$intsocket]['joined'] !== true) {
+ $this->debug_message("*** Recipient has not joined session, returning false");
+ $waitForSession = true;
+ return false;
+ }
+
+ $this->debug_message("*** Attempting to send message to $recipient using existing SB session");
+
+ if ($this->sendMessageViaSB($recipient, $message)) {
+ $this->debug_message('*** Message sent successfully');
+ return true;
+ }
+
+ $waitForSession = false;
+ return false;
+ }
+ }
+ } elseif ($network == 'Offline') {
+ //Send OIM
+ //FIXME: 修正Send OIM
+ $lockkey = '';
+ $re_login = false;
+ for ($i = 0; $i < $this->oim_try; $i++) {
+ if (($oim_result = $this->sendOIM($recipient, $message, $lockkey)) === true) break;
+ if (is_array($oim_result) && $oim_result['challenge'] !== false) {
+ // need challenge lockkey
+ $this->debug_message("*** Need challenge code for ".$oim_result['challenge']);
+ $lockkey = $this->getChallenge($oim_result['challenge']);
+ continue;
+ }
+ if ($oim_result === false || $oim_result['auth_policy'] !== false) {
+ if ($re_login) {
+ $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so returning false");
+ return false;
+ }
+ $this->debug_message("*** Can't send OIM, maybe ticket expired, trying to login again");
+
+ // Maybe we need to re-login again
+ if (!$this->get_passport_ticket()) {
+ $this->debug_message("*** Can't re-login, something went wrong here, returning false");
+ return false;
+ }
+ $this->debug_message("*** Getting new ticket and trying again");
+ continue;
+ }
+ }
+ return true;
+ } else {
+ // Other network
+ return $this->sendOtherNetworkMessage($recipient, $message, $network);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * OIM methods
+ */
+
+ /**
+ * Get OIM mail data
+ *
+ * @return string mail data or false on failure
+ */
+ function getOIM_maildata() {
+ preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);
+ if (count($matches) == 0) {
+ $this->debug_message('*** No web ticket?');
+ return false;
+ }
+ $t = htmlspecialchars($matches[1]);
+ $p = htmlspecialchars($matches[2]);
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+ <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+ <t>'.$t.'</t>
+ <p>'.$p.'</p>
+ </PassportCookie>
+</soap:Header>
+<soap:Body>
+ <GetMetadata xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi" />
+</soap:Body>
+</soap:Envelope>';
+
+ $header_array = array(
+ 'SOAPAction: '.self::OIM_MAILDATA_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'
+ );
+
+ $this->debug_message('*** URL: '.self::OIM_MAILDATA_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::OIM_MAILDATA_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) {
+ $this->debug_message("*** Could not get OIM maildata! http code: $http_code");
+ return false;
+ }
+
+ // <GetMetadataResponse xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">See #XML_Data</GetMetadataResponse>
+ preg_match('#<GetMetadataResponse([^>]*)>(.*)</GetMetadataResponse>#', $data, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message('*** Could not get OIM maildata');
+ return false;
+ }
+ return $matches[2];
+ }
+
+ /**
+ * Fetch OIM message with given id
+ *
+ * @param string $msgid
+ * @return string Message or false on failure
+ */
+ function getOIM_message($msgid) {
+ preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);
+ if (count($matches) == 0) {
+ $this->debug_message('*** No web ticket?');
+ return false;
+ }
+ $t = htmlspecialchars($matches[1]);
+ $p = htmlspecialchars($matches[2]);
+
+ // read OIM
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+ <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+ <t>'.$t.'</t>
+ <p>'.$p.'</p>
+ </PassportCookie>
+</soap:Header>
+<soap:Body>
+ <GetMessage xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+ <messageId>'.$msgid.'</messageId>
+ <alsoMarkAsRead>false</alsoMarkAsRead>
+ </GetMessage>
+</soap:Body>
+</soap:Envelope>';
+
+ $header_array = array(
+ 'SOAPAction: '.self::OIM_READ_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'
+ );
+
+ $this->debug_message('*** URL: '.self::OIM_READ_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::OIM_READ_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) {
+ $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code");
+ return false;
+ }
+
+ // why can't use preg_match('#<GetMessageResult>(.*)</GetMessageResult>#', $data, $matches)?
+ // multi-lines?
+ $start = strpos($data, '<GetMessageResult>');
+ $end = strpos($data, '</GetMessageResult>');
+ if ($start === false || $end === false || $start > $end) {
+ $this->debug_message("*** Can't get OIM: $msgid");
+ return false;
+ }
+ $lines = substr($data, $start + 18, $end - $start);
+ $aLines = @explode("\n", $lines);
+ $header = true;
+ $ignore = false;
+ $sOIM = '';
+ foreach ($aLines as $line) {
+ $line = rtrim($line);
+ if ($header) {
+ if ($line === '') {
+ $header = false;
+ continue;
+ }
+ continue;
+ }
+ // stop at empty lines
+ if ($line === '') break;
+ $sOIM .= $line;
+ }
+ $sMsg = base64_decode($sOIM);
+ //$this->debug_message("*** we get OIM ($msgid): $sMsg");
+
+ // delete OIM
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+ <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+ <t>'.$t.'</t>
+ <p>'.$p.'</p>
+ </PassportCookie>
+</soap:Header>
+<soap:Body>
+ <DeleteMessages xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+ <messageIds>
+ <messageId>'.$msgid.'</messageId>
+ </messageIds>
+ </DeleteMessages>
+</soap:Body>
+</soap:Envelope>';
+
+ $header_array = array(
+ 'SOAPAction: '.self::OIM_DEL_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'
+ );
+
+ $this->debug_message('*** URL: '.self::OIM_DEL_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::OIM_DEL_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200)
+ $this->debug_message("*** Could not delete OIM: $msgid, http code = $http_code");
+ else
+ $this->debug_message("*** OIM ($msgid) deleted");
+ return $sMsg;
+ }
+
+ /**
+ * Send offline message
+ *
+ * @param string $to Intended recipient
+ * @param string $sMessage Message
+ * @param string $lockkey Lock key
+ * @return mixed true on success or error data
+ */
+ private function sendOIM($to, $sMessage, $lockkey) {
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+ <From memberName="'.$this->user.'"
+ friendlyName="=?utf-8?B?'.base64_encode($this->user).'?="
+ xml:lang="zh-TW"
+ proxy="MSNMSGR"
+ xmlns="http://messenger.msn.com/ws/2004/09/oim/"
+ msnpVer="'.self::PROTOCOL.'"
+ buildVer="'.self::BUILDVER.'"/>
+ <To memberName="'.$to.'" xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>
+ <Ticket passport="'.htmlspecialchars($this->ticket['oim_ticket']).'"
+ appid="'.self::PROD_ID.'"
+ lockkey="'.$lockkey.'"
+ xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>
+ <Sequence xmlns="http://schemas.xmlsoap.org/ws/2003/03/rm">
+ <Identifier xmlns="http://schemas.xmlsoap.org/ws/2002/07/utility">http://messenger.msn.com</Identifier>
+ <MessageNumber>1</MessageNumber>
+ </Sequence>
+</soap:Header>
+<soap:Body>
+ <MessageType xmlns="http://messenger.msn.com/ws/2004/09/oim/">text</MessageType>
+ <Content xmlns="http://messenger.msn.com/ws/2004/09/oim/">MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: base64
+X-OIM-Message-Type: OfflineMessage
+X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7}
+X-OIM-Sequence-Num: 1
+
+'.chunk_split(base64_encode($sMessage)).'
+ </Content>
+</soap:Body>
+</soap:Envelope>';
+
+ $header_array = array(
+ 'SOAPAction: '.self::OIM_SEND_SOAP,
+ 'Content-Type: text/xml',
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'
+ );
+
+ $this->debug_message('*** URL: '.self::OIM_SEND_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::OIM_SEND_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code == 200) {
+ $this->debug_message("*** OIM sent for $to");
+ return true;
+ }
+
+ $challenge = false;
+ $auth_policy = false;
+ // the lockkey is invalid, authenticated fail, we need challenge it again
+ // <LockKeyChallenge xmlns="http://messenger.msn.com/ws/2004/09/oim/">364763969</LockKeyChallenge>
+ preg_match("#<LockKeyChallenge (.*)>(.*)</LockKeyChallenge>#", $data, $matches);
+ if (count($matches) != 0) {
+ // yes, we get new LockKeyChallenge
+ $challenge = $matches[2];
+ $this->debug_message("*** OIM need new challenge ($challenge) for $to");
+ }
+ // auth policy error
+ // <RequiredAuthPolicy xmlns="http://messenger.msn.com/ws/2004/09/oim/">MBI_SSL</RequiredAuthPolicy>
+ preg_match("#<RequiredAuthPolicy (.*)>(.*)</RequiredAuthPolicy>#", $data, $matches);
+ if (count($matches) != 0) {
+ $auth_policy = $matches[2];
+ $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to");
+ }
+ if ($auth_policy === false && $challenge === false) {
+ //<faultcode xmlns:q0="http://messenger.msn.com/ws/2004/09/oim/">q0:AuthenticationFailed</faultcode>
+ preg_match("#<faultcode (.*)>(.*)</faultcode>#", $data, $matches);
+ if (count($matches) == 0) {
+ // no error, we assume the OIM is sent
+ $this->debug_message("*** OIM sent for $to");
+ return true;
+ }
+ $err_code = $matches[2];
+ //<faultstring>Exception of type 'System.Web.Services.Protocols.SoapException' was thrown.</faultstring>
+ preg_match("#<faultstring>(.*)</faultstring>#", $data, $matches);
+ if (count($matches) > 0)
+ $err_msg = $matches[1];
+ else
+ $err_msg = '';
+ $this->debug_message("*** OIM failed for $to");
+ $this->debug_message("*** OIM Error code: $err_code");
+ $this->debug_message("*** OIM Error Message: $err_msg");
+ return false;
+ }
+ return array('challenge' => $challenge, 'auth_policy' => $auth_policy);
+ }
+
+ /**
+ * Contact / Membership list methods
+ */
+
+ /**
+ * Fetch contact list
+ *
+ * @return boolean true on success
+ */
+ private function UpdateContacts() {
+ $ABApplicationHeaderArray = array(
+ 'ABApplicationHeader' => array(
+ ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),
+ 'ApplicationId' => 'CFE80F9D-180F-4399-82AB-413F33A1FA11',
+ 'IsMigration' => false,
+ 'PartnerScenario' => 'ContactSave'
+ )
+ );
+
+ $ABApplicationHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray));
+ $ABFindAllArray = array(
+ 'ABFindAll' => array(
+ ':' => array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
+ 'abId' => '00000000-0000-0000-0000-000000000000',
+ 'abView' => 'Full',
+ 'lastChange' => '0001-01-01T00:00:00.0000000-08:00',
+ )
+ );
+ $ABFindAll = new SoapParam($this->Array2SoapVar($ABFindAllArray), 'ABFindAll');
+ $this->ABService->__setSoapHeaders(array($ABApplicationHeader, $this->ABAuthHeader));
+ $this->Contacts = array();
+ try {
+ $this->debug_message('*** Updating Contacts...');
+ $Result = $this->ABService->ABFindAll($ABFindAll);
+ $this->debug_message("*** Result:\n".print_r($Result, true)."\n".$this->ABService->__getLastResponse());
+ foreach($Result->ABFindAllResult->contacts->Contact as $Contact)
+ $this->Contacts[$Contact->contactInfo->passportName] = $Contact;
+ } catch(Exception $e) {
+ $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Add contact
+ *
+ * @param string $email
+ * @param integer $network
+ * @param string $display
+ * @param boolean $sendADL
+ * @return boolean true on success
+ */
+ private function addContact($email, $network, $display = '', $sendADL = false) {
+ if ($network != 1) return true;
+ if (isset($this->Contacts[$email])) return true;
+
+ $ABContactAddArray = array(
+ 'ABContactAdd' => array(
+ ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),
+ 'abId' => '00000000-0000-0000-0000-000000000000',
+ 'contacts' => array(
+ 'Contact' => array(
+ ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),
+ 'contactInfo' => array(
+ 'contactType' => 'LivePending',
+ 'passportName' => $email,
+ 'isMessengerUser' => true,
+ 'MessengerMemberInfo' => array(
+ 'DisplayName' => $email
+ )
+ )
+ )
+ ),
+ 'options' => array(
+ 'EnableAllowListManagement' => true
+ )
+ )
+ );
+ $ABContactAdd = new SoapParam($this->Array2SoapVar($ABContactAddArray), 'ABContactAdd');
+ try {
+ $this->debug_message("*** Adding Contact $email...");
+ $this->ABService->ABContactAdd($ABContactAdd);
+ } catch(Exception $e) {
+ $this->debug_message("*** Add Contact Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());
+ return false;
+ }
+ if ($sendADL && !feof($this->NSfp)) {
+ @list($u_name, $u_domain) = @explode('@', $email);
+ foreach (array('1', '2') as $l) {
+ $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="'.$l.'" t="'.$network.'" /></d></ml>';
+ $len = strlen($str);
+ // NS: >>> ADL {id} {size}
+ $this->ns_writeln("ADL $this->id $len");
+ $this->ns_writedata($str);
+ }
+ }
+ $this->UpdateContacts();
+ return true;
+ }
+
+ /**
+ * Remove contact from list
+ *
+ * @param integer $memberID
+ * @param string $email
+ * @param integer $network
+ * @param string $list
+ */
+ function delMemberFromList($memberID, $email, $network, $list) {
+ if ($network != 1 && $network != 32) return true;
+ if ($memberID === false) return true;
+ $user = $email;
+ $ticket = htmlspecialchars($this->ticket['contact_ticket']);
+ if ($network == 1)
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+ <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+ <IsMigration>false</IsMigration>
+ <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+ </ABApplicationHeader>
+ <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ManagedGroupRequest>false</ManagedGroupRequest>
+ <TicketToken>'.$ticket.'</TicketToken>
+ </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+ <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">
+ <serviceHandle>
+ <Id>0</Id>
+ <Type>Messenger</Type>
+ <ForeignId></ForeignId>
+ </serviceHandle>
+ <memberships>
+ <Membership>
+ <MemberRole>'.$list.'</MemberRole>
+ <Members>
+ <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <Type>Passport</Type>
+ <MembershipId>'.$memberID.'</MembershipId>
+ <State>Accepted</State>
+ </Member>
+ </Members>
+ </Membership>
+ </memberships>
+ </DeleteMember>
+</soap:Body>
+</soap:Envelope>';
+ else
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+ <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+ <IsMigration>false</IsMigration>
+ <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+ </ABApplicationHeader>
+ <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ManagedGroupRequest>false</ManagedGroupRequest>
+ <TicketToken>'.$ticket.'</TicketToken>
+ </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+ <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">
+ <serviceHandle>
+ <Id>0</Id>
+ <Type>Messenger</Type>
+ <ForeignId></ForeignId>
+ </serviceHandle>
+ <memberships>
+ <Membership>
+ <MemberRole>'.$list.'</MemberRole>
+ <Members>
+ <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <Type>Email</Type>
+ <MembershipId>'.$memberID.'</MembershipId>
+ <State>Accepted</State>
+ </Member>
+ </Members>
+ </Membership>
+ </memberships>
+ </DeleteMember>
+</soap:Body>
+</soap:Envelope>';
+
+ $header_array = array(
+ 'SOAPAction: '.self::DELMEMBER_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+ );
+
+ $this->debug_message('*** URL: '.self::DELMEMBER_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::DELMEMBER_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) {
+ preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list");
+ return false;
+ }
+ $faultcode = trim($matches[1]);
+ $faultstring = trim($matches[2]);
+ if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) {
+ $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, error code: $faultcode, $faultstring");
+ return false;
+ }
+ $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, not present in list");
+ return true;
+ }
+ $this->debug_message("*** Member successfully deleted (network: $network) $email ($memberID) from $list list");
+ return true;
+ }
+
+ /**
+ * Add contact to list
+ *
+ * @param string $email
+ * @param integer $network
+ * @param string $list
+ */
+ function addMemberToList($email, $network, $list) {
+ if ($network != 1 && $network != 32) return true;
+ $ticket = htmlspecialchars($this->ticket['contact_ticket']);
+ $user = $email;
+
+ if ($network == 1)
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+ <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+ <IsMigration>false</IsMigration>
+ <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+ </ABApplicationHeader>
+ <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ManagedGroupRequest>false</ManagedGroupRequest>
+ <TicketToken>'.$ticket.'</TicketToken>
+ </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+ <AddMember xmlns="http://www.msn.com/webservices/AddressBook">
+ <serviceHandle>
+ <Id>0</Id>
+ <Type>Messenger</Type>
+ <ForeignId></ForeignId>
+ </serviceHandle>
+ <memberships>
+ <Membership>
+ <MemberRole>'.$list.'</MemberRole>
+ <Members>
+ <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <Type>Passport</Type>
+ <State>Accepted</State>
+ <PassportName>'.$user.'</PassportName>
+ </Member>
+ </Members>
+ </Membership>
+ </memberships>
+ </AddMember>
+</soap:Body>
+</soap:Envelope>';
+ else
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+ <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+ <IsMigration>false</IsMigration>
+ <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+ </ABApplicationHeader>
+ <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ManagedGroupRequest>false</ManagedGroupRequest>
+ <TicketToken>'.$ticket.'</TicketToken>
+ </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+ <AddMember xmlns="http://www.msn.com/webservices/AddressBook">
+ <serviceHandle>
+ <Id>0</Id>
+ <Type>Messenger</Type>
+ <ForeignId></ForeignId>
+ </serviceHandle>
+ <memberships>
+ <Membership>
+ <MemberRole>'.$list.'</MemberRole>
+ <Members>
+ <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <Type>Email</Type>
+ <State>Accepted</State>
+ <Email>'.$user.'</Email>
+ <Annotations>
+ <Annotation>
+ <Name>MSN.IM.BuddyType</Name>
+ <Value>32:YAHOO</Value>
+ </Annotation>
+ </Annotations>
+ </Member>
+ </Members>
+ </Membership>
+ </memberships>
+ </AddMember>
+</soap:Body>
+</soap:Envelope>';
+ $header_array = array(
+ 'SOAPAction: '.self::ADDMEMBER_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+ );
+
+ $this->debug_message('*** URL: '.self::ADDMEMBER_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::ADDMEMBER_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) {
+ preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message("*** Could not add member (network: $network) $email to $list list");
+ return false;
+ }
+ $faultcode = trim($matches[1]);
+ $faultstring = trim($matches[2]);
+ if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) {
+ $this->debug_message("*** Could not add member (network: $network) $email to $list list, error code: $faultcode, $faultstring");
+ return false;
+ }
+ $this->debug_message("*** Could not add member (network: $network) $email to $list list, already present");
+ return true;
+ }
+ $this->debug_message("*** Member successfully added (network: $network) $email to $list list");
+ return true;
+ }
+
+ /**
+ * Get membership lists
+ *
+ * @param mixed $returnData Membership list or false on failure
+ */
+ function getMembershipList($returnData = false) {
+ $ticket = htmlspecialchars($this->ticket['contact_ticket']);
+ $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+ <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+ <IsMigration>false</IsMigration>
+ <PartnerScenario>Initial</PartnerScenario>
+ </ABApplicationHeader>
+ <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+ <ManagedGroupRequest>false</ManagedGroupRequest>
+ <TicketToken>'.$ticket.'</TicketToken>
+ </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+ <FindMembership xmlns="http://www.msn.com/webservices/AddressBook">
+ <serviceFilter>
+ <Types>
+ <ServiceType>Messenger</ServiceType>
+ <ServiceType>Invitation</ServiceType>
+ <ServiceType>SocialNetwork</ServiceType>
+ <ServiceType>Space</ServiceType>
+ <ServiceType>Profile</ServiceType>
+ </Types>
+ </serviceFilter>
+ </FindMembership>
+</soap:Body>
+</soap:Envelope>';
+ $header_array = array(
+ 'SOAPAction: '.self::MEMBERSHIP_SOAP,
+ 'Content-Type: text/xml; charset=utf-8',
+ 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+ );
+ $this->debug_message('*** URL: '.self::MEMBERSHIP_URL);
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, self::MEMBERSHIP_URL);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) return false;
+ $p = $data;
+ $aMemberships = array();
+ while (1) {
+ //$this->debug_message("search p = $p");
+ $start = strpos($p, '<Membership>');
+ $end = strpos($p, '</Membership>');
+ if ($start === false || $end === false || $start > $end) break;
+ //$this->debug_message("start = $start, end = $end");
+ $end += 13;
+ $sMembership = substr($p, $start, $end - $start);
+ $aMemberships[] = $sMembership;
+ //$this->debug_message("add sMembership = $sMembership");
+ $p = substr($p, $end);
+ }
+ //$this->debug_message("aMemberships = ".var_export($aMemberships, true));
+
+ $aContactList = array();
+ foreach ($aMemberships as $sMembership) {
+ //$this->debug_message("sMembership = $sMembership");
+ if (isset($matches)) unset($matches);
+ preg_match('#<MemberRole>(.*)</MemberRole>#', $sMembership, $matches);
+ if (count($matches) == 0) continue;
+ $sMemberRole = $matches[1];
+ //$this->debug_message("MemberRole = $sMemberRole");
+ if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue;
+ $p = $sMembership;
+ if (isset($aMembers)) unset($aMembers);
+ $aMembers = array();
+ while (1) {
+ //$this->debug_message("search p = $p");
+ $start = strpos($p, '<Member xsi:type="');
+ $end = strpos($p, '</Member>');
+ if ($start === false || $end === false || $start > $end) break;
+ //$this->debug_message("start = $start, end = $end");
+ $end += 9;
+ $sMember = substr($p, $start, $end - $start);
+ $aMembers[] = $sMember;
+ //$this->debug_message("add sMember = $sMember");
+ $p = substr($p, $end);
+ }
+ //$this->debug_message("aMembers = ".var_export($aMembers, true));
+ foreach ($aMembers as $sMember) {
+ //$this->debug_message("sMember = $sMember");
+ if (isset($matches)) unset($matches);
+ preg_match('#<Member xsi\:type="([^"]*)">#', $sMember, $matches);
+ if (count($matches) == 0) continue;
+ $sMemberType = $matches[1];
+ //$this->debug_message("MemberType = $sMemberType");
+ $network = -1;
+ preg_match('#<MembershipId>(.*)</MembershipId>#', $sMember, $matches);
+ if (count($matches) == 0) continue;
+ $id = $matches[1];
+ if ($sMemberType == 'PassportMember') {
+ if (strpos($sMember, '<Type>Passport</Type>') === false) continue;
+ $network = 1;
+ preg_match('#<PassportName>(.*)</PassportName>#', $sMember, $matches);
+ }
+ else if ($sMemberType == 'EmailMember') {
+ if (strpos($sMember, '<Type>Email</Type>') === false) continue;
+ // Value is 32: or 32:YAHOO
+ preg_match('#<Annotation><Name>MSN.IM.BuddyType</Name><Value>(.*):(.*)</Value></Annotation>#', $sMember, $matches);
+ if (count($matches) == 0) continue;
+ if ($matches[1] != 32) continue;
+ $network = 32;
+ preg_match('#<Email>(.*)</Email>#', $sMember, $matches);
+ }
+ if ($network == -1) continue;
+ if (count($matches) > 0) {
+ $email = $matches[1];
+ @list($u_name, $u_domain) = @explode('@', $email);
+ if ($u_domain == NULL) continue;
+ $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id;
+ $this->debug_message("*** Adding new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)");
+ }
+ }
+ }
+ return $aContactList;
+ }
+
+ /**
+ * MsnObj related methods
+ */
+
+ /**
+ *
+ * @param $FilePath 圖檔路徑
+ * @param $Type 檔案類型 3=>大頭貼,2表情圖案
+ * @return array
+ */
+ private function MsnObj($FilePath, $Type = 3) {
+ if (!($FileSize=filesize($FilePath))) return '';
+ $Location = md5($FilePath);
+ $Friendly = md5($FilePath.$Type);
+ if (isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location];
+ $sha1d = base64_encode(sha1(file_get_contents($FilePath), true));
+ $sha1c = base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d", true));
+ $this->MsnObjArray[$Location] = $FilePath;
+ $MsnObj = '<msnobj Creator="'.$this->user.'" Size="'.$FileSize.'" Type="'.$Type.'" Location="'.$Location.'" Friendly="'.$Friendly.'" SHA1D="'.$sha1d.'" SHA1C="'.$sha1c.'"/>';
+ $this->MsnObjMap[$Location] = $MsnObj;
+ $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n");
+ return $MsnObj;
+ }
+
+ private function GetPictureFilePath($Context) {
+ $MsnObj = base64_decode($Context);
+ if (preg_match('/location="(.*?)"/i', $MsnObj, $Match))
+ $location = $Match[1];
+ $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n");
+ if ($location && isset($this->MsnObjArray[$location]))
+ return $this->MsnObjArray[$location];
+ return false;
+ }
+
+ private function GetMsnObjDefine($Message) {
+ $DefineString = '';
+ if (is_array($this->Emotions))
+ foreach ($this->Emotions as $Pattern => $FilePath) {
+ if (strpos($Message, $Pattern) !== false)
+ $DefineString .= "$Pattern\t".$this->MsnObj($FilePath, 2)."\t";
+ }
+ return $DefineString;
+ }
+
+ /**
+ * Socket methods
+ */
+
+ /**
+ * Read data of specified size from NS socket
+ *
+ * @param integer $size Size to read
+ * @return string Data read
+ */
+ private function ns_readdata($size) {
+ $data = '';
+ $count = 0;
+ while (!feof($this->NSfp)) {
+ $buf = @fread($this->NSfp, $size - $count);
+ $data .= $buf;
+ $count += strlen($buf);
+ if ($count >= $size) break;
+ }
+ $this->debug_message("NS: data ($size/$count) <<<\n$data");
+ return $data;
+ }
+
+ /**
+ * Read line from the NS socket
+ *
+ * @return string Data read
+ */
+ private function ns_readln() {
+ $data = @fgets($this->NSfp, 4096);
+ if ($data !== false) {
+ $data = trim($data);
+ $this->debug_message("NS: <<< $data");
+ }
+ return $data;
+ }
+
+ /**
+ * Write line to NS socket
+ *
+ * Also increments id
+ *
+ * @param string $data Line to write to socket
+ * @return mixed Bytes written or false on failure
+ */
+ private function ns_writeln($data) {
+ $result = @fwrite($this->NSfp, $data."\r\n");
+ if ($result !== false) {
+ $this->debug_message("NS: >>> $data");
+ $this->id++;
+ }
+ return $result;
+ }
+
+ /**
+ * Write data to NS socket
+ *
+ * @param string $data Data to write to socket
+ * @return mixed Bytes written or false on failure
+ */
+ private function ns_writedata($data) {
+ $result = @fwrite($this->NSfp, $data);
+ if ($result !== false) {
+ $this->debug_message("NS: >>> $data");
+ }
+ return $result;
+ }
+
+ /**
+ * Read data of specified size from given SB socket
+ *
+ * @param resource $socket SB socket
+ * @param integer $size Size to read
+ * @return string Data read
+ */
+ private function sb_readdata($socket, $size) {
+ $data = '';
+ $count = 0;
+ while (!feof($socket)) {
+ $buf = @fread($socket, $size - $count);
+ $data .= $buf;
+ $count += strlen($buf);
+ if ($count >= $size) break;
+ }
+ $this->debug_message("SB: data ($size/$count) <<<\n$data");
+ return $data;
+ }
+
+ /**
+ * Read line from given SB socket
+ *
+ * @param resource $socket SB Socket
+ * @return string Line read
+ */
+ private function sb_readln($socket) {
+ $data = @fgets($socket, 4096);
+ if ($data !== false) {
+ $data = trim($data);
+ $this->debug_message("SB: <<< $data");
+ }
+ return $data;
+ }
+
+ /**
+ * Write line to given SB socket
+ *
+ * Also increments id
+ *
+ * @param resource $socket SB socket
+ * @param integer $id Reference to SB id
+ * @param string $data Line to write
+ * @return mixed Bytes written or false on error
+ */
+ private function sb_writeln($socket, &$id, $data) {
+ $result = @fwrite($socket, $data."\r\n");
+ if ($result !== false) {
+ $this->debug_message("SB: >>> $data");
+ $id++;
+ }
+ return $result;
+ }
+
+ /**
+ * Write data to given SB socket
+ *
+ * @param resource $socket SB socket
+ * @param $data Data to write to socket
+ * @return mixed Bytes written or false on error
+ */
+ private function sb_writedata($socket, $data) {
+ $result = @fwrite($socket, $data);
+ if ($result !== false) {
+ $this->debug_message("SB: >>> $data");
+ }
+ return $result;
+ }
+
+ /**
+ * Get all the sockets currently in use
+ *
+ * @return array Array of socket resources
+ */
+ public function getSockets() {
+ return array_merge(array($this->NSfp), $this->switchBoardSessionLookup);
+ }
+
+ /**
+ * Checks socket for end of file
+ *
+ * @param resource $socket Socket to check
+ * @return boolean true if end of file (socket)
+ */
+ private static function socketcheck($socket){
+ $info = stream_get_meta_data($socket);
+ return $info['eof'];
+ }
+
+ /**
+ * Key generation methods
+ */
+
+ private function derive_key($key, $magic) {
+ $hash1 = $this->mhash_sha1($magic, $key);
+ $hash2 = $this->mhash_sha1($hash1.$magic, $key);
+ $hash3 = $this->mhash_sha1($hash1, $key);
+ $hash4 = $this->mhash_sha1($hash3.$magic, $key);
+ return $hash2.substr($hash4, 0, 4);
+ }
+
+ private function generateLoginBLOB($key, $challenge) {
+ $key1 = base64_decode($key);
+ $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH');
+ $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION');
+
+ // get hash of challenge using key2
+ $hash = $this->mhash_sha1($challenge, $key2);
+
+ // get 8 bytes random data
+ $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8);
+
+ $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv);
+
+ $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72);
+ $blob .= $iv;
+ $blob .= $hash;
+ $blob .= $cipher;
+
+ return base64_encode($blob);
+ }
+
+ /**
+ * Generate challenge response
+ *
+ * @param string $code
+ * @return string challenge response code
+ */
+ private function getChallenge($code) {
+ // MSNP15
+ // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
+ // Step 1: The MD5 Hash
+ $md5Hash = md5($code.self::PROD_KEY);
+ $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0"));
+ for ($i = 0; $i < 4; $i++) {
+ $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0"))));
+ $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF;
+ }
+
+ // Step 2: A new string
+ $chl_id = $code.self::PROD_ID;
+ $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8));
+
+ $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1));
+ for ($i = 0; $i < count($aID); $i++) {
+ $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0"))));
+ $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10);
+ }
+
+ // Step 3: The 64 bit key
+ $magic_num = 0x0E79A9C1;
+ $str7f = 0x7FFFFFFF;
+ $high = 0;
+ $low = 0;
+ for ($i = 0; $i < count($aID); $i += 2) {
+ $temp = $aID[$i];
+ $temp = bcmod(bcmul($magic_num, $temp), $str7f);
+ $temp = bcadd($temp, $high);
+ $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]);
+ $temp = bcmod($temp, $str7f);
+
+ $high = $aID[$i+1];
+ $high = bcmod(bcadd($high, $temp), $str7f);
+ $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]);
+ $high = bcmod($high, $str7f);
+
+ $low = bcadd(bcadd($low, $high), $temp);
+ }
+
+ $high = bcmod(bcadd($high, $aMD5[1]), $str7f);
+ $low = bcmod(bcadd($low, $aMD5[3]), $str7f);
+
+ $new_high = bcmul($high & 0xFF, 0x1000000);
+ $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100));
+ $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100));
+ $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000));
+ // we need integer here
+ $high = 0+$new_high;
+
+ $new_low = bcmul($low & 0xFF, 0x1000000);
+ $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100));
+ $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100));
+ $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000));
+ // we need integer here
+ $low = 0+$new_low;
+
+ // we just use 32 bits integer, don't need the key, just high/low
+ // $key = bcadd(bcmul($high, 0x100000000), $low);
+
+ // Step 4: Using the key
+ $md5Hash = md5($code.self::PROD_KEY);
+ $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0"));
+
+ $hash = '';
+ $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high);
+ $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low);
+ $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high);
+ $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low);
+
+ return $hash;
+ }
+
+ /**
+ * Utility methods
+ */
+
+ private function Array2SoapVar($Array, $ReturnSoapVarObj = true, $TypeName = null, $TypeNameSpace = null) {
+ $ArrayString = '';
+ foreach($Array as $Key => $Val) {
+ if ($Key{0} == ':') continue;
+ $Attrib = '';
+ if (is_array($Val[':'])) {
+ foreach ($Val[':'] as $AttribName => $AttribVal)
+ $Attrib .= " $AttribName = '$AttribVal'";
+ }
+ if ($Key{0} == '!') {
+ //List Type Define
+ $Key = substr($Key,1);
+ foreach ($Val as $ListKey => $ListVal) {
+ if ($ListKey{0} == ':') continue;
+ if (is_array($ListVal)) $ListVal = $this->Array2SoapVar($ListVal, false);
+ elseif (is_bool($ListVal)) $ListVal = $ListVal ? 'true' : 'false';
+ $ArrayString .= "<$Key$Attrib>$ListVal</$Key>";
+ }
+ continue;
+ }
+ if (is_array($Val)) $Val = $this->Array2SoapVar($Val, false);
+ elseif (is_bool($Val)) $Val = $Val ? 'true' : 'false';
+ $ArrayString .= "<$Key$Attrib>$Val</$Key>";
+ }
+ if ($ReturnSoapVarObj) return new SoapVar($ArrayString, XSD_ANYXML, $TypeName, $TypeNameSpace);
+ return $ArrayString;
+ }
+
+ private function linetoArray($lines) {
+ $lines = str_replace("\r", '', $lines);
+ $lines = explode("\n", $lines);
+ foreach ($lines as $line) {
+ if (!isset($line{3})) continue;
+ list($Key, $Val) = explode(':', $line);
+ $Data[trim($Key)] = trim($Val);
+ }
+ return $Data;
+ }
+
+ /**
+ * Get Passport ticket
+ *
+ * @param string $url URL string (Optional)
+ * @return mixed Array of tickets or false on failure
+ */
+ private function get_passport_ticket($url = '') {
+ $user = $this->user;
+ $password = htmlspecialchars($this->password);
+
+ if ($url === '')
+ $passport_url = self::PASSPORT_URL;
+ else
+ $passport_url = $url;
+
+ $XML = '<?xml version="1.0" encoding="UTF-8"?>
+<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
+ xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
+ xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
+ xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
+ xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc"
+ xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">
+<Header>
+ <ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">
+ <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>
+ <ps:BinaryVersion>4</ps:BinaryVersion>
+ <ps:UIVersion>1</ps:UIVersion>
+ <ps:Cookies></ps:Cookies>
+ <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>
+ </ps:AuthInfo>
+ <wsse:Security>
+ <wsse:UsernameToken Id="user">
+ <wsse:Username>'.$user.'</wsse:Username>
+ <wsse:Password>'.$password.'</wsse:Password>
+ </wsse:UsernameToken>
+ </wsse:Security>
+</Header>
+<Body>
+ <ps:RequestMultipleSecurityTokens xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="RSTS">
+ <wst:RequestSecurityToken Id="RST0">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>http://Passport.NET/tb</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST1">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>messengerclear.live.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="'.$this->passport_policy.'"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST2">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>messenger.msn.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="?id=507"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST3">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>contacts.msn.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST4">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>messengersecure.live.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="MBI_SSL"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST5">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>spaces.live.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST6">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>storage.msn.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ </ps:RequestMultipleSecurityTokens>
+</Body>
+</Envelope>';
+
+ $this->debug_message("*** URL: $passport_url");
+ $this->debug_message("*** Sending SOAP:\n$XML");
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, $passport_url);
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+ $data = curl_exec($curl);
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ curl_close($curl);
+ $this->debug_message("*** Get Result:\n$data");
+
+ if ($http_code != 200) {
+ // sometimes, redirect to another URL
+ // MSNP15
+ //<faultcode>psf:Redirect</faultcode>
+ //<psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>
+ //<faultstring>Authentication Failure</faultstring>
+ if (strpos($data, '<faultcode>psf:Redirect</faultcode>') === false) {
+ $this->debug_message("*** Could not get passport ticket! http code = $http_code");
+ return false;
+ }
+ preg_match("#<psf\:redirectUrl>(.*)</psf\:redirectUrl>#", $data, $matches);
+ if (count($matches) == 0) {
+ $this->debug_message('*** Redirected, but could not get redirect URL!');
+ return false;
+ }
+ $redirect_url = $matches[1];
+ if ($redirect_url == $passport_url) {
+ $this->debug_message('*** Redirected, but to same URL!');
+ return false;
+ }
+ $this->debug_message("*** Redirected to $redirect_url");
+ return $this->get_passport_ticket($redirect_url);
+ }
+
+ // sometimes, redirect to another URL, also return 200
+ // MSNP15
+ //<faultcode>psf:Redirect</faultcode>
+ //<psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>
+ //<faultstring>Authentication Failure</faultstring>
+ if (strpos($data, '<faultcode>psf:Redirect</faultcode>') !== false) {
+ preg_match("#<psf\:redirectUrl>(.*)</psf\:redirectUrl>#", $data, $matches);
+ if (count($matches) != 0) {
+ $redirect_url = $matches[1];
+ if ($redirect_url == $passport_url) {
+ $this->debug_message('*** Redirected, but to same URL!');
+ return false;
+ }
+ $this->debug_message("*** Redirected to $redirect_url");
+ return $this->get_passport_ticket($redirect_url);
+ }
+ }
+
+ // no Redurect faultcode or URL
+ // we should get the ticket here
+
+ // we need ticket and secret code
+ // RST1: messengerclear.live.com
+ // <wsse:BinarySecurityToken Id="Compact1">t=tick&p=</wsse:BinarySecurityToken>
+ // <wst:BinarySecret>binary secret</wst:BinarySecret>
+ // RST2: messenger.msn.com
+ // <wsse:BinarySecurityToken Id="PPToken2">t=tick</wsse:BinarySecurityToken>
+ // RST3: contacts.msn.com
+ // <wsse:BinarySecurityToken Id="Compact3">t=tick&p=</wsse:BinarySecurityToken>
+ // RST4: messengersecure.live.com
+ // <wsse:BinarySecurityToken Id="Compact4">t=tick&p=</wsse:BinarySecurityToken>
+ // RST5: spaces.live.com
+ // <wsse:BinarySecurityToken Id="Compact5">t=tick&p=</wsse:BinarySecurityToken>
+ // RST6: storage.msn.com
+ // <wsse:BinarySecurityToken Id="Compact6">t=tick&p=</wsse:BinarySecurityToken>
+ preg_match("#".
+ "<wsse\:BinarySecurityToken Id=\"Compact1\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "<wst\:BinarySecret>(.*)</wst\:BinarySecret>(.*)".
+ "<wsse\:BinarySecurityToken Id=\"PPToken2\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "<wsse\:BinarySecurityToken Id=\"Compact3\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "<wsse\:BinarySecurityToken Id=\"Compact4\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "<wsse\:BinarySecurityToken Id=\"Compact5\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "<wsse\:BinarySecurityToken Id=\"Compact6\">(.*)</wsse\:BinarySecurityToken>(.*)".
+ "#",
+ $data, $matches);
+
+ // no ticket found!
+ if (count($matches) == 0) {
+ $this->debug_message('*** Could not get passport ticket!');
+ return false;
+ }
+
+ //$this->debug_message(var_export($matches, true));
+ // matches[0]: all data
+ // matches[1]: RST1 (messengerclear.live.com) ticket
+ // matches[2]: ...
+ // matches[3]: RST1 (messengerclear.live.com) binary secret
+ // matches[4]: ...
+ // matches[5]: RST2 (messenger.msn.com) ticket
+ // matches[6]: ...
+ // matches[7]: RST3 (contacts.msn.com) ticket
+ // matches[8]: ...
+ // matches[9]: RST4 (messengersecure.live.com) ticket
+ // matches[10]: ...
+ // matches[11]: RST5 (spaces.live.com) ticket
+ // matches[12]: ...
+ // matches[13]: RST6 (storage.live.com) ticket
+ // matches[14]: ...
+
+ // so
+ // ticket => $matches[1]
+ // secret => $matches[3]
+ // web_ticket => $matches[5]
+ // contact_ticket => $matches[7]
+ // oim_ticket => $matches[9]
+ // space_ticket => $matches[11]
+ // storage_ticket => $matches[13]
+
+ // yes, we get ticket
+ $aTickets = array(
+ 'ticket' => html_entity_decode($matches[1]),
+ 'secret' => html_entity_decode($matches[3]),
+ 'web_ticket' => html_entity_decode($matches[5]),
+ 'contact_ticket' => html_entity_decode($matches[7]),
+ 'oim_ticket' => html_entity_decode($matches[9]),
+ 'space_ticket' => html_entity_decode($matches[11]),
+ 'storage_ticket' => html_entity_decode($matches[13])
+ );
+ $this->ticket = $aTickets;
+ //$this->debug_message(var_export($aTickets, true));
+ $ABAuthHeaderArray = array(
+ 'ABAuthHeader' => array(
+ ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),
+ 'ManagedGroupRequest' => false,
+ 'TicketToken' => htmlspecialchars($this->ticket['contact_ticket']),
+ )
+ );
+ $this->ABAuthHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABAuthHeader', $this->Array2SoapVar($ABAuthHeaderArray));
+ return $aTickets;
+ }
+
+ /**
+ * Generate the data to send a message
+ *
+ * @param string $sMessage Message
+ * @param integer $network Network
+ * @return string Message data
+ */
+ private function getMessage($sMessage, $network = 1) {
+ $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n";
+ $msg_header_len = strlen($msg_header);
+ if ($network == 1)
+ $maxlen = self::MAX_MSN_MESSAGE_LEN - $msg_header_len;
+ else
+ $maxlen = self::MAX_YAHOO_MESSAGE_LEN - $msg_header_len;
+ $sMessage = str_replace("\r", '', $sMessage);
+ $msg = substr($sMessage, 0, $maxlen);
+ return $msg_header.$msg;
+ }
+
+ /**
+ * Sleep for the given number of seconds
+ *
+ * @param integer $wait Number of seconds to sleep for
+ */
+ private function NSRetryWait($wait) {
+ $this->debug_message("*** Sleeping for $wait seconds before retrying");
+ sleep($wait);
+ }
+
+ /**
+ * Sends a ping command
+ *
+ * Should be called about every 50 seconds
+ *
+ * @return void
+ */
+ public function sendPing() {
+ // NS: >>> PNG
+ $this->ns_writeln("PNG");
+ }
+
+ /**
+ * Methods to add / call callbacks
+ */
+
+ /**
+ * Calls User Handler
+ *
+ * Calls registered handler for a specific event.
+ *
+ * @param string $event Command (event) name (Rvous etc)
+ * @param array $data Data
+ * @see registerHandler
+ * @return void
+ */
+ private function callHandler($event, $data = NULL) {
+ if (isset($this->myEventHandlers[$event])) {
+ if ($data !== NULL) {
+ call_user_func($this->myEventHandlers[$event], $data);
+ } else {
+ call_user_func($this->myEventHandlers[$event]);
+ }
+ }
+ }
+
+ /**
+ * Registers a user handler
+ *
+ * Handler List
+ * IMIn, SessionReady, Pong, ConnectFailed, Reconnect,
+ * AddedToList, RemovedFromList, StatusChange
+ *
+ * @param string $event Event name
+ * @param string $handler User function to call
+ * @see callHandler
+ * @return boolean true if successful
+ */
+ public function registerHandler($event, $handler) {
+ if (is_callable($handler)) {
+ $this->myEventHandlers[$event] = $handler;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Debugging methods
+ */
+
+ /**
+ * Print message if debugging is enabled
+ *
+ * @param string $str Message to print
+ */
+ private function debug_message($str) {
+ if (!$this->debug) return;
+ echo $str."\n";
+ }
+
+ /**
+ * Dump binary data
+ *
+ * @param string $str Data string
+ * @return Binary data
+ */
+ private function dump_binary($str) {
+ $buf = '';
+ $a_str = '';
+ $h_str = '';
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i++) {
+ if (($i % 16) == 0) {
+ if ($buf !== '') {
+ $buf .= "$h_str $a_str\n";
+ }
+ $buf .= sprintf("%04X:", $i);
+ $a_str = '';
+ $h_str = '';
+ }
+ $ch = ord($str[$i]);
+ if ($ch < 32)
+ $a_str .= '.';
+ else
+ $a_str .= chr($ch);
+ $h_str .= sprintf(" %02X", $ch);
+ }
+ if ($h_str !== '')
+ $buf .= "$h_str $a_str\n";
+ return $buf;
+ }
+
+ function mhash_sha1($data, $key)
+ {
+ if (extension_loaded("mhash"))
+ return mhash(MHASH_SHA1, $data, $key);
+
+ if (function_exists("hash_hmac"))
+ return hash_hmac('sha1', $data, $key, true);
+
+ // RFC 2104 HMAC implementation for php. Hacked by Lance Rushing
+ $b = 64;
+ if (strlen($key) > $b)
+ $key = pack("H*", sha1($key));
+ $key = str_pad($key, $b, chr(0x00));
+ $ipad = str_pad("", $b, chr(0x36));
+ $opad = str_pad("", $b, chr(0x5c));
+ $k_ipad = $key ^ $ipad ;
+ $k_opad = $key ^ $opad;
+
+ $sha1_value = sha1($k_opad . pack("H*", sha1($k_ipad . $data)));
+
+ $hash_data = '';
+ $str = join('',explode('\x', $sha1_value));
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i += 2)
+ $hash_data .= chr(hexdec(substr($str, $i, 2)));
+ return $hash_data;
+ }
+}
diff --git a/plugins/Msn/extlib/phpmsnclass/msnbot.php b/plugins/Msn/extlib/phpmsnclass/msnbot.php
new file mode 100755
index 000000000..7a9f66ca2
--- /dev/null
+++ b/plugins/Msn/extlib/phpmsnclass/msnbot.php
@@ -0,0 +1,63 @@
+#!/usr/bin/php
+<?php
+global $msn;
+function ChildSignalFunction($signal)
+{
+ global $msn;
+ switch($signal)
+ {
+ case SIGTRAP:
+ case SIGTERM:
+ case SIGHUP:
+ if(is_object($msn)) $msn->End();
+ return;
+ }
+}
+
+// network:
+// 1: WLM/MSN
+// 2: LCS
+// 4: Mobile Phones
+// 32: Yahoo!
+function getNetworkName($network)
+{
+ switch ($network)
+ {
+ case 1:
+ return 'WLM/MSN';
+ case 2:
+ return 'LCS';
+ case 4:
+ return 'Mobile Phones';
+ case 32:
+ return 'Yahoo!';
+ }
+ return "Unknown ($network)";
+}
+
+
+require_once('config.php');
+include_once('msn.class.php');
+
+$msn = new MSN(array(
+ 'user' => 'xxx@hotmail.com',
+ 'password' => 'mypassword',
+ 'alias' => 'myalias',
+ 'psm' => 'psm',
+// 'PhotoSticker' => 'msntitle.jpg',
+ 'debug'=> true,
+/* 'Emotions' => array(
+ 'aaa' => 'emotion.gif'
+ ),*/
+));
+
+$fp=fopen(MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msnbot.pid', 'wt');
+if($fp)
+{
+ fputs($fp,posix_getpid());
+ fclose($fp);
+}
+declare(ticks = 1);
+$msn->Run();
+$msn->log_message("done!");
+@unlink(dirname($_SERVER['argv'][0]).DIRECTORY_SEPARATOR.'log'.DIRECTORY_SEPARATOR.'msnbot.pid');
diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd b/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd
new file mode 100644
index 000000000..46fc23f91
--- /dev/null
+++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd
@@ -0,0 +1,832 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- edited with XMLSpy v2008 sp1 (http://www.altova.com) by wp (freezingsoft) -->
+<xsd:schema xmlns:msnab="http://www.msn.com/webservices/AddressBook" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="http://www.msn.com/webservices/AddressBook" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0">
+ <xsd:complexType name="abInfoType">
+ <xsd:sequence>
+ <xsd:element name="name" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="ownerPuid" type="xsd:string"/>
+ <xsd:element name="OwnerCID" type="xsd:integer" minOccurs="0"/>
+ <xsd:element name="ownerEmail" type="xsd:string"/>
+ <xsd:element name="fDefault" type="xsd:boolean"/>
+ <xsd:element name="joinedNamespace" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="IsBot" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="IsParentManaged" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="SubscribeExternalPartner" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="NotifyExternalPartner" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="AddressBookType" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="MessengerApplicationServiceCreated" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="IsBetaMigrated" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="HandleType">
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:integer"/>
+ <xsd:element name="Type" type="xsd:string" default="Messenger"/>
+ <xsd:element name="ForeignId" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ServiceType">
+ <xsd:sequence>
+ <xsd:element name="Memberships" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Membership" type="msnab:Membership" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Info" type="msnab:InfoType"/>
+ <xsd:element name="Changes" type="xsd:string"/>
+ <xsd:element name="LastChange" type="xsd:dateTime" default="0001-01-01T00:00:00"/>
+ <xsd:element name="Deleted" type="xsd:boolean" default="false"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="Membership">
+ <xsd:sequence>
+ <xsd:element name="MemberRole" type="xsd:string"/>
+ <xsd:element name="Members">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Member" type="msnab:BaseMember" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="MembershipIsComplete" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="BaseMember">
+ <xsd:sequence>
+ <xsd:element name="MembershipId" type="xsd:positiveInteger" minOccurs="0"/>
+ <xsd:element name="Type" type="xsd:string"/>
+ <xsd:element name="Location" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Id" type="msnab:Guid"/>
+ <xsd:element name="IsPassportNameHidden" type="xsd:boolean"/>
+ <xsd:element name="CID" type="xsd:long"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="DisplayName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="State" type="msnab:MemberState"/>
+ <xsd:element name="Annotations" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Annotation" type="msnab:Annotation" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Deleted" type="xsd:boolean" default="false" minOccurs="0"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="JoinedDate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="ExpirationDate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="Changes" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CircleMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="CircleId" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="PassportMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="PassportName" type="xsd:string"/>
+ <xsd:element name="IsPassportNameHidden" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="PassportId" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="CID" type="xsd:long" minOccurs="0"/>
+ <xsd:element name="PassportChanges" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="EmailMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="Email" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="PhoneMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="PhoneNumber" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="RoleMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:string"/>
+ <xsd:element name="DefiningService">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:integer"/>
+ <xsd:element name="Type" type="xsd:string"/>
+ <xsd:element name="ForeignId" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="MaxRoleRecursionDepth" type="xsd:integer"/>
+ <xsd:element name="MaxDegreesSeparation" type="xsd:integer"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="ServiceMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="Service" type="msnab:HandleType"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="DomainMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="DomainName" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="EveryoneMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="GroupMember" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseMember">
+ <xsd:sequence>
+ <xsd:element name="Id" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:simpleType name="Guid">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:element name="MemberType">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Allow"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:simpleType name="MemberState">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Accepted"/>
+ <xsd:enumeration value="Pending"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="Annotation">
+ <xsd:sequence>
+ <xsd:element name="Name" type="xsd:string"/>
+ <xsd:element name="Value" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContactType">
+ <xsd:sequence>
+ <xsd:element name="contactId" type="msnab:Guid" minOccurs="0"/>
+ <xsd:element name="contactInfo" type="msnab:contactInfoType" minOccurs="0"/>
+ <xsd:element name="propertiesChanged" type="xsd:string" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ A space (ASCII #32) separated list of properties that
+ have changed as part of an update request. The property
+ names don't always match the name of the associated
+ element.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="fDeleted" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="lastChange" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="CreateDate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="LastModifiedBy" type="xsd:integer" minOccurs="0"/>
+ <xsd:element name="CreatedBy" type="xsd:integer" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContactIdType">
+ <xsd:sequence>
+ <xsd:element name="contactId" type="msnab:Guid" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="contactInfoType">
+ <xsd:sequence>
+ <xsd:element name="emails" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ContactEmail" type="msnab:contactEmailType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="phones" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ContactPhone" type="msnab:contactPhoneType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="locations" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ContactLocation" type="msnab:contactLocationType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="webSites" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ContactWebSite" type="msnab:contactWebSiteType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="annotations" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Annotation" type="msnab:Annotation" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupIds" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupIdsDeleted" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contactType" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="quickName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="firstName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="MiddleName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="lastName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Suffix" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="NameTitle" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="passportName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="IsPassportNameHidden" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="displayName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="puid" type="xsd:long" minOccurs="0"/>
+ <xsd:element name="CID" type="xsd:long" minOccurs="0"/>
+ <xsd:element name="BrandIdList" type="xsd:anyType" minOccurs="0"/>
+ <xsd:element name="comment" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="IsNotMobileVisible" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="isMobileIMEnabled" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="isMessengerUser" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="isFavorite" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="isSmtp" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="hasSpace" type="xsd:boolean" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Indicates whether the contact has a Windows Live
+ Space or not.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="spotWatchState" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="birthdate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="primaryEmailType" type="msnab:ContactEmailTypeType" minOccurs="0"/>
+ <xsd:element name="PrimaryLocation" type="msnab:ContactLocationTypeType" minOccurs="0"/>
+ <xsd:element name="PrimaryPhone" type="msnab:ContactPhoneTypeType" minOccurs="0"/>
+ <xsd:element name="IsPrivate" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="Anniversary" type="xsd:string" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>
+ Seen is YYYY/MM/DD format.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="Gender" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="TimeZone" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="NetworkInfoList" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="NetworkInfo" type="msnab:NetworkInfoType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="PublicDisplayName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="IsAutoUpdateDisabled" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="MessengerMemberInfo" type="msnab:MessengerMemberInfo" minOccurs="0"/>
+ <xsd:element name="PropertiesChanged" type="xsd:anyType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="contactEmailType">
+ <xsd:sequence>
+ <xsd:element name="contactEmailType" type="msnab:ContactEmailTypeType"/>
+ <xsd:element name="email" type="xsd:string"/>
+ <xsd:element name="isMessengerEnabled" type="xsd:boolean"/>
+ <xsd:element name="Capability" type="xsd:integer"/>
+ <xsd:element name="MessengerEnabledExternally" type="xsd:boolean"/>
+ <xsd:element name="propertiesChanged" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:simpleType name="ContactEmailTypeType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ContactEmailPersonal"/>
+ <xsd:enumeration value="ContactEmailBusiness"/>
+ <xsd:enumeration value="ContactEmailOther"/>
+ <xsd:enumeration value="ContactEmailMessenger"/>
+ <xsd:enumeration value="Messenger2"/>
+ <xsd:enumeration value="Messenger3"/>
+ <xsd:enumeration value="Messenger4"/>
+ <xsd:enumeration value="Passport"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="contactPhoneType">
+ <xsd:sequence>
+ <xsd:element name="contactPhoneType" type="msnab:ContactPhoneTypeType"/>
+ <xsd:element name="number" type="xsd:string"/>
+ <xsd:element name="isMessengerEnabled" type="xsd:boolean"/>
+ <xsd:element name="propertiesChanged" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:simpleType name="ContactPhoneTypeType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ContactPhonePersonal"/>
+ <xsd:enumeration value="ContactPhoneBusiness"/>
+ <xsd:enumeration value="ContactPhoneMobile"/>
+ <xsd:enumeration value="ContactPhonePager"/>
+ <xsd:enumeration value="ContactPhoneOther"/>
+ <xsd:enumeration value="ContactPhoneFax"/>
+ <xsd:enumeration value="Personal2"/>
+ <xsd:enumeration value="Business2"/>
+ <xsd:enumeration value="BusinessFax"/>
+ <xsd:enumeration value="BusinessMobile"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="contactLocationType">
+ <xsd:sequence>
+ <xsd:element name="contactLocationType" type="msnab:ContactLocationTypeType"/>
+ <xsd:element name="name" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="street" type="xsd:string"/>
+ <xsd:element name="city" type="xsd:string"/>
+ <xsd:element name="state" type="xsd:string"/>
+ <xsd:element name="country" type="xsd:string"/>
+ <xsd:element name="postalCode" type="xsd:string"/>
+ <xsd:element name="Department" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Changes" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:simpleType name="ContactLocationTypeType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ContactLocationPersonal"/>
+ <xsd:enumeration value="ContactLocationBusiness"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="contactWebSiteType">
+ <xsd:sequence>
+ <xsd:element name="contactWebSiteType" type="msnab:ContactWebSiteTypeType"/>
+ <xsd:element name="webURL" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:simpleType name="ContactWebSiteTypeType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="ContactWebSitePersonal"/>
+ <xsd:enumeration value="ContactWebSiteBusiness"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="GroupType">
+ <xsd:sequence>
+ <xsd:element name="groupId" type="msnab:Guid"/>
+ <xsd:element name="groupInfo" type="msnab:groupInfoType"/>
+ <xsd:element name="propertiesChanged" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>
+ A space (ASCII #32) separated list of properties that
+ have changed as part of an update request. The property
+ names don't always match the name of the associated
+ element.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="fDeleted" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="lastChange" type="xsd:dateTime" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="groupInfoType">
+ <xsd:sequence>
+ <xsd:element name="annotations" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Annotation" type="msnab:Annotation" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupType" type="msnab:Guid" default="C8529CE2-6EAD-434d-881F-341E17DB3FF8" minOccurs="0"/>
+ <xsd:element name="name" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="IsNotMobileVisible" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="IsPrivate" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="IsFavorite" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="fMessenger" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="groupFilterType">
+ <xsd:sequence>
+ <xsd:element name="groupIds">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="InvalidPassportUser">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="errorcode" type="xsd:string"/>
+ <xsd:element name="errorstring" type="xsd:string"/>
+ <xsd:element name="additionalDetails">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="originalExceptionErrorMessage" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="MessengerMemberInfo">
+ <xsd:sequence>
+ <xsd:element name="PendingAnnotations" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Annotation" type="msnab:Annotation" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="DisplayName" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="InfoType">
+ <xsd:sequence>
+ <xsd:element name="Handle" type="msnab:HandleType"/>
+ <xsd:element name="DisplayName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="InverseRequired" type="xsd:boolean" default="false"/>
+ <xsd:element name="AuthorizationCriteria" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="RSSUrl" type="xsd:anyURI" minOccurs="0"/>
+ <xsd:element name="IsBot" type="xsd:boolean" default="false"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="NotificationDataType">
+ <xsd:sequence>
+ <xsd:element name="StoreService" type="msnab:ServiceType"/>
+ <xsd:element name="Status" type="xsd:string"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime"/>
+ <xsd:element name="Gleam" type="xsd:boolean" default="false"/>
+ <xsd:element name="InstanceId" type="xsd:string" default="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="BaseDynamicItemType">
+ <xsd:sequence>
+ <xsd:element name="Type" type="xsd:string"/>
+ <xsd:element name="Deleted" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="Notifications" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="NotificationData" type="msnab:NotificationDataType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Changes" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CircleDynamicItem" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseDynamicItemType">
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="PassportDynamicItem" block="" mixed="false">
+ <xsd:complexContent mixed="false">
+ <xsd:extension base="msnab:BaseDynamicItemType">
+ <xsd:sequence>
+ <xsd:element name="CID" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="PassportName" type="xsd:string"/>
+ <xsd:element name="PassportId" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="SpaceStatus" type="xsd:string"/>
+ <xsd:element name="SpaceLastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="SpaceLastViewed" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="SpaceGleam" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="ProfileLastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="ProfileLastView" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="ProfileStatus" type="xsd:string"/>
+ <xsd:element name="ProfileGleam" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="ContactProfileStatus" type="xsd:string"/>
+ <xsd:element name="ContactProfileLastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="ContactProfileLastViewed" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="LiveContactLastChanged" type="xsd:dateTime" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="abType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid"/>
+ <xsd:element name="abInfo" type="msnab:abInfoType"/>
+ <xsd:element name="lastChange" type="xsd:dateTime"/>
+ <xsd:element name="DynamicItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="RecentActivityItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="createDate" type="xsd:dateTime"/>
+ <xsd:element name="propertiesChanged" type="xsd:anyType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CircleResultType">
+ <xsd:sequence>
+ <xsd:element name="Circles" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="CircleInverseInfo" type="msnab:CircleInverseInfoType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CircleTicket" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="NetworkInfoType">
+ <xsd:sequence>
+ <xsd:element name="DomainId" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="DomainTag" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="UserTileURL" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="ProfileURL" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="DisplayName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="RelationshipType" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="RelationshipState" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="RelationshipStateDate" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="RelationshipRole" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="NDRCount" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="InviterCID" type="xsd:long" minOccurs="0"/>
+ <xsd:element name="CreateDate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="PropertiesChanged" type="xsd:anyType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContactFilterType">
+ <xsd:sequence>
+ <xsd:element name="IncludeHiddenContacts" type="xsd:boolean"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="filterOptionsType">
+ <xsd:sequence>
+ <xsd:element name="DeltasOnly" type="xsd:boolean"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="ContactFilter" type="msnab:ContactFilterType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="entityHandle">
+ <xsd:sequence>
+ <xsd:element name="Cid" type="xsd:long"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="NotationType">
+ <xsd:sequence>
+ <xsd:element name="Name" type="xsd:string"/>
+ <xsd:element name="Value" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ListTemplateVariableItemType">
+ <xsd:sequence>
+ <xsd:element name="Values">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Value" type="msnab:SimpleTemplateVariableBaseType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="TemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Name" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="SimpleTemplateVariableBaseType">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:TemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Value" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="PublisherIdTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:TemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:string"/>
+ <xsd:element name="NameHint" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="TargetIdTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:PublisherIdTemplateVariable">
+ <xsd:sequence>
+ <xsd:element name="IdOwner" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="TextTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:SimpleTemplateVariableBaseType"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="HlinkTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:SimpleTemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Text" type="xsd:string"/>
+ <xsd:element name="Notations">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Notation" type="msnab:NotationType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="ListTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:TemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Items">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ListTemplateVariableItem" type="msnab:ListTemplateVariableItemType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="ImageTemplateVariable">
+ <xsd:complexContent>
+ <xsd:extension base="msnab:SimpleTemplateVariableBaseType">
+ <xsd:sequence>
+ <xsd:element name="Href" type="xsd:anyURI"/>
+ <xsd:element name="Notations">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Notation" type="msnab:NotationType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="ActivityDetailsType">
+ <xsd:sequence>
+ <xsd:element name="OwnerCID" type="xsd:string"/>
+ <xsd:element name="ObjectId" type="xsd:string"/>
+ <xsd:element name="ApplicationId" type="xsd:string"/>
+ <xsd:element name="ChangeType" type="xsd:string"/>
+ <xsd:element name="PublishDate" type="xsd:dateTime"/>
+ <xsd:element name="TemplateVariables">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="TemplateVariable" type="msnab:TemplateVariableBaseType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="RecentActivityTemplateType">
+ <xsd:sequence>
+ <xsd:element name="Cardinality" type="xsd:string"/>
+ <xsd:element name="Data" type="xsd:string"/>
+ <xsd:element name="Title" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="RequestedLocalesType">
+ <xsd:sequence>
+ <xsd:element name="string" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="RecentActivityTemplateContainerType">
+ <xsd:sequence>
+ <xsd:element name="ApplicationId" type="xsd:string"/>
+ <xsd:element name="ApplicationName" type="xsd:string"/>
+ <xsd:element name="ChangeType" type="xsd:integer"/>
+ <xsd:element name="Locale" type="xsd:string"/>
+ <xsd:element name="RequestedLocales" type="msnab:RequestedLocalesType"/>
+ <xsd:element name="TemplateRevision" type="xsd:integer"/>
+ <xsd:element name="Templates">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="RecentActivityTemplate" type="msnab:RecentActivityTemplateType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CollapseCondition" type="msnab:CollapseConditionType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CollapseConditionType">
+ <xsd:sequence>
+ <xsd:element name="string" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CirclePersonalMembershipType">
+ <xsd:sequence>
+ <xsd:element name="Role" type="xsd:string"/>
+ <xsd:element name="State" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="abHandleType">
+ <xsd:sequence>
+ <xsd:element name="ABId" type="xsd:string"/>
+ <xsd:element name="Puid" type="xsd:long"/>
+ <xsd:element name="Cid" type="xsd:long"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="contactHandleType">
+ <xsd:sequence>
+ <xsd:element name="Email" type="xsd:string"/>
+ <xsd:element name="Puid" type="xsd:long"/>
+ <xsd:element name="Cid" type="xsd:long"/>
+ <xsd:element name="CircleId" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="MembershipInfoType">
+ <xsd:sequence>
+ <xsd:element name="CirclePersonalMembership" type="msnab:CirclePersonalMembershipType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="PersonalInfoType">
+ <xsd:sequence>
+ <xsd:element name="MembershipInfo" type="msnab:MembershipInfoType"/>
+ <xsd:element name="Name" type="xsd:string"/>
+ <xsd:element name="IsNotMobileVisible" type="xsd:boolean"/>
+ <xsd:element name="IsFavorite" type="xsd:boolean"/>
+ <xsd:element name="IsFamily" type="xsd:boolean"/>
+ <xsd:element name="Changes" type="xsd:anyType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContentInfoType">
+ <xsd:sequence>
+ <xsd:element name="Domain" type="xsd:int"/>
+ <xsd:element name="HostedDomain" type="xsd:string"/>
+ <xsd:element name="Type" type="xsd:int"/>
+ <xsd:element name="MembershipAccess" type="xsd:int"/>
+ <xsd:element name="IsPresenceEnabled" type="xsd:boolean"/>
+ <xsd:element name="RequestMembershipOption" type="xsd:int"/>
+ <xsd:element name="DisplayName" type="xsd:string"/>
+ <xsd:element name="ProfileLastUpdated" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="Changes" type="xsd:anyType" minOccurs="0"/>
+ <xsd:element name="CreateDate" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="LastChanged" type="xsd:dateTime" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContentHandleType">
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ContentType">
+ <xsd:sequence>
+ <xsd:element name="Handle" type="msnab:ContentHandleType"/>
+ <xsd:element name="Info" type="msnab:ContentInfoType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CircleInverseInfoType">
+ <xsd:sequence>
+ <xsd:element name="Content" type="msnab:ContentType"/>
+ <xsd:element name="PersonalInfo" type="msnab:PersonalInfoType"/>
+ <xsd:element name="Deleted" type="xsd:boolean"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="callerInfoType">
+ <xsd:sequence>
+ <xsd:element name="PublicDisplayName" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+</xsd:schema>
diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd b/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd
new file mode 100644
index 000000000..3fa9798b6
--- /dev/null
+++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd
@@ -0,0 +1,567 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- edited with XMLSpy v2008 sp1 (http://www.altova.com) by wp (freezingsoft) -->
+<xsd:schema xmlns:msnab="http://www.msn.com/webservices/AddressBook" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.msn.com/webservices/AddressBook" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0">
+ <xsd:include schemaLocation="msnab_datatypes.xsd"/>
+ <xsd:element name="ABApplicationHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ApplicationId" type="msnab:Guid" fixed="09607671-1C32-421F-A6A6-CBFAA51AB5F4"/>
+ <xsd:element name="IsMigration" type="xsd:boolean" default="false"/>
+ <xsd:element name="PartnerScenario" type="xsd:string" default="Initial"/>
+ <xsd:element name="CacheKey" type="xsd:token" minOccurs="0"/>
+ <xsd:element name="BrandId" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABAuthHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ManagedGroupRequest" type="xsd:boolean" default="false"/>
+ <xsd:element name="TicketToken" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ServiceHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Version" type="xsd:token"/>
+ <xsd:element name="CacheKey" type="xsd:token" minOccurs="0"/>
+ <xsd:element name="CacheKeyChanged" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="PreferredHostName" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="SessionId" type="msnab:Guid" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="FindMembership" type="msnab:FindMembershipRequestType"/>
+ <xsd:complexType name="FindMembershipRequestType">
+ <xsd:sequence>
+ <xsd:element name="serviceFilter">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Types">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ServiceType" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="View" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="deltasOnly" type="xsd:boolean" default="false" minOccurs="0"/>
+ <xsd:element name="lastChange" type="xsd:dateTime" default="0001-01-01T00:00:00.0000000-08:00" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="FindMembershipResultType">
+ <xsd:sequence>
+ <xsd:element name="Services">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Service" type="msnab:ServiceType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="OwnerNamespace">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Info">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Handle">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Id" type="xsd:string"/>
+ <xsd:element name="IsPassportNameHidden" type="xsd:boolean"/>
+ <xsd:element name="CID" type="xsd:integer"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CreatorPuid" type="xsd:integer"/>
+ <xsd:element name="CreatorCID" type="xsd:integer"/>
+ <xsd:element name="CreatorPassportName" type="xsd:string"/>
+ <xsd:element name="CircleAttributes">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="IsPresenceEnabled" type="xsd:boolean"/>
+ <xsd:element name="IsEvent" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="Domain" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="MessengerApplicationServiceCreated" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Changes" type="xsd:string"/>
+ <xsd:element name="CreateDate" type="xsd:dateTime"/>
+ <xsd:element name="LastChange" type="xsd:dateTime"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="FindMembershipResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="FindMembershipResult" type="msnab:FindMembershipResultType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABFindAll" type="msnab:ABFindAllRequestType"/>
+ <xsd:complexType name="ABFindAllRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="abView" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="deltasOnly" type="xsd:boolean" default="false" minOccurs="0"/>
+ <xsd:element name="lastChange" type="xsd:dateTime" default="0001-01-01T00:00:00.0000000-08:00" minOccurs="0"/>
+ <xsd:element name="dynamicItemView" type="xsd:string" fixed="Gleam" minOccurs="0"/>
+ <xsd:element name="dynamicItemLastChange" type="xsd:dateTime" default="0001-01-01T00:00:00.0000000-08:00" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ABFindAllResultType">
+ <xsd:sequence>
+ <xsd:element name="groups" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Group" type="msnab:GroupType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="DynamicItems" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="DynamicItem" type="msnab:BaseDynamicItemType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CircleResult">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="CircleTicket" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ab">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid"/>
+ <xsd:element name="abInfo" type="msnab:abInfoType"/>
+ <xsd:element name="lastChange" type="xsd:dateTime"/>
+ <xsd:element name="DynamicItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="RecentActivityItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="createDate" type="xsd:dateTime"/>
+ <xsd:element name="propertiesChanged" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABFindAllResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ABFindAllResult" type="msnab:ABFindAllResultType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABContactAdd" type="msnab:ABContactAddRequestType"/>
+ <xsd:complexType name="ABContactAddRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="options" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="EnableAllowListManagement" type="xsd:boolean"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ABContactAddResultType">
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABContactAddResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ABContactAddResult" type="msnab:ABContactAddResultType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABContactDelete" type="msnab:ABContactDeleteRequestType"/>
+ <xsd:complexType name="ABContactDeleteRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactIdType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABContactDeleteResponse"/>
+ <xsd:element name="ABGroupContactAdd" type="msnab:ABGroupContactAddRequestType"/>
+ <xsd:complexType name="ABGroupContactAddRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="groupFilter" type="msnab:groupFilterType"/>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupContactAddOptions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="fGenerateMissingQuickName" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="EnableAllowListManagement" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ABGroupContactAddResultType">
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABGroupContactAddResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ABGroupContactAddResult" type="msnab:ABGroupContactAddResultType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABGroupAdd" type="msnab:ABGroupAddRequestType"/>
+ <xsd:complexType name="ABGroupAddRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="groupAddOptions">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="fRenameOnMsgrConflict" type="xsd:boolean" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupInfo">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="GroupInfo" type="msnab:groupInfoType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ABGroupAddResultType">
+ <xsd:sequence>
+ <xsd:element name="guid" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABGroupAddResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ABGroupAddResult" type="msnab:ABGroupAddResultType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ABGroupUpdate" type="msnab:ABGroupUpdateRequestType"/>
+ <xsd:complexType name="ABGroupUpdateRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="groups">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Group" type="msnab:GroupType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABGroupUpdateResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:element name="ABGroupDelete" type="msnab:ABGroupDeleteRequestType"/>
+ <xsd:complexType name="ABGroupDeleteRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="groupFilter" type="msnab:groupFilterType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABGroupDeleteResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:element name="ABContactUpdate" type="msnab:ABContactUpdateRequestType"/>
+ <xsd:complexType name="ABContactUpdateRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABContactUpdateResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:element name="ABGroupContactDelete" type="msnab:ABGroupContactDeleteRequestType"/>
+ <xsd:complexType name="ABGroupContactDeleteRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid" fixed="00000000-0000-0000-0000-000000000000"/>
+ <xsd:element name="contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="groupFilter" type="msnab:groupFilterType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABGroupContactDeleteResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:element name="AddMember" type="msnab:AddMemberRequestType"/>
+ <xsd:complexType name="AddMemberRequestType">
+ <xsd:sequence>
+ <xsd:element name="serviceHandle" type="msnab:HandleType"/>
+ <xsd:element name="memberships">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Membership" type="msnab:Membership" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="AddMemberResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:element name="DeleteMember" type="msnab:DeleteMemberRequestType"/>
+ <xsd:complexType name="DeleteMemberRequestType">
+ <xsd:sequence>
+ <xsd:element name="serviceHandle" type="msnab:HandleType"/>
+ <xsd:element name="memberships">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Membership" type="msnab:Membership" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="nsHandle" type="msnab:ContentHandleType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="DeleteMemberResponse">
+ <xsd:complexType/>
+ </xsd:element>
+ <xsd:complexType name="ABAddResponseType">
+ <xsd:sequence>
+ <xsd:element name="ABAddResult" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABAddResponse" type="msnab:ABAddResponseType"/>
+ <xsd:element name="ABAdd" type="msnab:ABAddRequestType"/>
+ <xsd:complexType name="ABAddRequestType">
+ <xsd:sequence>
+ <xsd:element name="abInfo" type="msnab:abInfoType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="UpdateDynamicItemRequestType">
+ <xsd:sequence>
+ <xsd:element name="abId" type="xsd:string"/>
+ <xsd:element name="dynamicItems">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="DynamicItem" type="msnab:BaseDynamicItemType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="UpdateDynamicItem" type="msnab:UpdateDynamicItemRequestType"/>
+ <xsd:element name="UpdateDynamicItemResponse"/>
+ <xsd:element name="ABFindContactsPaged" type="msnab:ABFindContactsPagedRequestType"/>
+ <xsd:complexType name="ABFindContactsPagedRequestType">
+ <xsd:sequence>
+ <xsd:element name="filterOptions" type="msnab:filterOptionsType"/>
+ <xsd:element name="abView" type="xsd:string"/>
+ <xsd:element name="extendedContent" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ABFindContactsPagedResultType">
+ <xsd:sequence>
+ <xsd:element name="Groups" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Group" type="msnab:GroupType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Contacts">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Contact" type="msnab:ContactType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CircleResult" type="msnab:CircleResultType"/>
+ <xsd:element name="Ab">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="abId" type="msnab:Guid"/>
+ <xsd:element name="abInfo" type="msnab:abInfoType"/>
+ <xsd:element name="lastChange" type="xsd:dateTime"/>
+ <xsd:element name="DynamicItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="RecentActivityItemLastChanged" type="xsd:dateTime"/>
+ <xsd:element name="createDate" type="xsd:dateTime"/>
+ <xsd:element name="propertiesChanged" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ABFindContactsPagedResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ABFindContactsPagedResult" type="msnab:ABFindContactsPagedResultType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="WNApplicationHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ApplicationId" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="WNAuthHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="TicketToken" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="WNServiceHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Version" type="xsd:token"/>
+ <xsd:element name="CacheKey" type="xsd:token" minOccurs="0"/>
+ <xsd:element name="CacheKeyChanged" type="xsd:boolean" minOccurs="0"/>
+ <xsd:element name="PreferredHostName" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="GetContactsRecentActivity" type="msnab:GetContactsRecentActivityRequestType"/>
+ <xsd:complexType name="GetContactsRecentActivityRequestType">
+ <xsd:sequence>
+ <xsd:element name="entityHandle" type="msnab:entityHandle"/>
+ <xsd:element name="locales">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="string" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="count" type="xsd:int"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="GetContactsRecentActivityResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="GetContactsRecentActivityResult" type="msnab:GetContactsRecentActivityResultType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="GetContactsRecentActivityResultType">
+ <xsd:sequence>
+ <xsd:element name="Activities">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ActivityDetails" type="msnab:ActivityDetailsType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Templates">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="RecentActivityTemplateContainer" type="msnab:RecentActivityTemplateContainerType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="FeedUrl" type="xsd:anyURI"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ManageWLConnection" type="msnab:ManageWLConnectionRequestType"/>
+ <xsd:complexType name="ManageWLConnectionRequestType">
+ <xsd:sequence>
+ <xsd:element name="abHandle" type="msnab:abHandleType"/>
+ <xsd:element name="contactId" type="xsd:string"/>
+ <xsd:element name="connection" type="xsd:boolean"/>
+ <xsd:element name="presence" type="xsd:boolean"/>
+ <xsd:element name="action" type="xsd:integer"/>
+ <xsd:element name="relationshipType" type="xsd:int"/>
+ <xsd:element name="relationshipRole" type="xsd:int"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="ManageWLConnectionResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="ManageWLConnectionResult" type="msnab:ContactType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CreateContact" type="msnab:CreateContactType"/>
+ <xsd:complexType name="CreateContactType">
+ <xsd:sequence>
+ <xsd:element name="abHandle" type="msnab:abHandleType"/>
+ <xsd:element name="contactHandle" type="msnab:contactHandleType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="CreateContactResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="CreateContactResult" type="msnab:ContactType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="CreateCircle" type="msnab:CreateCircleRequestType"/>
+ <xsd:complexType name="CreateCircleRequestType">
+ <xsd:sequence>
+ <xsd:element name="properties" type="msnab:ContentInfoType"/>
+ <xsd:element name="callerInfo" type="msnab:callerInfoType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CreateCircleResponseType">
+ <xsd:sequence>
+ <xsd:element name="Id" type="msnab:Guid"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="CreateCircleResponse">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="CreateCircleResult" type="msnab:CreateCircleResponseType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+</xsd:schema>
diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl b/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl
new file mode 100644
index 000000000..7ec87f90c
--- /dev/null
+++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl
@@ -0,0 +1,532 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- edited with XMLSpy v2008 sp1 (http://www.altova.com) by wp (freezingsoft) -->
+<definitions xmlns:msnab="http://www.msn.com/webservices/AddressBook" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://schemas.xmlsoap.org/soap/encoding/" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://www.msn.com/webservices/AddressBook">
+ <types>
+ <ns:schema xmlns="http://www.w3.org/2001/XMLSchema">
+ <ns:import schemaLocation="msnab_servicetypes.xsd" namespace="http://www.msn.com/webservices/AddressBook"/>
+ </ns:schema>
+ </types>
+ <message name="ABHeader">
+ <part name="ApplicationHeader" element="msnab:ABApplicationHeader"/>
+ <part name="AuthHeader" element="msnab:ABAuthHeader"/>
+ </message>
+ <message name="FindMembershipMessage">
+ <part name="FindMembershipRequest" element="msnab:FindMembership"/>
+ </message>
+ <message name="ABFindAllMessage">
+ <part name="ABFindAllRequest" element="msnab:ABFindAll"/>
+ </message>
+ <message name="ABContactAddMessage">
+ <part name="ABContactAddRequest" element="msnab:ABContactAdd"/>
+ </message>
+ <message name="ABContactDeleteMessage">
+ <part name="ABContactDeleteRequest" element="msnab:ABContactDelete"/>
+ </message>
+ <message name="ABGroupContactAddMessage">
+ <part name="ABGroupContactAddRequest" element="msnab:ABGroupContactAdd"/>
+ </message>
+ <message name="ABGroupAddMessage">
+ <part name="ABGroupAddRequest" element="msnab:ABGroupAdd"/>
+ </message>
+ <message name="ABGroupUpdateMessage">
+ <part name="ABGroupUpdateRequest" element="msnab:ABGroupUpdate"/>
+ </message>
+ <message name="ABGroupDeleteMessage">
+ <part name="ABGroupDeleteRequest" element="msnab:ABGroupDelete"/>
+ </message>
+ <message name="ABGroupContactDeleteMessage">
+ <part name="ABGroupContactDeleteRequest" element="msnab:ABGroupContactDelete"/>
+ </message>
+ <message name="ABContactUpdateMessage">
+ <part name="ABContactUpdateRequest" element="msnab:ABContactUpdate"/>
+ </message>
+ <message name="AddMemberMessage">
+ <part name="AddMemberRequest" element="msnab:AddMember"/>
+ </message>
+ <message name="DeleteMemberMessage">
+ <part name="DeleteMemberRequest" element="msnab:DeleteMember"/>
+ </message>
+ <message name="ServiceHeader">
+ <part name="ServiceHeader" element="msnab:ServiceHeader"/>
+ </message>
+ <message name="FindMembershipResponseMessage">
+ <part name="FindMembershipResponse" element="msnab:FindMembershipResponse"/>
+ </message>
+ <message name="ABFindAllResponseMessage">
+ <part name="ABFindAllResponse" element="msnab:ABFindAllResponse"/>
+ </message>
+ <message name="ABContactAddResponseMessage">
+ <part name="ABContactAddResponse" element="msnab:ABContactAddResponse"/>
+ </message>
+ <message name="ABContactDeleteResponseMessage">
+ <part name="ABContactDeleteResponse" element="msnab:ABContactDeleteResponse"/>
+ </message>
+ <message name="ABGroupContactAddResponseMessage">
+ <part name="ABGroupContactAddResponse" element="msnab:ABGroupContactAddResponse"/>
+ </message>
+ <message name="ABGroupAddResponseMessage">
+ <part name="ABGroupAddResponse" element="msnab:ABGroupAddResponse"/>
+ </message>
+ <message name="ABGroupUpdateResponseMessage">
+ <part name="ABGroupUpdateResponse" element="msnab:ABGroupUpdateResponse"/>
+ </message>
+ <message name="ABGroupDeleteResponseMessage">
+ <part name="ABGroupDeleteResponse" element="msnab:ABGroupDeleteResponse"/>
+ </message>
+ <message name="ABGroupContactDeleteResponseMessage">
+ <part name="ABGroupContactDeleteResponse" element="msnab:ABGroupContactDeleteResponse"/>
+ </message>
+ <message name="ABContactUpdateResponseMessage">
+ <part name="ABContactUpdateResponse" element="msnab:ABContactUpdateResponse"/>
+ </message>
+ <message name="AddMemberResponseMessage">
+ <part name="AddMemberResponse" element="msnab:AddMemberResponse"/>
+ </message>
+ <message name="DeleteMemberResponseMessage">
+ <part name="DeleteMemberResponse" element="msnab:DeleteMemberResponse"/>
+ </message>
+ <message name="InvalidPassportUserMessage">
+ <part name="fault" element="msnab:InvalidPassportUser"/>
+ </message>
+ <message name="ABAddMessage">
+ <part name="ABAddRequest" element="msnab:ABAdd"/>
+ </message>
+ <message name="ABAddResponseMessage">
+ <part name="ABAddResponse" element="msnab:ABAddResponse"/>
+ </message>
+ <message name="UpdateDynamicItemMessage">
+ <part name="UpdateDynamicItem" element="msnab:UpdateDynamicItem"/>
+ </message>
+ <message name="UpdateDynamicItemResponseMessage">
+ <part name="UpdateDynamicItemResponse" element="msnab:UpdateDynamicItemResponse"/>
+ </message>
+ <message name="ABFindContactsPagedMessage">
+ <part name="ABFindContactsPagedRequest" element="msnab:ABFindContactsPaged"/>
+ </message>
+ <message name="ABFindContactsPagedResponseMessage">
+ <part name="ABFindContactsPagedResponse" element="msnab:ABFindContactsPagedResponse"/>
+ </message>
+ <message name="GetContactsRecentActivityMessage">
+ <part name="GetContactsRecentActivityRequest" element="msnab:GetContactsRecentActivity"/>
+ </message>
+ <message name="GetContactsRecentActivityResponseMessage">
+ <part name="GetContactsRecentActivityResponse" element="msnab:GetContactsRecentActivityResponse"/>
+ </message>
+ <message name="WNHeader">
+ <part name="WNApplicationHeader" element="msnab:WNApplicationHeader"/>
+ <part name="WNAuthHeader" element="msnab:WNAuthHeader"/>
+ <part name="WNServiceHeader" element="msnab:WNServiceHeader"/>
+ </message>
+ <message name="CreateCircleMessage">
+ <part name="CreateCircleRequest" element="msnab:CreateCircle"/>
+ </message>
+ <message name="CreateCircleResponseMessage">
+ <part name="CreateCircleResponse" element="msnab:CreateCircleResponse"/>
+ </message>
+ <message name="CreateContactMessage">
+ <part name="CreateContactRequest" element="msnab:CreateContact"/>
+ </message>
+ <message name="CreateContactResponseMessage">
+ <part name="CreateContactResponse" element="msnab:CreateContactResponse"/>
+ </message>
+ <message name="ManageWLConnectionMessage">
+ <part name="ManageWLConnection" element="msnab:ManageWLConnection"/>
+ </message>
+ <message name="ManageWLConnectionResponseMessage">
+ <part name="ManageWLConnectionResponse" element="msnab:ManageWLConnectionResponse"/>
+ </message>
+ <portType name="SharingServicePortType">
+ <operation name="FindMembership">
+ <input message="msnab:FindMembershipMessage"/>
+ <output message="msnab:FindMembershipResponseMessage"/>
+ </operation>
+ <operation name="AddMember">
+ <input message="msnab:AddMemberMessage"/>
+ <output message="msnab:AddMemberResponseMessage"/>
+ </operation>
+ <operation name="DeleteMember">
+ <input message="msnab:DeleteMemberMessage"/>
+ <output message="msnab:DeleteMemberResponseMessage"/>
+ </operation>
+ <operation name="CreateCircle">
+ <input message="msnab:CreateCircleMessage"/>
+ <output message="msnab:CreateCircleResponseMessage"/>
+ </operation>
+ </portType>
+ <portType name="ABServicePortType">
+ <operation name="ABFindAll">
+ <input message="msnab:ABFindAllMessage"/>
+ <output message="msnab:ABFindAllResponseMessage"/>
+ </operation>
+ <operation name="ABContactAdd">
+ <input message="msnab:ABContactAddMessage"/>
+ <output message="msnab:ABContactAddResponseMessage"/>
+ <fault name="InvalidPassportUserException" message="msnab:InvalidPassportUserMessage"/>
+ </operation>
+ <operation name="ABContactDelete">
+ <input message="msnab:ABContactDeleteMessage"/>
+ <output message="msnab:ABContactDeleteResponseMessage"/>
+ <fault name="InvalidPassportUserException" message="msnab:InvalidPassportUserMessage"/>
+ </operation>
+ <operation name="ABGroupContactAdd">
+ <input message="msnab:ABGroupContactAddMessage"/>
+ <output message="msnab:ABGroupContactAddResponseMessage"/>
+ <fault name="InvalidPassportUserException" message="msnab:InvalidPassportUserMessage"/>
+ </operation>
+ <operation name="ABGroupAdd">
+ <input message="msnab:ABGroupAddMessage"/>
+ <output message="msnab:ABGroupAddResponseMessage"/>
+ </operation>
+ <operation name="ABGroupUpdate">
+ <input message="msnab:ABGroupUpdateMessage"/>
+ <output message="msnab:ABGroupUpdateResponseMessage"/>
+ </operation>
+ <operation name="ABGroupDelete">
+ <input message="msnab:ABGroupDeleteMessage"/>
+ <output message="msnab:ABGroupDeleteResponseMessage"/>
+ </operation>
+ <operation name="ABGroupContactDelete">
+ <input message="msnab:ABGroupContactDeleteMessage"/>
+ <output message="msnab:ABGroupContactDeleteResponseMessage"/>
+ </operation>
+ <operation name="ABContactUpdate">
+ <input message="msnab:ABContactUpdateMessage"/>
+ <output message="msnab:ABContactUpdateResponseMessage"/>
+ </operation>
+ <operation name="ABAdd">
+ <input message="msnab:ABAddMessage"/>
+ <output message="msnab:ABAddResponseMessage"/>
+ </operation>
+ <operation name="UpdateDynamicItem">
+ <input message="msnab:UpdateDynamicItemMessage"/>
+ <output message="msnab:UpdateDynamicItemResponseMessage"/>
+ </operation>
+ <operation name="ABFindContactsPaged">
+ <input message="msnab:ABFindContactsPagedMessage"/>
+ <output message="msnab:ABFindContactsPagedResponseMessage"/>
+ </operation>
+ <operation name="CreateContact">
+ <input message="msnab:CreateContactMessage"/>
+ <output message="msnab:CreateContactResponseMessage"/>
+ </operation>
+ <operation name="ManageWLConnection">
+ <input message="msnab:ManageWLConnectionMessage"/>
+ <output message="msnab:ManageWLConnectionResponseMessage"/>
+ </operation>
+ </portType>
+ <portType name="WhatsUpServicePortType">
+ <operation name="GetContactsRecentActivity">
+ <input message="msnab:GetContactsRecentActivityMessage"/>
+ <output message="msnab:GetContactsRecentActivityResponseMessage"/>
+ </operation>
+ </portType>
+ <binding name="SharingServiceBinding" type="msnab:SharingServicePortType">
+ <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
+ <operation name="FindMembership">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/FindMembership"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="AddMember">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/AddMember"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="DeleteMember">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/DeleteMember"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="CreateCircle">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/CreateCircle"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ </binding>
+ <binding name="ABServiceBinding" type="msnab:ABServicePortType">
+ <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
+ <operation name="ABFindAll">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABFindAll"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABContactAdd">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABContactAdd"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ <fault name="InvalidPassportUserException">
+ <soap:fault name="InvalidPassportUserException" use="literal"/>
+ </fault>
+ </operation>
+ <operation name="ABContactDelete">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABContactDelete"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ <fault name="InvalidPassportUserException">
+ <soap:fault name="InvalidPassportUserException" use="literal"/>
+ </fault>
+ </operation>
+ <operation name="ABGroupContactAdd">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABGroupContactAdd"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ <fault name="InvalidPassportUserException">
+ <soap:fault name="InvalidPassportUserException" use="literal"/>
+ </fault>
+ </operation>
+ <operation name="ABGroupAdd">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABGroupAdd"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABGroupUpdate">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABGroupUpdate"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABGroupDelete">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABGroupDelete"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABGroupContactDelete">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABGroupContactDelete"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABContactUpdate">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABContactUpdate"/>
+ <input>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </input>
+ <output>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ <soap:body use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABAdd">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABAdd"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ <operation name="UpdateDynamicItem">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/UpdateDynamicItem"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ <operation name="ABFindContactsPaged">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ABFindContactsPaged"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ <operation name="CreateContact">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/CreateContact"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ <operation name="ManageWLConnection">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/ManageWLConnection"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ABHeader" part="ApplicationHeader" use="literal"/>
+ <soap:header message="msnab:ABHeader" part="AuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:ServiceHeader" part="ServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ </binding>
+ <binding name="WhatsUpServiceBinding" type="msnab:WhatsUpServicePortType">
+ <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
+ <operation name="GetContactsRecentActivity">
+ <soap:operation soapAction="http://www.msn.com/webservices/AddressBook/GetContactsRecentActivity"/>
+ <input>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:WNHeader" part="WNApplicationHeader" use="literal"/>
+ <soap:header message="msnab:WNHeader" part="WNAuthHeader" use="literal"/>
+ </input>
+ <output>
+ <soap:body use="literal"/>
+ <soap:header message="msnab:WNHeader" part="WNServiceHeader" use="literal"/>
+ </output>
+ </operation>
+ </binding>
+ <service name="SharingService">
+ <port name="FindMembershipPort" binding="msnab:SharingServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/SharingService.asmx"/>
+ </port>
+ <port name="AddMemberPort" binding="msnab:SharingServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/SharingService.asmx"/>
+ </port>
+ <port name="DeleteMemberPort" binding="msnab:SharingServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/SharingService.asmx"/>
+ </port>
+ <port name="CreateCirclePort" binding="msnab:SharingServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/SharingService.asmx"/>
+ </port>
+ </service>
+ <service name="ABService">
+ <port name="ABFindAllPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABContactAddPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABContactDeletePort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABGroupContactAddPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABGroupAddPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABGroupUpdatePort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABGroupDeletePort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABGroupContactDeletePort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABContactUpdatePort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABAddPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="UpdateDynamicItemPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ABFindContactsPagedPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="CreateContactPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ <port name="ManageWLConnectionPort" binding="msnab:ABServiceBinding">
+ <soap:address location="https://contacts.msn.com/abservice/abservice.asmx"/>
+ </port>
+ </service>
+ <service name="WhatsUpService">
+ <port name="GetContactsRecentActivityPort" binding="msnab:WhatsUpServiceBinding">
+ <soap:address location="http://sup.live.com/whatsnew/whatsnewservice.asmx"/>
+ </port>
+ </service>
+</definitions>
diff --git a/plugins/Msn/msn_waiting_message.php b/plugins/Msn/msn_waiting_message.php
new file mode 100644
index 000000000..0af7c4f3e
--- /dev/null
+++ b/plugins/Msn/msn_waiting_message.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Table Definition for msn_waiting_message
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Msn_waiting_message extends Memcached_DataObject {
+
+ public $__table = 'msn_waiting_message'; // table name
+ public $id; // int primary_key not_null auto_increment
+ public $screenname; // varchar(255) not_null
+ public $message; // text not_null
+ public $created; // datetime() not_null
+ public $claimed; // datetime()
+
+ /* Static get */
+ public function staticGet($k, $v = null) {
+ return Memcached_DataObject::staticGet('Msn_waiting_message', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+ public function table() {
+ return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'screenname' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'message' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'claimed' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has, since it
+ * won't appear in StatusNet's own keys list. In most cases, this will
+ * simply reference your keyTypes() function.
+ *
+ * @return array list of key field names
+ */
+ public function keys() {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+ public function keyTypes() {
+ return array('id' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+ function sequenceKey() {
+ return array(false, false, false);
+ }
+
+ /**
+ * @param string $screenname screenname or array of screennames to pull from
+ * If not specified, checks all queues in the system.
+ */
+ public static function top($screenname = null) {
+ $wm = new Msn_waiting_message();
+ if ($screenname) {
+ if (is_array($screenname)) {
+ // @fixme use safer escaping
+ $list = implode("','", array_map('addslashes', $screenname));
+ $wm->whereAdd("screenname in ('$list')");
+ } else {
+ $wm->screenname = $screenname;
+ }
+ }
+ $wm->orderBy('created');
+ $wm->whereAdd('claimed is null');
+
+ $wm->limit(1);
+
+ $cnt = $wm->find(true);
+
+ if ($cnt) {
+ # XXX: potential race condition
+ # can we force it to only update if claimed is still null
+ # (or old)?
+ common_log(LOG_INFO, 'claiming msn waiting message id = ' . $wm->id);
+ $orig = clone($wm);
+ $wm->claimed = common_sql_now();
+ $result = $wm->update($orig);
+ if ($result) {
+ common_log(LOG_INFO, 'claim succeeded.');
+ return $wm;
+ } else {
+ common_log(LOG_INFO, 'claim failed.');
+ }
+ }
+ $wm = null;
+ return null;
+ }
+
+ /**
+ * Release a claimed item.
+ */
+ public function releaseClaim() {
+ // DB_DataObject doesn't let us save nulls right now
+ $sql = sprintf("UPDATE msn_waiting_message SET claimed=NULL WHERE id=%d", $this->id);
+ $this->query($sql);
+
+ $this->claimed = null;
+ $this->encache();
+ }
+}
diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php
new file mode 100644
index 000000000..a8996ecaf
--- /dev/null
+++ b/plugins/Msn/msnmanager.php
@@ -0,0 +1,275 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * MSN background connection manager for MSN-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 MSN enabled.
+ */
+
+class MsnManager extends ImManager {
+ public $conn = null;
+ protected $lastPing = null;
+ protected $pingInterval;
+
+ /**
+ * Initialise connection to server.
+ *
+ * @return boolean true on success
+ */
+ public function start($master) {
+ if (parent::start($master)) {
+ $this->requeue_waiting_messages();
+ $this->connect();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return any open sockets that the run loop should listen
+ * for input on.
+ *
+ * @return array Array of socket resources
+ */
+ public function getSockets() {
+ $this->connect();
+ if ($this->conn) {
+ return $this->conn->getSockets();
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Idle processing for io manager's execution loop.
+ * Send keepalive pings to server.
+ *
+ * @return void
+ */
+ public function idle($timeout = 0) {
+ if (empty($this->lastPing) || time() - $this->lastPing > $this->pingInterval) {
+ $this->send_ping();
+ }
+ }
+
+ /**
+ * Message pump is triggered on socket input, so we only need an idle()
+ * call often enough to trigger our outgoing pings.
+ */
+ public function timeout() {
+ return $this->pingInterval;
+ }
+
+ /**
+ * Process MSN events that have come in over the wire.
+ *
+ * @param resource $socket Socket ready
+ * @return void
+ */
+ public function handleInput($socket) {
+ common_log(LOG_DEBUG, 'Servicing the MSN queue.');
+ $this->stats('msn_process');
+ $this->conn->receive();
+ }
+
+ /**
+ * Initiate connection
+ *
+ * @return void
+ */
+ public function connect() {
+ if (!$this->conn) {
+ $this->conn = new MSN(
+ array(
+ 'user' => $this->plugin->user,
+ 'password' => $this->plugin->password,
+ 'alias' => $this->plugin->nickname,
+ 'psm' => 'Send me a message to post a notice',
+ 'debug' => false
+ )
+ );
+ $this->conn->registerHandler('IMin', array($this, 'handle_msn_message'));
+ $this->conn->registerHandler('SessionReady', array($this, 'handle_session_ready'));
+ $this->conn->registerHandler('Pong', array($this, 'update_ping_time'));
+ $this->conn->registerHandler('ConnectFailed', array($this, 'handle_connect_failed'));
+ $this->conn->registerHandler('Reconnect', array($this, 'handle_reconnect'));
+ $this->conn->signon();
+ $this->lastPing = time();
+ }
+ return $this->conn;
+ }
+
+ /**
+ * Called by the idle process to send a ping
+ * when necessary
+ *
+ * @return void
+ */
+ protected function send_ping() {
+ $this->connect();
+ if (!$this->conn) {
+ return false;
+ }
+
+ $this->conn->sendPing();
+ $this->lastPing = time();
+ $this->pingInterval = 50;
+ return true;
+ }
+
+ /**
+ * Update the time till the next ping
+ *
+ * @param $data Time till next ping
+ * @return void
+ */
+ public function update_ping_time($data) {
+ $this->pingInterval = $data;
+ }
+
+ /**
+ * Called via a callback when a message is received
+ *
+ * Passes it back to the queuing system
+ *
+ * @param array $data Data
+ * @return boolean
+ */
+ public function handle_msn_message($data) {
+ $this->plugin->enqueueIncomingRaw($data);
+ return true;
+ }
+
+ /**
+ * Called via a callback when a session becomes ready
+ *
+ * @param array $data Data
+ */
+ public function handle_session_ready($data) {
+ $sessionFailed = false;
+ $wm = Msn_waiting_message::top($data['to']);
+ while ($wm != NULL) {
+ if ($sessionFailed) {
+ $this->plugin->sendMessage($wm->screenname, $wm->message);
+ $sessionFailed = true;
+ } elseif (!$this->conn->sendMessage($wm->screenname, $wm->message, $ignore)) {
+ $this->plugin->sendMessage($wm->screenname, $wm->message);
+ }
+
+ $wm->delete();
+ $wm = Msn_waiting_message::top($data['to']);
+ }
+ }
+
+ /**
+ * Requeue messages from the waiting table so we try
+ * to send them again
+ *
+ * @return void
+ */
+ protected function requeue_waiting_messages() {
+ $wm = Msn_waiting_message::top();
+ while ($wm != NULL) {
+ $this->plugin->sendMessage($wm->screenname, $wm->message);
+ $wm->delete();
+ $wm = Msn_waiting_message::top();
+ }
+ }
+
+ /**
+ * Called by callback to log failure during connect
+ *
+ * @param string $message error message reported
+ * @return void
+ */
+ public function handle_connect_failed($message) {
+ common_log(LOG_NOTICE, 'MSN connect failed, retrying: ' . $message);
+ }
+
+ /**
+ * Called by callback to log reconnection
+ *
+ * @param void $data Not used (there to keep callback happy)
+ * @return void
+ */
+ public function handle_reconnect($data) {
+ common_log(LOG_NOTICE, 'MSN reconnecting');
+ // Requeue messages waiting in the DB
+ $this->requeue_waiting_messages();
+ }
+
+ /**
+ * Enters a message into the database for sending via a callback
+ * when the session is established
+ *
+ * @param string $to Intended recipient
+ * @param string $message Message
+ */
+ protected function enqueue_waiting_message($to, $message) {
+ $wm = new Msn_waiting_message();
+
+ $wm->screenname = $to;
+ $wm->message = $message;
+ $wm->created = common_sql_now();
+ $result = $wm->insert();
+
+ if (!$result) {
+ common_log_db_error($wm, 'INSERT', __FILE__);
+ throw new ServerException('DB error inserting queue item');
+ }
+
+ return true;
+ }
+
+ /**
+ * Send a message using the daemon
+ *
+ * @param $data Message data
+ * @return boolean true on success
+ */
+ public function send_raw_message($data) {
+ $this->connect();
+ if (!$this->conn) {
+ return false;
+ }
+
+ $waitForSession = false;
+ if (!$this->conn->sendMessage($data['to'], $data['message'], $waitForSession)) {
+ if ($waitForSession) {
+ $this->enqueue_waiting_message($data['to'], $data['message']);
+ } else {
+ return false;
+ }
+ }
+
+ // Sending a command updates the time till next ping
+ $this->lastPing = time();
+ $this->pingInterval = 50;
+ return true;
+ }
+}
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index 4ab2023cb..e38d52d3d 100644
--- a/plugins/OStatus/OStatusPlugin.php
+++ b/plugins/OStatus/OStatusPlugin.php
@@ -52,8 +52,6 @@ class OStatusPlugin extends Plugin
function onRouterInitialized($m)
{
// Discovery actions
- $m->connect('.well-known/host-meta',
- array('action' => 'hostmeta'));
$m->connect('main/xrd',
array('action' => 'userxrd'));
$m->connect('main/ownerxrd',
@@ -1011,4 +1009,12 @@ class OStatusPlugin extends Plugin
return true;
}
+
+ function onStartHostMetaLinks(&$links) {
+ $url = common_local_url('userxrd');
+ $url.= '?uri={uri}';
+ $links[] = array('rel' => Discovery::LRDD_REL,
+ 'template' => $url,
+ 'title' => array('Resource Descriptor'));
+ }
}
diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php
deleted file mode 100644
index c8cffed9c..000000000
--- a/plugins/OStatus/lib/xrd.php
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * A sample module to show best practices for StatusNet plugins
- *
- * PHP version 5
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * @package StatusNet
- * @author James Walker <james@status.net>
- * @copyright 2010 StatusNet, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link http://status.net/
- */
-
-class XRD
-{
- const XML_NS = 'http://www.w3.org/2000/xmlns/';
-
- const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0';
-
- const HOST_META_NS = 'http://host-meta.net/xrd/1.0';
-
- public $expires;
-
- public $subject;
-
- public $host;
-
- public $alias = array();
-
- public $types = array();
-
- public $links = array();
-
- public static function parse($xml)
- {
- $xrd = new XRD();
-
- $dom = new DOMDocument();
-
- // Don't spew XML warnings to output
- $old = error_reporting();
- error_reporting($old & ~E_WARNING);
- $ok = $dom->loadXML($xml);
- error_reporting($old);
-
- if (!$ok) {
- // TRANS: Exception.
- throw new Exception(_m('Invalid XML.'));
- }
- $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
- if (!$xrd_element) {
- // TRANS: Exception.
- throw new Exception(_m('Invalid XML, missing XRD root.'));
- }
-
- // Check for host-meta host
- $host = $xrd_element->getElementsByTagName('Host')->item(0);
- if ($host) {
- $xrd->host = $host->nodeValue;
- }
-
- // Loop through other elements
- foreach ($xrd_element->childNodes as $node) {
- if (!($node instanceof DOMElement)) {
- continue;
- }
- switch ($node->tagName) {
- case 'Expires':
- $xrd->expires = $node->nodeValue;
- break;
- case 'Subject':
- $xrd->subject = $node->nodeValue;
- break;
-
- case 'Alias':
- $xrd->alias[] = $node->nodeValue;
- break;
-
- case 'Link':
- $xrd->links[] = $xrd->parseLink($node);
- break;
-
- case 'Type':
- $xrd->types[] = $xrd->parseType($node);
- break;
-
- }
- }
- return $xrd;
- }
-
- public function toXML()
- {
- $xs = new XMLStringer();
-
- $xs->startXML();
- $xs->elementStart('XRD', array('xmlns' => XRD::XRD_NS));
-
- if ($this->host) {
- $xs->element('hm:Host', array('xmlns:hm' => XRD::HOST_META_NS), $this->host);
- }
-
- if ($this->expires) {
- $xs->element('Expires', null, $this->expires);
- }
-
- if ($this->subject) {
- $xs->element('Subject', null, $this->subject);
- }
-
- foreach ($this->alias as $alias) {
- $xs->element('Alias', null, $alias);
- }
-
- foreach ($this->links as $link) {
- $titles = array();
- if (isset($link['title'])) {
- $titles = $link['title'];
- unset($link['title']);
- }
- $xs->elementStart('Link', $link);
- foreach ($titles as $title) {
- $xs->element('Title', null, $title);
- }
- $xs->elementEnd('Link');
- }
-
- $xs->elementEnd('XRD');
-
- return $xs->getString();
- }
-
- function parseType($element)
- {
- return array();
- }
-
- function parseLink($element)
- {
- $link = array();
- $link['rel'] = $element->getAttribute('rel');
- $link['type'] = $element->getAttribute('type');
- $link['href'] = $element->getAttribute('href');
- $link['template'] = $element->getAttribute('template');
- foreach ($element->childNodes as $node) {
- if ($node instanceof DOMElement) {
- switch($node->tagName) {
- case 'Title':
- $link['title'][] = $node->nodeValue;
- }
- }
- }
-
- return $link;
- }
-}
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index a033a5010..9c3207452 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -636,6 +636,28 @@ class OpenIDPlugin extends Plugin
}
/**
+ * Add OpenID information to the Account Management Control Document
+ * Event supplied by the Account Manager plugin
+ *
+ * @param array &$amcd Array that expresses the AMCD
+ *
+ * @return boolean hook value
+ */
+
+ function onEndAccountManagementControlDocument(&$amcd)
+ {
+ $amcd['auth-methods']['openid'] = array(
+ 'connect' => array(
+ 'method' => 'POST',
+ 'path' => common_local_url('openidlogin'),
+ 'params' => array(
+ 'identity' => 'openid_url'
+ )
+ )
+ );
+ }
+
+ /**
* Add our version information to output
*
* @param array &$versions Array of version-data arrays
diff --git a/plugins/OpenID/openidlogin.php b/plugins/OpenID/openidlogin.php
index 4046068cf..8d25a2e9a 100644
--- a/plugins/OpenID/openidlogin.php
+++ b/plugins/OpenID/openidlogin.php
@@ -44,14 +44,6 @@ class OpenidloginAction extends Action
oid_assert_allowed($openid_url);
- # CSRF protection
- $token = $this->trimmed('token');
- if (!$token || $token != common_session_token()) {
- // TRANS: Message given when there is a problem with the user's session token.
- $this->showForm(_m('There was a problem with your session token. Try again, please.'), $openid_url);
- return;
- }
-
$rememberme = $this->boolean('rememberme');
common_ensure_session();
@@ -138,7 +130,6 @@ class OpenidloginAction extends Action
$this->elementStart('fieldset');
// TRANS: OpenID plugin logon form legend.
$this->element('legend', null, _m('OpenID login'));
- $this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php
index 2963e8997..25a463c0b 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/Recaptcha/RecaptchaPlugin.php b/plugins/Recaptcha/RecaptchaPlugin.php
index 0c46a33e0..08557cbd8 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;
}
@@ -118,4 +120,4 @@ class RecaptchaPlugin extends Plugin
'captcha to the registration page.'));
return true;
}
-}
+} \ No newline at end of file
diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php
index 5e2e85878..24250f4d0 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 b8e5addb1..8fd645945 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 41f64bb26..000000000
--- a/plugins/UrlShortener/UrlShortenerPlugin.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Superclass for plugins that do URL shortening
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * @category Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Superclass for plugins that do URL shortening
- *
- * @category Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-abstract class UrlShortenerPlugin extends Plugin
-{
- public $shortenerName;
- public $freeService=false;
- //------------Url Shortener 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;
- }
- }
- }
-}
diff --git a/plugins/Xmpp/Queued_XMPP.php b/plugins/Xmpp/Queued_XMPP.php
new file mode 100644
index 000000000..24f542805
--- /dev/null
+++ b/plugins/Xmpp/Queued_XMPP.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Queue-mediated proxy class for outgoing XMPP messages.
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Network
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+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->enqueueOutgoingRaw($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/OStatus/actions/hostmeta.php b/plugins/Xmpp/Sharing_XMPP.php
index 14f69ac19..4b69125da 100644
--- a/plugins/OStatus/actions/hostmeta.php
+++ b/plugins/Xmpp/Sharing_XMPP.php
@@ -1,7 +1,11 @@
<?php
-/*
+/**
* StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Send and receive notices using the Jabber network
+ *
+ * PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -15,34 +19,25 @@
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * @package OStatusPlugin
- * @maintainer James Walker <james@status.net>
+ *
+ * @category Jabber
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @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 HostMetaAction extends Action
+class Sharing_XMPP extends XMPPHP_XMPP
{
- function handle()
+ function getSocket()
{
- parent::handle();
-
- $domain = common_config('site', 'server');
- $url = common_local_url('userxrd');
- $url.= '?uri={uri}';
-
- $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();
+ return $this->socket;
}
}
diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php
new file mode 100644
index 000000000..200254178
--- /dev/null
+++ b/plugins/Xmpp/XmppPlugin.php
@@ -0,0 +1,433 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Send and receive notices using the XMPP network
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category IM
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @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 <evan@status.net>
+ * @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';
+
+ function getDisplayName(){
+ return _m('XMPP/Jabber/GTalk');
+ }
+
+ /**
+ * Splits a Jabber ID (JID) into node, domain, and resource portions.
+ *
+ * Based on validation routine submitted by:
+ * @copyright 2009 Patrick Georgi <patrick@georgi-clan.de>
+ * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact.
+ *
+ * @param string $jid string to check
+ *
+ * @return array with "node", "domain", and "resource" indices
+ * @throws Exception if input is not valid
+ */
+
+ protected function splitJid($jid)
+ {
+ $chars = '';
+ /* the following definitions come from stringprep, Appendix C,
+ which is used in its entirety by nodeprop, Chapter 5, "Prohibited Output" */
+ /* C1.1 ASCII space characters */
+ $chars .= "\x{20}";
+ /* C1.2 Non-ASCII space characters */
+ $chars .= "\x{a0}\x{1680}\x{2000}-\x{200b}\x{202f}\x{205f}\x{3000a}";
+ /* C2.1 ASCII control characters */
+ $chars .= "\x{00}-\x{1f}\x{7f}";
+ /* C2.2 Non-ASCII control characters */
+ $chars .= "\x{80}-\x{9f}\x{6dd}\x{70f}\x{180e}\x{200c}\x{200d}\x{2028}\x{2029}\x{2060}-\x{2063}\x{206a}-\x{206f}\x{feff}\x{fff9}-\x{fffc}\x{1d173}-\x{1d17a}";
+ /* C3 - Private Use */
+ $chars .= "\x{e000}-\x{f8ff}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}";
+ /* C4 - Non-character code points */
+ $chars .= "\x{fdd0}-\x{fdef}\x{fffe}\x{ffff}\x{1fffe}\x{1ffff}\x{2fffe}\x{2ffff}\x{3fffe}\x{3ffff}\x{4fffe}\x{4ffff}\x{5fffe}\x{5ffff}\x{6fffe}\x{6ffff}\x{7fffe}\x{7ffff}\x{8fffe}\x{8ffff}\x{9fffe}\x{9ffff}\x{afffe}\x{affff}\x{bfffe}\x{bffff}\x{cfffe}\x{cffff}\x{dfffe}\x{dffff}\x{efffe}\x{effff}\x{ffffe}\x{fffff}\x{10fffe}\x{10ffff}";
+ /* C5 - Surrogate codes */
+ $chars .= "\x{d800}-\x{dfff}";
+ /* C6 - Inappropriate for plain text */
+ $chars .= "\x{fff9}-\x{fffd}";
+ /* C7 - Inappropriate for canonical representation */
+ $chars .= "\x{2ff0}-\x{2ffb}";
+ /* C8 - Change display properties or are deprecated */
+ $chars .= "\x{340}\x{341}\x{200e}\x{200f}\x{202a}-\x{202e}\x{206a}-\x{206f}";
+ /* C9 - Tagging characters */
+ $chars .= "\x{e0001}\x{e0020}-\x{e007f}";
+
+ /* Nodeprep forbids some more characters */
+ $nodeprepchars = $chars;
+ $nodeprepchars .= "\x{22}\x{26}\x{27}\x{2f}\x{3a}\x{3c}\x{3e}\x{40}";
+
+ $parts = explode("/", $jid, 2);
+ if (count($parts) > 1) {
+ $resource = $parts[1];
+ if ($resource == '') {
+ // Warning: empty resource isn't legit.
+ // But if we're normalizing, we may as well take it...
+ }
+ } else {
+ $resource = null;
+ }
+
+ $node = explode("@", $parts[0]);
+ if ((count($node) > 2) || (count($node) == 0)) {
+ throw new Exception("Invalid JID: too many @s");
+ } else if (count($node) == 1) {
+ $domain = $node[0];
+ $node = null;
+ } else {
+ $domain = $node[1];
+ $node = $node[0];
+ if ($node == '') {
+ throw new Exception("Invalid JID: @ but no node");
+ }
+ }
+
+ // Length limits per http://xmpp.org/rfcs/rfc3920.html#addressing
+ if ($node !== null) {
+ if (strlen($node) > 1023) {
+ throw new Exception("Invalid JID: node too long.");
+ }
+ if (preg_match("/[".$nodeprepchars."]/u", $node)) {
+ throw new Exception("Invalid JID node '$node'");
+ }
+ }
+
+ if (strlen($domain) > 1023) {
+ throw new Exception("Invalid JID: domain too long.");
+ }
+ if (!common_valid_domain($domain)) {
+ throw new Exception("Invalid JID domain name '$domain'");
+ }
+
+ if ($resource !== null) {
+ if (strlen($resource) > 1023) {
+ throw new Exception("Invalid JID: resource too long.");
+ }
+ if (preg_match("/[".$chars."]/u", $resource)) {
+ throw new Exception("Invalid JID resource '$resource'");
+ }
+ }
+
+ return array('node' => is_null($node) ? null : mb_strtolower($node),
+ 'domain' => is_null($domain) ? null : mb_strtolower($domain),
+ 'resource' => $resource);
+ }
+
+ /**
+ * Checks whether a string is a syntactically valid Jabber ID (JID),
+ * either with or without a resource.
+ *
+ * Note that a bare domain can be a valid JID.
+ *
+ * @param string $jid string to check
+ * @param bool $check_domain whether we should validate that domain...
+ *
+ * @return boolean whether the string is a valid JID
+ */
+ protected function validateFullJid($jid, $check_domain=false)
+ {
+ try {
+ $parts = $this->splitJid($jid);
+ if ($check_domain) {
+ if (!$this->checkDomain($parts['domain'])) {
+ return false;
+ }
+ }
+ return $parts['resource'] !== ''; // missing or present; empty ain't kosher
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether a string is a syntactically valid base Jabber ID (JID).
+ * A base JID won't include a resource specifier on the end; since we
+ * take it off when reading input we can't really use them reliably
+ * to direct outgoing messages yet (sorry guys!)
+ *
+ * Note that a bare domain can be a valid JID.
+ *
+ * @param string $jid string to check
+ * @param bool $check_domain whether we should validate that domain...
+ *
+ * @return boolean whether the string is a valid JID
+ */
+ protected function validateBaseJid($jid, $check_domain=false)
+ {
+ try {
+ $parts = $this->splitJid($jid);
+ if ($check_domain) {
+ if (!$this->checkDomain($parts['domain'])) {
+ return false;
+ }
+ }
+ return ($parts['resource'] === null); // missing; empty ain't kosher
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Normalizes a Jabber ID for comparison, dropping the resource component if any.
+ *
+ * @param string $jid JID to check
+ * @param bool $check_domain if true, reject if the domain isn't findable
+ *
+ * @return string an equivalent JID in normalized (lowercase) form
+ */
+
+ function normalize($jid)
+ {
+ try {
+ $parts = $this->splitJid($jid);
+ if ($parts['node'] !== null) {
+ return $parts['node'] . '@' . $parts['domain'];
+ } else {
+ return $parts['domain'];
+ }
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Check if this domain's got some legit DNS record
+ */
+ protected function checkDomain($domain)
+ {
+ if (checkdnsrr("_xmpp-server._tcp." . $domain, "SRV")) {
+ return true;
+ }
+ if (checkdnsrr($domain, "ANY")) {
+ return true;
+ }
+ return false;
+ }
+
+ function daemonScreenname()
+ {
+ $ret = $this->user . '@' . $this->server;
+ if($this->resource)
+ {
+ return $ret . '/' . $this->resource;
+ }else{
+ return $ret;
+ }
+ }
+
+ function validate($screenname)
+ {
+ return $this->validateBaseJid($screenname, common_config('email', 'check_domain'));
+ }
+
+ /**
+ * 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 'XMPPHP_XMPP':
+ require_once $dir . '/extlib/XMPPHP/XMPP.php';
+ return false;
+ case 'Sharing_XMPP':
+ case 'Queued_XMPP':
+ require_once $dir . '/'.$cls.'.php';
+ return false;
+ case 'XmppManager':
+ require_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 sendMessage($screenname, $body)
+ {
+ $this->queuedConnection()->message($screenname, $body, 'chat');
+ }
+
+ function sendNotice($screenname, $notice)
+ {
+ $msg = $this->formatNotice($notice);
+ $entry = $this->format_entry($notice);
+
+ $this->queuedConnection()->message($screenname, $msg, 'chat', null, $entry);
+ 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 receiveRawMessage($pl)
+ {
+ $from = $this->normalize($pl['from']);
+
+ if ($pl['type'] != 'chat') {
+ $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from: " . $pl['xml']->toString());
+ return;
+ }
+
+ if (mb_strlen($pl['body']) == 0) {
+ $this->log(LOG_WARNING, "Ignoring message with empty body from $from: " . $pl['xml']->toString());
+ return;
+ }
+
+ $this->handleIncoming($from, $pl['body']);
+
+ return true;
+ }
+
+ /**
+ * 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");
+ }
+ 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");
+ }
+
+ return new Queued_XMPP($this, $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
+ );
+ }
+
+ 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/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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/** XMPPHP_XMLStream */
+require_once dirname(__FILE__) . "/XMPP.php";
+
+/**
+ * XMPPHP Main Class
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ xml_parse($this->parser, $buff, false);
+ $response = $this->__sendBody($body);
+ $rxml = new SimpleXMLElement($response);
+ $this->sid = $rxml['sid'];
+
+ } else {
+ $buff = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ 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("<body xmlns='http://jabber.org/protocol/httpbind' xmlns:xmpp='urn:xmpp:xbosh' />");
+ $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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ $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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Exception
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Log
+ *
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Roster Object
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP XML Object
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 .= "$body</{$this->name}>";
+ 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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 = '<stream>';
+ /**
+ * @var string
+ */
+ protected $stream_end = '</stream>';
+ /**
+ * @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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
+ $this->stream_end = '</stream:stream>';
+ $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 = "<message from=\"{$this->fulljid}\" to=\"$to\" type='$type'>";
+ if($subject) $out .= "<subject>$subject</subject>";
+ $out .= "<body>$body</body>";
+ if($payload) $out .= $payload;
+ $out .= "</message>";
+
+ $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 = "<presence";
+ if($to) $out .= " to=\"$to\"";
+ if($type) $out .= " type='$type'";
+ if($show == 'available' and !$status) {
+ $out .= "/>";
+ } else {
+ $out .= ">";
+ if($show != 'available') $out .= "<show>$show</show>";
+ if($status) $out .= "<status>$status</status>";
+ if($priority) $out .= "<priority>$priority</priority>";
+ $out .= "</presence>";
+ }
+
+ $this->send($out);
+ }
+ /**
+ * Send Auth request
+ *
+ * @param string $jid
+ */
+ public function subscribe($jid) {
+ $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
+ #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
+ }
+
+ /**
+ * 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("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
+ $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
+ }
+ $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("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
+ } elseif($xml->hasSub('bind') and $this->authed) {
+ $id = $this->getId();
+ $this->addIdHandler($id, 'resource_bind_handler');
+ $this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
+ } else {
+ $this->log->log("Attempting Auth...");
+ if ($this->password) {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
+ } else {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ }
+ }
+ }
+
+ /**
+ * 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("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
+ }
+
+ /**
+ * Retrieves the roster
+ *
+ */
+ public function getRoster() {
+ $id = $this->getID();
+ $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
+ }
+
+ /**
+ * 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("<iq type=\"reply\" id=\"{$xml->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("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
+ } else {
+ $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
+ }
+ }
+
+ /**
+ * 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 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @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 = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">';
+ $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("<iq type='get' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username></query></iq>");
+ }
+
+ /**
+ * 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 = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><digest>{$hash}</digest><resource>{$this->resource}</resource></query></iq>";
+ } else {
+ $out = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><password>{$this->password}</password><resource>{$this->resource}</resource></query></iq>";
+ }
+ $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/xmppmanager.php b/plugins/Xmpp/xmppmanager.php
new file mode 100644
index 000000000..1a4e9546d
--- /dev/null
+++ b/plugins/Xmpp/xmppmanager.php
@@ -0,0 +1,279 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * XMPP background connection manager for XMPP-using queue handlers,
+ * allowing them to send outgoing messages on the right connection.
+ *
+ * Input is handled during socket select loop, keepalive pings during idle.
+ * Any incoming messages will be 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("<iq from='{" . $this->plugin->daemonScreenname() . "}' to='{$this->plugin->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
+ $this->lastping = $now;
+ return true;
+ }
+
+ function handle_xmpp_message(&$pl)
+ {
+ $this->plugin->enqueueIncomingRaw($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 = "<presence";
+ if ($to) {
+ $out .= " to='$to'";
+ }
+ if ($type) {
+ $out .= " type='$type'";
+ }
+ if ($show == 'available' and !$status) {
+ $out .= "/>";
+ } else {
+ $out .= ">";
+ if ($show && ($show != 'available')) {
+ $out .= "<show>$show</show>";
+ }
+ if ($status) {
+ $out .= "<status>$status</status>";
+ }
+ $out .= "</presence>";
+ }
+ $this->conn->send($out);
+ return true;
+ }
+}