ejabberd-contrib/mod_webpresence/src/mod_webpresence.erl

1075 lines
37 KiB
Erlang
Raw Normal View History

%%%----------------------------------------------------------------------
%%% File : mod_webpresence.erl
%%% Author : Igor Goryachev <igor@goryachev.org>, Badlop
%%% Purpose : Allow user to show presence in the web
%%% Created : 30 Apr 2006 by Igor Goryachev <igor@goryachev.org>
%%% Id : $Id: mod_webpresence.erl 1083 2010-06-01 18:32:55Z badlop $
%%%----------------------------------------------------------------------
-module(mod_webpresence).
-behaviour(gen_server).
-behaviour(gen_mod).
%% API
-export([start_link/2,
start/2,
stop/1,
remove_user/2,
web_menu_host/3, web_page_host/3,
process/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("web/ejabberd_web_admin.hrl").
-include("web/ejabberd_http.hrl").
-record(webpresence, {us, ridurl = false, jidurl = false, xml = false, avatar = false, js = false, text = false, icon = "---"}).
-record(state, {host, server_host, base_url, access}).
-record(presence, {resource, show, priority, status}).
%% Copied from ejabberd_sm.erl
-record(session, {sid, usr, us, priority, info}).
-define(PROCNAME, ejabberd_mod_webpresence).
-define(PIXMAPS_DIR, "pixmaps").
-define(AUTO_ACL, webpresence_auto).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
Default_dir = case code:priv_dir(ejabberd) of
{error, _} -> ?PIXMAPS_DIR;
Path -> filename:join([Path, ?PIXMAPS_DIR])
end,
Dir = gen_mod:get_opt(pixmaps_path, Opts, Default_dir),
catch ets:new(pixmaps_dirs, [named_table, public]),
ets:insert(pixmaps_dirs, {directory, Dir}),
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
mnesia:create_table(webpresence,
[{disc_copies, [node()]},
{attributes, record_info(fields, webpresence)}]),
mnesia:add_table_index(webpresence, ridurl),
update_table(),
MyHost = gen_mod:get_opt_host(Host, Opts, "webpresence.@HOST@"),
Access = gen_mod:get_opt(access, Opts, local),
Port = gen_mod:get_opt(port, Opts, 5280),
Path = gen_mod:get_opt(path, Opts, "presence"),
BaseURL = gen_mod:get_opt(baseurl, Opts, io_lib:format("http://~s:~p/~s/",[Host, Port, Path])),
ejabberd_router:register_route(MyHost),
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
{ok, #state{host = MyHost,
server_host = Host,
base_url = BaseURL,
access = Access}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
#state{host = Host,
server_host = ServerHost,
base_url = BaseURL,
access = Access} = State) ->
case catch do_route(Host, ServerHost, Access, From, To, Packet, BaseURL) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
{noreply, State};
handle_info({tell_baseurl, Pid},
#state{base_url = BaseURL} = State) ->
Pid ! {baseurl_is, BaseURL},
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, #state{host = Host}) ->
ejabberd_router:unregister_route(Host),
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(Host, ServerHost, Access, From, To, Packet, BaseURL) ->
case acl:match_rule(ServerHost, Access, From) of
allow ->
do_route1(Host, From, To, Packet, BaseURL);
_ ->
{xmlelement, _Name, Attrs, _Els} = Packet,
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "Access denied by service policy",
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end.
do_route1(Host, From, To, Packet, BaseURL) ->
{xmlelement, Name, Attrs, _Els} = Packet,
case Name of
"iq" -> do_route1_iq(Host, From, To, Packet, BaseURL, jlib:iq_query_info(Packet));
_ -> case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
_ -> Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
ejabberd_router:route(To, From, Err)
end
end.
do_route1_iq(_, From, To, _, _,
#iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ) ->
SubEl2 = {xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Lang)},
Res = IQ#iq{type = result, sub_el = [SubEl2]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
do_route1_iq(_, _, _, _, _,
#iq{type = get, xmlns = ?NS_DISCO_ITEMS}) ->
ok;
do_route1_iq(Host, From, To, _, _,
#iq{type = get, xmlns = ?NS_REGISTER, lang = Lang} = IQ) ->
SubEl2 = {xmlelement, "query", [{"xmlns", ?NS_REGISTER}], iq_get_register_info(Host, From, Lang)},
Res = IQ#iq{type = result, sub_el = [SubEl2]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
do_route1_iq(Host, From, To, Packet, BaseURL,
#iq{type = set, xmlns = ?NS_REGISTER, lang = Lang, sub_el = SubEl} = IQ) ->
case process_iq_register_set(From, SubEl, Host, BaseURL, Lang) of
{result, IQRes} ->
SubEl2 = {xmlelement, "query", [{"xmlns", ?NS_REGISTER}], IQRes},
Res = IQ#iq{type = result, sub_el = [SubEl2]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
{error, Error} ->
Err = jlib:make_error_reply(Packet, Error),
ejabberd_router:route(To, From, Err)
end;
do_route1_iq(_Host, From, To, _, _,
#iq{type = get, xmlns = ?NS_VCARD = XMLNS} = IQ) ->
SubEl2 = {xmlelement, "vCard", [{"xmlns", XMLNS}], iq_get_vcard()},
Res = IQ#iq{type = result, sub_el = [SubEl2]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
do_route1_iq(_Host, From, To, Packet, _, #iq{}) ->
Err = jlib:make_error_reply( Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
ejabberd_router:route(To, From, Err);
do_route1_iq(_, _, _, _, _, _) ->
ok.
iq_disco_info(Lang) ->
[{xmlelement, "identity",
[{"category", "component"},
{"type", "presence"},
{"name", ?T("Web Presence")}], []},
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
-define(XFIELDS(Type, Label, Var, Vals),
{xmlelement, "field", [{"type", Type},
{"label", ?T(Label)},
{"var", Var}],
Vals}).
-define(XFIELD(Type, Label, Var, Val),
?XFIELDS(Type, Label, Var,
[{xmlelement, "value", [], [{xmlcdata, Val}]}])
).
-define(XFIELDFIXED(Val),
{xmlelement, "field", [{"type", "fixed"}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}
).
%% @spec ridurl_out(ridurl()) -> boolean_string()
%% @type ridurl() = string() | false
%% @type boolean_string() = "true" | "false"
ridurl_out(false) -> "false";
ridurl_out(Id) when is_list(Id) -> "true".
to_bool("false") -> false;
to_bool("true") -> true;
to_bool("0") -> false;
to_bool("1") -> true.
get_pr(LUS) ->
case catch mnesia:dirty_read(webpresence, LUS) of
[#webpresence{jidurl = J, ridurl = H, xml = X, avatar = A, js = S, text = T, icon = I}] ->
{J, H, X, A, S, T, I, true};
_ ->
{true, false, false, false, false, false, "---", false}
end.
get_pr_rid(LUS) ->
{_, H, _, _, _, _, _, _} = get_pr(LUS),
H.
iq_get_register_info(_Host, From, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
{JidUrl, RidUrl, XML, Avatar, JS, Text, Icon, Registered} = get_pr(LUS),
RegisteredXML = case Registered of
true -> [{xmlelement, "registered", [], []}];
false -> []
end,
RegisteredXML ++
[{xmlelement, "instructions", [],
[{xmlcdata, ?T("You need an x:data capable client to register presence")}]},
{xmlelement, "x",
[{"xmlns", ?NS_XDATA}],
[{xmlelement, "title", [],
[{xmlcdata,
?T("Web Presence")}]},
{xmlelement, "instructions", [], [{xmlcdata, ?T("What features do you want to enable?")}]},
?XFIELDFIXED(?T("URL Type")++". "++?T("Select one at least")),
?XFIELD("boolean", "Jabber ID", "jidurl", atom_to_list(JidUrl)),
?XFIELD("boolean", "Random ID", "ridurl", ridurl_out(RidUrl)),
?XFIELDFIXED(?T("Output Type")++". "++?T("Select one at least")),
?XFIELDS("list-single", ?T("Icon Theme"), "icon",
[{xmlelement, "value", [], [{xmlcdata, Icon}]},
{xmlelement, "option", [{"label", "---"}],
[{xmlelement, "value", [], [{xmlcdata, "---"}]}]}
] ++ available_themes(xdata)
),
?XFIELD("boolean", "XML", "xml", atom_to_list(XML)),
?XFIELD("boolean", "JavaScript", "js", atom_to_list(JS)),
?XFIELD("boolean", "Text", "text", atom_to_list(Text)),
?XFIELD("boolean", "Avatar", "avatar", atom_to_list(Avatar))]}].
%% TODO: Check if remote users are allowed to reach here: they should not be allowed
iq_set_register_info(From, {Host, JidUrl, RidUrl, XML, Avatar, JS, Text, Icon, _, Lang} = Opts) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
Check_URLTypes = (JidUrl == true) or (RidUrl =/= false),
Check_OutputTypes = (XML == true) or (Avatar == true) or (JS == true) or (Text == true) or (Icon =/= "---"),
case Check_URLTypes and Check_OutputTypes of
true -> iq_set_register_info2(From, LUS, Opts);
false -> unregister_webpresence(From, Host, Lang)
end.
iq_set_register_info2(From, LUS, {Host, JidUrl, RidUrl, XML, Avatar, JS, Text, Icon, BaseURL, Lang}) ->
RidUrl2 = get_rid_final_value(RidUrl, LUS),
WP = #webpresence{us = LUS,
jidurl = JidUrl,
ridurl = RidUrl2,
xml = XML,
avatar = Avatar,
js = JS,
text = Text,
icon = Icon},
F = fun() -> mnesia:write(WP) end,
case mnesia:transaction(F) of
{atomic, ok} ->
send_message_registered(WP, From, Host, BaseURL, Lang),
{result, []};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
get_rid_final_value(false, _) -> false;
get_rid_final_value(true, {U, S} = LUS) ->
case get_pr_rid(LUS) of
false ->
integer_to_list(erlang:phash2(U) * erlang:phash2(S)
* calendar:datetime_to_gregorian_seconds(
calendar:local_time()))
++ randoms:get_string();
H when is_list(H) ->
H
end.
send_message_registered(WP, To, Host, BaseURL, Lang) ->
{User, Server} = WP#webpresence.us,
JID = jlib:make_jid(User, Server, ""),
JIDS = jlib:jid_to_string(JID),
Oavatar = case WP#webpresence.avatar of
false -> "";
true -> " avatar\n"
" avatar/my.png\n"
end,
Ojs = case WP#webpresence.js of
false -> "";
true -> " js\n"
end,
Otext = case WP#webpresence.text of
false -> "";
true -> " text\n"
" text/res/<"++?T("Resource")++">\n"
end,
Oimage = case WP#webpresence.icon of
"---" -> "";
I when is_list(I) ->
" image\n"
" image/example.php\n"
" image/mypresence.png\n"
" image/res/<"++?T("Resource")++">\n"
" image/theme/<"++?T("Icon Theme")++">\n"
" image/theme/<"++?T("Icon Theme")++">/res/<"++?T("Resource")++">\n"
end,
Oxml = case WP#webpresence.xml of
false -> "";
true -> " xml\n"
end,
Allowed_type = case {Oimage, Oxml, Oavatar, Otext, Ojs} of
{"", "", "", "", _} -> "js";
{"", "", "", _, _} -> "text";
{"", "", _, _, _} -> "avatar";
{"", _, _, _, _} -> "xml";
{_, _, _, _, _} -> "image"
end,
{USERID_jid, Example_jid} = case WP#webpresence.jidurl of
false -> {"", ""};
true ->
JIDT = "jid/"++User++"/"++Server,
{" "++JIDT++"\n",
" "++BaseURL++JIDT++"/"++Allowed_type++"/\n"}
end,
{USERID_rid, Example_rid, Text_rid} = case WP#webpresence.ridurl of
false -> {"", "", ""};
RID when is_list(RID) ->
RIDT = "rid/"++RID,
{" "++RIDT++"\n",
" "++BaseURL++RIDT++"/"++Allowed_type++"/\n",
?T("If you forget your RandomID, register again to receive this message.")++"\n"
++?T("To get a new RandomID, disable the option and register again.")++"\n"
}
end,
Subject = ?T("Web Presence")++": "++?T("registered"),
Body = ?T("You have registered:")++" "++JIDS++"\n\n"
++?T("Use URLs like:")++"\n"
" "++BaseURL++"USERID/OUTPUT/\n"
"\n"
"USERID:\n"++USERID_jid++USERID_rid++"\n"
"OUTPUT:\n"++Oimage++Oxml++Ojs++Otext++Oavatar++"\n"
++?T("Example:")++"\n"++Example_jid++Example_rid++"\n"
++Text_rid,
send_headline(Host, To, Subject, Body).
send_message_unregistered(To, Host, Lang) ->
Subject = ?T("Web Presence")++": "++?T("unregistered"),
Body = ?T("You have unregistered.")++"\n\n",
send_headline(Host, To, Subject, Body).
send_headline(Host, To, Subject, Body) ->
ejabberd_router:route(
jlib:make_jid("", Host, ""),
To,
{xmlelement, "message", [{"type", "headline"}],
[{xmlelement, "subject", [], [{xmlcdata, Subject}]},
{xmlelement, "body", [], [{xmlcdata, Body}]}]}).
get_attr(Attr, XData, Default) ->
case lists:keysearch(Attr, 1, XData) of
{value, {_, [Value]}} -> Value;
false -> Default
end.
process_iq_register_set(From, SubEl, Host, BaseURL, Lang) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:get_subtag(SubEl, "remove") of
false -> case catch process_iq_register_set2(From, Els, Host, BaseURL, Lang) of
{'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
R -> R
end;
_ -> unregister_webpresence(From, Host, Lang)
end.
process_iq_register_set2(From, Els, Host, BaseURL, Lang) ->
[{xmlelement, "x", _Attrs1, _Els1} = XEl] = xml:remove_cdata(Els),
case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of
{?NS_XDATA, "cancel"} ->
{result, []};
{?NS_XDATA, "submit"} ->
XData = jlib:parse_xdata_submit(XEl),
false = (invalid == XData),
JidUrl = get_attr("jidurl", XData, "false"),
RidUrl = get_attr("ridurl", XData, "false"),
XML = get_attr("xml", XData, "false"),
Avatar = get_attr("avatar", XData, "false"),
JS = get_attr("js", XData, "false"),
Text = get_attr("text", XData, "false"),
Icon = get_attr("icon", XData, "---"),
iq_set_register_info(From, {Host, to_bool(JidUrl), to_bool(RidUrl), to_bool(XML), to_bool(Avatar), to_bool(JS), to_bool(Text), Icon, BaseURL, Lang})
end.
unregister_webpresence(From, Host, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
remove_user(LUser, LServer),
send_message_unregistered(From, Host, Lang),
{result, []}.
remove_user(User, Server) ->
mnesia:dirty_delete(webpresence, {User, Server}).
iq_get_vcard() ->
[{xmlelement, "FN", [],
[{xmlcdata, "ejabberd/mod_webpresence"}]},
{xmlelement, "URL", [],
[{xmlcdata, "http://www.ejabberd.im/mod_webpresence"}]},
{xmlelement, "DESC", [],
[{xmlcdata, "ejabberd web presence module\nCopyright (c) 2006-2007 Igor Goryachev, 2007 Badlop"}]}].
get_wp(LUser, LServer) ->
LUS = {LUser, LServer},
case catch mnesia:dirty_read(webpresence, LUS) of
{'EXIT', _Reason} ->
try_auto_webpresence(LUser, LServer);
[] ->
try_auto_webpresence(LUser, LServer);
[WP] when is_record(WP, webpresence) ->
WP
end.
try_auto_webpresence(LUser, LServer) ->
From = jlib:make_jid(LUser, LServer, ""),
case acl:match_rule(LServer, ?AUTO_ACL, From) of
deny ->
#webpresence{};
allow ->
#webpresence{us = {LUser, LServer},
ridurl = false,
jidurl = true,
xml = true,
avatar = true,
js = true,
text = true,
icon = "jsf-jabber-text"}
end.
get_status_weight(Show) ->
case Show of
"chat" -> 0;
"available" -> 1;
"away" -> 2;
"xa" -> 3;
"dnd" -> 4;
_ -> 9
end.
session_to_presence(#session{sid = {_, Pid}, priority = Priority}) ->
{_User, Resource, Show, Status} = ejabberd_c2s:get_presence(Pid),
#presence{resource = Resource,
show = Show,
priority = Priority,
status = Status}.
get_presences({bare, LUser, LServer}) ->
[session_to_presence(Session) ||
Session <- mnesia:dirty_index_read(session, {LUser, LServer}, #session.us)];
get_presences({sorted, LUser, LServer}) ->
lists:sort(
fun(A, B) ->
if
A#presence.priority == B#presence.priority ->
WA = get_status_weight(A#presence.show),
WB = get_status_weight(B#presence.show),
WA < WB;
true ->
A#presence.priority > B#presence.priority
end
end,
get_presences({bare, LUser, LServer}));
get_presences({xml, LUser, LServer, Show_us}) ->
{xmlelement, "presence",
case Show_us of
true -> [{"user", LUser}, {"server", LServer}];
false -> []
end,
lists:map(
fun(Presence) ->
{xmlelement, "resource",
[{"name", Presence#presence.resource},
{"show", Presence#presence.show},
{"priority", integer_to_list(Presence#presence.priority)}],
[{xmlcdata, Presence#presence.status}]}
end,
get_presences({sorted, LUser, LServer}))};
get_presences({status, LUser, LServer, LResource}) ->
case get_presences({sorted, LUser, LServer}) of
[] -> "unavailable";
Rs ->
{value, R} = lists:keysearch(LResource, 2, Rs),
R#presence.status
end;
get_presences({status, LUser, LServer}) ->
case get_presences({sorted, LUser, LServer}) of
[Highest | _Rest] ->
Highest#presence.status;
_ ->
"unavailable"
end;
get_presences({show, LUser, LServer, LResource}) ->
case get_presences({sorted, LUser, LServer}) of
[] -> "unavailable";
Rs ->
{value, R} = lists:keysearch(LResource, 2, Rs),
R#presence.show
end;
get_presences({show, LUser, LServer}) ->
case get_presences({sorted, LUser, LServer}) of
[Highest | _Rest] ->
Highest#presence.show;
_ ->
"unavailable"
end.
make_js(WP, Prs, Show_us, Lang, Q) ->
{User, Server} = WP#webpresence.us,
BaseURL = get_baseurl(Server),
US_string = case Show_us of
true ->
"var jabber_user='"++User++"';\n"
"var jabber_server='"++Server++"';\n";
false -> ""
end,
FunImage = fun(I, S) ->
case I of
"---" -> "";
Icon -> " image:'"++BaseURL++"image/"++Icon++"/"++S++"'\n"
end
end,
R_string_list = case Prs of
[] ->
Show = "unavailable",
["{show:'"++Show++"',\n"
" long_show:'"++long_show(Show, Lang)++"',\n"
" status:'',\n" % TODO
++ FunImage(WP#webpresence.icon, Show) ++
"}"];
_ -> lists:map(
fun(Pr) ->
Show = Pr#presence.show,
"{name:'"++Pr#presence.resource++"',\n"
" priority:"++intund2string(Pr#presence.priority)++",\n"
" show:'"++Show++"',\n"
" long_show:'"++long_show(Show, Lang)++"',\n"
" status:'"++escape(Pr#presence.status)++"',\n"
++ FunImage(WP#webpresence.icon, Show) ++
"}"
end,
Prs)
end,
R_string = lists:foldl(
fun(RS, Res) ->
case Res of
"" -> RS;
_ -> Res ++ ",\n" ++ RS
end
end,
"",
R_string_list),
CB_string = case lists:keysearch("cb", 1, Q) of
{value, {_, CB}} -> " " ++ CB ++ "();";
_ -> ""
end,
US_string ++ "var jabber_resources=[\n"++R_string++"];" ++ CB_string.
long_show("available", Lang) -> ?T("available");
long_show("chat", Lang) -> ?T("free for chat");
long_show("away", Lang) -> ?T("away");
long_show("xa", Lang) -> ?T("extended away");
long_show("dnd", Lang) -> ?T("do not disturb");
long_show(_, Lang) -> ?T("unavailable").
%% @spec(A) -> string()
%% where A = integer() | undefined
intund2string(undefined) -> "undefined";
intund2string(Int) when is_integer(Int) -> integer_to_list(Int).
escape(S1) ->
S2 = re:replace(S1, "\'", "\\'", [global, {return, list}]),
re:replace(S2, "\n", "\\n", [global, {return, list}]).
get_baseurl(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! {tell_baseurl, self()},
receive
{baseurl_is, BaseURL} -> BaseURL
end.
-define(XML_HEADER, "<?xml version='1.0' encoding='utf-8'?>").
get_pixmaps_directory() ->
[{directory, Path} | _] = ets:lookup(pixmaps_dirs, directory),
Path.
available_themes(list) ->
case file:list_dir(get_pixmaps_directory()) of
{ok, List} ->
L2 = lists:sort(List),
%% Remove from the list of themes the directories that start with a dot
[T || T <- L2, hd(T) =/= 46];
{error, _} ->
[]
end;
available_themes(xdata) ->
lists:map(
fun(Theme) ->
{xmlelement, "option", [{"label", Theme}],
[{xmlelement, "value", [], [{xmlcdata, Theme}]}]}
end, available_themes(list)).
show_presence({image_no_check, Theme, Pr}) ->
Dir = get_pixmaps_directory(),
Image = Pr ++ ".{gif,png,jpg}",
[First | _Rest] = filelib:wildcard(filename:join([Dir, Theme, Image])),
Mime = string:substr(First, string:len(First) - 2, 3),
{ok, Content} = file:read_file(First),
{200, [{"Content-Type", "image/" ++ Mime}], binary_to_list(Content)};
show_presence({image, WP, LUser, LServer}) ->
Icon = WP#webpresence.icon,
false = ("---" == Icon),
Pr = get_presences({show, LUser, LServer}),
show_presence({image_no_check, Icon, Pr});
show_presence({image, WP, LUser, LServer, Theme}) ->
false = ("---" == WP#webpresence.icon),
Pr = get_presences({show, LUser, LServer}),
show_presence({image_no_check, Theme, Pr});
show_presence({image_res, WP, LUser, LServer, LResource}) ->
Icon = WP#webpresence.icon,
false = ("---" == Icon),
Pr = get_presences({show, LUser, LServer, LResource}),
show_presence({image_no_check, Icon, Pr});
show_presence({image_res, WP, LUser, LServer, Theme, LResource}) ->
false = ("---" == WP#webpresence.icon),
Pr = get_presences({show, LUser, LServer, LResource}),
show_presence({image_no_check, Theme, Pr});
show_presence({xml, WP, LUser, LServer, Show_us}) ->
true = WP#webpresence.xml,
Presence_xml = xml:element_to_string(get_presences({xml, LUser, LServer, Show_us})),
{200, [{"Content-Type", "text/xml; charset=utf-8"}], ?XML_HEADER ++ Presence_xml};
show_presence({js, WP, LUser, LServer, Show_us, Lang, Q}) ->
true = WP#webpresence.js,
Prs = get_presences({sorted, LUser, LServer}),
Js = make_js(WP, Prs, Show_us, Lang, Q),
{200, [{"Content-Type", "text/html; charset=utf-8"}], Js};
show_presence({text, WP, LUser, LServer}) ->
true = WP#webpresence.text,
Presence_text = get_presences({status, LUser, LServer}),
{200, [{"Content-Type", "text/html; charset=utf-8"}], Presence_text};
show_presence({text, WP, LUser, LServer, LResource}) ->
true = WP#webpresence.text,
Presence_text = get_presences({status, LUser, LServer, LResource}),
{200, [{"Content-Type", "text/html; charset=utf-8"}], Presence_text};
show_presence({avatar, WP, LUser, LServer}) ->
true = WP#webpresence.avatar,
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, LServer}),
JID = jlib:make_jid(LUser, LServer, ""),
IQ = #iq{type = get, xmlns = ?NS_VCARD},
IQr = Module:Function(JID, JID, IQ),
[VCard] = IQr#iq.sub_el,
Mime = xml:get_path_s(VCard, [{elem, "PHOTO"}, {elem, "TYPE"}, cdata]),
BinVal = xml:get_path_s(VCard, [{elem, "PHOTO"}, {elem, "BINVAL"}, cdata]),
Photo = jlib:decode_base64(BinVal),
{200, [{"Content-Type", Mime}], Photo};
show_presence({image_example, Theme, Show}) ->
Dir = get_pixmaps_directory(),
Image = Show ++ ".{gif,png,jpg}",
[First | _Rest] = filelib:wildcard(filename:join([Dir, Theme, Image])),
Mime = string:substr(First, string:len(First) - 2, 3),
{ok, Content} = file:read_file(First),
{200, [{"Content-Type", "image/" ++ Mime}], binary_to_list(Content)}.
%% ---------------------
%% Web Publish
%% ---------------------
make_xhtml(Els) -> make_xhtml([], Els).
make_xhtml(Title, Els) ->
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", "en"},
{"lang", "en"}],
[{xmlelement, "head", [],
[{xmlelement, "meta", [{"http-equiv", "Content-Type"},
{"content", "text/html; charset=utf-8"}], []}]
++ Title},
{xmlelement, "body", [], Els}
]}.
themes_to_xhtml(Themes) ->
ShowL = ["available", "chat", "dnd", "away", "xa", "unavailable"],
THeadL = [""] ++ ShowL,
[?XAE("table", [],
[?XE("tr", [?XC("th", T) || T <- THeadL])] ++
[?XE("tr", [?XC("td", Theme) |
[?XE("td", [?XA("img", [{"src", "image/"++Theme++"/"++T}])]) || T <- ShowL]
]
) || Theme <- Themes]
)
].
parse_lang(Lang) -> hd(string:tokens(Lang,"-")).
process(LocalPath, Request) ->
case catch process2(LocalPath, Request) of
{'EXIT', Reason} ->
?DEBUG("The call to path ~p in the~nrequest: ~p~ncrashed with error: ~p", [LocalPath, Request, Reason]),
{404, [], make_xhtml([?XC("h1", "Not found")])};
Res ->
Res
end.
process2([], #request{lang = Lang1}) ->
Lang = parse_lang(Lang1),
Title = [?XC("title", ?T("Web Presence"))],
Desc = [?XC("p", ?T("To publish your presence using this system you need a Jabber account in this Jabber server.")++" "++
?T("Login with a Jabber client, open the Service Discovery and register in Web Presence.")++
?T("You will receive a message with further instructions."))],
Link_themes = [?AC("themes", ?T("Icon Theme"))],
Body = [?XC("h1", ?T("Web Presence"))] ++ Desc ++ Link_themes,
make_xhtml(Title, Body);
process2(["themes"], #request{lang = Lang1}) ->
Lang = parse_lang(Lang1),
Title = [?XC("title", ?T("Web Presence")++" - "++?T("Icon Theme"))],
Themes = available_themes(list),
Icon_themes = themes_to_xhtml(Themes),
Body = [?XC("h1", ?T("Icon Theme"))] ++ Icon_themes,
make_xhtml(Title, Body);
process2(["image", Theme, Show], _Request) ->
Args = {image_example, Theme, Show},
show_presence(Args);
process2(["jid", User, Server | Tail], Request) ->
serve_web_presence(jid, User, Server, Tail, Request);
process2(["rid", Rid | Tail], Request) ->
[Pr] = mnesia:dirty_index_read(webpresence, Rid, #webpresence.ridurl),
{User, Server} = Pr#webpresence.us,
serve_web_presence(rid, User, Server, Tail, Request);
%% Compatibility with old mod_presence
process2([User, Server | Tail], Request) ->
serve_web_presence(jid, User, Server, Tail, Request).
serve_web_presence(TypeURL, User, Server, Tail, #request{lang = Lang1, q = Q}) ->
LServer = jlib:nameprep(Server),
true = lists:member(LServer, ?MYHOSTS),
LUser = jlib:nodeprep(User),
WP = get_wp(LUser, LServer),
case TypeURL of
jid -> true = WP#webpresence.jidurl;
rid -> true = is_list(WP#webpresence.ridurl)
end,
Show_us = (TypeURL == jid),
Lang = parse_lang(Lang1),
Args = case Tail of
["image", "theme", Theme, "res", Resource | _] ->
{image_res, WP, LUser, LServer, Theme, Resource};
["image", "theme", Theme | _] ->
{image, WP, LUser, LServer, Theme};
["image", "res", Resource | _] ->
{image_res, WP, LUser, LServer, Resource};
["image" | _] ->
{image, WP, LUser, LServer};
["xml"] ->
{xml, WP, LUser, LServer, Show_us};
["js"] ->
{js, WP, LUser, LServer, Show_us, Lang, Q};
["text"] ->
{text, WP, LUser, LServer};
["text", "res", Resource] ->
{text, WP, LUser, LServer, Resource};
["avatar" | _] ->
{avatar, WP, LUser, LServer}
end,
show_presence(Args).
%% ---------------------
%% Web Admin
%% ---------------------
web_menu_host(Acc, _Host, Lang) ->
[{"webpresence", ?T("Web Presence")} | Acc].
web_page_host(_, _Host,
#request{path = ["webpresence"],
lang = Lang} = _Request) ->
Res = [?XCT("h1", "Web Presence"),
?XE("ul", [
?LI([?ACT("stats", "Statistics")]),
?LI([?ACT("users", "Registered Users")])])],
{stop, Res};
web_page_host(_, Host,
#request{path = ["webpresence", "users"],
lang = Lang} = _Request) ->
Users = get_users(Host),
Table = make_users_table(Users, Lang),
Res = [?XCT("h1", "Web Presence"),
?XCT("h2", "Registered Users")] ++ Table,
{stop, Res};
web_page_host(_, Host,
#request{path = ["webpresence", "stats"],
lang = Lang} = _Request) ->
Users = get_users(Host),
Res = [?XCT("h1", "Web Presence"),
css_table(),
?XCT("h2", "Statistics")]
++ make_stats_options(Users, Lang)
++ make_stats_iconthemes(Users, Lang),
{stop, Res};
web_page_host(Acc, _, _) -> Acc.
get_users(Host) ->
Select = [{{webpresence, {'$1', Host}, '$2', '$3', '$4', '$5', '$6', '$7', '$8'}, [], ['$$']}],
mnesia:dirty_select(webpresence, Select).
make_users_table(Records, Lang) ->
TList = lists:map(
fun([User, RidUrl, JIDUrl, XML, Avatar, JS, Text, Icon]) ->
?XE("tr",
[?XE("td", [?AC("../user/"++User++"/", User)]),
?XC("td", atom_to_list(JIDUrl)),
?XC("td", ridurl_out(RidUrl)),
?XC("td", Icon),
?XC("td", atom_to_list(XML)),
?XC("td", atom_to_list(JS)),
?XC("td", atom_to_list(Text)),
?XC("td", atom_to_list(Avatar))])
end, Records),
[?XE("table",
[?XE("thead",
[?XE("tr",
[?XCT("td", "User"),
?XCT("td", "Jabber ID"),
?XCT("td", "Random ID"),
?XCT("td", "Icon Theme"),
?XC("td", "XML"),
?XC("td", "JS"),
?XCT("td", "Text"),
?XCT("td", "Avatar")
])]),
?XE("tbody", TList)])].
make_stats_options(Records, Lang) ->
[RegUsers, JJ, RR, XX, AA, SS, TT, II] = lists:foldl(
fun([_User, RidUrl, JidUrl, XML, Avatar, JS, Text, Icon], [N, J, R, X, A, S, T, I]) ->
J2 = J + case JidUrl of false -> 0; true -> 1 end,
R2 = R + case RidUrl of false -> 0; _ -> 1 end,
X2 = X + case XML of false -> 0; true -> 1 end,
A2 = A + case Avatar of false -> 0; true -> 1 end,
S2 = S + case JS of false -> 0; true -> 1 end,
T2 = T + case Text of false -> 0; true -> 1 end,
I2 = I + case Icon of "---" -> 0; _ -> 1 end,
[N+1, J2, R2, X2, A2, S2, T2, I2]
end,
[0, 0, 0, 0, 0, 0, 0, 0],
Records),
URLTList = [{"Jabber ID", JJ}, {"Random ID", RR}],
OutputTList = [{"Icon Theme", II}, {"XML", XX}, {"JavaScript", SS}, {"Text", TT}, {"Avatar", AA}],
[
?C("Registered Users" ++": "++ integer_to_list(RegUsers)),
?XCT("h3", "URL Type"),
?XAE("table", [{"class", "stats"}],
[?XE("tbody", do_stat_table_with(URLTList, RegUsers))]
),
?XCT("h3", "Output Type"),
?XAE("table", [{"class", "stats"}],
[?XE("tbody", do_stat_table_with(OutputTList, RegUsers))]
)].
make_stats_iconthemes(Records, Lang) ->
Themes1 = [{T, 0} || T <- available_themes(list)],
Dict = lists:foldl(
fun([_, _, _, _, _, _, _, Icon], D) ->
dict:update_counter(Icon, 1, D)
end,
dict:from_list(Themes1),
Records),
Themes = lists:keysort(1, dict:to_list(Dict)),
[?XCT("h3", "Icon Theme"),
?XAE("table", [{"class", "stats"}],
[?XE("tbody", do_stat_table_with(Themes))]
)].
%% Do table with bars
do_stat_table_with(Values) ->
Ns = [Ni || {_, Ni} <- Values],
Total = lists:sum(Ns),
do_stat_table_with(Values, Total).
do_stat_table_with(Values, Total) ->
lists:map(
fun({L, N}) ->
Perc = case Total of
0 -> "0";
_ -> integer_to_list(trunc(100 * N / Total))
end,
do_table_element(?C(L), io_lib:format("~p", [N]), Perc)
end,
Values).
do_table_element(L, [N], Perc) ->
?XE("tr",
[?XE("td", [L]),
?XAC("td", [{"class", "alignright"}], [N]),
?XE("td",
[?XAE("div",
[{"class", "graph"}],
[?XAC("div",
[{"class", "bar"}, {"style", "width: " ++ Perc ++ "%;"}],
[]
)]
)]
),
?XAC("td", [{"class", "alignright"}], [Perc++"%"])
]).
css_table()->
?XAE("style", [{"type", "text/css"}],
[?C(".stats {
padding-left: 20px;
padding-top: 10px;
}
.graph {
position: relative;
width: 200px;
border: 1px solid #D47911;
padding: 1px;
}
.graph .bar {
display: block;
position: relative;
background: #FFE3C9;
text-align: center;
color: #333;
height: 1.5em;
line-height: 1.5em;
}
.graph .bar span { position: absolute; left: 1em; }")]).
%%%--------------------------------
%%% Update table schema and content from older versions
%%%--------------------------------
update_table() ->
case catch mnesia:table_info(presence_registered, size) of
Size when is_integer(Size) -> catch migrate_data_mod_presence(Size);
_ -> ok
end.
migrate_data_mod_presence(Size) ->
Migrate = fun(Old, S) ->
{presence_registered, {US, _Host}, XML, Icon} = Old,
New = #webpresence{us = US,
ridurl = false,
jidurl = true,
xml = list_to_atom(XML),
avatar = false,
js = false,
text = false,
icon = Icon},
mnesia:write(New),
mnesia:delete_object(Old),
S-1
end,
F = fun() -> mnesia:foldl(Migrate, Size, presence_registered) end,
{atomic, 0} = mnesia:transaction(F),
{atomic, ok} = mnesia:delete_table(presence_registered).