From 83491e4810da44715ab3315865f2bd9eebc7b2ad Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Tue, 7 Mar 2017 20:36:56 +0500 Subject: [PATCH 01/16] Features added: register user, update user vCard, list of user's rooms package renamed for packagist --- composer.json | 2 +- src/Protocol/Room/RoomList.php | 90 ++++++ src/Protocol/User/RegisterUser.php | 212 ++++++++++++++ src/Protocol/User/RequestUserRegisterForm.php | 107 +++++++ src/Protocol/User/VCardPresence.php | 83 ++++++ src/Protocol/User/VCardUpdate.php | 268 ++++++++++++++++++ 6 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 src/Protocol/Room/RoomList.php create mode 100644 src/Protocol/User/RegisterUser.php create mode 100644 src/Protocol/User/RequestUserRegisterForm.php create mode 100644 src/Protocol/User/VCardPresence.php create mode 100644 src/Protocol/User/VCardUpdate.php diff --git a/composer.json b/composer.json index 92a051d..eb18afd 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "fabiang/xmpp", + "name": "elfuvo/xmpp", "description": "Library for XMPP protocol (Jabber) connections", "license": "BSD-2-Clause", "homepage": "https://github.com/fabiang/xmpp", diff --git a/src/Protocol/Room/RoomList.php b/src/Protocol/Room/RoomList.php new file mode 100644 index 0000000..52c2943 --- /dev/null +++ b/src/Protocol/Room/RoomList.php @@ -0,0 +1,90 @@ +" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set server address - for example: conference.xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/RegisterUser.php b/src/Protocol/User/RegisterUser.php new file mode 100644 index 0000000..af83232 --- /dev/null +++ b/src/Protocol/User/RegisterUser.php @@ -0,0 +1,212 @@ +setFrom($from) + ->setTo($to) + ->setUserJID($userJid) + ->setPassword($password) + ->setSID($sid); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + "" . + "http://jabber.org/protocol/admin" . + "" . + "" . + "%s" . + "" . + "" . + "%s" . + "" . + "" . + "%s" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo(), + $this->getSID(), + $this->getUserJID(), + $this->getPassword(), + $this->getPassword() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get UserJID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set UserJID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @param $sid + * @return $this + */ + public function setSID($sid) + { + $this->sid = (string)$sid; + return $this; + } + + /** + * @return string + */ + public function getSID() + { + return $this->sid; + } + + /** + * Get UserJID. + * + * @return string + */ + public function getUserJID() + { + return $this->userJid; + } + + /** + * set user account JID + * + * @param $userJid string + * @return $this + */ + public function setUserJID($userJid) + { + $this->userJid = (string)$userJid; + return $this; + } + + /** + * Get account password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param $password string - set account password + * @return $this + */ + public function setPassword($password) + { + $this->password = (string)$password; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/RequestUserRegisterForm.php b/src/Protocol/User/RequestUserRegisterForm.php new file mode 100644 index 0000000..6d61ca4 --- /dev/null +++ b/src/Protocol/User/RequestUserRegisterForm.php @@ -0,0 +1,107 @@ +setFrom($from) + ->setTo($to); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/VCardPresence.php b/src/Protocol/User/VCardPresence.php new file mode 100644 index 0000000..b9b2b84 --- /dev/null +++ b/src/Protocol/User/VCardPresence.php @@ -0,0 +1,83 @@ +imageId = $imageId; + } + + /** + * {@inheritdoc} + */ + public function toString() + { + if ($this->imageId) { + return XML::quoteMessage("" . + "" . + "%s" . + "" . + "" . + "%s" . + "" . + "" . + "", + $this->getImageId(), + $this->getImageId() + ); + } else { + return XML::quoteMessage("" . + "" . + ""); + } + } + + /** + * set SHA1 image id + * + * @param $imageId + * @return $this + */ + protected function setImageId($imageId) + { + $this->imageId = $imageId; + return $this; + } + + /** + * get image SHA1 id + * + * @return string + */ + public function getImageId() + { + return $this->imageId; + } +} \ No newline at end of file diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php new file mode 100644 index 0000000..2114dd9 --- /dev/null +++ b/src/Protocol/User/VCardUpdate.php @@ -0,0 +1,268 @@ +setFrom($from); + $this->setProperty('JABBERID', $from); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage("" . + "%s" . + "", + $this->getFrom(), + XML::generateId(), + $this->composeVCard() + ); + } + + /** + * compose XML string of vCard + * @return string + */ + protected function composeVCard() + { + $xmlVCard = ''; + if (!empty($this->vCard)) { + foreach ($this->vCard as $attrName => $attrValue) { + $xmlVCard .= '<' . $attrName . '>'; + if (is_array($attrValue)) { + foreach ($attrValue as $subAttrName => $subAttrValue) { + $xmlVCard .= '<' . $subAttrName . '>'; + $xmlVCard .= (string)$subAttrValue; + $xmlVCard .= ''; + } + } else { + $xmlVCard .= (string)$attrValue; + } + $xmlVCard .= ''; + } + } + return $xmlVCard; + } + + /** + * Get UserJID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set UserJID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * set property of vCard + * + * @param $property + * @param $value + * @return $this + */ + public function setProperty($property, $value) + { + if (in_array($property, $this->availableProperties)) { + switch ($property) { + case 'GIVEN': + case 'FAMILY': + case 'MIDDLE': + if (!isset($this->vCard['N'])) { + $this->vCard['N'] = array('FAMILY' => '', 'GIVEN' => '', 'MIDDLE' => ''); + $this->vCard['FN'] = ''; + } + $this->vCard['N'][$property] = (string)$value; + $this->vCard['FN'] .= empty($this->vCard['FN']) ? (string)$value : ' ' . (string)$value; + break; + case 'URL': + case 'NICKNAME': + case 'JABBERID': + case 'DESC': + $this->vCard[$property] = strip_tags($value); + break; + case 'PHOTO': + // value must be path to image + $this->setImage($value); + $this->vCard['PHOTO'] = array('TYPE' => $this->getImageMime(), 'BINVAL' => $this->getImageBase64Data()); + // free some memory + $this->imagePath = null; + break; + } + } + return $this; + } + + /** + * Set image content + * + * @param $path + * @return $this + */ + public function setImage($path) + { + if (!file_exists($path)) { + throw new InvalidArgumentException('File "' . $path . '" does not exists.'); + } + $this->imagePath = $path; + + if (!in_array($this->getImageMime(), $this->mimes)) { + throw new InvalidArgumentException('Type of Image must be of allowed mimes: ' . implode(', ', $this->mimes) . '.'); + } + + $this->imageData = file_get_contents($path); + $this->setImageId(); + return $this; + } + + /** + * set SHA1 image id + */ + protected function setImageId() + { + $this->imageId = sha1($this->imageData); + } + + /** + * get image SHA1 id + * + * @return string + */ + public function getImageId() + { + return $this->imageId; + } + + + /** + * get converted to Base64 image data + * + * @return string + */ + protected function getImageBase64Data() + { + return base64_encode($this->imageData); + } + + /** + * get image mime type + * @return string + */ + protected function getImageMime() + { + if (!$this->imageMime) { + $info = array(); + getimagesize($this->imagePath, $info); + + return image_type_to_mime_type($info['info']); + } + return $this->imageMime; + } + + +} \ No newline at end of file From 983520bf0e023ce8eae43c7cf54c6db6c3db7088 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Wed, 8 Mar 2017 09:54:54 +0500 Subject: [PATCH 02/16] some ads & fixes --- composer.json | 2 +- src/Options.php | 28 +++++++++++++++++++++++++++- src/Protocol/User/VCardUpdate.php | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index eb18afd..92a051d 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "elfuvo/xmpp", + "name": "fabiang/xmpp", "description": "Library for XMPP protocol (Jabber) connections", "license": "BSD-2-Clause", "homepage": "https://github.com/fabiang/xmpp", diff --git a/src/Options.php b/src/Options.php index 63b4bd5..6beb50c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -37,8 +37,8 @@ namespace Fabiang\Xmpp; use Fabiang\Xmpp\Connection\ConnectionInterface; -use Fabiang\Xmpp\Protocol\ImplementationInterface; use Fabiang\Xmpp\Protocol\DefaultImplementation; +use Fabiang\Xmpp\Protocol\ImplementationInterface; use Psr\Log\LoggerInterface; /** @@ -99,6 +99,12 @@ class Options */ protected $jid; + /** + * + * @var string + */ + protected $sid; + /** * * @var boolean @@ -339,6 +345,26 @@ public function setJid($jid) return $this; } + /** + * Get sid. + * + * @return string + */ + public function getSid() + { + return $this->sid; + } + + /** + * @param $sid string + * @return $this + */ + public function setSid($sid) + { + $this->sid = (string) $sid; + return $this; + } + /** * Is user authenticated. * diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php index 2114dd9..c1e8613 100644 --- a/src/Protocol/User/VCardUpdate.php +++ b/src/Protocol/User/VCardUpdate.php @@ -54,6 +54,7 @@ class VCardUpdate implements ProtocolImplementationInterface 'PHOTO', 'JABBERID', 'NICKNAME', + 'URL', 'DESC', ]; /** From 402f863277ed8945de9c376b4aee7377ff7a94de Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Wed, 8 Mar 2017 10:05:10 +0500 Subject: [PATCH 03/16] User register events --- .../Stream/RequestUserRegisterFrom.php | 111 ++++++++++++++++++ src/Protocol/DefaultImplementation.php | 16 +-- 2 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 src/EventListener/Stream/RequestUserRegisterFrom.php diff --git a/src/EventListener/Stream/RequestUserRegisterFrom.php b/src/EventListener/Stream/RequestUserRegisterFrom.php new file mode 100644 index 0000000..fab61da --- /dev/null +++ b/src/EventListener/Stream/RequestUserRegisterFrom.php @@ -0,0 +1,111 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\Protocol\User\User; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class RequestUserRegisterFrom extends AbstractEventListener implements BlockingEventListenerInterface +{ + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + /** + * user object. + * + * @var User + */ + protected $userObject; + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); + } + + /** + * Sending a query request for roster sets listener to blocking mode. + * + * @return void + */ + public function query() + { + $this->blocking = true; + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + /* @var $element \DOMElement */ + $sid = $event->getParameter(0)->getAttribute('sessionid'); + + $this->getOptions()->setSid($sid); + $this->blocking = false; + } + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } +} \ No newline at end of file diff --git a/src/Protocol/DefaultImplementation.php b/src/Protocol/DefaultImplementation.php index 93a4b58..794e236 100644 --- a/src/Protocol/DefaultImplementation.php +++ b/src/Protocol/DefaultImplementation.php @@ -36,17 +36,18 @@ namespace Fabiang\Xmpp\Protocol; -use Fabiang\Xmpp\Options; -use Fabiang\Xmpp\EventListener\EventListenerInterface; -use Fabiang\Xmpp\Event\EventManagerInterface; use Fabiang\Xmpp\Event\EventManager; -use Fabiang\Xmpp\EventListener\Stream\Stream; -use Fabiang\Xmpp\EventListener\Stream\StreamError; -use Fabiang\Xmpp\EventListener\Stream\StartTls; +use Fabiang\Xmpp\Event\EventManagerInterface; +use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\EventListener\Stream\Authentication; use Fabiang\Xmpp\EventListener\Stream\Bind; -use Fabiang\Xmpp\EventListener\Stream\Session; +use Fabiang\Xmpp\EventListener\Stream\RequestUserRegisterFrom; use Fabiang\Xmpp\EventListener\Stream\Roster as RosterListener; +use Fabiang\Xmpp\EventListener\Stream\Session; +use Fabiang\Xmpp\EventListener\Stream\StartTls; +use Fabiang\Xmpp\EventListener\Stream\Stream; +use Fabiang\Xmpp\EventListener\Stream\StreamError; +use Fabiang\Xmpp\Options; /** * Default Protocol implementation. @@ -82,6 +83,7 @@ public function register() $this->registerListener(new Bind); $this->registerListener(new Session); $this->registerListener(new RosterListener); + $this->registerListener(new RequestUserRegisterFrom); } /** From fd0591301a36663c4d2cd4cfaf81119af3bb9b27 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Wed, 8 Mar 2017 13:14:47 +0500 Subject: [PATCH 04/16] Features added: change user password --- .../Stream/AbstractSessionEvent.php | 7 +- src/EventListener/Stream/Bind.php | 4 +- .../Stream/RequestChangePasswordFrom.php | 128 +++++++++++ .../Stream/RequestUserRegisterFrom.php | 17 ++ src/EventListener/Stream/Session.php | 4 +- .../Stream/ChangePasswordErrorException.php | 45 ++++ .../Stream/RegistrationErrorException.php | 45 ++++ src/Protocol/DefaultImplementation.php | 2 + src/Protocol/User/ChangeUserPassword.php | 207 ++++++++++++++++++ src/Protocol/User/RegisterUser.php | 4 +- .../User/RequestChangePasswordForm.php | 110 ++++++++++ 11 files changed, 564 insertions(+), 9 deletions(-) create mode 100644 src/EventListener/Stream/RequestChangePasswordFrom.php create mode 100644 src/Exception/Stream/ChangePasswordErrorException.php create mode 100644 src/Exception/Stream/RegistrationErrorException.php create mode 100644 src/Protocol/User/ChangeUserPassword.php create mode 100644 src/Protocol/User/RequestChangePasswordForm.php diff --git a/src/EventListener/Stream/AbstractSessionEvent.php b/src/EventListener/Stream/AbstractSessionEvent.php index 7b26033..7dbc625 100644 --- a/src/EventListener/Stream/AbstractSessionEvent.php +++ b/src/EventListener/Stream/AbstractSessionEvent.php @@ -36,8 +36,8 @@ namespace Fabiang\Xmpp\EventListener\Stream; -use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\Util\XML; /** @@ -66,15 +66,16 @@ abstract class AbstractSessionEvent extends AbstractEventListener * Handle session event. * * @param XMLEvent $event + * @param string $data * @return void */ - protected function respondeToFeatures(XMLEvent $event, $data) + protected function respondToFeatures(XMLEvent $event, $data) { if ($event->isEndTag()) { /* @var $element \DOMElement */ $element = $event->getParameter(0); - // bind element occured in + // bind element occurred in if ('features' === $element->parentNode->localName) { $this->blocking = true; $this->getConnection()->send(sprintf( diff --git a/src/EventListener/Stream/Bind.php b/src/EventListener/Stream/Bind.php index b6499fd..8c3009e 100644 --- a/src/EventListener/Stream/Bind.php +++ b/src/EventListener/Stream/Bind.php @@ -36,8 +36,8 @@ namespace Fabiang\Xmpp\EventListener\Stream; -use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; /** * Listener @@ -65,7 +65,7 @@ public function attachEvents() */ public function bindFeatures(XMLEvent $event) { - $this->respondeToFeatures( + $this->respondToFeatures( $event, '%s' ); diff --git a/src/EventListener/Stream/RequestChangePasswordFrom.php b/src/EventListener/Stream/RequestChangePasswordFrom.php new file mode 100644 index 0000000..347ddfa --- /dev/null +++ b/src/EventListener/Stream/RequestChangePasswordFrom.php @@ -0,0 +1,128 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\Exception\Stream\ChangePasswordErrorException; +use Fabiang\Xmpp\Protocol\User\User; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class RequestChangePasswordFrom extends AbstractEventListener implements BlockingEventListenerInterface +{ + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + /** + * user object. + * + * @var User + */ + protected $userObject; + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); + } + + /** + * Sending a query request for roster sets listener to blocking mode. + * + * @return void + */ + public function query() + { + $this->blocking = true; + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + /* @var $element \DOMElement */ + $sid = $event->getParameter(0)->getAttribute('sessionid'); + + $this->getOptions()->setSid($sid); + $this->blocking = false; + } + } + + /** + * we have some errors. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function error(XMLEvent $event) + { + if (false === $event->isStartTag()) { + $this->blocking = false; + throw ChangePasswordErrorException::createFromEvent($event); + } + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } +} \ No newline at end of file diff --git a/src/EventListener/Stream/RequestUserRegisterFrom.php b/src/EventListener/Stream/RequestUserRegisterFrom.php index fab61da..d7c1662 100644 --- a/src/EventListener/Stream/RequestUserRegisterFrom.php +++ b/src/EventListener/Stream/RequestUserRegisterFrom.php @@ -39,6 +39,7 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\Exception\Stream\RegistrationErrorException; use Fabiang\Xmpp\Protocol\User\User; /** @@ -70,6 +71,8 @@ public function attachEvents() { $this->getOutputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); $this->getInputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); } @@ -101,6 +104,20 @@ public function result(XMLEvent $event) } } + /** + * we have some errors. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function error(XMLEvent $event) + { + if (false === $event->isStartTag()) { + $this->blocking = false; + throw RegistrationErrorException::createFromEvent($event); + } + } + /** * {@inheritDoc} */ diff --git a/src/EventListener/Stream/Session.php b/src/EventListener/Stream/Session.php index 10b8c60..0540d1b 100644 --- a/src/EventListener/Stream/Session.php +++ b/src/EventListener/Stream/Session.php @@ -36,8 +36,8 @@ namespace Fabiang\Xmpp\EventListener\Stream; -use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; /** * Listener @@ -65,7 +65,7 @@ public function attachEvents() */ public function sessionStart(XMLEvent $event) { - $this->respondeToFeatures( + $this->respondToFeatures( $event, '' ); diff --git a/src/Exception/Stream/ChangePasswordErrorException.php b/src/Exception/Stream/ChangePasswordErrorException.php new file mode 100644 index 0000000..f21f41a --- /dev/null +++ b/src/Exception/Stream/ChangePasswordErrorException.php @@ -0,0 +1,45 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\Exception\Stream; + +/** + * Class ChangePasswordErrorException + * @package Fabiang\Xmpp\Exception\Stream + */ +class ChangePasswordErrorException extends StreamErrorException +{ + +} \ No newline at end of file diff --git a/src/Exception/Stream/RegistrationErrorException.php b/src/Exception/Stream/RegistrationErrorException.php new file mode 100644 index 0000000..e00b955 --- /dev/null +++ b/src/Exception/Stream/RegistrationErrorException.php @@ -0,0 +1,45 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\Exception\Stream; + +/** + * Class RegistrationErrorException + * @package Fabiang\Xmpp\Exception\Stream + */ +class RegistrationErrorException extends StreamErrorException +{ + +} \ No newline at end of file diff --git a/src/Protocol/DefaultImplementation.php b/src/Protocol/DefaultImplementation.php index 794e236..57e5349 100644 --- a/src/Protocol/DefaultImplementation.php +++ b/src/Protocol/DefaultImplementation.php @@ -41,6 +41,7 @@ use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\EventListener\Stream\Authentication; use Fabiang\Xmpp\EventListener\Stream\Bind; +use Fabiang\Xmpp\EventListener\Stream\RequestChangePasswordFrom; use Fabiang\Xmpp\EventListener\Stream\RequestUserRegisterFrom; use Fabiang\Xmpp\EventListener\Stream\Roster as RosterListener; use Fabiang\Xmpp\EventListener\Stream\Session; @@ -84,6 +85,7 @@ public function register() $this->registerListener(new Session); $this->registerListener(new RosterListener); $this->registerListener(new RequestUserRegisterFrom); + $this->registerListener(new RequestChangePasswordFrom); } /** diff --git a/src/Protocol/User/ChangeUserPassword.php b/src/Protocol/User/ChangeUserPassword.php new file mode 100644 index 0000000..ebf06f4 --- /dev/null +++ b/src/Protocol/User/ChangeUserPassword.php @@ -0,0 +1,207 @@ +setFrom($from) + ->setTo($to) + ->setUserJID($userJid) + ->setPassword($password) + ->setSID($sid); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + "" . + "http://jabber.org/protocol/admin" . + "" . + "" . + "%s" . + "" . + "" . + "%s" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo(), + $this->getSID(), + $this->getUserJID(), + $this->getPassword() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get UserJID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set UserJID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @param $sid + * @return $this + */ + public function setSID($sid) + { + $this->sid = (string)$sid; + return $this; + } + + /** + * @return string + */ + public function getSID() + { + return $this->sid; + } + + /** + * Get UserJID. + * + * @return string + */ + public function getUserJID() + { + return $this->userJid; + } + + /** + * set user account JID + * + * @param $userJid string + * @return $this + */ + public function setUserJID($userJid) + { + $this->userJid = (string)$userJid; + return $this; + } + + /** + * Get account password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param $password string - set account password + * @return $this + */ + public function setPassword($password) + { + $this->password = (string)$password; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/RegisterUser.php b/src/Protocol/User/RegisterUser.php index af83232..040ab26 100644 --- a/src/Protocol/User/RegisterUser.php +++ b/src/Protocol/User/RegisterUser.php @@ -13,7 +13,7 @@ use Fabiang\Xmpp\Util\XML; /** - * Class RequestUserRegisterForm + * Class RegisterUser * * Register user * @@ -59,7 +59,7 @@ class RegisterUser implements ProtocolImplementationInterface * RegisterUser constructor. * @param $userJid string * @param $password string - * @param $sid string - SID of request form @see RequestUserRegisterForm + * @param $sid string - SID of request form @see RegisterUser * @param $from string - admin user JID * @param null|string $to */ diff --git a/src/Protocol/User/RequestChangePasswordForm.php b/src/Protocol/User/RequestChangePasswordForm.php new file mode 100644 index 0000000..59c66bb --- /dev/null +++ b/src/Protocol/User/RequestChangePasswordForm.php @@ -0,0 +1,110 @@ +setFrom($from) + ->setTo($to); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } +} \ No newline at end of file From 34dad50a91cb1b84eb360c959af70d0940dcd666 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Wed, 8 Mar 2017 13:56:21 +0500 Subject: [PATCH 05/16] detect mime type of image --- src/Protocol/User/VCardUpdate.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php index c1e8613..9e535d2 100644 --- a/src/Protocol/User/VCardUpdate.php +++ b/src/Protocol/User/VCardUpdate.php @@ -257,10 +257,8 @@ protected function getImageBase64Data() protected function getImageMime() { if (!$this->imageMime) { - $info = array(); - getimagesize($this->imagePath, $info); - - return image_type_to_mime_type($info['info']); + $size = getimagesize($this->imagePath); + $this->imageMime = isset($size['mime']) ? $size['mime'] : ''; } return $this->imageMime; } From e15da74ae3d07a4db93fa04f5b36c01474aa7c4a Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Wed, 8 Mar 2017 15:15:43 +0500 Subject: [PATCH 06/16] small fixes --- src/EventListener/Stream/Authentication.php | 8 ++++---- src/Protocol/User/RegisterUser.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EventListener/Stream/Authentication.php b/src/EventListener/Stream/Authentication.php index 2934abc..bc58eab 100644 --- a/src/EventListener/Stream/Authentication.php +++ b/src/EventListener/Stream/Authentication.php @@ -37,11 +37,11 @@ namespace Fabiang\Xmpp\EventListener\Stream; use Fabiang\Xmpp\Event\XMLEvent; -use Fabiang\Xmpp\Exception\RuntimeException; -use Fabiang\Xmpp\EventListener\Stream\Authentication\AuthenticationInterface; -use Fabiang\Xmpp\Exception\Stream\AuthenticationErrorException; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\EventListener\Stream\Authentication\AuthenticationInterface; +use Fabiang\Xmpp\Exception\RuntimeException; +use Fabiang\Xmpp\Exception\Stream\AuthenticationErrorException; /** * Listener @@ -154,7 +154,7 @@ protected function determineMechanismClass() * Authentication failed. * * @param XMLEvent $event - * @throws StreamErrorException + * @throws AuthenticationErrorException */ public function failure(XMLEvent $event) { diff --git a/src/Protocol/User/RegisterUser.php b/src/Protocol/User/RegisterUser.php index 040ab26..02f95c5 100644 --- a/src/Protocol/User/RegisterUser.php +++ b/src/Protocol/User/RegisterUser.php @@ -59,7 +59,7 @@ class RegisterUser implements ProtocolImplementationInterface * RegisterUser constructor. * @param $userJid string * @param $password string - * @param $sid string - SID of request form @see RegisterUser + * @param $sid string - SID of request form RequestUserRegisterForm * @param $from string - admin user JID * @param null|string $to */ @@ -83,8 +83,8 @@ public function toString() "" . "" . "http://jabber.org/protocol/admin" . - "" . - "" . + "" . + "" . "%s" . "" . "" . From 86fd83f3f14f92424f60787ed631c7454bd2d3e2 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Tue, 18 Apr 2017 12:19:24 +0500 Subject: [PATCH 07/16] vCard fixed Form\FormInterface examples added --- examples/avatar.png | Bin 0 -> 3730 bytes examples/config.inc.php | 10 + example.php => examples/example.php | 26 +-- examples/register.php | 75 +++++++ examples/vcard.php | 56 +++++ src/Connection/Socket.php | 16 +- ...uestChangePasswordFrom.php => Command.php} | 43 +++- .../Stream/RequestUserRegisterFrom.php | 128 ----------- ...xception.php => CommandErrorException.php} | 51 ++++- src/Form/AbstractForm.php | 211 ++++++++++++++++++ src/Form/Form.php | 18 ++ src/Form/FormInterface.php | 90 ++++++++ src/Options.php | 89 ++++++-- src/Protocol/DefaultImplementation.php | 6 +- src/Protocol/Presence.php | 55 ++++- src/Protocol/Room/RequestRoomConfigForm.php | 156 +++++++++++++ src/Protocol/User/ChangeUserPassword.php | 55 ++--- src/Protocol/User/RegisterUser.php | 79 ++++--- src/Protocol/User/VCardUpdate.php | 22 +- src/Stream/SocketClient.php | 42 +++- src/Stream/XMLStream.php | 41 ++-- tests/src/EventListener/Stream/BindTest.php | 4 +- .../User/RequestChangePasswordFormTest.php | 95 ++++++++ .../User/RequestUserRegisterFormTest.php | 92 ++++++++ 24 files changed, 1150 insertions(+), 310 deletions(-) create mode 100644 examples/avatar.png create mode 100644 examples/config.inc.php rename example.php => examples/example.php (68%) create mode 100644 examples/register.php create mode 100644 examples/vcard.php rename src/EventListener/Stream/{RequestChangePasswordFrom.php => Command.php} (66%) delete mode 100644 src/EventListener/Stream/RequestUserRegisterFrom.php rename src/Exception/Stream/{RegistrationErrorException.php => CommandErrorException.php} (53%) create mode 100644 src/Form/AbstractForm.php create mode 100644 src/Form/Form.php create mode 100644 src/Form/FormInterface.php create mode 100644 src/Protocol/Room/RequestRoomConfigForm.php create mode 100644 tests/src/Protocol/User/RequestChangePasswordFormTest.php create mode 100644 tests/src/Protocol/User/RequestUserRegisterFormTest.php diff --git a/examples/avatar.png b/examples/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..55e678e6a6be75400db89609596074a03ee65cda GIT binary patch literal 3730 zcmV;D4sG#?P)8TRzR1gCA|d1QE*Va5LVq;5fxn=Sl3baa9v$i*HKqx1=m^K!(-IN0|lW6P14eq zL$Dlec&YST99k)#Loc>CV>|6lqiP5S>yD(*P^GjC{qlJ~#& zzIVU(UvkOCE4*v&+}R@$MoD1gj~Sm7@d$*={J@AGBR+q~M#Bhg^ja zDFM}x$a7&-q{e(}gw`}KQfFGE(wf)czqt`QV}69TE?>Puzg3;2Umu-Rwm5p(nPdY_*v?frmFd$S(z`*WD zXv{76oo)E{3o*&HvBOPYsLV$r#7H387r83UJTp>b%0c40rOd{yw7^|CHh8AM0slGH z1_>o?pgYkBx^Hao$jBx4u-k6WjTJAyui=>3# z@>v5cLS_$Hx}mGLANbYR*9RRP9pHAmJwTu7K3(+PgZLW~)KtVF#i`Pn3w;IggEBuR z!#Wm;TFnWJLU?+w9gfy_!vMHl!FE z#(jMB;S0|^zzFi@bP`0!w5$dwHgyFg{N1nGM2A-i>SR64*<1@-%bKCxrJQ-`RZ!Oj zH?ON__@a|5`r?*P z++k)!zO=^zpPg=k$F`Xuex-Lvygc0kzxkpWl1@6|WaByL>ko{N`w%iEiXbFR(lCV2 zDd!lDBuDK}?a|q-C1Rt~%#f|`P;^q?TH0XxnrbG1w~DQ>uc`$Onmd^qzkj?99^Bpp zV>7ISa)g`K*F*9tC%DhcM~|#>7pPN(-$iJRnM7+i>0_is03MrSHZKyVg!$Ia9$2{7 ziZ&{!XdIr>#_#Nmc?yeC*l+HF^zYoT08M3Pwhdy^2J(O#Q4n96yJe$CR)t%VU>V<5 z$nQw$jmyNGnG3+n+jECUYA<)4YV3xaS66%NbJm7hNIlWymwA7%r?wMbL*H=aDnSrq zbSB6=*(4h}6VQAFAsG9jwC33g3Z+p}rzaIalr+xsP)80=&gPHRcfkZ*6=QsIdKE1H zs==f7N|=|z?wU@Rv!&4!J#tH%hYX!W+dMH$Vk+GbooI?5LesqjFiIi-l~gr&l0%_R z&fDAE=aFdxBlhR(sxj2rLuQ_U`HIRGcsSR{a9)*K1vPf(kfF1!yN~$~vDQfU6WvrI zpMC}4RWed~%k65YIL{r_i+ik$&>J!hkXPOuHuLoR!b%6^>+NvMs%oYQe?QP7Yl8W; zU7n1Qr_GW=DN-VUk-h@(@}4}mrwmVyd86G0abk@3r}pH?$g4k7=z#e}4)$3I^Bk6b zWn-akT!zIfG6cFF-z}H|<}3x0yhbe*fUjzd7*y4XxxK&i);Ba|VSMMOU+BYp5;A_n zx_YlLN0CG+^Yrvu*be=f}Pb}@Dj%KD>4V-`RyBR?BA>L6e;c#Lxor*QT$$A>Vi!b zF4!Ys+?RQRCq%V=<8Z5@B#ay=9ZGbfzH8bm%O{FHKt4%gogA^A?^nP)!N*4h8=acv z-OwP9yr+&59T)q2Pp%C?8R@XM`DOppXq zKNHO8%_D8H%oBZ1mm6-(6rxhx(vvSME|jQ^MGvB8h7#uK^A3aCOU-ZF*nlVN_B29r z(B}QM#iCP9aAm5&lQHr_iRJeG+GsM#m@j?*E)p~|N?W^CSx$__1n=RI+9_e4?o<4Y zPZv)9CUkhd#f-((>uG4jqlUbWYJ9q>8=P(dUrW0SPMKRVU2bL?vS5D;MvE4B?|3^D zVF+GytlcAk!&YTxNw7=r5PFO{vjdB{kxEYgR^G!4rj13{k`n@vq}sNQ-a!N|FL5$E zHnauT86sR`Kz1*;d6EOvf_Y z&ClA}J@~4coa{dT4#95B=A&57FTTnhpGJr;t+r z6$`0g@hKJnrHO1%i8DHCHU>-Vsit!tHKmTua4=67;bLJ7HS@T!Eogj8l$ZeTl+cux z1VFR8P>B76KMaX=INf10e-1+d$M?|@TS)l)Il+KEK^jq$04Ns^WoXfZKcQrW9NyQ) z*07lOl(oow96C=2W{aWVlZ3%{WP5@%pC=zcQ`%52BI^E4bu82+9k+$WykNiWOqlm= ztPNS1JP}f9Esx9g1Zg>eN!%wDR?0jP_u*loH}F7qU0BT1?;gk&`ho8c5j}>wT-ZJ0 zsjymWm@eBBq|FR*Ie|4Tf@PkFJdMK;Uey^|&kg!+M^8VTX+F=)g@Dw&v!{O$z6#71 zd4)(gLlGtoV1(1Jv)}#5Q))#$LAIG8W`n^p&tdk)I%d1%fPAlKG0o=fHeMk>!5bJ7UQzCX+nQ% z@SYT(t;NaO;3R-79I1!GJX!FRwL(82e(8yYihF{zlfzqLN37i;Rdp1;rihJD>H_-< zZ#B3uLBJ=&QhYD$wlL;z!UA8{GfkcbChOtpgq~oz<9yJ}yOEwpw+P`=olsg3^5zKB0&yx#;Uw7HKA}WN zizyMIrpTl<1A73I*J7@q6Z!+(*%4fqQH?{<4w$i7NM0z94>lBU!Sx$qYowz8ip9nE zg|#+A14bf*c5+BXhhm;iJmbGEHcYv3-N1TVis<3(U48KCm#q*>5kS%$AsDZ%gg5p# zKwDRjR|Q8vf}?7X>WmXM|Jx&+c?%9txhBOVmS%>%Geje1mBv_4#L`ZV%ZGVtPLPAU zGusxhjr|TTNl9t8cl5ya3J0qdDlysFR&EEY!{sH+gc4^!eAlA(P<*dy_3jK2ZI5jg zRXI|9-ca}bggt^b1gDX zi%vt0x(QaEal=ou>b-29V4SuJE?+rV#Yv!a>T>%7I$XP%xxU70q7k}C-mOW8{tcZ%MLU$r@etka_eAXTGgQO*Erl1qv`MBuiJ4Hu55Xt00*eE zHq;C-pJJ>EYfVmePmr!pYa_6-@(AIYHT8p3UvA`0!q{Qxfc52W__WLo+YFt4$K^xe z+k|<;HR%I+L!_qmEYUVxFGHw@+`R=DDcuaGS-^tg)}ZJ8p{&i2@Lt@7BeE3(YClG6 zEFhYO>opj^dxCyD$f>5+X(f#dVj^k-dH?T=`Hv3U1oPoGpQP_qX$=1igI{HPf%eXPRv)ZkgX&Qo1SiKFmN_e`=86T5e?ucJl56If73m3s4>#lWhBCkPN*=&FDY3N&PsgH zdxGJF&dG0YpB}rsEGsI-WWn9t{y=GMKuD?WS0`6kV-rf(PkJ-wmT+H&2x(7n*xn{P z0GHzHEsu;&wIr#urhQmjl&f?`Bbtnjfc{qLOl7K6bKz*MDJeRo?$ 'xmpp.nko.home', + 'port' => 5222, + 'connectionType' => 'tcp', + 'verifyPeer' => false, + 'login' => 'admin', + 'password' => 'pass1view', +]; \ No newline at end of file diff --git a/example.php b/examples/example.php similarity index 68% rename from example.php rename to examples/example.php index e0641a3..ed5c21c 100644 --- a/example.php +++ b/examples/example.php @@ -1,32 +1,26 @@ pushHandler(new StreamHandler('php://stdout', Logger::DEBUG)); -$hostname = 'localhost'; -$port = 5222; -$connectionType = 'tcp'; -$address = "$connectionType://$hostname:$port"; - -$username = 'xmpp'; -$password = 'test'; +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; $options = new Options($address); $options->setLogger($logger) - ->setUsername($username) - ->setPassword($password); + ->setUsername($config['login']) + ->setPassword($config['password']); $client = new Client($options); diff --git a/examples/register.php b/examples/register.php new file mode 100644 index 0000000..9fe5203 --- /dev/null +++ b/examples/register.php @@ -0,0 +1,75 @@ +pushHandler(new StreamHandler($fh, Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$newUser = 'testuser'; +$newPassword = '123456'; + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($config['login']) + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + +$client->connect(); +$request = new RequestUserRegisterForm($config['login'] . '@' . $config['host'], $config['host']); +$client->send($request); + +$form = $client->getOptions()->getForm(); +$user = new RegisterUser( + $newUser . '@' . $config['host'], + $newPassword, + $config['login'] . '@' . $config['host'], + $config['host'], + $form +); + +try { + print $user->toString() . PHP_EOL; + $client->send($user); + print 'user sent' . PHP_EOL; +} catch (CommandErrorException $e) { + /** + * @see https://xmpp.org/extensions/xep-0086.html#sect-idm139696314152720 + */ + if ($e->getCode() == CommandErrorException::ERROR_CONFLICT) { // conflict + fwrite(STDOUT, 'User already exists. Try to change password' . PHP_EOL); + $request = new RequestChangePasswordForm( + $config['login'] . '@' . $config['host'], + $config['host'] + ); + $client->send($request); + + $form = $client->getOptions()->getForm(); + $user = new ChangeUserPassword( + $newUser . '@' . $config['host'], + $newPassword, + $config['login'] . '@' . $config['host'], + $config['host'], + $form + ); + } else { + fwrite(STDOUT, 'Failed to register user!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + } +} +$client->disconnect(); \ No newline at end of file diff --git a/examples/vcard.php b/examples/vcard.php new file mode 100644 index 0000000..400ab33 --- /dev/null +++ b/examples/vcard.php @@ -0,0 +1,56 @@ +pushHandler(new StreamHandler($fh, Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$login = 'testuser'; +$password = 'test-password'; + + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($login) + ->setPassword($password) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + +$client->connect(); + +$vCard = new VCardUpdate($login); +$vCard->setProperty('NICKNAME', 'iCoolVan 22222') + ->setProperty('FAMILY', 'Ivanov') + ->setProperty('GIVEN', 'Ivan') + ->setProperty('MIDDLE', 'Ivanovich') + ->setProperty('EMAIL', 'info@personal-site.com'); + + +$vCard->setProperty('PHOTO', 'avatar.png'); +$image_hash = $vCard->getImageId(); + +$vCard->setProperty('URL', 'https://personal-site.com'); +try { + $client->send($vCard); + // tell other users that we have update vCard + $presence = new VCardPresence($image_hash); + $client->send($presence); + fwrite(STDOUT, 'vCard was updated.' . PHP_EOL); +} catch (CommandErrorException $e) { + fwrite(STDOUT, 'Failed to update user vCard!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); +} +$client->disconnect(); diff --git a/src/Connection/Socket.php b/src/Connection/Socket.php index 8ce49a7..52f8a53 100644 --- a/src/Connection/Socket.php +++ b/src/Connection/Socket.php @@ -36,11 +36,11 @@ namespace Fabiang\Xmpp\Connection; -use Psr\Log\LogLevel; +use Fabiang\Xmpp\Exception\TimeoutException; +use Fabiang\Xmpp\Options; use Fabiang\Xmpp\Stream\SocketClient; use Fabiang\Xmpp\Util\XML; -use Fabiang\Xmpp\Options; -use Fabiang\Xmpp\Exception\TimeoutException; +use Psr\Log\LogLevel; /** * Connection to a socket stream. @@ -51,11 +51,11 @@ class Socket extends AbstractConnection implements SocketConnectionInterface { const DEFAULT_LENGTH = 4096; - const STREAM_START = <<<'XML' + const STREAM_START = <<<'XML' XML; - const STREAM_END = ''; + const STREAM_END = ''; /** * Socket. @@ -74,7 +74,7 @@ class Socket extends AbstractConnection implements SocketConnectionInterface /** * Constructor set default socket instance if no socket was given. * - * @param StreamSocket $socket Socket instance + * @param SocketClient $socket Socket instance */ public function __construct(SocketClient $socket) { @@ -89,7 +89,7 @@ public function __construct(SocketClient $socket) */ public static function factory(Options $options) { - $socket = new SocketClient($options->getAddress()); + $socket = new SocketClient($options->getAddress(), $options->getVerifyPeer()); $object = new static($socket); $object->setOptions($options); return $object; @@ -189,7 +189,7 @@ public function connect() public function disconnect() { if (true === $this->connected) { - $address = $this->getAddress(); + $address = $this->getAddress(); $this->send(static::STREAM_END); $this->getSocket()->close(); $this->connected = false; diff --git a/src/EventListener/Stream/RequestChangePasswordFrom.php b/src/EventListener/Stream/Command.php similarity index 66% rename from src/EventListener/Stream/RequestChangePasswordFrom.php rename to src/EventListener/Stream/Command.php index 347ddfa..22f1a6f 100644 --- a/src/EventListener/Stream/RequestChangePasswordFrom.php +++ b/src/EventListener/Stream/Command.php @@ -39,7 +39,8 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; -use Fabiang\Xmpp\Exception\Stream\ChangePasswordErrorException; +use Fabiang\Xmpp\Exception\Stream\CommandErrorException; +use Fabiang\Xmpp\Form\Form; use Fabiang\Xmpp\Protocol\User\User; /** @@ -47,7 +48,7 @@ * * @package Xmpp\EventListener */ -class RequestChangePasswordFrom extends AbstractEventListener implements BlockingEventListenerInterface +class Command extends AbstractEventListener implements BlockingEventListenerInterface { /** @@ -71,8 +72,34 @@ public function attachEvents() { $this->getOutputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); + /** + * error events + * @see https://xmpp.org/extensions/xep-0133.html#errors + */ $this->getInputEventManager() ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}conflict', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}feature-not-implemented', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}forbidden', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}not-allowed', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable', array($this, 'error')); + /** + * MUC error events + * @see https://xmpp.org/extensions/xep-0045.html#enter-errorcodes + */ + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}item-not-found', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}registration-required', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}jid-malformed', array($this, 'error')); + + // result $this->getInputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); } @@ -96,10 +123,11 @@ public function query() public function result(XMLEvent $event) { if ($event->isEndTag()) { - /* @var $element \DOMElement */ - $sid = $event->getParameter(0)->getAttribute('sessionid'); - - $this->getOptions()->setSid($sid); + // same events + if (!$this->getOptions()->getForm()) { + $form = new Form($event); + $this->getOptions()->setForm($form); + } $this->blocking = false; } } @@ -114,7 +142,8 @@ public function error(XMLEvent $event) { if (false === $event->isStartTag()) { $this->blocking = false; - throw ChangePasswordErrorException::createFromEvent($event); + /** @var $event \DOMElement */ + throw CommandErrorException::createFromEvent($event); } } diff --git a/src/EventListener/Stream/RequestUserRegisterFrom.php b/src/EventListener/Stream/RequestUserRegisterFrom.php deleted file mode 100644 index d7c1662..0000000 --- a/src/EventListener/Stream/RequestUserRegisterFrom.php +++ /dev/null @@ -1,128 +0,0 @@ - - * @copyright 2014 Fabian Grutschus. All rights reserved. - * @license BSD - * @link http://github.com/fabiang/xmpp - */ - -namespace Fabiang\Xmpp\EventListener\Stream; - -use Fabiang\Xmpp\Event\XMLEvent; -use Fabiang\Xmpp\EventListener\AbstractEventListener; -use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; -use Fabiang\Xmpp\Exception\Stream\RegistrationErrorException; -use Fabiang\Xmpp\Protocol\User\User; - -/** - * Listener - * - * @package Xmpp\EventListener - */ -class RequestUserRegisterFrom extends AbstractEventListener implements BlockingEventListenerInterface -{ - - /** - * Blocking. - * - * @var boolean - */ - protected $blocking = false; - - /** - * user object. - * - * @var User - */ - protected $userObject; - - /** - * {@inheritDoc} - */ - public function attachEvents() - { - $this->getOutputEventManager() - ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); - $this->getInputEventManager() - ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); - } - - /** - * Sending a query request for roster sets listener to blocking mode. - * - * @return void - */ - public function query() - { - $this->blocking = true; - } - - /** - * Result received. - * - * @param \Fabiang\Xmpp\Event\XMLEvent $event - * @return void - */ - public function result(XMLEvent $event) - { - if ($event->isEndTag()) { - /* @var $element \DOMElement */ - $sid = $event->getParameter(0)->getAttribute('sessionid'); - - $this->getOptions()->setSid($sid); - $this->blocking = false; - } - } - - /** - * we have some errors. - * - * @param \Fabiang\Xmpp\Event\XMLEvent $event - * @return void - */ - public function error(XMLEvent $event) - { - if (false === $event->isStartTag()) { - $this->blocking = false; - throw RegistrationErrorException::createFromEvent($event); - } - } - - /** - * {@inheritDoc} - */ - public function isBlocking() - { - return $this->blocking; - } -} \ No newline at end of file diff --git a/src/Exception/Stream/RegistrationErrorException.php b/src/Exception/Stream/CommandErrorException.php similarity index 53% rename from src/Exception/Stream/RegistrationErrorException.php rename to src/Exception/Stream/CommandErrorException.php index e00b955..1270bdb 100644 --- a/src/Exception/Stream/RegistrationErrorException.php +++ b/src/Exception/Stream/CommandErrorException.php @@ -35,11 +35,58 @@ namespace Fabiang\Xmpp\Exception\Stream; +use Fabiang\Xmpp\Event\XMLEvent; + /** - * Class RegistrationErrorException + * Class CommandErrorException * @package Fabiang\Xmpp\Exception\Stream */ -class RegistrationErrorException extends StreamErrorException +class CommandErrorException extends StreamErrorException { + /** + * @see https://xmpp.org/extensions/xep-0086.html#sect-idm139696314152720 + */ + const ERROR_UNDEFINED = 0; + const ERROR_BAD_REQUEST = 400; + const ERROR_CONFLICT = 409; + const ERROR_FEATURE_NOT_IMPLEMENTED = 501; + const ERROR_FORBIDDEN = 403; + const ERROR_GONE = 302; + const ERROR_INTERNAL_SERVER_ERROR = 500; + const ERROR_ITEM_NOT_FOUND = 404; + const ERROR_NOT_ACCEPTABLE = 406; + const ERROR_NOT_ALLOWED = 405; + const ERROR_NOT_AUTHORIZED = 401; + const ERROR_REGISTRATION_REQUIRED = 407; + const ERROR_SERVER_TIMEOUT = 504; + const ERROR_SERVICE_UNAVAILABLE = 503; + + /** + * Create exception from XMLEvent object. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event XMLEvent object + * + * @return static + */ + public static function createFromEvent(XMLEvent $event) + { + /* @var $element \DOMElement */ + list($element) = $event->getParameters(); + + /* @var $first \DOMElement */ + $parent = $element->parentNode; + + if (null !== $parent && XML_ELEMENT_NODE === $parent->nodeType) { + $code = (int)$parent->getAttribute('code'); + $message = 'Stream Error: "' . $element->localName . '"'; + } else { + $code = 0; + $message = 'Generic stream error'; + } + + $exception = new static($message, $code); + $exception->setContent($element->ownerDocument->saveXML($element)); + return $exception; + } } \ No newline at end of file diff --git a/src/Form/AbstractForm.php b/src/Form/AbstractForm.php new file mode 100644 index 0000000..aede105 --- /dev/null +++ b/src/Form/AbstractForm.php @@ -0,0 +1,211 @@ +title; + } + + /** + * get instructions for form filling + * + * @return string + */ + public function getInstructions() + { + return (string)$this->instructions; + } + + /** + * get fields of form + * + * @return array + */ + public function getFieldNames() + { + return array_keys($this->fields); + } + + /** + * set value of a field + * + * @param $fieldName string + * @param $value string + * @return bool + */ + public function setFieldValue($fieldName, $value) + { + if (isset($this->fields[$fieldName])) { + $valueNode = $this->form->ownerDocument->createElement('value', (string)$value); + // remove previous value + while ($this->fields[$fieldName]->hasChildNodes()) { + $this->fields[$fieldName]->removeChild($this->fields[$fieldName]->firstChild); + } + // set new value + $this->fields[$fieldName]->appendChild($valueNode); + return true; + } + return false; + } + + /** + * add multiple value for a field + * + * @param $fieldName string + * @param $value string + * @return bool + */ + public function addFieldValue($fieldName, $value) + { + if (isset($this->fields[$fieldName])) { + $valueNode = $this->form->ownerDocument->createElement('value', (string)$value); + // add new value + $this->fields[$fieldName]->appendChild($valueNode); + return true; + } + return false; + } + + /** + * get field attributes, e.g. type, name, label or required + * + * @param $field + * @return array|null + */ + public function getFieldAttributes($field) + { + if (isset($this->fields[$field])) { + $attributes = []; + if ($type = $this->fields[$field]->getAttribute('type')) { + $attributes['type'] = (string)$type; + } + if ($name = $this->fields[$field]->getAttribute('var')) { + $attributes['name'] = (string)$name; + } + if ($label = $this->fields[$field]->getAttribute('label')) { + $attributes['label'] = (string)$label; + } + if ($required = $this->fields[$field]->getElementsByTagName('required')) { + $attributes['required'] = $required->length > 0 ? true : false; + } + return $attributes; + } + return null; + } + + /** + * converts form to XML string + * + * @return string + */ + public function toString() + { + $this->form->removeAttribute('status'); + $this->form->firstChild->setAttribute('type', 'submit'); + // remove previous values + while ($this->form->firstChild->hasChildNodes()) { + $this->form->firstChild->removeChild($this->form->firstChild->firstChild); + } + // append new values + foreach ($this->fields as $field) { + if ($field->getAttribute('type') && $field->getAttribute('type') != 'hidden') { + $field->removeAttribute('type'); + } + if ($field->getAttribute('label')) { + $field->removeAttribute('label'); + } + $this->form->firstChild->appendChild($field); + } + + return $this->form->ownerDocument->saveXML($this->form); + } + + /** + * parse XMLEvent to the form fields + * + * AbstractForm constructor. + * @param XMLEvent $event + */ + public function __construct(XMLEvent $event) + { + /** @var $event \DOMElement */ + $this->form = $event->getParameter(0); + if ($this->sid = $this->form->getAttribute('sessionid')) { + $titleNode = $this->form->getElementsByTagName('title')->item(0); + if ($titleNode) { + $this->title = $titleNode->nodeValue; + } + unset($titleNode); + $instructionsNode = $this->form->getElementsByTagName('instructions')->item(0); + if ($instructionsNode) { + $this->instructions = $instructionsNode->nodeValue; + } + unset($instructionsNode); + $fieldNodeList = $this->form->getElementsByTagName('field'); + if ($fieldNodeList->length > 0) { + foreach ($fieldNodeList as $field) { + /**@var $field \DOMElement */ + $this->fields[$field->getAttribute('var')] = $field; + } + } else { + $this->fields = []; + } + unset($fieldNodeList); + } + } +} \ No newline at end of file diff --git a/src/Form/Form.php b/src/Form/Form.php new file mode 100644 index 0000000..100fbab --- /dev/null +++ b/src/Form/Form.php @@ -0,0 +1,18 @@ + '\\Fabiang\\Xmpp\\EventListener\\Stream\\Authentication\\DigestMd5', - 'plain' => '\\Fabiang\\Xmpp\\EventListener\\Stream\\Authentication\\Plain' + 'plain' => '\\Fabiang\\Xmpp\\EventListener\\Stream\\Authentication\\Plain' ); /** @@ -192,7 +207,7 @@ public function getAddress() */ public function setAddress($address) { - $this->address = (string) $address; + $this->address = (string)$address; if (false !== ($host = parse_url($address, PHP_URL_HOST))) { $this->setTo($host); } @@ -263,7 +278,7 @@ public function getTo() */ public function setTo($to) { - $this->to = (string) $to; + $this->to = (string)$to; return $this; } @@ -285,7 +300,7 @@ public function getUsername() */ public function setUsername($username) { - $this->username = (string) $username; + $this->username = (string)$username; return $this; } @@ -319,7 +334,7 @@ public function getPassword() */ public function setPassword($password) { - $this->password = (string) $password; + $this->password = (string)$password; return $this; } @@ -341,30 +356,48 @@ public function getJid() */ public function setJid($jid) { - $this->jid = (string) $jid; + $this->jid = (string)$jid; return $this; } /** - * Get sid. + * @return FormInterface + */ + public function getForm() + { + return $this->form; + } + + /** + * set form * - * @return string + * @param FormInterface $form + * @return $this */ - public function getSid() + public function setForm(FormInterface $form) { - return $this->sid; + $this->form = $form; + return $this; } /** - * @param $sid string + * @param $verify bool * @return $this */ - public function setSid($sid) + public function setVerifyPeer($verify) { - $this->sid = (string) $sid; + $this->verifyPeer = (bool)$verify; return $this; } + /** + * @return bool + */ + public function getVerifyPeer() + { + return $this->verifyPeer; + } + /** * Is user authenticated. * @@ -383,7 +416,7 @@ public function isAuthenticated() */ public function setAuthenticated($authenticated) { - $this->authenticated = (bool) $authenticated; + $this->authenticated = (bool)$authenticated; return $this; } @@ -409,6 +442,28 @@ public function setUsers(array $users) return $this; } + /** + * Get room. + * + * @return Protocol\Room\Room + */ + public function getRoom() + { + return $this->room; + } + + /** + * Set room. + * + * @param Room $room + * @return $this + */ + public function setRoom(Room $room) + { + $this->room = $room; + return $this; + } + /** * Get authentication classes. * @@ -448,7 +503,7 @@ public function getTimeout() */ public function setTimeout($timeout) { - $this->timeout = (int) $timeout; + $this->timeout = (int)$timeout; return $this; } } diff --git a/src/Protocol/DefaultImplementation.php b/src/Protocol/DefaultImplementation.php index 57e5349..90db456 100644 --- a/src/Protocol/DefaultImplementation.php +++ b/src/Protocol/DefaultImplementation.php @@ -41,8 +41,7 @@ use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\EventListener\Stream\Authentication; use Fabiang\Xmpp\EventListener\Stream\Bind; -use Fabiang\Xmpp\EventListener\Stream\RequestChangePasswordFrom; -use Fabiang\Xmpp\EventListener\Stream\RequestUserRegisterFrom; +use Fabiang\Xmpp\EventListener\Stream\Command; use Fabiang\Xmpp\EventListener\Stream\Roster as RosterListener; use Fabiang\Xmpp\EventListener\Stream\Session; use Fabiang\Xmpp\EventListener\Stream\StartTls; @@ -84,8 +83,7 @@ public function register() $this->registerListener(new Bind); $this->registerListener(new Session); $this->registerListener(new RosterListener); - $this->registerListener(new RequestUserRegisterFrom); - $this->registerListener(new RequestChangePasswordFrom); + $this->registerListener(new Command); } /** diff --git a/src/Protocol/Presence.php b/src/Protocol/Presence.php index 93cf800..5d60921 100644 --- a/src/Protocol/Presence.php +++ b/src/Protocol/Presence.php @@ -117,7 +117,12 @@ class Presence implements ProtocolImplementationInterface * @var string|null */ protected $to; - + /** + * presence show + * + * @var string + */ + protected $show; /** * Priority. * @@ -133,15 +138,15 @@ class Presence implements ProtocolImplementationInterface protected $nickname; /** - * Constructor. - * - * @param integer $priority - * @param string $to - * @param string $nickname + * Presence constructor. + * @param int $priority + * @param null $to + * @param null $nickname + * @param string $show */ - public function __construct($priority = 1, $to = null, $nickname = null) + public function __construct($priority = 1, $to = null, $nickname = null, $show = null) { - $this->setPriority($priority)->setTo($to)->setNickname($nickname); + $this->setPriority($priority)->setTo($to)->setShow($show)->setNickname($nickname); } /** @@ -154,8 +159,12 @@ public function toString() if (null !== $this->getTo()) { $presence .= ' to="' . XML::quote($this->getTo()) . '/' . XML::quote($this->getNickname()) . '"'; } + $presence .= '>'; - return $presence . '>' . $this->getPriority() . ''; + if ('' !== $this->getShow()) { + $presence .= '' . $this->getShow() . ''; + } + return $presence . '' . $this->getPriority() . ''; } /** @@ -176,14 +185,36 @@ public function getNickname() */ public function setNickname($nickname) { - $this->nickname = (string) $nickname; + $this->nickname = (string)$nickname; + return $this; + } + + /** + * Get show. + * + * @return string + */ + public function getShow() + { + return $this->show; + } + + /** + * Set show. + * + * @param string $show + * @return $this + */ + public function setShow($show) + { + $this->show = (string)$show; return $this; } /** * Get to. * - * @return string¦null + * @return string|null */ public function getTo() { @@ -220,7 +251,7 @@ public function getPriority() */ public function setPriority($priority) { - $this->priority = (int) $priority; + $this->priority = (int)$priority; return $this; } } diff --git a/src/Protocol/Room/RequestRoomConfigForm.php b/src/Protocol/Room/RequestRoomConfigForm.php new file mode 100644 index 0000000..6753ce5 --- /dev/null +++ b/src/Protocol/Room/RequestRoomConfigForm.php @@ -0,0 +1,156 @@ +setFrom($from) + ->setTo($to) + ->setRoom($room); + } + + /** + * {@inheritDoc} + */ + public function toString() + { + return XML::quoteMessage("" . + "" . + "", + $this->getFrom(), + Xml::generateId(), + $this->getFullRoomName() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get server address. + * + * @return string + */ + public function getRoom() + { + return $this->room; + } + + /** + * Get server address. + * + * @return string + */ + public function getFullRoomName() + { + return $this->room . '@' . $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $room string + * @return $this + */ + public function setRoom($room) + { + $this->room = (string)$room; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/ChangeUserPassword.php b/src/Protocol/User/ChangeUserPassword.php index ebf06f4..7b9f750 100644 --- a/src/Protocol/User/ChangeUserPassword.php +++ b/src/Protocol/User/ChangeUserPassword.php @@ -6,6 +6,7 @@ namespace Fabiang\Xmpp\Protocol\User; +use Fabiang\Xmpp\Form\FormInterface; use Fabiang\Xmpp\Protocol\ProtocolImplementationInterface; use Fabiang\Xmpp\Util\XML; @@ -46,27 +47,28 @@ class ChangeUserPassword implements ProtocolImplementationInterface */ protected $password; /** - * SID of registration form - * - * @var string + * @var FormInterface */ - protected $sid; + protected $form; /** * ChangeUserPassword constructor. * @param $userJid string * @param $password string - * @param $sid string - SID of request form @see ChangeUserPassword - * @param $from string - admin user JID - * @param null|string $to + * @param $from string + * @param $to string + * @param FormInterface $form */ - public function __construct($userJid, $password, $sid, $from, $to = null) + public function __construct($userJid, $password, $from, $to, FormInterface $form) { $this->setFrom($from) ->setTo($to) ->setUserJID($userJid) ->setPassword($password) - ->setSID($sid); + ->setForm($form); + + $this->form->setFieldValue('accountjid', $this->getUserJID()); + $this->form->setFieldValue('password', $this->getPassword()); } /** @@ -76,28 +78,11 @@ public function toString() { return XML::quoteMessage( "" . - "" . - "" . - "" . - "http://jabber.org/protocol/admin" . - "" . - "" . - "%s" . - "" . - "" . - "%s" . - "" . - "" . - "" . + $this->form->toString() . "", $this->getFrom(), XML::generateId(), - $this->getTo(), - $this->getSID(), - $this->getUserJID(), - $this->getPassword() + $this->getTo() ); } @@ -146,23 +131,15 @@ public function setFrom($from) } /** - * @param $sid + * @param FormInterface $form * @return $this */ - public function setSID($sid) + private function setForm(FormInterface $form) { - $this->sid = (string)$sid; + $this->form = $form; return $this; } - /** - * @return string - */ - public function getSID() - { - return $this->sid; - } - /** * Get UserJID. * diff --git a/src/Protocol/User/RegisterUser.php b/src/Protocol/User/RegisterUser.php index 02f95c5..ee2119a 100644 --- a/src/Protocol/User/RegisterUser.php +++ b/src/Protocol/User/RegisterUser.php @@ -9,6 +9,7 @@ namespace Fabiang\Xmpp\Protocol\User; +use Fabiang\Xmpp\Form\FormInterface; use Fabiang\Xmpp\Protocol\ProtocolImplementationInterface; use Fabiang\Xmpp\Util\XML; @@ -36,6 +37,11 @@ class RegisterUser implements ProtocolImplementationInterface */ protected $to; + /** + * @var FormInterface + */ + protected $form; + /** * new user account JID * @@ -48,28 +54,48 @@ class RegisterUser implements ProtocolImplementationInterface * @var string */ protected $password; + + /** + * user E-mail + * + * @var string + */ + protected $email; + /** - * SID of registration form + * user surname * * @var string */ - protected $sid; + protected $surname; + + /** + * user given name + * + * @var string + */ + protected $givenName; + /** * RegisterUser constructor. * @param $userJid string * @param $password string - * @param $sid string - SID of request form RequestUserRegisterForm - * @param $from string - admin user JID - * @param null|string $to + * @param $from string - admin account + * @param $to string + * @param FormInterface $form */ - public function __construct($userJid, $password, $sid, $from, $to = null) + public function __construct($userJid, $password, $from, $to, FormInterface $form) { $this->setFrom($from) ->setTo($to) + ->setForm($form) ->setUserJID($userJid) - ->setPassword($password) - ->setSID($sid); + ->setPassword($password); + + $this->form->setFieldValue('accountjid', $this->getUserJID()); + $this->form->setFieldValue('password', $this->getPassword()); + $this->form->setFieldValue('password-verify', $this->getPassword()); } /** @@ -79,30 +105,11 @@ public function toString() { return XML::quoteMessage( "" . - "" . - "" . - "" . - "http://jabber.org/protocol/admin" . - "" . - "" . - "%s" . - "" . - "" . - "%s" . - "" . - "" . - "%s" . - "" . - "" . - "" . + $this->form->toString() . "", $this->getFrom(), XML::generateId(), - $this->getTo(), - $this->getSID(), - $this->getUserJID(), - $this->getPassword(), - $this->getPassword() + $this->getTo() ); } @@ -151,23 +158,15 @@ public function setFrom($from) } /** - * @param $sid + * @param FormInterface $form * @return $this */ - public function setSID($sid) + private function setForm(FormInterface $form) { - $this->sid = (string)$sid; + $this->form = $form; return $this; } - /** - * @return string - */ - public function getSID() - { - return $this->sid; - } - /** * Get UserJID. * diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php index 9e535d2..0d26c8e 100644 --- a/src/Protocol/User/VCardUpdate.php +++ b/src/Protocol/User/VCardUpdate.php @@ -52,6 +52,7 @@ class VCardUpdate implements ProtocolImplementationInterface 'FAMILY', 'MIDDLE', 'PHOTO', + 'EMAIL', 'JABBERID', 'NICKNAME', 'URL', @@ -104,12 +105,12 @@ public function __construct($from) */ public function toString() { - return XML::quoteMessage("" . - "%s" . + return XML::quoteMessage("" . + "" . + $this->composeVCard() . + "" . "", - $this->getFrom(), - XML::generateId(), - $this->composeVCard() + XML::generateId() ); } @@ -122,6 +123,7 @@ protected function composeVCard() $xmlVCard = ''; if (!empty($this->vCard)) { foreach ($this->vCard as $attrName => $attrValue) { + // DOMNode maybe? $xmlVCard .= '<' . $attrName . '>'; if (is_array($attrValue)) { foreach ($attrValue as $subAttrName => $subAttrValue) { @@ -187,12 +189,15 @@ public function setProperty($property, $value) case 'DESC': $this->vCard[$property] = strip_tags($value); break; + case 'EMAIL': + $this->vCard[$property]['USERID'] = strip_tags($value); + break; case 'PHOTO': // value must be path to image $this->setImage($value); $this->vCard['PHOTO'] = array('TYPE' => $this->getImageMime(), 'BINVAL' => $this->getImageBase64Data()); // free some memory - $this->imagePath = null; + $this->imageData = null; break; } } @@ -243,14 +248,17 @@ public function getImageId() /** * get converted to Base64 image data * + * @see https://xmpp.org/extensions/xep-0153.html#bizrules-image * @return string */ protected function getImageBase64Data() { - return base64_encode($this->imageData); + return "\n" . wordwrap(XML::base64Encode($this->imageData), 75, "\n", true) . "\n"; } /** + * todo: check image size: 32-96px, file size: 8096 b, + * extensions: image/png;image/gif;image/jpeg * get image mime type * @return string */ diff --git a/src/Stream/SocketClient.php b/src/Stream/SocketClient.php index afba977..dbcb756 100644 --- a/src/Stream/SocketClient.php +++ b/src/Stream/SocketClient.php @@ -63,21 +63,30 @@ class SocketClient */ protected $address; + /** + * verify peer on connect + * + * @var bool + */ + protected $verifyPeer = true; + /** * Constructor takes address as argument. * * @param string $address + * @param bool $verifyPeer */ - public function __construct($address) + public function __construct($address, $verifyPeer = true) { $this->address = $address; + $this->verifyPeer = $verifyPeer; } /** * Connect. * - * @param integer $timeout Timeout for connection - * @param boolean $persistent Persitent connection + * @param integer $timeout Timeout for connection + * @param boolean $persistent Persistent connection * @return void */ public function connect($timeout = 30, $persistent = false) @@ -88,9 +97,24 @@ public function connect($timeout = 30, $persistent = false) $flags = STREAM_CLIENT_CONNECT; } + $context = null; + if ($this->verifyPeer === false) { + $context = stream_context_create(array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ) + ); + } + // call stream_socket_client with custom error handler enabled $handler = new ErrorHandler( - function ($address, $timeout, $flags) { + function ($address, $timeout, $flags) use ($context) { + if ($context) { + return stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context); + } return stream_socket_client($address, $errno, $errstr, $timeout, $flags); }, $this->address, @@ -106,9 +130,9 @@ function ($address, $timeout, $flags) { /** * Reconnect and optionally use different address. * - * @param string $address + * @param string $address * @param integer $timeout - * @param bool $persistent + * @param bool $persistent */ public function reconnect($address = null, $timeout = 30, $persistent = false) { @@ -139,7 +163,7 @@ public function close() */ public function setBlocking($flag = true) { - stream_set_blocking($this->resource, (int) $flag); + stream_set_blocking($this->resource, (int)$flag); return $this; } @@ -157,7 +181,7 @@ public function read($length = self::BUFFER_LENGTH) /** * Write to stream. * - * @param string $string String + * @param string $string String * @param integer $length Limit * @return void */ @@ -173,7 +197,7 @@ public function write($string, $length = null) /** * Enable/disable cryptography on stream. * - * @param boolean $enable Flag + * @param boolean $enable Flag * @param integer $cryptoType One of the STREAM_CRYPTO_METHOD_* constants. * @return void * @throws InvalidArgumentException diff --git a/src/Stream/XMLStream.php b/src/Stream/XMLStream.php index 72e26e4..d35389f 100644 --- a/src/Stream/XMLStream.php +++ b/src/Stream/XMLStream.php @@ -36,9 +36,9 @@ namespace Fabiang\Xmpp\Stream; +use Fabiang\Xmpp\Event\EventManager; use Fabiang\Xmpp\Event\EventManagerAwareInterface; use Fabiang\Xmpp\Event\EventManagerInterface; -use Fabiang\Xmpp\Event\EventManager; use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\Event\XMLEventInterface; use Fabiang\Xmpp\Exception\XMLParserException; @@ -123,7 +123,9 @@ class XMLStream implements EventManagerAwareInterface protected $eventCache = array(); /** - * Constructor. + * XMLStream constructor. + * @param string $encoding + * @param XMLEventInterface|null $eventObject */ public function __construct($encoding = 'UTF-8', XMLEventInterface $eventObject = null) { @@ -176,6 +178,7 @@ public function parse($source) * * Method resets the parser instance if createAttributeNodes($attribs); @@ -234,7 +237,7 @@ protected function startXml() if (null !== $prefix) { $namespaceElement = $this->namespacePrefixes[$prefix]; } else { - $namespaceAttrib = true; + $namespaceAttrib = true; $namespaceElement = $namespaceURI; } @@ -279,8 +282,8 @@ protected function createAttributeNodes(array $attribs) $this->namespacePrefixes[$prefix] = $value; } else { - $attribute = $this->document->createAttribute($name); - $attribute->value = $value; + $attribute = $this->document->createAttribute($name); + $attribute->value = $value; $attributesNodes[] = $attribute; } } @@ -307,7 +310,7 @@ protected function endXml() $localName = $element->localName; - // Frist: try to get the namespace from element. + // First: try to get the namespace from element. $namespaceURI = $element->namespaceURI; // Second: loop over namespaces till namespace is not null @@ -323,7 +326,7 @@ protected function endXml() * Data found. * * @param resource $parser XML parser - * @param string $data Element data + * @param string $data Element data * @return void */ protected function dataXml() @@ -338,9 +341,9 @@ protected function dataXml() /** * Add event to cache. * - * @param string $event + * @param string $event * @param boolean $startTag - * @param array $params + * @param array $params * @return void */ protected function cacheEvent($event, $startTag, $params) @@ -379,12 +382,12 @@ public function reset() xml_set_element_handler($parser, 'startXml', 'endXml'); xml_set_character_data_handler($parser, 'dataXml'); - $this->parser = $parser; - $this->depth = 0; - $this->document = new \DOMDocument('1.0', $this->encoding); - $this->namespaces = array(); + $this->parser = $parser; + $this->depth = 0; + $this->document = new \DOMDocument('1.0', $this->encoding); + $this->namespaces = array(); $this->namespacePrefixes = array(); - $this->elements = array(); + $this->elements = array(); } /** diff --git a/tests/src/EventListener/Stream/BindTest.php b/tests/src/EventListener/Stream/BindTest.php index de90ef4..99f0c1a 100644 --- a/tests/src/EventListener/Stream/BindTest.php +++ b/tests/src/EventListener/Stream/BindTest.php @@ -37,8 +37,8 @@ namespace Fabiang\Xmpp\EventListener\Stream; use Fabiang\Xmpp\Connection\Test; -use Fabiang\Xmpp\Options; use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\Options; /** * Generated by PHPUnit_SkeletonGenerator 1.2.1 on 2014-01-17 at 15:11:34. @@ -131,7 +131,7 @@ public function testBind() $this->assertTrue($this->object->isBlocking()); $buffer = $this->connection->getBuffer(); $this->assertRegExp( - '##', + '##', $buffer[1] ); } diff --git a/tests/src/Protocol/User/RequestChangePasswordFormTest.php b/tests/src/Protocol/User/RequestChangePasswordFormTest.php new file mode 100644 index 0000000..5e903da --- /dev/null +++ b/tests/src/Protocol/User/RequestChangePasswordFormTest.php @@ -0,0 +1,95 @@ +object = new RequestChangePasswordForm('admin@xmpp.server.org', 'xmpp.server.org'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + } + + /** + * Test turning object into string. + * + * @covers ::toString + * @uses Fabiang\Xmpp\Protocol\User\Req + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::__construct + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::getType + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::setType + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::getTo + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::setTo + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::getUser\RequestChangePasswordForm + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::setUser\RequestChangePasswordForm + * @uses Fabiang\Xmpp\Util\XML::generateId + * @uses Fabiang\Xmpp\Util\XML::quote + * @uses Fabiang\Xmpp\Util\XML::quoteMessage + * @return void + */ + public function testToString() + { + $this->object->setTo('xmpp.server.org')->setFrom('admin@xmpp.server.org'); + $this->assertRegExp( + "#" . + "" . + "#", + $this->object->toString() + ); + } + + /** + * Test constructor. + * + * @covers ::__construct + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::getFrom + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::setFrom + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::getTo + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::setTo + * @return void + */ + public function testConstructor() + { + $object = new RequestChangePasswordForm('admin@xmpp.server.org', 'xmpp.server.org'); + $this->assertSame('admin@xmpp.server.org', $object->getFrom()); + $this->assertSame('xmpp.server.org', $object->getTo()); + } + + /** + * Test setters and getters. + * + * @covers ::getFrom + * @covers ::setFrom + * @covers ::getTo + * @covers ::setTo + * @uses Fabiang\Xmpp\Protocol\User\RequestChangePasswordForm::__construct + * @return void + */ + public function testSettersAndGetters() + { + $this->assertSame('admin@xmpp.server.org', $this->object->getFrom()); + $this->assertSame('user@xmpp.server.org', $this->object->setFrom('user@xmpp.server.org')->getFrom()); + $this->assertSame('xmpp.server.org', $this->object->getTo()); + $this->assertSame('xmpp.other.server.org', $this->object->setTo('xmpp.other.server.org')->getTo()); + } +} diff --git a/tests/src/Protocol/User/RequestUserRegisterFormTest.php b/tests/src/Protocol/User/RequestUserRegisterFormTest.php new file mode 100644 index 0000000..fcb134e --- /dev/null +++ b/tests/src/Protocol/User/RequestUserRegisterFormTest.php @@ -0,0 +1,92 @@ +object = new RequestUserRegisterForm('admin@xmpp.server.org', 'xmpp.server.org'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + } + + /** + * Test turning object into string. + * + * @covers ::toString + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::__construct + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::getType + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::setType + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::getTo + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::setTo + * @uses Fabiang\Xmpp\Util\XML::generateId + * @uses Fabiang\Xmpp\Util\XML::quote + * @uses Fabiang\Xmpp\Util\XML::quoteMessage + * @return void + */ + public function testToString() + { + $this->object->setTo('xmpp.server.org')->setFrom('admin@xmpp.server.org'); + $this->assertRegExp( + "#" . + "" . + "#", + $this->object->toString() + ); + } + + /** + * Test constructor. + * + * @covers ::__construct + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::getFrom + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::setFrom + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::getTo + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::setTo + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::getMessage + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::setMessage + * @return void + */ + public function testConstructor() + { + $object = new RequestUserRegisterForm('admin@xmpp.server.org', 'xmpp.server.org'); + $this->assertSame('admin@xmpp.server.org', $object->getFrom()); + $this->assertSame('xmpp.server.org', $object->getTo()); + } + + /** + * Test setters and getters. + * + * @covers ::getFrom + * @covers ::setFrom + * @covers ::getTo + * @covers ::setTo + * @uses Fabiang\Xmpp\Protocol\User\RequestUserRegisterForm::__construct + * @return void + */ + public function testSettersAndGetters() + { + $this->assertSame('admin@xmpp.server.org', $this->object->getFrom()); + $this->assertSame('user@xmpp.server.org', $this->object->setFrom('user@xmpp.server.org')->getFrom()); + $this->assertSame('xmpp.server.org', $this->object->getTo()); + $this->assertSame('xmpp.other.server.org', $this->object->setTo('xmpp.other.server.org')->getTo()); + } +} From 376683c4dc70c6b5a19fd0a17477ec01ab7d74d4 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Thu, 20 Apr 2017 22:56:18 +0500 Subject: [PATCH 08/16] vCard avatar fixed Avatar RoomPresence RoomConfig --- .gitignore | 1 + examples/avatar.php | 57 +++++ examples/config.inc.php | 7 +- examples/example.php | 2 +- examples/register.php | 37 +-- examples/room.php | 122 ++++++++++ examples/vcard.php | 11 +- src/Connection/AbstractConnection.php | 24 +- src/Connection/ConnectionInterface.php | 22 +- src/Connection/Socket.php | 1 + .../AuthenticationInterface.php | 7 + .../Stream/Authentication/DigestMd5.php | 21 +- .../Stream/Authentication/Plain.php | 8 + src/EventListener/Stream/Avatar.php | 132 ++++++++++ src/EventListener/Stream/Command.php | 56 +---- src/EventListener/Stream/RoomOwner.php | 137 +++++++++++ src/EventListener/Stream/RoomPresence.php | 141 +++++++++++ src/EventListener/Stream/Stanzas.php | 105 ++++++++ .../UnBlockingEventListenerInterface.php} | 18 +- ...xception.php => StanzasErrorException.php} | 6 +- src/Form/AbstractForm.php | 81 +++---- src/Form/Form.php | 41 ++++ src/Form/FormInterface.php | 10 +- src/Form/RoomForm.php | 53 +++++ src/Options.php | 39 ++- src/Protocol/DefaultImplementation.php | 8 + src/Protocol/Room/DirectInvite.php | 182 ++++++++++++++ src/Protocol/Room/Room.php | 191 +++++++++++++++ src/Protocol/Room/RoomConfig.php | 146 ++++++++++++ src/Protocol/Room/RoomPresence.php | 166 +++++++++++++ src/Protocol/User/Avatar.php | 194 +++++++++++++++ src/Protocol/User/AvatarMetadata.php | 225 ++++++++++++++++++ src/Protocol/User/ChangeUserPassword.php | 5 +- src/Protocol/User/RegisterUser.php | 28 +-- src/Protocol/User/VCardPresence.php | 1 - src/Protocol/User/VCardUpdate.php | 18 +- 36 files changed, 2116 insertions(+), 187 deletions(-) create mode 100644 examples/avatar.php create mode 100644 examples/room.php create mode 100644 src/EventListener/Stream/Avatar.php create mode 100644 src/EventListener/Stream/RoomOwner.php create mode 100644 src/EventListener/Stream/RoomPresence.php create mode 100644 src/EventListener/Stream/Stanzas.php rename src/{Exception/Stream/ChangePasswordErrorException.php => EventListener/UnBlockingEventListenerInterface.php} (87%) rename src/Exception/Stream/{CommandErrorException.php => StanzasErrorException.php} (95%) create mode 100644 src/Form/RoomForm.php create mode 100644 src/Protocol/Room/DirectInvite.php create mode 100644 src/Protocol/Room/Room.php create mode 100644 src/Protocol/Room/RoomConfig.php create mode 100644 src/Protocol/Room/RoomPresence.php create mode 100644 src/Protocol/User/Avatar.php create mode 100644 src/Protocol/User/AvatarMetadata.php diff --git a/.gitignore b/.gitignore index 908ecac..874a4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /nbproject/ composer.lock composer.phar +/.idea diff --git a/examples/avatar.php b/examples/avatar.php new file mode 100644 index 0000000..6c905c3 --- /dev/null +++ b/examples/avatar.php @@ -0,0 +1,57 @@ +pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$login = $config['login'];//'testuser'; +$password = '123456'; + + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($login) + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + +$client->connect(); + +$imagePath = 'avatar.png'; + +$avatar = new Avatar( + $login . '@' . $config['host'], + $imagePath +); + +$url = ''; +//$url = 'https://avatar.personal-site.com/64x64/testuser.png'; + + +try { + $client->send($avatar); + // update avatar metadata + $meta = new AvatarMetadata( + $login . '@' . $config['host'], + $imagePath, + $url + ); + $client->send($meta); + fwrite(STDOUT, 'Avatar was updated.' . PHP_EOL); +} catch (Exception $e) { + fwrite(STDOUT, 'Failed to update user avatar!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); +} +$client->disconnect(); diff --git a/examples/config.inc.php b/examples/config.inc.php index 197e22a..656df92 100644 --- a/examples/config.inc.php +++ b/examples/config.inc.php @@ -1,10 +1,11 @@ 'xmpp.nko.home', + 'host' => 'xmpp.site.com', + 'conference' => 'conference.xmpp.site.com', 'port' => 5222, 'connectionType' => 'tcp', - 'verifyPeer' => false, + 'verifyPeer' => true, 'login' => 'admin', - 'password' => 'pass1view', + 'password' => '123456', ]; \ No newline at end of file diff --git a/examples/example.php b/examples/example.php index ed5c21c..2f839da 100644 --- a/examples/example.php +++ b/examples/example.php @@ -13,7 +13,7 @@ use Monolog\Logger; $logger = new Logger('xmpp'); -$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG)); +$logger->pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); $address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; diff --git a/examples/register.php b/examples/register.php index 9fe5203..bf79e36 100644 --- a/examples/register.php +++ b/examples/register.php @@ -4,7 +4,7 @@ error_reporting(-1); use Fabiang\Xmpp\Client; -use Fabiang\Xmpp\Exception\Stream\CommandErrorException; +use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; use Fabiang\Xmpp\Options; use Fabiang\Xmpp\Protocol\User\ChangeUserPassword; use Fabiang\Xmpp\Protocol\User\RegisterUser; @@ -14,8 +14,7 @@ use Monolog\Logger; $logger = new Logger('xmpp'); -$fh = fopen('xmpp.log', 'a+'); -$logger->pushHandler(new StreamHandler($fh, Logger::DEBUG)); +$logger->pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); $address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; @@ -44,29 +43,35 @@ ); try { - print $user->toString() . PHP_EOL; $client->send($user); - print 'user sent' . PHP_EOL; -} catch (CommandErrorException $e) { + print 'User is registered' . PHP_EOL; +} catch (StanzasErrorException $e) { /** * @see https://xmpp.org/extensions/xep-0086.html#sect-idm139696314152720 */ - if ($e->getCode() == CommandErrorException::ERROR_CONFLICT) { // conflict + if ($e->getCode() == StanzasErrorException::ERROR_CONFLICT) { // conflict fwrite(STDOUT, 'User already exists. Try to change password' . PHP_EOL); $request = new RequestChangePasswordForm( $config['login'] . '@' . $config['host'], $config['host'] ); - $client->send($request); - $form = $client->getOptions()->getForm(); - $user = new ChangeUserPassword( - $newUser . '@' . $config['host'], - $newPassword, - $config['login'] . '@' . $config['host'], - $config['host'], - $form - ); + try { + $client->send($request); + + $form = $client->getOptions()->getForm(); + $user = new ChangeUserPassword( + $newUser . '@' . $config['host'], + $newPassword, + $config['login'] . '@' . $config['host'], + $config['host'], + $form + ); + } catch (StanzasErrorException $e) { + fwrite(STDOUT, 'Failed to change password of user!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + } + } else { fwrite(STDOUT, 'Failed to register user!' . PHP_EOL); fwrite(STDOUT, $e->getMessage() . PHP_EOL); diff --git a/examples/room.php b/examples/room.php new file mode 100644 index 0000000..2f37662 --- /dev/null +++ b/examples/room.php @@ -0,0 +1,122 @@ +pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$room = 'new-room'; +$password = ''; + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($config['login']) + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + +$client->connect(); +try { + $request = new RoomPresence( + $config['login'] . '@' . $config['host'], + $config['conference'], + $room + ); + + $client->send($request); + +} catch (StanzasErrorException $e) { + if ($e->getCode() != StanzasErrorException::ERROR_CONFLICT) { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; + } else { + fwrite(STDOUT, 'Room is already exists.' . PHP_EOL); + } +} + +if ($client->getOptions()->getRoom()->isJustCreated()) { + fwrite(STDOUT, 'Room presence has been created.' . PHP_EOL); +} + +if (!$client->getOptions()->getRoom()->isOwner()) { + fwrite(STDOUT, 'You are not owner of this room, so forbidden for configuring it.' . PHP_EOL); + exit; +} + + +try { + $requestForm = new RequestRoomConfigForm( + $config['login'] . '@' . $config['host'], + $config['conference'], + $room + ); + $client->send($requestForm); + $form = $client->getOptions()->getForm(); + + /** + * @see https://xmpp.org/extensions/xep-0045.html#createroom-reserved + */ + $form->setFieldValue('muc#roomconfig_roomname', 'New cool room'); + $form->setFieldValue('muc#roomconfig_roomdesc', 'Some description...'); + // no public logging. before turn this option on you must check that logging is enabled + $form->setFieldValue('muc#roomconfig_enablelogging', Room::CONFIG_NO); + // only owner can change name of this room + $form->setFieldValue('muc#roomconfig_changesubject', Room::CONFIG_NO); + // members can invite other users? + $form->setFieldValue('muc#roomconfig_allowinvites', Room::CONFIG_NO); + // allow private messages in this room + //$form->setFieldValue('muc#roomconfig_allowpm', 'anyone'); + // max users limit. + $options = $form->getFieldOptions('muc#roomconfig_maxusers'); + $max_users = empty($options) ? 100 : end($options); + $form->setFieldValue('muc#roomconfig_maxusers', $max_users); + + + // hidden room + $form->setFieldValue('muc#roomconfig_publicroom', Room::CONFIG_NO); + + $form->setFieldValue('muc#roomconfig_persistentroom', Room::CONFIG_YES); + $form->setFieldValue('muc#roomconfig_moderatedroom', Room::CONFIG_NO); + // Only invited users can become members + $form->setFieldValue('muc#roomconfig_membersonly', Room::CONFIG_YES); + // password protected + $form->setFieldValue('muc#roomconfig_passwordprotectedroom', Room::CONFIG_NO); + //$form->setFieldValue('muc#roomconfig_roomsecret', ''); + + // Who May Discover Real JIDs? + $form->setFieldValue('muc#roomconfig_whois', 'anyone'); + // how many message keeps in history + $form->setFieldValue('muc#maxhistoryfetch', 100); + + try { + $roomConfig = new RoomConfig($config['login'] . '@' . $config['host'], + $config['conference'], + $room, + $form); + $client->send($roomConfig); + + fwrite(STDOUT, 'Room success configured' . PHP_EOL); + } catch (StanzasErrorException $e) { + fwrite(STDOUT, 'Failed to configure room!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + } + +} catch (StanzasErrorException $e) { + fwrite(STDOUT, 'Failed to create room!' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); +} +$client->disconnect(); \ No newline at end of file diff --git a/examples/vcard.php b/examples/vcard.php index 400ab33..078d9cd 100644 --- a/examples/vcard.php +++ b/examples/vcard.php @@ -4,7 +4,7 @@ error_reporting(-1); use Fabiang\Xmpp\Client; -use Fabiang\Xmpp\Exception\Stream\CommandErrorException; +use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; use Fabiang\Xmpp\Options; use Fabiang\Xmpp\Protocol\User\VCardPresence; use Fabiang\Xmpp\Protocol\User\VCardUpdate; @@ -12,13 +12,12 @@ use Monolog\Logger; $logger = new Logger('xmpp'); -$fh = fopen('xmpp.log', 'a+'); -$logger->pushHandler(new StreamHandler($fh, Logger::DEBUG)); +$logger->pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); $address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; $login = 'testuser'; -$password = 'test-password'; +$password = '123456'; $options = new Options($address); @@ -32,7 +31,7 @@ $client->connect(); $vCard = new VCardUpdate($login); -$vCard->setProperty('NICKNAME', 'iCoolVan 22222') +$vCard->setProperty('NICKNAME', 'iCoolVan') ->setProperty('FAMILY', 'Ivanov') ->setProperty('GIVEN', 'Ivan') ->setProperty('MIDDLE', 'Ivanovich') @@ -49,7 +48,7 @@ $presence = new VCardPresence($image_hash); $client->send($presence); fwrite(STDOUT, 'vCard was updated.' . PHP_EOL); -} catch (CommandErrorException $e) { +} catch (StanzasErrorException $e) { fwrite(STDOUT, 'Failed to update user vCard!' . PHP_EOL); fwrite(STDOUT, $e->getMessage() . PHP_EOL); } diff --git a/src/Connection/AbstractConnection.php b/src/Connection/AbstractConnection.php index 30f79c4..2db24ee 100644 --- a/src/Connection/AbstractConnection.php +++ b/src/Connection/AbstractConnection.php @@ -36,13 +36,13 @@ namespace Fabiang\Xmpp\Connection; -use Fabiang\Xmpp\Stream\XMLStream; -use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\Event\EventManager; use Fabiang\Xmpp\Event\EventManagerInterface; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; -use Fabiang\Xmpp\Options; +use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\Exception\TimeoutException; +use Fabiang\Xmpp\Options; +use Fabiang\Xmpp\Stream\XMLStream; use Psr\Log\LogLevel; /** @@ -82,7 +82,7 @@ abstract class AbstractConnection implements ConnectionInterface /** * Event listeners. * - * @var EventListenerInterface[] + * @var BlockingEventListenerInterface[] */ protected $listeners = array(); @@ -187,7 +187,7 @@ public function isReady() */ public function setReady($flag) { - $this->ready = (bool) $flag; + $this->ready = (bool)$flag; return $this; } @@ -226,7 +226,7 @@ public function setEventManager(EventManagerInterface $events) /** * Get listeners. * - * @return EventListenerInterface + * @return BlockingEventListenerInterface[] */ public function getListeners() { @@ -253,8 +253,8 @@ public function setOptions(Options $options) /** * Call logging event. * - * @param string $message Log message - * @param integer $level Log level + * @param string $message Log message + * @param integer $level Log level * @return void */ protected function log($message, $level = LogLevel::DEBUG) @@ -285,6 +285,14 @@ protected function checkBlockingListeners() return $blocking; } + /** + * @return BlockingEventListenerInterface + */ + public function getLastBlockingListener() + { + return $this->lastBlockingListener; + } + /** * Check for timeout. * diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index b9f6fc8..2d4d2c9 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -36,10 +36,11 @@ namespace Fabiang\Xmpp\Connection; -use Fabiang\Xmpp\Stream\XMLStream; use Fabiang\Xmpp\Event\EventManagerAwareInterface; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\OptionsAwareInterface; +use Fabiang\Xmpp\Stream\XMLStream; /** * Connections must implement this interface. @@ -61,10 +62,10 @@ public function connect(); * @return void */ public function disconnect(); - + /** * Set stream is ready. - * + * * @param boolean $flag Flag * @return $this */ @@ -72,7 +73,7 @@ public function setReady($flag); /** * Is stream ready. - * + * * @return boolean */ public function isReady(); @@ -83,7 +84,7 @@ public function isReady(); * @return boolean */ public function isConnected(); - + /** * Receive data. * @@ -128,10 +129,10 @@ public function setOutputStream(XMLStream $outputStream); * @return $this */ public function setInputStream(XMLStream $inputStream); - + /** * Reset streams. - * + * * @return void */ public function resetStreams(); @@ -143,4 +144,11 @@ public function resetStreams(); * @return $this */ public function addListener(EventListenerInterface $eventListener); + + /** + * get last blocking listener + * + * @return BlockingEventListenerInterface + */ + public function getLastBlockingListener(); } diff --git a/src/Connection/Socket.php b/src/Connection/Socket.php index 52f8a53..4ce0a11 100644 --- a/src/Connection/Socket.php +++ b/src/Connection/Socket.php @@ -162,6 +162,7 @@ public function send($buffer) $this->getOutputStream()->parse($buffer); while ($this->checkBlockingListeners()) { + usleep(100); $this->receive(); } } diff --git a/src/EventListener/Stream/Authentication/AuthenticationInterface.php b/src/EventListener/Stream/Authentication/AuthenticationInterface.php index 903c763..d763c4e 100644 --- a/src/EventListener/Stream/Authentication/AuthenticationInterface.php +++ b/src/EventListener/Stream/Authentication/AuthenticationInterface.php @@ -54,4 +54,11 @@ interface AuthenticationInterface extends EventListenerInterface * @return void */ public function authenticate($username, $password); + + /** + * get current class name + * + * @return string + */ + public static function className(); } diff --git a/src/EventListener/Stream/Authentication/DigestMd5.php b/src/EventListener/Stream/Authentication/DigestMd5.php index 49b43fd..58f3c94 100644 --- a/src/EventListener/Stream/Authentication/DigestMd5.php +++ b/src/EventListener/Stream/Authentication/DigestMd5.php @@ -36,10 +36,10 @@ namespace Fabiang\Xmpp\EventListener\Stream\Authentication; -use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\Event\XMLEvent; -use Fabiang\Xmpp\Util\XML; +use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\Exception\Stream\AuthenticationErrorException; +use Fabiang\Xmpp\Util\XML; /** * Handler for "digest md5" authentication mechanism. @@ -68,6 +68,14 @@ class DigestMd5 extends AbstractEventListener implements AuthenticationInterface */ protected $password; + /** + * @return string + */ + public static function className() + { + return get_class(); + } + /** * {@inheritDoc} */ @@ -113,7 +121,7 @@ public function challenge(XMLEvent $event) list($element) = $event->getParameters(); $challenge = XML::base64Decode($element->nodeValue); - $values = $this->parseCallenge($challenge); + $values = $this->parseCallenge($challenge); if (isset($values['nonce'])) { $send = '' @@ -132,12 +140,13 @@ public function challenge(XMLEvent $event) * Generate response data. * * @param array $values + * @return string */ protected function response($values) { $values['cnonce'] = uniqid(mt_rand(), false); - $values['nc'] = '00000001'; - $values['qop'] = 'auth'; + $values['nc'] = '00000001'; + $values['qop'] = 'auth'; if (!isset($values['realm'])) { $values['realm'] = $this->getOptions()->getTo(); @@ -190,7 +199,7 @@ protected function parseCallenge($challenge) preg_match_all('#(\w+)\=(?:"([^"]+)"|([^,]+))#', $challenge, $matches); list(, $variables, $quoted, $unquoted) = $matches; // filter empty strings; preserve keys - $quoted = array_filter($quoted); + $quoted = array_filter($quoted); $unquoted = array_filter($unquoted); // replace "unquoted" values into "quoted" array and combine variables array with it return array_combine($variables, array_replace($quoted, $unquoted)); diff --git a/src/EventListener/Stream/Authentication/Plain.php b/src/EventListener/Stream/Authentication/Plain.php index 9a08fc4..93d56a7 100644 --- a/src/EventListener/Stream/Authentication/Plain.php +++ b/src/EventListener/Stream/Authentication/Plain.php @@ -47,6 +47,14 @@ class Plain extends AbstractEventListener implements AuthenticationInterface { + /** + * @return string + */ + public static function className() + { + return get_class(); + } + /** * {@inheritDoc} */ diff --git a/src/EventListener/Stream/Avatar.php b/src/EventListener/Stream/Avatar.php new file mode 100644 index 0000000..09c4d44 --- /dev/null +++ b/src/EventListener/Stream/Avatar.php @@ -0,0 +1,132 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class Avatar extends AbstractEventListener implements BlockingEventListenerInterface +{ + /** + * Generated id. + * + * @var string + */ + protected $id; + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/pubsub}pubsub', array($this, 'query')); + $this->getInputEventManager() + ->attach('{jabber:client}iq', array($this, 'result')); + } + + /** + * @param XMLEvent $event + */ + public function query(XMLEvent $event) + { + $this->blocking = true; + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + $this->setId($element->parentNode->getAttribute('id')); + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + if ($this->getId() === $element->getAttribute('id')) { + $this->blocking = false; + } + } + } + + /** + * Get generated id. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set generated id. + * + * @param string $id + * @return void + */ + public function setId($id) + { + $this->id = (string)$id; + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } +} diff --git a/src/EventListener/Stream/Command.php b/src/EventListener/Stream/Command.php index 22f1a6f..fd61b92 100644 --- a/src/EventListener/Stream/Command.php +++ b/src/EventListener/Stream/Command.php @@ -39,16 +39,15 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; -use Fabiang\Xmpp\Exception\Stream\CommandErrorException; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; use Fabiang\Xmpp\Form\Form; -use Fabiang\Xmpp\Protocol\User\User; /** * Listener * * @package Xmpp\EventListener */ -class Command extends AbstractEventListener implements BlockingEventListenerInterface +class Command extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface { /** @@ -58,12 +57,6 @@ class Command extends AbstractEventListener implements BlockingEventListenerInte */ protected $blocking = false; - /** - * user object. - * - * @var User - */ - protected $userObject; /** * {@inheritDoc} @@ -72,34 +65,7 @@ public function attachEvents() { $this->getOutputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'query')); - /** - * error events - * @see https://xmpp.org/extensions/xep-0133.html#errors - */ - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}conflict', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}feature-not-implemented', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}forbidden', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}not-allowed', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable', array($this, 'error')); - /** - * MUC error events - * @see https://xmpp.org/extensions/xep-0045.html#enter-errorcodes - */ - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}item-not-found', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}registration-required', array($this, 'error')); - $this->getInputEventManager() - ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}jid-malformed', array($this, 'error')); - // result $this->getInputEventManager() ->attach('{http://jabber.org/protocol/commands}command', array($this, 'result')); } @@ -123,7 +89,6 @@ public function query() public function result(XMLEvent $event) { if ($event->isEndTag()) { - // same events if (!$this->getOptions()->getForm()) { $form = new Form($event); $this->getOptions()->setForm($form); @@ -133,25 +98,18 @@ public function result(XMLEvent $event) } /** - * we have some errors. - * - * @param \Fabiang\Xmpp\Event\XMLEvent $event - * @return void + * {@inheritDoc} */ - public function error(XMLEvent $event) + public function isBlocking() { - if (false === $event->isStartTag()) { - $this->blocking = false; - /** @var $event \DOMElement */ - throw CommandErrorException::createFromEvent($event); - } + return $this->blocking; } /** * {@inheritDoc} */ - public function isBlocking() + public function unBlock() { - return $this->blocking; + $this->blocking = false; } } \ No newline at end of file diff --git a/src/EventListener/Stream/RoomOwner.php b/src/EventListener/Stream/RoomOwner.php new file mode 100644 index 0000000..7adcee6 --- /dev/null +++ b/src/EventListener/Stream/RoomOwner.php @@ -0,0 +1,137 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; +use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; +use Fabiang\Xmpp\Form\RoomForm; +use Fabiang\Xmpp\Protocol\User\User; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class RoomOwner extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface +{ + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + /** + * user object. + * + * @var User + */ + protected $userObject; + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/muc#owner}query', array($this, 'query')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/muc#owner}query', array($this, 'result')); + } + + /** + * Sending a query request for roster sets listener to blocking mode. + * + * @return void + */ + public function query() + { + $this->blocking = true; + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + if (!$this->getOptions()->getForm()) { + $form = new RoomForm($event); + $this->getOptions()->setForm($form); + } + $this->blocking = false; + } + } + + /** + * we have some errors. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function error(XMLEvent $event) + { + if (false === $event->isStartTag()) { + $this->blocking = false; + /** @var $event \DOMElement */ + throw StanzasErrorException::createFromEvent($event); + } + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } + + /** + * {@inheritDoc} + */ + public function unBlock() + { + $this->blocking = false; + } +} \ No newline at end of file diff --git a/src/EventListener/Stream/RoomPresence.php b/src/EventListener/Stream/RoomPresence.php new file mode 100644 index 0000000..f40e388 --- /dev/null +++ b/src/EventListener/Stream/RoomPresence.php @@ -0,0 +1,141 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; +use Fabiang\Xmpp\Protocol\Room\Room; +use Fabiang\Xmpp\Protocol\User\User; + +/** + * Listener + * + * @see https://xmpp.org/extensions/xep-0045.html#createroom-general + * + * @package Xmpp\EventListener + */ +class RoomPresence extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface +{ + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + /** + * user object. + * + * @var User + */ + protected $userObject; + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/muc}x', array($this, 'query')); + + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/muc#user}x', array($this, 'result')); + } + + /** + * Sending a query request for roster sets listener to blocking mode. + * + * @return void + */ + public function query() + { + $this->blocking = true; + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + if (!$room = $this->getOptions()->getRoom()) { + $room = new Room(); + $this->getOptions()->setRoom($room); + } + + /** @var \DOMElement $element */ + $element = $event->getParameter(0); + $affiliationNode = $element->getElementsByTagName('item')->item(0); + if ($affiliationNode && $affiliation = $affiliationNode->getAttribute('affiliation')) { + $room->setAffiliation($affiliation); + } else { + $room->setAffiliation(Room::AFFILIATION_OUTCAST); + } + + $statusNodeList = $element->getElementsByTagName('status'); + if ($statusNodeList->length > 0) { + for ($i = 0; $i < $statusNodeList->length; $i++) { + $statusNode = $statusNodeList->item($i); + $room->addStatus($statusNode->getAttribute('code')); + } + } + $this->blocking = false; + } + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } + + /** + * {@inheritDoc} + */ + public function unBlock() + { + $this->blocking = false; + } +} \ No newline at end of file diff --git a/src/EventListener/Stream/Stanzas.php b/src/EventListener/Stream/Stanzas.php new file mode 100644 index 0000000..2ad1a28 --- /dev/null +++ b/src/EventListener/Stream/Stanzas.php @@ -0,0 +1,105 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\EventListenerInterface; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; +use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class Stanzas extends AbstractEventListener implements EventListenerInterface +{ + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + /** + * error events + * @see https://xmpp.org/extensions/xep-0133.html#errors + */ + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}bad-request', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}conflict', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}feature-not-implemented', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}forbidden', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}not-allowed', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable', array($this, 'error')); + /** + * MUC error events + * @see https://xmpp.org/extensions/xep-0045.html#enter-errorcodes + */ + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}item-not-found', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}registration-required', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}not-acceptable', array($this, 'error')); + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}jid-malformed', array($this, 'error')); + } + + /** + * we have some errors. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function error(XMLEvent $event) + { + if ($event->isEndTag()) { + $blockedEvent = $this->getConnection()->getLastBlockingListener(); + if ($blockedEvent instanceof UnBlockingEventListenerInterface) { + $blockedEvent->unBlock(); + } + throw StanzasErrorException::createFromEvent($event); + } + } + +} \ No newline at end of file diff --git a/src/Exception/Stream/ChangePasswordErrorException.php b/src/EventListener/UnBlockingEventListenerInterface.php similarity index 87% rename from src/Exception/Stream/ChangePasswordErrorException.php rename to src/EventListener/UnBlockingEventListenerInterface.php index f21f41a..21f5ce2 100644 --- a/src/Exception/Stream/ChangePasswordErrorException.php +++ b/src/EventListener/UnBlockingEventListenerInterface.php @@ -1,4 +1,5 @@ nodeType) { $code = (int)$parent->getAttribute('code'); - $message = 'Stream Error: "' . $element->localName . '"'; + $message = 'Stanzas error: "' . $element->localName . '"'; } else { $code = 0; $message = 'Generic stream error'; diff --git a/src/Form/AbstractForm.php b/src/Form/AbstractForm.php index aede105..b25bd0a 100644 --- a/src/Form/AbstractForm.php +++ b/src/Form/AbstractForm.php @@ -8,8 +8,6 @@ namespace Fabiang\Xmpp\Form; -use Fabiang\Xmpp\Event\XMLEvent; - /** * Class AbstractForm * @package Fabiang\Xmpp\Form @@ -34,12 +32,12 @@ class AbstractForm implements FormInterface /** * @var \DOMElement[] */ - protected $fields = []; + protected $fields = array(); /** * @var \DOMElement */ - private $form; + protected static $form; /** * get sessionid of form @@ -91,7 +89,7 @@ public function getFieldNames() public function setFieldValue($fieldName, $value) { if (isset($this->fields[$fieldName])) { - $valueNode = $this->form->ownerDocument->createElement('value', (string)$value); + $valueNode = static::$form->ownerDocument->createElement('value', (string)$value); // remove previous value while ($this->fields[$fieldName]->hasChildNodes()) { $this->fields[$fieldName]->removeChild($this->fields[$fieldName]->firstChild); @@ -113,7 +111,7 @@ public function setFieldValue($fieldName, $value) public function addFieldValue($fieldName, $value) { if (isset($this->fields[$fieldName])) { - $valueNode = $this->form->ownerDocument->createElement('value', (string)$value); + $valueNode = static::$form->ownerDocument->createElement('value', (string)$value); // add new value $this->fields[$fieldName]->appendChild($valueNode); return true; @@ -148,6 +146,27 @@ public function getFieldAttributes($field) return null; } + /** + * return field options if has + * + * @param $field string + * @return array + */ + public function getFieldOptions($field) + { + $options = array(); + if (isset($this->fields[$field])) { + $optionNodeList = $this->fields[$field]->getElementsByTagName('option'); + if ($optionNodeList->length > 0) { + for ($i = 0; $i < $optionNodeList->length; $i++) { + $optionNode = $optionNodeList->item($i); + array_push($options, $optionNode->firstChild->textContent); + } + } + } + return $options; + } + /** * converts form to XML string * @@ -155,12 +174,7 @@ public function getFieldAttributes($field) */ public function toString() { - $this->form->removeAttribute('status'); - $this->form->firstChild->setAttribute('type', 'submit'); - // remove previous values - while ($this->form->firstChild->hasChildNodes()) { - $this->form->firstChild->removeChild($this->form->firstChild->firstChild); - } + static::$form->setAttribute('type', 'submit'); // append new values foreach ($this->fields as $field) { if ($field->getAttribute('type') && $field->getAttribute('type') != 'hidden') { @@ -169,43 +183,20 @@ public function toString() if ($field->getAttribute('label')) { $field->removeAttribute('label'); } - $this->form->firstChild->appendChild($field); + // remove option artifacts + $optionNodeList = $field->getElementsByTagName('option'); + while ($optionNodeList->length > 0) { + $field->removeChild($optionNodeList->item(0)); + } + + static::$form->appendChild($field); } - return $this->form->ownerDocument->saveXML($this->form); + return static::$form->ownerDocument->saveXML(static::$form); } - /** - * parse XMLEvent to the form fields - * - * AbstractForm constructor. - * @param XMLEvent $event - */ - public function __construct(XMLEvent $event) + public function unsetAllFields() { - /** @var $event \DOMElement */ - $this->form = $event->getParameter(0); - if ($this->sid = $this->form->getAttribute('sessionid')) { - $titleNode = $this->form->getElementsByTagName('title')->item(0); - if ($titleNode) { - $this->title = $titleNode->nodeValue; - } - unset($titleNode); - $instructionsNode = $this->form->getElementsByTagName('instructions')->item(0); - if ($instructionsNode) { - $this->instructions = $instructionsNode->nodeValue; - } - unset($instructionsNode); - $fieldNodeList = $this->form->getElementsByTagName('field'); - if ($fieldNodeList->length > 0) { - foreach ($fieldNodeList as $field) { - /**@var $field \DOMElement */ - $this->fields[$field->getAttribute('var')] = $field; - } - } else { - $this->fields = []; - } - unset($fieldNodeList); - } + $this->fields = array(); } } \ No newline at end of file diff --git a/src/Form/Form.php b/src/Form/Form.php index 100fbab..cf0dd43 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -8,11 +8,52 @@ namespace Fabiang\Xmpp\Form; +use Fabiang\Xmpp\Event\XMLEvent; + /** * Class Form * @package Fabiang\Xmpp\Form */ class Form extends AbstractForm implements FormInterface { + /** + * parse XMLEvent to the form fields + * + * AbstractForm constructor. + * @param XMLEvent $event + */ + public function __construct(XMLEvent $event) + { + /** @var $event \DOMElement */ + + $command = $event->getParameter(0); + $form = $command->getElementsByTagName('x')->item(0); + if ($form) { + static::$form = $form; + $this->sid = $command->getAttribute('sessionid'); + $titleNode = static::$form->getElementsByTagName('title')->item(0); + if ($titleNode) { + $this->title = $titleNode->nodeValue; + self::$form->removeChild($titleNode); + } + unset($titleNode); + $instructionsNode = static::$form->getElementsByTagName('instructions')->item(0); + if ($instructionsNode) { + $this->instructions = $instructionsNode->nodeValue; + self::$form->removeChild($instructionsNode); + } + unset($instructionsNode); + $fieldNodeList = static::$form->getElementsByTagName('field'); + if ($fieldNodeList->length > 0) { + for ($i = 0; $i < $fieldNodeList->length; $i++) { + $field = $fieldNodeList->item($i); + $this->fields[$field->getAttribute('var')] = $field; + } + } else { + $this->fields = array(); + } + unset($fieldNodeList); + } + } } \ No newline at end of file diff --git a/src/Form/FormInterface.php b/src/Form/FormInterface.php index dbf542c..fbee025 100644 --- a/src/Form/FormInterface.php +++ b/src/Form/FormInterface.php @@ -24,7 +24,7 @@ interface FormInterface * @param XMLEvent $event * @return mixed */ - public function __construct(XMLEvent $event); + //public function __construct(XMLEvent $event); /** * returns title of form @@ -80,6 +80,14 @@ public function addFieldValue($fieldName, $value); */ public function getFieldAttributes($field); + /** + * return field options if has + * + * @param $field string + * @return array + */ + public function getFieldOptions($field); + /** * converts form to XML string * diff --git a/src/Form/RoomForm.php b/src/Form/RoomForm.php new file mode 100644 index 0000000..43d9063 --- /dev/null +++ b/src/Form/RoomForm.php @@ -0,0 +1,53 @@ +getParameter(0); + $form = $query->getElementsByTagName('x')->item(0); + if ($form) { + static::$form = $form; + unset($query); + $titleNode = static::$form->getElementsByTagName('title')->item(0); + if ($titleNode) { + $this->title = $titleNode->nodeValue; + self::$form->removeChild($titleNode); + } + unset($titleNode); + $instructionsNode = static::$form->getElementsByTagName('instructions')->item(0); + if ($instructionsNode) { + $this->instructions = $instructionsNode->nodeValue; + self::$form->removeChild($instructionsNode); + } + unset($instructionsNode); + $fieldNodeList = static::$form->getElementsByTagName('field'); + if ($fieldNodeList->length > 0) { + for ($i = 0; $i < $fieldNodeList->length; $i++) { + $field = $fieldNodeList->item($i); + $this->fields[$field->getAttribute('var')] = $field; + } + } else { + $this->fields = array(); + } + unset($fieldNodeList); + } + } + +} \ No newline at end of file diff --git a/src/Options.php b/src/Options.php index fe94376..df5c281 100644 --- a/src/Options.php +++ b/src/Options.php @@ -37,10 +37,13 @@ namespace Fabiang\Xmpp; use Fabiang\Xmpp\Connection\ConnectionInterface; +use Fabiang\Xmpp\EventListener\Stream\Authentication\DigestMd5; +use Fabiang\Xmpp\EventListener\Stream\Authentication\Plain; use Fabiang\Xmpp\Form\FormInterface; use Fabiang\Xmpp\Protocol\DefaultImplementation; use Fabiang\Xmpp\Protocol\ImplementationInterface; use Fabiang\Xmpp\Protocol\Room\Room; +use Fabiang\Xmpp\Protocol\User\User; use Psr\Log\LoggerInterface; /** @@ -132,6 +135,12 @@ class Options */ protected $room; + /** + * + * @var null|User + */ + protected $user; + /** * Timeout for connection. * @@ -144,10 +153,7 @@ class Options * * @var array */ - protected $authenticationClasses = array( - 'digest-md5' => '\\Fabiang\\Xmpp\\EventListener\\Stream\\Authentication\\DigestMd5', - 'plain' => '\\Fabiang\\Xmpp\\EventListener\\Stream\\Authentication\\Plain' - ); + protected $authenticationClasses; /** * Constructor. @@ -156,6 +162,11 @@ class Options */ public function __construct($address = null) { + $this->authenticationClasses = array( + 'digest-md5' => DigestMd5::className(), + 'plain' => Plain::className() + ); + if (null !== $address) { $this->setAddress($address); } @@ -442,6 +453,26 @@ public function setUsers(array $users) return $this; } + /** + * Get users. + * + * @return Protocol\User\User + */ + public function getUser() + { + return $this->user; + } + + /** + * @param User $user + * @return $this + */ + public function setUser(User $user) + { + $this->user = $user; + return $this; + } + /** * Get room. * diff --git a/src/Protocol/DefaultImplementation.php b/src/Protocol/DefaultImplementation.php index 90db456..a979db4 100644 --- a/src/Protocol/DefaultImplementation.php +++ b/src/Protocol/DefaultImplementation.php @@ -40,10 +40,14 @@ use Fabiang\Xmpp\Event\EventManagerInterface; use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\EventListener\Stream\Authentication; +use Fabiang\Xmpp\EventListener\Stream\Avatar; use Fabiang\Xmpp\EventListener\Stream\Bind; use Fabiang\Xmpp\EventListener\Stream\Command; +use Fabiang\Xmpp\EventListener\Stream\RoomOwner; +use Fabiang\Xmpp\EventListener\Stream\RoomPresence; use Fabiang\Xmpp\EventListener\Stream\Roster as RosterListener; use Fabiang\Xmpp\EventListener\Stream\Session; +use Fabiang\Xmpp\EventListener\Stream\Stanzas; use Fabiang\Xmpp\EventListener\Stream\StartTls; use Fabiang\Xmpp\EventListener\Stream\Stream; use Fabiang\Xmpp\EventListener\Stream\StreamError; @@ -83,7 +87,11 @@ public function register() $this->registerListener(new Bind); $this->registerListener(new Session); $this->registerListener(new RosterListener); + $this->registerListener(new Stanzas); $this->registerListener(new Command); + $this->registerListener(new Avatar); + $this->registerListener(new RoomPresence); + $this->registerListener(new RoomOwner); } /** diff --git a/src/Protocol/Room/DirectInvite.php b/src/Protocol/Room/DirectInvite.php new file mode 100644 index 0000000..fe3bdcf --- /dev/null +++ b/src/Protocol/Room/DirectInvite.php @@ -0,0 +1,182 @@ +setFrom($from) + ->setTo($to) + ->setRoom($room) + ->setPassword($password) + ->setReason($reason); + } + + /** + * {@inheritDoc} + */ + public function toString() + { + return XML::quoteMessage("" . + "" . + "", + $this->getFrom(), + $this->getTo(), + $this->getRoom(), + $this->getPassword(), + $this->getReason() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get server address. + * + * @return string + */ + public function getRoom() + { + return $this->room; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $room string + * @return $this + */ + public function setRoom($room) + { + $this->room = (string)$room; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * set room password + * + * @param $password string + * @return $this + */ + public function setPassword($password) + { + $this->password = (string)$password; + return $this; + } + + /** + * @return string + */ + public function getReason() + { + return $this->reason; + } + + /** + * @param $reason string + * @return $this + */ + public function setReason($reason) + { + $this->reason = (string)$reason; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/Room/Room.php b/src/Protocol/Room/Room.php new file mode 100644 index 0000000..9f9e540 --- /dev/null +++ b/src/Protocol/Room/Room.php @@ -0,0 +1,191 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\Protocol\Room; + +/** + * Room object. + * + * @package Xmpp\Protocol + */ +class Room +{ + /** + * role of user in MUC + * @see https://xmpp.org/extensions/xep-0045.html#affil + */ + const AFFILIATION_OWNER = 'owner'; + const AFFILIATION_ADMIN = 'admin'; + const AFFILIATION_MEMBER = 'member'; + const AFFILIATION_OUTCAST = 'outcast'; + const AFFILIATION_NONE = 'none'; + + + /** + * @see https://xmpp.org/extensions/xep-0045.html#registrar-statuscodes + */ + // user role has been changed + const STATUS_AFFILIATION_CHANGED = 101; + // user is presence in room + const STATUS_PRESENCE = 110; + // new room has been created + const STATUS_CREATED = 201; + + const STATUS_BANNED = 301; + const STATUS_KICKED = 307; + + + const CONFIG_YES = 1; + const CONFIG_NO = 0; + + + /** + * @var array + */ + protected $statuses = array(); + + /** + * + * @var string + */ + protected $affiliation; + + /** + * + * @var string + */ + protected $name; + + /** + * + * @var string + */ + protected $jid; + + public function getName() + { + return $this->name; + } + + public function setName($name = null) + { + if (null === $name || '' === $name) { + $this->name = null; + } else { + $this->name = $name; + } + return $this; + } + + public function getJid() + { + return $this->jid; + } + + public function setJid($jid) + { + $this->jid = (string)$jid; + return $this; + } + + /** + * @return string + */ + public function getAffiliation() + { + return $this->affiliation; + } + + /** + * @param $affiliation + * @return $this + */ + public function setAffiliation($affiliation) + { + $this->affiliation = $affiliation; + return $this; + } + + /** + * current user is owner of room + * + * @return bool + */ + public function isOwner() + { + return $this->affiliation == self::AFFILIATION_OWNER; + } + + /** + * current user is admin of room + * + * @return bool + */ + public function isAdmin() + { + return $this->affiliation == self::AFFILIATION_ADMIN; + } + + /** + * current user is member of room + * + * @return bool + */ + public function isMember() + { + return $this->affiliation != self::AFFILIATION_OUTCAST; + } + + /** + * add room status + * + * @param $code string + * @return $this + */ + public function addStatus($code) + { + array_push($this->statuses, (int)$code); + return $this; + } + + /** + * @return bool + */ + public function isJustCreated() + { + return in_array(self::STATUS_CREATED, $this->statuses); + } +} diff --git a/src/Protocol/Room/RoomConfig.php b/src/Protocol/Room/RoomConfig.php new file mode 100644 index 0000000..49154f6 --- /dev/null +++ b/src/Protocol/Room/RoomConfig.php @@ -0,0 +1,146 @@ +setFrom($from) + ->setTo($to) + ->setForm($form) + ->setRoom($room); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" .//$this->form->toString() . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getFullRoomName() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @param FormInterface $form + * @return $this + */ + private function setForm(FormInterface $form) + { + $this->form = $form; + return $this; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $room string + * @return $this + */ + public function setRoom($room) + { + $this->room = (string)$room; + return $this; + } + + /** + * @return string + */ + public function getRoom(){ + return $this->room; + } + /** + * Get server address. + * + * @return string + */ + public function getFullRoomName() + { + return $this->room . '@' . $this->to; + } + +} \ No newline at end of file diff --git a/src/Protocol/Room/RoomPresence.php b/src/Protocol/Room/RoomPresence.php new file mode 100644 index 0000000..ac86aa1 --- /dev/null +++ b/src/Protocol/Room/RoomPresence.php @@ -0,0 +1,166 @@ +setFrom($from) + ->setTo($to) + ->setRoom($room) + ->setNickname($nickname); + } + + /** + * {@inheritDoc} + */ + public function toString() + { + return XML::quoteMessage("" . + "" . + "", + $this->getFrom(), + Xml::generateId(), + $this->getFullRoomName() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get server address. + * + * @return string + */ + public function getRoom() + { + return $this->room; + } + + /** + * Get server address. + * + * @return string + */ + public function getFullRoomName() + { + return $this->room . '@' . $this->to . '/' . $this->nickname; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $room string + * @return $this + */ + public function setRoom($room) + { + $this->room = (string)$room; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set nickname for chat + * + * @param $nickname string + * @return $this + */ + public function setNickname($nickname) + { + $this->nickname = (string)$nickname; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getNickname() + { + return $this->nickname; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/User/Avatar.php b/src/Protocol/User/Avatar.php new file mode 100644 index 0000000..5226d5a --- /dev/null +++ b/src/Protocol/User/Avatar.php @@ -0,0 +1,194 @@ +setFrom($from) + ->setImage($imagePath); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + "" . + "" . + $this->getImageBase64Data() . + "" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getImageId() + ); + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * Set image content + * + * @param $path + * @return $this + */ + public function setImage($path) + { + if (!file_exists($path)) { + throw new InvalidArgumentException('File "' . $path . '" does not exists.'); + } + $this->imagePath = $path; + + $this->getImageMime(); + $this->imageData = file_get_contents($path); + $this->setImageId(); + return $this; + } + + /** + * set SHA1 image id + */ + protected function setImageId() + { + $this->imageId = sha1($this->imageData); + } + + /** + * get image SHA1 id + * + * @return string + */ + public function getImageId() + { + return $this->imageId; + } + + + /** + * get converted to Base64 image data + * + * @see https://xmpp.org/extensions/xep-0153.html#bizrules-image + * @return string + */ + protected function getImageBase64Data() + { + return "\n" . wordwrap(XML::base64Encode($this->imageData), 75, "\n", true) . "\n"; + } + + /** + * get image mime type + * @return string + */ + protected function getImageMime() + { + if (!$this->imageMime) { + $size = getimagesize($this->imagePath); + $this->imageMime = isset($size['mime']) ? $size['mime'] : ''; + + if ($size[0] < 32 || $size[1] < 32 || $size[0] > 96 || $size[1] > 96) { + throw new InvalidArgumentException('Image size must be between 32px and 96px'); + } + if (!in_array($this->imageMime, $this->mimes)) { + throw new InvalidArgumentException('Type of Image must be of allowed mimes: ' . implode(', ', $this->mimes) . '.'); + } + } + return $this->imageMime; + } +} \ No newline at end of file diff --git a/src/Protocol/User/AvatarMetadata.php b/src/Protocol/User/AvatarMetadata.php new file mode 100644 index 0000000..bcebbb9 --- /dev/null +++ b/src/Protocol/User/AvatarMetadata.php @@ -0,0 +1,225 @@ +setFrom($from) + ->setImage($imagePath) + ->setUrl($url); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + "" . + "" . + "getUrl() ? "url='" . $this->getUrl() . "'" : "") . + "/>" . + "" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getImageId(), + $this->getImageSize(), + $this->getImageId(), + $this->getImageHeight(), + $this->getImageType(), + $this->getImageWidth() + ); + } + + /** + * Get UserJID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set UserJID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * set image url + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * get image url + * + * @param $url + * @return $this + */ + public function setUrl($url) + { + if (!empty($url)) { + $this->url = (string)$url; + } + return $this; + } + + + /** + * Set image content + * + * @param $path + * @return $this + */ + public function setImage($path) + { + if (!file_exists($path)) { + throw new InvalidArgumentException('File "' . $path . '" does not exists.'); + } + $this->imagePath = $path; + + $size = getimagesize($this->imagePath); + $this->imageMime = isset($size['mime']) ? $size['mime'] : ''; + list($this->imageWidth, $this->imageHeight) = $size; + $this->imageSize = filesize($path); + $this->imageId = sha1(file_get_contents($path)); + return $this; + } + + + /** + * get image SHA1 id + * + * @return string + */ + public function getImageId() + { + return $this->imageId; + } + + /** + * get image size + * + * @return string + */ + public function getImageSize() + { + return (string)$this->imageSize; + } + + /** + * get image size + * + * @return string + */ + public function getImageWidth() + { + return (string)$this->imageWidth; + } + + /** + * get image size + * + * @return string + */ + public function getImageHeight() + { + return (string)$this->imageHeight; + } + + /** + * get image size + * + * @return string + */ + public function getImageType() + { + return (string)$this->imageMime; + } +} \ No newline at end of file diff --git a/src/Protocol/User/ChangeUserPassword.php b/src/Protocol/User/ChangeUserPassword.php index 7b9f750..0a1b783 100644 --- a/src/Protocol/User/ChangeUserPassword.php +++ b/src/Protocol/User/ChangeUserPassword.php @@ -78,11 +78,14 @@ public function toString() { return XML::quoteMessage( "" . + "" . $this->form->toString() . "", $this->getFrom(), XML::generateId(), - $this->getTo() + $this->getTo(), + $this->form->getSid() ); } diff --git a/src/Protocol/User/RegisterUser.php b/src/Protocol/User/RegisterUser.php index ee2119a..24bdcd6 100644 --- a/src/Protocol/User/RegisterUser.php +++ b/src/Protocol/User/RegisterUser.php @@ -55,28 +55,6 @@ class RegisterUser implements ProtocolImplementationInterface */ protected $password; - /** - * user E-mail - * - * @var string - */ - protected $email; - - /** - * user surname - * - * @var string - */ - protected $surname; - - /** - * user given name - * - * @var string - */ - protected $givenName; - - /** * RegisterUser constructor. * @param $userJid string @@ -105,11 +83,15 @@ public function toString() { return XML::quoteMessage( "" . + "" . $this->form->toString() . + "" . "", $this->getFrom(), XML::generateId(), - $this->getTo() + $this->getTo(), + $this->form->getSid() ); } diff --git a/src/Protocol/User/VCardPresence.php b/src/Protocol/User/VCardPresence.php index b9b2b84..e00984a 100644 --- a/src/Protocol/User/VCardPresence.php +++ b/src/Protocol/User/VCardPresence.php @@ -47,7 +47,6 @@ public function toString() "" . "%s" . "" . - "" . "", $this->getImageId(), $this->getImageId() diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php index 0d26c8e..fd4ede3 100644 --- a/src/Protocol/User/VCardUpdate.php +++ b/src/Protocol/User/VCardUpdate.php @@ -84,11 +84,11 @@ class VCardUpdate implements ProtocolImplementationInterface * Allowed image mime types * @var array */ - protected $mimes = [ + protected $mimes = array( 'image/jpeg', 'image/png', 'image/gif' - ]; + ); /** * VCardUpdate constructor. @@ -217,10 +217,7 @@ public function setImage($path) } $this->imagePath = $path; - if (!in_array($this->getImageMime(), $this->mimes)) { - throw new InvalidArgumentException('Type of Image must be of allowed mimes: ' . implode(', ', $this->mimes) . '.'); - } - + $this->getImageMime(); $this->imageData = file_get_contents($path); $this->setImageId(); return $this; @@ -257,8 +254,6 @@ protected function getImageBase64Data() } /** - * todo: check image size: 32-96px, file size: 8096 b, - * extensions: image/png;image/gif;image/jpeg * get image mime type * @return string */ @@ -267,6 +262,13 @@ protected function getImageMime() if (!$this->imageMime) { $size = getimagesize($this->imagePath); $this->imageMime = isset($size['mime']) ? $size['mime'] : ''; + + if ($size[0] < 32 || $size[1] < 32 || $size[0] > 96 || $size[1] > 96) { + throw new InvalidArgumentException('Image size must be between 32px and 96px'); + } + if (!in_array($this->imageMime, $this->mimes)) { + throw new InvalidArgumentException('Type of Image must be of allowed mimes: ' . implode(', ', $this->mimes) . '.'); + } } return $this->imageMime; } From 0f3f77f0a39729135aeb77288138541a5fa5ff12 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Thu, 27 Apr 2017 22:11:43 +0500 Subject: [PATCH 09/16] adding member to the room adding bookmark for the room Direct invite to the room --- examples/invitation.php | 47 ++++ examples/room.php | 94 +++++++- src/EventListener/Stream/Membership.php | 104 ++++++++ .../Stream/{Avatar.php => Pubsub.php} | 10 +- src/EventListener/Stream/RoomPresence.php | 4 + src/Options.php | 1 + src/Protocol/DefaultImplementation.php | 6 +- src/Protocol/Room/Bookmark.php | 208 ++++++++++++++++ src/Protocol/Room/DirectInvite.php | 10 +- src/Protocol/Room/Membership.php | 226 ++++++++++++++++++ src/Protocol/Room/RequestRoomConfigForm.php | 12 +- src/Protocol/Room/Room.php | 14 ++ src/Protocol/Room/RoomConfig.php | 6 +- src/Protocol/Room/RoomList.php | 2 +- src/Protocol/Room/RoomPresence.php | 6 + 15 files changed, 719 insertions(+), 31 deletions(-) create mode 100644 examples/invitation.php create mode 100644 src/EventListener/Stream/Membership.php rename src/EventListener/Stream/{Avatar.php => Pubsub.php} (93%) create mode 100644 src/Protocol/Room/Bookmark.php create mode 100644 src/Protocol/Room/Membership.php diff --git a/examples/invitation.php b/examples/invitation.php new file mode 100644 index 0000000..98bfa6c --- /dev/null +++ b/examples/invitation.php @@ -0,0 +1,47 @@ +pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$room = 'new-room'; +$password = ''; +$newUser = 'testuser'; + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($config['login']) + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + +$client->connect(); +$invitation = new DirectInvite( + $config['login'] . '@' . $config['host'], + $newUser . '@' . $config['host'], + $room . '@' . $config['conference'], + $password, + 'This is a cool party room! Join to it!' +); +try { + $client->send($invitation); + + fwrite(STDOUT, 'Invitation sent' . PHP_EOL); +} catch (StreamErrorException $e) { + fwrite(STDOUT, 'Invitation failed' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); +} + +$client->disconnect(); \ No newline at end of file diff --git a/examples/room.php b/examples/room.php index 2f37662..c993d26 100644 --- a/examples/room.php +++ b/examples/room.php @@ -5,7 +5,10 @@ use Fabiang\Xmpp\Client; use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; +use Fabiang\Xmpp\Exception\Stream\StreamErrorException; use Fabiang\Xmpp\Options; +use Fabiang\Xmpp\Protocol\Room\Bookmark; +use Fabiang\Xmpp\Protocol\Room\Membership; use Fabiang\Xmpp\Protocol\Room\RequestRoomConfigForm; use Fabiang\Xmpp\Protocol\Room\Room; use Fabiang\Xmpp\Protocol\Room\RoomConfig; @@ -18,8 +21,10 @@ $address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; -$room = 'new-room'; +$room = 'test-room'; $password = ''; +$newUser = 'testuser'; +$newPassword = '123456'; $options = new Options($address); $options->setLogger($logger) @@ -31,13 +36,13 @@ $client->connect(); try { - $request = new RoomPresence( + $presence = new RoomPresence( $config['login'] . '@' . $config['host'], $config['conference'], $room ); - $client->send($request); + $client->send($presence); } catch (StanzasErrorException $e) { if ($e->getCode() != StanzasErrorException::ERROR_CONFLICT) { @@ -50,27 +55,36 @@ if ($client->getOptions()->getRoom()->isJustCreated()) { fwrite(STDOUT, 'Room presence has been created.' . PHP_EOL); +} else if ($client->getOptions()->getRoom()->isOwner()) { + fwrite(STDOUT, 'Room is already exists. But you are owner of the group. Continue configuring...' . PHP_EOL); } + if (!$client->getOptions()->getRoom()->isOwner()) { - fwrite(STDOUT, 'You are not owner of this room, so forbidden for configuring it.' . PHP_EOL); - exit; + // this case is often occurs when persistent room is already exists + // TODO: check room affiliation of current user + fwrite(STDOUT, 'You are not owner of this room, so forbidden to configuring it.' . PHP_EOL); } try { - $requestForm = new RequestRoomConfigForm( + $presenceForm = new RequestRoomConfigForm( $config['login'] . '@' . $config['host'], $config['conference'], $room ); - $client->send($requestForm); + $client->send($presenceForm); $form = $client->getOptions()->getForm(); + if (!$client->getOptions()->getRoom()) { + $client->getOptions()->setRoom(new Room()); + } + $client->getOptions()->getRoom()->setName('New cool room'); + /** * @see https://xmpp.org/extensions/xep-0045.html#createroom-reserved - */ - $form->setFieldValue('muc#roomconfig_roomname', 'New cool room'); + **/ + $form->setFieldValue('muc#roomconfig_roomname', $client->getOptions()->getRoom()->getName()); $form->setFieldValue('muc#roomconfig_roomdesc', 'Some description...'); // no public logging. before turn this option on you must check that logging is enabled $form->setFieldValue('muc#roomconfig_enablelogging', Room::CONFIG_NO); @@ -109,7 +123,37 @@ $form); $client->send($roomConfig); + fwrite(STDOUT, 'Room success configured' . PHP_EOL); + + + // add room in a roster + $bookmark = new Bookmark( + $config['login'] . '@' . $config['host'], + $client->getOptions()->getRoom()->getJid(), + $client->getOptions()->getRoom()->getName(), + $newUser, + true + ); + + $client->send($bookmark); + + + try { + + $member = new Membership( + $config['login'] . '@' . $config['host'], + $client->getOptions()->getRoom()->getJid(), + Membership::AFFILIATION_MEMBER, + $newUser . '@' . $config['host'] + ); + $client->send($member); + + fwrite(STDOUT, 'The ' . $newUser . '@' . $config['host'] . ' now is member of the room ' . $room . PHP_EOL); + } catch (StanzasErrorException $e) { + fwrite(STDOUT, 'Can\'t add member' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + } } catch (StanzasErrorException $e) { fwrite(STDOUT, 'Failed to configure room!' . PHP_EOL); fwrite(STDOUT, $e->getMessage() . PHP_EOL); @@ -119,4 +163,36 @@ fwrite(STDOUT, 'Failed to create room!' . PHP_EOL); fwrite(STDOUT, $e->getMessage() . PHP_EOL); } +$client->disconnect(); + + +// the room is not in user roster +// so add it into bookmarks +fwrite(STDOUT, 'Login as ' . $newUser . PHP_EOL); + +$options = new Options($address); +$options->setLogger($logger) + ->setUsername($newUser) + ->setPassword($newPassword) + ->setVerifyPeer($config['verifyPeer']); + +$client = new Client($options); + + +$client->connect(); +try { + $bookmark = new Bookmark( + $newUser . '@' . $config['host'], + $room . '@' . $config['conference'], + 'New cool room', + $newUser, + true + ); + + $client->send($bookmark); + fwrite(STDOUT, 'Bookmark for ' . $room . '@' . $config['conference'] . ' is created.' . PHP_EOL); +} catch (StreamErrorException $e) { + fwrite(STDOUT, 'Can\'t create bookmark for ' . $room . '@' . $config['conference'] . '.' . PHP_EOL); + fwrite(STDOUT, $e->getMessage() . PHP_EOL); +} $client->disconnect(); \ No newline at end of file diff --git a/src/EventListener/Stream/Membership.php b/src/EventListener/Stream/Membership.php new file mode 100644 index 0000000..a18bcdd --- /dev/null +++ b/src/EventListener/Stream/Membership.php @@ -0,0 +1,104 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\EventListener\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; +use Fabiang\Xmpp\EventListener\AbstractEventListener; +use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; + +/** + * Listener + * + * @package Xmpp\EventListener + */ +class Membership extends AbstractEventListener implements BlockingEventListenerInterface +{ + /** + * Generated id. + * + * @var string + */ + protected $id; + + /** + * Blocking. + * + * @var boolean + */ + protected $blocking = false; + + + /** + * {@inheritDoc} + */ + public function attachEvents() + { + $this->getOutputEventManager() + ->attach('{http://jabber.org/protocol/muc#admin}query', array($this, 'query')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/muc#admin}query', array($this, 'result')); + } + + /** + * Blocking event + */ + public function query() + { + $this->blocking = true; + } + + /** + * Result received. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function result(XMLEvent $event) + { + if ($event->isEndTag()) { + $this->blocking = false; + } + } + + /** + * {@inheritDoc} + */ + public function isBlocking() + { + return $this->blocking; + } +} diff --git a/src/EventListener/Stream/Avatar.php b/src/EventListener/Stream/Pubsub.php similarity index 93% rename from src/EventListener/Stream/Avatar.php rename to src/EventListener/Stream/Pubsub.php index 09c4d44..e51bfd1 100644 --- a/src/EventListener/Stream/Avatar.php +++ b/src/EventListener/Stream/Pubsub.php @@ -41,11 +41,19 @@ use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; /** + * pubsub is using in many cases + * + * avatars + * @see https://xmpp.org/extensions/xep-0084.html#process-pubmeta + * + * bookmarks + * @see https://xmpp.org/extensions/xep-0048.html#storage-pubsub-upload + * * Listener * * @package Xmpp\EventListener */ -class Avatar extends AbstractEventListener implements BlockingEventListenerInterface +class Pubsub extends AbstractEventListener implements BlockingEventListenerInterface { /** * Generated id. diff --git a/src/EventListener/Stream/RoomPresence.php b/src/EventListener/Stream/RoomPresence.php index f40e388..0ce0500 100644 --- a/src/EventListener/Stream/RoomPresence.php +++ b/src/EventListener/Stream/RoomPresence.php @@ -105,6 +105,10 @@ public function result(XMLEvent $event) /** @var \DOMElement $element */ $element = $event->getParameter(0); + $from = $element->parentNode->getAttribute('from'); + $from = explode("/", $from); + $room->setJid($from[0]); + $affiliationNode = $element->getElementsByTagName('item')->item(0); if ($affiliationNode && $affiliation = $affiliationNode->getAttribute('affiliation')) { $room->setAffiliation($affiliation); diff --git a/src/Options.php b/src/Options.php index df5c281..c4ff19f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -163,6 +163,7 @@ class Options public function __construct($address = null) { $this->authenticationClasses = array( + // 'scram-sha-1' => ScramSha1::className(), 'digest-md5' => DigestMd5::className(), 'plain' => Plain::className() ); diff --git a/src/Protocol/DefaultImplementation.php b/src/Protocol/DefaultImplementation.php index a979db4..32ed881 100644 --- a/src/Protocol/DefaultImplementation.php +++ b/src/Protocol/DefaultImplementation.php @@ -40,9 +40,10 @@ use Fabiang\Xmpp\Event\EventManagerInterface; use Fabiang\Xmpp\EventListener\EventListenerInterface; use Fabiang\Xmpp\EventListener\Stream\Authentication; -use Fabiang\Xmpp\EventListener\Stream\Avatar; use Fabiang\Xmpp\EventListener\Stream\Bind; use Fabiang\Xmpp\EventListener\Stream\Command; +use Fabiang\Xmpp\EventListener\Stream\Membership; +use Fabiang\Xmpp\EventListener\Stream\Pubsub; use Fabiang\Xmpp\EventListener\Stream\RoomOwner; use Fabiang\Xmpp\EventListener\Stream\RoomPresence; use Fabiang\Xmpp\EventListener\Stream\Roster as RosterListener; @@ -89,9 +90,10 @@ public function register() $this->registerListener(new RosterListener); $this->registerListener(new Stanzas); $this->registerListener(new Command); - $this->registerListener(new Avatar); $this->registerListener(new RoomPresence); $this->registerListener(new RoomOwner); + $this->registerListener(new Membership); + $this->registerListener(new Pubsub); } /** diff --git a/src/Protocol/Room/Bookmark.php b/src/Protocol/Room/Bookmark.php new file mode 100644 index 0000000..8602e2b --- /dev/null +++ b/src/Protocol/Room/Bookmark.php @@ -0,0 +1,208 @@ +setFrom($from) + ->setJid($jid) + ->setName($name) + ->setAutoJoin($autojoin) + ->setNickname($nickname); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + "" . + "" . + "" . + "%s" . + "" . + "" . + "" . + "" . + "" . + "" . + "" . + "http://jabber.org/protocol/pubsub#publish-options" . + "" . + "" . + "true" . + "" . + "" . + "whitelist" . + "" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getName(), + $this->getJid(), + $this->getAutoJoin(), + $this->getNickname() + ); + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param $name string + * @return $this + */ + public function setName($name) + { + $this->name = (string)$name; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getJid() + { + return $this->jid; + } + + /** + * Set jabberID. + * + * @param $jid string + * @return $this + */ + public function setJid($jid) + { + $this->jid = (string)$jid; + return $this; + } + + /** + * @param $autojoin bool + * @return $this + */ + public function setAutoJoin($autojoin) + { + $this->autojoin = (bool)$autojoin; + return $this; + } + + /** + * @return string + */ + public function getAutoJoin() + { + return $this->autojoin === false ? 'false' : 'true'; + } + + /** + * Set nickname for chat + * + * @param $nickname string + * @return $this + */ + public function setNickname($nickname) + { + $this->nickname = (string)$nickname; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getNickname() + { + return $this->nickname; + } +} \ No newline at end of file diff --git a/src/Protocol/Room/DirectInvite.php b/src/Protocol/Room/DirectInvite.php index fe3bdcf..82733f8 100644 --- a/src/Protocol/Room/DirectInvite.php +++ b/src/Protocol/Room/DirectInvite.php @@ -45,11 +45,11 @@ class DirectInvite implements ProtocolImplementationInterface /** * DirectInvite constructor. - * @param $from string - * @param $to string - * @param $room string - * @param string $password - * @param string $reason + * @param $from string - invitation from user + * @param $to string - invitation to user + * @param $room string - full room name, like an new-room@conference.xmpp.site.com + * @param string $password - password for the room + * @param string $reason - reason for invitation */ public function __construct($from, $to, $room, $password = '', $reason = '') { diff --git a/src/Protocol/Room/Membership.php b/src/Protocol/Room/Membership.php new file mode 100644 index 0000000..bfc4cb5 --- /dev/null +++ b/src/Protocol/Room/Membership.php @@ -0,0 +1,226 @@ +setFrom($from) + ->setTo($to) + ->setAffiliation($affiliation) + ->setJid($jid) + ->setNickname($nickname) + ->setReason($reason); + } + + /** + * {@inheritDoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + "" . + ($this->getReason() ? "" . XML::quote($this->getReason()) . "" : "") . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getTo(), + $this->getAffiliation(), + $this->getJid(), + $this->getNickname(), + $this->getReason() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * Set receiver - for example: xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * @return string + */ + public function getJid() + { + return $this->jid; + } + + /** + * @param $jid + * @return $this + */ + public function setJid($jid) + { + $this->jid = (string)$jid; + return $this; + } + + /** + * @return string + */ + public function getAffiliation() + { + return $this->affiliation; + } + + /** + * @param $affiliation string + * @return $this + */ + public function setAffiliation($affiliation) + { + $this->affiliation = (string)$affiliation; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * Set nickname for chat + * + * @param $nickname string + * @return $this + */ + public function setNickname($nickname) + { + $this->nickname = (string)$nickname; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getNickname() + { + return $this->nickname; + } + + + /** + * @return string + */ + public function getReason() + { + return $this->reason; + } + + /** + * @param $reason string + * @return $this + */ + public function setReason($reason) + { + $this->reason = (string)$reason; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/Room/RequestRoomConfigForm.php b/src/Protocol/Room/RequestRoomConfigForm.php index 6753ce5..e3375ff 100644 --- a/src/Protocol/Room/RequestRoomConfigForm.php +++ b/src/Protocol/Room/RequestRoomConfigForm.php @@ -14,7 +14,7 @@ /** * Class RoomRequestConfig - * request from for configuring the room + * request form for configuring the room * user must be owner * * @see https://xmpp.org/extensions/xep-0045.html#createroom-reserved @@ -22,16 +22,6 @@ */ class RequestRoomConfigForm implements ProtocolImplementationInterface { - /** - * role of user in MUC - * @see https://xmpp.org/extensions/xep-0045.html#affil - */ - const AFFILIATION_OWNER = 'owner'; - const AFFILIATION_ADMIN = 'admin'; - const AFFILIATION_MEMBER = 'member'; - const AFFILIATION_OUTCAST = 'outcast'; - const AFFILIATION_NONE = 'none'; - /** * @var string - name of MUC server */ diff --git a/src/Protocol/Room/Room.php b/src/Protocol/Room/Room.php index 9f9e540..2d39e2c 100644 --- a/src/Protocol/Room/Room.php +++ b/src/Protocol/Room/Room.php @@ -95,11 +95,18 @@ class Room */ protected $jid; + /** + * @return string + */ public function getName() { return $this->name; } + /** + * @param string|null $name + * @return $this + */ public function setName($name = null) { if (null === $name || '' === $name) { @@ -110,11 +117,18 @@ public function setName($name = null) return $this; } + /** + * @return string + */ public function getJid() { return $this->jid; } + /** + * @param $jid + * @return $this + */ public function setJid($jid) { $this->jid = (string)$jid; diff --git a/src/Protocol/Room/RoomConfig.php b/src/Protocol/Room/RoomConfig.php index 49154f6..a8eb024 100644 --- a/src/Protocol/Room/RoomConfig.php +++ b/src/Protocol/Room/RoomConfig.php @@ -8,7 +8,9 @@ use Fabiang\Xmpp\Util\XML; /** - * Class CreateReservedRoom + * Send configuration of the room to the server + * + * Class RoomConfig * @package Fabiang\Xmpp\Protocol\Room */ class RoomConfig implements ProtocolImplementationInterface @@ -52,7 +54,7 @@ public function toString() return XML::quoteMessage( "" . "" . - "" .//$this->form->toString() . + $this->form->toString() . "" . "", $this->getFrom(), diff --git a/src/Protocol/Room/RoomList.php b/src/Protocol/Room/RoomList.php index 52c2943..900a089 100644 --- a/src/Protocol/Room/RoomList.php +++ b/src/Protocol/Room/RoomList.php @@ -18,7 +18,7 @@ * list of rooms * @package Fabiang\Xmpp\Protocol */ -class Room implements ProtocolImplementationInterface +class RoomList implements ProtocolImplementationInterface { /** diff --git a/src/Protocol/Room/RoomPresence.php b/src/Protocol/Room/RoomPresence.php index ac86aa1..2a91111 100644 --- a/src/Protocol/Room/RoomPresence.php +++ b/src/Protocol/Room/RoomPresence.php @@ -12,6 +12,12 @@ use Fabiang\Xmpp\Protocol\ProtocolImplementationInterface; use Fabiang\Xmpp\Util\XML; +/** + * @see https://xmpp.org/extensions/xep-0045.html#enter-pres + * + * Class RoomPresence + * @package Fabiang\Xmpp\Protocol\Room + */ class RoomPresence implements ProtocolImplementationInterface { From b453e31f72ec36218325b620e78291b2382e97bb Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Fri, 28 Apr 2017 15:01:24 +0500 Subject: [PATCH 10/16] when socket buffer is small eventManager can't detect endTag - hook: enlarge buffer --- src/Connection/Socket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Connection/Socket.php b/src/Connection/Socket.php index 4ce0a11..1adab9f 100644 --- a/src/Connection/Socket.php +++ b/src/Connection/Socket.php @@ -50,7 +50,7 @@ class Socket extends AbstractConnection implements SocketConnectionInterface { - const DEFAULT_LENGTH = 4096; + const DEFAULT_LENGTH = 65536; const STREAM_START = <<<'XML' From d3b0d02ebb1b250cb2887b7454c7f97d73c10f73 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Mon, 1 May 2017 13:15:47 +0500 Subject: [PATCH 11/16] extend pubsub --- examples/example.php | 3 +- examples/room.php | 77 +++-- src/EventListener/Stream/Pubsub.php | 77 +++++ src/EventListener/Stream/RoomOwner.php | 16 - src/EventListener/Stream/Stanzas.php | 7 + src/Exception/Stream/PubsubErrorException.php | 76 +++++ src/Protocol/Pubsub/BookmarkItem.php | 183 +++++++++++ src/Protocol/Pubsub/BookmarkItemInterface.php | 50 +++ src/Protocol/Pubsub/PubsubGet.php | 142 +++++++++ src/Protocol/Pubsub/PubsubItemInterface.php | 31 ++ src/Protocol/Pubsub/PubsubSet.php | 291 ++++++++++++++++++ src/Protocol/Room/Bookmark.php | 208 ------------- src/Protocol/User/User.php | 71 +++++ 13 files changed, 987 insertions(+), 245 deletions(-) create mode 100644 src/Exception/Stream/PubsubErrorException.php create mode 100644 src/Protocol/Pubsub/BookmarkItem.php create mode 100644 src/Protocol/Pubsub/BookmarkItemInterface.php create mode 100644 src/Protocol/Pubsub/PubsubGet.php create mode 100644 src/Protocol/Pubsub/PubsubItemInterface.php create mode 100644 src/Protocol/Pubsub/PubsubSet.php delete mode 100644 src/Protocol/Room/Bookmark.php diff --git a/examples/example.php b/examples/example.php index 2f839da..e32baee 100644 --- a/examples/example.php +++ b/examples/example.php @@ -20,7 +20,8 @@ $options = new Options($address); $options->setLogger($logger) ->setUsername($config['login']) - ->setPassword($config['password']); + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); $client = new Client($options); diff --git a/examples/room.php b/examples/room.php index c993d26..241db18 100644 --- a/examples/room.php +++ b/examples/room.php @@ -7,7 +7,9 @@ use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; use Fabiang\Xmpp\Exception\Stream\StreamErrorException; use Fabiang\Xmpp\Options; -use Fabiang\Xmpp\Protocol\Room\Bookmark; +use Fabiang\Xmpp\Protocol\Pubsub\BookmarkItem; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubGet; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubSet; use Fabiang\Xmpp\Protocol\Room\Membership; use Fabiang\Xmpp\Protocol\Room\RequestRoomConfigForm; use Fabiang\Xmpp\Protocol\Room\Room; @@ -127,18 +129,6 @@ fwrite(STDOUT, 'Room success configured' . PHP_EOL); - // add room in a roster - $bookmark = new Bookmark( - $config['login'] . '@' . $config['host'], - $client->getOptions()->getRoom()->getJid(), - $client->getOptions()->getRoom()->getName(), - $newUser, - true - ); - - $client->send($bookmark); - - try { $member = new Membership( @@ -166,6 +156,7 @@ $client->disconnect(); + // the room is not in user roster // so add it into bookmarks fwrite(STDOUT, 'Login as ' . $newUser . PHP_EOL); @@ -180,17 +171,63 @@ $client->connect(); + try { - $bookmark = new Bookmark( + // firstly, we must get all bookmarks + $pubsub = new PubsubGet( $newUser . '@' . $config['host'], - $room . '@' . $config['conference'], - 'New cool room', - $newUser, - true + '',// set this option empty for bookmarks, in other cases this option must by something like this pubsub.xmpp.stie.com + PubsubGet::NODE_BOOKMARKS ); - $client->send($bookmark); - fwrite(STDOUT, 'Bookmark for ' . $room . '@' . $config['conference'] . ' is created.' . PHP_EOL); + $client->send($pubsub); + +} catch (StanzasErrorException $e) { + if ($e->getCode() == StanzasErrorException::ERROR_ITEM_NOT_FOUND) { + fwrite(STDOUT, 'Bookmarks is empty' . PHP_EOL); + } else { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; + } + +} + +try { + // secondly, we must check that bookmark does not exists + $exists = false; + /** @var BookmarkItem $item */ + foreach ($client->getOptions()->getUser()->getPubsubs(PubsubGet::NODE_BOOKMARKS) as $item) { + if ($item->getJid() == $room . '@' . $config['conference']) { + $exists = true; + break; + } + } + + if (!$exists) { + // add bookmark to a list + $bookmark = new BookmarkItem( + $room . '@' . $config['conference'], + 'New cool room', + true, + $newUser + ); + $client->getOptions()->getUser()->addBookmark($bookmark); + + $bookmarkSet = new PubsubSet( + $newUser . '@' . $config['host'], + PubsubSet::NODE_BOOKMARKS); + + $bookmarkSet->setItems( + $client->getOptions()->getUser()->getPubsubs(PubsubGet::NODE_BOOKMARKS) + ); + + $client->send($bookmarkSet); + + + fwrite(STDOUT, 'Bookmark for ' . $room . '@' . $config['conference'] . ' is created.' . PHP_EOL); + } else { + fwrite(STDOUT, 'Bookmark ' . $room . '@' . $config['conference'] . ' exists.' . PHP_EOL); + } } catch (StreamErrorException $e) { fwrite(STDOUT, 'Can\'t create bookmark for ' . $room . '@' . $config['conference'] . '.' . PHP_EOL); fwrite(STDOUT, $e->getMessage() . PHP_EOL); diff --git a/src/EventListener/Stream/Pubsub.php b/src/EventListener/Stream/Pubsub.php index e51bfd1..2a8d971 100644 --- a/src/EventListener/Stream/Pubsub.php +++ b/src/EventListener/Stream/Pubsub.php @@ -39,6 +39,10 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\Exception\Stream\PubsubErrorException; +use Fabiang\Xmpp\Protocol\Pubsub\BookmarkItem; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubGet; +use Fabiang\Xmpp\Protocol\User\User; /** * pubsub is using in many cases @@ -79,6 +83,28 @@ public function attachEvents() ->attach('{http://jabber.org/protocol/pubsub}pubsub', array($this, 'query')); $this->getInputEventManager() ->attach('{jabber:client}iq', array($this, 'result')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub}pubsub', array($this, 'collection')); + /** + * errors + * @see https://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-error + */ + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}jid-required', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}subid-required', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}invalid-subid', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}not-subscribed', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}unsupported', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}closed-node', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}not-in-roster-group', array($this, 'error')); + $this->getInputEventManager() + ->attach('{http://jabber.org/protocol/pubsub#errors}presence-subscription-required', array($this, 'error')); } /** @@ -109,6 +135,57 @@ public function result(XMLEvent $event) } } + /** + * @param XMLEvent $event + */ + public function collection(XMLEvent $event) + { + $this->blocking = false; + + $element = $event->getParameter(0); + + if (!$this->getOptions()->getUser()) { + $this->getOptions()->setUser(new User()); + } + $user = $this->getOptions()->getUser(); + + /** + * bookmark items + * + * @see https://xmpp.org/extensions/xep-0048.html#storage-pubsub-retrieve + */ + $items = $element->getElementsByTagName('conference'); + if ($items && $items->length > 0) { + /** @var \DOMElement $item */ + foreach ($items as $item) { + $bookmark = new BookmarkItem( + $item->getAttribute('jid'), + $item->getAttribute('name'), + $item->getAttribute('autojoin') + ); + if ($item->firstChild) { + $bookmark->setNickname($item->firstChild->textContent); + } + + $user->addPubsub(PubsubGet::NODE_BOOKMARKS, $bookmark); + } + } + } + + /** + * we have some errors. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event + * @return void + */ + public function error(XMLEvent $event) + { + if ($event->isEndTag()) { + $this->blocking = false; + throw PubsubErrorException::createFromEvent($event); + } + } + /** * Get generated id. * diff --git a/src/EventListener/Stream/RoomOwner.php b/src/EventListener/Stream/RoomOwner.php index 7adcee6..a08c640 100644 --- a/src/EventListener/Stream/RoomOwner.php +++ b/src/EventListener/Stream/RoomOwner.php @@ -40,7 +40,6 @@ use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; -use Fabiang\Xmpp\Exception\Stream\StanzasErrorException; use Fabiang\Xmpp\Form\RoomForm; use Fabiang\Xmpp\Protocol\User\User; @@ -104,21 +103,6 @@ public function result(XMLEvent $event) } } - /** - * we have some errors. - * - * @param \Fabiang\Xmpp\Event\XMLEvent $event - * @return void - */ - public function error(XMLEvent $event) - { - if (false === $event->isStartTag()) { - $this->blocking = false; - /** @var $event \DOMElement */ - throw StanzasErrorException::createFromEvent($event); - } - } - /** * {@inheritDoc} */ diff --git a/src/EventListener/Stream/Stanzas.php b/src/EventListener/Stream/Stanzas.php index 2ad1a28..6dc881e 100644 --- a/src/EventListener/Stream/Stanzas.php +++ b/src/EventListener/Stream/Stanzas.php @@ -83,6 +83,13 @@ public function attachEvents() ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}not-acceptable', array($this, 'error')); $this->getInputEventManager() ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}jid-malformed', array($this, 'error')); + /** + * Pubsub + * @see https://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-error + */ + $this->getInputEventManager() + ->attach('{urn:ietf:params:xml:ns:xmpp-stanzas}payment-required', array($this, 'error')); + } /** diff --git a/src/Exception/Stream/PubsubErrorException.php b/src/Exception/Stream/PubsubErrorException.php new file mode 100644 index 0000000..a23a11c --- /dev/null +++ b/src/Exception/Stream/PubsubErrorException.php @@ -0,0 +1,76 @@ + + * @copyright 2014 Fabian Grutschus. All rights reserved. + * @license BSD + * @link http://github.com/fabiang/xmpp + */ + +namespace Fabiang\Xmpp\Exception\Stream; + +use Fabiang\Xmpp\Event\XMLEvent; + +/** + * Class PubsubErrorException + * @package Fabiang\Xmpp\Exception\Stream + */ +class PubsubErrorException extends StreamErrorException +{ + + /** + * Create exception from XMLEvent object. + * + * @param \Fabiang\Xmpp\Event\XMLEvent $event XMLEvent object + * + * @return static + */ + public static function createFromEvent(XMLEvent $event) + { + /* @var $element \DOMElement */ + list($element) = $event->getParameters(); + + /* @var $first \DOMElement */ + $parent = $element->parentNode; + + if (null !== $parent && XML_ELEMENT_NODE === $parent->nodeType) { + $message = 'Pubsub error: "' . $element->localName . '"'; + } else { + $message = 'Generic stream error'; + } + + if ($feature = $element->getAttribute('feature')) { + $message .= ' (' . $feature . ')'; + } + $exception = new static($message); + $exception->setContent($element->ownerDocument->saveXML($element)); + + return $exception; + } +} \ No newline at end of file diff --git a/src/Protocol/Pubsub/BookmarkItem.php b/src/Protocol/Pubsub/BookmarkItem.php new file mode 100644 index 0000000..56957b5 --- /dev/null +++ b/src/Protocol/Pubsub/BookmarkItem.php @@ -0,0 +1,183 @@ +setJid($jid) + ->setName($name) + ->setAutoJoin($autojoin) + ->setNickname($nickname); + } + + public function toString() + { + return XML::quoteMessage( + "" . + "%s" . + "", + $this->getName(), + $this->getJid(), + $this->getAutoJoin(), + $this->getNickname() + ); + } + + /** + * @return string + */ + public function getOuterStartTag() + { + return "" . + ""; + } + + /** + * @return string + */ + public function getOuterEndTag() + { + return "" . + ""; + } + + /** + * parse conference node + * + * @param DOMElement $item + * @return $this + */ + public static function parseItem(DOMElement $item) + { + $jid = $item->getAttribute('jid'); + $name = $item->getAttribute('name'); + $autojoin = $item->getAttribute('autojoin') == 'true'; + + $nickname = ''; + if ($item->firstChild) { + $nickname = $item->firstChild->textContent; + } + + return new self($jid, $name, $autojoin, $nickname); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param $name string + * @return $this + */ + public function setName($name) + { + $this->name = (string)$name; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getJid() + { + return $this->jid; + } + + /** + * Set jabberID. + * + * @param $jid string + * @return $this + */ + public function setJid($jid) + { + $this->jid = (string)$jid; + return $this; + } + + /** + * @param $autojoin bool + * @return $this + */ + public function setAutoJoin($autojoin) + { + $this->autojoin = (bool)$autojoin; + return $this; + } + + /** + * @return string + */ + public function getAutoJoin() + { + return $this->autojoin === false ? 'false' : 'true'; + } + + /** + * Set nickname for chat + * + * @param $nickname string + * @return $this + */ + public function setNickname($nickname) + { + $this->nickname = (string)$nickname; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getNickname() + { + return $this->nickname; + } + +} \ No newline at end of file diff --git a/src/Protocol/Pubsub/BookmarkItemInterface.php b/src/Protocol/Pubsub/BookmarkItemInterface.php new file mode 100644 index 0000000..f675adc --- /dev/null +++ b/src/Protocol/Pubsub/BookmarkItemInterface.php @@ -0,0 +1,50 @@ +setFrom($from) + ->setTo($to) + ->setNode($node); + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "getTo() ? "to='" . XML::quote($this->getTo()) . "' " : "") . + "id='%s'>" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getNode() + ); + } + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * pubsub service address, for example: pubsub.xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + /** + * @return string + */ + public function getNode() + { + return $this->node; + } + + /** + * @param string $node + * @return $this + */ + public function setNode($node) + { + $this->node = (string)$node; + return $this; + } +} \ No newline at end of file diff --git a/src/Protocol/Pubsub/PubsubItemInterface.php b/src/Protocol/Pubsub/PubsubItemInterface.php new file mode 100644 index 0000000..4aeafbb --- /dev/null +++ b/src/Protocol/Pubsub/PubsubItemInterface.php @@ -0,0 +1,31 @@ +setFrom($from) + ->setNode($node) + ->setAccessModel($access_model) + ->setPersistent($persistent); + if ($to) { + $this->setTo($to); + } + } + + /** + * {@inheritdoc} + */ + public function toString() + { + return XML::quoteMessage( + "" . + "" . + $this->composeItems() . + "" . + "" . + "" . + "http://jabber.org/protocol/pubsub#publish-options" . + "" . + "" . + "%s" . + "" . + "" . + "%s" . + "" . + "" . + "" . + "" . + "", + $this->getFrom(), + XML::generateId(), + $this->getPersistent(), + $this->getAccessModel() + ); + } + + /** + * Get JabberID. + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set jabberID. + * + * @param $from string + * @return $this + */ + public function setFrom($from) + { + $this->from = (string)$from; + return $this; + } + + + /** + * Get server address. + * + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * set node name + * + * @param $node + * @return $this + */ + public function setNode($node) + { + $this->node = (string)$node; + return $this; + } + + /** + * @return string + */ + public function getNode() + { + return $this->node; + } + + /** + * pubsub service address, for example: pubsub.xmpp.example.org + * + * @param $to string + * @return $this + */ + public function setTo($to) + { + $this->to = (string)$to; + return $this; + } + + /** + * @param $model + * @return $this + */ + public function setAccessModel($model) + { + + $this->accessModel = (string)$model; + return $this; + } + + /** + * @return string + */ + public function getAccessModel() + { + return $this->accessModel; + } + + /** + * @param $persistent + * @return $this + */ + public function setPersistent($persistent) + { + + $this->persistent = (string)$persistent; + return $this; + } + + /** + * @return string + */ + public function getPersistent() + { + return $this->persistent; + } + + /** + * @param $items PubsubItemInterface[] + * @return $this + */ + public function setItems($items) + { + foreach ($items as $contact) { + $this->addContact($contact); + } + return $this; + } + + /** + * @return PubsubItemInterface[] + */ + public function getItems() + { + return $this->items; + } + + /** + * @param PubsubItemInterface $contact + * @return $this + */ + public function addContact(PubsubItemInterface $contact) + { + array_push($this->items, $contact); + return $this; + } + + /** + * compose XML string of items for publishing + * + * @return string + */ + protected function composeItems() + { + $result = ""; + + foreach ($this->getItems() as $key => $item) { + // we must set parent XML nodes for collection + if ($key == 0) { + $result .= $item->getOuterStartTag(); + } + $result .= $item->toString(); + // close collection tags + if (($key + 1) == count($this->getItems())) { + $result .= $item->getOuterEndTag(); + } + } + return $result . ''; + } +} \ No newline at end of file diff --git a/src/Protocol/Room/Bookmark.php b/src/Protocol/Room/Bookmark.php deleted file mode 100644 index 8602e2b..0000000 --- a/src/Protocol/Room/Bookmark.php +++ /dev/null @@ -1,208 +0,0 @@ -setFrom($from) - ->setJid($jid) - ->setName($name) - ->setAutoJoin($autojoin) - ->setNickname($nickname); - } - - /** - * {@inheritdoc} - */ - public function toString() - { - return XML::quoteMessage( - "" . - "" . - "" . - "" . - "" . - "" . - "%s" . - "" . - "" . - "" . - "" . - "" . - "" . - "" . - "http://jabber.org/protocol/pubsub#publish-options" . - "" . - "" . - "true" . - "" . - "" . - "whitelist" . - "" . - "" . - "" . - "" . - "", - $this->getFrom(), - XML::generateId(), - $this->getName(), - $this->getJid(), - $this->getAutoJoin(), - $this->getNickname() - ); - } - - /** - * Get JabberID. - * - * @return string - */ - public function getFrom() - { - return $this->from; - } - - /** - * Set jabberID. - * - * @param $from string - * @return $this - */ - public function setFrom($from) - { - $this->from = (string)$from; - return $this; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param $name string - * @return $this - */ - public function setName($name) - { - $this->name = (string)$name; - return $this; - } - - /** - * Get JabberID. - * - * @return string - */ - public function getJid() - { - return $this->jid; - } - - /** - * Set jabberID. - * - * @param $jid string - * @return $this - */ - public function setJid($jid) - { - $this->jid = (string)$jid; - return $this; - } - - /** - * @param $autojoin bool - * @return $this - */ - public function setAutoJoin($autojoin) - { - $this->autojoin = (bool)$autojoin; - return $this; - } - - /** - * @return string - */ - public function getAutoJoin() - { - return $this->autojoin === false ? 'false' : 'true'; - } - - /** - * Set nickname for chat - * - * @param $nickname string - * @return $this - */ - public function setNickname($nickname) - { - $this->nickname = (string)$nickname; - return $this; - } - - /** - * Get JabberID. - * - * @return string - */ - public function getNickname() - { - return $this->nickname; - } -} \ No newline at end of file diff --git a/src/Protocol/User/User.php b/src/Protocol/User/User.php index ba78d8c..9531838 100644 --- a/src/Protocol/User/User.php +++ b/src/Protocol/User/User.php @@ -35,6 +35,9 @@ */ namespace Fabiang\Xmpp\Protocol\User; +use Fabiang\Xmpp\Protocol\Pubsub\BookmarkItem; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubGet; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubItemInterface; /** * User object. @@ -68,11 +71,23 @@ class User */ protected $groups = array(); + /** + * @var array + */ + protected $pubsubs = array(); + + /** + * @return string + */ public function getName() { return $this->name; } + /** + * @param string $name + * @return $this + */ public function setName($name = null) { if (null === $name || '' === $name) { @@ -83,42 +98,98 @@ public function setName($name = null) return $this; } + /** + * @return string + */ public function getJid() { return $this->jid; } + /** + * @param $jid + * @return $this + */ public function setJid($jid) { $this->jid = (string) $jid; return $this; } + /** + * @return string + */ public function getSubscription() { return $this->subscription; } + /** + * @param string $subscription + * @return $this + */ public function setSubscription($subscription) { $this->subscription = (string) $subscription; return $this; } + /** + * @return array + */ public function getGroups() { return $this->groups; } + /** + * @param array $groups + * @return $this + */ public function setGroups(array $groups) { $this->groups = $groups; return $this; } + /** + * @param $group + * @return $this + */ public function addGroup($group) { $this->groups[] = (string) $group; return $this; } + + /** + * @param BookmarkItem $item + */ + public function addBookmark(BookmarkItem $item){ + $this->addPubsub(PubsubGet::NODE_BOOKMARKS, $item); + } + + /** + * @param $node + * @param PubsubItemInterface $item + * @return $this + */ + public function addPubsub($node, PubsubItemInterface $item){ + if(!array_key_exists($node, $this->pubsubs)){ + $this->pubsubs[$node] = array(); + } + array_push($this->pubsubs[$node], $item); + return $this; + } + + /** + * @param $node + * @return array + */ + public function getPubsubs($node){ + if(array_key_exists($node, $this->pubsubs)){ + return $this->pubsubs[$node]; + } + return array(); + } } From 3e816edfc54225c9aa640ab5e3d4052c7f23bcd3 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Thu, 11 May 2017 15:59:52 +0500 Subject: [PATCH 12/16] vCard url for photo --- examples/room.php | 16 +++++++++------- examples/vcard.php | 3 ++- src/Protocol/User/User.php | 24 ++++++++++++++---------- src/Protocol/User/VCardUpdate.php | 17 +++++++++++++++++ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/examples/room.php b/examples/room.php index 241db18..cafd159 100644 --- a/examples/room.php +++ b/examples/room.php @@ -63,19 +63,20 @@ if (!$client->getOptions()->getRoom()->isOwner()) { - // this case is often occurs when persistent room is already exists + // this case is often occurs when persistent room is already exists, + // because jabber server does not returns current user affiliation on presence // TODO: check room affiliation of current user fwrite(STDOUT, 'You are not owner of this room, so forbidden to configuring it.' . PHP_EOL); } try { - $presenceForm = new RequestRoomConfigForm( + $requestForm = new RequestRoomConfigForm( $config['login'] . '@' . $config['host'], $config['conference'], $room ); - $client->send($presenceForm); + $client->send($requestForm); $form = $client->getOptions()->getForm(); if (!$client->getOptions()->getRoom()) { @@ -88,7 +89,7 @@ **/ $form->setFieldValue('muc#roomconfig_roomname', $client->getOptions()->getRoom()->getName()); $form->setFieldValue('muc#roomconfig_roomdesc', 'Some description...'); - // no public logging. before turn this option on you must check that logging is enabled + // no public logging. before turn on this option on you must check that logging is enabled $form->setFieldValue('muc#roomconfig_enablelogging', Room::CONFIG_NO); // only owner can change name of this room $form->setFieldValue('muc#roomconfig_changesubject', Room::CONFIG_NO); @@ -156,8 +157,7 @@ $client->disconnect(); - -// the room is not in user roster +// the newly created room is not in user roster // so add it into bookmarks fwrite(STDOUT, 'Login as ' . $newUser . PHP_EOL); @@ -225,8 +225,10 @@ fwrite(STDOUT, 'Bookmark for ' . $room . '@' . $config['conference'] . ' is created.' . PHP_EOL); + + } else { - fwrite(STDOUT, 'Bookmark ' . $room . '@' . $config['conference'] . ' exists.' . PHP_EOL); + fwrite(STDOUT, 'Bookmark for ' . $room . '@' . $config['conference'] . ' exists.' . PHP_EOL); } } catch (StreamErrorException $e) { fwrite(STDOUT, 'Can\'t create bookmark for ' . $room . '@' . $config['conference'] . '.' . PHP_EOL); diff --git a/examples/vcard.php b/examples/vcard.php index 078d9cd..7168243 100644 --- a/examples/vcard.php +++ b/examples/vcard.php @@ -38,7 +38,8 @@ ->setProperty('EMAIL', 'info@personal-site.com'); -$vCard->setProperty('PHOTO', 'avatar.png'); +$vCard->setProperty('PHOTO', 'avatar.png') + ->setImageUrl("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png"); $image_hash = $vCard->getImageId(); $vCard->setProperty('URL', 'https://personal-site.com'); diff --git a/src/Protocol/User/User.php b/src/Protocol/User/User.php index 9531838..3c95c65 100644 --- a/src/Protocol/User/User.php +++ b/src/Protocol/User/User.php @@ -35,9 +35,10 @@ */ namespace Fabiang\Xmpp\Protocol\User; + use Fabiang\Xmpp\Protocol\Pubsub\BookmarkItem; -use Fabiang\Xmpp\Protocol\Pubsub\PubsubGet; use Fabiang\Xmpp\Protocol\Pubsub\PubsubItemInterface; +use Fabiang\Xmpp\Protocol\Pubsub\PubsubSet; /** * User object. @@ -112,7 +113,7 @@ public function getJid() */ public function setJid($jid) { - $this->jid = (string) $jid; + $this->jid = (string)$jid; return $this; } @@ -130,7 +131,7 @@ public function getSubscription() */ public function setSubscription($subscription) { - $this->subscription = (string) $subscription; + $this->subscription = (string)$subscription; return $this; } @@ -158,15 +159,16 @@ public function setGroups(array $groups) */ public function addGroup($group) { - $this->groups[] = (string) $group; + $this->groups[] = (string)$group; return $this; } /** * @param BookmarkItem $item */ - public function addBookmark(BookmarkItem $item){ - $this->addPubsub(PubsubGet::NODE_BOOKMARKS, $item); + public function addBookmark(BookmarkItem $item) + { + $this->addPubsub(PubsubSet::NODE_BOOKMARKS, $item); } /** @@ -174,8 +176,9 @@ public function addBookmark(BookmarkItem $item){ * @param PubsubItemInterface $item * @return $this */ - public function addPubsub($node, PubsubItemInterface $item){ - if(!array_key_exists($node, $this->pubsubs)){ + public function addPubsub($node, PubsubItemInterface $item) + { + if (!array_key_exists($node, $this->pubsubs)) { $this->pubsubs[$node] = array(); } array_push($this->pubsubs[$node], $item); @@ -186,8 +189,9 @@ public function addPubsub($node, PubsubItemInterface $item){ * @param $node * @return array */ - public function getPubsubs($node){ - if(array_key_exists($node, $this->pubsubs)){ + public function getPubsubs($node) + { + if (array_key_exists($node, $this->pubsubs)) { return $this->pubsubs[$node]; } return array(); diff --git a/src/Protocol/User/VCardUpdate.php b/src/Protocol/User/VCardUpdate.php index fd4ede3..6c18f7f 100644 --- a/src/Protocol/User/VCardUpdate.php +++ b/src/Protocol/User/VCardUpdate.php @@ -80,6 +80,11 @@ class VCardUpdate implements ProtocolImplementationInterface */ protected $imageId; + /** + * @var string|null + */ + protected $imageUrl; + /** * Allowed image mime types * @var array @@ -204,6 +209,18 @@ public function setProperty($property, $value) return $this; } + /** + * @param $url + * @return $this + */ + public function setImageUrl($url) + { + if (filter_var($url, FILTER_VALIDATE_URL) && isset($this->vCard['PHOTO'])) { + $this->vCard['PHOTO']['EXTVAL'] = $url; + } + return $this; + } + /** * Set image content * From 69f071097defd20a47e1807199fe6a1efb24570d Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Sat, 27 May 2017 11:53:18 +0500 Subject: [PATCH 13/16] vCard url for photo --- src/EventListener/Stream/Membership.php | 38 ++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/EventListener/Stream/Membership.php b/src/EventListener/Stream/Membership.php index a18bcdd..ae77851 100644 --- a/src/EventListener/Stream/Membership.php +++ b/src/EventListener/Stream/Membership.php @@ -43,6 +43,8 @@ /** * Listener * + * @see https://xmpp.org/extensions/xep-0045.html#grantmember + * * @package Xmpp\EventListener */ class Membership extends AbstractEventListener implements BlockingEventListenerInterface @@ -70,15 +72,18 @@ public function attachEvents() $this->getOutputEventManager() ->attach('{http://jabber.org/protocol/muc#admin}query', array($this, 'query')); $this->getInputEventManager() - ->attach('{http://jabber.org/protocol/muc#admin}query', array($this, 'result')); + ->attach('{jabber:client}iq', array($this, 'result')); } /** - * Blocking event + * @param XMLEvent $event */ - public function query() + public function query(XMLEvent $event) { $this->blocking = true; + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + $this->setId($element->parentNode->getAttribute('id')); } /** @@ -90,10 +95,35 @@ public function query() public function result(XMLEvent $event) { if ($event->isEndTag()) { - $this->blocking = false; + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + if ($this->getId() === $element->getAttribute('id')) { + $this->blocking = false; + } } } + /** + * Get generated id. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set generated id. + * + * @param string $id + * @return void + */ + public function setId($id) + { + $this->id = (string)$id; + } + /** * {@inheritDoc} */ From a2a7827223dca254294ecc40987c24be51f1ba62 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Mon, 29 May 2017 11:39:16 +0500 Subject: [PATCH 14/16] unBlocking interface for Membership, Pubsub on Stanzas errors --- src/EventListener/Stream/Membership.php | 11 ++++++++++- src/EventListener/Stream/Pubsub.php | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/EventListener/Stream/Membership.php b/src/EventListener/Stream/Membership.php index ae77851..51d2f20 100644 --- a/src/EventListener/Stream/Membership.php +++ b/src/EventListener/Stream/Membership.php @@ -39,6 +39,7 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; /** * Listener @@ -47,7 +48,7 @@ * * @package Xmpp\EventListener */ -class Membership extends AbstractEventListener implements BlockingEventListenerInterface +class Membership extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface { /** * Generated id. @@ -131,4 +132,12 @@ public function isBlocking() { return $this->blocking; } + + /** + * {@inheritDoc} + */ + public function unBlock() + { + $this->blocking = false; + } } diff --git a/src/EventListener/Stream/Pubsub.php b/src/EventListener/Stream/Pubsub.php index 2a8d971..836e8a6 100644 --- a/src/EventListener/Stream/Pubsub.php +++ b/src/EventListener/Stream/Pubsub.php @@ -39,6 +39,7 @@ use Fabiang\Xmpp\Event\XMLEvent; use Fabiang\Xmpp\EventListener\AbstractEventListener; use Fabiang\Xmpp\EventListener\BlockingEventListenerInterface; +use Fabiang\Xmpp\EventListener\UnBlockingEventListenerInterface; use Fabiang\Xmpp\Exception\Stream\PubsubErrorException; use Fabiang\Xmpp\Protocol\Pubsub\BookmarkItem; use Fabiang\Xmpp\Protocol\Pubsub\PubsubGet; @@ -48,6 +49,7 @@ * pubsub is using in many cases * * avatars + * * @see https://xmpp.org/extensions/xep-0084.html#process-pubmeta * * bookmarks @@ -57,7 +59,7 @@ * * @package Xmpp\EventListener */ -class Pubsub extends AbstractEventListener implements BlockingEventListenerInterface +class Pubsub extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface { /** * Generated id. @@ -122,6 +124,7 @@ public function query(XMLEvent $event) * Result received. * * @param \Fabiang\Xmpp\Event\XMLEvent $event + * * @return void */ public function result(XMLEvent $event) @@ -176,6 +179,7 @@ public function collection(XMLEvent $event) * we have some errors. * * @param \Fabiang\Xmpp\Event\XMLEvent $event + * * @return void */ public function error(XMLEvent $event) @@ -200,6 +204,7 @@ public function getId() * Set generated id. * * @param string $id + * * @return void */ public function setId($id) @@ -214,4 +219,12 @@ public function isBlocking() { return $this->blocking; } + + /** + * {@inheritDoc} + */ + public function unBlock() + { + $this->blocking = false; + } } From 15c6526daf30a7a904c490cb5d000085f97da653 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Fri, 9 Jun 2017 11:30:41 +0500 Subject: [PATCH 15/16] RoomOwner success configured --- src/Connection/AbstractConnection.php | 3 ++ src/Connection/Socket.php | 1 - src/EventListener/Stream/RoomOwner.php | 54 ++++++++++++++++++++++++-- tests/src/Connection/SocketTest.php | 10 ++--- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/Connection/AbstractConnection.php b/src/Connection/AbstractConnection.php index 2db24ee..70872c0 100644 --- a/src/Connection/AbstractConnection.php +++ b/src/Connection/AbstractConnection.php @@ -279,6 +279,9 @@ protected function checkBlockingListeners() $this->lastBlockingListener = $listener; } $blocking = true; + // if listener in blocking mode and can't receive data long time + // this situation may occurs when we construct EventListener badly - we must check all events + $this->checkTimeout(''); } } diff --git a/src/Connection/Socket.php b/src/Connection/Socket.php index 1adab9f..3a54453 100644 --- a/src/Connection/Socket.php +++ b/src/Connection/Socket.php @@ -162,7 +162,6 @@ public function send($buffer) $this->getOutputStream()->parse($buffer); while ($this->checkBlockingListeners()) { - usleep(100); $this->receive(); } } diff --git a/src/EventListener/Stream/RoomOwner.php b/src/EventListener/Stream/RoomOwner.php index a08c640..f8193c1 100644 --- a/src/EventListener/Stream/RoomOwner.php +++ b/src/EventListener/Stream/RoomOwner.php @@ -50,6 +50,11 @@ */ class RoomOwner extends AbstractEventListener implements BlockingEventListenerInterface, UnBlockingEventListenerInterface { + /** Generated id. + * + * @var string + */ + protected $id; /** * Blocking. @@ -74,16 +79,19 @@ public function attachEvents() ->attach('{http://jabber.org/protocol/muc#owner}query', array($this, 'query')); $this->getInputEventManager() ->attach('{http://jabber.org/protocol/muc#owner}query', array($this, 'result')); + $this->getInputEventManager() + ->attach('{jabber:client}iq', array($this, 'success')); } /** - * Sending a query request for roster sets listener to blocking mode. - * - * @return void + * @param XMLEvent $event */ - public function query() + public function query(XMLEvent $event) { $this->blocking = true; + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + $this->setId($element->parentNode->getAttribute('id')); } /** @@ -103,6 +111,44 @@ public function result(XMLEvent $event) } } + /** + * room success configured + * + * @param XMLEvent $event + */ + public function success(XMLEvent $event) + { + if ($event->isEndTag()) { + /* @var $element \DOMElement */ + $element = $event->getParameter(0); + if ($this->getId() === $element->getAttribute('id')) { + $this->blocking = false; + } + } + } + + /** + * Get generated id. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set generated id. + * + * @param string $id + * + * @return void + */ + public function setId($id) + { + $this->id = (string)$id; + } + /** * {@inheritDoc} */ diff --git a/tests/src/Connection/SocketTest.php b/tests/src/Connection/SocketTest.php index 523a64c..bef0020 100644 --- a/tests/src/Connection/SocketTest.php +++ b/tests/src/Connection/SocketTest.php @@ -36,12 +36,12 @@ namespace Fabiang\Xmpp\Connection; -use Fabiang\Xmpp\Stream\XMLStream; -use Fabiang\Xmpp\Stream\SocketClient; -use Fabiang\Xmpp\EventListener\Stream\Stream; -use Fabiang\Xmpp\Event\EventManager; use Fabiang\Xmpp\Event\Event; +use Fabiang\Xmpp\Event\EventManager; +use Fabiang\Xmpp\EventListener\Stream\Stream; use Fabiang\Xmpp\Options; +use Fabiang\Xmpp\Stream\SocketClient; +use Fabiang\Xmpp\Stream\XMLStream; use Psr\Log\LogLevel; /** @@ -117,7 +117,7 @@ public function testReceive() $mock = $this->object->getSocket(); $mock->expects($this->once()) ->method('read') - ->with($this->equalTo(4096)) + ->with($this->equalTo(65536)) ->will($this->returnValue($return)); $this->assertSame($return, $this->object->receive()); } From db54a77271eb7fa35fc2fdce50bb591cb645a826 Mon Sep 17 00:00:00 2001 From: eLFuvo Date: Sun, 23 Jul 2017 13:18:35 +0500 Subject: [PATCH 16/16] user subscription --- examples/subscribe.php | 112 +++++++++++++++++++++++++++++++++++++ src/Protocol/Presence.php | 38 +++++++++++-- src/Protocol/User/User.php | 26 +++++++-- 3 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 examples/subscribe.php diff --git a/examples/subscribe.php b/examples/subscribe.php new file mode 100644 index 0000000..6950bf2 --- /dev/null +++ b/examples/subscribe.php @@ -0,0 +1,112 @@ +pushHandler(new StreamHandler('xmpp.log', Logger::DEBUG)); + +$address = $config['connectionType'] . '://' . $config['host'] . ':' . $config['port']; + +$newUser = 'testuser'; +$newPassword = '123456'; + +$options1 = new Options($address); +$options1->setLogger($logger) + ->setUsername($config['login']) + ->setPassword($config['password']) + ->setVerifyPeer($config['verifyPeer']); + +$options2 = new Options($address); +$options2->setLogger($logger) + ->setUsername($newUser) + ->setPassword($newPassword) + ->setVerifyPeer($config['verifyPeer']); + +$client1 = new Client($options1); + +$client1->connect(); + +// 0. check subscription state from roster +$roster = new Roster(); +try { + $client1->send($roster); + $userList = $client1->getOptions()->getUsers(); + if (!empty($userList)) { + foreach ($userList as $user) { + if ($user->getJid() == $newUser . '@' . $config['host']) { + fwrite(STDOUT, 'We have subscription for user ' . + $newUser . '@' . $config['host'] . '.' . + PHP_EOL); + + if ($user->isSubscribed()) { + fwrite(STDOUT, 'End.' . PHP_EOL); + $client1->disconnect(); + exit; + } + } + } + fwrite(STDOUT, 'We are not completely subscribed to user ' . $newUser . '@' . $config['host'] . PHP_EOL); + } else { + fwrite(STDOUT, 'Roster is empty!' . PHP_EOL); + } +} catch (StreamErrorException $e) { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; +} + + +// 1. send subscription request +$presence = new Presence(1, $newUser . '@' . $config['host'], Presence::TYPE_SUBSCRIBE); +try { + $client1->send($presence); + fwrite(STDOUT, 'Subscribe request to ' . $newUser . '@' . $config['host'] . ' is sent.' . PHP_EOL); +} catch (StreamErrorException $e) { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; +} + +sleep(1); + +$client2 = new Client($options2); + +$client2->connect(); + +// 2. send subscription request from sender 2 +$presence = new Presence(1, $config['login'] . '@' . $config['host'], Presence::TYPE_SUBSCRIBE); +try { + $client2->send($presence); + // 3. set auto approve for subscribed request + $presence = new Presence(1, $config['login'] . '@' . $config['host'], Presence::TYPE_SUBSCRIBED); + $client2->send($presence); + fwrite(STDOUT, 'Subscribe request to ' . $config['login'] . '@' . $config['host'] . ' is sent.' . PHP_EOL); +} catch (StreamErrorException $e) { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; +} + +sleep(1); + +// 4. approve subscription from sender 1 +$presence = new Presence(1, $newUser . '@' . $config['host'], Presence::TYPE_SUBSCRIBED); +try { + $client1->send($presence); + fwrite(STDOUT, 'Subscribe request from ' . $newUser . '@' . $config['host'] . ' is approved.' . PHP_EOL); +} catch (StreamErrorException $e) { + fwrite(STDOUT, $e->getMessage() . PHP_EOL); + exit; +} +sleep(1); + + +$client1->disconnect(); +$client2->disconnect(); \ No newline at end of file diff --git a/src/Protocol/Presence.php b/src/Protocol/Presence.php index 5d60921..eb7e8d2 100644 --- a/src/Protocol/Presence.php +++ b/src/Protocol/Presence.php @@ -42,6 +42,8 @@ * Protocol setting for Xmpp. * * @package Xmpp\Protocol + * + * @see https://xmpp.org/rfcs/rfc3921.html */ class Presence implements ProtocolImplementationInterface { @@ -137,16 +139,24 @@ class Presence implements ProtocolImplementationInterface */ protected $nickname; + /** + * Presence status + * + * @var string + */ + protected $type; + /** * Presence constructor. * @param int $priority * @param null $to + * @param null $type * @param null $nickname * @param string $show */ - public function __construct($priority = 1, $to = null, $nickname = null, $show = null) + public function __construct($priority = 1, $to = null, $type = null, $nickname = null, $show = null) { - $this->setPriority($priority)->setTo($to)->setShow($show)->setNickname($nickname); + $this->setPriority($priority)->setTo($to)->setShow($show)->setNickname($nickname)->setType($type); } /** @@ -157,7 +167,11 @@ public function toString() $presence = 'getTo()) { - $presence .= ' to="' . XML::quote($this->getTo()) . '/' . XML::quote($this->getNickname()) . '"'; + if ($type = $this->getType()) { + $presence .= ' type="' . $type . '" '; + } + $presence .= ' to="' . XML::quote($this->getTo()) . + ($this->getNickname() ? '/' . XML::quote($this->getNickname()) : '') . '"'; } $presence .= '>'; @@ -254,4 +268,20 @@ public function setPriority($priority) $this->priority = (int)$priority; return $this; } -} + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param string $type + */ + public function setType($type) + { + $this->type = $type; + } +} \ No newline at end of file diff --git a/src/Protocol/User/User.php b/src/Protocol/User/User.php index 3c95c65..17ae612 100644 --- a/src/Protocol/User/User.php +++ b/src/Protocol/User/User.php @@ -48,6 +48,9 @@ class User { + const SUBSCRIBE_NONE = 'none'; + + const SUBSCRIBE_BOTH = 'both'; /** * * @var string @@ -70,12 +73,12 @@ class User * * @var array */ - protected $groups = array(); + protected $groups = []; /** * @var array */ - protected $pubsubs = array(); + protected $pubsubs = []; /** * @return string @@ -96,6 +99,7 @@ public function setName($name = null) } else { $this->name = $name; } + return $this; } @@ -114,6 +118,7 @@ public function getJid() public function setJid($jid) { $this->jid = (string)$jid; + return $this; } @@ -125,6 +130,14 @@ public function getSubscription() return $this->subscription; } + /** + * @return string + */ + public function isSubscribed() + { + return $this->subscription == self::SUBSCRIBE_BOTH; + } + /** * @param string $subscription * @return $this @@ -132,6 +145,7 @@ public function getSubscription() public function setSubscription($subscription) { $this->subscription = (string)$subscription; + return $this; } @@ -150,6 +164,7 @@ public function getGroups() public function setGroups(array $groups) { $this->groups = $groups; + return $this; } @@ -160,6 +175,7 @@ public function setGroups(array $groups) public function addGroup($group) { $this->groups[] = (string)$group; + return $this; } @@ -179,9 +195,10 @@ public function addBookmark(BookmarkItem $item) public function addPubsub($node, PubsubItemInterface $item) { if (!array_key_exists($node, $this->pubsubs)) { - $this->pubsubs[$node] = array(); + $this->pubsubs[$node] = []; } array_push($this->pubsubs[$node], $item); + return $this; } @@ -194,6 +211,7 @@ public function getPubsubs($node) if (array_key_exists($node, $this->pubsubs)) { return $this->pubsubs[$node]; } - return array(); + + return []; } }