* @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany * @homepage http://oss.tiggerswelt.net/twMuc * @copyright Copyright © 2010 tiggersWelt.net **/ require_once ('tiggerXMPP/component.php'); require_once ('tiggerXMPP/presence.php'); require_once ('tiggerXMPP/message.php'); require_once ('tiggerXMPP/xep/0004.php'); require_once ('tiggerXMPP/xep/0004/form.php'); require_once ('tiggerXMPP/xep/0004/field.php'); require_once ('room.php'); /** * twMUC_Handler * ------------- * Handle the whole XMPP-Distribution-Stuff * * @class twMUC_Handler * @package twMUC * @author Bernd Holzmueller **/ class twMUC_Handler extends tiggerXMPP_Component { private $Domains = array (); // {{{ __construct /** * Just override default debug-level * * @access friendly * @return void **/ function __construct ($Domain, $Router, $RouterPort = 5347, $Debug = tiggerXMPP_Stream::DEBUG_PACKETS) { parent::__construct ($Domain, $Router, $RouterPort, $Debug); $this->registerNamespace ('http://jabber.org/protocol/muc', null); $this->registerNamespace ('http://jabber.org/protocol/muc#user', null); $this->registerNamespace ('http://jabber.org/protocol/muc#admin', 'adminHandler'); } // }}} // {{{ getRoomClass /** * Retrive the classname for new muc-rooms * * @access protected * @return string **/ protected function getRoomClass () { return 'twMUC_Room'; } // }}} // {{{ createRoom /** * Create a new room * * @param string $JID * @param string $OwnerJID (optional) * * @access public * @return object **/ public function createRoom ($JID, $OwnerJID = null) { // Retrive the name and domain of the requested room $Name = strtolower ($this->getJID (true, false, false, $JID)); $Domain = strtolower ($this->getJID (false, true, false, $JID)); // Create the room if (!class_exists ($Class = $this->getRoomClass ()) || !(is_subclass_of ($Class, 'twMUC_Room') || ($Class == 'twMUC_Room'))) { trigger_error ('Room-Class does not exist or does not implement twMUC_Room'); return false; } $Handle = new $Class ($this, $Name, $Domain, $OwnerJID); return $this->addRoom ($Handle); } // }}} // {{{ addRoom /** * Append a room to our internal storage * * @param object $Handle * * @access public * @return object **/ public function addRoom ($Handle) { // Make sure the room implements our API if (!($Handle instanceof twMUC_Room)) return false; // Store the room $Name = strtolower ($Handle->getName (false)); $Domain = strtolower ($Handle->getDomain ()); if (!isset ($this->Domains [$Domain])) $this->Domains [$Domain] = array (); $this->Domains [$Domain][$Name] = $Handle; return $Handle; } // }}} // {{{ removeRoom /** * Remove a room from our collection * * @param object $Handle * * @access public * @return bool **/ public function removeRoom ($Handle) { // Make sure the room implements our API if (!($Handle instanceof twMUC_Room)) return false; $Name = strtolower ($Handle->getName (false)); $Domain = strtolower ($Handle->getDomain ()); unset ($this->Domains [$Domain][$Name]); return true; } // }}} // {{{ allowAutoCreateRoom /** * Check wheter to allow auto-creation of rooms * * @param string $roomJID * @param string $Requester (optional) * * @access public * @return bool **/ public function allowAutoCreateRoom ($roomJID, $Requester = null) { return true; } // }}} // {{{ getRoom /** * Retrive the handle of a room on this chat-server * * @param string $JID Name of this room (maybe a Jabber-ID) * * @access public * @return object **/ public function getRoom ($JID, $autoCreate = true) { // Retrive the name and domain of the requested room $Name = strtolower ($this->getJID (true, false, false, $JID)); $Domain = strtolower ($this->getJID (false, true, false, $JID)); // Check if the room-name is empty if (strlen ($Name) < 1) return false; // Check wheter to autocreate to room if ($autoCreate && (!isset ($this->Domains [$Domain]) || !isset ($this->Domains [$Domain][$Name])) && $this->allowAutoCreateRoom ($Name . '@' . $Domain) && !is_object ($this->createRoom ($Name . '@' . $Domain))) return false; // Return the handle return $this->Domains [$Domain][$Name]; } // }}} // {{{ discoRooms /** * Collect room-information for service-discovery * * @param string $JID * @param stirng $Receipient * * @access public * @return array **/ public function discoRooms ($JID, $Receipient) { // Create an empty result $Items = array (); // Make sure that everything is in lower-case $JID = strtolower ($JID); // Check for a query on a room if (strpos ($JID, '@') !== false) { // Don't allow query on users if (strpos ($JID, '/') !== false) return $Items; // Try to get a room-handle if (!is_object ($Room = $this->getRoom ($JID))) return $Items; // List users in room if (!is_array ($Users = $Room->listUsers ())) return $Items; foreach ($Users as $User) if ($User->isOnline ()) $Items [] = new tiggerXMPP_XEP_0030_Item ($Room->getJID () . '/' . $User->getNickname (), $User->getNickname ()); } elseif (isset ($this->Domains [$JID])) foreach ($this->Domains [$JID] as $Room) if ($Room->isExposed ($Receipient)) $Items [] = new tiggerXMPP_XEP_0030_Item ($Room->getJID (), $Room->getName ()); return $Items; } // }}} // {{{ discoRoomInfo /** * Query the Information of a specific room * * @param string $JID * @param stirng $Receipient * * @access public * @return array **/ public function discoRoomInfo ($JID, $Receipient) { // Check if the request is for a room if (!is_object ($Room = $this->getRoom ($JID, false))) return array (); // Return identity of a room-occupant if (strlen ($Nickname = $this->getJID (false, false, true, $JID)) > 0) return array (new tiggerXMPP_XEP_0030_Identity ($Nickname, 'conference', 'text', '', array ('http://jabber.org/protocol/muc'))); // Check wheter to show the room #if (!$Room->isExposed ($Receipient)) # return array (); // Return identity of a room $Features = array ('http://jabber.org/protocol/muc'); $Features [] = ($Room->requireRegistration () ? 'muc_membersonly' : 'muc_open'); $Features [] = ($Room->requirePassword ($Receipient) ? 'muc_passwordprotected' : 'muc_unsecured'); $Features [] = ($Room->isPersistent () ? 'muc_persistent' : 'muc_temporary'); $Features [] = ($Room->isModerated () ? 'muc_moderated' : 'muc_unmoderated'); $Features [] = ($Room->isExposed () ? 'muc_public' : 'muc_hidden'); if ($Room->isUsernameExposed ($Receipient)) $Features [] = 'muc_nonanonymous'; elseif ($Room->anyUsernameExposed ()) $Features [] = 'muc_semianonymous'; // Append XEP-0004 Data-Form $Features [] = $Form = new tiggerXMPP_XEP_0004_Form (); new tiggerXMPP_XEP_0004_Field ('', $Form, 'http://jabber.org/protocol/muc#roominfo', tiggerXMPP_XEP_0004_Field::TYPE_HIDDEN, 'FORM_TYPE'); new tiggerXMPP_XEP_0004_Field ('', $Form, $Room->getSubject (), null, 'muc#roominfo_subject', 'Subject'); new tiggerXMPP_XEP_0004_Field ('', $Form, $Room->getDescription (), null, 'muc#roominfo_description', 'Description'); new tiggerXMPP_XEP_0004_Field ('', $Form, ($Room->allowSubjectChange () ? 'true' : 'false'), tiggerXMPP_XEP_0004_Field::TYPE_BOOLEAN, 'muc#roominfo_changesubject', 'Whether Occupants May Change the Subject'); new tiggerXMPP_XEP_0004_Field ('', $Form, 'true', tiggerXMPP_XEP_0004_Field::TYPE_BOOLEAN, 'muc#roomconfig_changesubject', 'Subject can be modified'); new tiggerXMPP_XEP_0004_Field ('', $Form, 'crone1@shakespeare.lit', null, 'muc#roominfo_contactjid', 'Contact Addresses'); new tiggerXMPP_XEP_0004_Field ('', $Form, '3', null, 'muc#roominfo_occupants', 'Number of occupants'); # new tiggerXMPP_XEP_0004_Field ('', $Form, 'dc=lit,dc=shakespeare,cn=witches', null, 'muc#roominfo_ldapgroup', 'Associated LDAP Group'); new tiggerXMPP_XEP_0004_Field ('', $Form, 'de', null, 'muc#roominfo_lang', 'Language of discussion'); # new tiggerXMPP_XEP_0004_Field ('', $Form, 'http://www.shakespeare.lit/chatlogs/darkcave/', null, 'muc#roominfo_logs', 'URL for discussion logs'); # new tiggerXMPP_XEP_0004_Field ('', $Form, 'xmpp:pubsub.shakespeare.lit?;node=the-darkcave-node', null, 'muc#roominfo_pubsub', 'Associated pubsub node'); return array (new tiggerXMPP_XEP_0030_Identity ($Room->getName (), 'conference', 'text', '', $Features)); } // }}} // {{{ returnIQError /** * Throw an errornous IQ-Packet back to its originator * * @param string $Type * @param string $Subtype * @param string $From * @param stirng $To * @param object $Packet * * @access private * @return bool Always false, because calling this function indicates an error **/ private function returnIQError ($Type, $Subtype, $From, $To, $Packet) { // Find IQ-Packet in chain while ($Packet->getName () != 'iq') { if (!is_object ($P = $Packet->getParent ())) break; $Packet = $P; } // Create a copy of this packet $Packet = clone $Packet; // Rewrite the originator and receiver $Packet->setOriginator ($From); $Packet->setDestination ($To); // Append an Error $Packet->appendError ($Subtype); // Write the packet to the line $this->sendXML ($Packet); return false; } // }}} // {{{ returnPresenceError /** * Generate an error-packet for presence-inquiries * * @param string $Type * @param string $Subtype * @param string $From * @param string $To * @param bool $ExposeMUC (optional) * * @access private * @return bool **/ private function returnPresenceError ($Type, $Subtype, $From, $To, $ExposeMUC = true) { // Create a new Presence-Packet $Presence = tiggerXMPP_Presence::newTag ($To, $From); if ($ExposeMUC) { $X = $Presence->createSubtag ('x'); $X->setNamespace ('http://jabber.org/protocol/muc'); } // Set our error-type (the type-major is handled automatically by tiggerXMPP) $Presence->appendError ($Subtype); // Write the packet to the wire return $this->sendXML ($Presence); } // }}} // {{{ returnMessageError /** * Send an errornous message back to a user * * @param enum $Type * @param enum $Subtype * @param string $From * @param string $To * @param string $Body (optional) * @param string $Subject (optional) * * @access private * @return bool **/ private function returnMessageError ($Type, $Subtype, $From, $To, $Body = null, $Subject = null) { // Create a new Message-Packet $Message = tiggerXMPP_Message::newTag ($To, $From); if ($Body !== null) $Message->setMessage ($Body); if ($Subject !== null) $Message->setSubject ($Subject); // Set our error-type (the type-major is handled automatically by tiggerXMPP) $Message->appendError ($Subtype); // Write the packet to the wire return $this->sendXML ($Message); } // }}} // {{{ contactStatus /** * Handle incoming presence-packets (join rooms and change nicknames) * * @param string $JID JID of the user * @param enum $Status XMPP-Status of user * @param string $Message Status-Message * @param int $Priority Priority * @param string $Receiver Desired Chat-Room and Nickname * * @access protected * @return void **/ protected function contactStatus ($JID, $Status, $Message, $Priority, $Receiver, $Packet) { // Check if the room is there or try to auto-create if ($roomCreate = !is_object ($Room = $this->getRoom ($Receiver, false))) { // Check if we may auto-create the room if (!$this->allowAutoCreateRoom ($Receiver, $JID)) return $this->returnPresenceError ('cancel', 'item-not-found', $Receiver, $JID); // Try to create the room if (!is_object ($Room = $this->createRoom ($Receiver, $JID))) return $this->returnPresenceError ('cancel', 'item-not-found', $Receiver, $JID); } // Retrive and check the desired nickname if (!$Room->checkNickname ($Nickname = $this->getJID (false, false, true, $Receiver))) return $this->returnPresenceError ('modify', 'jid-malformed', $Receiver, $JID); // Check if we change our nickname or just get a contact-status if (($changeNickname = $Room->registeredJID ($JID)) && ($Nickname == ($oldNickname = $Room->getNickname ($JID)))) { # TODO: Just forward the presence return; } // Handle room-joins if (!$changeNickname) { // Check if the room is Members-Only and we are on the list if ($Room->requireRegistration () && !$Room->isRegistered ($JID)) return $this->returnPresenceError ('auth', 'registration-required', $Receiver, $JID); // Handle password-protected rooms if ($Room->requirePassword () && !(is_object ($X = $Packet->getSubtagByName ('x')) && is_object ($P = $X->getSubtagByName ('password')) && $Room->checkPassword ($JID, $P->getValue ()))) return $this->returnPresenceError ('auth', 'not-authorized', $Receiver, $JID); // Check if the user is banned if ($Room->disallowUser ($JID)) return $this->returnPresenceError ('auth', 'forbidden', $Receiver, $JID); } // Check if the nickname is occupied if ($Room->registeredNickname ($Nickname, $JID)) return $this->returnPresenceError ('cancel', 'conflict', $Receiver, $JID); // Check if nickname may be changed if ($changeNickname && $Room->allowNickchange ($JID, $oldNickname, $Nickname)) return $this->returnPresenceError ('cancel', 'not-acceptable', $Receiver, $JID); // Try to join/change nick on the room if ($changeNickname && !is_object ($User = $Room->changeNickname ($JID, $Nickname))) return $this->returnPresenceError ('cancel', 'not-acceptable', $Receiver, $JID); elseif (!$changeNickname && !is_object ($User = $Room->joinUser ($JID, $Nickname))) return $this->returnPresenceError ('wait', 'service-unavailable', $Receiver, $JID); // Nickname is changed now if ($changeNickname) return; // Forward the subject if (strlen ($Subject = $Room->getSubject ()) > 0) { $sMessage = tiggerXMPP_Message::newTag ($JID, $Room->getJID ()); $sMessage->setSubject ($Subject); $sMessage->setType (tiggerXMPP_Message::TYPE_GROUPCHAT); $this->sendXML ($sMessage); } // Forward the log to the new user if (count ($History = $Room->getHistory ()) > 0) { $minTimestamp = 0; $maxSize = null; // Handle filter-settings if (is_object ($X = $Packet->getSubtagByName ('x')) && is_object ($hSettings = $X->getSubtagByName ('history'))) { // Filter by seconds if (($Secs = $hSettings->getAttribute ('seconds', false)) !== false) $minTimestamp = time () - $Secs - 1; // Handle minimum date if (($TS = $hSettings->getAttribute ('since', false)) !== false) $minTimestamp = max ($minTimestamp, strtotime ($TS)); // Handle maximum packets if (($C = $hSettings->getAttribute ('maxstanzas', false)) !== false) while (count ($History) > $C) array_shift ($History); $maxSize = $hSettings->getAttribute ('maxchars', null); } if (($maxSize === null) || ($maxSize != 0)) { $curSize = 0; $sMessage = tiggerXMPP_Message::newTag ($JID); $sMessage->setType (tiggerXMPP_Message::TYPE_GROUPCHAT); $Delay = new tiggerXMPP_Packet ('delay', $sMessage); $Delay->setNamespace ('urn:xmpp:delay'); foreach ($History as $Message) { // Handle Second-Filter if ($Message->getTimestamp () < $minTimestamp) continue; // Prepare and submit the message $sMessage->setMessage ($Message->getMessage ()); $sMessage->setOriginator ($Message->getJID ()); $Delay->setOriginator ($Message->getJID ()); $Delay->setAttribute ('stamp', date ('c', $Message->getTimestamp ())); if (($maxSize !== null) && (strlen ($sMessage->toString ()) + $curSize > $maxSize)) break; $curSize += $this->sendXML ($sMessage, null, true); } } } return true; } // }}} // {{{ contactOffline /** * A user wants to leave * * @param string $JID * @param string $Message * @param string $Receiver * * @access protected * @return bool **/ protected function contactOffline ($JID, $Message, $Receiver) { // Request the room-object if (!is_object ($Room = $this->getRoom ($Receiver))) return $this->returnPresenceError ('cancel', 'item-not-found', $Receiver, $JID); // List all users in room if (!is_array ($Users = $Room->listUsers ())) return $this->returnPresenceError ('wait', 'internal-server-error', $Receiver, $JID); // Tell the room that someone left if (!is_object ($User = $Room->partUser ($JID, $this->getJID (false, false, true, $Receiver), $Message))) return $this->returnPresenceError ('wait', 'internal-server-error', $Receiver, $JID); return true; } // }}} // {{{ receiveMessage /** * Receive and forward a message * * @param string $JID * @param string $Subject * @param string $Body * @param enum $Type (optional) * @param string $ID (optional) * @param string $Receiver (optional) * @param string $Thread (optional) * @param object $Packet (optional) * * @access protected * @return bool */ protected function receiveMessage ($JID, $Subject, $Body, $Type = self::MESSAGE_NORMAL, $ID = null, $Receiver = null, $Thread = null, $Packet = null) { // Request the room-object if (!is_object ($Room = $this->getRoom ($Receiver))) return $this->returnMessageError ('cancel', 'item-not-found', $Receiver, $JID, $Body); // Retrive the requested nickname $Nickname = $this->getJID (false, false, true, $Receiver); // Create a message for forwarding $Message = tiggerXMPP_Message::newTag (); $Message->setMessage ($Body); // Handle Groupchat-Messages if ($Type == tiggerXMPP_Message::TYPE_GROUPCHAT) { // Retrive the originator if (!($Originator = $Room->getNickname ($JID))) return $this->returnMessageError ('auth', 'not-authorized', $Receiver, $JID, $Body); // Don't allow groupchat-messages to a user if (strlen ($Nickname) > 0) return $this->returnMessageError ('modify', 'bad-request', $Receiver, $JID, $Body); // Retrive all users on the room if (!is_array ($Users = $Room->listUsers ())) return $this->returnMessageError ('wait', 'internal-server-error', $Receiver, $JID, $Body); // Set type of the message $Message->setOriginator ($Room->getJID () . '/' . $Originator); $Message->Type = tiggerXMPP_Message::TYPE_GROUPCHAT; // Try to rewrite the subject if ($Subject !== null) { if (!$Room->setSubject ($Subject, $JID)) return $this->returnMessageError ('wait', 'internal-server-error', $Receiver, $JID, null, $Subject); return true; #$Message->unsetMessage (); #$Message->setSubject ($Subject); // Push the message to the room-log } else { $Timestamp = null; // Check for a delay-information if (is_object ($Delay = $Packet->getSubtagByName ('delay')) && ($D = $Delay->getAttribute ('stamp', false))) $Timestamp = strtotime ($D); $Room->logMessage ($Originator, $Body, $Timestamp); } foreach ($Users as $User) if ($User->isOnline ()) { $Message->setDestination ($User->getJID ()); $this->sendXML ($Message); } // Handle special MUC-Stuff-Messages } elseif ((strlen ($Nickname) == 0) && is_object ($X = $Packet->getSubtagByName ('x'))) { // Handle invitations if (is_object ($Invitation = $X->getSubtagByName ('invite'))) { // Invite the user into room if (!$Room->inviteUser ($Packet->getOriginator (), $this->getJID (true, true, false, $Invitation->getAttribute ('to')))) return $this->returnMessageError ('auth', 'forbidden', $Receiver, $JID, $Message, $Subject); // Forward the invitation to the user $Message->setOriginator ($Room->getJID ()); $Message->setDestination ($Invitation->getAttribute ('to')); $Message->unsetMessage (); $Invitation = clone $Invitation; $Invitation->setAttribute ('from', $Packet->getOriginator ()); $Invitation->unsetAttribute ('to'); $X = new tiggerXMPP_Packet ('x', $Message); $X->setNamespace ('http://jabber.org/protocol/muc#user'); $X->addSubtag ($Invitation); # TODO: Add password-tag below x? $this->sendXML ($Message); // Handle decline of an invitation } elseif (is_object ($Decline = $X->getSubtagByName ('decline'))) { // Let the room decline the invitation (and retrive its originator) if (!($toJID = $Room->declineInvitation ($this->getJID (true, true, false, $JID)))) return $this->returnMessageError ('auth', 'forbidden', $Receiver, $JID, $Message, $Subject); // Forward the invitation to the user $Message->setOriginator ($Room->getJID ()); $Message->setDestination ($toJID); $Message->unsetMessage (); $Decline = clone $Decline; $Decline->unsetAttribte ('to'); $Decline->setAttribute ('from', $this->getJID (true, true, false, $Packet->getOriginator ())); $X = new tiggerXMPP_Packet ('x', $Message); $X->setNamespace ('http://jabber.org/protocol/muc#user'); $X->addSubtag ($Decline); $this->sendXML ($Message); } } elseif (!($Originator = $Room->getNickname ($JID))) return $this->returnMessageError ('auth', 'not-authorized', $Receiver, $JID, $Body); else return $this->returnMessageError ('modify', 'bad-request', $Receiver, $JID, $Body); return true; } // }}} // {{{ sendRewrite /** * Proxy packets between Room-Users * * @param object $Packet * * @access private * @return bool **/ private function sendRewrite ($Packet) { // Request the room-object if (!is_object ($Room = $this->getRoom ($Packet->getDestination ()))) return false; // Try to get originator if (!($Originator = $Room->getNickname ($Packet->getOriginator ()))) return false; // Try to get the new destination if (!($JID = $Room->getUsernameByNickname ($this->getJID (false, false, true, $Packet->getDestination ())))) return false; // Change the enveolpe $Packet->setOriginator ($Room->getJID () . '/' . $Originator); $Packet->setDestination ($JID); // Send packet to the wire return $this->sendXML ($Packet); } // }}} // {{{ handleIQ /** * Proxy IQ-Packets between users * * @param string $ID * @param object $Packet * @param bool $Continue * @param object $XMPP * * @access protected * @return void **/ protected function handleIQ ($ID, $Packet, &$Continue, &$XMPP) { // Try to rewrite if ($this->sendRewrite ($Packet)) { $Continue = false; return true; } // Just pass the packet to our parent return parent::handleIQ ($ID, $Packet, $Continue, $XMPP); } // }}} // {{{ handleMessage /** * Proxy Messages between users * * @param string $ID * @param object $Packet * @param bool $Continue * @param object $XMPP * * @access protected * @return void **/ protected function handleMessage ($ID, $Packet, &$Continue, &$XMPP) { // Try to rewrite if (($Packet->getAttribute ('type') != tiggerXMPP_Message::TYPE_GROUPCHAT) && (strlen ($this->getJID (false, false, true, $Packet->getDestination ())) > 0) && $this->sendRewrite ($Packet)) { $Continue = false; return true; } // Just pass the packet to our parent return parent::handleMessage ($ID, $Packet, $Continue, $XMPP); } // }}} // {{{ adminHandler /** * Handle room-administration requests * * @access protected * @return mixed **/ protected function adminHandler ($Tag, $Response) { // Retrive our parent IQ-Packet if (!is_object ($IQ = $Tag->getParent ())) return false; if ($IQ->getName () != 'iq') return false; // Request the room-object if (!is_object ($Room = $this->getRoom ($IQ->getDestination ()))) return false; // Handle modifications if ($IQ->getAttribute ('type') == 'set') return $this->adminSetHandler ($IQ->getOriginator (), $Room, $Tag, $Response); // Handle lists return $this->adminListHandler ($IQ->getOriginator (), $Room, $Tag, $Response); } // }}} // {{{ adminSetHandler /** * Handle any administrative modification of room-ocupants * * @param string $JID * @param object $Room * @param object $Tag * @param object $Response * * @access protected * @return mixed **/ protected function adminSetHandler ($JID, $Room, $Tag, $Response) { // Check if the user may perform *ANY* transition if (!is_object ($User = $Room->getUserByUsername ($JID))) return $this->returnIQError ('cancel', tiggerXMPP_Error::ERROR_NOT_ALLOWDED, $Room->getJID (), $JID, $Tag); // Retrive the items we have to modify if (!is_array ($Items = $Tag->getSubtagsByName ('item'))) return true; // Role-transitions (none < visitor < participant < moderator) // Any does not include "none" - users have to be on the room // any -> none kicked // visitor -> participant voiced on moderated rooms // participant -> visitor visitor on moderated rooms // any -> moderator moderator // Affiliation-transitions (outcast < none < member < admin < owner) // any -> outcast ban from room // any -> member set user as member // any -> none revoke membership / remove ban foreach ($Items as $i=>$Item) { // Handle transitions of affiliation # TODO // Handle transitions of role if ($Role = $Item->getAttribute ('role', false)) { if (!$Room->allowRoleChange ($JID, $Item->getAttribute ('nick'), $Role)) continue; // Handle kicking if ($Role == twMuc_User::ROLE_NONE) $rc = $Room->kickUser ($Item->getAttribute ('nick'), (is_object ($V = $Item->getSubtagByName ('reason')) ? $V->getValue () : ''), $JID); // Handle any other role-change else { // Retrive handle of the destination-user if (!is_object ($toUser = $Room->getUserByNickname ($Item->getAttribute ('nick')))) continue; // Retrive the reason $Reason = null; if (is_object ($R = $Item->getSubtagByName ('reason'))) $Reason = $R->getValue (); // Try to set the new role $rc = $Room->setRole ($JID, $toUser->getNickname (), $Role, $Reason); } // Keep the item if it was not handled if (!$rc) continue; // ... or remove it $Tag->removeSubtag ($Item); unset ($Items [$i]); } # TODO: Handle errors } // Force the component to return return true; } // }}} // {{{ adminListHandler /** * Handler for administrative listings * * @param string $JID * @param object $Room * @param object $Tag * @param object $Response * * @access protected * @return mixed **/ protected function adminListHandler ($JID, $Room, $Tag, $Response) { // Retrive the items we have to modify if (!is_array ($Items = $Tag->getSubtagsByName ('item'))) return true; // Create response-tag $List = $Response->createSubtag ('query'); $List->setNamespace ('http://jabber.org/protocol/muc#admin'); // Load all users from room $Users = $Room->listUsers (); foreach ($Items as $Item) { // Append Users by Affiliation if (($Affiliation = $Item->getAttribute ('affiliation', false)) && $Room->allowAffiliationList ($JID, $Affiliation)) { foreach ($Users as $User) if ($User->getAffiliation () == $Affiliation) { // Create new Item on the result-list $lItem = $List->createSubtag ('item'); // Set JID and Affiliation $lItem->setAttribute ('jid', $User->getJID ()); $lItem->setAttribute ('affiliation', $Affiliation); // Handle special case for outcasts if ($Affiliation == twMUC_User::AFFILIATION_OUTCAST) { if (strlen ($Msg = $User->getBanReason ()) > 0) $lItem->createSubtag ('item', null, false, $Msg); // Handle all other cases (except 'none') } elseif ($Affiliation != twMUC_User::AFFILIATION_NONE) { if (strlen ($Nick = $User->getNickname ()) > 0) $lItem->setAttribute ('nick', $Nick); $lItem->setAttribute ('role', $User->getRole ()); } } // Append Users by Role } elseif (($Role = $Item->getAttribute ('role', false)) && ($Role != twMUC_User::ROLE_NONE) && $Room->allowRoleList ($JID, $Role)) foreach ($Users as $User) if ($User->getRole () == $Role) { // Create new Item on the result-list $lItem = $List->createSubtag ('item'); // Set Nickname and role $lItem->setAttribute ('nick', $User->getNickname ()); $lItem->setAttribute ('role', $User->getRole ()); $lItem->setAttribute ('affiliation', $User->getAffiliation ()); if (($Role == twMuc_User::ROLE_MODERATOR) || $Room->isUsernameExposed ($JID)) $lItem->setAttribute ('jid', $User->getJID ()); } } // Force the component to return return true; } // }}} // {{{ roleChanged /** * Callback: The role of a user was changed * * @param object $Room The Room were the role was changed * @param object $User Handle of this user that was changed * @param string $Reason (optional) The reason why the change happend * @param array $Users (optional) Receivers of the change * @param string $fromNickname (optional) Fake the sender to this nickname (Only JIDs are affected, not the nick-tag!) * @param array $Status (optional) Append these status-codes * @param enum $presenceType (optional) Set presence-packet to this type * @param string $presenceMessage (optional) Set message on presence * @param string $actorJID (optional) * * @access public * @return void **/ public function roleChanged ($Room, $User, $Reason = null, $Users = null, $fromNickname = null, $Status = null, $presenceType = null, $presenceMessage = null, $actorJID = null) { // Create presence-information $Presence = new tiggerXMPP_Presence; $Presence->setOriginator ($Room->getJID () . '/' . ($fromNickname !== null ? $fromNickname : $User->getNickname ())); if ($presenceType !== null) $Presence->setType ($presenceType); elseif (!$User->isOnline ()) $Presence->setType (tiggerXMPP_Presence::TYPE_OFFLINE); if ($presenceMessage !== null) $Presence->setMessage ($presenceMessage); $X = $Presence->createSubtag ('x'); $X->setNamespace ('http://jabber.org/protocol/muc#user'); $pItem = $X->createSubtag ('item'); $pItem->setAttribute ('nick', $User->getNickname ()); $pItem->setAttribute ('role', $User->getRole ()); $pItem->setAttribute ('affiliation', $User->getAffiliation ()); if ($actorJID !== null) { $Actor = $pItem->createSubtag ('actor'); $Actor->setAttribute ('jid', $actorJID); } // Append the reason if (strlen ($Reason) > 0) $pItem->createSubtag ('reason', null, false, $Reason); // Append user-defined stati if (is_array ($Status)) foreach ($Status as $Code) if (is_object ($S = $X->createSubtag ('status'))) $S->setAttribute ('code', $Code); // Forward the presence to all users on room if ((is_array ($Users) && (count ($Users) > 0)) || is_array ($Users = $Room->listUsers ())) foreach ($Users as $toUser) if ($toUser->isOnline () || ($toUser == $User)) { if (($toUser == $User) && is_object ($Status = $X->createSubtag ('status'))) $Status->setAttribute ('code', 110); $Presence->setDestination ($toUser->getJID ()); if ($Room->isUsernameExposed ($toUser->getJID ())) $pItem->setAttribute ('jid', $User->getJID ()); else $pItem->unsetAttribute ('jid'); $this->sendXML ($Presence); if ($toUser == $User) $X->removeSubtagsByName ('status'); } } // }}} public function spreadMessage ($Room, $Message, $Subject = null, $From = null) { $Tag = tiggerXMPP_Message::newTag (); if (strlen ($Message) > 0) $Tag->setMessage ($Message); if (strlen ($Subject) > 0) $Tag->setSubject ($Subject); $Tag->setOriginator ($Room->getJID () . (strlen ($From) > 0 ? '/' . $Room->getNickname ($From) : '')); $Tag->Type = tiggerXMPP_Message::TYPE_GROUPCHAT; foreach ($Room->listUsers () as $User) if ($User->isOnline ()) { $Tag->setDestination ($User->getJID ()); $this->sendXML ($Tag); } } } ?>