/** * @file llgroupactions.cpp * @brief Group-related actions (join, leave, new, delete, etc) * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llgroupactions.h" #include "message.h" #include "llagent.h" #include "llcommandhandler.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llgroupmgr.h" #include "llfloaterimcontainer.h" #include "llimview.h" // for gIMMgr #include "llnotificationsutil.h" #include "llstartup.h" #include "llstatusbar.h" // can_afford_transaction() #include "groupchatlistener.h" // [RLVa:KB] - Checked: 2011-03-28 (RLVa-1.3.0) #include "llslurl.h" #include "rlvactions.h" #include "rlvcommon.h" #include "rlvhandler.h" // [/RLVa:KB] // Firestorm includes #include "exogroupmutelist.h" #include "fscommon.h" #include "fsdata.h" #include "fsfloatercontacts.h" #include "fsfloatergroup.h" #include "fsfloaterim.h" #include "llpanelgroup.h" #include "llresmgr.h" // // Globals // static LLGroupChatListener sGroupChatListener; class LLGroupHandler : public LLCommandHandler { public: // requires trusted browser to trigger LLGroupHandler() : LLCommandHandler("group", UNTRUSTED_THROTTLE) { } virtual bool canHandleUntrusted( const LLSD& params, const LLSD& query_map, LLMediaCtrl* web, const std::string& nav_type) { if (params.size() < 1) { return true; // don't block, will fail later } if (nav_type == NAV_TYPE_CLICKED || nav_type == NAV_TYPE_EXTERNAL) { return true; } const std::string verb = params[0].asString(); if (verb == "create") { return false; } return true; } bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) { if (LLStartUp::getStartupState() < STATE_STARTED) { return true; } if (tokens.size() < 1) { return false; } if (tokens[0].asString() == "create") { LLGroupActions::createGroup(); return true; } if (tokens.size() < 2) { return false; } if (tokens[0].asString() == "list") { if (tokens[1].asString() == "show") { // Obey FSUseV2Friends setting where to open the group list //LLSD params; //params["people_panel_tab_name"] = "groups_panel"; //LLFloaterSidePanelContainer::showPanel("people", "panel_people", params); if (gSavedSettings.getBOOL("FSUseV2Friends") && !FSCommon::isLegacySkin()) { LLSD params; params["people_panel_tab_name"] = "groups_panel"; LLFloaterSidePanelContainer::showPanel("people", "panel_people", params); } else { FSFloaterContacts::getInstance()->openTab("groups"); } // return true; } return false; } LLUUID group_id; if (!group_id.set(tokens[0], false)) { return false; } if (tokens[1].asString() == "about") { if (group_id.isNull()) return true; LLGroupActions::show(group_id); return true; } if (tokens[1].asString() == "inspect") { if (group_id.isNull()) return true; LLGroupActions::inspect(group_id); return true; } return false; } }; LLGroupHandler gGroupHandler; // This object represents a pending request for specified group member information // which is needed to check whether avatar can leave group class LLFetchGroupMemberData : public LLGroupMgrObserver { public: LLFetchGroupMemberData(const LLUUID& group_id) : mGroupId(group_id), mRequestProcessed(false), LLGroupMgrObserver(group_id) { LL_INFOS() << "Sending new group member request for group_id: "<< group_id << LL_ENDL; LLGroupMgr* mgr = LLGroupMgr::getInstance(); // register ourselves as an observer mgr->addObserver(this); // send a request mgr->sendGroupPropertiesRequest(group_id); mgr->sendCapGroupMembersRequest(group_id); } ~LLFetchGroupMemberData() { if (!mRequestProcessed) { // Request is pending LL_WARNS() << "Destroying pending group member request for group_id: " << mGroupId << LL_ENDL; } // Remove ourselves as an observer LLGroupMgr::getInstance()->removeObserver(this); } void changed(LLGroupChange gc) { if (gc == GC_PROPERTIES && !mRequestProcessed) { LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); if (!gdatap) { LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData() was NULL" << LL_ENDL; } else if (!gdatap->isMemberDataComplete()) { LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData()->isMemberDataComplete() was false" << LL_ENDL; processGroupData(); mRequestProcessed = true; } } } LLUUID getGroupId() { return mGroupId; } virtual void processGroupData() = 0; protected: LLUUID mGroupId; bool mRequestProcessed; }; class LLFetchLeaveGroupData: public LLFetchGroupMemberData { public: LLFetchLeaveGroupData(const LLUUID& group_id) : LLFetchGroupMemberData(group_id) {} void processGroupData() { LLGroupActions::processLeaveGroupDataResponse(mGroupId); } void changed(LLGroupChange gc) { if (gc == GC_PROPERTIES && !mRequestProcessed) { LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); if (!gdatap) { LL_WARNS() << "GroupData was NULL" << LL_ENDL; } else { processGroupData(); mRequestProcessed = true; } } } }; LLFetchLeaveGroupData* gFetchLeaveGroupData = NULL; // static void LLGroupActions::search() { // Open groups search panel instead of invoking presumed failed websearch //LLFloaterReg::showInstance("search", LLSD().with("collection", "groups")); LLFloaterReg::showInstance("search", LLSD().with("tab", "groups")); // } // static void LLGroupActions::startCall(const LLUUID& group_id) { // create a new group voice session LLGroupData gdata; if (!gAgent.getGroupData(group_id, gdata)) { LL_WARNS() << "Error getting group data" << LL_ENDL; return; } // [RLVa:KB] - Checked: 2013-05-09 (RLVa-1.4.9) if (!RlvActions::canStartIM(group_id)) { make_ui_sound("UISndInvalidOp"); RlvUtil::notifyBlocked(RlvStringKeys::Blocked::StartIm, LLSD().with("RECIPIENT", LLSLURL("group", group_id, "about").getSLURLString())); return; } // [/RLVa:KB] LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, LLSD()); if (session_id == LLUUID::null) { LL_WARNS() << "Error adding session" << LL_ENDL; return; } // start the call gIMMgr->autoStartCallOnStartup(session_id); make_ui_sound("UISndStartIM"); } // static void LLGroupActions::join(const LLUUID& group_id) { if (!gAgent.canJoinGroups()) { LLNotificationsUtil::add("JoinedTooManyGroups"); return; } // fsdata support if (FSData::instance().isSupportGroup(group_id) && FSData::instance().isAgentFlag(gAgentID, FSData::NO_SUPPORT)) { return; } // LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); if (gdatap) { S32 cost = gdatap->mMembershipFee; LLSD args; args["COST"] = llformat("%d", cost); args["NAME"] = gdatap->mName; LLSD payload; payload["group_id"] = group_id; if (can_afford_transaction(cost)) { if(cost > 0) LLNotificationsUtil::add("JoinGroupCanAfford", args, payload, onJoinGroup); else LLNotificationsUtil::add("JoinGroupNoCost", args, payload, onJoinGroup); } else { LLNotificationsUtil::add("JoinGroupCannotAfford", args, payload); } } else { LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData(" << group_id << ") was NULL" << LL_ENDL; } } // static bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 1) { // user clicked cancel return false; } LLGroupMgr::getInstance()-> sendGroupMemberJoin(notification["payload"]["group_id"].asUUID()); return false; } // static void LLGroupActions::leave(const LLUUID& group_id) { // if (group_id.isNull()) // [RLVa:KB] - Checked: RLVa-1.3.0 if ( (group_id.isNull()) || ((gAgent.getGroupID() == group_id) && (!RlvActions::canChangeActiveGroup())) ) // [/RLVa:KB] { return; } LLGroupData group_data; if (gAgent.getGroupData(group_id, group_data)) { LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); if (!gdatap || !gdatap->isMemberDataComplete()) { if (gFetchLeaveGroupData != NULL) { delete gFetchLeaveGroupData; gFetchLeaveGroupData = NULL; } gFetchLeaveGroupData = new LLFetchLeaveGroupData(group_id); } else { processLeaveGroupDataResponse(group_id); } } } //static void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id) { LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); LLUUID agent_id = gAgent.getID(); LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.find(agent_id); //get the member data for the group if ( mit != gdatap->mMembers.end() ) { LLGroupMemberData* member_data = (*mit).second; if ( member_data && member_data->isOwner() && gdatap->mMemberCount == 1) { LLNotificationsUtil::add("OwnerCannotLeaveGroup"); return; } } LLSD args; args["GROUP"] = gdatap->mName; LLSD payload; payload["group_id"] = group_id; if (gdatap->mMembershipFee > 0) { args["COST"] = gdatap->mMembershipFee; LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); } else { LLNotificationsUtil::add("GroupLeaveConfirmMemberNoFee", args, payload, onLeaveGroup); } } // static void LLGroupActions::activate(const LLUUID& group_id) { // [RLVa:KB] - Checked: RLVa-1.3.0 if ( (!RlvActions::canChangeActiveGroup()) && (gRlvHandler.getAgentGroup() != group_id) ) { return; } // [/RLVa:KB] LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_ActivateGroup); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_GroupID, group_id); gAgent.sendReliableMessage(); } static bool isGroupUIVisible() { static LLPanel* panel = 0; if(!panel) panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray"); if(!panel) return false; return panel->isInVisibleChain(); } // static void LLGroupActions::inspect(const LLUUID& group_id) { LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", group_id)); } // static void LLGroupActions::show(const LLUUID &group_id, bool expand_notices_tab) { if (group_id.isNull()) return; LLSD params; params["group_id"] = group_id; params["open_tab_name"] = "panel_group_info_sidetray"; if (expand_notices_tab) { params["action"] = "show_notices"; } // Standalone group floaters //LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); //LLFloater *floater = LLFloaterReg::getTypedInstance("people"); //if (!floater->isFrontmost()) //{ // floater->setVisibleAndFrontmost(true, params); //} LLFloater* floater = nullptr; if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { if (expand_notices_tab) floater = FSFloaterGroup::openGroupFloater(params); else floater = FSFloaterGroup::openGroupFloater(group_id); } else { LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); LLFloaterSidePanelContainer* floater_people = LLFloaterReg::findTypedInstance("people"); if (floater_people) { LLPanel* group_info_panel = floater_people->getPanel("people", "panel_group_info_sidetray"); if (group_info_panel && group_info_panel->getVisible()) { floater = floater_people; } } } if (floater) { if (floater->isMinimized()) { floater->setMinimized(false); } if (!floater->hasFocus()) { floater->setFocus(true); } } // } // Standalone group floaters //void LLGroupActions::refresh_notices() void LLGroupActions::refresh_notices(const LLUUID& group_id /*= LLUUID::null*/) { // Standalone group floaters //if(!isGroupUIVisible()) // return; // LLSD params; params["group_id"] = LLUUID::null; params["open_tab_name"] = "panel_group_info_sidetray"; params["action"] = "refresh_notices"; // Standalone group floaters //LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { if (FSFloaterGroup::isFloaterVisible(group_id)) { FSFloaterGroup::openGroupFloater(LLSD().with("group_id", group_id).with("open_tab_name", "panel_group_info_sidetray").with("action", "refresh_notices")); } } else { if (isGroupUIVisible()) { LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); } } // } //static void LLGroupActions::refresh(const LLUUID& group_id) { // Standalone group floaters //if(!isGroupUIVisible()) // return; // LLSD params; params["group_id"] = group_id; params["open_tab_name"] = "panel_group_info_sidetray"; params["action"] = "refresh"; // Standalone group floaters //LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { if (FSFloaterGroup::isFloaterVisible(group_id)) { FSFloaterGroup::openGroupFloater(params); } } else { if (isGroupUIVisible()) { LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); } } // } //static void LLGroupActions::createGroup() { LLSD params; params["group_id"] = LLUUID::null; params["open_tab_name"] = "panel_group_creation_sidetray"; params["action"] = "create"; // Standalone group floaters //LLFloaterSidePanelContainer::showPanel("people", "panel_group_creation_sidetray", params); if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { FSFloaterGroup::openGroupFloater(params); } else { LLFloaterSidePanelContainer::showPanel("people", "panel_group_creation_sidetray", params); } // } //static void LLGroupActions::closeGroup(const LLUUID& group_id) { // Standalone group floaters //if(!isGroupUIVisible()) // return; // LLSD params; params["group_id"] = group_id; params["open_tab_name"] = "panel_group_info_sidetray"; params["action"] = "close"; // Standalone group floaters //LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { FSFloaterGroup::closeGroupFloater(group_id); } else { if (isGroupUIVisible()) { LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); } } // } // static LLUUID LLGroupActions::startIM(const LLUUID& group_id) { if (group_id.isNull()) return LLUUID::null; // [RLVa:KB] - Checked: 2013-05-09 (RLVa-1.4.9) if (!RlvActions::canStartIM(group_id)) { make_ui_sound("UISndInvalidOp"); RlvUtil::notifyBlocked(RlvStringKeys::Blocked::StartIm, LLSD().with("RECIPIENT", LLSLURL("group", group_id, "about").getSLURLString())); return LLUUID::null; } // [/RLVa:KB] LLGroupData group_data; if (gAgent.getGroupData(group_id, group_data)) { // // Unmute the group if the user tries to start a session with it. exoGroupMuteList::instance().remove(group_id); // LLUUID session_id = gIMMgr->addSession( group_data.mName, IM_SESSION_GROUP_START, group_id); if (session_id != LLUUID::null) { // [FS communication UI] //LLFloaterIMContainer::getInstance()->showConversation(session_id); FSFloaterIM::show(session_id); // [FS communication UI] } make_ui_sound("UISndStartIM"); return session_id; } else { // this should never happen, as starting a group IM session // relies on you belonging to the group and hence having the group data make_ui_sound("UISndInvalidOp"); return LLUUID::null; } } // [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-17 (Catznip-3.3) static void snooze_group_im(const LLUUID& group_id, S32 duration = -1) { LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); if (session_id.notNull()) { LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); if (!session) { LL_WARNS() << "Empty session." << LL_ENDL; return; } session->mSnoozeTime = duration; session->mCloseAction = LLIMModel::LLIMSession::CLOSE_SNOOZE; gIMMgr->leaveSession(session_id); } } static void snooze_group_im_duration_callback(const LLSD& notification, const LLSD& response, const LLUUID& group_id) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (0 == option) { std::istringstream duration_str(response["duration"].asString()); S32 duration(-1); if (duration_str >> duration && duration >= 0) { snooze_group_im(group_id, duration); } else { LLNotificationsUtil::add("SnoozeDurationInvalidInput"); } } } static void confirm_group_im_snooze(const LLUUID& group_id) { if (group_id.isNull()) { return; } if (gSavedSettings.getBOOL("FSEnablePerGroupSnoozeDuration")) { LLSD args; args["DURATION"] = gSavedSettings.getS32("GroupSnoozeTime"); LLNotificationsUtil::add("SnoozeDuration", args, LLSD(), boost::bind(&snooze_group_im_duration_callback, _1, _2, group_id)); return; } snooze_group_im(group_id); } static void close_group_im(const LLUUID& group_id, LLIMModel::LLIMSession::SCloseAction close_action) { if (group_id.isNull()) { return; } LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); if (session_id.notNull()) { LLIMModel::LLIMSession* pIMSession = LLIMModel::getInstance()->findIMSession(session_id); if (pIMSession) { pIMSession->mCloseAction = close_action; } gIMMgr->leaveSession(session_id); } } void LLGroupActions::leaveIM(const LLUUID& group_id) { close_group_im(group_id, LLIMModel::LLIMSession::CLOSE_LEAVE); } void LLGroupActions::snoozeIM(const LLUUID& group_id) { confirm_group_im_snooze(group_id); } void LLGroupActions::endIM(const LLUUID& group_id) { close_group_im(group_id, LLIMModel::LLIMSession::CLOSE_DEFAULT); } // [/SL:KB] //// static //void LLGroupActions::endIM(const LLUUID& group_id) //{ // if (group_id.isNull()) // return; // // LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); // if (session_id != LLUUID::null) // { // gIMMgr->leaveSession(session_id); // } //} // static bool LLGroupActions::isInGroup(const LLUUID& group_id) { // *TODO: Move all the LLAgent group stuff into another class, such as // this one. return gAgent.isInGroup(group_id); } // static bool LLGroupActions::isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id) { if(group_id.isNull() || avatar_id.isNull()) { return false; } LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id); if(!group_data) { return false; } if(group_data->mMembers.end() == group_data->mMembers.find(avatar_id)) { return false; } return true; } // [SL:KB] - Patch: Chat-GroupSessionEject | Checked: 2012-02-04 (Catznip-3.2.1) | Added: Catznip-3.2.1 bool LLGroupActions::canEjectFromGroup(const LLUUID& idGroup, const LLUUID& idAgent) { if (gAgent.isGodlike()) { return true; } if (gAgent.hasPowerInGroup(idGroup, GP_MEMBER_EJECT)) { const LLGroupMgrGroupData* pGroupData = LLGroupMgr::getInstance()->getGroupData(idGroup); if ( (!pGroupData) || (!pGroupData->isMemberDataComplete()) ) { // There is no (or not enough) information on this group but the user does have the group eject power return true; } LLGroupMgrGroupData::member_list_t::const_iterator itMember = pGroupData->mMembers.find(idAgent); if (pGroupData->mMembers.end() != itMember) { const LLGroupMemberData* pMemberData = (*itMember).second; if ( (pGroupData->isRoleDataComplete()) && (pGroupData->isRoleMemberDataComplete()) ) { for (LLGroupMemberData::role_list_t::const_iterator itRole = pMemberData->roleBegin(); itRole != pMemberData->roleEnd(); ++itRole) { if ((*itRole).first.notNull()) { // Someone who belongs to any roles other than "Everyone" can't be ejected return false; } } } // Owners can never be ejected return (itMember->second) && (!itMember->second->isOwner()); } } return false; } void LLGroupActions::ejectFromGroup(const LLUUID& idGroup, const LLUUID& idAgent) { LLSD args; LLSD payload; payload["avatar_id"] = idAgent; payload["group_id"] = idGroup; // Show complete name in eject dialog //std::string fullname = LLSLURL("agent", idAgent, "inspect").getSLURLString(); std::string fullname = LLSLURL("agent", idAgent, "completename").getSLURLString(); // args["AVATAR_NAME"] = fullname; LLNotificationsUtil::add("EjectGroupMemberWarning", args, payload, callbackEject); } bool LLGroupActions::callbackEject(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (2 == option) // Cancel button { return false; } if (0 == option) // Eject button { LLUUID idAgent = notification["payload"]["avatar_id"].asUUID(); LLUUID idGroup = notification["payload"]["group_id"].asUUID(); if (!canEjectFromGroup(idGroup, idAgent)) return false; uuid_vec_t idAgents; idAgents.push_back(idAgent); LLGroupMgr::instance().sendGroupMemberEjects(idGroup, idAgents); } return false; } // [/SL:KB] //-- Private methods ---------------------------------------------------------- // static bool LLGroupActions::onLeaveGroup(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); LLUUID group_id = notification["payload"]["group_id"].asUUID(); if(option == 0) { // Standalone group floaters if (gSavedSettings.getBOOL("FSUseStandaloneGroupFloater")) { FSFloaterGroup::closeGroupFloater(group_id); } // LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_LeaveGroupRequest); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_GroupData); msg->addUUIDFast(_PREHASH_GroupID, group_id); gAgent.sendReliableMessage(); } return false; }