From: Brion Vibber Date: Mon, 4 Oct 2010 19:54:36 +0000 (-0700) Subject: Merge branch '0.9.x' of gitorious.org:statusnet/mainline into 1.0.x X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=59119482ca34540bd7f0a2a1aa994de1d5328ea2;hp=1652ded48c9c62c40157a5142e5231adbc574ddb;p=quix0rs-gnu-social.git Merge branch '0.9.x' of gitorious.org:statusnet/mainline into 1.0.x Conflicts: actions/hostmeta.php actions/imsettings.php classes/User.php lib/adminpanelaction.php lib/channel.php lib/default.php lib/router.php lib/util.php --- diff --git a/EVENTS.txt b/EVENTS.txt index 2496416173..58189b9c3f 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -569,6 +569,12 @@ EndPublicXRDS: End XRDS output (right before the closing XRDS tag) - $action: the current action - &$xrdsoutputter - XRDSOutputter object to write to +StartHostMetaLinks: Start /.well-known/host-meta links +- &links: array containing the links elements to be written + +EndHostMetaLinks: End /.well-known/host-meta links +- &links: array containing the links elements to be written + StartCheckPassword: Check a username/password - $nickname: The nickname to check - $password: The password to check @@ -734,6 +740,24 @@ StartShowContentLicense: Showing the default license for content EndShowContentLicense: Showing the default license for content - $action: the current action +GetImTransports: Get IM transports that are available +- &$transports: append your transport to this array like so: $transports[transportName]=array('display'=>display) + +NormalizeImScreenname: Normalize an IM screenname +- $transport: transport the screenname is on +- &$screenname: screenname to be normalized + +ValidateImScreenname: Validate an IM screenname +- $transport: transport the screenname is on +- $screenname: screenname to be validated +- $valid: is the screenname valid? + +SendImConfirmationCode: Send a confirmation code to confirm a user owns an IM screenname +- $transport: transport the screenname exists on +- $screenname: screenname being confirmed +- $code: confirmation code for confirmation URL +- $user: user requesting the confirmation + StartUserRegister: When a new user is being registered - &$profile: new profile data (no ID) - &$user: new user account (no ID or URI) diff --git a/README b/README index c4d529f19d..934fb94fc9 100644 --- a/README +++ b/README @@ -862,9 +862,7 @@ sslserver: use an alternate server name for SSL URLs, like parameters correctly so that both the SSL server and the "normal" server can access the session cookie and preferably other cookies as well. -shorturllength: Length of URL at which URLs in a message exceeding 140 - characters will be sent to the user's chosen - shortening service. +shorturllength: ignored. See 'url' section below. dupelimit: minimum time allowed for one person to say the same thing twice. Default 60s. Anything lower is considered a user or UI error. @@ -1487,6 +1485,22 @@ disallow: Array of (virtual) directories to disallow. Default is 'main', 'search', 'message', 'settings', 'admin'. Ignored when site is private, in which case the entire site ('/') is disallowed. +url +--- + +Everybody loves URL shorteners. These are some options for fine-tuning +how and when the server shortens URLs. + +shortener: URL shortening service to use by default. Users can override + individually. 'ur1.ca' by default. +maxlength: If an URL is strictly longer than this limit, it will be + shortened. Note that the URL shortener service may return an + URL longer than this limit. Defaults to 25. Users can + override. If set to 0, all URLs will be shortened. +maxnoticelength: If a notice is strictly longer than this limit, all + URLs in the notice will be shortened. Users can override. + -1 means the text limit for notices. + Plugins ======= diff --git a/actions/apiaccountupdatedeliverydevice.php b/actions/apiaccountupdatedeliverydevice.php index 2d903cb460..e732e23560 100644 --- a/actions/apiaccountupdatedeliverydevice.php +++ b/actions/apiaccountupdatedeliverydevice.php @@ -121,10 +121,16 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction if (strtolower($this->device) == 'sms') { $this->user->smsnotify = true; } elseif (strtolower($this->device) == 'im') { - $this->user->jabbernotify = true; + //TODO IM is pluginized now, so what should we do? + //Enable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = true; } elseif (strtolower($this->device == 'none')) { $this->user->smsnotify = false; - $this->user->jabbernotify = false; + //TODO IM is pluginized now, so what should we do? + //Disable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = false; } $result = $this->user->update($original); diff --git a/actions/apigroupprofileupdate.php b/actions/apigroupprofileupdate.php new file mode 100644 index 0000000000..6ac4b5a4b5 --- /dev/null +++ b/actions/apigroupprofileupdate.php @@ -0,0 +1,367 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * API analog to the group edit page + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupProfileUpdateAction extends ApiAuthAction +{ + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->nickname = common_canonical_nickname($this->trimmed('nickname')); + + $this->fullname = $this->trimmed('fullname'); + $this->homepage = $this->trimmed('homepage'); + $this->description = $this->trimmed('description'); + $this->location = $this->trimmed('location'); + $this->aliasstring = $this->trimmed('aliases'); + + $this->user = $this->auth_user; + $this->group = $this->getTargetGroup($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * See which request params have been set, and update the profile + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found.'), + 404, + $this->format + ); + return; + } + + if (empty($this->user)) { + $this->clientError(_('No such user.'), 404, $this->format); + return; + } + + if (empty($this->group)) { + $this->clientError(_('Group not found.'), 404, $this->format); + return false; + } + + if (!$this->user->isAdmin($this->group)) { + $this->clientError(_('You must be an admin to edit the group.'), 403); + return false; + } + + $this->group->query('BEGIN'); + + $orig = clone($this->group); + + try { + + if (!empty($this->nickname)) { + if ($this->validateNickname()) { + $this->group->nickname = $this->nickname; + $this->group->mainpage = common_local_url( + 'showgroup', + array('nickname' => $this->nickname) + ); + } + } + + if (!empty($this->fullname)) { + $this->validateFullname(); + $this->group->fullname = $this->fullname; + } + + if (!empty($this->homepage)) { + $this->validateHomepage(); + $this->group->homepage = $this->hompage; + } + + if (!empty($this->description)) { + $this->validateDescription(); + $this->group->description = $this->decription; + } + + if (!empty($this->location)) { + $this->validateLocation(); + $this->group->location = $this->location; + } + + } catch (ApiValidationException $ave) { + $this->clientError( + $ave->getMessage(), + 403, + $this->format + ); + return; + } + + $result = $this->group->update($orig); + + if (!$result) { + common_log_db_error($this->group, 'UPDATE', __FILE__); + $this->serverError(_('Could not update group.')); + } + + $aliases = array(); + + try { + + if (!empty($this->aliasstring)) { + $aliases = $this->validateAliases(); + } + + } catch (ApiValidationException $ave) { + $this->clientError( + $ave->getMessage(), + 403, + $this->format + ); + return; + } + + $result = $this->group->setAliases($aliases); + + if (!$result) { + $this->serverError(_('Could not create aliases.')); + } + + if (!empty($this->nickname) && ($this->nickname != $orig->nickname)) { + common_log(LOG_INFO, "Saving local group info."); + $local = Local_group::staticGet('group_id', $this->group->id); + $local->setNickname($this->nickname); + } + + $this->group->query('COMMIT'); + + switch($this->format) { + case 'xml': + $this->showSingleXmlGroup($this->group); + break; + case 'json': + $this->showSingleJsonGroup($this->group); + break; + default: + $this->clientError(_('API method not found.'), 404, $this->format); + break; + } + } + + function nicknameExists($nickname) + { + $group = Local_group::staticGet('nickname', $nickname); + + if (!empty($group) && + $group->group_id != $this->group->id) { + return true; + } + + $alias = Group_alias::staticGet('alias', $nickname); + + if (!empty($alias) && + $alias->group_id != $this->group->id) { + return true; + } + + return false; + } + + function validateNickname() + { + if (!Validate::string( + $this->nickname, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT + ) + ) + ) { + throw new ApiValidationException( + _( + 'Nickname must have only lowercase letters ' . + 'and numbers and no spaces.' + ) + ); + } else if ($this->nicknameExists($this->nickname)) { + throw new ApiValidationException( + _('Nickname already in use. Try another one.') + ); + } else if (!User_group::allowedNickname($this->nickname)) { + throw new ApiValidationException( + _('Not a valid nickname.') + ); + } + + return true; + } + + function validateHomepage() + { + if (!is_null($this->homepage) + && (strlen($this->homepage) > 0) + && !Validate::uri( + $this->homepage, + array('allowed_schemes' => array('http', 'https') + ) + ) + ) { + throw new ApiValidationException( + _('Homepage is not a valid URL.') + ); + } + } + + function validateFullname() + { + if (!is_null($this->fullname) && mb_strlen($this->fullname) > 255) { + throw new ApiValidationException( + _('Full name is too long (max 255 chars).') + ); + } + } + + function validateDescription() + { + if (User_group::descriptionTooLong($this->description)) { + throw new ApiValidationException( + sprintf( + _('description is too long (max %d chars).'), + User_group::maxDescription() + ) + ); + } + } + + function validateLocation() + { + if (!is_null($this->location) && mb_strlen($this->location) > 255) { + throw new ApiValidationException( + _('Location is too long (max 255 chars).') + ); + } + } + + function validateAliases() + { + $aliases = array_map( + 'common_canonical_nickname', + array_unique( + preg_split('/[\s,]+/', + $this->aliasstring + ) + ) + ); + + if (count($aliases) > common_config('group', 'maxaliases')) { + throw new ApiValidationException( + sprintf( + _('Too many aliases! Maximum %d.'), + common_config('group', 'maxaliases') + ) + ); + } + + foreach ($aliases as $alias) { + if (!Validate::string( + $alias, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT) + ) + ) { + throw new ApiValidationException( + sprintf( + _('Invalid alias: "%s"'), + $alias + ) + ); + } + + if ($this->nicknameExists($alias)) { + throw new ApiValidationException( + sprintf( + _('Alias "%s" already in use. Try another one.'), + $alias) + ); + } + + // XXX assumes alphanum nicknames + if (strcmp($alias, $this->nickname) == 0) { + throw new ApiValidationException( + _('Alias can\'t be the same as nickname.') + ); + } + } + + return $aliases; + } + +} \ No newline at end of file diff --git a/actions/confirmaddress.php b/actions/confirmaddress.php index 8bf8c8c4d4..f92db3ec45 100644 --- a/actions/confirmaddress.php +++ b/actions/confirmaddress.php @@ -49,7 +49,7 @@ class ConfirmaddressAction extends Action { /** type of confirmation. */ - var $type = null; + var $address; /** * Accept a confirmation code @@ -86,39 +86,76 @@ class ConfirmaddressAction extends Action return; } $type = $confirm->address_type; - if (!in_array($type, array('email', 'jabber', 'sms'))) { - // TRANS: Server error for an unknow address type, which can be 'email', 'jabber', or 'sms'. - $this->serverError(sprintf(_('Unrecognized address type %s.'), $type)); + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) { + // TRANS: Server error for an unknown address type, which can be 'email', 'sms', or the name of an IM network (such as 'xmpp' or 'aim') + $this->serverError(sprintf(_('Unrecognized address type %s'), $type)); return; } - if ($cur->$type == $confirm->address) { - // TRANS: Client error for an already confirmed email/jabbel/sms address. - $this->clientError(_('That address has already been confirmed.')); - return; - } - + $this->address = $confirm->address; $cur->query('BEGIN'); + if (in_array($type, array('email', 'sms'))) + { + if ($cur->$type == $confirm->address) { + $this->clientError(_('That address has already been confirmed.')); + return; + } + + $orig_user = clone($cur); + + $cur->$type = $confirm->address; + + if ($type == 'sms') { + $cur->carrier = ($confirm->address_extra)+0; + $carrier = Sms_carrier::staticGet($cur->carrier); + $cur->smsemail = $carrier->toEmailAddress($cur->sms); + } + + $result = $cur->updateKeys($orig_user); + + if (!$result) { + common_log_db_error($cur, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user.')); + return; + } + + if ($type == 'email') { + $cur->emailChanged(); + } + + } else { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + if ($user_im_prefs->find() && $user_im_prefs->fetch()) { + if($user_im_prefs->screenname == $confirm->address){ + $this->clientError(_('That address has already been confirmed.')); + return; + } + $user_im_prefs->screenname = $confirm->address; + $result = $user_im_prefs->update(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user im preferences.')); + return; + } + }else{ + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->screenname = $confirm->address; + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + $result = $user_im_prefs->insert(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t insert user im preferences.')); + return; + } + } - $orig_user = clone($cur); - - $cur->$type = $confirm->address; - - if ($type == 'sms') { - $cur->carrier = ($confirm->address_extra)+0; - $carrier = Sms_carrier::staticGet($cur->carrier); - $cur->smsemail = $carrier->toEmailAddress($cur->sms); - } - - $result = $cur->updateKeys($orig_user); - - if (!$result) { - common_log_db_error($cur, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); - return; - } - - if ($type == 'email') { - $cur->emailChanged(); } $result = $confirm->delete(); @@ -130,8 +167,6 @@ class ConfirmaddressAction extends Action } $cur->query('COMMIT'); - - $this->type = $type; $this->showPage(); } @@ -155,11 +190,10 @@ class ConfirmaddressAction extends Action function showContent() { $cur = common_current_user(); - $type = $this->type; $this->element('p', null, sprintf(_('The address "%s" has been '. 'confirmed for your account.'), - $cur->$type)); + $this->address)); } } diff --git a/actions/getfile.php b/actions/getfile.php index 9cbe8e1d99..a45506ce2a 100644 --- a/actions/getfile.php +++ b/actions/getfile.php @@ -129,9 +129,9 @@ class GetfileAction extends Action return null; } - $cache = common_memcache(); + $cache = Cache::instance(); if($cache) { - $key = common_cache_key('attachments:etag:' . $this->path); + $key = Cache::key('attachments:etag:' . $this->path); $etag = $cache->get($key); if($etag === false) { $etag = crc32(file_get_contents($this->path)); diff --git a/actions/hostmeta.php b/actions/hostmeta.php new file mode 100644 index 0000000000..be73665f29 --- /dev/null +++ b/actions/hostmeta.php @@ -0,0 +1,58 @@ +. + */ + +/** + * @category Action + * @package StatusNet + * @maintainer James Walker + * @author Craig Andrews + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class HostMetaAction extends Action +{ + + /** + * Is read only? + * + * @return boolean true + */ + function isReadOnly() + { + return true; + } + + function handle() + { + parent::handle(); + + $domain = common_config('site', 'server'); + + $xrd = new XRD(); + $xrd->host = $domain; + + if(Event::handle('StartHostMetaLinks', array(&$xrd->links))) { + Event::handle('EndHostMetaLinks', array(&$xrd->links)); + } + + header('Content-type: application/xrd+xml'); + print $xrd->toXML(); + } +} diff --git a/actions/imsettings.php b/actions/imsettings.php index 29cbeb82e3..1b1bc0dc0d 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -31,9 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/connectsettingsaction.php'; -require_once INSTALLDIR.'/lib/jabber.php'; - /** * Settings for Jabber/XMPP integration * @@ -72,8 +69,8 @@ class ImsettingsAction extends ConnectSettingsAction // TRANS: [instant messages] is link text, "(%%doc.im%%)" is the link. // TRANS: the order and formatting of link text and link should remain unchanged. return _('You can send and receive notices through '. - 'Jabber/GTalk [instant messages](%%doc.im%%). '. - 'Configure your address and settings below.'); + 'instant messaging [instant messages](%%doc.im%%). '. + 'Configure your addresses and settings below.'); } /** @@ -88,105 +85,124 @@ class ImsettingsAction extends ConnectSettingsAction function showContent() { - if (!common_config('xmpp', 'enabled')) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (! $transports) { $this->element('div', array('class' => 'error'), - // TRANS: Message given in the IM settings if XMPP is not enabled on the site. + // TRANS: Message given in the IM settings if IM is not enabled on the site. _('IM is not available.')); return; } $user = common_current_user(); - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_settings_im', - 'class' => 'form_settings', - 'action' => - common_local_url('imsettings'))); - $this->elementStart('fieldset', array('id' => 'settings_im_address')); - // TRANS: Form legend for IM settings form. - $this->element('legend', null, _('IM address')); - $this->hidden('token', common_session_token()); - - if ($user->jabber) { - $this->element('p', 'form_confirmed', $user->jabber); - // TRANS: Form note in IM settings form. - $this->element('p', 'form_note', - _('Current confirmed Jabber/GTalk address.')); - $this->hidden('jabber', $user->jabber); - // TRANS: Button label to remove a confirmed IM address. - $this->submit('remove', _m('BUTTON','Remove')); - } else { - $confirm = $this->getConfirmation(); - if ($confirm) { - $this->element('p', 'form_unconfirmed', $confirm->address); + + $user_im_prefs_by_transport = array(); + + foreach($transports as $transport=>$transport_info) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_address')); + // TRANS: Form legend for IM settings form. + $this->element('legend', null, $transport_info['display']); + $this->hidden('token', common_session_token()); + $this->hidden('transport', $transport); + + if ($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $transport, 'user_id' => $user->id) )) { + $user_im_prefs_by_transport[$transport] = $user_im_prefs; + $this->element('p', 'form_confirmed', $user_im_prefs->screenname); + // TRANS: Form note in IM settings form. $this->element('p', 'form_note', - // TRANS: Form note in IM settings form. - // TRANS: %s is the IM address set for the site. - sprintf(_('Awaiting confirmation on this address. '. - 'Check your Jabber/GTalk account for a '. - 'message with further instructions. '. - '(Did you add %s to your buddy list?)'), - jabber_daemon_address())); - $this->hidden('jabber', $confirm->address); - // TRANS: Button label to cancel an IM address confirmation procedure. - $this->submit('cancel', _m('BUTTON','Cancel')); + sprintf(_('Current confirmed %s address.'),$transport_info['display'])); + $this->hidden('screenname', $user_im_prefs->screenname); + // TRANS: Button label to remove a confirmed IM address. + $this->submit('remove', _m('BUTTON','Remove')); } else { - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - // TRANS: Field label for IM address input in IM settings form. - $this->input('jabber', _('IM address'), - ($this->arg('jabber')) ? $this->arg('jabber') : null, - // TRANS: IM address input field instructions in IM settings form. - // TRANS: %s is the IM address set for the site. - // TRANS: Do not translate "example.org". It is one of the domain names reserved for use in examples by - // TRANS: http://www.rfc-editor.org/rfc/rfc2606.txt. Any other domain may be owned by a legitimate - // TRANS: person or organization. - sprintf(_('Jabber or GTalk address, '. - 'like "UserName@example.org". '. - 'First, make sure to add %s to your '. - 'buddy list in your IM client or on GTalk.'), - jabber_daemon_address())); - $this->elementEnd('li'); - $this->elementEnd('ul'); - // TRANS: Button label for adding an IM address in IM settings form. - $this->submit('add', _m('BUTTON','Add')); + $confirm = $this->getConfirmation($transport); + if ($confirm) { + $this->element('p', 'form_unconfirmed', $confirm->address); + // TRANS: Form note in IM settings form. + $this->element('p', 'form_note', + // TRANS: Form note in IM settings form. + // TRANS: %s is the IM address set for the site. + sprintf(_('Awaiting confirmation on this address. '. + 'Check your %s account for a '. + 'message with further instructions. '. + '(Did you add %s to your buddy list?)'), + $transport_info['display'], + $transport_info['daemonScreenname'])); + $this->hidden('screenname', $confirm->address); + // TRANS: Button label to cancel an IM address confirmation procedure. + $this->submit('cancel', _m('BUTTON','Cancel')); + } else { + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('screenname', _('IM address'), + ($this->arg('screenname')) ? $this->arg('screenname') : null, + sprintf(_('%s screenname.'), + $transport_info['display'])); + $this->elementEnd('li'); + $this->elementEnd('ul'); + // TRANS: Button label for adding an IM address in IM settings form. + $this->submit('add', _m('BUTTON','Add')); + } } + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + if($user_im_prefs_by_transport) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); + // TRANS: Header for IM preferences form. + $this->element('legend', null, _('IM Preferences')); + $this->hidden('token', common_session_token()); + $this->elementStart('table'); + $this->elementStart('tr'); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $this->element('th', null, $transports[$transport]['display']); + } + $this->elementEnd('tr'); + $preferences = array( + // TRANS: Checkbox label in IM preferences form. + array('name'=>'notify', 'description'=>_('Send me notices')), + // TRANS: Checkbox label in IM preferences form. + array('name'=>'updatefrompresence', 'description'=>_('Post a notice when my status changes.')), + // TRANS: Checkbox label in IM preferences form. + array('name'=>'replies', 'description'=>_('Send me replies '. + 'from people I\'m not subscribed to.')), + // TRANS: Checkbox label in IM preferences form. + array('name'=>'microid', 'description'=>_('Publish a MicroID')) + ); + foreach($preferences as $preference) + { + $this->elementStart('tr'); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $preference_name = $preference['name']; + $this->elementStart('td'); + $this->checkbox($transport . '_' . $preference['name'], + $preference['description'], + $user_im_prefs->$preference_name); + $this->elementEnd('td'); + } + $this->elementEnd('tr'); + } + $this->elementEnd('table'); + // TRANS: Button label to save IM preferences. + $this->submit('save', _m('BUTTON','Save')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); } - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); - // TRANS: Form legend for IM preferences form. - $this->element('legend', null, _('IM preferences')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->checkbox('jabbernotify', - // TRANS: Checkbox label in IM preferences form. - _('Send me notices through Jabber/GTalk.'), - $user->jabbernotify); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('updatefrompresence', - // TRANS: Checkbox label in IM preferences form. - _('Post a notice when my Jabber/GTalk status changes.'), - $user->updatefrompresence); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabberreplies', - // TRANS: Checkbox label in IM preferences form. - _('Send me replies through Jabber/GTalk '. - 'from people I\'m not subscribed to.'), - $user->jabberreplies); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabbermicroid', - // TRANS: Checkbox label in IM preferences form. - _('Publish a MicroID for my Jabber/GTalk address.'), - $user->jabbermicroid); - $this->elementEnd('li'); - $this->elementEnd('ul'); - // TRANS: Button label to save IM preferences. - $this->submit('save', _m('BUTTON','Save')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); } /** @@ -195,14 +211,14 @@ class ImsettingsAction extends ConnectSettingsAction * @return Confirm_address address object for this user */ - function getConfirmation() + function getConfirmation($transport) { $user = common_current_user(); $confirm = new Confirm_address(); $confirm->user_id = $user->id; - $confirm->address_type = 'jabber'; + $confirm->address_type = $transport; if ($confirm->find(true)) { return $confirm; @@ -257,35 +273,33 @@ class ImsettingsAction extends ConnectSettingsAction function savePreferences() { - $jabbernotify = $this->boolean('jabbernotify'); - $updatefrompresence = $this->boolean('updatefrompresence'); - $jabberreplies = $this->boolean('jabberreplies'); - $jabbermicroid = $this->boolean('jabbermicroid'); - $user = common_current_user(); - assert(!is_null($user)); // should already be checked - - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabbernotify = $jabbernotify; - $user->updatefrompresence = $updatefrompresence; - $user->jabberreplies = $jabberreplies; - $user->jabbermicroid = $jabbermicroid; - - $result = $user->update($original); - - if ($result === false) { - common_log_db_error($user, 'UPDATE', __FILE__); - // TRANS: Server error thrown on database error updating IM preferences. - $this->serverError(_('Couldn\'t update user.')); - return; + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->query('BEGIN'); + $user_im_prefs->user_id = $user->id; + if($user_im_prefs->find() && $user_im_prefs->fetch()) + { + $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); + do + { + $original = clone($user_im_prefs); + $new = clone($user_im_prefs); + foreach($preferences as $preference) + { + $new->$preference = $this->boolean($new->transport . '_' . $preference); + } + $result = $new->update($original); + + if ($result === false) { + common_log_db_error($user, 'UPDATE', __FILE__); + // TRANS: Server error thrown on database error updating IM preferences. + $this->serverError(_('Couldn\'t update IM preferences.')); + return; + } + }while($user_im_prefs->fetch()); } - - $user->query('COMMIT'); - + $user_im_prefs->query('COMMIT'); // TRANS: Confirmation message for successful IM preferences save. $this->showForm(_('Preferences saved.'), true); } @@ -294,7 +308,7 @@ class ImsettingsAction extends ConnectSettingsAction * Sends a confirmation to the address given * * Stores a confirmation record and sends out a - * Jabber message with the confirmation info. + * message with the confirmation info. * * @return void */ @@ -303,41 +317,45 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->trimmed('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Some validation - if (!$jabber) { + if (!$screenname) { // TRANS: Message given saving IM address without having provided one. - $this->showForm(_('No Jabber ID.')); + $this->showForm(_('No screenname.')); return; } - $jabber = jabber_normalize_jid($jabber); + if (!$transport) { + $this->showForm(_('No transport.')); + return; + } - if (!$jabber) { + Event::handle('NormalizeImScreenname', array($transport, &$screenname)); + + if (!$screenname) { // TRANS: Message given saving IM address that cannot be normalised. - $this->showForm(_('Cannot normalize that Jabber ID')); + $this->showForm(_('Cannot normalize that screenname')); return; } - if (!jabber_valid_base_jid($jabber, common_config('email', 'domain_check'))) { + $valid = false; + Event::handle('ValidateImScreenname', array($transport, $screenname, &$valid)); + if (!$valid) { // TRANS: Message given saving IM address that not valid. - $this->showForm(_('Not a valid Jabber ID')); - return; - } else if ($user->jabber == $jabber) { - // TRANS: Message given saving IM address that is already set. - $this->showForm(_('That is already your Jabber ID.')); + $this->showForm(_('Not a valid screenname')); return; - } else if ($this->jabberExists($jabber)) { + } else if ($this->screennameExists($transport, $screenname)) { // TRANS: Message given saving IM address that is already set for another user. - $this->showForm(_('Jabber ID already belongs to another user.')); + $this->showForm(_('Screenname already belongs to another user.')); return; } $confirm = new Confirm_address(); - $confirm->address = $jabber; - $confirm->address_type = 'jabber'; + $confirm->address = $screenname; + $confirm->address_type = $transport; $confirm->user_id = $user->id; $confirm->code = common_confirmation_code(64); $confirm->sent = common_sql_now(); @@ -352,17 +370,11 @@ class ImsettingsAction extends ConnectSettingsAction return; } - jabber_confirm_address($confirm->code, - $user->nickname, - $jabber); + Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $user)); // TRANS: Message given saving valid IM address that is to be confirmed. - // TRANS: %s is the IM address set for the site. - $msg = sprintf(_('A confirmation code was sent '. - 'to the IM address you added. '. - 'You must approve %s for '. - 'sending messages to you.'), - jabber_daemon_address()); + $msg = _('A confirmation code was sent '. + 'to the IM address you added.'); $this->showForm($msg, true); } @@ -377,16 +389,17 @@ class ImsettingsAction extends ConnectSettingsAction function cancelConfirmation() { - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); - $confirm = $this->getConfirmation(); + $confirm = $this->getConfirmation($transport); if (!$confirm) { // TRANS: Message given canceling IM address confirmation that is not pending. $this->showForm(_('No pending confirmation to cancel.')); return; } - if ($confirm->address != $jabber) { + if ($confirm->address != $screenname) { // TRANS: Message given canceling IM address confirmation for the wrong IM address. $this->showForm(_('That is the wrong IM address.')); return; @@ -397,7 +410,7 @@ class ImsettingsAction extends ConnectSettingsAction 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 IM confirmation.')); + $this->serverError(_('Couldn\'t delete confirmation.')); return; } @@ -417,32 +430,29 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Maybe an old tab open...? - if ($user->jabber != $jabber) { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $user->id; + if(! ($user_im_prefs->find() && $user_im_prefs->fetch())) { // TRANS: Message given trying to remove an IM address that is not // TRANS: registered for the active user. - $this->showForm(_('That is not your Jabber ID.')); + $this->showForm(_('That is not your screenname.')); return; } - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabber = null; - - $result = $user->updateKeys($original); + $result = $user_im_prefs->delete(); if (!$result) { common_log_db_error($user, 'UPDATE', __FILE__); // TRANS: Server error thrown on database error removing a registered IM address. + $this->serverError(_('Couldn\'t update user im prefs.')); $this->serverError(_('Couldn\'t update user.')); return; } - $user->query('COMMIT'); // XXX: unsubscribe to the old address @@ -451,25 +461,27 @@ class ImsettingsAction extends ConnectSettingsAction } /** - * Does this Jabber ID exist? + * Does this screenname exist? * * Checks if we already have another user with this address. * - * @param string $jabber Address to check + * @param string $transport Transport to check + * @param string $screenname Screenname to check * - * @return boolean whether the Jabber ID exists + * @return boolean whether the screenname exists */ - function jabberExists($jabber) + function screennameExists($transport, $screenname) { $user = common_current_user(); - $other = User::staticGet('jabber', $jabber); - - if (!$other) { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $transport; + $user_im_prefs->screenname = $screenname; + if($user_im_prefs->find() && $user_im_prefs->fetch()){ + return true; + }else{ return false; - } else { - return $other->id != $user->id; } } } diff --git a/actions/login.php b/actions/login.php index d3e4312f71..07c601a4db 100644 --- a/actions/login.php +++ b/actions/login.php @@ -118,27 +118,10 @@ class LoginAction extends Action * @return void */ - function checkLogin($user_id=null, $token=null) + function checkLogin($user_id=null) { // XXX: login throttle - // CSRF protection - token set in NoticeForm - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $st = common_session_token(); - if (empty($token)) { - common_log(LOG_WARNING, 'No token provided by client.'); - } else if (empty($st)) { - common_log(LOG_WARNING, 'No session token stored.'); - } else { - common_log(LOG_WARNING, 'Token = ' . $token . ' and session token = ' . $st); - } - - $this->clientError(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - $nickname = $this->trimmed('nickname'); $password = $this->arg('password'); @@ -261,7 +244,6 @@ class LoginAction extends Action $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('submit', _('Login')); - $this->hidden('token', common_session_token()); $this->elementEnd('fieldset'); $this->elementEnd('form'); $this->elementStart('p'); diff --git a/actions/othersettings.php b/actions/othersettings.php index 10e9873b39..8d6e004047 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -98,8 +98,10 @@ class OthersettingsAction extends AccountSettingsAction $this->hidden('token', common_session_token()); $this->elementStart('ul', 'form_data'); - $shorteners = array(); + $shorteners = array(_('[none]') => array('freeService' => false)); + Event::handle('GetUrlShorteners', array(&$shorteners)); + $services = array(); foreach($shorteners as $name=>$value) { @@ -119,8 +121,22 @@ class OthersettingsAction extends AccountSettingsAction $this->elementEnd('li'); } $this->elementStart('li'); + $this->input('maxurllength', + _('URL longer than'), + (!is_null($this->arg('maxurllength'))) ? + $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), + _('URLs longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->input('maxnoticelength', + _('Text longer than'), + (!is_null($this->arg('maxnoticelength'))) ? + $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), + _('URLs in notices longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); $this->checkbox('viewdesigns', _('View profile designs'), - $user->viewdesigns, _('Show or hide profile designs.')); + - $user->viewdesigns, _('Show or hide profile designs.')); $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('save', _('Save')); @@ -156,6 +172,18 @@ class OthersettingsAction extends AccountSettingsAction $viewdesigns = $this->boolean('viewdesigns'); + $maxurllength = $this->trimmed('maxurllength'); + + if (!Validate::number($maxurllength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max url length.')); + } + + $maxnoticelength = $this->trimmed('maxnoticelength'); + + if (!Validate::number($maxnoticelength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max notice length.')); + } + $user = common_current_user(); assert(!is_null($user)); // should already be checked @@ -175,6 +203,32 @@ class OthersettingsAction extends AccountSettingsAction return; } + $prefs = User_urlshortener_prefs::getPrefs($user); + $orig = null; + + if (empty($prefs)) { + $prefs = new User_urlshortener_prefs(); + + $prefs->user_id = $user->id; + $prefs->created = common_sql_now(); + } else { + $orig = clone($prefs); + } + + $prefs->urlshorteningservice = $urlshorteningservice; + $prefs->maxurllength = $maxurllength; + $prefs->maxnoticelength = $maxnoticelength; + + if (!empty($orig)) { + $result = $prefs->update($orig); + } else { + $result = $prefs->insert(); + } + + if (!$result) { + throw new ServerException(_('Error saving user URL shortening preferences.')); + } + $user->query('COMMIT'); $this->showForm(_('Preferences saved.'), true); diff --git a/actions/plugindisable.php b/actions/plugindisable.php new file mode 100644 index 0000000000..7f107b3335 --- /dev/null +++ b/actions/plugindisable.php @@ -0,0 +1,78 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin enable action. + * + * (Re)-enables a plugin from the default plugins list. + * + * Takes parameters: + * + * - plugin: plugin name + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class PluginDisableAction extends PluginEnableAction +{ + /** + * Value to save into $config['plugins']['disable-'] + */ + protected function overrideValue() + { + return 1; + } + + protected function successShortTitle() + { + // TRANS: Page title for AJAX form return when a disabling a plugin. + return _m('plugin', 'Disabled'); + } + + protected function successNextForm() + { + return new EnablePluginForm($this, $this->plugin); + } +} + + diff --git a/actions/pluginenable.php b/actions/pluginenable.php new file mode 100644 index 0000000000..2dbb3e3956 --- /dev/null +++ b/actions/pluginenable.php @@ -0,0 +1,166 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin enable action. + * + * (Re)-enables a plugin from the default plugins list. + * + * Takes parameters: + * + * - plugin: plugin name + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class PluginEnableAction extends Action +{ + var $user; + var $plugin; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // @fixme these are pretty common, should a parent class factor these out? + + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + if (!AdminPanelAction::canAdmin('plugins')) { + $this->clientError(_('You cannot administer plugins.')); + return false; + } + + $this->plugin = $this->arg('plugin'); + $defaultPlugins = common_config('plugins', 'default'); + if (!array_key_exists($this->plugin, $defaultPlugins)) { + $this->clientError(_('No such plugin.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + $key = 'disable-' . $this->plugin; + Config::save('plugins', $key, $this->overrideValue()); + + // @fixme this is a pretty common pattern and should be refactored down + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, $this->successShortTitle()); + $this->elementEnd('head'); + $this->elementStart('body'); + $form = $this->successNextForm(); + $form->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('pluginsadminpanel'); + common_redirect($url, 303); + } + } + + /** + * Value to save into $config['plugins']['disable-'] + */ + protected function overrideValue() + { + return 0; + } + + protected function successShortTitle() + { + // TRANS: Page title for AJAX form return when enabling a plugin. + return _m('plugin', 'Enabled'); + } + + protected function successNextForm() + { + return new DisablePluginForm($this, $this->plugin); + } +} diff --git a/actions/pluginsadminpanel.php b/actions/pluginsadminpanel.php new file mode 100644 index 0000000000..bc400bd514 --- /dev/null +++ b/actions/pluginsadminpanel.php @@ -0,0 +1,110 @@ +. + * + * @category Settings + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugins settings + * + * @category Admin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PluginsadminpanelAction extends AdminPanelAction +{ + + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + // TRANS: Tab and title for plugins admin panel. + return _('Plugins'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + // TRANS: Instructions at top of plugin admin page. + return _('Additional plugins can be enabled and configured manually. ' . + 'See the online plugin ' . + 'documentation for more details.'); + } + + /** + * Show the plugins admin panel form + * + * @return void + */ + + function showForm() + { + $this->elementStart('fieldset', array('id' => 'settings_plugins_default')); + + // TRANS: Admin form section header + $this->element('legend', null, _('Default plugins'), 'default'); + + $this->showDefaultPlugins(); + + $this->elementEnd('fieldset'); + } + + /** + * Until we have a general plugin metadata infrastructure, for now + * we'll just list up the ones we know from the global default + * plugins list. + */ + protected function showDefaultPlugins() + { + $plugins = array_keys(common_config('plugins', 'default')); + natsort($plugins); + + if ($plugins) { + $list = new PluginList($plugins, $this); + $list->show(); + } else { + $this->element('p', null, + _('All default plugins have been disabled from the ' . + 'site\'s configuration file.')); + } + } +} diff --git a/actions/shownotice.php b/actions/shownotice.php index 005335e3b4..1161de8636 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -278,12 +278,6 @@ class ShownoticeAction extends OwnerDesignAction 'content' => $id->toString())); } - if ($user->jabbermicroid && $user->jabber && $this->notice->uri) { - $id = new Microid('xmpp:', $user->jabber, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } $this->element('link',array('rel'=>'alternate', 'type'=>'application/json+oembed', 'href'=>common_local_url( diff --git a/actions/showstream.php b/actions/showstream.php index 2476f19fab..744b6906eb 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -166,12 +166,6 @@ class ShowstreamAction extends ProfileAction $this->element('meta', array('name' => 'microid', 'content' => $id->toString())); } - if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) { - $id = new Microid('xmpp:'.$this->user->jabber, - $this->selfUrl()); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } // See https://wiki.mozilla.org/Microsummaries diff --git a/actions/subscriptions.php b/actions/subscriptions.php index 7b10b3425b..da563a218f 100644 --- a/actions/subscriptions.php +++ b/actions/subscriptions.php @@ -185,7 +185,9 @@ class SubscriptionsListItem extends SubscriptionListItem return; } - if (!common_config('xmpp', 'enabled') && !common_config('sms', 'enabled')) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (!$transports && !common_config('sms', 'enabled')) { return; } @@ -195,7 +197,7 @@ class SubscriptionsListItem extends SubscriptionListItem 'action' => common_local_url('subedit'))); $this->out->hidden('token', common_session_token()); $this->out->hidden('profile', $this->profile->id); - if (common_config('xmpp', 'enabled')) { + if ($transports) { $attrs = array('name' => 'jabber', 'type' => 'checkbox', 'class' => 'checkbox', @@ -205,7 +207,7 @@ class SubscriptionsListItem extends SubscriptionListItem } $this->out->element('input', $attrs); - $this->out->element('label', array('for' => 'jabber-'.$this->profile->id), _('Jabber')); + $this->out->element('label', array('for' => 'jabber-'.$this->profile->id), _('IM')); } else { $this->out->hidden('jabber', $sub->jabber); } diff --git a/classes/Config.php b/classes/Config.php index 43b99587fa..e14730438e 100644 --- a/classes/Config.php +++ b/classes/Config.php @@ -58,7 +58,7 @@ class Config extends Memcached_DataObject $c = self::memcache(); if (!empty($c)) { - $settings = $c->get(common_cache_key(self::settingsKey)); + $settings = $c->get(Cache::key(self::settingsKey)); if ($settings !== false) { return $settings; } @@ -77,7 +77,7 @@ class Config extends Memcached_DataObject $config->free(); if (!empty($c)) { - $c->set(common_cache_key(self::settingsKey), $settings); + $c->set(Cache::key(self::settingsKey), $settings); } return $settings; @@ -154,7 +154,7 @@ class Config extends Memcached_DataObject $c = self::memcache(); if (!empty($c)) { - $c->delete(common_cache_key(self::settingsKey)); + $c->delete(Cache::key(self::settingsKey)); } } } diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 68fed77e8b..92f0125a40 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -176,22 +176,52 @@ class File_redirection extends Memcached_DataObject * @param string $long_url * @return string */ - function makeShort($long_url) { + function makeShort($long_url) + { $canon = File_redirection::_canonUrl($long_url); $short_url = File_redirection::_userMakeShort($canon); // Did we get one? Is it shorter? - if (!empty($short_url) && mb_strlen($short_url) < mb_strlen($long_url)) { + + if (!empty($short_url)) { + return $short_url; + } else { + return $long_url; + } + } + + /** + * Shorten a URL with the current user's configured shortening + * options, if applicable. + * + * If it cannot be shortened or the "short" URL is longer than the + * original, the original is returned. + * + * If the referenced item has not been seen before, embedding data + * may be saved. + * + * @param string $long_url + * @return string + */ + + function forceShort($long_url) + { + $canon = File_redirection::_canonUrl($long_url); + + $short_url = File_redirection::_userMakeShort($canon, true); + + // Did we get one? Is it shorter? + if (!empty($short_url)) { return $short_url; } else { return $long_url; } } - function _userMakeShort($long_url) { - $short_url = common_shorten_url($long_url); + function _userMakeShort($long_url, $force = false) { + $short_url = common_shorten_url($long_url, $force); if (!empty($short_url) && $short_url != $long_url) { $short_url = (string)$short_url; // store it diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index ccfd886a1d..8ffb46cc52 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -124,7 +124,7 @@ class Memcached_DataObject extends Safe_DataObject } static function memcache() { - return common_memcache(); + return Cache::instance(); } static function cacheKey($cls, $k, $v) { @@ -134,7 +134,7 @@ class Memcached_DataObject extends Safe_DataObject str_replace("\n", " ", $e->getTraceAsString())); } $vstr = self::valueString($v); - return common_cache_key(strtolower($cls).':'.$k.':'.$vstr); + return Cache::key(strtolower($cls).':'.$k.':'.$vstr); } static function getcached($cls, $k, $v) { @@ -302,8 +302,8 @@ class Memcached_DataObject extends Safe_DataObject $inst->query($qry); return $inst; } - $key_part = common_keyize($cls).':'.md5($qry); - $ckey = common_cache_key($key_part); + $key_part = Cache::keyize($cls).':'.md5($qry); + $ckey = Cache::key($key_part); $stored = $c->get($ckey); if ($stored !== false) { @@ -550,7 +550,7 @@ class Memcached_DataObject extends Safe_DataObject $keyPart = vsprintf($format, $args); - $cacheKey = common_cache_key($keyPart); + $cacheKey = Cache::key($keyPart); return $c->delete($cacheKey); } @@ -592,7 +592,7 @@ class Memcached_DataObject extends Safe_DataObject return false; } - $cacheKey = common_cache_key($keyPart); + $cacheKey = Cache::key($keyPart); return $c->get($cacheKey); } @@ -605,7 +605,7 @@ class Memcached_DataObject extends Safe_DataObject return false; } - $cacheKey = common_cache_key($keyPart); + $cacheKey = Cache::key($keyPart); return $c->set($cacheKey, $value, $flag, $expiry); } diff --git a/classes/Notice.php b/classes/Notice.php index e268544b50..a8ec0529f2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -593,7 +593,7 @@ class Notice extends Memcached_DataObject function getStreamByIds($ids) { - $cache = common_memcache(); + $cache = Cache::instance(); if (!empty($cache)) { $notices = array(); @@ -769,7 +769,7 @@ class Notice extends Memcached_DataObject $c = self::memcache(); if (!empty($c)) { - $ni = $c->get(common_cache_key('notice:who_gets:'.$this->id)); + $ni = $c->get(Cache::key('notice:who_gets:'.$this->id)); if ($ni !== false) { return $ni; } @@ -821,7 +821,7 @@ class Notice extends Memcached_DataObject if (!empty($c)) { // XXX: pack this data better - $c->set(common_cache_key('notice:who_gets:'.$this->id), $ni); + $c->set(Cache::key('notice:who_gets:'.$this->id), $ni); } return $ni; @@ -1650,7 +1650,7 @@ class Notice extends Memcached_DataObject function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0) { - $cache = common_memcache(); + $cache = Cache::instance(); if (empty($cache) || $since_id != 0 || $max_id != 0 || @@ -1660,7 +1660,7 @@ class Notice extends Memcached_DataObject $max_id))); } - $idkey = common_cache_key($cachekey); + $idkey = Cache::key($cachekey); $idstr = $cache->get($idkey); @@ -1842,17 +1842,17 @@ class Notice extends Memcached_DataObject function repeatStream($limit=100) { - $cache = common_memcache(); + $cache = Cache::instance(); if (empty($cache)) { $ids = $this->_repeatStreamDirect($limit); } else { - $idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id)); + $idstr = $cache->get(Cache::key('notice:repeats:'.$this->id)); if ($idstr !== false) { $ids = explode(',', $idstr); } else { $ids = $this->_repeatStreamDirect(100); - $cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids)); + $cache->set(Cache::key('notice:repeats:'.$this->id), implode(',', $ids)); } if ($limit < 100) { // We do a max of 100, so slice down to limit @@ -2003,10 +2003,10 @@ class Notice extends Memcached_DataObject if ($tag->find()) { while ($tag->fetch()) { - self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, common_keyize($tag->tag)); - self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, common_keyize($tag->tag)); - self::blow('notice_tag:notice_ids:%s', common_keyize($tag->tag)); - self::blow('notice_tag:notice_ids:%s;last', common_keyize($tag->tag)); + self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, Cache::keyize($tag->tag)); + self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, Cache::keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s', Cache::keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($tag->tag)); $tag->delete(); } } diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index a5d0716a71..9ade36c34a 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -40,7 +40,7 @@ class Notice_tag extends Memcached_DataObject $ids = Notice::stream(array('Notice_tag', '_streamDirect'), array($tag), - 'notice_tag:notice_ids:' . common_keyize($tag), + 'notice_tag:notice_ids:' . Cache::keyize($tag), $offset, $limit); return Notice::getStreamByIds($ids); @@ -82,9 +82,9 @@ class Notice_tag extends Memcached_DataObject function blowCache($blowLast=false) { - self::blow('notice_tag:notice_ids:%s', common_keyize($this->tag)); + self::blow('notice_tag:notice_ids:%s', Cache::keyize($this->tag)); if ($blowLast) { - self::blow('notice_tag:notice_ids:%s;last', common_keyize($this->tag)); + self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($this->tag)); } } diff --git a/classes/Profile.php b/classes/Profile.php index 3844077e62..1d130c44ad 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -428,10 +428,10 @@ class Profile extends Memcached_DataObject function subscriptionCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id)); + $cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id)); if (is_integer($cnt)) { return (int) $cnt; } @@ -445,7 +445,7 @@ class Profile extends Memcached_DataObject $cnt = ($cnt > 0) ? $cnt - 1 : $cnt; if (!empty($c)) { - $c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt); + $c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt); } return $cnt; @@ -453,9 +453,9 @@ class Profile extends Memcached_DataObject function subscriberCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id)); + $cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id)); if (is_integer($cnt)) { return (int) $cnt; } @@ -467,7 +467,7 @@ class Profile extends Memcached_DataObject $cnt = (int) $sub->count('distinct subscriber'); if (!empty($c)) { - $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt); + $c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt); } return $cnt; @@ -475,7 +475,7 @@ class Profile extends Memcached_DataObject function hasFave($notice) { - $cache = common_memcache(); + $cache = Cache::instance(); // XXX: Kind of a hack. @@ -510,9 +510,9 @@ class Profile extends Memcached_DataObject function faveCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id)); + $cnt = $c->get(Cache::key('profile:fave_count:'.$this->id)); if (is_integer($cnt)) { return (int) $cnt; } @@ -523,7 +523,7 @@ class Profile extends Memcached_DataObject $cnt = (int) $faves->count('distinct notice_id'); if (!empty($c)) { - $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt); + $c->set(Cache::key('profile:fave_count:'.$this->id), $cnt); } return $cnt; @@ -531,10 +531,10 @@ class Profile extends Memcached_DataObject function noticeCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id)); + $cnt = $c->get(Cache::key('profile:notice_count:'.$this->id)); if (is_integer($cnt)) { return (int) $cnt; } @@ -545,7 +545,7 @@ class Profile extends Memcached_DataObject $cnt = (int) $notices->count('distinct id'); if (!empty($c)) { - $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt); + $c->set(Cache::key('profile:notice_count:'.$this->id), $cnt); } return $cnt; @@ -567,33 +567,33 @@ class Profile extends Memcached_DataObject function blowSubscriberCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $c->delete(common_cache_key('profile:subscriber_count:'.$this->id)); + $c->delete(Cache::key('profile:subscriber_count:'.$this->id)); } } function blowSubscriptionCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $c->delete(common_cache_key('profile:subscription_count:'.$this->id)); + $c->delete(Cache::key('profile:subscription_count:'.$this->id)); } } function blowFaveCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $c->delete(common_cache_key('profile:fave_count:'.$this->id)); + $c->delete(Cache::key('profile:fave_count:'.$this->id)); } } function blowNoticeCount() { - $c = common_memcache(); + $c = Cache::instance(); if (!empty($c)) { - $c->delete(common_cache_key('profile:notice_count:'.$this->id)); + $c->delete(Cache::key('profile:notice_count:'.$this->id)); } } diff --git a/classes/User.php b/classes/User.php index e784fd9e9a..259df7e2c3 100644 --- a/classes/User.php +++ b/classes/User.php @@ -48,11 +48,6 @@ class User extends Memcached_DataObject public $language; // varchar(50) public $timezone; // varchar(50) public $emailpost; // tinyint(1) default_1 - public $jabber; // varchar(255) unique_key - public $jabbernotify; // tinyint(1) - public $jabberreplies; // tinyint(1) - public $jabbermicroid; // tinyint(1) default_1 - public $updatefrompresence; // tinyint(1) public $sms; // varchar(64) unique_key public $carrier; // int(4) public $smsnotify; // tinyint(1) @@ -93,7 +88,7 @@ class User extends Memcached_DataObject { $this->_connect(); $parts = array(); - foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { + foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { if (strcmp($this->$k, $orig->$k) != 0) { $parts[] = $k . ' = ' . $this->_quote($this->$k); } diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php new file mode 100644 index 0000000000..75be8969e0 --- /dev/null +++ b/classes/User_im_prefs.php @@ -0,0 +1,94 @@ +. + * + * @category Data + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class User_im_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_im_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $screenname; // varchar(255) not_null + public $transport; // varchar(255) not_null + public $notify; // tinyint(1) + public $replies; // tinyint(1) + public $microid; // tinyint(1) + public $updatefrompresence; // tinyint(1) + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_im_prefs',$k,$v); } + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('User_im_prefs', $kv); + } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /* + DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function. + In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but + DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric + type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and + manage the sequence itself. This is not the correct behavior for the user_id in this class. + So we override that incorrect behavior, and simply say there is no sequence key. + */ + function sequenceKey() + { + return array(false,false); + } + + /** + * We have two compound keys with unique constraints: + * (transport, user_id) which is our primary key, and + * (transport, screenname) which is an additional constraint. + * + * Currently there's not a way to represent that second key + * in the general keys list, so we're adding it here to the + * list of keys to use for caching, ensuring that it gets + * cleared as well when we change. + * + * @return array of cache keys + */ + function _allCacheKeys() + { + $ukeys = 'transport,screenname'; + $uvals = $this->transport . ',' . $this->screenname; + + $ckeys = parent::_allCacheKeys(); + $ckeys[] = $this->cacheKey($this->tableName(), $ukeys, $uvals); + return $ckeys; + } + +} diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php new file mode 100755 index 0000000000..e0f85af012 --- /dev/null +++ b/classes/User_urlshortener_prefs.php @@ -0,0 +1,105 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class User_urlshortener_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_urlshortener_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $urlshorteningservice; // varchar(50) default_ur1.ca + public $maxurllength; // int(4) not_null + public $maxnoticelength; // int(4) not_null + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_urlshortener_prefs',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } + + static function maxUrlLength($user) + { + $def = common_config('url', 'maxlength'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxurllength; + } + } + + static function maxNoticeLength($user) + { + $def = common_config('url', 'maxnoticelength'); + + if ($def == -1) { + $def = Notice::maxContent(); + } + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxnoticelength; + } + } + + static function urlShorteningService($user) + { + $def = common_config('url', 'shortener'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + if (!empty($user)) { + return $user->urlshorteningservice; + } else { + return $def; + } + } else { + return $prefs->urlshorteningservice; + } + } + + static function getPrefs($user) + { + if (empty($user)) { + return null; + } + + $prefs = User_urlshortener_prefs::staticGet('user_id', $user->id); + + return $prefs; + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 3fb8ee208b..b57d862263 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -561,11 +561,6 @@ emailmicroid = 17 language = 2 timezone = 2 emailpost = 17 -jabber = 2 -jabbernotify = 17 -jabberreplies = 17 -jabbermicroid = 17 -updatefrompresence = 17 sms = 2 carrier = 1 smsnotify = 17 @@ -585,7 +580,6 @@ id = K nickname = U email = U incomingemail = U -jabber = U sms = U uri = U @@ -638,3 +632,33 @@ modified = 384 [user_location_prefs__keys] user_id = K + +[user_im_prefs] +user_id = 129 +screenname = 130 +transport = 130 +notify = 17 +replies = 17 +microid = 17 +updatefrompresence = 17 +created = 142 +modified = 384 + +[user_im_prefs__keys] +user_id = K +transport = K +; There's another unique index on (transport, screenname) +; but we have no way to represent a compound index other than +; the primary key in here. To ensure proper cache purging, +; we need to tweak the class. + +[user_urlshortener_prefs] +user_id = 129 +urlshorteningservice = 2 +maxurllength = 129 +maxnoticelength = 129 +created = 142 +modified = 384 + +[user_urlshortener_prefs__keys] +user_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index 3f95948e1e..a0c497fff5 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -62,11 +62,6 @@ create table user ( language varchar(50) comment 'preferred language', timezone varchar(50) comment 'timezone', emailpost tinyint default 1 comment 'Post by email', - jabber varchar(255) unique key comment 'jabber ID for notices', - jabbernotify tinyint default 0 comment 'whether to send notices to jabber', - jabberreplies tinyint default 0 comment 'whether to send notices to jabber on replies', - jabbermicroid tinyint default 1 comment 'whether to publish xmpp microid', - updatefrompresence tinyint default 0 comment 'whether to record updates from Jabber presence notices', sms varchar(64) unique key comment 'sms phone number', carrier integer comment 'foreign key to sms_carrier' references sms_carrier (id), smsnotify tinyint default 0 comment 'whether to send notices to SMS', @@ -259,9 +254,9 @@ create table oid_nonces ( create table confirm_address ( code varchar(32) not null primary key comment 'good random code', user_id integer not null comment 'user who requested confirmation' references user (id), - address varchar(255) not null comment 'address (email, Jabber, SMS, etc.)', + address varchar(255) not null comment 'address (email, xmpp, SMS, etc.)', address_extra varchar(255) not null comment 'carrier ID, for SMS', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', claimed datetime comment 'date this was claimed for queueing', sent datetime comment 'date this was sent for queueing', modified timestamp comment 'date this record was modified' @@ -276,7 +271,7 @@ create table remember_me ( create table queue_item ( id integer auto_increment primary key comment 'unique identifier', frame blob not null comment 'data: object reference or opaque string', - transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + transport varchar(8) not null comment 'queue for what? "email", "xmpp", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', @@ -348,7 +343,7 @@ create table invitation ( code varchar(32) not null primary key comment 'random code for an invitation', user_id int not null comment 'who sent the invitation' references user (id), address varchar(255) not null comment 'invitation sent to', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', created datetime not null comment 'date this record was created', index invitation_address_idx (address, address_type), @@ -639,6 +634,21 @@ create table inbox ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table user_im_prefs ( + user_id integer not null comment 'user' references user (id), + screenname varchar(255) not null comment 'screenname on this service', + transport varchar(255) not null comment 'transport (ex xmpp, aim)', + notify tinyint(1) not null default 0 comment 'Notify when a new notice is sent', + replies tinyint(1) not null default 0 comment 'Send replies from people not subscribed to', + microid tinyint(1) not null default 1 comment 'Publish a MicroID', + updatefrompresence tinyint(1) not null default 0 comment 'Send replies from people not subscribed to.', + created timestamp not null DEFAULT CURRENT_TIMESTAMP comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id, transport), + constraint unique key `transport_screenname_key` ( `transport` , `screenname` ) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + create table conversation ( id integer auto_increment primary key comment 'unique identifier', uri varchar(225) unique comment 'URI of the conversation', @@ -656,3 +666,15 @@ create table local_group ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table user_urlshortener_prefs ( + + user_id integer not null comment 'user' references user (id), + urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs', + maxurllength integer not null comment 'urls greater than this length will be shortened, 0 = always, null = never', + maxnoticelength integer not null comment 'notices with content greater than this value will have all urls shortened, 0 = always, null = never', + + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/extlib/Mail.php b/extlib/Mail.php old mode 100644 new mode 100755 index 3a0c1a9cb8..75132ac2a6 --- a/extlib/Mail.php +++ b/extlib/Mail.php @@ -1,22 +1,47 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Mail.php,v 1.17 2006/09/15 03:41:18 jon Exp $ +/** + * PEAR's Mail:: interface. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 1997-2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ require_once 'PEAR.php'; @@ -26,7 +51,7 @@ require_once 'PEAR.php'; * useful in multiple mailer backends. * * @access public - * @version $Revision: 1.17 $ + * @version $Revision: 294747 $ * @package Mail */ class Mail @@ -82,12 +107,20 @@ class Mail * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. + * * @access public * @deprecated use Mail_mail::send instead */ function send($recipients, $headers, $body) { - $this->_sanitizeHeaders($headers); + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } // if we're passed an array of recipients, implode it. if (is_array($recipients)) { @@ -103,10 +136,9 @@ class Mail } // flatten the headers out. - list(,$text_headers) = Mail::prepareHeaders($headers); + list(, $text_headers) = Mail::prepareHeaders($headers); return mail($recipients, $subject, $body, $text_headers); - } /** @@ -151,9 +183,9 @@ class Mail foreach ($headers as $key => $value) { if (strcasecmp($key, 'From') === 0) { include_once 'Mail/RFC822.php'; - $parser = &new Mail_RFC822(); + $parser = new Mail_RFC822(); $addresses = $parser->parseAddressList($value, 'localhost', false); - if (PEAR::isError($addresses)) { + if (is_a($addresses, 'PEAR_Error')) { return $addresses; } @@ -221,7 +253,7 @@ class Mail $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false); // If parseAddressList() returned a PEAR_Error object, just return it. - if (PEAR::isError($addresses)) { + if (is_a($addresses, 'PEAR_Error')) { return $addresses; } diff --git a/extlib/Mail/RFC822.php b/extlib/Mail/RFC822.php old mode 100644 new mode 100755 index 8714df2e29..58d36465cb --- a/extlib/Mail/RFC822.php +++ b/extlib/Mail/RFC822.php @@ -1,37 +1,48 @@ | -// | Chuck Hagenbuch | -// +-----------------------------------------------------------------------+ +/** + * RFC 822 Email address list validation Utility + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2001-2010, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category Mail + * @package Mail + * @author Richard Heyes + * @author Chuck Hagenbuch * @author Chuck Hagenbuch - * @version $Revision: 1.24 $ + * @version $Revision: 294749 $ * @license BSD * @package Mail */ @@ -635,8 +646,8 @@ class Mail_RFC822 { $comment = $this->_splitCheck($parts, ')'); $comments[] = $comment; - // +1 is for the trailing ) - $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1); + // +2 is for the brackets + $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2); } else { break; } diff --git a/extlib/Mail/mail.php b/extlib/Mail/mail.php old mode 100644 new mode 100755 index b13d695656..a8b4b5dbee --- a/extlib/Mail/mail.php +++ b/extlib/Mail/mail.php @@ -1,27 +1,52 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: mail.php,v 1.20 2007/10/06 17:00:00 chagenbu Exp $ +/** + * internal PHP-mail() implementation of the PEAR Mail:: interface. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * internal PHP-mail() implementation of the PEAR Mail:: interface. * @package Mail - * @version $Revision: 1.20 $ + * @version $Revision: 294747 $ */ class Mail_mail extends Mail { diff --git a/extlib/Mail/mock.php b/extlib/Mail/mock.php old mode 100644 new mode 100755 index 971dae6a0e..61570ba408 --- a/extlib/Mail/mock.php +++ b/extlib/Mail/mock.php @@ -1,29 +1,53 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: mock.php,v 1.1 2007/12/08 17:57:54 chagenbu Exp $ -// +/** + * Mock implementation + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * Mock implementation of the PEAR Mail:: interface for testing. * @access public * @package Mail - * @version $Revision: 1.1 $ + * @version $Revision: 294747 $ */ class Mail_mock extends Mail { diff --git a/extlib/Mail/null.php b/extlib/Mail/null.php old mode 100644 new mode 100755 index 982bfa45b6..f8d58272ee --- a/extlib/Mail/null.php +++ b/extlib/Mail/null.php @@ -1,29 +1,53 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: null.php,v 1.2 2004/04/06 05:19:03 jon Exp $ -// +/** + * Null implementation of the PEAR Mail interface + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Phil Kernick + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category Mail + * @package Mail + * @author Phil Kernick + * @copyright 2010 Phil Kernick + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * Null implementation of the PEAR Mail:: interface. * @access public * @package Mail - * @version $Revision: 1.2 $ + * @version $Revision: 294747 $ */ class Mail_null extends Mail { diff --git a/extlib/Mail/sendmail.php b/extlib/Mail/sendmail.php old mode 100644 new mode 100755 index cd248e61d2..b056575e99 --- a/extlib/Mail/sendmail.php +++ b/extlib/Mail/sendmail.php @@ -20,7 +20,7 @@ * Sendmail implementation of the PEAR Mail:: interface. * @access public * @package Mail - * @version $Revision: 1.19 $ + * @version $Revision: 294744 $ */ class Mail_sendmail extends Mail { @@ -117,7 +117,7 @@ class Mail_sendmail extends Mail { if (is_a($recipients, 'PEAR_Error')) { return $recipients; } - $recipients = escapeShellCmd(implode(' ', $recipients)); + $recipients = implode(' ', array_map('escapeshellarg', $recipients)); $headerElements = $this->prepareHeaders($headers); if (is_a($headerElements, 'PEAR_Error')) { @@ -141,7 +141,8 @@ class Mail_sendmail extends Mail { return PEAR::raiseError('From address specified with dangerous characters.'); } - $from = escapeShellCmd($from); + $from = escapeshellarg($from); // Security bug #16200 + $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w'); if (!$mail) { return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.'); diff --git a/extlib/Mail/smtp.php b/extlib/Mail/smtp.php old mode 100644 new mode 100755 index baf3a962ba..52ea602086 --- a/extlib/Mail/smtp.php +++ b/extlib/Mail/smtp.php @@ -1,21 +1,48 @@ | -// | Jon Parise | -// +----------------------------------------------------------------------+ +/** + * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010, Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request + * @author Jon Parise + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** Error: Failed to create a Net_SMTP object */ define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000); @@ -42,7 +69,7 @@ define('PEAR_MAIL_SMTP_ERROR_DATA', 10006); * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. * @access public * @package Mail - * @version $Revision: 1.33 $ + * @version $Revision: 294747 $ */ class Mail_smtp extends Mail { @@ -278,6 +305,16 @@ class Mail_smtp extends Mail { /* Send the message's headers and the body as SMTP data. */ $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body); + list(,$args) = $this->_smtp->getResponse(); + + if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { + $this->queued_as = $queued[1]; + } + + /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to. + * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */ + $this->greeting = $this->_smtp->getGreeting(); + if (is_a($res, 'PEAR_Error')) { $error = $this->_error('Failed to send data', $res); $this->_smtp->rset(); diff --git a/extlib/Mail/smtpmx.php b/extlib/Mail/smtpmx.php old mode 100644 new mode 100755 index 9d2dccfb13..f0b6940868 --- a/extlib/Mail/smtpmx.php +++ b/extlib/Mail/smtpmx.php @@ -8,19 +8,43 @@ * * PHP versions 4 and 5 * - * LICENSE: This source file is subject to version 3.0 of the PHP license - * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_0.txt. If you did not receive a copy of - * the PHP License and are unable to obtain it through the web, please - * send a note to license@php.net so we can mail you a copy immediately. + * LICENSE: + * + * Copyright (c) 2010, gERD Schaufelberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors may not 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. * * @category Mail * @package Mail_smtpmx * @author gERD Schaufelberger - * @copyright 1997-2005 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $ - * @see Mail + * @copyright 2010 gERD Schaufelberger + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ */ require_once 'Net/SMTP.php'; @@ -32,7 +56,7 @@ require_once 'Net/SMTP.php'; * @access public * @author gERD Schaufelberger * @package Mail - * @version $Revision: 1.2 $ + * @version $Revision: 294747 $ */ class Mail_smtpmx extends Mail { diff --git a/extlib/Net/SMTP.php b/extlib/Net/SMTP.php index d632258d63..ea4b55e8d2 100644 --- a/extlib/Net/SMTP.php +++ b/extlib/Net/SMTP.php @@ -18,7 +18,7 @@ // | Damian Alejandro Fernandez Sosa | // +----------------------------------------------------------------------+ // -// $Id: SMTP.php,v 1.63 2008/06/10 05:39:12 jon Exp $ +// $Id: SMTP.php 293948 2010-01-24 21:46:00Z jon $ require_once 'PEAR.php'; require_once 'Net/Socket.php'; @@ -91,6 +91,13 @@ class Net_SMTP */ var $_debug = false; + /** + * Debug output handler. + * @var callback + * @access private + */ + var $_debug_handler = null; + /** * The socket resource being used to connect to the SMTP server. * @var resource @@ -112,6 +119,13 @@ class Net_SMTP */ var $_arguments = array(); + /** + * Stores the SMTP server's greeting string. + * @var string + * @access private + */ + var $_greeting = null; + /** * Stores detected features of the SMTP server. * @var array @@ -172,9 +186,30 @@ class Net_SMTP * @access public * @since 1.1.0 */ - function setDebug($debug) + function setDebug($debug, $handler = null) { $this->_debug = $debug; + $this->_debug_handler = $handler; + } + + /** + * Write the given debug text to the current debug output handler. + * + * @param string $message Debug mesage text. + * + * @access private + * @since 1.3.3 + */ + function _debug($message) + { + if ($this->_debug) { + if ($this->_debug_handler) { + call_user_func_array($this->_debug_handler, + array(&$this, $message)); + } else { + echo "DEBUG: $message\n"; + } + } } /** @@ -189,13 +224,12 @@ class Net_SMTP */ function _send($data) { - if ($this->_debug) { - echo "DEBUG: Send: $data\n"; - } + $this->_debug("Send: $data"); - if (PEAR::isError($error = $this->_socket->write($data))) { - return PEAR::raiseError('Failed to write to socket: ' . - $error->getMessage()); + $error = $this->_socket->write($data); + if ($error === false || PEAR::isError($error)) { + $msg = ($error) ? $error->getMessage() : "unknown error"; + return PEAR::raiseError("Failed to write to socket: $msg"); } return true; @@ -262,9 +296,7 @@ class Net_SMTP for ($i = 0; $i <= $this->_pipelined_commands; $i++) { while ($line = $this->_socket->readLine()) { - if ($this->_debug) { - echo "DEBUG: Recv: $line\n"; - } + $this->_debug("Recv: $line"); /* If we receive an empty line, the connection has been closed. */ if (empty($line)) { @@ -319,6 +351,20 @@ class Net_SMTP return array($this->_code, join("\n", $this->_arguments)); } + /** + * Return the SMTP server's greeting string. + * + * @return string A string containing the greeting string, or null if a + * greeting has not been received. + * + * @access public + * @since 1.3.3 + */ + function getGreeting() + { + return $this->_greeting; + } + /** * Attempt to connect to the SMTP server. * @@ -334,6 +380,7 @@ class Net_SMTP */ function connect($timeout = null, $persistent = false) { + $this->_greeting = null; $result = $this->_socket->connect($this->host, $this->port, $persistent, $timeout); if (PEAR::isError($result)) { @@ -344,6 +391,10 @@ class Net_SMTP if (PEAR::isError($error = $this->_parseResponse(220))) { return $error; } + + /* Extract and store a copy of the server's greeting string. */ + list(, $this->_greeting) = $this->getResponse(); + if (PEAR::isError($error = $this->_negotiate())) { return $error; } @@ -452,40 +503,43 @@ class Net_SMTP * @param string The password to authenticate with. * @param string The requested authentication method. If none is * specified, the best supported method will be used. + * @param bool Flag indicating whether or not TLS should be attempted. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access public * @since 1.0 */ - function auth($uid, $pwd , $method = '') + function auth($uid, $pwd , $method = '', $tls = true) { - if (empty($this->_esmtp['AUTH'])) { - if (version_compare(PHP_VERSION, '5.1.0', '>=')) { - if (!isset($this->_esmtp['STARTTLS'])) { - return PEAR::raiseError('SMTP server does not support authentication'); - } - if (PEAR::isError($result = $this->_put('STARTTLS'))) { - return $result; - } - if (PEAR::isError($result = $this->_parseResponse(220))) { - return $result; - } - if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { - return $result; - } elseif ($result !== true) { - return PEAR::raiseError('STARTTLS failed'); - } - - /* Send EHLO again to recieve the AUTH string from the - * SMTP server. */ - $this->_negotiate(); - if (empty($this->_esmtp['AUTH'])) { - return PEAR::raiseError('SMTP server does not support authentication'); - } - } else { - return PEAR::raiseError('SMTP server does not support authentication'); + /* We can only attempt a TLS connection if one has been requested, + * we're running PHP 5.1.0 or later, have access to the OpenSSL + * extension, are connected to an SMTP server which supports the + * STARTTLS extension, and aren't already connected over a secure + * (SSL) socket connection. */ + if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') && + extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) && + strncasecmp($this->host, 'ssl://', 6) !== 0) { + /* Start the TLS connection attempt. */ + if (PEAR::isError($result = $this->_put('STARTTLS'))) { + return $result; + } + if (PEAR::isError($result = $this->_parseResponse(220))) { + return $result; + } + if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { + return $result; + } elseif ($result !== true) { + return PEAR::raiseError('STARTTLS failed'); } + + /* Send EHLO again to recieve the AUTH string from the + * SMTP server. */ + $this->_negotiate(); + } + + if (empty($this->_esmtp['AUTH'])) { + return PEAR::raiseError('SMTP server does not support authentication'); } /* If no method has been specified, get the name of the best @@ -844,30 +898,51 @@ class Net_SMTP /** * Send the DATA command. * - * @param string $data The message body to send. + * @param mixed $data The message data, either as a string or an open + * file resource. + * @param string $headers The message headers. If $headers is provided, + * $data is assumed to contain only body data. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access public * @since 1.0 */ - function data($data) + function data($data, $headers = null) { + /* Verify that $data is a supported type. */ + if (!is_string($data) && !is_resource($data)) { + return PEAR::raiseError('Expected a string or file resource'); + } + /* RFC 1870, section 3, subsection 3 states "a value of zero * indicates that no fixed maximum message size is in force". * Furthermore, it says that if "the parameter is omitted no * information is conveyed about the server's fixed maximum * message size". */ if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) { - if (strlen($data) >= $this->_esmtp['SIZE']) { + /* Start by considering the size of the optional headers string. + * We also account for the addition 4 character "\r\n\r\n" + * separator sequence. */ + $size = (is_null($headers)) ? 0 : strlen($headers) + 4; + + if (is_resource($data)) { + $stat = fstat($data); + if ($stat === false) { + return PEAR::raiseError('Failed to get file size'); + } + $size += $stat['size']; + } else { + $size += strlen($data); + } + + if ($size >= $this->_esmtp['SIZE']) { $this->disconnect(); - return PEAR::raiseError('Message size excedes the server limit'); + return PEAR::raiseError('Message size exceeds server limit'); } } - /* Quote the data based on the SMTP standards. */ - $this->quotedata($data); - + /* Initiate the DATA command. */ if (PEAR::isError($error = $this->_put('DATA'))) { return $error; } @@ -875,9 +950,40 @@ class Net_SMTP return $error; } - if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { - return $result; + /* If we have a separate headers string, send it first. */ + if (!is_null($headers)) { + $this->quotedata($headers); + if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) { + return $result; + } } + + /* Now we can send the message body data. */ + if (is_resource($data)) { + /* Stream the contents of the file resource out over our socket + * connection, line by line. Each line must be run through the + * quoting routine. */ + while ($line = fgets($data, 1024)) { + $this->quotedata($line); + if (PEAR::isError($result = $this->_send($line))) { + return $result; + } + } + + /* Finally, send the DATA terminator sequence. */ + if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) { + return $result; + } + } else { + /* Just send the entire quoted string followed by the DATA + * terminator. */ + $this->quotedata($data); + if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { + return $result; + } + } + + /* Verify that the data was successfully received by the server. */ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { return $error; } diff --git a/extlib/XMPPHP/BOSH.php b/extlib/XMPPHP/BOSH.php deleted file mode 100644 index befaf60a77..0000000000 --- a/extlib/XMPPHP/BOSH.php +++ /dev/null @@ -1,188 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMPP.php"; - -/** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_BOSH extends XMPPHP_XMPP { - - protected $rid; - protected $sid; - protected $http_server; - protected $http_buffer = Array(); - protected $session = false; - - public function connect($server, $wait='1', $session=false) { - $this->http_server = $server; - $this->use_encryption = false; - $this->session = $session; - - $this->rid = 3001; - $this->sid = null; - if($session) - { - $this->loadSession(); - } - if(!$this->sid) { - $body = $this->__buildBody(); - $body->addAttribute('hold','1'); - $body->addAttribute('to', $this->host); - $body->addAttribute('route', "xmpp:{$this->host}:{$this->port}"); - $body->addAttribute('secure','true'); - $body->addAttribute('xmpp:version','1.6', 'urn:xmpp:xbosh'); - $body->addAttribute('wait', strval($wait)); - $body->addAttribute('ack','1'); - $body->addAttribute('xmlns:xmpp','urn:xmpp:xbosh'); - $buff = ""; - xml_parse($this->parser, $buff, false); - $response = $this->__sendBody($body); - $rxml = new SimpleXMLElement($response); - $this->sid = $rxml['sid']; - - } else { - $buff = ""; - xml_parse($this->parser, $buff, false); - } - } - - public function __sendBody($body=null, $recv=true) { - if(!$body) { - $body = $this->__buildBody(); - } - $ch = curl_init($this->http_server); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML()); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - $header = array('Accept-Encoding: gzip, deflate','Content-Type: text/xml; charset=utf-8'); - curl_setopt($ch, CURLOPT_HTTPHEADER, $header ); - curl_setopt($ch, CURLOPT_VERBOSE, 0); - $output = ''; - if($recv) { - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $output = curl_exec($ch); - $this->http_buffer[] = $output; - } - curl_close($ch); - return $output; - } - - public function __buildBody($sub=null) { - $xml = new SimpleXMLElement(""); - $xml->addAttribute('content', 'text/xml; charset=utf-8'); - $xml->addAttribute('rid', $this->rid); - $this->rid += 1; - if($this->sid) $xml->addAttribute('sid', $this->sid); - #if($this->sid) $xml->addAttribute('xmlns', 'http://jabber.org/protocol/httpbind'); - $xml->addAttribute('xml:lang', 'en'); - if($sub) { // ok, so simplexml is lame - $p = dom_import_simplexml($xml); - $c = dom_import_simplexml($sub); - $cn = $p->ownerDocument->importNode($c, true); - $p->appendChild($cn); - $xml = simplexml_import_dom($p); - } - return $xml; - } - - public function __process() { - if($this->http_buffer) { - $this->__parseBuffer(); - } else { - $this->__sendBody(); - $this->__parseBuffer(); - } - } - - public function __parseBuffer() { - while ($this->http_buffer) { - $idx = key($this->http_buffer); - $buffer = $this->http_buffer[$idx]; - unset($this->http_buffer[$idx]); - if($buffer) { - $xml = new SimpleXMLElement($buffer); - $children = $xml->xpath('child::node()'); - foreach ($children as $child) { - $buff = $child->asXML(); - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - } - } - } - - public function send($msg) { - $this->log->log("SEND: $msg", XMPPHP_Log::LEVEL_VERBOSE); - $msg = new SimpleXMLElement($msg); - #$msg->addAttribute('xmlns', 'jabber:client'); - $this->__sendBody($this->__buildBody($msg), true); - #$this->__parseBuffer(); - } - - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - #$this->send($this->stream_start); - $body = $this->__buildBody(); - $body->addAttribute('to', $this->host); - $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh'); - $buff = ""; - $response = $this->__sendBody($body); - $this->been_reset = true; - xml_parse($this->parser, $buff, false); - } - - public function loadSession() { - if(isset($_SESSION['XMPPHP_BOSH_RID'])) $this->rid = $_SESSION['XMPPHP_BOSH_RID']; - if(isset($_SESSION['XMPPHP_BOSH_SID'])) $this->sid = $_SESSION['XMPPHP_BOSH_SID']; - if(isset($_SESSION['XMPPHP_BOSH_authed'])) $this->authed = $_SESSION['XMPPHP_BOSH_authed']; - if(isset($_SESSION['XMPPHP_BOSH_jid'])) $this->jid = $_SESSION['XMPPHP_BOSH_jid']; - if(isset($_SESSION['XMPPHP_BOSH_fulljid'])) $this->fulljid = $_SESSION['XMPPHP_BOSH_fulljid']; - } - - public function saveSession() { - $_SESSION['XMPPHP_BOSH_RID'] = (string) $this->rid; - $_SESSION['XMPPHP_BOSH_SID'] = (string) $this->sid; - $_SESSION['XMPPHP_BOSH_authed'] = (boolean) $this->authed; - $_SESSION['XMPPHP_BOSH_jid'] = (string) $this->jid; - $_SESSION['XMPPHP_BOSH_fulljid'] = (string) $this->fulljid; - } -} diff --git a/extlib/XMPPHP/Exception.php b/extlib/XMPPHP/Exception.php deleted file mode 100644 index da59bc7918..0000000000 --- a/extlib/XMPPHP/Exception.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Exception - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_Exception extends Exception { -} diff --git a/extlib/XMPPHP/Log.php b/extlib/XMPPHP/Log.php deleted file mode 100644 index a9bce3d841..0000000000 --- a/extlib/XMPPHP/Log.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Log - * - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_Log { - - const LEVEL_ERROR = 0; - const LEVEL_WARNING = 1; - const LEVEL_INFO = 2; - const LEVEL_DEBUG = 3; - const LEVEL_VERBOSE = 4; - - /** - * @var array - */ - protected $data = array(); - - /** - * @var array - */ - protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE'); - - /** - * @var integer - */ - protected $runlevel; - - /** - * @var boolean - */ - protected $printout; - - /** - * Constructor - * - * @param boolean $printout - * @param string $runlevel - */ - public function __construct($printout = false, $runlevel = self::LEVEL_INFO) { - $this->printout = (boolean)$printout; - $this->runlevel = (int)$runlevel; - } - - /** - * Add a message to the log data array - * If printout in this instance is set to true, directly output the message - * - * @param string $msg - * @param integer $runlevel - */ - public function log($msg, $runlevel = self::LEVEL_INFO) { - $time = time(); - #$this->data[] = array($this->runlevel, $msg, $time); - if($this->printout and $runlevel <= $this->runlevel) { - $this->writeLine($msg, $runlevel, $time); - } - } - - /** - * Output the complete log. - * Log will be cleared if $clear = true - * - * @param boolean $clear - * @param integer $runlevel - */ - public function printout($clear = true, $runlevel = null) { - if($runlevel === null) { - $runlevel = $this->runlevel; - } - foreach($this->data as $data) { - if($runlevel <= $data[0]) { - $this->writeLine($data[1], $runlevel, $data[2]); - } - } - if($clear) { - $this->data = array(); - } - } - - protected function writeLine($msg, $runlevel, $time) { - //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n"; - echo $time." [".$this->names[$runlevel]."]: ".$msg."\n"; - flush(); - } -} diff --git a/extlib/XMPPHP/Roster.php b/extlib/XMPPHP/Roster.php deleted file mode 100644 index 2e459e2a2f..0000000000 --- a/extlib/XMPPHP/Roster.php +++ /dev/null @@ -1,163 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP Roster Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ - -class Roster { - /** - * Roster array, handles contacts and presence. Indexed by jid. - * Contains array with potentially two indexes 'contact' and 'presence' - * @var array - */ - protected $roster_array = array(); - /** - * Constructor - * - */ - public function __construct($roster_array = array()) { - if ($this->verifyRoster($roster_array)) { - $this->roster_array = $roster_array; //Allow for prepopulation with existing roster - } else { - $this->roster_array = array(); - } - } - - /** - * - * Check that a given roster array is of a valid structure (empty is still valid) - * - * @param array $roster_array - */ - protected function verifyRoster($roster_array) { - #TODO once we know *what* a valid roster array looks like - return True; - } - - /** - * - * Add given contact to roster - * - * @param string $jid - * @param string $subscription - * @param string $name - * @param array $groups - */ - public function addContact($jid, $subscription, $name='', $groups=array()) { - $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups); - if ($this->isContact($jid)) { - $this->roster_array[$jid]['contact'] = $contact; - } else { - $this->roster_array[$jid] = array('contact' => $contact); - } - } - - /** - * - * Retrieve contact via jid - * - * @param string $jid - */ - public function getContact($jid) { - if ($this->isContact($jid)) { - return $this->roster_array[$jid]['contact']; - } - } - - /** - * - * Discover if a contact exists in the roster via jid - * - * @param string $jid - */ - public function isContact($jid) { - return (array_key_exists($jid, $this->roster_array)); - } - - /** - * - * Set presence - * - * @param string $presence - * @param integer $priority - * @param string $show - * @param string $status - */ - public function setPresence($presence, $priority, $show, $status) { - list($jid, $resource) = split("/", $presence); - if ($show != 'unavailable') { - if (!$this->isContact($jid)) { - $this->addContact($jid, 'not-in-roster'); - } - $resource = $resource ? $resource : ''; - $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); - } else { //Nuke unavailable resources to save memory - unset($this->roster_array[$jid]['resource'][$resource]); - } - } - - /* - * - * Return best presence for jid - * - * @param string $jid - */ - public function getPresence($jid) { - $split = split("/", $jid); - $jid = $split[0]; - if($this->isContact($jid)) { - $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 - foreach($this->roster_array[$jid]['presence'] as $resource => $presence) { - //Highest available priority or just highest priority - if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) { - $current = $presence; - $current['resource'] = $resource; - } - } - return $current; - } - } - /** - * - * Get roster - * - */ - public function getRoster() { - return $this->roster_array; - } -} -?> diff --git a/extlib/XMPPHP/XMLObj.php b/extlib/XMPPHP/XMLObj.php deleted file mode 100644 index 0d3e219912..0000000000 --- a/extlib/XMPPHP/XMLObj.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** - * XMPPHP XML Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMLObj { - /** - * Tag name - * - * @var string - */ - public $name; - - /** - * Namespace - * - * @var string - */ - public $ns; - - /** - * Attributes - * - * @var array - */ - public $attrs = array(); - - /** - * Subs? - * - * @var array - */ - public $subs = array(); - - /** - * Node data - * - * @var string - */ - public $data = ''; - - /** - * Constructor - * - * @param string $name - * @param string $ns - * @param array $attrs - * @param string $data - */ - public function __construct($name, $ns = '', $attrs = array(), $data = '') { - $this->name = strtolower($name); - $this->ns = $ns; - if(is_array($attrs) && count($attrs)) { - foreach($attrs as $key => $value) { - $this->attrs[strtolower($key)] = $value; - } - } - $this->data = $data; - } - - /** - * Dump this XML Object to output. - * - * @param integer $depth - */ - public function printObj($depth = 0) { - print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data; - print "\n"; - foreach($this->subs as $sub) { - $sub->printObj($depth + 1); - } - } - - /** - * Return this XML Object in xml notation - * - * @param string $str - */ - public function toString($str = '') { - $str .= "<{$this->name} xmlns='{$this->ns}' "; - foreach($this->attrs as $key => $value) { - if($key != 'xmlns') { - $value = htmlspecialchars($value); - $str .= "$key='$value' "; - } - } - $str .= ">"; - foreach($this->subs as $sub) { - $str .= $sub->toString(); - } - $body = htmlspecialchars($this->data); - $str .= "$bodyname}>"; - return $str; - } - - /** - * Has this XML Object the given sub? - * - * @param string $name - * @return boolean - */ - public function hasSub($name, $ns = null) { - foreach($this->subs as $sub) { - if(($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) return true; - } - return false; - } - - /** - * Return a sub - * - * @param string $name - * @param string $attrs - * @param string $ns - */ - public function sub($name, $attrs = null, $ns = null) { - #TODO attrs is ignored - foreach($this->subs as $sub) { - if($sub->name == $name and ($ns == null or $sub->ns == $ns)) { - return $sub; - } - } - } -} diff --git a/extlib/XMPPHP/XMLStream.php b/extlib/XMPPHP/XMLStream.php deleted file mode 100644 index d33411ec54..0000000000 --- a/extlib/XMPPHP/XMLStream.php +++ /dev/null @@ -1,763 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_Exception */ -require_once dirname(__FILE__) . '/Exception.php'; - -/** XMPPHP_XMLObj */ -require_once dirname(__FILE__) . '/XMLObj.php'; - -/** XMPPHP_Log */ -require_once dirname(__FILE__) . '/Log.php'; - -/** - * XMPPHP XML Stream - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMLStream { - /** - * @var resource - */ - protected $socket; - /** - * @var resource - */ - protected $parser; - /** - * @var string - */ - protected $buffer; - /** - * @var integer - */ - protected $xml_depth = 0; - /** - * @var string - */ - protected $host; - /** - * @var integer - */ - protected $port; - /** - * @var string - */ - protected $stream_start = ''; - /** - * @var string - */ - protected $stream_end = ''; - /** - * @var boolean - */ - protected $disconnected = false; - /** - * @var boolean - */ - protected $sent_disconnect = false; - /** - * @var array - */ - protected $ns_map = array(); - /** - * @var array - */ - protected $current_ns = array(); - /** - * @var array - */ - protected $xmlobj = null; - /** - * @var array - */ - protected $nshandlers = array(); - /** - * @var array - */ - protected $xpathhandlers = array(); - /** - * @var array - */ - protected $idhandlers = array(); - /** - * @var array - */ - protected $eventhandlers = array(); - /** - * @var integer - */ - protected $lastid = 0; - /** - * @var string - */ - protected $default_ns; - /** - * @var string - */ - protected $until = ''; - /** - * @var string - */ - protected $until_count = ''; - /** - * @var array - */ - protected $until_happened = false; - /** - * @var array - */ - protected $until_payload = array(); - /** - * @var XMPPHP_Log - */ - protected $log; - /** - * @var boolean - */ - protected $reconnect = true; - /** - * @var boolean - */ - protected $been_reset = false; - /** - * @var boolean - */ - protected $is_server; - /** - * @var float - */ - protected $last_send = 0; - /** - * @var boolean - */ - protected $use_ssl = false; - /** - * @var integer - */ - protected $reconnectTimeout = 30; - - /** - * Constructor - * - * @param string $host - * @param string $port - * @param boolean $printlog - * @param string $loglevel - * @param boolean $is_server - */ - public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) { - $this->reconnect = !$is_server; - $this->is_server = $is_server; - $this->host = $host; - $this->port = $port; - $this->setupParser(); - $this->log = new XMPPHP_Log($printlog, $loglevel); - } - - /** - * Destructor - * Cleanup connection - */ - public function __destruct() { - if(!$this->disconnected && $this->socket) { - $this->disconnect(); - } - } - - /** - * Return the log instance - * - * @return XMPPHP_Log - */ - public function getLog() { - return $this->log; - } - - /** - * Get next ID - * - * @return integer - */ - public function getId() { - $this->lastid++; - return $this->lastid; - } - - /** - * Set SSL - * - * @return integer - */ - public function useSSL($use=true) { - $this->use_ssl = $use; - } - - /** - * Add ID Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addIdHandler($id, $pointer, $obj = null) { - $this->idhandlers[$id] = array($pointer, $obj); - } - - /** - * Add Handler - * - * @param string $name - * @param string $ns - * @param string $pointer - * @param string $obj - * @param integer $depth - */ - public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) { - #TODO deprication warning - $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth); - } - - /** - * Add XPath Handler - * - * @param string $xpath - * @param string $pointer - * @param - */ - public function addXPathHandler($xpath, $pointer, $obj = null) { - if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) { - $ns_tags = $regs[0]; - } else { - $ns_tags = array($xpath); - } - foreach($ns_tags as $ns_tag) { - list($l, $r) = split("}", $ns_tag); - if ($r != null) { - $xpart = array(substr($l, 1), $r); - } else { - $xpart = array(null, $l); - } - $xpath_array[] = $xpart; - } - $this->xpathhandlers[] = array($xpath_array, $pointer, $obj); - } - - /** - * Add Event Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addEventHandler($name, $pointer, $obj) { - $this->eventhandlers[] = array($name, $pointer, $obj); - } - - /** - * Connect to XMPP Host - * - * @param integer $timeout - * @param boolean $persistent - * @param boolean $sendinit - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) { - $this->sent_disconnect = false; - $starttime = time(); - - do { - $this->disconnected = false; - $this->sent_disconnect = false; - if($persistent) { - $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT; - } else { - $conflag = STREAM_CLIENT_CONNECT; - } - $conntype = 'tcp'; - if($this->use_ssl) $conntype = 'ssl'; - $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}"); - try { - $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag); - } catch (Exception $e) { - throw new XMPPHP_Exception($e->getMessage()); - } - if(!$this->socket) { - $this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR); - $this->disconnected = true; - # Take it easy for a few seconds - sleep(min($timeout, 5)); - } - } while (!$this->socket && (time() - $starttime) < $timeout); - - if ($this->socket) { - stream_set_blocking($this->socket, 1); - if($sendinit) $this->send($this->stream_start); - } else { - throw new XMPPHP_Exception("Could not connect before timeout."); - } - } - - /** - * Reconnect XMPP Host - */ - public function doReconnect() { - if(!$this->is_server) { - $this->log->log("Reconnecting ($this->reconnectTimeout)...", XMPPHP_Log::LEVEL_WARNING); - $this->connect($this->reconnectTimeout, false, false); - $this->reset(); - $this->event('reconnect'); - } - } - - public function setReconnectTimeout($timeout) { - $this->reconnectTimeout = $timeout; - } - - /** - * Disconnect from XMPP Host - */ - public function disconnect() { - $this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE); - if(false == (bool) $this->socket) { - return; - } - $this->reconnect = false; - $this->send($this->stream_end); - $this->sent_disconnect = true; - $this->processUntil('end_stream', 5); - $this->disconnected = true; - } - - /** - * Are we are disconnected? - * - * @return boolean - */ - public function isDisconnected() { - return $this->disconnected; - } - - /** - * Core reading tool - * 0 -> only read if data is immediately ready - * NULL -> wait forever and ever - * integer -> process for this amount of time - */ - - private function __process($maximum=5) { - - $remaining = $maximum; - - do { - $starttime = (microtime(true) * 1000000); - $read = array($this->socket); - $write = array(); - $except = array(); - if (is_null($maximum)) { - $secs = NULL; - $usecs = NULL; - } else if ($maximum == 0) { - $secs = 0; - $usecs = 0; - } else { - $usecs = $remaining % 1000000; - $secs = floor(($remaining - $usecs) / 1000000); - } - $updated = @stream_select($read, $write, $except, $secs, $usecs); - if ($updated === false) { - $this->log->log("Error on stream_select()", XMPPHP_Log::LEVEL_VERBOSE); - if ($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } else if ($updated > 0) { - # XXX: Is this big enough? - $buff = @fread($this->socket, 4096); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } else { - # $updated == 0 means no changes during timeout. - } - $endtime = (microtime(true)*1000000); - $time_past = $endtime - $starttime; - $remaining = $remaining - $time_past; - } while (is_null($maximum) || $remaining > 0); - return true; - } - - /** - * Process - * - * @return string - */ - public function process() { - $this->__process(NULL); - } - - /** - * Process until a timeout occurs - * - * @param integer $timeout - * @return string - */ - public function processTime($timeout=NULL) { - if (is_null($timeout)) { - return $this->__process(NULL); - } else { - return $this->__process($timeout * 1000000); - } - } - - /** - * Process until a specified event or a timeout occurs - * - * @param string|array $event - * @param integer $timeout - * @return string - */ - public function processUntil($event, $timeout=-1) { - $start = time(); - if(!is_array($event)) $event = array($event); - $this->until[] = $event; - end($this->until); - $event_key = key($this->until); - reset($this->until); - $this->until_count[$event_key] = 0; - $updated = ''; - while(!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) { - $this->__process(); - } - if(array_key_exists($event_key, $this->until_payload)) { - $payload = $this->until_payload[$event_key]; - unset($this->until_payload[$event_key]); - unset($this->until_count[$event_key]); - unset($this->until[$event_key]); - } else { - $payload = array(); - } - return $payload; - } - - /** - * Obsolete? - */ - public function Xapply_socket($socket) { - $this->socket = $socket; - } - - /** - * XML start callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function startXML($parser, $name, $attr) { - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth++; - if(array_key_exists('XMLNS', $attr)) { - $this->current_ns[$this->xml_depth] = $attr['XMLNS']; - } else { - $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1]; - if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns; - } - $ns = $this->current_ns[$this->xml_depth]; - foreach($attr as $key => $value) { - if(strstr($key, ":")) { - $key = explode(':', $key); - $key = $key[1]; - $this->ns_map[$key] = $value; - } - } - if(!strstr($name, ":") === false) - { - $name = explode(':', $name); - $ns = $this->ns_map[$name[0]]; - $name = $name[1]; - } - $obj = new XMPPHP_XMLObj($name, $ns, $attr); - if($this->xml_depth > 1) { - $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj; - } - $this->xmlobj[$this->xml_depth] = $obj; - } - - /** - * XML end callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function endXML($parser, $name) { - #$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG); - #print "$name\n"; - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth--; - if($this->xml_depth == 1) { - #clean-up old objects - #$found = false; #FIXME This didn't appear to be in use --Gar - foreach($this->xpathhandlers as $handler) { - if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - $nstag = array_shift($handler[0]); - if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) { - foreach($handler[0] as $nstag) { - if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) { - $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]); - } else { - $searchxml = null; - break; - } - } - if ($searchxml !== null) { - if($handler[2] === null) $handler[2] = $this; - $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[2]->$handler[1]($this->xmlobj[2]); - } - } - } - } - foreach($this->nshandlers as $handler) { - if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) { - $searchxml = $this->xmlobj[2]->sub($handler[0]); - } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - } - if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { - if($handler[3] === null) $handler[3] = $this; - $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[3]->$handler[2]($this->xmlobj[2]); - } - } - foreach($this->idhandlers as $id => $handler) { - if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { - if($handler[1] === null) $handler[1] = $this; - $handler[1]->$handler[0]($this->xmlobj[2]); - #id handlers are only used once - unset($this->idhandlers[$id]); - break; - } - } - if(is_array($this->xmlobj)) { - $this->xmlobj = array_slice($this->xmlobj, 0, 1); - if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) { - $this->xmlobj[0]->subs = null; - } - } - unset($this->xmlobj[2]); - } - if($this->xml_depth == 0 and !$this->been_reset) { - if(!$this->disconnected) { - if(!$this->sent_disconnect) { - $this->send($this->stream_end); - } - $this->disconnected = true; - $this->sent_disconnect = true; - fclose($this->socket); - if($this->reconnect) { - $this->doReconnect(); - } - } - $this->event('end_stream'); - } - } - - /** - * XML character callback - * @see xml_set_character_data_handler - * - * @param resource $parser - * @param string $data - */ - public function charXML($parser, $data) { - if(array_key_exists($this->xml_depth, $this->xmlobj)) { - $this->xmlobj[$this->xml_depth]->data .= $data; - } - } - - /** - * Event? - * - * @param string $name - * @param string $payload - */ - public function event($name, $payload = null) { - $this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG); - foreach($this->eventhandlers as $handler) { - if($name == $handler[0]) { - if($handler[2] === null) { - $handler[2] = $this; - } - $handler[2]->$handler[1]($payload); - } - } - foreach($this->until as $key => $until) { - if(is_array($until)) { - if(in_array($name, $until)) { - $this->until_payload[$key][] = array($name, $payload); - if(!isset($this->until_count[$key])) { - $this->until_count[$key] = 0; - } - $this->until_count[$key] += 1; - #$this->until[$key] = false; - } - } - } - } - - /** - * Read from socket - */ - public function read() { - $buff = @fread($this->socket, 1024); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - - /** - * Send to socket - * - * @param string $msg - */ - public function send($msg, $timeout=NULL) { - - if (is_null($timeout)) { - $secs = NULL; - $usecs = NULL; - } else if ($timeout == 0) { - $secs = 0; - $usecs = 0; - } else { - $maximum = $timeout * 1000000; - $usecs = $maximum % 1000000; - $secs = floor(($maximum - $usecs) / 1000000); - } - - $read = array(); - $write = array($this->socket); - $except = array(); - - $select = @stream_select($read, $write, $except, $secs, $usecs); - - if($select === False) { - $this->log->log("ERROR sending message; reconnecting."); - $this->doReconnect(); - # TODO: retry send here - return false; - } elseif ($select > 0) { - $this->log->log("Socket is ready; send it.", XMPPHP_Log::LEVEL_VERBOSE); - } else { - $this->log->log("Socket is not ready; break.", XMPPHP_Log::LEVEL_ERROR); - return false; - } - - $sentbytes = @fwrite($this->socket, $msg); - $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE); - if($sentbytes === FALSE) { - $this->log->log("ERROR sending message; reconnecting.", XMPPHP_Log::LEVEL_ERROR); - $this->doReconnect(); - return false; - } - $this->log->log("Successfully sent $sentbytes bytes.", XMPPHP_Log::LEVEL_VERBOSE); - return $sentbytes; - } - - public function time() { - list($usec, $sec) = explode(" ", microtime()); - return (float)$sec + (float)$usec; - } - - /** - * Reset connection - */ - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - if(!$this->is_server) { - $this->send($this->stream_start); - } - $this->been_reset = true; - } - - /** - * Setup the XML parser - */ - public function setupParser() { - $this->parser = xml_parser_create('UTF-8'); - xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); - xml_set_object($this->parser, $this); - xml_set_element_handler($this->parser, 'startXML', 'endXML'); - xml_set_character_data_handler($this->parser, 'charXML'); - } - - public function readyToProcess() { - $read = array($this->socket); - $write = array(); - $except = array(); - $updated = @stream_select($read, $write, $except, 0); - return (($updated !== false) && ($updated > 0)); - } -} diff --git a/extlib/XMPPHP/XMPP.php b/extlib/XMPPHP/XMPP.php deleted file mode 100644 index c0f8963396..0000000000 --- a/extlib/XMPPHP/XMPP.php +++ /dev/null @@ -1,432 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMLStream.php"; -require_once dirname(__FILE__) . "/Roster.php"; - -/** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ - */ -class XMPPHP_XMPP extends XMPPHP_XMLStream { - /** - * @var string - */ - public $server; - - /** - * @var string - */ - public $user; - - /** - * @var string - */ - protected $password; - - /** - * @var string - */ - protected $resource; - - /** - * @var string - */ - protected $fulljid; - - /** - * @var string - */ - protected $basejid; - - /** - * @var boolean - */ - protected $authed = false; - protected $session_started = false; - - /** - * @var boolean - */ - protected $auto_subscribe = false; - - /** - * @var boolean - */ - protected $use_encryption = true; - - /** - * @var boolean - */ - public $track_presence = true; - - /** - * @var object - */ - public $roster; - - /** - * Constructor - * - * @param string $host - * @param integer $port - * @param string $user - * @param string $password - * @param string $resource - * @param string $server - * @param boolean $printlog - * @param string $loglevel - */ - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { - parent::__construct($host, $port, $printlog, $loglevel); - - $this->user = $user; - $this->password = $password; - $this->resource = $resource; - if(!$server) $server = $host; - $this->basejid = $this->user . '@' . $this->host; - - $this->roster = new Roster(); - $this->track_presence = true; - - $this->stream_start = ''; - $this->stream_end = ''; - $this->default_ns = 'jabber:client'; - - $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler'); - $this->addXPathHandler('{jabber:client}message', 'message_handler'); - $this->addXPathHandler('{jabber:client}presence', 'presence_handler'); - $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler'); - } - - /** - * Turn encryption on/ff - * - * @param boolean $useEncryption - */ - public function useEncryption($useEncryption = true) { - $this->use_encryption = $useEncryption; - } - - /** - * Turn on auto-authorization of subscription requests. - * - * @param boolean $autoSubscribe - */ - public function autoSubscribe($autoSubscribe = true) { - $this->auto_subscribe = $autoSubscribe; - } - - /** - * Send XMPP Message - * - * @param string $to - * @param string $body - * @param string $type - * @param string $subject - */ - public function message($to, $body, $type = 'chat', $subject = null, $payload = null) { - if(is_null($type)) - { - $type = 'chat'; - } - - $to = htmlspecialchars($to); - $body = htmlspecialchars($body); - $subject = htmlspecialchars($subject); - - $out = "fulljid}\" to=\"$to\" type='$type'>"; - if($subject) $out .= "$subject"; - $out .= "$body"; - if($payload) $out .= $payload; - $out .= ""; - - $this->send($out); - } - - /** - * Set Presence - * - * @param string $status - * @param string $show - * @param string $to - */ - public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) { - if($type == 'available') $type = ''; - $to = htmlspecialchars($to); - $status = htmlspecialchars($status); - if($show == 'unavailable') $type = 'unavailable'; - - $out = "send($out); - } - /** - * Send Auth request - * - * @param string $jid - */ - public function subscribe($jid) { - $this->send(""); - #$this->send(""); - } - - /** - * Message handler - * - * @param string $xml - */ - public function message_handler($xml) { - if(isset($xml->attrs['type'])) { - $payload['type'] = $xml->attrs['type']; - } else { - $payload['type'] = 'chat'; - } - $payload['from'] = $xml->attrs['from']; - $payload['body'] = $xml->sub('body')->data; - $payload['xml'] = $xml; - $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG); - $this->event('message', $payload); - } - - /** - * Presence handler - * - * @param string $xml - */ - public function presence_handler($xml) { - $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available'; - $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type']; - $payload['from'] = $xml->attrs['from']; - $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : ''; - $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0; - $payload['xml'] = $xml; - if($this->track_presence) { - $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']); - } - $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG); - if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') { - if($this->auto_subscribe) { - $this->send(""); - $this->send(""); - } - $this->event('subscription_requested', $payload); - } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') { - $this->event('subscription_accepted', $payload); - } else { - $this->event('presence', $payload); - } - } - - /** - * Features handler - * - * @param string $xml - */ - protected function features_handler($xml) { - if($xml->hasSub('starttls') and $this->use_encryption) { - $this->send(""); - } elseif($xml->hasSub('bind') and $this->authed) { - $id = $this->getId(); - $this->addIdHandler($id, 'resource_bind_handler'); - $this->send("{$this->resource}"); - } else { - $this->log->log("Attempting Auth..."); - if ($this->password) { - $this->send("" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . ""); - } else { - $this->send(""); - } - } - } - - /** - * SASL success handler - * - * @param string $xml - */ - protected function sasl_success_handler($xml) { - $this->log->log("Auth success!"); - $this->authed = true; - $this->reset(); - } - - /** - * SASL feature handler - * - * @param string $xml - */ - protected function sasl_failure_handler($xml) { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - - throw new XMPPHP_Exception('Auth failed!'); - } - - /** - * Resource bind handler - * - * @param string $xml - */ - protected function resource_bind_handler($xml) { - if($xml->attrs['type'] == 'result') { - $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data); - $this->fulljid = $xml->sub('bind')->sub('jid')->data; - $jidarray = explode('/',$this->fulljid); - $this->jid = $jidarray[0]; - } - $id = $this->getId(); - $this->addIdHandler($id, 'session_start_handler'); - $this->send(""); - } - - /** - * Retrieves the roster - * - */ - public function getRoster() { - $id = $this->getID(); - $this->send(""); - } - - /** - * Roster iq handler - * Gets all packets matching XPath "iq/{jabber:iq:roster}query' - * - * @param string $xml - */ - protected function roster_iq_handler($xml) { - $status = "result"; - $xmlroster = $xml->sub('query'); - foreach($xmlroster->subs as $item) { - $groups = array(); - if ($item->name == 'item') { - $jid = $item->attrs['jid']; //REQUIRED - $name = $item->attrs['name']; //MAY - $subscription = $item->attrs['subscription']; - foreach($item->subs as $subitem) { - if ($subitem->name == 'group') { - $groups[] = $subitem->data; - } - } - $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen - } else { - $status = "error"; - } - } - if ($status == "result") { //No errors, add contacts - foreach($contacts as $contact) { - $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]); - } - } - if ($xml->attrs['type'] == 'set') { - $this->send("attrs['id']}\" to=\"{$xml->attrs['from']}\" />"); - } - } - - /** - * Session start handler - * - * @param string $xml - */ - protected function session_start_handler($xml) { - $this->log->log("Session started"); - $this->session_started = true; - $this->event('session_start'); - } - - /** - * TLS proceed handler - * - * @param string $xml - */ - protected function tls_proceed_handler($xml) { - $this->log->log("Starting TLS encryption"); - stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); - $this->reset(); - } - - /** - * Retrieves the vcard - * - */ - public function getVCard($jid = Null) { - $id = $this->getID(); - $this->addIdHandler($id, 'vcard_get_handler'); - if($jid) { - $this->send(""); - } else { - $this->send(""); - } - } - - /** - * VCard retrieval handler - * - * @param XML Object $xml - */ - protected function vcard_get_handler($xml) { - $vcard_array = array(); - $vcard = $xml->sub('vcard'); - // go through all of the sub elements and add them to the vcard array - foreach ($vcard->subs as $sub) { - if ($sub->subs) { - $vcard_array[$sub->name] = array(); - foreach ($sub->subs as $sub_child) { - $vcard_array[$sub->name][$sub_child->name] = $sub_child->data; - } - } else { - $vcard_array[$sub->name] = $sub->data; - } - } - $vcard_array['from'] = $xml->attrs['from']; - $this->event('vcard', $vcard_array); - } -} diff --git a/extlib/XMPPHP/XMPP_Old.php b/extlib/XMPPHP/XMPP_Old.php deleted file mode 100644 index 43f56b1546..0000000000 --- a/extlib/XMPPHP/XMPP_Old.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMPP - * - * This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's. - * In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it. - * The old Jabber protocol wasn't standardized, so use at your own risk. - * - */ -require_once "XMPP.php"; - - class XMPPHP_XMPPOld extends XMPPHP_XMPP { - /** - * - * @var string - */ - protected $session_id; - - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - if(!$server) $server = $host; - $this->stream_start = ''; - $this->fulljid = "{$user}@{$server}/{$resource}"; - } - - /** - * Override XMLStream's startXML - * - * @param parser $parser - * @param string $name - * @param array $attr - */ - public function startXML($parser, $name, $attr) { - if($this->xml_depth == 0) { - $this->session_id = $attr['ID']; - $this->authenticate(); - } - parent::startXML($parser, $name, $attr); - } - - /** - * Send Authenticate Info Request - * - */ - public function authenticate() { - $id = $this->getId(); - $this->addidhandler($id, 'authfieldshandler'); - $this->send("{$this->user}"); - } - - /** - * Retrieve auth fields and send auth attempt - * - * @param XMLObj $xml - */ - public function authFieldsHandler($xml) { - $id = $this->getId(); - $this->addidhandler($id, 'oldAuthResultHandler'); - if($xml->sub('query')->hasSub('digest')) { - $hash = sha1($this->session_id . $this->password); - print "{$this->session_id} {$this->password}\n"; - $out = "{$this->user}{$hash}{$this->resource}"; - } else { - $out = "{$this->user}{$this->password}{$this->resource}"; - } - $this->send($out); - - } - - /** - * Determine authenticated or failure - * - * @param XMLObj $xml - */ - public function oldAuthResultHandler($xml) { - if($xml->attrs['type'] != 'result') { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - throw new XMPPHP_Exception('Auth failed!'); - } else { - $this->log->log("Session started"); - $this->event('session_start'); - } - } - } - - -?> diff --git a/index.php b/index.php index 9501e2275d..21e222e3b8 100644 --- a/index.php +++ b/index.php @@ -41,8 +41,6 @@ define('INSTALLDIR', dirname(__FILE__)); define('STATUSNET', true); define('LACONICA', true); // compatibility -require_once INSTALLDIR . '/lib/common.php'; - $user = null; $action = null; @@ -72,52 +70,69 @@ function getPath($req) */ function handleError($error) { - if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { - return; - } + try { - $logmsg = "PEAR error: " . $error->getMessage(); - if (common_config('site', 'logdebug')) { - $logmsg .= " : ". $error->getDebugInfo(); - } - // DB queries often end up with a lot of newlines; merge to a single line - // for easier grepability... - $logmsg = str_replace("\n", " ", $logmsg); - common_log(LOG_ERR, $logmsg); - - // @fixme backtrace output should be consistent with exception handling - if (common_config('site', 'logdebug')) { - $bt = $error->getBacktrace(); - foreach ($bt as $n => $line) { - common_log(LOG_ERR, formatBacktraceLine($n, $line)); + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; } - } - if ($error instanceof DB_DataObject_Error - || $error instanceof DB_Error - ) { - $msg = sprintf( - _( - 'The database for %s isn\'t responding correctly, '. - 'so the site won\'t work properly. '. - 'The site admins probably know about the problem, '. - 'but you can contact them at %s to make sure. '. - 'Otherwise, wait a few minutes and try again.' - ), - common_config('site', 'name'), - common_config('site', 'email') - ); - } else { - $msg = _( - 'An important error occured, probably related to email setup. '. - 'Check logfiles for more info..' - ); - } - $dac = new DBErrorAction($msg, 500); - $dac->showPage(); + $logmsg = "PEAR error: " . $error->getMessage(); + if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) { + $logmsg .= " : ". $error->toText(); + } + // DB queries often end up with a lot of newlines; merge to a single line + // for easier grepability... + $logmsg = str_replace("\n", " ", $logmsg); + common_log(LOG_ERR, $logmsg); + + // @fixme backtrace output should be consistent with exception handling + if (common_config('site', 'logdebug')) { + $bt = $error->getTrace(); + foreach ($bt as $n => $line) { + common_log(LOG_ERR, formatBacktraceLine($n, $line)); + } + } + if ($error instanceof DB_DataObject_Error + || $error instanceof DB_Error + || ($error instanceof PEAR_Exception && $error->getCode() == -24) + ) { + //If we run into a DB error, assume we can't connect to the DB at all + //so set the current user to null, so we don't try to access the DB + //while rendering the error page. + global $_cur; + $_cur = null; + + $msg = sprintf( + _( + 'The database for %s isn\'t responding correctly, '. + 'so the site won\'t work properly. '. + 'The site admins probably know about the problem, '. + 'but you can contact them at %s to make sure. '. + 'Otherwise, wait a few minutes and try again.' + ), + common_config('site', 'name'), + common_config('site', 'email') + ); + } else { + $msg = _( + 'An important error occured, probably related to email setup. '. + 'Check logfiles for more info..' + ); + } + + $dac = new DBErrorAction($msg, 500); + $dac->showPage(); + + } catch (Exception $e) { + echo _('An error occurred.'); + } exit(-1); } +set_exception_handler('handleError'); + +require_once INSTALLDIR . '/lib/common.php'; + /** * Format a backtrace line for debug output roughly like debug_print_backtrace() does. * Exceptions already have this built in, but PEAR error objects just give us the array. @@ -189,7 +204,7 @@ function checkMirror($action_obj, $args) function isLoginAction($action) { - static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd'); + static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd', 'hostmeta'); $login = null; @@ -242,10 +257,6 @@ function main() return; } - // For database errors - - PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); - // Make sure RW database is setup setupRW(); diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index fae9f4fa57..8dd16e9d0c 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -404,6 +404,14 @@ class AdminPanelNav extends Widget $menu_title, $action_name == 'licenseadminpanel', 'nav_license_admin_panel'); } + if (AdminPanelAction::canAdmin('plugins')) { + // TRANS: Menu item title/tooltip + $menu_title = _('Plugins configuration'); + // TRANS: Menu item for site administration + $this->out->menuItem(common_local_url('pluginsadminpanel'), _('Plugins'), + $menu_title, $action_name == 'pluginsadminpanel', 'nav_design_admin_panel'); + } + Event::handle('EndAdminPanelNav', array($this)); } $this->action->elementEnd('ul'); diff --git a/lib/apiaction.php b/lib/apiaction.php index 0ebf88282a..d8249055a4 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -98,6 +98,8 @@ if (!defined('STATUSNET')) { exit(1); } +class ApiValidationException extends Exception { } + /** * Contains most of the Twitter-compatible API output functions. * diff --git a/lib/cache.php b/lib/cache.php index ea0ff769d1..3d78c79adb 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -80,7 +80,7 @@ class Cache $base_key = common_config('cache', 'base'); if (empty($base_key)) { - $base_key = common_keyize(common_config('site', 'name')); + $base_key = self::keyize(common_config('site', 'name')); } return 'statusnet:' . $base_key . ':' . $extra; diff --git a/lib/channel.php b/lib/channel.php index fbc2e8697c..ae9b2d214f 100644 --- a/lib/channel.php +++ b/lib/channel.php @@ -69,62 +69,6 @@ class CLIChannel extends Channel } } -class XMPPChannel extends Channel -{ - var $conn = null; - - function source() - { - return 'xmpp'; - } - - function __construct($conn) - { - $this->conn = $conn; - } - - function on($user) - { - return $this->set_notify($user, 1); - } - - function off($user) - { - return $this->set_notify($user, 0); - } - - function output($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function error($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function set_notify(&$user, $notify) - { - $orig = clone($user); - $user->jabbernotify = $notify; - $result = $user->update($orig); - if (!$result) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, - 'Could not set notify flag to ' . $notify . - ' for user ' . common_log_objstring($user) . - ': ' . $last_error->message); - return false; - } else { - common_log(LOG_INFO, - 'User ' . $user->nickname . ' set notify flag to ' . $notify); - return true; - } - } -} - class WebChannel extends Channel { var $out = null; @@ -216,12 +160,12 @@ class MailChannel extends Channel function on($user) { - return $this->set_notify($user, 1); + return $this->setNotify($user, 1); } function off($user) { - return $this->set_notify($user, 0); + return $this->setNotify($user, 0); } function output($user, $text) @@ -246,7 +190,7 @@ class MailChannel extends Channel return mail_send(array($this->addr), $headers, $text); } - function set_notify($user, $value) + function setNotify($user, $value) { $orig = clone($user); $user->smsnotify = $value; diff --git a/lib/command.php b/lib/command.php index 658262a090..efe917fb11 100644 --- a/lib/command.php +++ b/lib/command.php @@ -714,7 +714,7 @@ class OffCommand extends Command } function handle($channel) { - if ($other) { + if ($this->other) { // TRANS: Error text shown when issuing the command "off" with a setting which has not yet been implemented. $channel->error($this->user, _("Command not yet implemented.")); } else { @@ -740,7 +740,7 @@ class OnCommand extends Command function handle($channel) { - if ($other) { + if ($this->other) { // TRANS: Error text shown when issuing the command "on" with a setting which has not yet been implemented. $channel->error($this->user, _("Command not yet implemented.")); } else { diff --git a/lib/common.php b/lib/common.php index 236f2d68a7..2a11ab722d 100644 --- a/lib/common.php +++ b/lib/common.php @@ -71,6 +71,7 @@ if (!function_exists('dl')) { # global configuration object require_once('PEAR.php'); +require_once('PEAR/Exception.php'); require_once('DB/DataObject.php'); require_once('DB/DataObject/Cast.php'); # for dates @@ -127,6 +128,23 @@ require_once INSTALLDIR.'/lib/subs.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; + +//set PEAR error handling to use regular PHP exceptions +function PEAR_ErrorToPEAR_Exception($err) +{ + //DB_DataObject throws error when an empty set would be returned + //That behavior is weird, and not how the rest of StatusNet works. + //So just ignore those errors. + if ($err->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; + } + if ($err->getCode()) { + throw new PEAR_Exception($err->getMessage(), $err->getCode()); + } + throw new PEAR_Exception($err->getMessage()); +} +PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception'); + try { StatusNet::init(@$server, @$path, @$conffile); } catch (NoConfigException $e) { diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index bb2e86176a..c2e759f0f3 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -100,7 +100,9 @@ class ConnectSettingsNav extends Widget # action => array('prompt', 'title') $menu = array(); - if (common_config('xmpp', 'enabled')) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if ($transports) { $menu['imsettings'] = // TRANS: Menu item for Instant Messaging settings. array(_m('MENU','IM'), diff --git a/lib/default.php b/lib/default.php index 45e35e83d3..79b54fc3bd 100644 --- a/lib/default.php +++ b/lib/default.php @@ -297,11 +297,13 @@ $default = 'OStatus' => null, 'WikiHashtags' => null, 'RSSCloud' => null, + 'ClientSideShorten' => null, 'OpenID' => null), 'locale_path' => false, // Set to a path to use *instead of* each plugin's own locale subdirectories ), + 'pluginlist' => array(), 'admin' => - array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'license')), + array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'license', 'plugins')), 'singleuser' => array('enabled' => false, 'nickname' => null), @@ -315,6 +317,10 @@ $default = array('subscribers' => true, 'members' => true, 'peopletag' => true), + 'url' => + array('shortener' => 'ur1.ca', + 'maxlength' => 25, + 'maxnoticelength' => -1), 'http' => // HTTP client settings when contacting other sites array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt') 'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.) diff --git a/lib/designform.php b/lib/designform.php new file mode 100644 index 0000000000..b22d77f312 --- /dev/null +++ b/lib/designform.php @@ -0,0 +1,293 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Form for choosing a design + * + * Used for choosing a site design, user design, or group design. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + */ + +class DesignForm extends Form +{ + /** + * Return-to args + */ + + var $design = null; + var $actionurl = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Design $design initial design + * @param Design $actionurl url of action (for form posting) + */ + + function __construct($out, $design, $actionurl) + { + parent::__construct($out); + + $this->design = $design; + $this->actionurl = $actionurl; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'design'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_design'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return $this->actionurl; + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _('Change design')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('ul', 'form_data'); + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'design_background-image_file'), + _('Upload file')); + $this->out->element('input', array('name' => 'design_background-image_file', + 'type' => 'file', + 'id' => 'design_background-image_file')); + $this->out->element('p', 'form_guide', _('You can upload your personal ' . + 'background image. The maximum file size is 2Mb.')); + $this->out->element('input', array('name' => 'MAX_FILE_SIZE', + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); + $this->out->elementEnd('li'); + + if (!empty($design->backgroundimage)) { + + $this->out->elementStart('li', array('id' => + 'design_background-image_onoff')); + + $this->out->element('img', array('src' => + Design::url($design->backgroundimage))); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_on', + 'class' => 'radio', + 'value' => 'on'); + + if ($design->disposition & BACKGROUND_ON) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'design_background-image_on', + 'class' => 'radio'), + _('On')); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_off', + 'class' => 'radio', + 'value' => 'off'); + + if ($design->disposition & BACKGROUND_OFF) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'design_background-image_off', + 'class' => 'radio'), + _('Off')); + $this->out->element('p', 'form_guide', _('Turn background image on or off.')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->checkbox('design_background-image_repeat', + _('Tile background image'), + ($design->disposition & BACKGROUND_TILE) ? true : false); + $this->out->elementEnd('li'); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset', array('id' => 'settings_design_color')); + $this->out->element('legend', null, _('Change colours')); + $this->out->elementStart('ul', 'form_data'); + + try { + + $bgcolor = new WebColor($design->backgroundcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-1'), _('Background')); + $this->out->element('input', array('name' => 'design_background', + 'type' => 'text', + 'id' => 'swatch-1', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $ccolor = new WebColor($design->contentcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-2'), _('Content')); + $this->out->element('input', array('name' => 'design_content', + 'type' => 'text', + 'id' => 'swatch-2', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $sbcolor = new WebColor($design->sidebarcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-3'), _('Sidebar')); + $this->out->element('input', array('name' => 'design_sidebar', + 'type' => 'text', + 'id' => 'swatch-3', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $tcolor = new WebColor($design->textcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-4'), _('Text')); + $this->out->element('input', array('name' => 'design_text', + 'type' => 'text', + 'id' => 'swatch-4', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $lcolor = new WebColor($design->linkcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-5'), _('Links')); + $this->out->element('input', array('name' => 'design_links', + 'type' => 'text', + 'id' => 'swatch-5', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + } catch (WebColorException $e) { + common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset'); + + $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default', + 'defaults', _('Restore default designs')); + + $this->out->element('input', array('id' => 'settings_design_reset', + 'type' => 'reset', + 'value' => 'Reset', + 'class' => 'submit form_action-primary', + 'title' => _('Reset back to default'))); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('save', _('Save'), 'submit form_action-secondary', + 'save', _('Save design')); + } +} diff --git a/lib/designsettings.php b/lib/designsettings.php index 4955e92199..98ef8256cd 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -87,177 +87,8 @@ class DesignSettingsAction extends AccountSettingsAction function showDesignForm($design) { - - $this->elementStart('form', array('method' => 'post', - 'enctype' => 'multipart/form-data', - 'id' => 'form_settings_design', - 'class' => 'form_settings', - 'action' => $this->submitaction)); - $this->elementStart('fieldset'); - $this->hidden('token', common_session_token()); - - $this->elementStart('fieldset', array('id' => - 'settings_design_background-image')); - $this->element('legend', null, _('Change background image')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->element('label', array('for' => 'design_background-image_file'), - _('Upload file')); - $this->element('input', array('name' => 'design_background-image_file', - 'type' => 'file', - 'id' => 'design_background-image_file')); - $this->element('p', 'form_guide', _('You can upload your personal ' . - 'background image. The maximum file size is 2MB.')); - $this->element('input', array('name' => 'MAX_FILE_SIZE', - 'type' => 'hidden', - 'id' => 'MAX_FILE_SIZE', - 'value' => ImageFile::maxFileSizeInt())); - $this->elementEnd('li'); - - if (!empty($design->backgroundimage)) { - - $this->elementStart('li', array('id' => - 'design_background-image_onoff')); - - $this->element('img', array('src' => - Design::url($design->backgroundimage))); - - $attrs = array('name' => 'design_background-image_onoff', - 'type' => 'radio', - 'id' => 'design_background-image_on', - 'class' => 'radio', - 'value' => 'on'); - - if ($design->disposition & BACKGROUND_ON) { - $attrs['checked'] = 'checked'; - } - - $this->element('input', $attrs); - - $this->element('label', array('for' => 'design_background-image_on', - 'class' => 'radio'), - _('On')); - - $attrs = array('name' => 'design_background-image_onoff', - 'type' => 'radio', - 'id' => 'design_background-image_off', - 'class' => 'radio', - 'value' => 'off'); - - if ($design->disposition & BACKGROUND_OFF) { - $attrs['checked'] = 'checked'; - } - - $this->element('input', $attrs); - - $this->element('label', array('for' => 'design_background-image_off', - 'class' => 'radio'), - _('Off')); - $this->element('p', 'form_guide', _('Turn background image on or off.')); - $this->elementEnd('li'); - - $this->elementStart('li'); - $this->checkbox('design_background-image_repeat', - _('Tile background image'), - ($design->disposition & BACKGROUND_TILE) ? true : false); - $this->elementEnd('li'); - } - - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset', array('id' => 'settings_design_color')); - $this->element('legend', null, _('Change colours')); - $this->elementStart('ul', 'form_data'); - - try { - - $bgcolor = new WebColor($design->backgroundcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-1'), _('Background')); - $this->element('input', array('name' => 'design_background', - 'type' => 'text', - 'id' => 'swatch-1', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $ccolor = new WebColor($design->contentcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-2'), _('Content')); - $this->element('input', array('name' => 'design_content', - 'type' => 'text', - 'id' => 'swatch-2', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $sbcolor = new WebColor($design->sidebarcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-3'), _('Sidebar')); - $this->element('input', array('name' => 'design_sidebar', - 'type' => 'text', - 'id' => 'swatch-3', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $tcolor = new WebColor($design->textcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-4'), _('Text')); - $this->element('input', array('name' => 'design_text', - 'type' => 'text', - 'id' => 'swatch-4', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $lcolor = new WebColor($design->linkcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-5'), _('Links')); - $this->element('input', array('name' => 'design_links', - 'type' => 'text', - 'id' => 'swatch-5', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - } catch (WebColorException $e) { - common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id); - } - - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - - $this->submit('defaults', _('Use defaults'), 'submit form_action-default', - 'defaults', _('Restore default designs')); - - $this->element('input', array('id' => 'settings_design_reset', - 'type' => 'reset', - 'value' => 'Reset', - 'class' => 'submit form_action-primary', - 'title' => _('Reset back to default'))); - - $this->submit('save', _('Save'), 'submit form_action-secondary', - 'save', _('Save design')); - - $this->elementEnd('fieldset'); - $this->elementEnd('form'); + $form = new DesignForm($this, $design, $this->selfUrl()); + $form->show(); } /** diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 44b0296046..9780dc4243 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -177,7 +177,7 @@ class HTMLOutputter extends XMLOutputter $attrs = array('name' => $id, 'type' => 'text', 'id' => $id); - if ($value) { + if (!is_null($value)) { // value can be 0 or '' $attrs['value'] = $value; } $this->element('input', $attrs); diff --git a/lib/imchannel.php b/lib/imchannel.php new file mode 100644 index 0000000000..61355a429c --- /dev/null +++ b/lib/imchannel.php @@ -0,0 +1,104 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class IMChannel extends Channel +{ + + var $imPlugin; + + function source() + { + return $imPlugin->transport; + } + + function __construct($imPlugin) + { + $this->imPlugin = $imPlugin; + } + + function on($user) + { + return $this->setNotify($user, 1); + } + + function off($user) + { + return $this->setNotify($user, 0); + } + + function output($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + $this->imPlugin->sendMessage($this->imPlugin->getScreenname($user), $text); + } + + function error($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + + $screenname = $this->imPlugin->getScreenname($user); + if($screenname){ + $this->imPlugin->sendMessage($screenname, $text); + return true; + }else{ + common_log(LOG_ERR, + 'Could not send error message to user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : user preference does not exist'); + return false; + } + } + + function setNotify($user, $notify) + { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $this->imPlugin->transport; + $user_im_prefs->user_id = $user->id; + if($user_im_prefs->find() && $user_im_prefs->fetch()){ + if($user_im_prefs->notify == $notify){ + //notify is already set the way they want + return true; + }else{ + $original = clone($user_im_prefs); + $user_im_prefs->notify = $notify; + $result = $user_im_prefs->update($original); + + if (!$result) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : ' . $last_error->message); + return false; + } else { + common_log(LOG_INFO, + 'User ' . $user->nickname . ' set notify flag to ' . $notify); + return true; + } + } + }else{ + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : user preference does not exist'); + return false; + } + } +} diff --git a/lib/immanager.php b/lib/immanager.php new file mode 100644 index 0000000000..9563a53262 --- /dev/null +++ b/lib/immanager.php @@ -0,0 +1,56 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * IKM background connection manager for IM-using queue handlers, + * allowing them to send outgoing messages on the right connection. + * + * In a multi-site queuedaemon.php run, one connection will be instantiated + * for each site being handled by the current process that has IM enabled. + * + * Implementations that extend this class will likely want to: + * 1) override start() with their connection process. + * 2) override handleInput() with what to do when data is waiting on + * one of the sockets + * 3) override idle($timeout) to do keepalives (if necessary) + * 4) implement send_raw_message() to send raw data that ImPlugin::enqueueOutgoingRaw + * enqueued + */ + +abstract class ImManager extends IoManager +{ + abstract function send_raw_message($data); + + function __construct($imPlugin) + { + $this->plugin = $imPlugin; + $this->plugin->imManager = $this; + } + + /** + * Fetch the singleton manager for the current site. + * @return mixed ImManager, or false if unneeded + */ + public static function get() + { + throw new Exception('ImManager should be created using it\'s constructor, not the static get method'); + } +} diff --git a/lib/implugin.php b/lib/implugin.php new file mode 100644 index 0000000000..2811e7d644 --- /dev/null +++ b/lib/implugin.php @@ -0,0 +1,626 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Superclass for plugins that do authentication + * + * Implementations will likely want to override onStartIoManagerClasses() so that their + * IO manager is used + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class ImPlugin extends Plugin +{ + //name of this IM transport + public $transport = null; + //list of screennames that should get all public notices + public $public = array(); + + /** + * normalize a screenname for comparison + * + * @param string $screenname screenname to normalize + * + * @return string an equivalent screenname in normalized form + */ + abstract function normalize($screenname); + + /** + * validate (ensure the validity of) a screenname + * + * @param string $screenname screenname to validate + * + * @return boolean + */ + abstract function validate($screenname); + + /** + * get the internationalized/translated display name of this IM service + * + * @return string + */ + abstract function getDisplayName(); + + /** + * send a single notice to a given screenname + * The implementation should put raw data, ready to send, into the outgoing + * queue using enqueueOutgoingRaw() + * + * @param string $screenname screenname to send to + * @param Notice $notice notice to send + * + * @return boolean success value + */ + function sendNotice($screenname, $notice) + { + return $this->sendMessage($screenname, $this->formatNotice($notice)); + } + + /** + * send a message (text) to a given screenname + * The implementation should put raw data, ready to send, into the outgoing + * queue using enqueueOutgoingRaw() + * + * @param string $screenname screenname to send to + * @param Notice $body text to send + * + * @return boolean success value + */ + abstract function sendMessage($screenname, $body); + + /** + * receive a raw message + * Raw IM data is taken from the incoming queue, and passed to this function. + * It should parse the raw message and call handleIncoming() + * + * Returning false may CAUSE REPROCESSING OF THE QUEUE ITEM, and should + * be used for temporary failures only. For permanent failures such as + * unrecognized addresses, return true to indicate your processing has + * completed. + * + * @param object $data raw IM data + * + * @return boolean true if processing completed, false for temporary failures + */ + abstract function receiveRawMessage($data); + + /** + * get the screenname of the daemon that sends and receives message for this service + * + * @return string screenname of this plugin + */ + abstract function daemonScreenname(); + + /** + * get the microid uri of a given screenname + * + * @param string $screenname screenname + * + * @return string microid uri + */ + function microiduri($screenname) + { + return $this->transport . ':' . $screenname; + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\ + + /** + * Put raw message data (ready to send) into the outgoing queue + * + * @param object $data + */ + function enqueueOutgoingRaw($data) + { + $qm = QueueManager::get(); + $qm->enqueue($data, $this->transport . '-out'); + } + + /** + * Put raw message data (received, ready to be processed) into the incoming queue + * + * @param object $data + */ + function enqueueIncomingRaw($data) + { + $qm = QueueManager::get(); + $qm->enqueue($data, $this->transport . '-in'); + } + + /** + * given a screenname, get the corresponding user + * + * @param string $screenname + * + * @return User user + */ + function getUser($screenname) + { + $user_im_prefs = $this->getUserImPrefsFromScreenname($screenname); + if($user_im_prefs){ + $user = User::staticGet('id', $user_im_prefs->user_id); + $user_im_prefs->free(); + return $user; + }else{ + return false; + } + } + + /** + * given a screenname, get the User_im_prefs object for this transport + * + * @param string $screenname + * + * @return User_im_prefs user_im_prefs + */ + function getUserImPrefsFromScreenname($screenname) + { + $user_im_prefs = User_im_prefs::pkeyGet( + array('transport' => $this->transport, + 'screenname' => $this->normalize($screenname))); + if ($user_im_prefs) { + return $user_im_prefs; + } else { + return false; + } + } + + /** + * given a User, get their screenname + * + * @param User $user + * + * @return string screenname of that user + */ + function getScreenname($user) + { + $user_im_prefs = $this->getUserImPrefsFromUser($user); + if ($user_im_prefs) { + return $user_im_prefs->screenname; + } else { + return false; + } + } + + /** + * given a User, get their User_im_prefs + * + * @param User $user + * + * @return User_im_prefs user_im_prefs of that user + */ + function getUserImPrefsFromUser($user) + { + $user_im_prefs = User_im_prefs::pkeyGet( + array('transport' => $this->transport, + 'user_id' => $user->id)); + if ($user_im_prefs){ + return $user_im_prefs; + } else { + return false; + } + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - SENDING ========================\ + /** + * Send a message to a given screenname from the site + * + * @param string $screenname screenname to send the message to + * @param string $msg message contents to send + * + * @param boolean success + */ + protected function sendFromSite($screenname, $msg) + { + $text = '['.common_config('site', 'name') . '] ' . $msg; + $this->sendMessage($screenname, $text); + } + + /** + * send a confirmation code to a user + * + * @param string $screenname screenname sending to + * @param string $code the confirmation code + * @param User $user user sending to + * + * @return boolean success value + */ + function sendConfirmationCode($screenname, $code, $user) + { + $body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' . + 'If that\'s true, you can confirm by clicking on this URL: ' . + '%s' . + ' . (If you cannot click it, copy-and-paste it into the ' . + 'address bar of your browser). If that user isn\'t you, ' . + 'or if you didn\'t request this confirmation, just ignore this message.'), + $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code))); + + return $this->sendMessage($screenname, $body); + } + + /** + * send a notice to all public listeners + * + * For notices that are generated on the local system (by users), we can optionally + * forward them to remote listeners by XMPP. + * + * @param Notice $notice notice to broadcast + * + * @return boolean success flag + */ + + function publicNotice($notice) + { + // Now, users who want everything + + // FIXME PRIV don't send out private messages here + // XXX: should we send out non-local messages if public,localonly + // = false? I think not + + foreach ($this->public as $screenname) { + common_log(LOG_INFO, + 'Sending notice ' . $notice->id . + ' to public listener ' . $screenname, + __FILE__); + $this->sendNotice($screenname, $notice); + } + + return true; + } + + /** + * broadcast a notice to all subscribers and reply recipients + * + * This function will send a notice to all subscribers on the local server + * who have IM addresses, and have IM notification enabled, and + * have this subscription enabled for IM. It also sends the notice to + * all recipients of @-replies who have IM addresses and IM notification + * enabled. This is really the heart of IM distribution in StatusNet. + * + * @param Notice $notice The notice to broadcast + * + * @return boolean success flag + */ + + function broadcastNotice($notice) + { + + $ni = $notice->whoGets(); + + foreach ($ni as $user_id => $reason) { + $user = User::staticGet($user_id); + if (empty($user)) { + // either not a local user, or just not found + continue; + } + $user_im_prefs = $this->getUserImPrefsFromUser($user); + if(!$user_im_prefs || !$user_im_prefs->notify){ + continue; + } + + switch ($reason) { + case NOTICE_INBOX_SOURCE_REPLY: + if (!$user_im_prefs->replies) { + continue 2; + } + break; + case NOTICE_INBOX_SOURCE_SUB: + $sub = Subscription::pkeyGet(array('subscriber' => $user->id, + 'subscribed' => $notice->profile_id)); + if (empty($sub) || !$sub->jabber) { + continue 2; + } + break; + case NOTICE_INBOX_SOURCE_GROUP: + break; + default: + throw new Exception(sprintf(_("Unknown inbox source %d."), $reason)); + } + + common_log(LOG_INFO, + 'Sending notice ' . $notice->id . ' to ' . $user_im_prefs->screenname, + __FILE__); + $this->sendNotice($user_im_prefs->screenname, $notice); + $user_im_prefs->free(); + } + + return true; + } + + /** + * makes a plain-text formatted version of a notice, suitable for IM distribution + * + * @param Notice $notice notice being sent + * + * @return string plain-text version of the notice, with user nickname prefixed + */ + + function formatNotice($notice) + { + $profile = $notice->getProfile(); + return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']'; + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - RECEIVING ========================\ + + /** + * Attempt to handle a message as a command + * @param User $user user the message is from + * @param string $body message text + * @return boolean true if the message was a command and was executed, false if it was not a command + */ + protected function handleCommand($user, $body) + { + $inter = new CommandInterpreter(); + $cmd = $inter->handle_command($user, $body); + if ($cmd) { + $chan = new IMChannel($this); + $cmd->execute($chan); + return true; + } else { + return false; + } + } + + /** + * Is some text an autoreply message? + * @param string $txt message text + * @return boolean true if autoreply + */ + protected function isAutoreply($txt) + { + if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { + return true; + } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { + return true; + } else { + return false; + } + } + + /** + * Is some text an OTR message? + * @param string $txt message text + * @return boolean true if OTR + */ + protected function isOtr($txt) + { + if (preg_match('/^\?OTR/', $txt)) { + return true; + } else { + return false; + } + } + + /** + * Helper for handling incoming messages + * Your incoming message handler will probably want to call this function + * + * @param string $from screenname the message was sent from + * @param string $message message contents + * + * @param boolean success + */ + protected function handleIncoming($from, $notice_text) + { + $user = $this->getUser($from); + // For common_current_user to work + global $_cur; + $_cur = $user; + + if (!$user) { + $this->sendFromSite($from, 'Unknown user; go to ' . + common_local_url('imsettings') . + ' to add your address to your account'); + common_log(LOG_WARNING, 'Message from unknown user ' . $from); + return; + } + if ($this->handleCommand($user, $notice_text)) { + common_log(LOG_INFO, "Command message by $from handled."); + return; + } else if ($this->isAutoreply($notice_text)) { + common_log(LOG_INFO, 'Ignoring auto reply from ' . $from); + return; + } else if ($this->isOtr($notice_text)) { + common_log(LOG_INFO, 'Ignoring OTR from ' . $from); + return; + } else { + + common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname); + + $this->addNotice($from, $user, $notice_text); + } + + $user->free(); + unset($user); + unset($_cur); + unset($message); + } + + /** + * Helper for handling incoming messages + * Your incoming message handler will probably want to call this function + * + * @param string $from screenname the message was sent from + * @param string $message message contents + * + * @param boolean success + */ + protected function addNotice($screenname, $user, $body) + { + $body = trim(strip_tags($body)); + $content_shortened = common_shorten_links($body); + if (Notice::contentTooLong($content_shortened)) { + $this->sendFromSite($screenname, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), + Notice::maxContent(), + mb_strlen($content_shortened))); + return; + } + + try { + $notice = Notice::saveNew($user->id, $content_shortened, $this->transport); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $this->sendFromSite($from, $e->getMessage()); + return; + } + + common_log(LOG_INFO, + 'Added notice ' . $notice->id . ' from user ' . $user->nickname); + $notice->free(); + unset($notice); + } + + //========================EVENT HANDLERS========================\ + + /** + * Register notice queue handler + * + * @param QueueManager $manager + * + * @return boolean hook return + */ + function onEndInitializeQueueManager($manager) + { + $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im'); + $manager->connect($this->transport, new ImQueueHandler($this)); + $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im'); + return true; + } + + function onStartImDaemonIoManagers(&$classes) + { + //$classes[] = new ImManager($this); // handles sending/receiving/pings/reconnects + return true; + } + + function onStartEnqueueNotice($notice, &$transports) + { + $profile = Profile::staticGet($notice->profile_id); + + if (!$profile) { + common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . + 'unknown profile ' . common_log_objstring($notice), + __FILE__); + }else{ + $transports[] = $this->transport; + } + + return true; + } + + function onEndShowHeadElements($action) + { + $aname = $action->trimmed('action'); + + if ($aname == 'shownotice') { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $action->profile->id; + $user_im_prefs->transport = $this->transport; + + if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) { + $id = new Microid($this->microiduri($user_im_prefs->screenname), + $action->notice->uri); + $action->element('meta', array('name' => 'microid', + 'content' => $id->toString())); + } + + } else if ($aname == 'showstream') { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $action->user->id; + $user_im_prefs->transport = $this->transport; + + if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->profile->profileurl) { + $id = new Microid($this->microiduri($user_im_prefs->screenname), + $action->selfUrl()); + $action->element('meta', array('name' => 'microid', + 'content' => $id->toString())); + } + } + } + + function onNormalizeImScreenname($transport, &$screenname) + { + if($transport == $this->transport) + { + $screenname = $this->normalize($screenname); + return false; + } + } + + function onValidateImScreenname($transport, $screenname, &$valid) + { + if($transport == $this->transport) + { + $valid = $this->validate($screenname); + return false; + } + } + + function onGetImTransports(&$transports) + { + $transports[$this->transport] = array( + 'display' => $this->getDisplayName(), + 'daemonScreenname' => $this->daemonScreenname()); + } + + function onSendImConfirmationCode($transport, $screenname, $code, $user) + { + if($transport == $this->transport) + { + $this->sendConfirmationCode($screenname, $code, $user); + return false; + } + } + + function onUserDeleteRelated($user, &$tables) + { + $tables[] = 'User_im_prefs'; + return true; + } + + function initialize() + { + if( ! common_config('queue', 'enabled')) + { + throw new ServerException("Queueing must be enabled to use IM plugins"); + } + + if(is_null($this->transport)){ + throw new ServerException('transport cannot be null'); + } + } +} diff --git a/lib/imqueuehandler.php b/lib/imqueuehandler.php new file mode 100644 index 0000000000..9c35890c62 --- /dev/null +++ b/lib/imqueuehandler.php @@ -0,0 +1,48 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle a notice + * @param Notice $notice + * @return boolean success + */ + function handle($notice) + { + $this->plugin->broadcastNotice($notice); + if ($notice->is_local == Notice::LOCAL_PUBLIC || + $notice->is_local == Notice::LOCAL_NONPUBLIC) { + $this->plugin->publicNotice($notice); + } + return true; + } + +} diff --git a/lib/imreceiverqueuehandler.php b/lib/imreceiverqueuehandler.php new file mode 100644 index 0000000000..aa4a663b7a --- /dev/null +++ b/lib/imreceiverqueuehandler.php @@ -0,0 +1,42 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM receiving queue handlers. + */ + +class ImReceiverQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle incoming IM data sent by a user to the IM bot + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->plugin->receiveRawMessage($data); + } +} diff --git a/lib/imsenderqueuehandler.php b/lib/imsenderqueuehandler.php new file mode 100644 index 0000000000..790dd7b107 --- /dev/null +++ b/lib/imsenderqueuehandler.php @@ -0,0 +1,43 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImSenderQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle outgoing IM data to be sent from the bot to a user + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->plugin->imManager->send_raw_message($data); + } +} + diff --git a/lib/jabber.php b/lib/jabber.php deleted file mode 100644 index cdcfc44232..0000000000 --- a/lib/jabber.php +++ /dev/null @@ -1,640 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Evan Prodromou - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once 'XMPPHP/XMPP.php'; - -/** - * Splits a Jabber ID (JID) into node, domain, and resource portions. - * - * Based on validation routine submitted by: - * @copyright 2009 Patrick Georgi - * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. - * - * @param string $jid string to check - * - * @return array with "node", "domain", and "resource" indices - * @throws Exception if input is not valid - */ - -function jabber_split_jid($jid) -{ - $chars = ''; - /* the following definitions come from stringprep, Appendix C, - which is used in its entirety by nodeprop, Chapter 5, "Prohibited Output" */ - /* C1.1 ASCII space characters */ - $chars .= "\x{20}"; - /* C1.2 Non-ASCII space characters */ - $chars .= "\x{a0}\x{1680}\x{2000}-\x{200b}\x{202f}\x{205f}\x{3000a}"; - /* C2.1 ASCII control characters */ - $chars .= "\x{00}-\x{1f}\x{7f}"; - /* C2.2 Non-ASCII control characters */ - $chars .= "\x{80}-\x{9f}\x{6dd}\x{70f}\x{180e}\x{200c}\x{200d}\x{2028}\x{2029}\x{2060}-\x{2063}\x{206a}-\x{206f}\x{feff}\x{fff9}-\x{fffc}\x{1d173}-\x{1d17a}"; - /* C3 - Private Use */ - $chars .= "\x{e000}-\x{f8ff}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}"; - /* C4 - Non-character code points */ - $chars .= "\x{fdd0}-\x{fdef}\x{fffe}\x{ffff}\x{1fffe}\x{1ffff}\x{2fffe}\x{2ffff}\x{3fffe}\x{3ffff}\x{4fffe}\x{4ffff}\x{5fffe}\x{5ffff}\x{6fffe}\x{6ffff}\x{7fffe}\x{7ffff}\x{8fffe}\x{8ffff}\x{9fffe}\x{9ffff}\x{afffe}\x{affff}\x{bfffe}\x{bffff}\x{cfffe}\x{cffff}\x{dfffe}\x{dffff}\x{efffe}\x{effff}\x{ffffe}\x{fffff}\x{10fffe}\x{10ffff}"; - /* C5 - Surrogate codes */ - $chars .= "\x{d800}-\x{dfff}"; - /* C6 - Inappropriate for plain text */ - $chars .= "\x{fff9}-\x{fffd}"; - /* C7 - Inappropriate for canonical representation */ - $chars .= "\x{2ff0}-\x{2ffb}"; - /* C8 - Change display properties or are deprecated */ - $chars .= "\x{340}\x{341}\x{200e}\x{200f}\x{202a}-\x{202e}\x{206a}-\x{206f}"; - /* C9 - Tagging characters */ - $chars .= "\x{e0001}\x{e0020}-\x{e007f}"; - - /* Nodeprep forbids some more characters */ - $nodeprepchars = $chars; - $nodeprepchars .= "\x{22}\x{26}\x{27}\x{2f}\x{3a}\x{3c}\x{3e}\x{40}"; - - $parts = explode("/", $jid, 2); - if (count($parts) > 1) { - $resource = $parts[1]; - if ($resource == '') { - // Warning: empty resource isn't legit. - // But if we're normalizing, we may as well take it... - } - } else { - $resource = null; - } - - $node = explode("@", $parts[0]); - if ((count($node) > 2) || (count($node) == 0)) { - throw new Exception("Invalid JID: too many @s"); - } else if (count($node) == 1) { - $domain = $node[0]; - $node = null; - } else { - $domain = $node[1]; - $node = $node[0]; - if ($node == '') { - throw new Exception("Invalid JID: @ but no node"); - } - } - - // Length limits per http://xmpp.org/rfcs/rfc3920.html#addressing - if ($node !== null) { - if (strlen($node) > 1023) { - throw new Exception("Invalid JID: node too long."); - } - if (preg_match("/[".$nodeprepchars."]/u", $node)) { - throw new Exception("Invalid JID node '$node'"); - } - } - - if (strlen($domain) > 1023) { - throw new Exception("Invalid JID: domain too long."); - } - if (!common_valid_domain($domain)) { - throw new Exception("Invalid JID domain name '$domain'"); - } - - if ($resource !== null) { - if (strlen($resource) > 1023) { - throw new Exception("Invalid JID: resource too long."); - } - if (preg_match("/[".$chars."]/u", $resource)) { - throw new Exception("Invalid JID resource '$resource'"); - } - } - - return array('node' => is_null($node) ? null : mb_strtolower($node), - 'domain' => is_null($domain) ? null : mb_strtolower($domain), - 'resource' => $resource); -} - -/** - * Checks whether a string is a syntactically valid Jabber ID (JID), - * either with or without a resource. - * - * Note that a bare domain can be a valid JID. - * - * @param string $jid string to check - * @param bool $check_domain whether we should validate that domain... - * - * @return boolean whether the string is a valid JID - */ -function jabber_valid_full_jid($jid, $check_domain=false) -{ - try { - $parts = jabber_split_jid($jid); - if ($check_domain) { - if (!jabber_check_domain($parts['domain'])) { - return false; - } - } - return $parts['resource'] !== ''; // missing or present; empty ain't kosher - } catch (Exception $e) { - return false; - } -} - -/** - * Checks whether a string is a syntactically valid base Jabber ID (JID). - * A base JID won't include a resource specifier on the end; since we - * take it off when reading input we can't really use them reliably - * to direct outgoing messages yet (sorry guys!) - * - * Note that a bare domain can be a valid JID. - * - * @param string $jid string to check - * @param bool $check_domain whether we should validate that domain... - * - * @return boolean whether the string is a valid JID - */ -function jabber_valid_base_jid($jid, $check_domain=false) -{ - try { - $parts = jabber_split_jid($jid); - if ($check_domain) { - if (!jabber_check_domain($parts['domain'])) { - return false; - } - } - return ($parts['resource'] === null); // missing; empty ain't kosher - } catch (Exception $e) { - return false; - } -} - -/** - * Normalizes a Jabber ID for comparison, dropping the resource component if any. - * - * @param string $jid JID to check - * @param bool $check_domain if true, reject if the domain isn't findable - * - * @return string an equivalent JID in normalized (lowercase) form - */ - -function jabber_normalize_jid($jid) -{ - try { - $parts = jabber_split_jid($jid); - if ($parts['node'] !== null) { - return $parts['node'] . '@' . $parts['domain']; - } else { - return $parts['domain']; - } - } catch (Exception $e) { - return null; - } -} - -/** - * Check if this domain's got some legit DNS record - */ -function jabber_check_domain($domain) -{ - if (checkdnsrr("_xmpp-server._tcp." . $domain, "SRV")) { - return true; - } - if (checkdnsrr($domain, "ANY")) { - return true; - } - return false; -} - -/** - * the JID of the Jabber daemon for this StatusNet instance - * - * @return string JID of the Jabber daemon - */ - -function jabber_daemon_address() -{ - return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server'); -} - -class Sharing_XMPP extends XMPPHP_XMPP -{ - function getSocket() - { - return $this->socket; - } -} - -/** - * Build an XMPP proxy connection that'll save outgoing messages - * to the 'xmppout' queue to be picked up by xmppdaemon later. - * - * If queueing is disabled, we'll grab a live connection. - * - * @return XMPPHP - */ -function jabber_proxy() -{ - if (common_config('queue', 'enabled')) { - $proxy = new Queued_XMPP(common_config('xmpp', 'host') ? - common_config('xmpp', 'host') : - common_config('xmpp', 'server'), - common_config('xmpp', 'port'), - common_config('xmpp', 'user'), - common_config('xmpp', 'password'), - common_config('xmpp', 'resource') . 'daemon', - common_config('xmpp', 'server'), - common_config('xmpp', 'debug') ? - true : false, - common_config('xmpp', 'debug') ? - XMPPHP_Log::LEVEL_VERBOSE : null); - return $proxy; - } else { - return jabber_connect(); - } -} - -/** - * Lazy-connect the configured Jabber account to the configured server; - * if already opened, the same connection will be returned. - * - * In a multi-site background process, each site configuration - * will get its own connection. - * - * @param string $resource Resource to connect (defaults to configured resource) - * - * @return XMPPHP connection to the configured server - */ - -function jabber_connect($resource=null) -{ - static $connections = array(); - $site = common_config('site', 'server'); - if (empty($connections[$site])) { - if (empty($resource)) { - $resource = common_config('xmpp', 'resource'); - } - $conn = new Sharing_XMPP(common_config('xmpp', 'host') ? - common_config('xmpp', 'host') : - common_config('xmpp', 'server'), - common_config('xmpp', 'port'), - common_config('xmpp', 'user'), - common_config('xmpp', 'password'), - $resource, - common_config('xmpp', 'server'), - common_config('xmpp', 'debug') ? - true : false, - common_config('xmpp', 'debug') ? - XMPPHP_Log::LEVEL_VERBOSE : null - ); - - if (!$conn) { - return false; - } - $connections[$site] = $conn; - - $conn->autoSubscribe(); - $conn->useEncryption(common_config('xmpp', 'encryption')); - - try { - common_log(LOG_INFO, __METHOD__ . ": connecting " . - common_config('xmpp', 'user') . '/' . $resource); - //$conn->connect(true); // true = persistent connection - $conn->connect(); // persistent connections break multisite - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - return false; - } - - $conn->processUntil('session_start'); - } - return $connections[$site]; -} - -/** - * Queue send for a single notice to a given Jabber address - * - * @param string $to JID to send the notice to - * @param Notice $notice notice to send - * - * @return boolean success value - */ - -function jabber_send_notice($to, $notice) -{ - $conn = jabber_proxy(); - $profile = Profile::staticGet($notice->profile_id); - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to send notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return false; - } - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - $conn->message($to, $msg, 'chat', null, $entry); - $profile->free(); - return true; -} - -/** - * extra information for XMPP messages, as defined by Twitter - * - * @param Profile $profile Profile of the sending user - * @param Notice $notice Notice being sent - * - * @return string Extra information (Atom, HTML, addresses) in string format - */ - -function jabber_format_entry($profile, $notice) -{ - $entry = $notice->asAtomEntry(true, true); - - $xs = new XMLStringer(); - $xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im')); - $xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml')); - $xs->element('a', array('href' => $profile->profileurl), - $profile->nickname); - $xs->text(": "); - if (!empty($notice->rendered)) { - $xs->raw($notice->rendered); - } else { - $xs->raw(common_render_content($notice->content, $notice)); - } - $xs->text(" "); - $xs->element('a', array( - 'href'=>common_local_url('conversation', - array('id' => $notice->conversation)).'#notice-'.$notice->id - ),sprintf(_('[%s]'),$notice->id)); - $xs->elementEnd('body'); - $xs->elementEnd('html'); - - $html = $xs->getString(); - - return $html . ' ' . $entry; -} - -/** - * sends a single text message to a given JID - * - * @param string $to JID to send the message to - * @param string $body body of the message - * @param string $type type of the message - * @param string $subject subject of the message - * - * @return boolean success flag - */ - -function jabber_send_message($to, $body, $type='chat', $subject=null) -{ - $conn = jabber_proxy(); - $conn->message($to, $body, $type, $subject); - return true; -} - -/** - * sends a presence stanza on the Jabber network - * - * @param string $status current status, free-form string - * @param string $show structured status value - * @param string $to recipient of presence, null for general - * @param string $type type of status message, related to $show - * @param int $priority priority of the presence - * - * @return boolean success value - */ - -function jabber_send_presence($status, $show='available', $to=null, - $type = 'available', $priority=null) -{ - $conn = jabber_connect(); - if (!$conn) { - return false; - } - $conn->presence($status, $show, $to, $type, $priority); - return true; -} - -/** - * sends a confirmation request to a JID - * - * @param string $code confirmation code for confirmation URL - * @param string $nickname nickname of confirming user - * @param string $address JID to send confirmation to - * - * @return boolean success flag - */ - -function jabber_confirm_address($code, $nickname, $address) -{ - $body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' . - 'has said that your Jabber ID belongs to them. ' . - 'If that\'s true, you can confirm by clicking on this URL: ' . - common_local_url('confirmaddress', array('code' => $code)) . - ' . (If you cannot click it, copy-and-paste it into the ' . - 'address bar of your browser). If that user isn\'t you, ' . - 'or if you didn\'t request this confirmation, just ignore this message.'; - - return jabber_send_message($address, $body); -} - -/** - * sends a "special" presence stanza on the Jabber network - * - * @param string $type Type of presence - * @param string $to JID to send presence to - * @param string $show show value for presence - * @param string $status status value for presence - * - * @return boolean success flag - * - * @see jabber_send_presence() - */ - -function jabber_special_presence($type, $to=null, $show=null, $status=null) -{ - // FIXME: why use this instead of jabber_send_presence()? - $conn = jabber_connect(); - - $to = htmlspecialchars($to); - $status = htmlspecialchars($status); - - $out = "send($out); -} - -/** - * Queue broadcast of a notice to all subscribers and reply recipients - * - * This function will send a notice to all subscribers on the local server - * who have Jabber addresses, and have Jabber notification enabled, and - * have this subscription enabled for Jabber. It also sends the notice to - * all recipients of @-replies who have Jabber addresses and Jabber notification - * enabled. This is really the heart of Jabber distribution in StatusNet. - * - * @param Notice $notice The notice to broadcast - * - * @return boolean success flag - */ - -function jabber_broadcast_notice($notice) -{ - if (!common_config('xmpp', 'enabled')) { - return true; - } - $profile = Profile::staticGet($notice->profile_id); - - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return true; // not recoverable; discard. - } - - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - - $profile->free(); - unset($profile); - - $sent_to = array(); - - $conn = jabber_proxy(); - - $ni = $notice->whoGets(); - - foreach ($ni as $user_id => $reason) { - $user = User::staticGet($user_id); - if (empty($user) || - empty($user->jabber) || - !$user->jabbernotify) { - // either not a local user, or just not found - continue; - } - switch ($reason) { - case NOTICE_INBOX_SOURCE_REPLY: - if (!$user->jabberreplies) { - continue 2; - } - break; - case NOTICE_INBOX_SOURCE_SUB: - $sub = Subscription::pkeyGet(array('subscriber' => $user->id, - 'subscribed' => $notice->profile_id)); - if (empty($sub) || !$sub->jabber) { - continue 2; - } - break; - case NOTICE_INBOX_SOURCE_GROUP: - break; - default: - throw new Exception(sprintf(_("Unknown inbox source %d."), $reason)); - } - - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . ' to ' . $user->jabber, - __FILE__); - $conn->message($user->jabber, $msg, 'chat', null, $entry); - } - - return true; -} - -/** - * Queue send of a notice to all public listeners - * - * For notices that are generated on the local system (by users), we can optionally - * forward them to remote listeners by XMPP. - * - * @param Notice $notice notice to broadcast - * - * @return boolean success flag - */ - -function jabber_public_notice($notice) -{ - // Now, users who want everything - - $public = common_config('xmpp', 'public'); - - // FIXME PRIV don't send out private messages here - // XXX: should we send out non-local messages if public,localonly - // = false? I think not - - if ($public && $notice->is_local == Notice::LOCAL_PUBLIC) { - $profile = Profile::staticGet($notice->profile_id); - - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return true; // not recoverable; discard. - } - - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - - $conn = jabber_proxy(); - - foreach ($public as $address) { - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . - ' to public listener ' . $address, - __FILE__); - $conn->message($address, $msg, 'chat', null, $entry); - } - $profile->free(); - } - - return true; -} - -/** - * makes a plain-text formatted version of a notice, suitable for Jabber distribution - * - * @param Profile &$profile profile of the sending user - * @param Notice &$notice notice being sent - * - * @return string plain-text version of the notice, with user nickname prefixed - */ - -function jabber_format_notice(&$profile, &$notice) -{ - return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']'; -} diff --git a/lib/jabberqueuehandler.php b/lib/jabberqueuehandler.php deleted file mode 100644 index d6b4b7416a..0000000000 --- a/lib/jabberqueuehandler.php +++ /dev/null @@ -1,47 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Queue handler for pushing new notices to Jabber users. - * @fixme this exception handling doesn't look very good. - */ -class JabberQueueHandler extends QueueHandler -{ - var $conn = null; - - function transport() - { - return 'jabber'; - } - - function handle($notice) - { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_broadcast_notice($notice); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; - } - } -} diff --git a/lib/plugindisableform.php b/lib/plugindisableform.php new file mode 100644 index 0000000000..3cbabdb2ce --- /dev/null +++ b/lib/plugindisableform.php @@ -0,0 +1,93 @@ +. + * + * @category Form + * @package StatusNet + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Form for joining a group + * + * @category Form + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see PluginEnableForm + */ + +class PluginDisableForm extends PluginEnableForm +{ + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'plugin-disable-' . $this->plugin; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_plugin_disable'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('plugindisable', + array('plugin' => $this->plugin)); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + // TRANS: Plugin admin panel controls + $this->out->submit('submit', _m('plugin', 'Disable')); + } + +} diff --git a/lib/pluginenableform.php b/lib/pluginenableform.php new file mode 100644 index 0000000000..8683ffd0be --- /dev/null +++ b/lib/pluginenableform.php @@ -0,0 +1,114 @@ +. + * + * @category Form + * @package StatusNet + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/form.php'; + +/** + * Form for joining a group + * + * @category Form + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see PluginDisableForm + */ + +class PluginEnableForm extends Form +{ + /** + * Plugin to enable/disable + */ + + var $plugin = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param string $plugin plugin to enable/disable + */ + + function __construct($out=null, $plugin=null) + { + parent::__construct($out); + + $this->plugin = $plugin; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'plugin-enable-' . $this->plugin; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_plugin_enable'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('pluginenable', + array('plugin' => $this->plugin)); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + // TRANS: Plugin admin panel controls + $this->out->submit('submit', _m('plugin', 'Enable')); + } +} diff --git a/lib/pluginlist.php b/lib/pluginlist.php new file mode 100644 index 0000000000..07a17ba397 --- /dev/null +++ b/lib/pluginlist.php @@ -0,0 +1,213 @@ +. + * + * @category Settings + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require INSTALLDIR . "/lib/pluginenableform.php"; +require INSTALLDIR . "/lib/plugindisableform.php"; + +/** + * Plugin list + * + * @category Admin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PluginList extends Widget +{ + var $plugins = array(); + + function __construct($plugins, $out) + { + parent::__construct($out); + $this->plugins = $plugins; + } + + function show() + { + $this->startList(); + $this->showPlugins(); + $this->endList(); + } + + function startList() + { + $this->out->elementStart('table', 'plugin_list'); + } + + function endList() + { + $this->out->elementEnd('table'); + } + + function showPlugins() + { + foreach ($this->plugins as $plugin) { + $pli = $this->newListItem($plugin); + $pli->show(); + } + } + + function newListItem($plugin) + { + return new PluginListItem($plugin, $this->out); + } +} + +class PluginListItem extends Widget +{ + /** Current plugin. */ + var $plugin = null; + + /** Local cache for plugin version info */ + protected static $versions = false; + + function __construct($plugin, $out) + { + parent::__construct($out); + $this->plugin = $plugin; + } + + function show() + { + $meta = $this->metaInfo(); + + $this->out->elementStart('tr', array('id' => 'plugin-' . $this->plugin)); + + // Name and controls + $this->out->elementStart('td'); + $this->out->elementStart('div'); + if (!empty($meta['homepage'])) { + $this->out->elementStart('a', array('href' => $meta['homepage'])); + } + $this->out->text($this->plugin); + if (!empty($meta['homepage'])) { + $this->out->elementEnd('a'); + } + $this->out->elementEnd('div'); + + $form = $this->getControlForm(); + $form->show(); + + $this->out->elementEnd('td'); + + // Version and authors + $this->out->elementStart('td'); + if (!empty($meta['version'])) { + $this->out->elementStart('div'); + $this->out->text($meta['version']); + $this->out->elementEnd('div'); + } + if (!empty($meta['author'])) { + $this->out->elementStart('div'); + $this->out->text($meta['author']); + $this->out->elementEnd('div'); + } + $this->out->elementEnd('td'); + + // Description + $this->out->elementStart('td'); + if (!empty($meta['rawdescription'])) { + $this->out->raw($meta['rawdescription']); + } + $this->out->elementEnd('td'); + + $this->out->elementEnd('tr'); + } + + /** + * Pull up the appropriate control form for this plugin, depending + * on its current state. + * + * @return Form + */ + protected function getControlForm() + { + $key = 'disable-' . $this->plugin; + if (common_config('plugins', $key)) { + return new PluginEnableForm($this->out, $this->plugin); + } else { + return new PluginDisableForm($this->out, $this->plugin); + } + } + + /** + * Grab metadata about this plugin... + * Warning: horribly inefficient and may explode! + * Doesn't work for disabled plugins either. + * + * @fixme pull structured data from plugin source + */ + function metaInfo() + { + $versions = self::getPluginVersions(); + $found = false; + + foreach ($versions as $info) { + // hack for URL shorteners... "LilUrl (ur1.ca)" etc + list($name, ) = explode(' ', $info['name']); + + if ($name == $this->plugin) { + if ($found) { + // hack for URL shorteners... + $found['rawdescription'] .= "
\n" . $info['rawdescription']; + } else { + $found = $info; + } + } + } + + if ($found) { + return $found; + } else { + return array('name' => $this->plugin, + 'rawdescription' => _m('plugin-description', + '(Plugin descriptions unavailable when disabled.)')); + } + } + + /** + * Lazy-load the set of active plugin version info + * @return array + */ + protected static function getPluginVersions() + { + if (!is_array(self::$versions)) { + $versions = array(); + Event::handle('PluginVersion', array(&$versions)); + self::$versions = $versions; + } + return self::$versions; + } +} diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php deleted file mode 100644 index a497d13850..0000000000 --- a/lib/publicqueuehandler.php +++ /dev/null @@ -1,45 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Queue handler for pushing new notices to public XMPP subscribers. - */ -class PublicQueueHandler extends QueueHandler -{ - - function transport() - { - return 'public'; - } - - function handle($notice) - { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_public_notice($notice); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; - } - } -} diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php deleted file mode 100644 index f6bccfd5ba..0000000000 --- a/lib/queued_xmpp.php +++ /dev/null @@ -1,127 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Brion Vibber - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/jabber.php'; - -class Queued_XMPP extends XMPPHP_XMPP -{ - /** - * Constructor - * - * @param string $host - * @param integer $port - * @param string $user - * @param string $password - * @param string $resource - * @param string $server - * @param boolean $printlog - * @param string $loglevel - */ - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) - { - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - - // We use $host to connect, but $server to build JIDs if specified. - // This seems to fix an upstream bug where $host was used to build - // $this->basejid, never seen since it isn't actually used in the base - // classes. - if (!$server) { - $server = $this->host; - } - $this->basejid = $this->user . '@' . $server; - - // Normally the fulljid is filled out by the server at resource binding - // time, but we need to do it since we're not talking to a real server. - $this->fulljid = "{$this->basejid}/{$this->resource}"; - } - - /** - * Send a formatted message to the outgoing queue for later forwarding - * to a real XMPP connection. - * - * @param string $msg - */ - public function send($msg, $timeout=NULL) - { - $qm = QueueManager::get('xmppout'); - $qm->enqueue(strval($msg), 'xmppout'); - } - - /** - * Since we'll be getting input through a queue system's run loop, - * we'll process one standalone message at a time rather than our - * own XMPP message pump. - * - * @param string $message - */ - public function processMessage($message) { - $frame = array_shift($this->frames); - xml_parse($this->parser, $frame->body, false); - } - - //@{ - /** - * Stream i/o functions disabled; push input through processMessage() - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) - { - throw new Exception("Can't connect to server from XMPP queue proxy."); - } - - public function disconnect() - { - throw new Exception("Can't connect to server from XMPP queue proxy."); - } - - public function process() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function processUntil($event, $timeout=-1) - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function read() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function readyToProcess() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - //@} -} - diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 2909cd83b1..2194dd1618 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -36,20 +36,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } class QueueHandler { - /** - * Return transport keyword which identifies items this queue handler - * services; must be defined for all subclasses. - * - * Must be 8 characters or less to fit in the queue_item database. - * ex "email", "jabber", "sms", "irc", ... - * - * @return string - */ - function transport() - { - return null; - } - /** * Here's the meat of your queue handler -- you're handed a Notice * or other object, which you may do as you will with. diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 0829c8a8bc..6666a6cb5a 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -156,21 +156,14 @@ abstract class QueueManager extends IoManager } /** - * Encode an object or variable for queued storage. - * Notice objects are currently stored as an id reference; - * other items are serialized. + * Encode an object for queued storage. * * @param mixed $item * @return string */ protected function encode($item) { - if ($item instanceof Notice) { - // Backwards compat - return $item->id; - } else { - return serialize($item); - } + return serialize($item); } /** @@ -182,25 +175,7 @@ abstract class QueueManager extends IoManager */ protected function decode($frame) { - if (is_numeric($frame)) { - // Back-compat for notices... - return Notice::staticGet(intval($frame)); - } elseif (substr($frame, 0, 1) == '<') { - // Back-compat for XML source - return $frame; - } else { - // Deserialize! - #$old = error_reporting(); - #error_reporting($old & ~E_NOTICE); - $out = unserialize($frame); - #error_reporting($old); - - if ($out === false && $frame !== 'b:0;') { - common_log(LOG_ERR, "Couldn't unserialize queued frame: $frame"); - return false; - } - return $out; - } + return unserialize($frame); } /** @@ -270,16 +245,6 @@ abstract class QueueManager extends IoManager // Broadcasting profile updates to OMB remote subscribers $this->connect('profile', 'ProfileQueueHandler'); - // XMPP output handlers... - if (common_config('xmpp', 'enabled')) { - // Delivery prep, read by queuedaemon.php: - $this->connect('jabber', 'JabberQueueHandler'); - $this->connect('public', 'PublicQueueHandler'); - - // Raw output, read by xmppdaemon.php: - $this->connect('xmppout', 'XmppOutQueueHandler', 'xmpp'); - } - // For compat with old plugins not registering their own handlers. $this->connect('plugin', 'PluginQueueHandler'); } diff --git a/lib/queuemonitor.php b/lib/queuemonitor.php index 1c306a6298..3dc0ea65aa 100644 --- a/lib/queuemonitor.php +++ b/lib/queuemonitor.php @@ -36,7 +36,7 @@ class QueueMonitor * Only explicitly listed thread/site/queue owners will be incremented. * * @param string $key counter name - * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01' + * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01' */ public function stats($key, $owners=array()) { diff --git a/lib/router.php b/lib/router.php index 00b2993734..3bbb4a044e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -151,6 +151,8 @@ class Router $m->connect('main/xrds', array('action' => 'publicxrds')); + $m->connect('.well-known/host-meta', + array('action' => 'hostmeta')); // these take a code @@ -655,6 +657,12 @@ class Router $m->connect('api/statusnet/groups/create.:format', array('action' => 'ApiGroupCreate', 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/update/:id.:format', + array('action' => 'ApiGroupProfileUpdate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + // Tags $m->connect('api/statusnet/tags/timeline/:tag.:format', array('action' => 'ApiTimelineTag', @@ -691,7 +699,13 @@ class Router $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel')); $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel')); $m->connect('admin/license', array('action' => 'licenseadminpanel')); - + $m->connect('admin/plugins', array('action' => 'pluginsadminpanel')); + $m->connect('admin/plugins/enable/:plugin', + array('action' => 'pluginenable'), + array('plugin' => '[A-Za-z0-9_]+')); + $m->connect('admin/plugins/disable/:plugin', + array('action' => 'plugindisable'), + array('plugin' => '[A-Za-z0-9_]+')); $m->connect('getfile/:filename', array('action' => 'getfile'), diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index 2f9f6e32e3..ea09b6fb2f 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -204,7 +204,7 @@ abstract class SpawningDaemon extends Daemon // Reconnect main memcached, or threads will stomp on // each other and corrupt their requests. - $cache = common_memcache(); + $cache = Cache::instance(); if ($cache) { $cache->reconnect(); } diff --git a/lib/statusnet.php b/lib/statusnet.php index 7212a4a47d..ff0502915a 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -177,6 +177,11 @@ class StatusNet { // Load default plugins foreach (common_config('plugins', 'default') as $name => $params) { + $key = 'disable-' . $name; + if (common_config('plugins', $key)) { + continue; + } + if (is_null($params)) { addPlugin($name); } else if (is_array($params)) { @@ -354,7 +359,6 @@ class StatusNet } // Backwards compatibility - if (array_key_exists('memcached', $config)) { if ($config['memcached']['enabled']) { addPlugin('Memcache', array('servers' => $config['memcached']['server'])); @@ -364,6 +368,21 @@ class StatusNet $config['cache']['base'] = $config['memcached']['base']; } } + if (array_key_exists('xmpp', $config)) { + if ($config['xmpp']['enabled']) { + addPlugin('xmpp', array( + 'server' => $config['xmpp']['server'], + 'port' => $config['xmpp']['port'], + 'user' => $config['xmpp']['user'], + 'resource' => $config['xmpp']['resource'], + 'encryption' => $config['xmpp']['encryption'], + 'password' => $config['xmpp']['password'], + 'host' => $config['xmpp']['host'], + 'debug' => $config['xmpp']['debug'], + 'public' => $config['xmpp']['public'] + )); + } + } } } diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index fc98c77d40..1d9a5ad207 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -578,7 +578,7 @@ class StompQueueManager extends QueueManager function incDeliveryCount($msgId) { $count = 0; - $cache = common_memcache(); + $cache = Cache::instance(); if ($cache) { $key = 'statusnet:stomp:message-retries:' . $msgId; $count = $cache->increment($key); diff --git a/lib/urlshortenerplugin.php b/lib/urlshortenerplugin.php new file mode 100644 index 0000000000..8acfac26f4 --- /dev/null +++ b/lib/urlshortenerplugin.php @@ -0,0 +1,155 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Superclass for plugins that do URL shortening + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class UrlShortenerPlugin extends Plugin +{ + public $shortenerName; + public $freeService = false; + + // Url Shortener plugins should implement some (or all) + // of these methods + + /** + * Make an URL shorter. + * + * @param string $url URL to shorten + * + * @return string shortened version of the url, or null on failure + */ + + protected abstract function shorten($url); + + /** + * Utility to get the data at an URL + * + * @param string $url URL to fetch + * + * @return string response body + * + * @todo rename to code-standard camelCase httpGet() + */ + + protected function http_get($url) + { + $request = HTTPClient::start(); + $response = $request->get($url); + return $response->getBody(); + } + + /** + * Utility to post a request and get a response URL + * + * @param string $url URL to fetch + * @param array $data post parameters + * + * @return string response body + * + * @todo rename to code-standard httpPost() + */ + + protected function http_post($url, $data) + { + $request = HTTPClient::start(); + $response = $request->post($url, null, $data); + return $response->getBody(); + } + + // Hook handlers + + /** + * Called when all plugins have been initialized + * + * @return boolean hook value + */ + + function onInitializePlugin() + { + if (!isset($this->shortenerName)) { + throw new Exception("must specify a shortenerName"); + } + return true; + } + + /** + * Called when a showing the URL shortener drop-down box + * + * Properties of the shortening service currently only + * include whether it's a free service. + * + * @param array &$shorteners array mapping shortener name to properties + * + * @return boolean hook value + */ + + function onGetUrlShorteners(&$shorteners) + { + $shorteners[$this->shortenerName] = + array('freeService' => $this->freeService); + return true; + } + + /** + * Called to shorten an URL + * + * @param string $url URL to shorten + * @param string $shortenerName Shortening service. Don't handle if it's + * not you! + * @param string &$shortenedUrl URL after shortening; out param. + * + * @return boolean hook value + */ + + function onStartShortenUrl($url, $shortenerName, &$shortenedUrl) + { + if ($shortenerName == $this->shortenerName) { + $result = $this->shorten($url); + if (isset($result) && $result != null && $result !== false) { + $shortenedUrl = $result; + common_log(LOG_INFO, + __CLASS__ . ": $this->shortenerName ". + "shortened $url to $shortenedUrl"); + return false; + } + } + return true; + } +} diff --git a/lib/util.php b/lib/util.php index dc853f657b..b20ed8225f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -145,7 +145,6 @@ function common_switch_locale($language=null) textdomain("statusnet"); } - function common_timezone() { if (common_logged_in()) { @@ -158,22 +157,38 @@ function common_timezone() return common_config('site', 'timezone'); } +function common_valid_language($lang) +{ + if ($lang) { + // Validate -- we don't want to end up with a bogus code + // left over from some old junk. + foreach (common_config('site', 'languages') as $code => $info) { + if ($info['lang'] == $lang) { + return true; + } + } + } + return false; +} + function common_language() { + // Allow ?uselang=xx override, very useful for debugging + // and helping translators check usage and context. + if (isset($_GET['uselang'])) { + $uselang = strval($_GET['uselang']); + if (common_valid_language($uselang)) { + return $uselang; + } + } + // If there is a user logged in and they've set a language preference // then return that one... if (_have_config() && common_logged_in()) { $user = common_current_user(); - $user_language = $user->language; - - if ($user->language) { - // Validate -- we don't want to end up with a bogus code - // left over from some old junk. - foreach (common_config('site', 'languages') as $code => $info) { - if ($info['lang'] == $user_language) { - return $user_language; - } - } + + if (common_valid_language($user->language)) { + return $user->language; } } @@ -901,9 +916,21 @@ function common_linkify($url) { function common_shorten_links($text, $always = false) { - $maxLength = Notice::maxContent(); - if (!$always && ($maxLength == 0 || mb_strlen($text) <= $maxLength)) return $text; - return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + common_debug("common_shorten_links() called"); + + $user = common_current_user(); + + $maxLength = User_urlshortener_prefs::maxNoticeLength($user); + + common_debug("maxLength = $maxLength"); + + if ($always || mb_strlen($text) > $maxLength) { + common_debug("Forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'forceShort')); + } else { + common_debug("Not forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + } } function common_xml_safe_str($str) @@ -1249,14 +1276,8 @@ function common_redirect($url, $code=307) exit; } -function common_broadcast_notice($notice, $remote=false) -{ - // DO NOTHING! -} +// Stick the notice on the queue -/** - * Stick the notice on the queue. - */ function common_enqueue_notice($notice) { static $localTransports = array('omb', @@ -1270,18 +1291,9 @@ function common_enqueue_notice($notice) $transports[] = 'plugin'; } - $xmpp = common_config('xmpp', 'enabled'); - - if ($xmpp) { - $transports[] = 'jabber'; - } - // We can skip these for gatewayed notices. if ($notice->isLocal()) { $transports = array_merge($transports, $localTransports); - if ($xmpp) { - $transports[] = 'public'; - } } if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) { @@ -1813,21 +1825,6 @@ function common_session_token() return $_SESSION['token']; } -function common_cache_key($extra) -{ - return Cache::key($extra); -} - -function common_keyize($str) -{ - return Cache::keyize($str); -} - -function common_memcache() -{ - return Cache::instance(); -} - function common_license_terms($uri) { if(preg_match('/creativecommons.org\/licenses\/([^\/]+)/', $uri, $matches)) { @@ -1868,30 +1865,42 @@ function common_database_tablename($tablename) /** * Shorten a URL with the current user's configured shortening service, * or ur1.ca if configured, or not at all if no shortening is set up. - * Length is not considered. * - * @param string $long_url + * @param string $long_url original URL + * @param boolean $force Force shortening (used when notice is too long) + * * @return string may return the original URL if shortening failed * * @fixme provide a way to specify a particular shortener * @fixme provide a way to specify to use a given user's shortening preferences */ -function common_shorten_url($long_url) + +function common_shorten_url($long_url, $force = false) { + common_debug("Shortening URL '$long_url' (force = $force)"); + $long_url = trim($long_url); + $user = common_current_user(); - if (empty($user)) { - // common current user does not find a user when called from the XMPP daemon - // therefore we'll set one here fix, so that XMPP given URLs may be shortened - $shortenerName = 'ur1.ca'; - } else { - $shortenerName = $user->urlshorteningservice; + + $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user); + common_debug("maxUrlLength = $maxUrlLength"); + + // $force forces shortening even if it's not strictly needed + + if (mb_strlen($long_url) < $maxUrlLength && !$force) { + common_debug("Skipped shortening URL."); + return $long_url; } - if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){ + $shortenerName = User_urlshortener_prefs::urlShorteningService($user); + + common_debug("Shortener name = '$shortenerName'"); + + if (Event::handle('StartShortenUrl', array($long_url, $shortenerName, &$shortenedUrl))) { //URL wasn't shortened, so return the long url return $long_url; - }else{ + } else { //URL was shortened, so return the result return trim($shortenedUrl); } diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php deleted file mode 100644 index 829eaa36cb..0000000000 --- a/lib/xmppmanager.php +++ /dev/null @@ -1,486 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -/** - * XMPP background connection manager for XMPP-using queue handlers, - * allowing them to send outgoing messages on the right connection. - * - * Input is handled during socket select loop, keepalive pings during idle. - * Any incoming messages will be forwarded to the main XmppDaemon process, - * which handles direct user interaction. - * - * In a multi-site queuedaemon.php run, one connection will be instantiated - * for each site being handled by the current process that has XMPP enabled. - */ - -class XmppManager extends IoManager -{ - protected $site = null; - protected $pingid = 0; - protected $lastping = null; - protected $conn = null; - - static protected $singletons = array(); - - const PING_INTERVAL = 120; - - /** - * Fetch the singleton XmppManager for the current site. - * @return mixed XmppManager, or false if unneeded - */ - public static function get() - { - if (common_config('xmpp', 'enabled')) { - $site = StatusNet::currentSite(); - if (empty(self::$singletons[$site])) { - self::$singletons[$site] = new XmppManager(); - } - return self::$singletons[$site]; - } else { - return false; - } - } - - /** - * Tell the i/o master we need one instance for each supporting site - * being handled in this process. - */ - public static function multiSite() - { - return IoManager::INSTANCE_PER_SITE; - } - - function __construct() - { - $this->site = StatusNet::currentSite(); - $this->resource = common_config('xmpp', 'resource') . 'daemon'; - } - - /** - * Initialize connection to server. - * @return boolean true on success - */ - public function start($master) - { - parent::start($master); - $this->switchSite(); - - require_once INSTALLDIR . "/lib/jabber.php"; - - # Low priority; we don't want to receive messages - - common_log(LOG_INFO, "INITIALIZE"); - $this->conn = jabber_connect($this->resource); - - if (empty($this->conn)) { - common_log(LOG_ERR, "Couldn't connect to server."); - return false; - } - - $this->log(LOG_DEBUG, "Initializing stanza handlers."); - - $this->conn->addEventHandler('message', 'handle_message', $this); - $this->conn->addEventHandler('presence', 'handle_presence', $this); - $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); - - $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100); - - return !is_null($this->conn); - } - - /** - * Message pump is triggered on socket input, so we only need an idle() - * call often enough to trigger our outgoing pings. - */ - function timeout() - { - return self::PING_INTERVAL; - } - - /** - * Lists the XMPP connection socket to allow i/o master to wake - * when input comes in here as well as from the queue source. - * - * @return array of resources - */ - public function getSockets() - { - if ($this->conn) { - return array($this->conn->getSocket()); - } else { - return array(); - } - } - - /** - * Process XMPP events that have come in over the wire. - * Side effects: may switch site configuration - * @fixme may kill process on XMPP error - * @param resource $socket - */ - public function handleInput($socket) - { - $this->switchSite(); - - # Process the queue for as long as needed - try { - if ($this->conn) { - assert($socket === $this->conn->getSocket()); - - common_log(LOG_DEBUG, "Servicing the XMPP queue."); - $this->stats('xmpp_process'); - $this->conn->processTime(0); - } - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } - } - - /** - * Idle processing for io manager's execution loop. - * Send keepalive pings to server. - * - * Side effect: kills process on exception from XMPP library. - * - * @fixme non-dying error handling - */ - public function idle($timeout=0) - { - if ($this->conn) { - $now = time(); - if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { - $this->switchSite(); - try { - $this->sendPing(); - $this->lastping = $now; - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } - } - } - } - - /** - * For queue handlers to pass us a message to push out, - * if we're active. - * - * @fixme should this be blocking etc? - * - * @param string $msg XML stanza to send - * @return boolean success - */ - public function send($msg) - { - if ($this->conn && !$this->conn->isDisconnected()) { - $bytes = $this->conn->send($msg); - if ($bytes > 0) { - $this->conn->processTime(0); - return true; - } else { - return false; - } - } else { - // Can't send right now... - return false; - } - } - - /** - * Send a keepalive ping to the XMPP server. - */ - protected function sendPing() - { - $jid = jabber_daemon_address().'/'.$this->resource; - $server = common_config('xmpp', 'server'); - - if (!isset($this->pingid)) { - $this->pingid = 0; - } else { - $this->pingid++; - } - - common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); - - $this->conn->send(""); - } - - /** - * Callback for Jabber reconnect event - * @param $pl - */ - function handle_reconnect(&$pl) - { - common_log(LOG_NOTICE, 'XMPP reconnected'); - - $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', 100); - } - - - function get_user($from) - { - $user = User::staticGet('jabber', jabber_normalize_jid($from)); - return $user; - } - - /** - * XMPP callback for handling message input... - * @param array $pl XMPP payload - */ - function handle_message(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - - if ($pl['type'] != 'chat') { - $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from: " . $pl['xml']->toString()); - return; - } - - if (mb_strlen($pl['body']) == 0) { - $this->log(LOG_WARNING, "Ignoring message with empty body from $from: " . $pl['xml']->toString()); - return; - } - - // Forwarded from another daemon for us to handle; this shouldn't - // happen any more but we might get some legacy items. - if ($this->is_self($from)) { - $this->log(LOG_INFO, "Got forwarded notice from self ($from)."); - $from = $this->get_ofrom($pl); - $this->log(LOG_INFO, "Originally sent by $from."); - if (is_null($from) || $this->is_self($from)) { - $this->log(LOG_INFO, "Ignoring notice originally sent by $from."); - return; - } - } - - $user = $this->get_user($from); - - // For common_current_user to work - global $_cur; - $_cur = $user; - - if (!$user) { - $this->from_site($from, 'Unknown user; go to ' . - common_local_url('imsettings') . - ' to add your address to your account'); - $this->log(LOG_WARNING, 'Message from unknown user ' . $from); - return; - } - if ($this->handle_command($user, $pl['body'])) { - $this->log(LOG_INFO, "Command message by $from handled."); - return; - } else if ($this->is_autoreply($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from); - return; - } else if ($this->is_otr($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring OTR from ' . $from); - return; - } else { - - $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname); - - $this->add_notice($user, $pl); - } - - $user->free(); - unset($user); - unset($_cur); - - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - - function is_self($from) - { - return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from)); - } - - function get_ofrom($pl) - { - $xml = $pl['xml']; - $addresses = $xml->sub('addresses'); - if (!$addresses) { - $this->log(LOG_WARNING, 'Forwarded message without addresses'); - return null; - } - $address = $addresses->sub('address'); - if (!$address) { - $this->log(LOG_WARNING, 'Forwarded message without address'); - return null; - } - if (!array_key_exists('type', $address->attrs)) { - $this->log(LOG_WARNING, 'No type for forwarded message'); - return null; - } - $type = $address->attrs['type']; - if ($type != 'ofrom') { - $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom'); - return null; - } - if (!array_key_exists('jid', $address->attrs)) { - $this->log(LOG_WARNING, 'No jid for forwarded message'); - return null; - } - $jid = $address->attrs['jid']; - if (!$jid) { - $this->log(LOG_WARNING, 'Could not get jid from address'); - return null; - } - $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid); - return $jid; - } - - function is_autoreply($txt) - { - if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { - return true; - } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { - return true; - } else { - return false; - } - } - - function is_otr($txt) - { - if (preg_match('/^\?OTR/', $txt)) { - return true; - } else { - return false; - } - } - - function from_site($address, $msg) - { - $text = '['.common_config('site', 'name') . '] ' . $msg; - jabber_send_message($address, $text); - } - - function handle_command($user, $body) - { - $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($user, $body); - if ($cmd) { - $chan = new XMPPChannel($this->conn); - $cmd->execute($chan); - return true; - } else { - return false; - } - } - - function add_notice(&$user, &$pl) - { - $body = trim($pl['body']); - $content_shortened = common_shorten_links($body); - if (Notice::contentTooLong($content_shortened)) { - $from = jabber_normalize_jid($pl['from']); - $this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), - Notice::maxContent(), - mb_strlen($content_shortened))); - return; - } - - try { - $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); - } catch (Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - $this->from_site($user->jabber, $e->getMessage()); - return; - } - - common_broadcast_notice($notice); - $this->log(LOG_INFO, - 'Added notice ' . $notice->id . ' from user ' . $user->nickname); - $notice->free(); - unset($notice); - } - - function handle_presence(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - switch ($pl['type']) { - case 'subscribe': - # We let anyone subscribe - $this->subscribed($from); - $this->log(LOG_INFO, - 'Accepted subscription from ' . $from); - break; - case 'subscribed': - case 'unsubscribed': - case 'unsubscribe': - $this->log(LOG_INFO, - 'Ignoring "' . $pl['type'] . '" from ' . $from); - break; - default: - if (!$pl['type']) { - $user = User::staticGet('jabber', $from); - if (!$user) { - $this->log(LOG_WARNING, 'Presence from unknown user ' . $from); - return; - } - if ($user->updatefrompresence) { - $this->log(LOG_INFO, 'Updating ' . $user->nickname . - ' status from presence.'); - $this->add_notice($user, $pl); - } - $user->free(); - unset($user); - } - break; - } - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - function log($level, $msg) - { - $text = 'XMPPDaemon('.$this->resource.'): '.$msg; - common_log($level, $text); - } - - function subscribed($to) - { - jabber_special_presence('subscribed', $to); - } - - /** - * Make sure we're on the right site configuration - */ - protected function switchSite() - { - if ($this->site != StatusNet::currentSite()) { - common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); - $this->stats('switch'); - StatusNet::switchSite($this->site); - } - } -} diff --git a/lib/xmppoutqueuehandler.php b/lib/xmppoutqueuehandler.php deleted file mode 100644 index 2afa260f18..0000000000 --- a/lib/xmppoutqueuehandler.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ - -/** - * Queue handler for pre-processed outgoing XMPP messages. - * Formatted XML stanzas will have been pushed into the queue - * via the Queued_XMPP connection proxy, probably from some - * other queue processor. - * - * Here, the XML stanzas are simply pulled out of the queue and - * pushed out over the wire; an XmppManager is needed to set up - * and maintain the actual server connection. - * - * This queue will be run via XmppDaemon rather than QueueDaemon. - * - * @author Brion Vibber - */ -class XmppOutQueueHandler extends QueueHandler -{ - function transport() { - return 'xmppout'; - } - - /** - * Take a previously-queued XMPP stanza and send it out ot the server. - * @param string $msg - * @return boolean true on success - */ - function handle($msg) - { - assert(is_string($msg)); - - $xmpp = XmppManager::get(); - $ok = $xmpp->send($msg); - - return $ok; - } -} - diff --git a/lib/xrd.php b/lib/xrd.php new file mode 100644 index 0000000000..c8cffed9cd --- /dev/null +++ b/lib/xrd.php @@ -0,0 +1,172 @@ +. + * + * @package StatusNet + * @author James Walker + * @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/AccountManager/AccountManagementControlDocumentAction.php b/plugins/AccountManager/AccountManagementControlDocumentAction.php new file mode 100644 index 0000000000..3fcea5af42 --- /dev/null +++ b/plugins/AccountManager/AccountManagementControlDocumentAction.php @@ -0,0 +1,98 @@ +. + * + * @category AccountManager + * @package StatusNet + * @author Craig Andrews + * @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 + * @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 0000000000..48b6034ff6 --- /dev/null +++ b/plugins/AccountManager/AccountManagementSessionStatusAction.php @@ -0,0 +1,73 @@ +. + * + * @category AccountManager + * @package StatusNet + * @author Craig Andrews + * @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 + * @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 0000000000..52dd64a24b --- /dev/null +++ b/plugins/AccountManager/AccountManagerPlugin.php @@ -0,0 +1,115 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 0000000000..a0715927b3 --- /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 0000000000..3a1799a2d8 --- /dev/null +++ b/plugins/Aim/AimPlugin.php @@ -0,0 +1,169 @@ +. + * + * @category IM + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} +// We bundle the phptoclib library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib'); + +/** + * Plugin for AIM + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class AimPlugin extends ImPlugin +{ + public $user = null; + public $password = null; + public $publicFeed = array(); + + public $transport = 'aim'; + + function getDisplayName() + { + return _m('AIM'); + } + + function normalize($screenname) + { + $screenname = str_replace(" ","", $screenname); + return strtolower($screenname); + } + + function 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 0000000000..139b68f82b --- /dev/null +++ b/plugins/Aim/Fake_Aim.php @@ -0,0 +1,43 @@ +. + * + * @category Network + * @package StatusNet + * @author Craig Andrews + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class Fake_Aim extends Aim +{ + public $would_be_sent = null; + + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $this->would_be_sent = array($sflap_type, $sflap_data, $no_null, $formatted); + } +} + diff --git a/plugins/Aim/README b/plugins/Aim/README new file mode 100644 index 0000000000..7d486a0366 --- /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 0000000000..8ff7ab7e70 --- /dev/null +++ b/plugins/Aim/aimmanager.php @@ -0,0 +1,100 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * AIM background connection manager for AIM-using queue handlers, + * allowing them to send outgoing messages on the right connection. + * + * Input is handled during socket select loop, keepalive pings during idle. + * Any incoming messages will be handled. + * + * In a multi-site queuedaemon.php run, one connection will be instantiated + * for each site being handled by the current process that has XMPP enabled. + */ + +class AimManager extends ImManager +{ + + public $conn = null; + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + public function getSockets() + { + $this->connect(); + if($this->conn){ + return array($this->conn->myConnection); + }else{ + return array(); + } + } + + /** + * Process AIM events that have come in over the wire. + * @param resource $socket + */ + public function handleInput($socket) + { + common_log(LOG_DEBUG, "Servicing the AIM queue."); + $this->stats('aim_process'); + $this->conn->receive(); + } + + function connect() + { + if (!$this->conn) { + $this->conn=new Aim($this->plugin->user,$this->plugin->password,4); + $this->conn->registerHandler("IMIn",array($this,"handle_aim_message")); + $this->conn->myServer="toc.oscar.aol.com"; + $this->conn->signon(); + $this->conn->setProfile(_m('Send me a message to post a notice'),false); + } + return $this->conn; + } + + function handle_aim_message($data) + { + $this->plugin->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 0000000000..0eec13af8a --- /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 0000000000..0657910d9e --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/aimclassw.php @@ -0,0 +1,2370 @@ + + * @author Rajiv Makhijani + * @package phptoclib + * @version 1.0RC1 + * @copyright 2005 + * @access public + * + */ +class Aim +{ + /** + * AIM ScreenName + * + * @var String + * @access private + */ + var $myScreenName; + + /** + * AIM Password (Plain Text) + * + * @var String + * @access private + */ + var $myPassword; + + + /** + * AIM TOC Server + * + * @var String + * @access public + */ + var $myServer="toc.oscar.aol.com"; + + /** + * AIM Formatted ScreenName + * + * @var String + * @access private + */ + var $myFormatSN; + + /** + * AIM TOC Server Port + * + * @var String + * @access public + */ + var $myPort="5190"; + + /** + * Profile Data + * Use setProfile() to update + * + * @var String + * @access private + */ + var $myProfile="Powered by phpTOCLib. Please visit http://sourceforge.net/projects/phptoclib for more information"; //The profile of the bot + + /** + * Socket Connection Resource ID + * + * @var Resource + * @access private + */ + var $myConnection; //Connection resource ID + + /** + * Roasted AIM Password + * + * @var String + * @access private + */ + var $myRoastedPass; + + /** + * Last Message Recieved From Server + * + * @var String + * @access private + */ + var $myLastReceived; + + /** + * Current Seq Number Used to Communicate with Server + * + * @var Integer + * @access private + */ + var $mySeqNum; + + /** + * Current Warning Level + * Getter: getWarning() + * Setter: setWarning() + * + * @var Integer + * @access private + */ + var $myWarnLevel; //Warning Level of the bot + + /** + * Auth Code + * + * @var Integer + * @access private + */ + var $myAuthCode; + + /** + * Buddies + * Getter: getBuddies() + * + * @var Array + * @access private + */ + var $myBuddyList; + + /** + * Blocked Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myBlockedList; + + /** + * Permited Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myPermitList; + + /** + * Permit/Deny Mode + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @var Integer + * @access private + */ + var $myPdMode; + + //Below variables added 4-29 by Jeremy: Implementing chat + + /** + * Contains Chat Room Info + * $myChatRooms['roomid'] = people in room + * + * @var Array + * @access private + */ + var $myChatRooms; + + //End of chat implementation + + + /** + * Event Handler Functions + * + * @var Array + * @access private + */ + var $myEventHandlers = array(); + + /** + * Array of direct connection objects(including file transfers) + * + * @var Array + * @access private + */ + var $myDirectConnections = array(); + + /** + * Array of the actual connections + * + * @var Array + * @access private + */ + var $myConnections = array(); + + /** + * The current state of logging + * + * @var Boolean + * @access private + */ + + var $myLogging = false; + + /** + * Constructor + * + * Permit/Deny Mode Options + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @param String $sn AIM Screenname + * @param String $password AIM Password + * @param Integer $pdmode Permit/Deny Mode + * @access public + */ + function Aim($sn, $password, $pdmode) + { + //Constructor assignment + $this->myScreenName = $this->normalize($sn); + $this->myPassword = $password; + $this->myRoastedPass = $this->roastPass($password); + $this->mySeqNum = 1; + $this->myConnection = 0; + $this->myWarnLevel = 0; + $this->myAuthCode = $this->makeCode(); + $this->myPdMode = $pdmode; + $this->myFormatSN = $this->myScreenName; + + $this->log("PHPTOCLIB v" . PHPTOCLIB_VERSION . " Object Created"); + + } + + /** + * Enables debug logging (Logging is disabled by default) + * + * + * @access public + * @return void + */ + + function setLogging($enable) + { + $this->myLogging=$enable; + } + + function log($data) + { + if($this->myLogging){ + error_log($data); + } + } + + /** + * Logs a packet + * + * + * @access private + * @param Array $packary Packet + * @param String $in Prepend + * @return void + */ + function logPacket($packary,$in) + { + if(!$this->myLogging || sizeof($packary)<=0 || (@strlen($packary['decoded'])<=0 && @isset($packary['decoded']))) + return; + $towrite=$in . ": "; + foreach($packary as $k=>$d) + { + $towrite.=$k . ":" . $d . "\r\n"; + } + $towrite.="\r\n\r\n"; + $this->log($towrite); + } + /** + * Roasts/Hashes Password + * + * @param String $password Password + * @access private + * @return String Roasted Password + */ + function roastPass($password) + { + $roaststring = 'Tic/Toc'; + $roasted_password = '0x'; + for ($i = 0; $i < strlen($password); $i++) + $roasted_password .= bin2hex($password[$i] ^ $roaststring[($i % 7)]); + return $roasted_password; + } + + /** + * Access Method for myScreenName + * + * @access public + * @param $formated Returns formatted Screenname if true as returned by server + * @return String Screenname + */ + function getMyScreenName($formated = false) + { + if ($formated) + { + return $this->myFormatSN; + } + else + { + return $this->normalize($this->myScreenName); + } + } + + /** + * Generated Authorization Code + * + * @access private + * @return Integer Auth Code + */ + function makeCode() + { + $sn = ord($this->myScreenName[0]) - 96; + $pw = ord($this->myPassword[0]) - 96; + $a = $sn * 7696 + 738816; + $b = $sn * 746512; + $c = $pw * $a; + + return $c - $a + $b + 71665152; + } + + + /** + * Reads from Socket + * + * @access private + * @return String Data + */ + function sflapRead() + { + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... Reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + + $header = fread($this->myConnection,SFLAP_HEADER_LEN); + + if (strlen($header) == 0) + { + $this->myLastReceived = ""; + return ""; + } + $header_data = unpack("aast/Ctype/nseq/ndlen", $header); + $this->log(" . ", false); + $packet = fread($this->myConnection, $header_data['dlen']); + if (strlen($packet) <= 0 && $sockinfo['blocked']) + $this->derror("Could not read data"); + + if ($header_data['type'] == SFLAP_TYPE_SIGNON) + { + $packet_data=unpack("Ndecoded", $packet); + } + + if ($header_data['type'] == SFLAP_TYPE_KEEPALIVE) + { + $this->myLastReceived = ''; + return 0; + } + else if (strlen($packet)>0) + { + $packet_data = unpack("a*decoded", $packet); + } + $this->log("socketcheck check now"); + if ($this->socketcheck($this->myConnection)) + { + $this->derror("Connection ended unexpectedly"); + } + + $data = array_merge($header_data, $packet_data); + $this->myLastReceived = $data; + $this->logPacket($data,"in"); + return $data; + } + + /** + * Sends Data on Socket + * + * @param String $sflap_type Type + * @param String $sflap_data Data + * @param boolean $no_null No Null + * @param boolean $formatted Format + * @access private + * @return String Roasted Password + */ + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $packet = ""; + if (strlen($sflap_data) >= MAX_PACKLENGTH) + $sflap_data = substr($sflap_data,0,MAX_PACKLENGTH); + + if ($formatted) + { + $len = strlen($sflap_len); + $sflap_header = pack("aCnn",'*', $sflap_type, $this->mySeqNum, $len); + $packet = $sflap_header . $sflap_data; + } else { + if (!$no_null) + { + $sflap_data = str_replace("\0","", trim($sflap_data)); + $sflap_data .= "\0"; + } + $data = pack("a*", $sflap_data); + $len = strlen($sflap_data); + $header = pack("aCnn","*", $sflap_type, $this->mySeqNum, $len); + $packet = $header . $data; + } + + //Make sure we are still connected + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + $sent = fputs($this->myConnection, $packet) or $this->derror("Error sending packet to AIM"); + $this->mySeqNum++; + sleep(ceil($this->myWarnLevel/10)); + $this->logPacket(array($sflap_type,$sflap_data),"out"); + } + + /** + * Escape the thing that TOC doesn't like,that would be + * ",', $,{,},[,] + * + * @param String $data Data to Escape + * @see decodeData + * @access private + * @return String $data Escaped Data + */ + function encodeData($data) + { + $data = str_replace('"','\"', $data); + $data = str_replace('$','\$', $data); + $data = str_replace("'","\'", $data); + $data = str_replace('{','\{', $data); + $data = str_replace('}','\}', $data); + $data = str_replace('[','\[', $data); + $data = str_replace(']','\]', $data); + return $data; + } + + /** + * Unescape data TOC has escaped + * ",', $,{,},[,] + * + * @param String $data Data to Unescape + * @see encodeData + * @access private + * @return String $data Unescape Data + */ + function decodeData($data) + { + $data = str_replace('\"','"', $data); + $data = str_replace('\$','$', $data); + $data = str_replace("\'","'", $data); + $data = str_replace('\{','{', $data); + $data = str_replace('\}','}', $data); + $data = str_replace('\[','[', $data); + $data = str_replace('\]',']', $data); + $data = str_replace('"','"', $data); + $data = str_replace('&','&', $data); + return $data; + } + + /** + * Normalize ScreenName + * no spaces and all lowercase + * + * @param String $nick ScreenName + * @access public + * @return String $nick Normalized ScreenName + */ + function normalize($nick) + { + $nick = str_replace(" ","", $nick); + $nick = strtolower($nick); + return $nick; + } + + /** + * Sets internal info with update buddy + * Currently only sets warning level + * + * @access public + * @return void + */ + function setMyInfo() + { + //Sets internal values bvase on the update buddy command + $this->log("Setting my warning level ..."); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_get_status " . $this->normalize($this->myScreenName),0,0); + //The rest of this will now be handled by the other functions. It is assumed + //that we may have other data queued in the socket, do we should just add this + //message to the queue instead of trying to set it in here + } + + /** + * Connects to AIM and Signs On Using Info Provided in Constructor + * + * @access public + * @return void + */ + function signon() + { + $this->log("Ready to sign on to the server"); + $this->myConnection = fsockopen($this->myServer, $this->myPort, $errno, $errstr,10) or die("$errorno:$errstr"); + $this->log("Connected to server"); + $this->mySeqNum = (time() % 65536); //Select an arbitrary starting point for + //sequence numbers + if (!$this->myConnection) + $this->derror("Error connecting to toc.oscar.aol.com"); + $this->log("Connected to AOL"); + //Send the flapon packet + fputs($this->myConnection,"FLAPON\r\n\n\0"); //send the initial handshake + $this->log("Sent flapon"); + $this->sflapRead(); //Make sure the server responds with what we expect + if (!$this->myLastReceived) + $this->derror("Error sending the initialization string"); + + //send the FLAP SIGNON packet back with what it needs + //There are 2 parts to the signon packet. They are sent in succession, there + //is no indication if either packet was correctly sent + $signon_packet = pack("Nnna".strlen($this->myScreenName),1,1,strlen($this->myScreenName), $this->myScreenName); + $this->sflapSend(SFLAP_TYPE_SIGNON, $signon_packet,1,0); + $this->log("sent signon packet part one"); + + $signon_packet_part2 = 'toc2_signon login.oscar.aol.com 29999 ' . $this->myScreenName . ' ' . $this->myRoastedPass . ' english-US "TIC:TOC2:REVISION" 160 ' . $this->myAuthCode; + $this->log($signon_packet_part2 . ""); + $this->sflapSend(SFLAP_TYPE_DATA, $signon_packet_part2,0,0); + $this->log("Sent signon packet part 2... Awaiting response..."); + + $this->sflapRead(); + $this->log("Received Sign on packet, beginning initilization..."); + $message = $this->getLastReceived(); + $this->log($message . "\n"); + if (strstr($message,"ERROR:")) + { + $this->onError($message); + die("Fatal signon error"); + } + stream_set_timeout($this->myConnection,2); + //The information sent before the config2 command is utterly useless to us + //So we will just skim through them until we reach it + + //Add the first entry to the connection array + $this->myConnections[] = $this->myConnection; + + + //UPDATED 4/12/03: Now this will use the receive function and send the + //received messaged to the assigned handlers. This is where the signon + //method has no more use + + $this->log("Done with signon proccess"); + //socket_set_blocking($this->myConnection,false); + } + + /** + * Sends Instant Message + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * @param boolean $auto Sent as Auto Response / Away Message Style + * @access public + * @return void + */ + function sendIM($to, $message, $auto = false) + { + if ($auto) $auto = "auto"; + else $auto = ""; + $to = $this->normalize($to); + $message = $this->encodeData($message); + $command = 'toc2_send_im "' . $to . '" "' . $message . '" ' . $auto; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $cleanedmessage = str_replace("
", " ", $this->decodeData($message)); + $cleanedmessage = strip_tags($cleanedmessage); + $this->log("TO - " . $to . " : " . $cleanedmessage); + } + + /** + * Set Away Message + * + * @param String $message Away message (some HTML supported). + * Use null to remove the away message + * @access public + * @return void + */ + function setAway($message) + { + $message = $this->encodeData($message); + $command = 'toc_set_away "' . $message . '"'; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $this->log("SET AWAY MESSAGE - " . $this->decodeData($message)); + } + + /** + * Fills Buddy List + * Not implemented fully yet + * + * @access public + * @return void + */ + function setBuddyList() + { + //This better be the right message + $message = $this->myLastReceived['decoded']; + if (strpos($message,"CONFIG2:") === false) + { + $this->log("setBuddyList cannot be called at this time because I got $message"); + return false; + } + $people = explode("\n",trim($message,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + for($i = 1; $imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + // + } + } + } + + /** + * Adds buddy to Permit list + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function addPermit($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($buddy),0,0); + $this->myPermitList[] = $this->normalize($buddy); + return 1; + } + + /** + * Blocks buddy + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function blockBuddy($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_deny " . $this->normalize($buddy),0,0); + $this->myBlockedList[] = $this->normalize($buddy); + return 1; + } + + /** + * Returns last message received from server + * + * @access private + * @return String Last Message from Server + */ + function getLastReceived() + { + if (@$instuff = $this->myLastReceived['decoded']){ + return $this->myLastReceived['decoded']; + }else{ + return; + } + } + + /** + * Returns Buddy List + * + * @access public + * @return array Buddy List + */ + function getBuddies() + { + return $this->myBuddyList; + } + + /** + * Returns Permit List + * + * @access public + * @return array Permit List + */ + function getPermit() + { + return $this->myPermitList; + } + + /** + * Returns Blocked Buddies + * + * @access public + * @return array Blocked List + */ + function getBlocked() + { + return $this->myBlockedList; + } + + + + + /** + * Reads and returns data from server + * + * This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data'] + * portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this + * function instead. This function has a return value. Calling this prevents the need to call + * $Aim->getLastReceived() + * + * @access public + * @return String Data recieved from server + */ + function read_from_aim() + { + $this->sflapRead(); + $returnme = $this->getLastReceived(); + return $returnme; + } + + /** + * Sets current internal warning level + * + * This allows you to update the bots warning level when warned. + * + * @param int Warning Level % + * @access private + * @return void + */ + function setWarningLevel($warnlevel) + { + $this->myWarnLevel = $warnlevel; + } + + /** + * Warns / "Evils" a User + * + * To successfully warn another user they must have sent you a message. + * There is a limit on how much and how often you can warn another user. + * Normally when you warn another user they are aware who warned them, + * however there is the option to warn anonymously. When warning anon. + * note that the warning is less severe. + * + * @param String $to Screenname to warn + * @param boolean $anon Warn's anonymously if true. (default = false) + * @access public + * @return void + */ + function warnUser($to, $anon = false) + { + if (!$anon) + $anon = '"norm"'; + + else + $anon = '"anon"'; + $this->sflapSend(SFLAP_TYPE_DATA,"toc_evil " . $this->normalize($to) . " $anon",0,0); + } + + /** + * Returns warning level of bot + * + * @access public + * @return void + */ + function getWarningLevel() + { + return $this->myWarningLevel; + } + + /** + * Sets bot's profile/info + * + * Limited to 1024 bytes. + * + * @param String $profiledata Profile Data (Can contain limited html: br,hr,font,b,i,u etc) + * @param boolean $poweredby If true, appends link to phpTOCLib project to profile + * @access public + * @return void + */ + function setProfile($profiledata, $poweredby = false) + { + if ($poweredby == false){ + $this->myProfile = $profiledata; + }else{ + $this->myProfile = $profiledata . "

Powered by phpTOCLib
http://sourceforge.net/projects/phptoclib
"; + } + + $this->sflapSend(SFLAP_TYPE_DATA,"toc_set_info \"" . $this->encodeData($this->myProfile) . "\"",0,0); + $this->setMyInfo(); + $this->log("Profile has been updated..."); + } + + //6/29/04 by Jeremy: + //Added mthod to accept a rvous,decline it, and + //read from the rvous socket + + //Decline + + /** + * Declines a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * + * @access public + * @return void + */ + function declineRvous($nick, $cookie, $uuid) + { + $nick = $this->normalize($nick); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_cancel $nick $cookie $uuid",0,0); + } + + /** + * Accepts a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * @param String $vip IP of User DC with + * @param int $port Port number to connect to + * + * @access public + * @return void + */ + function acceptRvous($nick, $cookie, $uuid, $vip, $port) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_accept $nick $cookie $uuid",0,0); + + //Now open the connection to that user + if ($uuid == IMAGE_UID) + { + $dcon = new Dconnect($vip, $port); + } + else if ($uuid == FILE_SEND_UID) + { + $dcon = new FileSendConnect($vip, $port); + } + if (!$dcon->connected) + { + $this->log("The connection failed"); + return false; + } + + //Place this dcon object inside the array + $this->myDirectConnections[] = $dcon; + //Place the socket in an array to + $this->myConnections[] = $dcon->sock; + + + //Get rid of the first packet because its worthless + //and confusing + $dcon->readDIM(); + //Assign the cookie + $dcon->cookie = $dcon->lastReceived['cookie']; + $dcon->connectedTo = $this->normalize($nick); + return $dcon; + } + + /** + * Sends a Message over a Direct Connection + * + * Only works if a direct connection is already established with user + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * + * @access public + * @return void + */ + function sendDim($to, $message) + { + //Find the connection + for($i = 0;$imyDirectConnections);$i++) + { + if ($this->normalize($to) == $this->myDirectConnections[$i]->connectedTo && $this->myDirectConnections[$i]->type == CONN_TYPE_DC) + { + $dcon = $this->myDirectConnections[$i]; + break; + } + } + if (!$dcon) + { + $this->log("Could not find a direct connection to $to"); + return false; + } + $dcon->sendMessage($message, $this->normalize($this->myScreenName)); + return true; + } + + /** + * Closes an established Direct Connection + * + * @param DConnect $dcon Direct Connection Object to Close + * + * @access public + * @return void + */ + function closeDcon($dcon) + { + + $nary = array(); + for($i = 0;$imyConnections);$i++) + { + if ($dcon->sock == $this->myConnections[$i]) + unset($this->myConnections[$i]); + } + + $this->myConnections = array_values($this->myConnections); + unset($nary); + $nary2 = array(); + + for($i = 0;$imyDirectConnections);$i++) + { + if ($dcon == $this->myDirectConnections[$i]) + unset($this->myDirectConnections[$i]); + } + $this->myDirectConnections = array_values($this->myDirectConnections); + $dcon->close(); + unset($dcon); + } + + //Added 4/29/04 by Jeremy: + //Various chat related methods + + /** + * Accepts a Chat Room Invitation (Joins room) + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function joinChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_accept " . $chatid,0,0); + } + + /** + * Leaves a chat room + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function leaveChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_leave " . $chatid,0,0); + } + + /** + * Sends a message in a chat room + * + * @param String $chatid ID of Chat Room + * @param String $message Message to send + * + * @access public + * @return void + */ + function chatSay($chatid, $message) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_send " . $chatid . " \"" . $this->encodeData($message) . "\"",0,0); + } + + /** + * Invites a user to a chat room + * + * @param String $chatid ID of Chat Room + * @param String $who Screenname of user + * @param String $message Note to include with invitiation + * + * @access public + * @return void + */ + function chatInvite($chatid, $who, $message) + { + $who = $this->normalize($who); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_invite " . $chatid . " \"" . $this->encodeData($message) . "\" " . $who,0,0); + } + + /** + * Joins/Creates a new chat room + * + * @param String $name Name of the new chat room + * @param String $exchange Exchange of new chat room + * + * @access public + * @return void + */ + function joinNewChat($name, $exchange) + { + //Creates a new chat + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_join " . $exchange . " \"" . $name . "\"",0,0); + } + + /** + * Disconnect error handler, attempts to reconnect in 60 seconds + * + * @param String $message Error message (desc of where error encountered etc) + * + * @access private + * @return void + */ + function derror($message) + { + $this->log($message); + $this->log("Error"); + fclose($this->myConnection); + if ((time() - $GLOBALS['errortime']) < 600){ + $this->log("Reconnecting in 60 Seconds"); + sleep(60); + } + $this->signon(); + $GLOBALS['errortime'] = time(); + } + + /** + * Returns connection type of socket (main or rvous etc) + * + * Helper method for recieve() + * + * @param Resource $sock Socket to determine type for + * + * @access private + * @return void + * @see receive + */ + function connectionType($sock) + { + //Is it the main connection? + if ($sock == $this->myConnection) + return CONN_TYPE_NORMAL; + else + { + for($i = 0;$imyDirectConnections);$i++) + { + if ($sock == $this->myDirectConnections[$i]->sock) + return $this->myDirectConnections[$i]->type; + } + } + return false; + } + + /** + * Checks for new data and calls appropriate methods + * + * This method is usually called in an infinite loop to keep checking for new data + * + * @access public + * @return void + * @see connectionType + */ + function receive() + { + //This function will be used to get the incoming data + //and it will be used to call the event handlers + + //First, get an array of sockets that have data that is ready to be read + $ready = array(); + $ready = $this->myConnections; + $numrdy = stream_select($ready, $w = NULL, $x = NULL,NULL); + + //Now that we've waited for something, go through the $ready + //array and read appropriately + + for($i = 0;$iconnectionType($ready[$i]); + if ($type == CONN_TYPE_NORMAL) + { + //Next step:Get the data sitting in the socket + $message = $this->read_from_aim(); + if (strlen($message) <= 0) + { + return; + } + + //Third step: Get the command from the server + @list($cmd, $rest) = explode(":", $message); + + //Fourth step, take the command, test the type, and pass it off + //to the correct internal handler. The internal handler will + //do what needs to be done on the class internals to allow + //it to work, then proceed to pass it off to the user created handle + //if there is one + $this->log($cmd); + switch($cmd) + { + case 'SIGN_ON': + $this->onSignOn($message); + break; + case 'CONFIG2': + $this->onConfig($message); + break; + case 'ERROR': + $this->onError($message); + break; + case 'NICK': + $this->onNick($message); + break; + case 'IM_IN2': + $this->onImIn($message); + break; + case 'UPDATE_BUDDY2': + $this->onUpdateBuddy($message); + break; + case 'EVILED': + $this->onWarn($message); + break; + case 'CHAT_JOIN': + $this->onChatJoin($message); + break; + case 'CHAT_IN': + $this->onChatIn($message); + break; + case 'CHAT_UPDATE_BUDDY': + $this->onChatUpdate($message); + break; + case 'CHAT_INVITE': + $this->onChatInvite($message); + break; + case 'CHAT_LEFT': + $this->onChatLeft($message); + break; + case 'GOTO_URL': + $this->onGotoURL($message); + break; + case 'DIR_STATUS': + $this->onDirStatus($message); + break; + case 'ADMIN_NICK_STATUS': + $this->onAdminNick($message); + break; + case 'ADMIN_PASSWD_STATUS': + $this->onAdminPasswd($message); + break; + case 'PAUSE': + $this->onPause($message); + break; + case 'RVOUS_PROPOSE': + $this->onRvous($message); + break; + default: + $this->log("Fell through: $message"); + $this->CatchAll($message); + break; + } + } + else + { + for($j = 0;$jmyDirectConnections);$j++) + { + if ($this->myDirectConnections[$j]->sock == $ready[$i]) + { + $dcon = $this->myDirectConnections[$j]; + break; + } + } + //Now read from the dcon + if ($dcon->type == CONN_TYPE_DC) + { + if ($dcon->readDIM() == false) + { + $this->closeDcon($dcon); + continue; + } + + $message['message'] = $dcon->lastMessage; + if ($message['message'] == "too big") + { + $this->sendDim("Connection dropped because you sent a message larger that " . MAX_DCON_SIZE . " bytes.", $dcon->connectedTo); + $this->closeDcon($dcon); + continue; + } + $message['from'] = $dcon->connectedTo; + $this->onDimIn($message); + } + } + } + $this->conn->myLastReceived=""; + //Now get out of this function because the handlers should take care + //of everything + } + + //The next block of code is all the event handlers needed by the class + //Some are left blank and only call the users handler because the class + //either does not support the command, or cannot do anything with it + // --------------------------------------------------------------------- + + /** + * Direct IM In Event Handler + * + * Called when Direct IM is received. + * Call's user handler (if available) for DimIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDimIn($data) + { + $this->callHandler("DimIn", $data); + } + + /** + * Sign On Event Handler + * + * Called when Sign On event occurs. + * Call's user handler (if available) for SIGN_ON. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onSignOn($data) + { + $this->callHandler("SignOn", $data); + } + + /** + * Config Event Handler + * + * Called when Config data received. + * Call's user handler (if available) for Config. + * + * Loads buddy list and other info + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onConfig($data) + { + $this->log("onConfig Message: " . $data); + + if (strpos($data,"CONFIG2:") === false) + { + $this->log("get_buddy_list cannot be called at this time because I got $data"); + //return false; + } + $people = explode("\n",trim($data,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + + //AIM decided to add this wonderful new feature, the recent buddy thing, this kind of + //messes this funtion up, so we need to adapt it... unfortuneately, its not really + //clear how this works, so we are just going to add their name to the permit list. + + //Recent buddies I believe are in the format + //number:name:number.... I think the first number counts down from 25 how long its + //been... but I don't know the second number,,,, + + //TODO: Figure out the new recent buddies system + + //Note: adding that at the bottom is a quick hack and may have adverse consequences... + for($i = 1;$imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + //This is assumed to be recent buddies... + $this->myPermitList[]=$name; + } + } + + //We only get the config message once, so now we should send our pd mode + + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_set_pdmode " . $this->myPdMode,0,0); + //Adds yourself to the permit list + //This is to fix an odd behavior if you have nobody on your list + //the server won't send the config command... so this takes care of it + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($this->myScreenName),0,0); + + //Now we allow the user to send a list, update anything they want, etc + $this->callHandler("Config", $data); + //Now that we have taken care of what the user wants, send the init_done message + $this->sflapSend(SFLAP_TYPE_DATA,"toc_init_done",0,0); + //'VOICE_UID' + //'FILE_GET_UID' + //'IMAGE_UID' + //'BUDDY_ICON_UID' + //'STOCKS_UID' + //'GAMES_UID' + $this->sflapSend(SFLAP_TYPE_DATA, "toc_set_caps " . IMAGE_UID . " " . FILE_SEND_UID ." " . FILE_GET_UID . " " . BUDDY_ICON_UID . "",0,0); + } + + + /** + * Error Event Handler + * + * Called when an Error occurs. + * Call's user handler (if available) for Error. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onError($data) + { + static $errarg = ''; + static $ERRORS = array( + 0=>'Success', + 901 =>'$errarg not currently available', + 902 =>'Warning of $errarg not currently available', + 903 =>'A message has been dropped, you are exceeding + the server speed limit', + 911 =>'Error validating input', + 912 =>'Invalid account', + 913 =>'Error encountered while processing request', + 914 =>'Service unavailable', + 950 =>'Chat in $errarg is unavailable.', + 960 =>'You are sending message too fast to $errarg', + 961 =>'You missed an im from $errarg because it was too big.', + 962 =>'You missed an im from $errarg because it was sent too fast.', + 970 =>'Failure', + 971 =>'Too many matches', + 972 =>'Need more qualifiers', + 973 =>'Dir service temporarily unavailable', + 974 =>'Email lookup restricted', + 975 =>'Keyword Ignored', + 976 =>'No Keywords', + 977 =>'Language not supported', + 978 =>'Country not supported', + 979 =>'Failure unknown $errarg', + 980 =>'Incorrect nickname or password.', + 981 =>'The service is temporarily unavailable.', + 982 =>'Your warning level is currently too high to sign on.', + 983 =>'You have been connecting and + disconnecting too frequently. Wait 10 minutes and try again. + If you continue to try, you will need to wait even longer.', + 989 =>'An unknown signon error has occurred $errarg' + ); + $data_array = explode(":", $data); + for($i=0; $ilog($errorstring . "\n"); + + $this->callHandler("Error", $data); + } + + /** + * Nick Event Handler + * + * Called when formatted own ScreenName is receieved + * Call's user handler (if available) for Nick. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onNick($data) + { + //This is our nick, so set a field called "myFormatSN" which will represent + //the actual name given by the server to us, NOT the normalized screen name + @list($cmd, $nick) = explode(":", $data); + $this->myFormatSN = $nick; + + $this->callHandler("Nick", $data); + } + + /** + * IM In Event Handler + * + * Called when an Instant Message is received. + * Call's user handler (if available) for IMIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onImIn($data) + { + //Perhaps we should add an internal log for debugging purposes?? + //But now, this should probably be handled by the user purely + + $this->callHandler("IMIn", $data); + } + + /** + * UpdateBuddy Event Handler + * + * Called when a Buddy Update is receieved. + * Call's user handler (if available) for UpdateBuddy. + * If info is about self, updates self info (Currently ownly warning). + * + * ToDo: Keep track of idle, warning etc on Buddy List + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onUpdateBuddy($data) + { + //Again, since our class currently does not deal with other people without + //outside help, then this is also probably best left to the user. Though + //we should probably allow this to replace the setMyInfo function above + //by handling the input if and only if it is us + //Check and see that this is the command expected + if (strpos($data,"UPDATE_BUDDY2:") == -1) + { + $this->log("A different message than expected was received"); + return false; + } + + //@list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + + //@list($cmd, $sn, $online, $warning, $starttime, $idletime, $uc) = explode(":", $data); + $info = $this->getMessageInfo($data); + if ($this->normalize($info['sn']) == $this->normalize($this->myScreenName)) + { + $warning = rtrim($info['warnlevel'],"%"); + $this->myWarnLevel = $warning; + $this->log("My warning level is $this->myWarnLevel %"); + } + + $this->callHandler("UpdateBuddy", $data); + } + + /** + * Warning Event Handler + * + * Called when bot is warned. + * Call's user handler (if available) for Warn. + * Updates internal warning level + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onWarn($data) + { + /* + For reference: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + */ + //SImply update our warning level + //@list($cmd, $newwarn, $user) = explode(":", $data); + + $info = $this->getMessageInfo($data); + + $this->setWarningLevel(trim($info['warnlevel'],"%")); + $this->log("My warning level is $this->myWarnLevel %"); + + $this->callHandler("Warned", $data); + } + + /** + * Chat Join Handler + * + * Called when bot joins a chat room. + * Call's user handler (if available) for ChatJoin. + * Adds chat room to internal chat room list. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatJoin($data) + { + @list($cmd, $rmid, $rmname) = explode(":", $data); + $this->myChatRooms[$rmid] = 0; + + $this->callHandler("ChatJoin", $data); + } + + /** + * Returns number of chat rooms bot is in + * + * @access public + * @param String $data Raw message from server + * @return int + */ + function getNumChats() + { + return count($this->myChatRooms); + } + + /** + * Chat Update Handler + * + * Called when bot received chat room data (user update). + * Call's user handler (if available) for ChatUpdate. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatUpdate($data) + { + $stuff = explode(":", $data); + $people = sizeof($stuff); + $people -= 2; + + $this->callHandler("ChatUpdate", $data); + } + + /** + * Chat Message In Handler + * + * Called when chat room message is received. + * Call's user handler (if available) for ChatIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatIn($data) + { + $this->callHandler("ChatIn", $data); + } + + + /** + * Chat Invite Handler + * + * Called when bot is invited to a chat room. + * Call's user handler (if available) for ChatInvite. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatInvite($data) + { + //@list($cmd, $name, $id, $from, $data) = explode(":", $data,6); + //$data = explode(":",$data,6); + //$nm = array(); + //@list($nm['cmd'],$nm['name'],$nm['id'],$nm['from'],$nm['message']) = $data; + + + $this->callHandler("ChatInvite", $data); + } + + /** + * Chat Left Handler + * + * Called when bot leaves a chat room + * Call's user handler (if available) for ChatLeft. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatLeft($data) + { + $info = $this->getMessageInfo($data); + unset($this->myChatRooms[$info['chatid']]); + $this->callHandler("ChatLeft", $data); + } + + /** + * Goto URL Handler + * + * Called on GotoURL. + * Call's user handler (if available) for GotoURL. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onGotoURL($data) + { + //This is of no use to the internal class + + $this->callHandler("GotoURL", $data); + } + + /** + * Dir Status Handler + * + * Called on DirStatus. + * Call's user handler (if available) for DirStatus. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDirStatus($data) + { + //This is not currently suported + + $this->callHandler("DirStatus", $data); + } + + /** + * AdminNick Handler + * + * Called on AdminNick. + * Call's user handler (if available) for AdminNick. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminNick($data) + { + //NOt particularly useful to us + $this->callHandler("AdminNick", $data); + } + + /** + * AdminPasswd Handler + * + * Called on AdminPasswd. + * Call's user handler (if available) for AdminPasswd. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminPasswd($data) + { + //Also not particlualry useful to the internals + $this->callHandler("AdminPasswd", $data); + } + + /** + * Pause Handler + * + * Called on Pause. + * Call's user handler (if available) for Pause. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onPause($data) + { + //This is pretty useless to us too... + + $this->callHandler("Pause", $data); + } + + /** + * Direct Connection Handler + * + * Called on Direct Connection Request(Rvous). + * Call's user handler (if available) for Rvous. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onRvous($data) + { + $this->callHandler("Rvous", $data); + } + + /** + * CatchAll Handler + * + * Called for unrecognized commands. + * Logs unsupported messages to array. + * Call's user handler (if available) for CatchAll. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function CatchAll($data) + { + //Add to a log of unsupported messages. + + $this->unsupported[] = $data; + //$this->log($data); + //print_r($data); + + $this->callHandler("CatchAll", $data); + } + + /** + * Calls User Handler + * + * Calls registered handler for a specific event. + * + * @access private + * @param String $event Command (event) name (Rvous etc) + * @param String $data Raw message from server + * @see registerHandler + * @return void + */ + function callHandler($event, $data) + { + + if (isset($this->myEventHandlers[$event])) + { + //$function = $this->myEventHandlers[$event] . "(\$data);"; + //eval($function); + call_user_func($this->myEventHandlers[$event], $data); + } + else + { + $this->noHandler($data); + } + } + + /** + * Registers a user handler + * + * Handler List + * SignOn, Config, ERROR, NICK, IMIn, UpdateBuddy, Eviled, Warned, ChatJoin + * ChatIn, ChatUpdate, ChatInvite, ChatLeft, GotoURL, DirStatus, AdminNick + * AdminPasswd, Pause, Rvous, DimIn, CatchAll + * + * @access private + * @param String $event Event name + * @param String $handler User function to call + * @see callHandler + * @return boolean Returns true if successful + */ + function registerHandler($event, $handler) + { + if (is_callable($handler)) + { + $this->myEventHandlers[$event] = $handler; + return true; + } + else + { + return false; + } + } + + /** + * No user handler method fall back. + * + * Does nothing with message. + * + * @access public + * @param String $message Raw server message + * @return void + */ + function noHandler($message) + { + //This function intentionally left blank + //This is where the handlers will fall to for now. I plan on including a more + //efficent check to avoid the apparent stack jumps that this code will produce + //But for now, just fall into here, and be happy + return; + } + + //GLOBAL FUNCTIONS + + /** + * Finds type, and returns as part of array ['type'] + * Puts message in ['incoming'] + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $message Raw server message + * @see msg_parse + * @see getMessageInfo + * @return array + */ + static function msg_type($message) + { + $command = array(); + @list($cmd, $rest) = explode(":", $message); + switch($cmd) + { + case 'IM_IN2': + $type = AIM_TYPE_MSG; + break; + + case 'UPDATE_BUDDY2': + $type = AIM_TYPE_UPDATEBUDDY; + break; + + case 'EVILED': + $type = AIM_TYPE_WARN; + break; + + case 'SIGN_ON': + $type = AIM_TYPE_SIGNON; + break; + + case 'NICK': + $type = AIM_TYPE_NICK; + break; + + case 'ERROR': + $type = AIM_TYPE_ERROR; + break; + + case 'CHAT_JOIN': + $type = AIM_TYPE_CHATJ; + break; + + case 'CHAT_IN': + $type = AIM_TYPE_CHATI; + break; + + case 'CHAT_UPDATE_BUDDY': + $type = AIM_TYPE_CHATUPDBUD; + break; + + case 'CHAT_INVITE': + $type = AIM_TYPE_CHATINV; + break; + + case 'CHAT_LEFT': + $type = AIM_TYPE_CHATLE; + break; + + case 'GOTO_URL': + $type = AIM_TYPE_URL; + break; + + case 'ADMIN_NICK_STATUS': + $type = AIM_TYPE_NICKSTAT; + break; + + case 'ADMIN_PASSWD_STATUS': + $type = AIM_TYPE_PASSSTAT; + break; + + case 'RVOUS_PROPOSE': + $type = AIM_TYPE_RVOUSP; + break; + + default: + $type = AIM_TYPE_NOT_IMPLEMENTED; + break; + } + $command['type'] = $type; + $command['incoming'] = $message; + return $command; + } + + /** + * Parses message and splits into info array + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $command Message and type (after msg_type) + * @see msg_type + * @see getMessageInfo + * @return array + */ + static function msg_parse($command) + { + $info = array(); + switch($command['type']) + { + case AIM_TYPE_WARN: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + + break; + + case AIM_TYPE_MSG: + $it = explode(":", $command['incoming'],5); + $info['auto'] = $it[2]; + $info['from'] = $it[1]; + $info['message'] = $it[4]; + break; + + case AIM_TYPE_UPDATEBUDDY: + @list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_SIGNON: + @list($cmd, $info['version']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_NICK: + @list($cmd, $info['nickname']) = explode(":", $command['incoming']); + break; + case AIM_TYPE_ERROR: + @list($cmd, $info['errorcode'], $info['args']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATJ: + @list($cmd, $info['chatid'], $info['chatname']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATI: + @list($cmd, $info['chatid'], $info['user'], $info['whisper'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATUPDBUD: + @list($cmd, $info['chatid'], $info['inside'], $info['userlist']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_CHATINV: + @list($cmd, $info['chatname'], $info['chatid'], $info['from'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATLE: + @list($cmd, $info['chatid']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_URL: + @list($cmd, $info['windowname'], $info['url']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_RVOUSP: + @list($cmd,$info['user'],$info['uuid'],$info['cookie'],$info['seq'],$info['rip'],$info['pip'],$info['vip'],$info['port'],$info['tlvs']) = explode(":",$command['incoming'],10); + break; + + case AIM_TYPE_NICKSTAT: + case AIM_TYPE_PASSSTAT: + @list($cmd, $info['returncode'], $info['opt']) = explode(":", $command['incoming'],3); + break; + + default: + $info['command'] = $command['incoming']; + } + return $info; + } + + /** + * Returns a parsed message + * + * Calls msg_parse(msg_type( to first determine message type and then parse accordingly + * + * @access public + * @param String $command Raw server message + * @see msg_type + * @see msg_parse + * @return array + */ + static function getMessageInfo($message) + { + return self::msg_parse(self::msg_type($message)); + } + + /** + * Checks socket for end of file + * + * @access public + * @param Resource $socket Socket to check + * @return boolean true if end of file (socket) + */ + static function socketcheck($socket){ + $info = stream_get_meta_data($socket); + return $info['eof']; + //return(feof($socket)); + } +} + +?> diff --git a/plugins/Aim/extlib/phptoclib/dconnection.php b/plugins/Aim/extlib/phptoclib/dconnection.php new file mode 100755 index 0000000000..c6be25ffb9 --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/dconnection.php @@ -0,0 +1,229 @@ +connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + /* + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return false; + } + */ + $head=fread($this->sock,6); + if(strlen($head)<=0) + { + sEcho("The direct connection has been closed"); + return false; + } + $minihead=unpack("a4ver/nsize",$head); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0 && $allheader['len'] <= MAX_DIM_SIZE) + { + $left=$allheader['len']; + $stuff=""; + $nonin=0; + while(strlen($stuff) < $allheader['len'] && $nonin<3) + { + $stuffg=fread($this->sock,$left); + if(strlen($stuffg)<0) + { + $nonin++; + continue; + } + $left=$left - strlen($stuffg); + $stuff.=$stuffg; + } + $data=unpack("a*decoded",$stuff); + } + + else if($allheader['len'] > MAX_DIM_SIZE) + { + $data['decoded']="too big"; + } + + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + $w=array($this->sock); + stream_select($r=NULL,$w,$e=NULL,NULL); + //Now send it all + fputs($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,0,20000); + return ($changed>0); + } + + function close() + { + $this->connected=false; + return fclose($this->sock); + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed"); + $this->sock=null; + return false; + } + return true; + } +} + + +class FileSendConnect +{ + var $sock; + var $lastReceived; + var $lastMessage; + var $connected; + var $cookie; + var $tpye=3; + + + function FileSendConnect($ip,$port) + { + if(!$this->connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return; + } + + $minihead=unpack("a4ver/nsize",fread($this->sock,6)); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0) + $data=unpack("a*decoded",fread($this->sock,$allheader['len'])); + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + + //Now send it all + fwrite($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,1); + return ($changed>0); + } + + function close() + { + $this->connected=false; + fclose($this->sock); + unset($this->sock); + return true; + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed to" . $ip . ":" . $port); + $this->sock=null; + return false; + } + return true; + } +} diff --git a/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php index 10d99b3588..e1c8d3462e 100644 --- a/plugins/BitlyUrl/BitlyUrlPlugin.php +++ b/plugins/BitlyUrl/BitlyUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class BitlyUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/CasAuthentication/caslogin.php b/plugins/CasAuthentication/caslogin.php index 846774e7c6..3301ce5824 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 e754374198..62a6175794 100644 --- a/plugins/CasAuthentication/extlib/CAS.php +++ b/plugins/CasAuthentication/extlib/CAS.php @@ -1,20 +1,46 @@ =')) { - 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", ''); /** * SOAP envelope for SAML POST */ -define ("SAML_SOAP_ENV", ''); +define("SAML_SOAP_ENV", ''); /** * SOAP body for SAML POST */ -define ("SAML_SOAP_BODY", ''); +define("SAML_SOAP_BODY", ''); /** * SAMLP request */ -define ("SAMLP_REQUEST", ''); -define ("SAMLP_REQUEST_CLOSE", ''); +define("SAMLP_REQUEST", ''); +define("SAMLP_REQUEST_CLOSE", ''); /** * SAMLP artifact tag (for the ticket) */ -define ("SAML_ASSERTION_ARTIFACT", ''); +define("SAML_ASSERTION_ARTIFACT", ''); /** * SAMLP close */ -define ("SAML_ASSERTION_ARTIFACT_CLOSE", ''); +define("SAML_ASSERTION_ARTIFACT_CLOSE", ''); /** * SOAP body close */ -define ("SAML_SOAP_BODY_CLOSE", ''); +define("SAML_SOAP_BODY_CLOSE", ''); /** * SOAP envelope close */ -define ("SAML_SOAP_ENV_CLOSE", ''); +define("SAML_SOAP_ENV_CLOSE", ''); /** * 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\nphpCAS error: ".__CLASS__."::".$function.'(): '.htmlentities($msg)." in ".$file." on line ".$line."
\n"; - phpCAS::trace($msg); - phpCAS::traceExit(); - exit(); - } - + echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\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 5a589e4b28..1e316b6f6a 100644 --- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php +++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php @@ -1,4 +1,32 @@ _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).'\')'); @@ -697,6 +698,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 @@ -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('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$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 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 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 27a3a56f72..65e27a0374 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 856c7f05fd..bdffb81e26 100644 --- a/plugins/ClientSideShorten/shorten.js +++ b/plugins/ClientSideShorten/shorten.js @@ -31,10 +31,21 @@ })(jQuery,'smartkeypress'); + function longestWordInString(string) + { + var words = string.split(/\s/); + var longestWord = 0; + for(var i=0;i longestWord) longestWord = words[i].length; + return longestWord; + } + function shorten() { - $noticeDataText = $('#'+SN.C.S.NoticeDataText); - if(Notice_maxContent > 0 && $noticeDataText.val().length > Notice_maxContent){ + var $noticeDataText = $('#'+SN.C.S.NoticeDataText); + var noticeText = $noticeDataText.val(); + + if(noticeText.length > maxNoticeLength || longestWordInString(noticeText) > maxUrlLength) { var original = $noticeDataText.val(); shortenAjax = $.ajax({ url: $('address .url')[0].href+'/plugins/ClientSideShorten/shorten', diff --git a/plugins/Geonames/GeonamesPlugin.php b/plugins/Geonames/GeonamesPlugin.php index 310641ce60..d88014bb80 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 e2f8c6d543..c49c060921 100644 --- a/plugins/Imap/imapmanager.php +++ b/plugins/Imap/imapmanager.php @@ -59,12 +59,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 0000000000..9d50b914ab --- /dev/null +++ b/plugins/Irc/ChannelResponseChannel.php @@ -0,0 +1,61 @@ +. + * + * @category Network + * @package StatusNet + * @author Luke Fitzgerald + * @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 0000000000..d95ab0491d --- /dev/null +++ b/plugins/Irc/Fake_Irc.php @@ -0,0 +1,47 @@ +. + * + * @category Network + * @package StatusNet + * @author Luke Fitzgerald + * @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 0000000000..7a53e5cbf5 --- /dev/null +++ b/plugins/Irc/IrcPlugin.php @@ -0,0 +1,396 @@ +. + * + * @category IM + * @package StatusNet + * @author Luke Fitzgerald + * @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 + * @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 0000000000..59eec63d8d --- /dev/null +++ b/plugins/Irc/Irc_waiting_message.php @@ -0,0 +1,142 @@ + 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 0000000000..0a5d9ea83f --- /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 0000000000..553fe8e258 --- /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 0000000000..553fe8e258 --- /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 0000000000..d7d23420ac --- /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 0000000000..0004f44e22 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Autoload.php @@ -0,0 +1,81 @@ + + * @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 + * @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 0000000000..85e8a00fc9 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Bot.php @@ -0,0 +1,390 @@ + + * @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 + * @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 0000000000..c182f2ac1e --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Config.php @@ -0,0 +1,186 @@ + + * @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 + * @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 = '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 0000000000..fb646c10c1 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php @@ -0,0 +1,44 @@ + + * @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 + * @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 0000000000..746dec05f4 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection.php @@ -0,0 +1,401 @@ + + * @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 + * @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 0000000000..aec1cd8e0f --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php @@ -0,0 +1,50 @@ + + * @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 + * @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 0000000000..e9aeddcd3e --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php @@ -0,0 +1,130 @@ + + * @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 + * @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 0000000000..62736620d4 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php @@ -0,0 +1,301 @@ + + * @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 + * @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 0000000000..5873b2cb96 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php @@ -0,0 +1,59 @@ + + * @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 + * @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 0000000000..84c85a01cc --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Statusnet.php @@ -0,0 +1,66 @@ +. + * + * 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 + * @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 0000000000..73c0230c71 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php @@ -0,0 +1,729 @@ + + * @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 + * @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 0000000000..54b035dc03 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php @@ -0,0 +1,62 @@ + + * @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 + * @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 0000000000..5940636ba7 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Command.php @@ -0,0 +1,62 @@ + + * @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 + * @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 0000000000..6b094a810c --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php @@ -0,0 +1,38 @@ + + * @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 + * @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 0000000000..e308df8a56 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php @@ -0,0 +1,190 @@ + + * @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 + * @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 0000000000..647b5acb87 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Request.php @@ -0,0 +1,468 @@ + + * @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 + * @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 0000000000..097e2535e8 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Response.php @@ -0,0 +1,953 @@ + + * @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 + * @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 +{ + /** + * No such nick/channel + * + * Used to indicate the nickname parameter supplied to a command is currently + * unused. + */ + const ERR_NOSUCHNICK = '401'; + + /** + * No such server + * + * Used to indicate the server name given currently doesn't exist. + */ + const ERR_NOSUCHSERVER = '402'; + + /** + * No such channel + * + * Used to indicate the given channel name is invalid. + */ + const ERR_NOSUCHCHANNEL = '403'; + + /** + * 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'; + + /** + * 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'; + + /** + * There was no such nickname + * + * Returned by WHOWAS to indicate there is no history information for that + * nickname. + */ + const ERR_WASNOSUCHNICK = '406'; + + /** + * 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 () + */ + const ERR_NORECIPIENT = '411'; + + /** + * No text to send + */ + const ERR_NOTEXTTOSEND = '412'; + + /** + * No toplevel domain specified + */ + const ERR_NOTOPLEVEL = '413'; + + /** + * 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 $" or "PRIVMSG #" + * is attempted. + */ + const ERR_WILDTOPLEVEL = '414'; + + /** + * 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'; + + /** + * 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 on + * + * 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'; + + /** + * 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'; + + /** + * 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'; + + /** + * 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'; + + /** + * 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'; + + /** + * 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'; + + /** + * 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 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'; + + /** + * 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 key already set + */ + const ERR_KEYSET = '467'; + + /** + * Cannot join channel (+l) + */ + const ERR_CHANNELISFULL = '471'; + + /** + * is unknown mode char to me + */ + const ERR_UNKNOWNMODE = '472'; + + /** + * Cannot join channel (+i) + */ + const ERR_INVITEONLYCHAN = '473'; + + /** + * Cannot join channel (+b) + */ + const ERR_BANNEDFROMCHAN = '474'; + + /** + * 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'; + + /** + * 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 format used by USERHOST to list replies to the query list. The reply + * string is composed as follows = ['*'] '=' <'+'|'-'> + * 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'; + + /** + * [ {}] + * + * Reply format used by ISON to list replies to the query list. + */ + const RPL_ISON = '303'; + + /** + * + */ + 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'; + + /** + * * + */ + const RPL_WHOISUSER = '311'; + + /** + * + */ + const RPL_WHOISSERVER = '312'; + + /** + * is an IRC operator + */ + const RPL_WHOISOPERATOR = '313'; + + /** + * seconds idle + */ + const RPL_WHOISIDLE = '317'; + + /** + * End of /WHOIS list + */ + const RPL_ENDOFWHOIS = '318'; + + /** + * {[@|+]} + * + * 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'; + + /** + * * + */ + const RPL_WHOWASUSER = '314'; + + /** + * 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'; + + /** + * <# visible> + */ + 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'; + + /** + * + */ + const RPL_CHANNELMODEIS = '324'; + + /** + * No topic is set + */ + const RPL_NOTOPIC = '331'; + + /** + * + * + * 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'; + + /** + * + * + * 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'; + + /** + * Summoning user to IRC + * + * Returned by a server answering a SUMMON message to indicate that it is + * summoning that user. + */ + const RPL_SUMMONING = '342'; + + /** + * . + * + * Reply by the server showing its version details. The is the version + * of the software being used (including any patchlevel revisions) and the + * 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'; + + /** + * [*][@|+] + */ + const RPL_WHOREPLY = '352'; + + /** + * 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 being the item. + */ + const RPL_ENDOFWHO = '315'; + + /** + * [[@|+] [[@|+] [...]]] + */ + const RPL_NAMREPLY = '353'; + + /** + * 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'; + + /** + * + */ + const RPL_LINKS = '364'; + + /** + * 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'; + + /** + * + */ + const RPL_BANLIST = '367'; + + /** + * 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'; + + /** + * + */ + 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'; + + /** + * - Message of the day - + */ + const RPL_MOTDSTART = '375'; + + /** + * - + */ + 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'; + + /** + * 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'; + + /** + * + * + * 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 + */ + const RPL_TRACELINK = '200'; + + /** + * Try. + */ + const RPL_TRACECONNECTING = '201'; + + /** + * H.S. + */ + const RPL_TRACEHANDSHAKE = '202'; + + /** + * ???? [] + */ + const RPL_TRACEUNKNOWN = '203'; + + /** + * Oper + */ + const RPL_TRACEOPERATOR = '204'; + + /** + * User + */ + const RPL_TRACEUSER = '205'; + + /** + * Serv S C @ + */ + const RPL_TRACESERVER = '206'; + + /** + * 0 + */ + const RPL_TRACENEWTYPE = '208'; + + /** + * File + * + * 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'; + + /** + *