* @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 ('user.php'); require_once ('history.php'); /** * twMUC_Room * ---------- * Abstract class to handle most of the logic stuff behind a chat-room * The Protocol-related stuff is done in twMUC_Handler, so usage with other protocols might be possible * * @class twMUC_Room * @package twMUC * @author Bernd Holzmueller **/ class twMUC_Room { private $Name = ''; private $Subject = ''; private $Domain = ''; private $Owner = null; private $Parent = null; private $Users = array (); protected $reqRegistration = false; private $requirePassword = false; private $Password = ''; protected $moderatedRoom = false; # TODO private $persistentRoom = false; # TODO private $exposeRoom = true; private $exposeJID = false; private $History = array (); private $maxHistory = 10; private $Invitations = array (); function __construct ($Handler, $Name, $Domain, $Owner = null) { $this->Parent = $Handler; $this->Name = $Name; $this->Domain = $Domain; if ($Owner !== null) $this->setOwner ($Owner); } // {{{ allowSubjectChange /** * Determine if the room is allowed to change its subject trough an occupant * * @access public * @return bool **/ public function allowSubjectChange () { return true; } // }}} // {{{ getSubject /** * Retrive the subject of this room * * @access public * @return string **/ public function getSubject () { return $this->Subject; } // }}} // {{{ setSubject /** * Set the new subject for this room * * @param string $Subject * @param string $JID * * @access public * @return bool **/ public function setSubject ($Subject, $JID) { if ($Subject == $this->getSubject ()) return true; # TODO: Handle ACLs? $this->Subject = $Subject; // Forward the subject-change if (is_object ($Handler = $this->getHandler ())) $Handler->spreadMessage ($this, null, $Subject, $JID); return true; } // }}} // {{{ getDescription /** * Retrive Description of this room * * @access public * @return string * @remark Just an alias at the moment **/ public function getDescription () { return $this->getSubject (); } // }}} // {{{ setOwner /** * Set the owner of this room * * @param string $JID * * @access public * @return bool **/ public function setOwner ($JID) { // Remove any ressource if (($p = strpos ($JID, '/')) !== false) $JID = substr ($JID, 0, $p); // Degrade the old owner # TODO: This does not work with ressources if ((strlen ($this->Owner) > 0) && is_object ($Owner = $this->getUserByUsername ($this->Owner))) $Owner->setAffiliation (twMUC_User::AFFILIATION_ADMIN); // Set the new owner $this->Owner = strtolower ($JID); // Upgrade the new owner # See above if (is_object ($Owner = $this->getUserByUsername ($JID))) $Owner->setAffiliation (twMUC_User::AFFILIATION_OWNER); return true; } // }}} public function disallowUser ($JID) { return false; } public function checkPassword ($JID, $Password) { // Allways succeed when no password is needed if (!$this->requirePassword ()) return true; // Check room-based password return ($this->roomPassword == $Password); } // {{{ checkNickname /** * Validate the format of a requested nickname * * @param string $Nickname * * @access public * @return bool * @todo Unimplemented **/ public function checkNickname ($Nickname) { # TODO: Implement this return true; } // }}} // {{{ registeredNickname /** * Check if a nickname is already registered within this room * * @param string $Nickname * * @access public * @return bool **/ public function registeredNickname ($Nickname, $Requester = null) { if (!is_object ($User = $this->getUserByNickname ($Nickname))) return false; if ($User->isOnline ()) return true; if ($User->matchJID ($Requester) || in_array ($User->getAffiliation (), array (twMUC_User::AFFILIATION_NONE, twMUC_User::AFFILIATION_OUTCAST))) return false; return true; } // }}} // {{{ registeredJID /** * Check if a Jabber-ID is registered within this room * * @param string $JID * * @access public * @return bool **/ public function registeredJID ($JID) { return (is_object ($U = $this->getUserByUsername ($JID)) && $U->isOnline ()); } // }}} // {{{ addUser /** * Add a user to our room-list * * @param object $User * @param bool $Online (optional) * * @access protected * @return bool **/ protected function addUser ($User, $Online = false) { // Check type of the user if (!($User instanceof twMuc_User)) return false; // Retrive the JID of this user $plainJID = $JID = $User->getJID (); if (isset ($this->Users [$JID])) $User = $this->Users [$JID]; else $this->Users [$JID] = $User; // Check for a resource inside the JID if (($p = strpos ($plainJID, '/')) !== false) $plainJID = substr ($plainJID, 0, $p); // Force the user to be owner if it realy is the one if ($plainJID == $this->Owner) $User->setAffiliation (twMUC_User::AFFILIATION_OWNER); // Quit here if user is not online $wasOnline = $User->isOnline (); if (!$Online && !$wasOnline) return $User; // Set the role of the user if (in_array ($User->getAffiliation (), array (twMUC_User::AFFILIATION_OWNER, twMUC_User::AFFILIATION_ADMIN))) $User->setRole (twMUC_User::ROLE_MODERATOR); elseif ($User->getRole () != twMUC_User::ROLE_MODERATOR) $User->setRole ($this->isModerated () ? twMUC_User::ROLE_VISITOR : twMUC_User::ROLE_PARTICIPANT); // Forward the roster to the new user if (!$wasOnline && is_object ($Handler = $this->getHandler ())) { foreach ($this->Users as $Occupant) if ($User != $Occupant) $Handler->roleChanged ($this, $Occupant, null, array ($User)); // Inform the new user about its own role $Status = array (); if ($this->anyUsernameExposed ()) $Status [] = 100; if ($this->hasPublicLog ()) $Status [] = 170; # TODO: If room was freshly created: Send status 201 $Handler->roleChanged ($this, $User, null, array ($User), null, $Status); // Inform the other users $Users = $this->Users; unset ($Users [$JID]); $Handler->roleChanged ($this, $User, null, $Users); } else print "Not forwarding roster to $JID\n"; return $User; } // }}} // {{{ joinUser /** * Register a Jabber-ID with a nickname in this room * * @param string $JID * @param string $Nickname * * @access public * @return bool **/ public function joinUser ($JID, $Nickname) { // Check if the nickname is occupied if ($this->registeredNickname ($Nickname, $JID)) return false; // Check if the user is already registered if ($this->registeredJID ($JID)) return false; // Register the user $JID = $this->normalizeUsername ($JID); if (!is_object ($User = $this->getUserByUsername ($JID))) $User = new twMuc_User ($JID, $Nickname, $this); else $User->setNickname ($Nickname); return $this->addUser ($User, true); } // }}} // {{{ allowNickchange /** * Check if a user may change its nickname * * @param string $JID * @param string $oldNickname * @param string $newNickanme * * @access public * @return bool **/ public function allowNickchange ($JID, $oldNickname, $newNickname) { return true; } // }}} // {{{ changeNickname /** * Check the registered nickname of a user * * @param string $JID * @param string $Nickname * * @access public * @return bool **/ public function changeNickname ($JID, $Nickname) { // Check if the user is registered if (!$this->registeredJID ($JID)) return false; // Check if the user may change its JID if (!$this->allowNickchange ($JID, $oldNickname = $this->getNickname ($JID), $Nickname)) return false; // Check if the nickname is occupied if ($this->registeredNickname ($Nickname)) return false; // Rewrite the registration $Key = $this->normalizeUsername ($JID); $this->Users [$Key]->setNickname ($Nickname); // Inform the users if (is_object ($Handler = $this->getHandler ())) { $Handler->roleChanged ($this, $this->Users [$Key], null, null, $oldNickname, array (303), tiggerXMPP_Presence::TYPE_OFFLINE); $Handler->roleChanged ($this, $this->Users [$Key]); } return $this->Users [$Key]; } // }}} // {{{ partUser /** * Remove a user from this room * * @param string $JID * @param string $Nickname * * @access public * @return object **/ public function partUser ($JID, $Nickname, $Message = null) { $Key = $this->normalizeUsername ($JID); if (!isset ($this->Users [$Key])) return false; $User = $this->Users [$Key]; $User->setRole (twMuc_User::ROLE_NONE); // Remove the handle only if there is no affiliation with this room if ($User->getAffiliation () == twMuc_User::AFFILIATION_NONE) unset ($this->Users [$Key]); // Forward the change if (is_object ($Handler = $this->getHandler ())) $Handler->roleChanged ($this, $User, null, null, null, null, null, $Message); return $User; } // }}} // {{{ kickUser /** * Kick a user from this room * * @param string $Nickname * @param string $Reason * @param string $JID (optional) * * @access public * @return bool **/ public function kickUser ($Nickname, $Reason, $JID = null) { // Check if the given JID may kick the user if (($JID !== null) && !$this->allowRoleChange ($JID, $Nickname, twMuc_User::ROLE_NONE)) return false; // Try to get handle of the user to be kicked if (!is_object ($User = $this->getUserByNickname ($Nickname))) return false; // Remove the user from room if (!is_object ($this->partUser ($User->getJID (), $Nickname))) return false; // Inform the user of its role-change if (is_object ($Handler = $this->getHandler ())) $Handler->roleChanged ($this, $User, $Reason, array ($User), null, array (307), null, null, $JID); // Set its affiliation to outcast $User->setAffiliation (twMuc_User::AFFILIATION_OUTCAST); return true; } // }}} // {{{ logMessage /** * Put a message onto the room-log * * @param string $Nickname * @param string $Message * @param int $Timestamp (optional) * * @access public * @return void **/ public function logMessage ($Nickname, $Message, $Timestamp = null) { // Truncate history while (count ($this->History) > $this->maxHistory - 1) array_shift ($this->History); // Append to history $this->History [] = new twMuc_History ($Nickname, $Message, $Timestamp, $this); } // }}} // {{{ getHistory /** * Retrive our stored history * * @access public * @return array **/ public function getHistory () { return $this->History; } // }}} // {{{ inviteUser /** * Store an invitation from a user to another * * @param string $fromJID * @param string $toJID * * @access public * @return bool **/ public function inviteUser ($fromJID, $toJID) { // Check if the inviting user is on this room if (!$this->getNickname ($fromJID)) return false; // Make sure we can find the JID $toJID = $this->normalizeUsername ($toJID); // Check if the user was invited before if (isset ($this->Invitations [$toJID])) return false; // Store this invitation $this->Invitations [$toJID] = $fromJID; return true; } // }}} // {{{ declineInvitation /** * Decline an invitation from an invited user and return its invitor * * @param string $fromJID * * @access public * @return string **/ public function declineInvitation ($fromJID) { $fromJID = $this->normalizeUsername ($fromJID); if (!isset ($this->Invitations [$fromJID])) return false; $rc = $this->Invitations [$fromJID]; unset ($this->Invitations [$fromJID]); return $rc; } // }}} // {{{ allowAffiliationList /** * Check if a given user may list users with a given affiliation * * @param string $JID * @param enum $Affiliation * * @access public * @return bool **/ public function allowAffiliationList ($JID, $Affiliation) { // The requesting user has to be on the room if (!is_object ($User = $this->getUserByUsername ($JID)) || !$User->isOnline ()) return false; switch ($Affiliation) { case twMUC_User::AFFILIATION_NONE: return false; case twMUC_User::AFFILIATION_MEMBER: case twMUC_User::AFFILIATION_ADMIN: case twMUC_User::AFFILIATION_OWNER: case twMUC_User::AFFILIATION_OUTCAST: return true; } trigger_error ('Unhandled Affiliation: ' . $Affiliation); return false; } // }}} // {{{ allowRoleList /** * Check if a given user may list users with a given role * * @param string $JID * @param enum $Role * * @access public * @return bool **/ public function allowRoleList ($JID, $Role) { // The requesting user has to be on the room if (!is_object ($User = $this->getUserByUsername ($JID)) || !$User->isOnline ()) return false; switch ($Role) { case twMuc_User::ROLE_NONE: return false; case twMuc_User::ROLE_VISITOR: case twMuc_User::ROLE_PARTICIPANT: case twMuc_User::ROLE_MODERATOR: return true; } trigger_error ('Unhandled role: ' . $Role); return false; } // }}} // {{{ allowRoleChange /** * Check if a user may change the role of another * * @param string $JID * @param string $Nickname * @param enum $Role * * @access public * @return bool **/ public function allowRoleChange ($JID, $Nickname, $Role) { // The requesting user has to be on the room if (!is_object ($User = $this->getUserByUsername ($JID)) || !$User->isOnline ()) return false; // The requesting user has to be a moderator if ($User->getRole () != twMuc_User::ROLE_MODERATOR) return false; // Check the destination if (!is_object ($dUser = $this->getUserByNickname ($Nickname)) || !$dUser->isOnline ()) return false; // Only Administrators or Owners may grant Moderator-Rights if ($Role == twMuc_User::ROLE_MODERATOR) return in_array ($User->getAffiliation (), array (twMUC_User::AFFILIATION_ADMIN, twMUC_User::AFFILIATION_OWNER)); // Allow the operation if destination is not moderator if ($dUser->getRole () != twMuc_User::ROLE_MODERATOR) return true; $ScoreMap = array ( twMUC_User::AFFILIATION_NONE => 1, twMUC_User::AFFILIATION_MEMBER => 2, twMUC_User::AFFILIATION_ADMIN => 3, twMUC_User::AFFILIATION_OWNER => 4, twMUC_User::AFFILIATION_OUTCAST => 0, ); return ($ScoreMap [$dUser->getAffiliation ()] < $ScoreMap [$User->getAffiliation ()]); } // }}} // {{{ setRole /** * Set the role for a given nickname * * @param string $JID * @param string $Nickname * @param enum $Role * @param string $Reason (optional) * @param bool $Callback (optional) Use the callback to tell our parent that a role was changed (default) * * @access public * @return bool **/ public function setRole ($JID, $Nickname, $Role, $Reason = null, $Callback = true) { // Check if the transition is allowed if (!$this->allowRoleChange ($JID, $Nickname, $Role)) return false; // Try to get handle of the destination if (!is_object ($User = $this->getUserByNickname ($Nickname))) return false; // Check wheter to remove the user from the room if ($Role == twMuc_User::ROLE_NONE) return is_object ($this->partUser ($User->getJID (), $Nickname)); if (!$User->setRole ($Role)) return false; // Forward the event to our parent if (is_object ($Handler = $this->getHandler ()) && $Callback) $Handler->roleChanged ($this, $User, $Reason); return true; } // }}} // =========================================== // General room-informations // {{{ getJID /** * Retrive the full JID of this room * * @access public * @return string **/ public function getJID () { return $this->Name . '@' . $this->Domain; } // }}} // {{{ getName /** * Retrive the name of the room * * @param bool $Full (optional) Include the Subject as well (default) * * @access public * @return string **/ public function getName ($Full = true) { if ($Full && (strlen ($s = $this->getSubject ()) > 0)) return $s . ' (' . $this->Name . ')'; return $this->Name; } // }}} // {{{ getDomain /** * Retrive the domain this room runs on * * @access public * @return string **/ public function getDomain () { return $this->Domain; } // }}} // {{{ getHandler /** * Retrive the MUC-Handler for this room * * @access public * @return object **/ public function getHandler () { return $this->Parent; } // }}} // {{{ requireRegistration /** * Check if this room is members-only * * @param bool $Toggle (optional) Change this propoerty * @param book $KickUsers (optional) Remove users from room when changing to members-only * * @access public * @return bool **/ public function requireRegistration ($Toggle = null, $KickUsers = true) { // Just return our status if ($Toggle === null) return $this->reqRegistration; // Set new status if (($this->reqRegistration = ($Toggle == true)) && $KickUsers) // Remove users with no affiliation from this room foreach ($this->listUsers () as $User) if ($User->isOnline () && ($User->getAffiliation () == twMUC_User::AFFILIATION_NONE)) $this->kickUser ($User->getNickname (), 'Room is now members-only'); // Indicate success[5~ return true; } // }}} // {{{ requirePassword /** * Check if this room requires a password for access * * @access public * @return bool **/ public function requirePassword ($Toggle = null, $Password = null) { // Just return our status if ($Toggle === null) return $this->requirePassword; if (($this->requirePassword = ($Toggle == true)) && (strlen ($Password) > 0)) $this->roomPassword = $Password; return true; } // }}} // {{{ isPersistent /** * Check if this room is persistent * * @access public * @return bool **/ public function isPersistent () { return $this->persistentRoom; } // }}} // {{{ isModerated /** * Check if this room is moderated * * @access public * @return bool **/ public function isModerated () { return $this->moderatedRoom; } // }}} public function hasPublicLog () { return false; } // {{{ isExposed /** * Check wheter to expose this room upon service-discovery * * @param string $Receipient * * @access public * @return bool **/ public function isExposed ($Receipient = null) { return $this->exposeRoom || ($Receipient !== null ? $this->isRegistered ($Receipient) : false); } // }}} // {{{ isUsernameExposed /** * Check wheter we expose Usernames to a given user * * @param string $Receipient * * @access public * @return bool **/ public function isUsernameExposed ($Receipient) { return $this->exposeJID; } // }}} // {{{ anyUsernameExposed /** * Check if a Username might be exposed * * @access public * @return bool **/ public function anyUsernameExposed () { return $this->exposeJID; } // }}} // =========================================== // Bare user-handling functions // {{{ listUsers /** * Retrive an list of all users in this room * * @access public * @return array **/ public function listUsers () { return $this->Users; } // }}} // {{{ getUserByUsername /** * Retrive a user-handle by its Username * * @access public * @return object **/ public function getUserByUsername ($Username) { $Key = $this->normalizeUsername ($Username); if (!isset ($this->Users [$Key])) return false; return $this->Users [$Key]; } // }}} // {{{ getUserByNickname /** * Retrive a user-handle by its nickname **/ public function getUserByNickname ($Nickname) { $Nickname = strtolower ($Nickname); foreach ($this->Users as $User) if ($User->matchNickname ($Nickname)) return $User; return false; } // }}} // {{{ normalizeUsername /** * Create a normalized form of a given username * * @param string $Username * * @access public * @return string **/ public function normalizeUsername ($Username) { if (($p = strpos ($Username, '/')) === false) return strtolower ($Username); return strtolower (substr ($Username, 0, $p)) . substr ($Username, $p); } // }}} // {{{ getNickname /** * Retrive the nickname of a registered Username * * @param string $Username * * @access public * @return string **/ public function getNickname ($Username) { if (!is_object ($User = $this->getUserByUsername ($Username))) return false; return $User->getNickname (); } // }}} // {{{ getUsernameByNickname /** * Lookup a username by its nickname * * @param string $Nickname * * @access public * @return string **/ public function getUsernameByNickname ($Nickname) { if (!is_object ($User = $this->getUserByNickname ($Nickname))) return false; return $User->getJID (); } // }}} // {{{ isRegistered /** * Check if a given Username may enter a members-only room * * @param string $Username * * @access public * @return bool **/ public function isRegistered ($Username) { if (!is_object ($User = $this->getUserByUsername ($Username))) return false; return ($User->getAffiliation () != twMUC_User::AFFILIATION_NONE) && ($User->getAffiliation () != twMUC_User::AFFILIATION_OUTCAST); } // }}} // =========================================== // User-invoked modifycations } ?>