%%%---------------------------------------------------------------------- %%% File : mod_webpresence.erl %%% Author : Igor Goryachev , Badlop, runcom %%% Purpose : Allow user to show presence in the web %%% Created : 30 Apr 2006 by Igor Goryachev %%% 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/2, stop/1, remove_user/2, web_menu_host/3, web_page_host/3, process_disco_info/1, process_disco_items/1, process_vcard/1, process_register/1, process/2 ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, mod_opt_type/1, mod_options/1, depends/2]). %% API -export([start_link/0]). -include("xmpp.hrl"). -include("logger.hrl"). -include("translate.hrl"). -include("ejabberd_web_admin.hrl"). -include("ejabberd_http.hrl"). -record(webpresence, {us, ridurl = false, jidurl = false, xml = false, avatar = false, js = false, text = false, icon = "jsf-text"}). -record(state, {host, server_host, base_url, access}). -record(presence2, {resource, show, priority, status}). %% Copied from ejabberd_sm.erl -record(session, {sid, usr, us, priority, info}). -define(PIXMAPS_DIR, <<"pixmaps">>). -define(AUTO_ACL, webpresence_auto). %%==================================================================== %% API %%==================================================================== start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). start(Host, Opts) -> Dir = gen_mod:get_opt(pixmaps_path, Opts), catch ets:new(pixmaps_dirs, [named_table, public]), ets:insert(pixmaps_dirs, {directory, Dir}), case gen_mod:start_child(?MODULE, Host, Opts) of {ok, Ref} -> {ok, Ref}; {error, {already_started, Ref}} -> {ok, Ref}; {error, Reason} -> {error, Reason} end. stop(Host) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), gen_server:call(Proc, stop), gen_mod:stop_child(?MODULE, Host), ok. mod_opt_type(host) -> econf:host(); mod_opt_type(access) -> econf:acl(); mod_opt_type(pixmaps_path) -> econf:directory(); mod_opt_type(port) -> econf:pos_int(); mod_opt_type(path) -> econf:binary(); mod_opt_type(baseurl) -> econf:binary(). -spec mod_options(binary()) -> [{atom(), any()}]. mod_options(Host) -> [{host, <<"webpresence.", Host/binary>>}, {access, local}, {pixmaps_path, ?PIXMAPS_DIR}, {port, 5280}, {path, <<"presence">>}, {baseurl, iolist_to_binary(io_lib:format(<<"http://~s:5280/presence/">>, [Host]))}]. -spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> []. %%==================================================================== %% 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, Opts), Access = gen_mod:get_opt(access, Opts), BaseURL1 = gen_mod:get_opt(baseurl, Opts), BaseURL2 = ejabberd_regexp:greplace(BaseURL1, <<"@HOST@">>, Host), register_iq_handlers(MyHost), ejabberd_router:register_route(MyHost, Host), 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 = BaseURL2, access = Access}}. register_iq_handlers(Host) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER, ?MODULE, process_register), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, ?MODULE, process_vcard), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, ?MODULE, process_disco_info), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, ?MODULE, process_disco_items). unregister_iq_handlers(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS). %%-------------------------------------------------------------------- %% 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, Packet}, #state{host = Host, server_host = ServerHost, base_url = BaseURL, access = Access} = State) -> From = xmpp:get_from(Packet), To = xmpp:get_to(Packet), case catch do_route(Host, ServerHost, Access, From, To, Packet, BaseURL) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); _ -> ok end, {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), unregister_iq_handlers(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); _ -> Lang = xmpp:get_lang(Packet), ErrText = <<"Access denied by service policy">>, Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end. -spec process_vcard(iq()) -> iq(). process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) -> Desc = translate:translate(Lang, <<"ejabberd Web Presence module">>), xmpp:make_iq_result( IQ, #vcard_temp{fn = <<"ejabberd/mod_webpresence">>, url = ejabberd_config:get_uri(), desc = Desc}); process_vcard(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_vcard(#iq{lang = Lang} = IQ) -> Txt = <<"No module is handling this query">>, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). do_route1(_Host, _From, _To, #iq{} = IQ, _BaseURL) -> ejabberd_router:process_iq(IQ); do_route1(_Host, _From, _To, Packet, _BaseURL) -> case xmpp:get_type(Packet) of error -> ok; _ -> Err = xmpp:err_item_not_found(), ejabberd_router:route_error(Packet, Err) end. -spec process_register(iq()) -> iq(). process_register(#iq{type = get, from = From, to = To, lang = Lang, sub_els = [#register{}]} = IQ) -> Host = To#jid.lserver, xmpp:make_iq_result(IQ, iq_get_register_info(Host, From, Lang)); process_register(#iq{type = set, from = From, to = To, lang = Lang, sub_els = [El = #register{}]} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), case process_iq_register_set(ServerHost, Host, From, El, Lang) of {result, Result} -> xmpp:make_iq_result(IQ, Result); {error, Err} -> xmpp:make_error(IQ, Err) end. -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_info(#iq{type = get, lang = Lang, sub_els = [#disco_info{node = <<"">>}]} = IQ) -> Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_REGISTER, ?NS_VCARD], Identity = #identity{category = <<"component">>, type = <<"presence">>, name = translate:translate(Lang, <<"Web Presence">>)}, xmpp:make_iq_result( IQ, #disco_info{features = Features, identities = [Identity]}); process_disco_info(#iq{type = get, lang = Lang, sub_els = [#disco_info{}]} = IQ) -> xmpp:make_error(IQ, xmpp:err_item_not_found(<<"Node not found">>, Lang)); process_disco_info(#iq{lang = Lang} = IQ) -> Txt = <<"No module is handling this query">>, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_items(#iq{type = get, sub_els = [#disco_items{}]} = IQ) -> xmpp:make_iq_result(IQ, #disco_items{}); process_disco_items(#iq{lang = Lang} = IQ) -> Txt = <<"No module is handling this query">>, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -define(XFIELDS(Type, Label, Var, Vals), #xmlel{ name = <<"field">>, attrs = [ {<<"type">>, Type}, {<<"label">>, translate:translate(Lang, ?T(Label))}, {<<"var">>, Var} ], children = Vals }). -define(XFIELD(Type, Label, Var, Val), ?XFIELDS(Type, Label, Var, [ #xmlel{ name = <<"value">>, attrs = [], children = [{xmlcdata, Val}] }]) ). -define(XFIELDFIXED(Val), #xmlel{ name = <<"field">>, attrs = [{<<"type">>, <<"fixed">>}], children = [ #xmlel{ name = <<"value">>, attrs = [], children = [{xmlcdata, Val}] } ] } ). -define(ATOM2BINARY(Val), iolist_to_binary(atom_to_list(Val))). -define(BC(L), iolist_to_binary(L)). ridurl_out(false) -> <<"false">>; ridurl_out(Id) when is_binary(Id) -> <<"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. iq_get_register_info(Host, From, Lang) -> LUS = {From#jid.luser, From#jid.lserver}, {_JidUrl, _RidUrl, _XML, _Avatar, _JS, _Text, Icon, Registered} = get_pr(LUS), Nick = Icon, Title = <<(translate:translate( Lang, <<"Web Presence Registration at ">>))/binary, Host/binary>>, Inst = translate:translate(Lang, <<"Enter iconset you want to use by default">>), Fields = muc_register:encode([{roomnick, Nick}], Lang), X = #xdata{type = form, title = Title, instructions = [Inst], fields = Fields}, #register{nick = Nick, registered = Registered, instructions = translate:translate( Lang, <<"You need a client that supports x:data " "to register to Web Presence">>), xdata = X}. process_iq_register_set(_ServerHost, Host, From, #register{remove = true}, Lang) -> unregister_webpresence(From, Host, Lang); process_iq_register_set(_ServerHost, _Host, _From, #register{xdata = #xdata{type = cancel}}, _Lang) -> {result, undefined}; process_iq_register_set(ServerHost, Host, From, #register{nick = Nick, xdata = XData}, Lang) -> case XData of #xdata{type = submit, fields = Fs} -> try Options = muc_register:decode(Fs), N = proplists:get_value(roomnick, Options), iq_set_register_info(ServerHost, Host, From, N, Lang) catch _:{muc_register, Why} -> ErrText = muc_register:format_error(Why), {error, xmpp:err_bad_request(ErrText, Lang)} end; #xdata{} -> Txt = <<"Incorrect data form">>, {error, xmpp:err_bad_request(Txt, Lang)}; _ when is_binary(Nick), Nick /= <<"">> -> iq_set_register_info(ServerHost, Host, From, Nick, Lang); _ -> ErrText = <<"You must fill in field \"Nickname\" in the form">>, {error, xmpp:err_not_acceptable(ErrText, Lang)} end. iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> case iq_set_register_info2(ServerHost, Host, From, Nick, Lang) of {atomic, ok} -> {result, undefined}; _ -> Txt = <<"Database failure">>, {error, xmpp:err_internal_server_error(Txt, Lang)} end. iq_set_register_info2(ServerHost, Host, From, Icon, Lang) -> %% RidUrl2 = get_rid_final_value(RidUrl, LUS), LUS = {From#jid.luser, From#jid.lserver}, WP = #webpresence{us = LUS, jidurl = true, ridurl = false, xml = true, avatar = true, js = true, text = true, icon = Icon}, F = fun() -> mnesia:write(WP) end, case mnesia:transaction(F) of {atomic, ok} -> BaseURL = get_baseurl(ServerHost), send_message_registered(WP, From, Host, BaseURL, Lang), {atomic, ok}; _ -> {error, xmpp:err_internal_server_error()} end. send_message_registered(WP, To, Host, BaseURL, Lang) -> {User, Server} = WP#webpresence.us, JIDS = jid:encode({User, Server, <<"">>}), 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 -> ?BC([ <<" text\n" " text/res/<">>, translate:translate(Lang, ?T("Resource")), <<">\n">> ]) end, Oimage = case WP#webpresence.icon of <<"---">> -> ""; I when is_binary(I) -> ?BC([ <<" image\n" " image/example.php\n" " image/mypresence.png\n" " image/res/<">>, translate:translate(Lang, ?T("Resource")), <<">\n" " image/theme/<">>, translate:translate(Lang, ?T("Icon Theme")), <<">\n" " image/theme/<">>, translate:translate(Lang, ?T("Icon Theme")), <<">/res/<">>, translate:translate(Lang, ?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 = ?BC([<<"jid/">>, User, <<"/">>, Server]), {?BC([<<" ">>, JIDT, <<"\n">>]), ?BC([<<" ">>, BaseURL, JIDT, <<"/">>, Allowed_type, <<"/\n">>])} end, {USERID_rid, Example_rid, Text_rid} = case WP#webpresence.ridurl of false -> {<<"">>, <<"">>, <<"">>}; RID when is_binary(RID) -> RIDT = ?BC([<<"rid/">>, RID]), {?BC([<<" ">>, RIDT, <<"\n">>]), ?BC([<<" ">>, BaseURL, RIDT, <<"/">>, Allowed_type, <<"/\n">>]), ?BC([translate:translate(Lang, ?T("If you forget your RandomID, register again to receive this message.")), <<"\n">>, translate:translate(Lang, ?T("To get a new RandomID, disable the option and register again.")), <<"\n">>]) } end, Subject = ?BC([translate:translate(Lang, ?T("Web Presence")), <<": ">>, translate:translate(Lang, ?T("registered"))]), Body = ?BC([translate:translate(Lang, ?T("You have registered:")), <<" ">>, JIDS, <<"\n\n">>, translate:translate(Lang, ?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">>, translate:translate(Lang, ?T("Example:")), <<"\n">>, Example_jid, Example_rid, <<"\n">>, Text_rid]), send_headline(Host, To, Subject, Body). send_message_unregistered(To, Host, Lang) -> Subject = ?BC([translate:translate(Lang, ?T("Web Presence")), <<": ">>, translate:translate(Lang, ?T("unregistered"))]), Body = ?BC([translate:translate(Lang, ?T("You have unregistered.")), <<"\n\n">>]), send_headline(Host, To, Subject, Body). send_headline(Host, To, Subject, Body) -> Packet = #message{type = headline, from = jid:make(Host), to = To, body = xmpp:mk_text(Body), subject = xmpp:mk_text(Subject)}, ejabberd_router:route(Packet). unregister_webpresence(From, Host, Lang) -> remove_user(From#jid.luser, From#jid.lserver), send_message_unregistered(From, Host, Lang), {result, undefined}. remove_user(User, Server) -> mnesia:dirty_delete(webpresence, {User, Server}). 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 = jid:make(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}}) -> P = ejabberd_c2s:get_presence(Pid), #presence2{resource = (P#presence.from)#jid.resource, show = misc:atom_to_binary(humanize_show(P#presence.show)), priority = P#presence.priority, status = xmpp:get_text(P#presence.status)}. humanize_show(undefined) -> available; humanize_show(Show) -> Show. 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#presence2.priority == B#presence2.priority -> WA = get_status_weight(A#presence2.show), WB = get_status_weight(B#presence2.show), WA < WB; true -> A#presence2.priority > B#presence2.priority end end, get_presences({bare, LUser, LServer})); get_presences({xml, LUser, LServer, Show_us}) -> #xmlel{ name = <<"presence">>, attrs = case Show_us of true -> [{<<"user">>, LUser}, {<<"server">>, LServer}]; false -> [] end, children = lists:map( fun(Presence) -> #xmlel{ name = <<"resource">>, attrs = [ {<<"name">>, Presence#presence2.resource}, {<<"show">>, Presence#presence2.show}, {<<"priority">>, intund2string(Presence#presence2.priority)} ], children = [{xmlcdata, Presence#presence2.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#presence2.status end; get_presences({status, LUser, LServer}) -> case get_presences({sorted, LUser, LServer}) of [Highest | _Rest] -> Highest#presence2.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#presence2.show end; get_presences({show, LUser, LServer}) -> case get_presences({sorted, LUser, LServer}) of [Highest | _Rest] -> Highest#presence2.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 -> ?BC([<<"var jabber_user='">>, User, <<"';\n">>, <<"var jabber_server='">>, Server, <<"';\n">>]); false -> <<"">> end, FunImage = fun(I, S) -> case I of <<"---">> -> <<"">>; Icon -> ?BC([<<" image:'">>, BaseURL, <<"image/">>, Icon, <<"/">>, S, <<"'\n">>]) end end, R_string_list = case Prs of [] -> Show = <<"unavailable">>, [?BC([<<"{show:'">>, Show, <<"',\n">>, <<" long_show:'">>, long_show(Show, Lang), <<"',\n">>, <<" status:'',\n">>, % TODO FunImage(WP#webpresence.icon, Show), <<"}">>])]; _ -> lists:map( fun(Pr) -> Show = Pr#presence2.show, ?BC([<<"{name:'">>, Pr#presence2.resource, <<"',\n">>, <<" priority:">>, intund2string(Pr#presence2.priority), <<",\n">>, <<" show:'">>, Show, <<"',\n">>, <<" long_show:'">>, long_show(Show, Lang), <<"',\n">>, <<" status:'">>, escape(Pr#presence2.status), <<"',\n">>, FunImage(WP#webpresence.icon, Show), <<"}">>]) end, Prs) end, R_string = lists:foldl( fun(RS, Res) -> case Res of <<"">> -> RS; _ -> ?BC([Res, <<",\n">>, RS]) end end, <<"">>, R_string_list), CB_string = case lists:keysearch(<<"cb">>, 1, Q) of {value, {_, CB}} -> ?BC([<<" ">>, CB, <<"();">>]); _ -> <<"">> end, ?BC([US_string, <<"var jabber_resources=[\n">>, R_string, <<"];">>, CB_string]). long_show(<<"available">>, Lang) -> translate:translate(Lang, ?T("available")); long_show(<<"chat">>, Lang) -> translate:translate(Lang, ?T("free for chat")); long_show(<<"away">>, Lang) -> translate:translate(Lang, ?T("away")); long_show(<<"xa">>, Lang) -> translate:translate(Lang, ?T("extended away")); long_show(<<"dnd">>, Lang) -> translate:translate(Lang, ?T("do not disturb")); long_show(_, Lang) -> translate:translate(Lang, ?T("unavailable")). intund2string(undefined) -> intund2string(0); intund2string(Int) when is_integer(Int) -> list_to_binary(integer_to_list(Int)). escape(S1) -> S2 = re:replace(S1, "\'", "\\'", [global, {return, list}]), re:replace(S2, "\n", "\\n", [global, {return, list}]). get_baseurl(Host) -> BaseURL1 = gen_mod:get_module_opt(Host, ?MODULE, baseurl), ejabberd_regexp:greplace(BaseURL1, <<"@HOST@">>, Host). -define(XML_HEADER, <<"">>). 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) -> #xmlel{ name = <<"option">>, attrs = [{<<"label">>, iolist_to_binary(Theme)}], children = [ #xmlel{ name = <<"value">>, attrs = [], children = [{xmlcdata, iolist_to_binary(Theme)}] } ] } end, available_themes(list)). show_presence({image_no_check, Theme, Pr}) -> Dir = get_pixmaps_directory(), Image = ?BC([Pr, <<".{gif,png,jpg}">>]), [First | _Rest] = filelib:wildcard(binary_to_list(filename:join([Dir, Theme, Image]))), Mime = string:substr(First, string:len(First) - 2, 3), {ok, Content} = file:read_file(First), {200, [{<<"Content-Type">>, ?BC([<<"image/">>, ?BC(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 = fxml:element_to_binary(get_presences({xml, LUser, LServer, Show_us})), {200, [{"Content-Type", "text/xml; charset=utf-8"}], ?BC([?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}] = ets:lookup(ejabberd_sm, {LServer, ?NS_VCARD}), JID = jid:make(LUser, LServer, <<"">>), IQ = #iq{type = get, from = JID, to = JID}, IQr = Module:Function(IQ), [VCard] = IQr#iq.sub_els, VCard2 = xmpp:decode(VCard), Mime = (VCard2#vcard_temp.photo)#vcard_photo.type, Photo = (VCard2#vcard_temp.photo)#vcard_photo.binval, {200, [{"Content-Type", Mime}], Photo}; show_presence({image_example, Theme, Show}) -> Dir = get_pixmaps_directory(), Image = ?BC([Show, <<".{gif,png,jpg}">>]), [First | _Rest] = filelib:wildcard(binary_to_list(filename:join([Dir, Theme, Image]))), Mime = string:substr(First, string:len(First) - 2, 3), {ok, Content} = file:read_file(First), {200, [{<<"Content-Type">>, ?BC([<<"image/">>, ?BC(Mime)])}], binary_to_list(Content)}. %% --------------------- %% Web Publish %% --------------------- make_xhtml(Els) -> make_xhtml([], Els). make_xhtml(Title, Els) -> #xmlel{ name = <<"html">>, attrs = [ {<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, {<<"xml:lang">>, <<"en">>}, {<<"lang">>, <<"en">>} ], children = [ #xmlel{ name = <<"head">>, attrs = [], children = [ #xmlel{ name = <<"meta">>, attrs = [ {<<"http-equiv">>, <<"Content-Type">>}, {<<"content">>, <<"text/html; charset=utf-8">>} ], children = [] } ] ++ Title }, #xmlel{ name = <<"body">>, attrs = [], children = Els } ] }. themes_to_xhtml(Themes) -> ShowL = ["available", "chat", "dnd", "away", "xa", "unavailable"], THeadL = [""] ++ ShowL, [?XAE(<<"table">>, [], [?XE( <<"tr">>, [?XC(<<"th">>, ?BC(T)) || T <- THeadL])] ++ [?XE(<<"tr">>, [?XC(<<"td">>, ?BC(Theme)) | [?XE(<<"td">>, [?XA(<<"img">>, [{<<"src">>, ?BC([<<"image/">>, ?BC(Theme), <<"/">>, ?BC(T)]) }])]) || T <- ShowL] ] ) || Theme <- Themes] ) ]. parse_lang(Lang) -> iolist_to_binary(hd(string:tokens(binary_to_list(Lang),"-"))). process(LocalPath, Request) -> case catch process2(LocalPath, Request) of {'EXIT', Reason} -> ?DEBUG("~p", [Request]), ?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">>, translate:translate(Lang, ?T("Web Presence")))], Desc = [?XC(<<"p">>, ?BC([ translate:translate(Lang, ?T("To publish your presence using this system you need a Jabber account in this Jabber server.")), <<" ">>, translate:translate(Lang, ?T("Login with a Jabber client, open the Service Discovery and register in Web Presence.")), translate:translate(Lang, ?T("You will receive a message with further instructions."))]))], Link_themes = [?AC(<<"themes">>, translate:translate(Lang, ?T("Icon Theme")))], Body = [?XC(<<"h1">>, translate:translate(Lang, ?T("Web Presence")))] ++ Desc ++ Link_themes, make_xhtml(Title, Body); process2([<<"themes">>], #request{lang = Lang1}) -> Lang = parse_lang(Lang1), Title = [?XC(<<"title">>, ?BC([translate:translate(Lang, ?T("Web Presence")), <<" - ">>, translate:translate(Lang, ?T("Icon Theme"))]))], Themes = available_themes(list), Icon_themes = themes_to_xhtml(Themes), Body = [?XC(<<"h1">>, translate:translate(Lang, ?T("Icon Theme")))] ++ Icon_themes, make_xhtml(Title, Body); process2([<<"image">>, Theme, Show], #request{} = _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 = jid:nameprep(Server), true = lists:member(Server, ejabberd_config:get_option(hosts)), LUser = jid:nodeprep(User), WP = get_wp(LUser, LServer), case TypeURL of jid -> true = WP#webpresence.jidurl; rid -> true = is_binary(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">>, translate:translate(Lang, ?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(?BC([<<"../../user/">>, User, <<"/">>]), User)]), ?XC(<<"td">>, iolist_to_binary(atom_to_list(JIDUrl))), ?XC(<<"td">>, ridurl_out(RidUrl)), ?XC(<<"td">>, Icon), ?XC(<<"td">>, iolist_to_binary(atom_to_list(XML))), ?XC(<<"td">>, iolist_to_binary(atom_to_list(JS))), ?XC(<<"td">>, iolist_to_binary(atom_to_list(Text))), ?XC(<<"td">>, iolist_to_binary(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(?BC([<<"Registered Users">>, <<": ">>, iolist_to_binary(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">>; _ -> iolist_to_binary(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">>, ?BC([<<"width: ">>, Perc, <<"%;">>])}], [] )] )] ), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], [?BC([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).