mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-06 16:42:56 +01:00
* src/xml_stream.erl: Added catching of gen_fsm:send_event errors
* src/ejabberd_s2s_out.erl: Better support for multiple SRV records (thanks to Sergei Golovan) * src/mod_muc/mod_muc_log.erl: Support for chatroom logging (thanks to Badlop) * src/mod_muc/mod_muc_room.erl: Likewise * src/mod_muc/Makefile.in: Likewise * src/mod_muc/Makefile.win32: Likewise * src/gen_iq_handler.erl: Added support for {queues, N} IQ handler type SVN Revision: 517
This commit is contained in:
parent
4f2a355408
commit
d5137b0563
18
ChangeLog
18
ChangeLog
@ -1,3 +1,21 @@
|
||||
2006-03-14 Alexey Shchepin <alexey@sevcom.net>
|
||||
|
||||
* src/xml_stream.erl: Added catching of gen_fsm:send_event errors
|
||||
|
||||
* src/ejabberd_s2s_out.erl: Better support for multiple SRV
|
||||
records (thanks to Sergei Golovan)
|
||||
|
||||
* src/mod_muc/mod_muc_log.erl: Support for chatroom logging
|
||||
(thanks to Badlop)
|
||||
* src/mod_muc/mod_muc_room.erl: Likewise
|
||||
* src/mod_muc/Makefile.in: Likewise
|
||||
* src/mod_muc/Makefile.win32: Likewise
|
||||
|
||||
2006-03-11 Alexey Shchepin <alexey@sevcom.net>
|
||||
|
||||
* src/gen_iq_handler.erl: Added support for {queues, N} IQ handler
|
||||
type
|
||||
|
||||
2006-03-06 Alexey Shchepin <alexey@sevcom.net>
|
||||
|
||||
* src/mod_muc/mod_muc_room.erl: Bugfix
|
||||
|
3
TODO
3
TODO
@ -1,7 +1,5 @@
|
||||
Win32 build: Make it possible to compile with +debug_info flag.
|
||||
|
||||
mod_muc logging
|
||||
|
||||
admin interface
|
||||
users management
|
||||
statistics about each user
|
||||
@ -10,7 +8,6 @@ admin interface
|
||||
S2S:
|
||||
check "id" attributes in db:verify packets
|
||||
|
||||
more correctly work with SRV DNS records (priority, weight, etc...)
|
||||
make roster set to work in one transaction
|
||||
add traffic shapers to c2s connection before authentification
|
||||
more traffic shapers
|
||||
|
@ -31,7 +31,8 @@
|
||||
handle_sync_event/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
code_change/4]).
|
||||
code_change/4,
|
||||
test_get_addr_port/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -141,7 +142,44 @@ init([From, Server, Type]) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
open_socket(init, StateData) ->
|
||||
{Addr, Port} = get_addr_port(StateData#state.server),
|
||||
AddrList = get_addr_port(StateData#state.server),
|
||||
case lists:foldl(fun({Addr, Port}, Acc) ->
|
||||
case Acc of
|
||||
{ok, Socket} ->
|
||||
{ok, Socket};
|
||||
_ ->
|
||||
open_socket1(Addr, Port)
|
||||
end
|
||||
end, {error, badarg}, AddrList) of
|
||||
{ok, Socket} ->
|
||||
ReceiverPid = ejabberd_receiver:start(Socket, gen_tcp, none),
|
||||
ok = gen_tcp:controlling_process(Socket, ReceiverPid),
|
||||
ejabberd_receiver:become_controller(ReceiverPid),
|
||||
Version = if
|
||||
StateData#state.use_v10 ->
|
||||
" version='1.0'";
|
||||
true ->
|
||||
""
|
||||
end,
|
||||
NewStateData = StateData#state{socket = Socket,
|
||||
sockmod = gen_tcp,
|
||||
tls_enabled = false,
|
||||
receiver = ReceiverPid,
|
||||
streamid = new_id()},
|
||||
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
Version])),
|
||||
{next_state, wait_for_stream, NewStateData};
|
||||
{error, _Reason} ->
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
open_socket(_, StateData) ->
|
||||
{next_state, open_socket, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
open_socket1(Addr, Port) ->
|
||||
Res = case idna:domain_utf8_to_ascii(Addr) of
|
||||
false -> {error, badarg};
|
||||
ASCIIAddr ->
|
||||
@ -164,37 +202,16 @@ open_socket(init, StateData) ->
|
||||
end,
|
||||
case Res of
|
||||
{ok, Socket} ->
|
||||
ReceiverPid = ejabberd_receiver:start(Socket, gen_tcp, none),
|
||||
ok = gen_tcp:controlling_process(Socket, ReceiverPid),
|
||||
ejabberd_receiver:become_controller(ReceiverPid),
|
||||
Version = if
|
||||
StateData#state.use_v10 ->
|
||||
" version='1.0'";
|
||||
true ->
|
||||
""
|
||||
end,
|
||||
NewStateData = StateData#state{socket = Socket,
|
||||
sockmod = gen_tcp,
|
||||
tls_enabled = false,
|
||||
receiver = ReceiverPid,
|
||||
streamid = new_id()},
|
||||
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
Version])),
|
||||
{next_state, wait_for_stream, NewStateData};
|
||||
{ok, Socket};
|
||||
{error, Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData};
|
||||
{error, Reason};
|
||||
{'EXIT', Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
open_socket(_, StateData) ->
|
||||
{next_state, open_socket, StateData}.
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
|
||||
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||
@ -789,18 +806,50 @@ get_addr_port(Server) ->
|
||||
case Res of
|
||||
{error, Reason} ->
|
||||
?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
|
||||
{Server, ejabberd_config:get_local_option(outgoing_s2s_port)};
|
||||
[{Server, ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
{ok, HEnt} ->
|
||||
?DEBUG("srv lookup of '~s': ~p~n",
|
||||
[Server, HEnt#hostent.h_addr_list]),
|
||||
case HEnt#hostent.h_addr_list of
|
||||
[] ->
|
||||
{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)};
|
||||
[{_, _, Port, Host} | _] ->
|
||||
{Host, Port}
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
AddrList ->
|
||||
% Probabilities are not exactly proportional to weights
|
||||
% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
{'EXIT', _Reasn} ->
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
SortedList ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
test_get_addr_port(Server) ->
|
||||
lists:foldl(
|
||||
fun(_, Acc) ->
|
||||
[HostPort | _] = get_addr_port(Server),
|
||||
case lists:keysearch(HostPort, 1, Acc) of
|
||||
false ->
|
||||
[{HostPort, 1} | Acc];
|
||||
{value, {_, Num}} ->
|
||||
lists:keyreplace(HostPort, 1, Acc, {HostPort, Num + 1})
|
||||
end
|
||||
end, [], lists:seq(1, 100000)).
|
||||
|
||||
|
@ -49,6 +49,17 @@ add_iq_handler(Component, Host, NS, Module, Function, Type) ->
|
||||
[Host, Module, Function]),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
{one_queue, Pid});
|
||||
{queues, N} ->
|
||||
Pids =
|
||||
lists:map(
|
||||
fun(_) ->
|
||||
{ok, Pid} = supervisor:start_child(
|
||||
ejabberd_iq_sup,
|
||||
[Host, Module, Function]),
|
||||
Pid
|
||||
end, lists:seq(1, N)),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
{queues, Pids});
|
||||
parallel ->
|
||||
Component:register_iq_handler(Host, NS, Module, Function, parallel)
|
||||
end.
|
||||
@ -60,6 +71,10 @@ stop_iq_handler(_Module, _Function, Opts) ->
|
||||
case Opts of
|
||||
{one_queue, Pid} ->
|
||||
gen_server:call(Pid, stop);
|
||||
{queues, Pids} ->
|
||||
lists:foreach(fun(Pid) ->
|
||||
catch gen_server:call(Pid, stop)
|
||||
end, Pids);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
@ -70,6 +85,9 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
|
||||
process_iq(Host, Module, Function, From, To, IQ);
|
||||
{one_queue, Pid} ->
|
||||
Pid ! {process_iq, From, To, IQ};
|
||||
{queues, Pids} ->
|
||||
Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids),
|
||||
Pid ! {process_iq, From, To, IQ};
|
||||
parallel ->
|
||||
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
|
||||
_ ->
|
||||
|
@ -12,6 +12,7 @@ OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
OBJS = \
|
||||
$(OUTDIR)/mod_muc.beam \
|
||||
$(OUTDIR)/mod_muc_log.beam \
|
||||
$(OUTDIR)/mod_muc_room.beam
|
||||
|
||||
all: $(OBJS)
|
||||
|
@ -6,6 +6,7 @@ EFLAGS = -I .. -pz ..
|
||||
|
||||
OBJS = \
|
||||
$(OUTDIR)\mod_muc.beam \
|
||||
$(OUTDIR)\mod_muc_log.beam \
|
||||
$(OUTDIR)\mod_muc_room.beam
|
||||
|
||||
ALL : $(OBJS)
|
||||
@ -16,5 +17,8 @@ CLEAN :
|
||||
$(OUTDIR)\mod_muc.beam : mod_muc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc.erl
|
||||
|
||||
$(OUTDIR)\mod_muc_log.beam : mod_muc_log.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_log.erl
|
||||
|
||||
$(OUTDIR)\mod_muc_room.beam : mod_muc_room.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_room.erl
|
||||
|
580
src/mod_muc/mod_muc_log.erl
Normal file
580
src/mod_muc/mod_muc_log.erl
Normal file
@ -0,0 +1,580 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_muc_log.erl
|
||||
%%% Author : Badlop
|
||||
%%% Purpose : MUC room logging
|
||||
%%% Created : 12 Mar 2006 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_muc_log).
|
||||
-author('badlop').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2,
|
||||
start/2,
|
||||
stop/1,
|
||||
check_access_log/2,
|
||||
add_to_log/5]).
|
||||
|
||||
%% 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").
|
||||
|
||||
-define(T(Text), translate:translate(Lang, Text)).
|
||||
-define(PROCNAME, ejabberd_mod_muc_log).
|
||||
-record(room, {jid, title, subject, subject_author, config}).
|
||||
|
||||
|
||||
-record(state, {host,
|
||||
out_dir,
|
||||
dir_type,
|
||||
css_file,
|
||||
access,
|
||||
lang,
|
||||
timezone,
|
||||
top_link}).
|
||||
|
||||
%%====================================================================
|
||||
%% 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]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
add_to_log(Host, Type, Data, Room, Opts) ->
|
||||
gen_server:cast(get_proc_name(Host),
|
||||
{add_to_log, Type, Data, Room, Opts}).
|
||||
|
||||
check_access_log(Host, From) ->
|
||||
case catch gen_server:call(get_proc_name(Host),
|
||||
{check_access_log, Host, From}) of
|
||||
{'EXIT', _Error} ->
|
||||
false;
|
||||
Res ->
|
||||
Res
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"),
|
||||
DirType = gen_mod:get_opt(dirtype, Opts, subdirs),
|
||||
CSSFile = gen_mod:get_opt(cssfile, Opts, false),
|
||||
AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin),
|
||||
Timezone = gen_mod:get_opt(timezone, Opts, local),
|
||||
Top_link = gen_mod:get_opt(top_link, Opts, {"/", "Home"}),
|
||||
Lang = case ejabberd_config:get_local_option({language, Host}) of
|
||||
undefined ->
|
||||
"";
|
||||
L ->
|
||||
L
|
||||
end,
|
||||
{ok, #state{host = Host,
|
||||
out_dir = OutDir,
|
||||
dir_type = DirType,
|
||||
css_file = CSSFile,
|
||||
access = AccessLog,
|
||||
lang = Lang,
|
||||
timezone = Timezone,
|
||||
top_link = Top_link}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% 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({check_access_log, ServerHost, FromJID}, _From, State) ->
|
||||
Reply = acl:match_rule(ServerHost, State#state.access, FromJID),
|
||||
{reply, Reply, State};
|
||||
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({add_to_log, Type, Data, Room, Opts}, State) ->
|
||||
case catch add_to_log2(Type, Data, Room, Opts, State) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
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(_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) ->
|
||||
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
|
||||
%%--------------------------------------------------------------------
|
||||
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
|
||||
case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
|
||||
{false, false} ->
|
||||
ok;
|
||||
{false, SubEl} ->
|
||||
Message = {body, htmlize(xml:get_tag_cdata(SubEl))},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State);
|
||||
{SubEl, _} ->
|
||||
Message = {subject, htmlize(xml:get_tag_cdata(SubEl))},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State)
|
||||
end;
|
||||
|
||||
add_to_log2(roomconfig_change, _, Room, Opts, State) ->
|
||||
add_message_to_log("", roomconfig_change, Room, Opts, State);
|
||||
|
||||
add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) ->
|
||||
add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State);
|
||||
|
||||
add_to_log2(join, Nick, Room, Opts, State) ->
|
||||
add_message_to_log(Nick, join, Room, Opts, State);
|
||||
|
||||
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) ->
|
||||
case Reason of
|
||||
"" -> add_message_to_log(Nick, leave, Room, Opts, State);
|
||||
_ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State)
|
||||
end;
|
||||
|
||||
add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) ->
|
||||
add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State).
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Core
|
||||
|
||||
build_filename_string(TimeStamp, OutDir, RoomJID, DirType) ->
|
||||
{{Year, Month, Day}, _Time} = TimeStamp,
|
||||
|
||||
% Directory and file names
|
||||
{Dir, Filename, Rel} =
|
||||
case DirType of
|
||||
subdirs ->
|
||||
SYear = lists:flatten(io_lib:format("~4..0w", [Year])),
|
||||
SMonth = lists:flatten(io_lib:format("~2..0w", [Month])),
|
||||
SDay = lists:flatten(io_lib:format("~2..0w", [Day])),
|
||||
{filename:join(SYear, SMonth), SDay, "../.."};
|
||||
plain ->
|
||||
Date = lists:flatten(
|
||||
io_lib:format("~4..0w-~2..0w-~2..0w",
|
||||
[Year, Month, Day])),
|
||||
{"", Date, "."}
|
||||
end,
|
||||
Fd = filename:join([OutDir, RoomJID, Dir]),
|
||||
Fn = filename:join([Fd, Filename ++ ".html"]),
|
||||
Fnrel = filename:join([Rel, Dir, Filename ++ ".html"]),
|
||||
{Fd, Fn, Fnrel}.
|
||||
|
||||
% calculate day before
|
||||
get_timestamp_daydiff(TimeStamp, Daydiff) ->
|
||||
{Date1, HMS} = TimeStamp,
|
||||
Date2 = calendar:gregorian_days_to_date(
|
||||
calendar:date_to_gregorian_days(Date1) + Daydiff),
|
||||
{Date2, HMS}.
|
||||
|
||||
% Try to close the previous day log, if it exists
|
||||
close_previous_log(Fn) ->
|
||||
case file:read_file_info(Fn) of
|
||||
{ok, _} ->
|
||||
{ok, F} = file:open(Fn, [append]),
|
||||
fw(F, "<div class=\"legend\">ejabberd/mod_muc log<span class=\"w3c\"><a href=\"http://validator.w3.org/check?uri=referer\"><img src=\"http://www.w3.org/Icons/valid-xhtml10\" alt=\"Valid XHTML 1.0 Transitional\" height=\"31\" width=\"88\" /></a> <a href=\"http://jigsaw.w3.org/css-validator/\"><img style=\"border:0;width:88px;height:31px\" src=\"http://jigsaw.w3.org/css-validator/images/vcss\" alt=\"Valid CSS!\" /></a></span></div></body></html>"),
|
||||
file:close(F);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
add_message_to_log(Nick, Message, RoomJID, Opts, State) ->
|
||||
#state{out_dir = OutDir,
|
||||
dir_type = DirType,
|
||||
css_file = CSSFile,
|
||||
lang = Lang,
|
||||
timezone = Timezone,
|
||||
top_link = TopLink} = State,
|
||||
Room = get_room_info(RoomJID, Opts),
|
||||
|
||||
TimeStamp = case Timezone of
|
||||
local -> calendar:now_to_local_time(now());
|
||||
universal -> calendar:now_to_universal_time(now())
|
||||
end,
|
||||
{Fd, Fn, _Dir} = build_filename_string(TimeStamp, OutDir, Room#room.jid, DirType),
|
||||
{Date, Time} = TimeStamp,
|
||||
|
||||
% Open file, create if it does not exist, create parent dirs if needed
|
||||
case file:read_file_info(Fn) of
|
||||
{ok, _} ->
|
||||
{ok, F} = file:open(Fn, [append]);
|
||||
{error, enoent} ->
|
||||
make_dir_rec(Fd),
|
||||
{ok, F} = file:open(Fn, [append]),
|
||||
Datestring = get_dateweek(Date, Lang),
|
||||
|
||||
TimeStampYesterday = get_timestamp_daydiff(TimeStamp, -1),
|
||||
{_FdYesterday, FnYesterday, DatePrev} =
|
||||
build_filename_string(
|
||||
TimeStampYesterday, OutDir, Room#room.jid, DirType),
|
||||
|
||||
TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
|
||||
{_FdTomorrow, _FnTomorrow, DateNext} =
|
||||
build_filename_string(
|
||||
TimeStampTomorrow, OutDir, Room#room.jid, DirType),
|
||||
|
||||
HourOffset = calc_hour_offset(TimeStamp),
|
||||
put_header(F, Room, Datestring, CSSFile, Lang,
|
||||
HourOffset, DatePrev, DateNext, TopLink),
|
||||
|
||||
close_previous_log(FnYesterday)
|
||||
end,
|
||||
|
||||
% Build message
|
||||
Text = case Message of
|
||||
roomconfig_change ->
|
||||
RoomConfig = roomconfig_to_string(Room#room.config, Lang),
|
||||
put_room_config(F, RoomConfig, Lang),
|
||||
io_lib:format("<font class=\"mrcm\">~s</font><br/>",
|
||||
[?T("Chatroom configuration modified")]);
|
||||
join ->
|
||||
io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
|
||||
[Nick, ?T("joins the room")]);
|
||||
leave ->
|
||||
io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
|
||||
[Nick, ?T("leaves the room")]);
|
||||
{leave, Reason} ->
|
||||
io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T("leaves the room"), Reason]);
|
||||
{kickban, "307", ""} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
|
||||
[Nick, ?T("has been kicked")]);
|
||||
{kickban, "307", Reason} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T("has been kicked"), Reason]);
|
||||
{kickban, "301", ""} ->
|
||||
io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
|
||||
[Nick, ?T("has been banned")]);
|
||||
{kickban, "301", Reason} ->
|
||||
io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T("has been banned"), Reason]);
|
||||
{nickchange, OldNick} ->
|
||||
io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
|
||||
[OldNick, ?T("is now known as"), Nick]);
|
||||
{subject, T} ->
|
||||
io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
|
||||
[Nick, ?T(" has set the subject to: "), T]);
|
||||
{body, T} ->
|
||||
case regexp:first_match(T, "^/me\s") of
|
||||
{match, _, _} ->
|
||||
io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
|
||||
[Nick, string:substr(T, 5)]);
|
||||
nomatch ->
|
||||
io_lib:format("<font class=\"mn\"><~s></font> ~s<br/>",
|
||||
[Nick, T])
|
||||
end
|
||||
end,
|
||||
{Hour, Minute, Second} = Time,
|
||||
STime = lists:flatten(
|
||||
io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Minute, Second])),
|
||||
|
||||
% Write message
|
||||
file:write(F, io_lib:format("<a name=\"~s\" href=\"#~s\" class=\"ts\">[~s]</a> ~s~n",
|
||||
[STime, STime, STime, Text])),
|
||||
|
||||
% Close file
|
||||
file:close(F),
|
||||
ok.
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Utilities
|
||||
|
||||
get_dateweek(Date, Lang) ->
|
||||
Weekday = case calendar:day_of_the_week(Date) of
|
||||
1 -> ?T("Monday");
|
||||
2 -> ?T("Tuesday");
|
||||
3 -> ?T("Wednesday");
|
||||
4 -> ?T("Thursday");
|
||||
5 -> ?T("Friday");
|
||||
6 -> ?T("Saturday");
|
||||
7 -> ?T("Sunday")
|
||||
end,
|
||||
{Y, M, D} = Date,
|
||||
Month = case M of
|
||||
1 -> ?T("January");
|
||||
2 -> ?T("February");
|
||||
3 -> ?T("March");
|
||||
4 -> ?T("April");
|
||||
5 -> ?T("May");
|
||||
6 -> ?T("June");
|
||||
7 -> ?T("July");
|
||||
8 -> ?T("August");
|
||||
9 -> ?T("September");
|
||||
10 -> ?T("October");
|
||||
11 -> ?T("November");
|
||||
12 -> ?T("December")
|
||||
end,
|
||||
case Lang of
|
||||
"en" -> io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
|
||||
"es" -> io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]);
|
||||
_ -> io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
|
||||
end.
|
||||
|
||||
make_dir_rec(Dir) ->
|
||||
case file:read_file_info(Dir) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
{error, enoent} ->
|
||||
DirS = filename:split(Dir),
|
||||
DirR = lists:sublist(DirS, length(DirS)-1),
|
||||
make_dir_rec(filename:join(DirR)),
|
||||
file:make_dir(Dir)
|
||||
end.
|
||||
|
||||
fw(F, S, O) -> io:format(F, S ++ "~n", O).
|
||||
fw(F, S) -> fw(F, S, []).
|
||||
|
||||
put_header(F, Room, Date, CSSFile, Lang, Hour_offset, Date_prev, Date_next, Top_link) ->
|
||||
fw(F, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"),
|
||||
fw(F, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"~s\" lang=\"~s\">", [Lang, Lang]),
|
||||
fw(F, "<head>"),
|
||||
fw(F, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"),
|
||||
fw(F, "<title>~s - ~s</title>", [Room#room.title, Date]),
|
||||
put_header_css(F, CSSFile),
|
||||
put_header_script(F),
|
||||
fw(F, "</head>"),
|
||||
fw(F, "<body>"),
|
||||
{Top_url, Top_text} = Top_link,
|
||||
fw(F, "<div style=\"text-align: right;\"><a style=\"color: #AAAAAA; font-family: monospace; text-decoration: none; font-weight: bold;\" href=\"~s\">~s</a></div>", [Top_url, Top_text]),
|
||||
fw(F, "<div class=\"roomtitle\"><a class=\"roomtitle\" href=\"xmpp:~s?join\">~s</a></div>", [Room#room.jid, Room#room.title]),
|
||||
fw(F, "<a class=\"roomjid\" href=\"xmpp:~s?join\">~s</a>", [Room#room.jid, Room#room.jid]),
|
||||
fw(F, "<div class=\"logdate\">~s<span class=\"w3c\"><a class=\"nav\" href=\"~s\"><</a> <a class=\"nav\" href=\".\/\">^</a> <a class=\"nav\" href=\"~s\">></a></span></div>", [Date, Date_prev, Date_next]),
|
||||
case {Room#room.subject_author, Room#room.subject} of
|
||||
{"", ""} -> ok;
|
||||
{SuA, Su} -> fw(F, "<div class=\"roomsubject\">~s~s~s</div>", [SuA, ?T(" has set the subject to: "), htmlize(Su)])
|
||||
end,
|
||||
RoomConfig = roomconfig_to_string(Room#room.config, Lang),
|
||||
put_room_config(F, RoomConfig, Lang),
|
||||
Time_offset_str = case Hour_offset<0 of
|
||||
true -> io_lib:format("~p", [Hour_offset]);
|
||||
false -> io_lib:format("+~p", [Hour_offset])
|
||||
end,
|
||||
fw(F, "<br/><a class=\"ts\">GMT~s</a><br/>", [Time_offset_str]).
|
||||
|
||||
put_header_css(F, false) ->
|
||||
fw(F, "<style type=\"text/css\">"),
|
||||
fw(F, "<!--"),
|
||||
fw(F, ".ts {color: #AAAAAA; text-decoration: none;}"),
|
||||
fw(F, ".mrcm {color: #009900; font-style: italic; font-weight: bold;}"),
|
||||
fw(F, ".msc {color: #009900; font-style: italic; font-weight: bold;}"),
|
||||
fw(F, ".mj {color: #009900; font-style: italic;}"),
|
||||
fw(F, ".ml {color: #009900; font-style: italic;}"),
|
||||
fw(F, ".mk {color: #009900; font-style: italic;}"),
|
||||
fw(F, ".mb {color: #009900; font-style: italic;}"),
|
||||
fw(F, ".mnc {color: #009900; font-style: italic;}"),
|
||||
fw(F, ".mn {color: #0000AA;}"),
|
||||
fw(F, ".mne {color: #AA0099;}"),
|
||||
fw(F, "a.nav {color: #AAAAAA; font-family: monospace; letter-spacing: 3px; text-decoration: none;}"),
|
||||
fw(F, "div.roomtitle {border-bottom: #224466 solid 3pt; margin-left: 20pt;}"),
|
||||
fw(F, "a.roomtitle {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; text-decoration: none;}"),
|
||||
fw(F, "a.roomjid {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; margin-left: 20pt; text-decoration: none;}"),
|
||||
fw(F, "div.logdate {color: #663399; font-size: 20px; font-weight: bold; font-family: sans-serif; letter-spacing: 2px; border-bottom: #224466 solid 1pt; margin-left:80pt; margin-top:20px;}"),
|
||||
fw(F, "div.roomsubject {color: #336699; font-size: 18px; font-family: sans-serif; margin-left: 80pt; margin-bottom: 10px;}"),
|
||||
fw(F, "div.rc {color: #336699; font-size: 12px; font-family: sans-serif; margin-left: 50%; text-align: right; background: #f3f6f9; border-bottom: 1px solid #336699; border-right: 4px solid #336699;}"),
|
||||
fw(F, "div.rct {font-weight: bold; background: #e3e6e9; padding-right: 10px;}"),
|
||||
fw(F, "div.rcos {padding-right: 10px;}"),
|
||||
fw(F, "div.rcoe {color: green;}"),
|
||||
fw(F, "div.rcod {color: red;}"),
|
||||
fw(F, "div.rcoe:after {content: \": v\";}"),
|
||||
fw(F, "div.rcod:after {content: \": x\";}"),
|
||||
fw(F, "div.rcot:after {}"),
|
||||
fw(F, ".legend {width: 100%; margin-top: 30px; border-top: #224466 solid 1pt; padding: 10px 0px 10px 0px; text-align: left; font-family: monospace; letter-spacing: 2px;}"),
|
||||
fw(F, ".w3c {position: absolute; right: 10px; width: 60%; text-align: right; font-family: monospace; letter-spacing: 1px;}"),
|
||||
fw(F, "//-->"),
|
||||
fw(F, "</style>");
|
||||
|
||||
put_header_css(F, CSSFile) ->
|
||||
fw(F, "<link rel=\"stylesheet\" type=\"text/css\" href=\"~s\" media=\"all\">", [CSSFile]).
|
||||
|
||||
put_header_script(F) ->
|
||||
fw(F, "<script type=\"text/javascript\">"),
|
||||
fw(F, "function sh(e) // Show/Hide an element"),
|
||||
fw(F, "{if(document.getElementById(e).style.display=='none')"),
|
||||
fw(F, "{document.getElementById(e).style.display='block';}"),
|
||||
fw(F, "else {document.getElementById(e).style.display='none';}}"),
|
||||
fw(F, "</script>").
|
||||
|
||||
put_room_config(F, RoomConfig, Lang) ->
|
||||
{_, Now2, _} = now(),
|
||||
fw(F, "<div class=\"rc\">"),
|
||||
fw(F, "<div class=\"rct\" onclick=\"sh('a~p');return false;\">~s</div>", [Now2, ?T("Room Configuration")]),
|
||||
fw(F, "<div class=\"rcos\" id=\"a~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomConfig]),
|
||||
fw(F, "</div>").
|
||||
|
||||
htmlize(S1) ->
|
||||
S2_list = string:tokens(S1, "\n"),
|
||||
lists:foldl(
|
||||
fun(Si, Res) ->
|
||||
Si2 = htmlize2(Si),
|
||||
case Res of
|
||||
"" -> Si2;
|
||||
_ -> Res ++ "<br/>" ++ Si2
|
||||
end
|
||||
end,
|
||||
"",
|
||||
S2_list).
|
||||
|
||||
htmlize2(S1) ->
|
||||
S2 = element(2, regexp:gsub(S1, "<", "\\<")),
|
||||
S3 = element(2, regexp:gsub(S2, ">", "\\>")),
|
||||
S4 = element(2, regexp:gsub(S3, "(http|ftp)://.[^ ]*", "<a href=\"&\">&</a>")),
|
||||
%element(2, regexp:gsub(S4, " ", "\\ ")).
|
||||
S4.
|
||||
|
||||
get_room_info(RoomJID, Opts) ->
|
||||
Title =
|
||||
case lists:keysearch(title, 1, Opts) of
|
||||
{value, {_, T}} -> T;
|
||||
false -> ""
|
||||
end,
|
||||
Subject =
|
||||
case lists:keysearch(subject, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
false -> ""
|
||||
end,
|
||||
SubjectAuthor =
|
||||
case lists:keysearch(subject_author, 1, Opts) of
|
||||
{value, {_, SA}} -> SA;
|
||||
false -> ""
|
||||
end,
|
||||
#room{jid = jlib:jid_to_string(RoomJID),
|
||||
title = Title,
|
||||
subject = Subject,
|
||||
subject_author = SubjectAuthor,
|
||||
config = Opts
|
||||
}.
|
||||
|
||||
roomconfig_to_string(Options, Lang) ->
|
||||
% Get title, if available
|
||||
Title = case lists:keysearch(title, 1, Options) of
|
||||
{value, Tuple} -> [Tuple];
|
||||
false -> []
|
||||
end,
|
||||
|
||||
% Remove title from list
|
||||
Os1 = lists:keydelete(title, 1, Options),
|
||||
|
||||
% Order list
|
||||
Os2 = lists:sort(Os1),
|
||||
|
||||
% Add title to ordered list
|
||||
Options2 = Title ++ Os2,
|
||||
|
||||
lists:foldl(
|
||||
fun({Opt, Val}, R) ->
|
||||
case get_roomconfig_text(Opt) of
|
||||
undefined ->
|
||||
R;
|
||||
OptT ->
|
||||
OptText = ?T(OptT),
|
||||
R2 = case Val of
|
||||
false -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
|
||||
true -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
|
||||
"" -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
|
||||
T ->
|
||||
case Opt of
|
||||
password -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
|
||||
title -> "<div class=\"rcot\">" ++ ?T("Room title") ++ ": \"" ++ T ++ "\"</div>";
|
||||
_ -> "\"" ++ T ++ "\""
|
||||
end
|
||||
end,
|
||||
R ++ R2
|
||||
end
|
||||
end,
|
||||
"",
|
||||
Options2).
|
||||
|
||||
get_roomconfig_text(title) -> "Room title";
|
||||
get_roomconfig_text(persistent) -> "Make room persistent";
|
||||
get_roomconfig_text(public) -> "Make room public searchable";
|
||||
get_roomconfig_text(public_list) -> "Make participants list public";
|
||||
get_roomconfig_text(password_protected) -> "Make room password protected";
|
||||
get_roomconfig_text(password) -> "Password";
|
||||
get_roomconfig_text(anonymous) -> "Make room semianonymous";
|
||||
get_roomconfig_text(members_only) -> "Make room members-only";
|
||||
get_roomconfig_text(moderated) -> "Make room moderated";
|
||||
get_roomconfig_text(members_by_default) -> "Default users as participants";
|
||||
get_roomconfig_text(allow_change_subj) -> "Allow users to change subject";
|
||||
get_roomconfig_text(allow_private_messages) -> "Allow users to send private messages";
|
||||
get_roomconfig_text(allow_query_users) -> "Allow users to query other users";
|
||||
get_roomconfig_text(allow_user_invites) -> "Allow users to send invites";
|
||||
get_roomconfig_text(logging) -> "Enable logging";
|
||||
get_roomconfig_text(_) -> undefined.
|
||||
|
||||
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
|
||||
|
||||
calc_hour_offset(TimeHere) ->
|
||||
TimeZero = calendar:now_to_universal_time(now()),
|
||||
TimeHereHour = calendar:datetime_to_gregorian_seconds(TimeHere) div 3600,
|
||||
TimeZeroHour = calendar:datetime_to_gregorian_seconds(TimeZero) div 3600,
|
||||
TimeHereHour - TimeZeroHour.
|
@ -51,7 +51,7 @@
|
||||
password_protected = false,
|
||||
password = "",
|
||||
anonymous = true,
|
||||
logging = false % TODO
|
||||
logging = false
|
||||
}).
|
||||
|
||||
-record(user, {jid,
|
||||
@ -370,7 +370,11 @@ normal_state({route, From, Nick,
|
||||
NewState =
|
||||
add_user_presence_un(From, Packet, StateData),
|
||||
send_new_presence(From, NewState),
|
||||
remove_online_user(From, NewState);
|
||||
Reason = case xml:get_subtag(Packet, "status") of
|
||||
false -> "";
|
||||
Status_el -> xml:get_tag_cdata(Status_el)
|
||||
end,
|
||||
remove_online_user(From, NewState, Reason);
|
||||
_ ->
|
||||
StateData
|
||||
end;
|
||||
@ -856,10 +860,17 @@ add_online_user(JID, Nick, Role, StateData) ->
|
||||
nick = Nick,
|
||||
role = Role},
|
||||
StateData#state.users),
|
||||
add_to_log(join, Nick, StateData),
|
||||
StateData#state{users = Users}.
|
||||
|
||||
remove_online_user(JID, StateData) ->
|
||||
remove_online_user(JID, StateData, "").
|
||||
|
||||
remove_online_user(JID, StateData, Reason) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
{ok, #user{nick = Nick}} =
|
||||
?DICT:find(LJID, StateData#state.users),
|
||||
add_to_log(leave, {Nick, Reason}, StateData),
|
||||
Users = ?DICT:erase(LJID, StateData#state.users),
|
||||
StateData#state{users = Users}.
|
||||
|
||||
@ -1298,6 +1309,7 @@ change_nick(JID, Nick, StateData) ->
|
||||
end, StateData#state.users),
|
||||
NewStateData = StateData#state{users = Users},
|
||||
send_nick_changing(JID, OldNick, NewStateData),
|
||||
add_to_log(nickchange, {OldNick, Nick}, StateData),
|
||||
NewStateData.
|
||||
|
||||
send_nick_changing(JID, OldNick, StateData) ->
|
||||
@ -1397,6 +1409,7 @@ add_message_to_history(FromNick, Packet, StateData) ->
|
||||
Size = lists:flatlength(xml:element_to_string(SPacket)),
|
||||
Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
|
||||
StateData#state.history),
|
||||
add_to_log(text, {FromNick, Packet}, StateData),
|
||||
StateData#state{history = Q1}.
|
||||
|
||||
send_history(JID, Shift, StateData) ->
|
||||
@ -1951,6 +1964,9 @@ send_kickban_presence(JID, Reason, Code, StateData) ->
|
||||
end
|
||||
end,
|
||||
lists:foreach(fun(J) ->
|
||||
{ok, #user{nick = Nick}} =
|
||||
?DICT:find(J, StateData#state.users),
|
||||
add_to_log(kickban, {Nick, Reason, Code}, StateData),
|
||||
send_kickban_presence1(J, Reason, Code, StateData)
|
||||
end, LJIDs).
|
||||
|
||||
@ -1998,7 +2014,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
|
||||
{?NS_XDATA, "cancel"} ->
|
||||
{result, [], StateData};
|
||||
{?NS_XDATA, "submit"} ->
|
||||
set_config(XEl, StateData);
|
||||
case check_allowed_log_change(XEl, StateData, From) of
|
||||
allow -> set_config(XEl, StateData);
|
||||
deny -> {error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
@ -2019,7 +2038,7 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = SubEl,
|
||||
case xml:remove_cdata(Els) of
|
||||
[] ->
|
||||
get_config(Lang, StateData);
|
||||
get_config(Lang, StateData, From);
|
||||
[Item] ->
|
||||
case xml:get_tag_attr("affiliation", Item) of
|
||||
false ->
|
||||
@ -2048,6 +2067,14 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
|
||||
{error, ?ERRT_FORBIDDEN(Lang, ErrText)}
|
||||
end.
|
||||
|
||||
check_allowed_log_change(XEl, StateData, From) ->
|
||||
case lists:keymember("logging", 1, jlib:parse_xdata_submit(XEl)) of
|
||||
false ->
|
||||
allow;
|
||||
true ->
|
||||
mod_muc_log:check_access_log(
|
||||
StateData#state.server_host, From)
|
||||
end.
|
||||
|
||||
|
||||
-define(XFIELD(Type, Label, Var, Val),
|
||||
@ -2070,7 +2097,7 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
|
||||
?XFIELD("text-private", Label, Var, Val)).
|
||||
|
||||
|
||||
get_config(Lang, StateData) ->
|
||||
get_config(Lang, StateData, From) ->
|
||||
Config = StateData#state.config,
|
||||
Res =
|
||||
[{xmlelement, "title", [],
|
||||
@ -2120,11 +2147,17 @@ get_config(Lang, StateData) ->
|
||||
Config#config.allow_query_users),
|
||||
?BOOLXFIELD("Allow users to send invites",
|
||||
"allow_user_invites",
|
||||
Config#config.allow_user_invites),
|
||||
?BOOLXFIELD("Enable logging",
|
||||
"logging",
|
||||
Config#config.logging)
|
||||
],
|
||||
Config#config.allow_user_invites)
|
||||
] ++
|
||||
case mod_muc_log:check_access_log(
|
||||
StateData#state.server_host, From) of
|
||||
allow ->
|
||||
[?BOOLXFIELD(
|
||||
"Enable logging",
|
||||
"logging",
|
||||
Config#config.logging)];
|
||||
_ -> []
|
||||
end,
|
||||
{result, [{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
@ -2144,7 +2177,10 @@ set_config(XEl, StateData) ->
|
||||
_ ->
|
||||
case set_xoption(XData, StateData#state.config) of
|
||||
#config{} = Config ->
|
||||
change_config(Config, StateData);
|
||||
Res = change_config(Config, StateData),
|
||||
{result, _, NSD} = Res,
|
||||
add_to_log(roomconfig_change, [], NSD),
|
||||
Res;
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
@ -2487,3 +2523,16 @@ check_invitation(From, Els, StateData) ->
|
||||
error
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Logging
|
||||
|
||||
add_to_log(Type, Data, StateData) ->
|
||||
case (StateData#state.config)#config.logging of
|
||||
true ->
|
||||
mod_muc_log:add_to_log(
|
||||
StateData#state.server_host, Type, Data,
|
||||
StateData#state.jid, make_opts(StateData));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
@ -64,8 +64,8 @@ process_data(CallbackPid, Stack, Data) ->
|
||||
{?XML_START, {Name, Attrs}} ->
|
||||
if
|
||||
Stack == [] ->
|
||||
gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamstart, Name, Attrs});
|
||||
catch gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamstart, Name, Attrs});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
@ -76,12 +76,12 @@ process_data(CallbackPid, Stack, Data) ->
|
||||
NewEl = {xmlelement, Name, Attrs, lists:reverse(Els)},
|
||||
case Tail of
|
||||
[] ->
|
||||
gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamend, EndName}),
|
||||
catch gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamend, EndName}),
|
||||
Tail;
|
||||
[_] ->
|
||||
gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamelement, NewEl}),
|
||||
catch gen_fsm:send_event(CallbackPid,
|
||||
{xmlstreamelement, NewEl}),
|
||||
Tail;
|
||||
[{xmlelement, Name1, Attrs1, Els1} | Tail1] ->
|
||||
[{xmlelement, Name1, Attrs1, [NewEl | Els1]} |
|
||||
@ -98,7 +98,7 @@ process_data(CallbackPid, Stack, Data) ->
|
||||
[] -> []
|
||||
end;
|
||||
{?XML_ERROR, Err} ->
|
||||
gen_fsm:send_event(CallbackPid, {xmlstreamerror, Err})
|
||||
catch gen_fsm:send_event(CallbackPid, {xmlstreamerror, Err})
|
||||
end.
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user