aboutsummaryrefslogtreecommitdiff
path: root/src/mod_shared_roster.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_shared_roster.erl')
-rw-r--r--src/mod_shared_roster.erl452
1 files changed, 402 insertions, 50 deletions
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 89c4777cb..4d323b7ee 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -5,7 +5,7 @@
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2008 Process-one
+%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
-%%%
+%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@@ -37,6 +37,8 @@
process_item/2,
in_subscription/6,
out_subscription/4,
+ register_user/2,
+ remove_user/2,
list_groups/1,
create_group/2,
create_group/3,
@@ -45,6 +47,7 @@
set_group_opts/3,
get_group_users/2,
get_group_explicit_users/2,
+ is_user_in_group/3,
add_user_to_group/3,
remove_user_from_group/3]).
@@ -81,7 +84,11 @@ start(Host, _Opts) ->
ejabberd_hooks:add(roster_get_jid_info, Host,
?MODULE, get_jid_info, 70),
ejabberd_hooks:add(roster_process_item, Host,
- ?MODULE, process_item, 50).
+ ?MODULE, process_item, 50),
+ ejabberd_hooks:add(register_user, Host,
+ ?MODULE, register_user, 50),
+ ejabberd_hooks:add(remove_user, Host,
+ ?MODULE, remove_user, 50).
%%ejabberd_hooks:add(remove_user, Host,
%% ?MODULE, remove_user, 50),
@@ -101,7 +108,11 @@ stop(Host) ->
ejabberd_hooks:delete(roster_get_jid_info, Host,
?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
- ?MODULE, process_item, 50).
+ ?MODULE, process_item, 50),
+ ejabberd_hooks:delete(register_user, Host,
+ ?MODULE, register_user, 50),
+ ejabberd_hooks:delete(remove_user, Host,
+ ?MODULE, remove_user, 50).
%%ejabberd_hooks:delete(remove_user, Host,
%% ?MODULE, remove_user, 50),
@@ -150,12 +161,13 @@ get_user_roster(Items, US) ->
{{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
-%% This function in use to rewrite the roster entries when moving or renaming
+%% This function rewrites the roster entries when moving or renaming
%% them in the user contact list.
process_item(RosterItem, Host) ->
- USFrom = RosterItem#roster.us,
- {User,Server,_Resource} = RosterItem#roster.jid,
- USTo = {User,Server},
+ USFrom = {UserFrom, ServerFrom} = RosterItem#roster.us,
+ {UserTo, ServerTo, ResourceTo} = RosterItem#roster.jid,
+ NameTo = RosterItem#roster.name,
+ USTo = {UserTo, ServerTo},
DisplayedGroups = get_user_displayed_groups(USFrom),
CommonGroups = lists:filter(fun(Group) ->
is_user_in_group(USTo, Group, Host)
@@ -169,9 +181,96 @@ process_item(RosterItem, Host) ->
end, CommonGroups),
RosterItem#roster{subscription = both, ask = none,
groups=[GroupNames]};
- _ -> RosterItem#roster{subscription = both, ask = none}
+ %% Both users have at least a common shared group,
+ %% So each user can see the other
+ _ ->
+ %% Check if the list of groups of the new roster item
+ %% include at least a new one
+ case lists:subtract(RosterItem#roster.groups, CommonGroups) of
+ %% If it doesn't, then remove this user from any
+ %% existing roster groups.
+ [] ->
+ %% Remove pending subscription by setting it
+ %% unsubscribed.
+ Mod = get_roster_mod(ServerFrom),
+
+ %% Remove pending out subscription
+ Mod:out_subscription(UserTo, ServerTo,
+ jlib:make_jid(UserFrom, ServerFrom, ""),
+ unsubscribe),
+
+ %% Remove pending in subscription
+ Mod:in_subscription(aaaa, UserFrom, ServerFrom,
+ jlib:make_jid(UserTo, ServerTo, ""),
+ unsubscribe, ""),
+
+ %% But we're still subscribed, so respond as such.
+ RosterItem#roster{subscription = both, ask = none};
+ %% If so, it means the user wants to add that contact
+ %% to his personal roster
+ PersonalGroups ->
+ %% Store roster items in From and To rosters
+ set_new_rosteritems(UserFrom, ServerFrom,
+ UserTo, ServerTo, ResourceTo, NameTo,
+ PersonalGroups)
+ end
end.
+build_roster_record(User1, Server1, User2, Server2, Name2, Groups) ->
+ USR2 = {User2, Server2, ""},
+ #roster{usj = {User1, Server1, USR2},
+ us = {User1, Server1},
+ jid = USR2,
+ name = Name2,
+ subscription = both,
+ ask = none,
+ groups = Groups
+ }.
+
+set_new_rosteritems(UserFrom, ServerFrom,
+ UserTo, ServerTo, ResourceTo, NameTo, GroupsFrom) ->
+ Mod = get_roster_mod(ServerFrom),
+
+ RIFrom = build_roster_record(UserFrom, ServerFrom,
+ UserTo, ServerTo, NameTo, GroupsFrom),
+ set_item(UserFrom, ServerFrom, ResourceTo, RIFrom),
+ JIDTo = jlib:make_jid(UserTo, ServerTo, ""),
+
+ JIDFrom = jlib:make_jid(UserFrom, ServerFrom, ""),
+ RITo = build_roster_record(UserTo, ServerTo,
+ UserFrom, ServerFrom, UserFrom,[]),
+ set_item(UserTo, ServerTo, "", RITo),
+
+ %% From requests
+ Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe),
+ Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""),
+
+ %% To accepts
+ Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribed),
+ Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""),
+
+ %% To requests
+ Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribe),
+ Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""),
+
+ %% From accepts
+ Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed),
+ Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""),
+
+ RIFrom.
+
+set_item(User, Server, Resource, Item) ->
+ ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
+ id = "push" ++ randoms:get_string(),
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", ?NS_ROSTER}],
+ [mod_roster:item_to_xml(Item)]}]},
+ ejabberd_router:route(
+ jlib:make_jid(User, Server, Resource),
+ jlib:make_jid("", Server, ""),
+ jlib:iq_to_xml(ResIQ)).
+
+
get_subscription_lists({F, T}, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -216,6 +315,18 @@ get_jid_info({Subscription, Groups}, User, Server, JID) ->
in_subscription(Acc, User, Server, JID, Type, _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
+out_subscription(UserFrom, ServerFrom, JIDTo, unsubscribed) ->
+ Mod = get_roster_mod(ServerFrom),
+
+ %% Remove pending out subscription
+ #jid{luser = UserTo, lserver = ServerTo} = JIDTo,
+ JIDFrom = jlib:make_jid(UserFrom, UserTo, ""),
+ Mod:out_subscription(UserTo, ServerTo, JIDFrom, unsubscribe),
+
+ %% Remove pending in subscription
+ Mod:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""),
+
+ process_subscription(out, UserFrom, ServerFrom, JIDTo, unsubscribed, false);
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, false).
@@ -252,6 +363,14 @@ list_groups(Host) ->
[{'==', '$2', Host}],
['$1']}]).
+groups_with_opts(Host) ->
+ Gs = mnesia:dirty_select(
+ sr_group,
+ [{#sr_group{group_host={'$1', Host}, opts='$2', _='_'},
+ [],
+ [['$1','$2']] }]),
+ lists:map(fun([G,O]) -> {G, O} end, Gs).
+
create_group(Host, Group) ->
create_group(Host, Group, []).
@@ -297,7 +416,7 @@ get_user_groups(US) ->
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ ->
[]
- end ++ get_all_users_groups(Host).
+ end ++ get_special_users_groups(Host).
is_group_enabled(Host, Group) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
@@ -328,23 +447,75 @@ get_group_users(Host, Group) ->
[]
end ++ get_group_explicit_users(Host, Group).
+get_group_users(_User, Host, Group, GroupOpts) ->
+ case proplists:get_value(all_users, GroupOpts, false) of
+ true ->
+ ejabberd_auth:get_vh_registered_users(Host);
+ false ->
+ []
+ end ++ get_group_explicit_users(Host, Group).
+
+%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
get_group_explicit_users(Host, Group) ->
- case catch mnesia:dirty_index_read(
- sr_user, {Group, Host}, #sr_user.group_host) of
- Rs when is_list(Rs) ->
- [R#sr_user.us || R <- Rs];
- _ ->
- []
- end.
+ Read = (catch mnesia:dirty_index_read(
+ sr_user,
+ {Group, Host},
+ #sr_user.group_host)),
+ case Read of
+ Rs when is_list(Rs) ->
+ [R#sr_user.us || R <- Rs];
+ _ ->
+ []
+ end.
get_group_name(Host, Group) ->
get_group_opt(Host, Group, name, Group).
-get_all_users_groups(Host) ->
+%% Get list of names of groups that have @all@ in the memberlist
+get_special_users_groups(Host) ->
lists:filter(
- fun(Group) -> get_group_opt(Host, Group, all_users, false) end,
+ fun(Group) ->
+ get_group_opt(Host, Group, all_users, false)
+ end,
list_groups(Host)).
+%% Given two lists of groupnames and their options,
+%% return the list of displayed groups to the second list
+displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
+ DisplayedGroups =
+ lists:usort(
+ lists:flatmap(
+ fun({_Group, Opts}) ->
+ [G || G <- proplists:get_value(displayed_groups, Opts, []),
+ not lists:member(disabled, Opts)]
+ end, SelectedGroupsOpts)),
+ [G || G <- DisplayedGroups,
+ not lists:member(disabled, proplists:get_value(G, GroupsOpts, []))].
+
+%% Given a list of group names with options,
+%% for those that have @all@ in memberlist,
+%% get the list of groups displayed
+get_special_displayed_groups(GroupsOpts) ->
+ Groups = lists:filter(
+ fun({_Group, Opts}) ->
+ proplists:get_value(all_users, Opts, false)
+ end, GroupsOpts),
+ displayed_groups(GroupsOpts, Groups).
+
+%% Given a username and server, and a list of group names with options,
+%% for the list of groups of that server that user is member
+%% get the list of groups displayed
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ Groups = case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
+ Rs when is_list(Rs) ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])} ||
+ #sr_user{group_host = {Group, H}} <- Rs, H == LServer];
+ _ ->
+ []
+ end,
+ displayed_groups(GroupsOpts, Groups).
+
+%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
DisplayedGroups1 =
@@ -360,27 +531,199 @@ get_user_displayed_groups(US) ->
end, get_user_groups(US))),
[Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
-is_user_in_group(US, Group, Host) ->
+is_user_in_group({_U, S} = US, Group, Host) ->
case catch mnesia:dirty_match_object(
#sr_user{us=US, group_host={Group, Host}}) of
- [] -> false;
+ [] -> lists:member(US, get_group_users(S, Group));
_ -> true
end.
+
+%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
add_user_to_group(Host, US, Group) ->
- R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun() ->
- mnesia:write(R)
- end,
- mnesia:transaction(F).
+ {LUser, LServer} = US,
+ case regexp:match(LUser, "^@.+@$") of
+ {match,_,_} ->
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
+ AllUsersOpt =
+ case LUser == "@all@" of
+ true -> [{all_users, true}];
+ false -> []
+ end,
+ mod_shared_roster:set_group_opts(
+ Host, Group,
+ GroupOpts ++ AllUsersOpt);
+ nomatch ->
+ %% Push this new user to members of groups where this group is displayed
+ push_user_to_displayed(LUser, LServer, Group, both),
+ %% Push members of groups that are displayed to this group
+ push_displayed_to_user(LUser, LServer, Group, Host, both),
+ R = #sr_user{us = US, group_host = {Group, Host}},
+ F = fun() ->
+ mnesia:write(R)
+ end,
+ mnesia:transaction(F)
+ end.
+
+push_displayed_to_user(LUser, LServer, Group, Host, Subscription) ->
+ GroupsOpts = groups_with_opts(LServer),
+ GroupOpts = proplists:get_value(Group, GroupsOpts, []),
+ DisplayedGroups = proplists:get_value(displayed_groups, GroupOpts, []),
+ [push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups].
remove_user_from_group(Host, US, Group) ->
GroupHost = {Group, Host},
- R = #sr_user{us = US, group_host = GroupHost},
- F = fun() ->
- mnesia:delete_object(R)
- end,
- mnesia:transaction(F).
+ {LUser, LServer} = US,
+ case regexp:match(LUser, "^@.+@$") of
+ {match,_,_} ->
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
+ NewGroupOpts =
+ case LUser of
+ "@all@" ->
+ lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts)
+ end,
+ mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts);
+ nomatch ->
+ R = #sr_user{us = US, group_host = GroupHost},
+ F = fun() ->
+ mnesia:delete_object(R)
+ end,
+ Result = mnesia:transaction(F),
+ %% Push removal of the old user to members of groups where the group that this user was members was displayed
+ push_user_to_displayed(LUser, LServer, Group, remove),
+ %% Push removal of members of groups that where displayed to the group which this user has left
+ push_displayed_to_user(LUser, LServer, Group, Host, remove),
+ Result
+ end.
+
+
+push_members_to_user(LUser, LServer, Group, Host, Subscription) ->
+ GroupsOpts = groups_with_opts(LServer),
+ GroupOpts = proplists:get_value(Group, GroupsOpts, []),
+ GroupName = proplists:get_value(name, GroupOpts, Group),
+ Members = get_group_users(Host, Group),
+ lists:foreach(
+ fun({U, S}) ->
+ push_roster_item(LUser, LServer, U, S, GroupName, Subscription)
+ end, Members).
+
+register_user(User, Server) ->
+ %% Get list of groups where this user is member
+ Groups = get_user_groups({User, Server}),
+ %% Push this user to members of groups where is displayed a group which this user is member
+ [push_user_to_displayed(User, Server, Group, both) || Group <- Groups].
+
+remove_user(User, Server) ->
+ push_user_to_members(User, Server, remove).
+
+push_user_to_members(User, Server, Subscription) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ GroupsOpts = groups_with_opts(LServer),
+ SpecialGroups = get_special_displayed_groups(GroupsOpts),
+ UserGroups = get_user_displayed_groups(LUser, LServer, GroupsOpts),
+ lists:foreach(
+ fun(Group) ->
+ GroupOpts = proplists:get_value(Group, GroupsOpts, []),
+ GroupName = proplists:get_value(name, GroupOpts, Group),
+ lists:foreach(
+ fun({U, S}) ->
+ push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
+ end, get_group_users(LUser, LServer, Group, GroupOpts))
+ end, lists:usort(SpecialGroups++UserGroups)).
+
+push_user_to_displayed(LUser, LServer, Group, Subscription) ->
+ GroupsOpts = groups_with_opts(LServer),
+ GroupOpts = proplists:get_value(Group, GroupsOpts, []),
+ GroupName = proplists:get_value(name, GroupOpts, Group),
+ DisplayedToGroupsOpts = displayed_to_groups(Group, LServer),
+ [push_user_to_group(LUser, LServer, GroupD, GroupName, Subscription) || {GroupD, _Opts} <- DisplayedToGroupsOpts].
+
+push_user_to_group(LUser, LServer, Group, GroupName, Subscription) ->
+ lists:foreach(
+ fun({U, S}) ->
+ push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
+ end, get_group_users(LServer, Group)).
+
+%% Get list of groups to which this group is displayed
+displayed_to_groups(GroupName, LServer) ->
+ GroupsOpts = groups_with_opts(LServer),
+ lists:filter(
+ fun({_Group, Opts}) ->
+ lists:member(GroupName, proplists:get_value(displayed_groups, Opts, []))
+ end, GroupsOpts).
+
+push_item(_User, _Server, _From, none) ->
+ ok;
+push_item(User, Server, From, Item) ->
+ %% It was
+ %% ejabberd_sm:route(jlib:make_jid("", "", ""),
+ %% jlib:make_jid(User, Server, "")
+ %% why?
+ ejabberd_sm:route(From, jlib:make_jid(User, Server, ""),
+ {xmlelement, "broadcast", [],
+ [{item,
+ Item#roster.jid,
+ Item#roster.subscription}]}),
+ Stanza = jlib:iq_to_xml(
+ #iq{type = set, xmlns = ?NS_ROSTER,
+ id = "push" ++ randoms:get_string(),
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", ?NS_ROSTER}],
+ [item_to_xml(Item)]}]}),
+ lists:foreach(
+ fun(Resource) ->
+ JID = jlib:make_jid(User, Server, Resource),
+ ejabberd_router:route(JID, JID, Stanza)
+ end, ejabberd_sm:get_user_resources(User, Server)).
+
+push_roster_item(User, Server, ContactU, ContactS, GroupName, Subscription) ->
+ Item = #roster{usj = {User, Server, {ContactU, ContactS, ""}},
+ us = {User, Server},
+ jid = {ContactU, ContactS, ""},
+ name = "",
+ subscription = Subscription,
+ ask = none,
+ groups = [GroupName]},
+ push_item(User, Server, jlib:make_jid("", Server, ""), Item).
+
+item_to_xml(Item) ->
+ Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}],
+ Attrs2 = case Item#roster.name of
+ "" ->
+ Attrs1;
+ Name ->
+ [{"name", Name} | Attrs1]
+ end,
+ Attrs3 = case Item#roster.subscription of
+ none ->
+ [{"subscription", "none"} | Attrs2];
+ from ->
+ [{"subscription", "from"} | Attrs2];
+ to ->
+ [{"subscription", "to"} | Attrs2];
+ both ->
+ [{"subscription", "both"} | Attrs2];
+ remove ->
+ [{"subscription", "remove"} | Attrs2]
+ end,
+ Attrs4 = case ask_to_pending(Item#roster.ask) of
+ out ->
+ [{"ask", "subscribe"} | Attrs3];
+ both ->
+ [{"ask", "subscribe"} | Attrs3];
+ _ ->
+ Attrs3
+ end,
+ SubEls1 = lists:map(fun(G) ->
+ {xmlelement, "group", [], [{xmlcdata, G}]}
+ end, Item#roster.groups),
+ SubEls = SubEls1 ++ Item#roster.xs,
+ {xmlelement, "item", Attrs4, SubEls}.
+
+ask_to_pending(subscribe) -> out;
+ask_to_pending(unsubscribe) -> none;
+ask_to_pending(Ask) -> Ask.
%%---------------------
@@ -430,10 +773,10 @@ list_shared_roster_groups(Host, Query, Lang) ->
]
)]
)]),
- [?XC("h1", ?T("Shared Roster Groups"))] ++
+ ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++
case Res of
- ok -> [?CT("Submitted"), ?P];
- error -> [?CT("Bad format"), ?P];
+ ok -> [?XREST("Submitted")];
+ error -> [?XREST("Bad format")];
nothing -> []
end ++
[?XAE("form", [{"action", ""}, {"method", "post"}],
@@ -496,8 +839,9 @@ shared_roster_group(Host, Group, Query, Lang) ->
[]
end ++ [[us_to_list(Member), $\n] || Member <- Members],
FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
+ DescNL = length(element(2, regexp:split(Description, "\n"))),
FGroup =
- ?XAE("table", [],
+ ?XAE("table", [{"class", "withtextareas"}],
[?XE("tbody",
[?XE("tr",
[?XCT("td", "Name:"),
@@ -506,34 +850,34 @@ shared_roster_group(Host, Group, Query, Lang) ->
),
?XE("tr",
[?XCT("td", "Description:"),
- ?XE("td", [?XAC("textarea", [{"name", "description"},
- {"rows", "3"},
- {"cols", "20"}],
- Description)])
+ ?XE("td", [
+ ?TEXTAREA("description", integer_to_list(lists:max([3, DescNL])), "20", Description)
+ ]
+ )
]
),
?XE("tr",
[?XCT("td", "Members:"),
- ?XE("td", [?XAC("textarea", [{"name", "members"},
- {"rows", "3"},
- {"cols", "20"}],
- FMembers)])
+ ?XE("td", [
+ ?TEXTAREA("members", integer_to_list(lists:max([3, length(FMembers)])), "20", FMembers)
+ ]
+ )
]
),
?XE("tr",
[?XCT("td", "Displayed Groups:"),
- ?XE("td", [?XAC("textarea", [{"name", "dispgroups"},
- {"rows", "3"},
- {"cols", "20"}],
- FDisplayedGroups)])
+ ?XE("td", [
+ ?TEXTAREA("dispgroups", integer_to_list(lists:max([3, length(FDisplayedGroups)])), "20", FDisplayedGroups)
+ ]
+ )
]
)]
)]),
- [?XC("h1", ?T("Shared Roster Groups"))] ++
+ ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++
[?XC("h2", ?T("Group ") ++ Group)] ++
case Res of
- ok -> [?CT("Submitted"), ?P];
- error -> [?CT("Bad format"), ?P];
+ ok -> [?XREST("Submitted")];
+ error -> [?XREST("Bad format")];
nothing -> []
end ++
[?XAE("form", [{"action", ""}, {"method", "post"}],
@@ -617,6 +961,14 @@ shared_roster_group_parse_query(Host, Group, Query) ->
nothing
end.
+%% Get the roster module for Server.
+get_roster_mod(Server) ->
+ case lists:member(mod_roster_odbc,
+ gen_mod:loaded_modules(Server)) of
+ true -> mod_roster_odbc;
+ false -> mod_roster
+ end.
+
get_opt(Opts, Opt, Default) ->
case lists:keysearch(Opt, 1, Opts) of
{value, {_, Val}} ->