• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.57
/src/ejabberd_web_admin.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : ejabberd_web_admin.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Administration web interface
5
%%% Created :  9 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
%%%% definitions
27

28
-module(ejabberd_web_admin).
29

30
-author('alexey@process-one.net').
31

32
-export([process/2, pretty_print_xml/1,
33
         make_command/2, make_command/4, make_command_raw_value/3,
34
         make_table/2, make_table/4,
35
         make_menu_system/4, make_menu_system_el/4,
36
         nice_this/1,
37
         term_to_id/1, id_to_term/1]).
38

39
%% Internal commands
40
-export([webadmin_host_last_activity/3,
41
         webadmin_node_db_table_page/3]).
42

43
-include_lib("xmpp/include/xmpp.hrl").
44
-include("ejabberd_commands.hrl").
45
-include("ejabberd_http.hrl").
46
-include("ejabberd_web_admin.hrl").
47
-include("logger.hrl").
48
-include("translate.hrl").
49

50
-define(INPUTATTRS(Type, Name, Value, Attrs),
51
        ?XA(<<"input">>,
52
            (Attrs ++
53
               [{<<"type">>, Type}, {<<"name">>, Name},
54
                {<<"value">>, Value}]))).
55

56
%%%==================================
57
%%%% get_acl_access
58

59
-spec get_acl_rule(Path::[binary()], 'GET' | 'POST') ->
60
    {HostOfRule::binary(), [AccessRule::atom()]}.
61

62
%% All accounts can access those URLs
UNCOV
63
get_acl_rule([], _) -> {<<"localhost">>, [all]};
48✔
64
get_acl_rule([<<"style.css">>], _) ->
65
    {<<"localhost">>, [all]};
×
66
get_acl_rule([<<"logo.png">>], _) ->
67
    {<<"localhost">>, [all]};
×
68
get_acl_rule([<<"favicon.ico">>], _) ->
69
    {<<"localhost">>, [all]};
×
70
get_acl_rule([<<"additions.js">>], _) ->
71
    {<<"localhost">>, [all]};
×
72
get_acl_rule([<<"sortable.min.css">>], _) ->
73
    {<<"localhost">>, [all]};
×
74
get_acl_rule([<<"sortable.min.js">>], _) ->
75
    {<<"localhost">>, [all]};
×
76
%% This page only displays vhosts that the user is admin:
77
get_acl_rule([<<"vhosts">>], _) ->
UNCOV
78
    {<<"localhost">>, [all]};
48✔
79
%% The pages of a vhost are only accessible if the user is admin of that vhost:
80
get_acl_rule([<<"server">>, VHost | _RPath], Method)
81
    when Method =:= 'GET' orelse Method =:= 'HEAD' ->
UNCOV
82
    {VHost, [configure]};
324✔
83
get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
UNCOV
84
    {VHost, [configure]};
24✔
85
%% Default rule: only global admins can access any other random page
86
get_acl_rule(_RPath, Method)
87
    when Method =:= 'GET' orelse Method =:= 'HEAD' ->
UNCOV
88
    {global, [configure]};
288✔
89
get_acl_rule(_RPath, 'POST') ->
90
    {global, [configure]}.
×
91

92
%%%==================================
93
%%%% Menu Items Access
94

95
get_jid(Auth, HostHTTP, Method) ->
UNCOV
96
    case get_auth_admin(Auth, HostHTTP, [], Method) of
40✔
97
      {ok, {User, Server}} ->
UNCOV
98
          jid:make(User, Server);
40✔
99
      {unauthorized, Error} ->
100
          ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
×
101
          throw({unauthorized, Auth})
×
102
    end.
103

104
get_menu_items(global, cluster, Lang, JID, Level) ->
UNCOV
105
    {_Base, _, Items} = make_server_menu([], [], Lang, JID, Level),
8✔
UNCOV
106
    lists:map(fun ({URI, Name}) ->
8✔
UNCOV
107
                      {<<URI/binary, "/">>, Name};
40✔
108
                  ({URI, Name, _SubMenu}) ->
UNCOV
109
                      {<<URI/binary, "/">>, Name}
16✔
110
              end,
111
              Items);
112
get_menu_items(Host, cluster, Lang, JID, Level) ->
113
    {_Base, _, Items} = make_host_menu(Host, [], [], Lang, JID, Level),
×
114
    lists:map(fun ({URI, Name}) ->
×
115
                      {<<URI/binary, "/">>, Name};
×
116
                  ({URI, Name, _SubMenu}) ->
117
                      {<<URI/binary, "/">>, Name}
×
118
              end,
119
              Items).
120

121
%% get_menu_items(Host, Node, Lang, JID) ->
122
%%     {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
123
%%     lists:map(
124
%%         fun({URI, Name}) ->
125
%%                 {Base++URI++"/", Name};
126
%%            ({URI, Name, _SubMenu}) ->
127
%%                 {Base++URI++"/", Name}
128
%%         end,
129
%%         Items
130
%%     ).
131

132
is_allowed_path(global, RPath, JID) ->
UNCOV
133
    is_allowed_path([], RPath, JID);
336✔
134
is_allowed_path(Host, RPath, JID) when is_binary(Host) ->
UNCOV
135
    is_allowed_path([<<"server">>, Host], RPath, JID);
316✔
136

137
is_allowed_path(BasePath, {Path, _}, JID) ->
UNCOV
138
    is_allowed_path(BasePath ++ [Path], JID);
492✔
139
is_allowed_path(BasePath, {Path, _, _}, JID) ->
UNCOV
140
    is_allowed_path(BasePath ++ [Path], JID).
160✔
141

142
is_allowed_path([<<"admin">> | Path], JID) ->
143
    is_allowed_path(Path, JID);
×
144
is_allowed_path(Path, JID) ->
UNCOV
145
    {HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
652✔
UNCOV
146
    any_rules_allowed(HostOfRule, AccessRule, JID).
652✔
147

148
%%%==================================
149
%%%% process/2
150

151
process(Path, #request{raw_path = RawPath} = Request) ->
UNCOV
152
    Continue = case Path of
48✔
153
                   [E] ->
154
                       binary:match(E, <<".">>) /= nomatch;
×
155
                   _ ->
UNCOV
156
                       false
48✔
157
               end,
UNCOV
158
    case Continue orelse binary:at(RawPath, size(RawPath) - 1) == $/ of
48✔
159
        true ->
UNCOV
160
            process2(Path, Request);
48✔
161
        _ ->
162
            {301, [{<<"Location">>, <<RawPath/binary, "/">>}], <<>>}
×
163
    end.
164

165
process2([<<"logout">> | _], #request{lang = Lang}) ->
166
    Text = [?XCT(<<"h1">>, ?T("Logged Out")),
×
167
            ?XE(<<"p">>, [?C(<<"Your web browser has logout from WebAdmin. Close this window, or login again in: ">>),
168
                          ?AC(<<"../">>, <<"ejabberd WebAdmin">>)])],
169
    {401, [{<<"WWW-Authenticate">>, <<"basic realm=\"ejabberd\"">>}],
×
170
     ejabberd_web:make_xhtml(Text)};
171
process2([<<"server">>, SHost | RPath] = Path,
172
        #request{auth = Auth, lang = Lang, host = HostHTTP,
173
                 method = Method} =
174
            Request) ->
UNCOV
175
    Host = jid:nameprep(SHost),
32✔
UNCOV
176
    case ejabberd_router:is_my_host(Host) of
32✔
177
      true ->
UNCOV
178
          case get_auth_admin(Auth, HostHTTP, Path, Method) of
32✔
179
            {ok, {User, Server}} ->
UNCOV
180
                AJID = get_jid(Auth, HostHTTP, Method),
32✔
UNCOV
181
                process_admin(Host,
32✔
182
                              Request#request{path = RPath,
183
                                              us = {User, Server}},
184
                              AJID);
185
            {unauthorized, <<"no-auth-provided">>} ->
186
                {401,
×
187
                 [{<<"WWW-Authenticate">>,
188
                   <<"basic realm=\"ejabberd\"">>}],
189
                 ejabberd_web:make_xhtml(make_unauthorized(Lang))};
190
            {unauthorized, Error} ->
191
                {BadUser, _BadPass} = Auth,
×
192
                {IPT, _Port} = Request#request.ip,
×
193
                IPS = ejabberd_config:may_hide_data(misc:ip_to_list(IPT)),
×
194
                ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
×
195
                             [BadUser, IPS, Error]),
×
196
                {401,
×
197
                 [{<<"WWW-Authenticate">>,
198
                   <<"basic realm=\"auth error, retry login "
199
                     "to ejabberd\"">>}],
200
                 ejabberd_web:make_xhtml(make_unauthorized(Lang))}
201
          end;
202
      false -> ejabberd_web:error(not_found)
×
203
    end;
204
process2(RPath,
205
        #request{auth = Auth, lang = Lang, host = HostHTTP,
206
                 method = Method} =
207
            Request) ->
UNCOV
208
    case get_auth_admin(Auth, HostHTTP, RPath, Method) of
16✔
209
        {ok, {User, Server}} ->
UNCOV
210
            AJID = get_jid(Auth, HostHTTP, Method),
8✔
UNCOV
211
            process_admin(global,
8✔
212
                          Request#request{path = RPath,
213
                                          us = {User, Server}},
214
                          AJID);
215
        {unauthorized, <<"no-auth-provided">>} ->
UNCOV
216
            {401,
8✔
217
             [{<<"WWW-Authenticate">>,
218
               <<"basic realm=\"ejabberd\"">>}],
219
             ejabberd_web:make_xhtml(make_unauthorized(Lang))};
220
        {unauthorized, Error} ->
221
            {BadUser, _BadPass} = Auth,
×
222
            {IPT, _Port} = Request#request.ip,
×
223
            IPS = ejabberd_config:may_hide_data(misc:ip_to_list(IPT)),
×
224
            ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
×
225
                         [BadUser, IPS, Error]),
×
226
            {401,
×
227
             [{<<"WWW-Authenticate">>,
228
               <<"basic realm=\"auth error, retry login "
229
                 "to ejabberd\"">>}],
230
             ejabberd_web:make_xhtml(make_unauthorized(Lang))}
231
    end.
232

233
make_unauthorized(Lang) ->
UNCOV
234
    [?XCT(<<"h1">>, ?T("Unauthorized")),
8✔
235
     ?XE(<<"p">>, [?C(<<"There was some problem authenticating, or the account doesn't have privilege.">>)]),
236
     ?XE(<<"p">>, [?C(<<"Please check the log file for a more precise error message.">>)])].
237

238
get_auth_admin(Auth, HostHTTP, RPath, Method) ->
UNCOV
239
    case Auth of
88✔
240
      {SJID, Pass} ->
UNCOV
241
          {HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
80✔
UNCOV
242
            try jid:decode(SJID) of
80✔
243
                #jid{luser = <<"">>, lserver = User} ->
244
                    case ejabberd_router:is_my_host(HostHTTP) of
×
245
                        true ->
246
                            get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
×
247
                                             Pass);
248
                        _ ->
249
                            {unauthorized, <<"missing-server">>}
×
250
                    end;
251
                #jid{luser = User, lserver = Server} ->
UNCOV
252
                    get_auth_account(HostOfRule, AccessRule, User, Server,
80✔
253
                                     Pass)
254
            catch _:{bad_jid, _} ->
255
                    {unauthorized, <<"badformed-jid">>}
×
256
            end;
257
      invalid -> {unauthorized, <<"no-auth-provided">>};
×
UNCOV
258
      undefined -> {unauthorized, <<"no-auth-provided">>}
8✔
259
    end.
260

261
get_auth_account(HostOfRule, AccessRule, User, Server,
262
                 Pass) ->
UNCOV
263
    case lists:member(Server, ejabberd_config:get_option(hosts)) of
80✔
UNCOV
264
        true -> get_auth_account2(HostOfRule, AccessRule, User, Server, Pass);
80✔
265
        false -> {unauthorized, <<"inexistent-host">>}
×
266
    end.
267

268
get_auth_account2(HostOfRule, AccessRule, User, Server,
269
                 Pass) ->
UNCOV
270
    case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
80✔
271
      true ->
UNCOV
272
          case any_rules_allowed(HostOfRule, AccessRule,
80✔
273
                                 jid:make(User, Server))
274
              of
275
            false -> {unauthorized, <<"unprivileged-account">>};
×
UNCOV
276
            true -> {ok, {User, Server}}
80✔
277
          end;
278
      false ->
279
          case ejabberd_auth:user_exists(User, Server) of
×
280
            true -> {unauthorized, <<"bad-password">>};
×
281
            false -> {unauthorized, <<"inexistent-account">>}
×
282
          end
283
    end.
284

285
%%%==================================
286
%%%% make_xhtml
287

288
make_xhtml(Els, Host, Request, JID, Level) ->
UNCOV
289
    make_xhtml(Els, Host, cluster, unspecified, Request, JID, Level).
16✔
290

291
make_xhtml(Els, Host, Username, Request, JID, Level) when
292
      (Username == unspecified) or (is_binary(Username)) ->
UNCOV
293
    make_xhtml(Els, Host, cluster, Username, Request, JID, Level);
24✔
294

295
make_xhtml(Els, Host, Node, Request, JID, Level) ->
296
    make_xhtml(Els, Host, Node, unspecified, Request, JID, Level).
×
297

298
-spec make_xhtml([xmlel()],
299
                 Host::global | binary(),
300
                 Node::cluster | atom(),
301
                 Username::unspecified | binary(),
302
                 Request::http_request(),
303
                 jid(),
304
                 Level::integer()) ->
305
    {200, [html], xmlel()}.
306
make_xhtml(Els, Host, Node, Username, #request{lang = Lang} = R, JID, Level) ->
UNCOV
307
    Base = get_base_path_sum(0, 0, Level),
40✔
UNCOV
308
    MenuItems = make_navigation(Host, Node, Username, Lang, JID, Level)
40✔
309
    ++ make_login_items(R, Level, JID),
UNCOV
310
    {200, [html],
40✔
311
     #xmlel{name = <<"html">>,
312
            attrs =
313
                [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
314
                 {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang),
315
            children =
316
                [#xmlel{name = <<"head">>, attrs = [],
317
                        children =
318
                            [?XCT(<<"title">>, ?T("ejabberd Web Admin")),
319
                             #xmlel{name = <<"meta">>,
320
                                    attrs =
321
                                        [{<<"http-equiv">>, <<"Content-Type">>},
322
                                         {<<"content">>,
323
                                          <<"text/html; charset=utf-8">>}],
324
                                    children = []},
325
                             #xmlel{name = <<"script">>,
326
                                    attrs =
327
                                        [{<<"src">>,
328
                                          <<Base/binary, "additions.js">>},
329
                                         {<<"type">>, <<"text/javascript">>}],
330
                                    children = [?C(<<" ">>)]},
331
                             #xmlel{name = <<"link">>,
332
                                    attrs =
333
                                        [{<<"href">>,
334
                                          <<Base/binary, "favicon.ico">>},
335
                                         {<<"type">>, <<"image/x-icon">>},
336
                                         {<<"rel">>, <<"shortcut icon">>}],
337
                                    children = []},
338
                             #xmlel{name = <<"link">>,
339
                                    attrs =
340
                                        [{<<"href">>,
341
                                          <<Base/binary, "style.css">>},
342
                                         {<<"type">>, <<"text/css">>},
343
                                         {<<"rel">>, <<"stylesheet">>}],
344
                                    children = []},
345
                             #xmlel{name = <<"link">>,
346
                                    attrs =
347
                                        [{<<"href">>,
348
                                          <<Base/binary, "sortable.min.css">>},
349
                                         {<<"type">>, <<"text/css">>},
350
                                         {<<"rel">>, <<"stylesheet">>}],
351
                                    children = []},
352
                             #xmlel{name = <<"script">>,
353
                                    attrs =
354
                                        [{<<"src">>,
355
                                          <<Base/binary, "sortable.min.js">>},
356
                                         {<<"type">>, <<"text/javascript">>}],
357
                                    children = [?C(<<" ">>)]}]},
358
                 ?XE(<<"body">>,
359
                     [?XAE(<<"div">>, [{<<"id">>, <<"container">>}],
360
                           [?XAE(<<"div">>, [{<<"id">>, <<"header">>}],
361
                                 [?XE(<<"h1">>,
362
                                      [?ACT(Base,
363
                                            <<"ejabberd Web Admin">>)])]),
364
                            ?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}],
365
                                 [?XE(<<"ul">>, MenuItems)]),
366
                            ?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els),
367
                            ?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}],
368
                                 [{xmlcdata, <<"">>}])]),
369
                      ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
370
                           [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
371
                                 [?XE(<<"p">>,
372
                                  [?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>),
373
                                   ?C(<<" ">>), ?C(ejabberd_option:version()),
374
                                   ?C(<<" (c) 2002-2026 ">>),
375
                                   ?AC(<<"https://www.process-one.net/">>, <<"ProcessOne, leader in messaging and push solutions">>)]
376
                                 )])])])]}}.
377

378
direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}];
×
UNCOV
379
direction(_) -> [].
40✔
380

381
get_base_path(Host, Node, Level) ->
UNCOV
382
    SumHost = case Host of
104✔
UNCOV
383
        global -> 0;
48✔
UNCOV
384
        _ -> -2
56✔
385
    end,
UNCOV
386
    SumNode = case Node of
104✔
UNCOV
387
        cluster -> 0;
80✔
UNCOV
388
        _ -> -2
24✔
389
    end,
UNCOV
390
    get_base_path_sum(SumHost, SumNode, Level).
104✔
391

392
get_base_path_sum(SumHost, SumNode, Level) ->
UNCOV
393
    iolist_to_binary(lists:duplicate(Level + SumHost + SumNode, "../")).
392✔
394

395
%%%==================================
396
%%%% css & images
397

398
additions_js() ->
399
    case misc:read_js("admin.js") of
×
400
        {ok, JS} -> JS;
×
401
        {error, _} -> <<>>
×
402
    end.
403

404
css(Host) ->
405
    case misc:read_css("admin.css") of
×
406
        {ok, CSS} ->
407
            Base = get_base_path(Host, cluster, 0),
×
408
            re:replace(CSS, <<"@BASE@">>, Base, [{return, binary}]);
×
409
        {error, _} ->
410
            <<>>
×
411
    end.
412

413
favicon() ->
414
    case misc:read_img("favicon.png") of
×
415
        {ok, ICO} -> ICO;
×
416
        {error, _} -> <<>>
×
417
    end.
418

419
logo() ->
420
    case misc:read_img("admin-logo.png") of
×
421
        {ok, Img} -> Img;
×
422
        {error, _} -> <<>>
×
423
    end.
424

425
sortable_css() ->
426
    case misc:read_css("sortable.min.css") of
×
427
        {ok, CSS} -> CSS;
×
428
        {error, _} -> <<>>
×
429
    end.
430

431
sortable_js() ->
432
    case misc:read_js("sortable.min.js") of
×
433
        {ok, JS} -> JS;
×
434
        {error, _} -> <<>>
×
435
    end.
436

437
%%%==================================
438
%%%% process_admin
439

440
process_admin(global, #request{path = [], lang = Lang} = Request, AJID) ->
UNCOV
441
    Title = ?H1GLraw(<<"">>, <<"">>, <<"home">>),
8✔
UNCOV
442
    MenuItems = get_menu_items(global, cluster, Lang, AJID, 0),
8✔
UNCOV
443
    Disclaimer = maybe_disclaimer_not_admin(MenuItems, AJID, Lang),
8✔
UNCOV
444
    WelcomeText =
8✔
445
        [?BR,
446
         ?XAE(<<"p">>, [{<<"align">>, <<"center">>}],
447
              [?XA(<<"img">>, [{<<"src">>, <<"logo.png">>},
448
                               {<<"style">>, <<"border-radius:10px; background:#49cbc1; padding: 1.1em;">>}])
449
              ])
450
         ] ++ Title ++ [
451
         ?XAE(<<"blockquote">>,
452
             [{<<"id">>, <<"welcome">>}],
453
             [?XC(<<"p">>, <<"Welcome to ejabberd's WebAdmin!">>),
454
              ?XC(<<"p">>, <<"Browse the menu to navigate your XMPP virtual hosts, "
455
                              "Erlang nodes, and other global server pages...">>),
456
              ?XC(<<"p">>, <<"Some pages have a link in the top right corner "
457
                              "to relevant documentation in ejabberd Docs.">>),
458
              ?X(<<"hr">>),
459
              ?XE(<<"p">>,
460
                  [?C(<<"Many pages use ejabberd's API commands to show information "
461
                         "and to allow you perform administrative tasks. "
462
                         "Click on a command name to view its details. "
463
                         "You can also execute those same API commands "
464
                         "using other interfaces, see: ">>),
465
                   ?AC(<<"https://docs.ejabberd.im/developer/ejabberd-api/">>,
466
                       <<"ejabberd Docs: API">>)
467
                  ]),
468
              ?XC(<<"p">>, <<"For example, this is the 'stats' command, "
469
                              "it accepts an argument and returns an integer:">>),
470
              make_command(stats, Request)]),
471
         ?BR],
UNCOV
472
    make_xhtml(Disclaimer ++ WelcomeText ++
8✔
473
                   [?XE(<<"ul">>,
UNCOV
474
                        [?LI([?ACT(MIU, MIN)])
56✔
475
                         || {MIU, MIN}
UNCOV
476
                                <- MenuItems])],
8✔
477
               global, Request, AJID, 0);
478
process_admin(Host, #request{path = [], lang = Lang} = R, AJID) ->
479
    make_xhtml([?XCT(<<"h1">>, ?T("Administration")),
×
480
                ?XE(<<"ul">>,
481
                    [?LI([?ACT(MIU, MIN)])
×
482
                     || {MIU, MIN}
483
                            <- get_menu_items(Host, cluster, Lang, AJID, 2)])],
×
484
               Host, R, AJID, 2);
485

486
process_admin(Host, #request{path = [<<"style.css">>]}, _) ->
487
    {200,
×
488
     [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
489
      cache_control_public()],
490
     css(Host)};
491
process_admin(_Host, #request{path = [<<"favicon.ico">>]}, _) ->
492
    {200,
×
493
     [{<<"Content-Type">>, <<"image/x-icon">>},
494
      last_modified(), cache_control_public()],
495
     favicon()};
496
process_admin(_Host, #request{path = [<<"logo.png">>]}, _) ->
497
    {200,
×
498
     [{<<"Content-Type">>, <<"image/png">>}, last_modified(),
499
      cache_control_public()],
500
     logo()};
501
process_admin(_Host, #request{path = [<<"additions.js">>]}, _) ->
502
    {200,
×
503
     [{<<"Content-Type">>, <<"text/javascript">>},
504
      last_modified(), cache_control_public()],
505
     additions_js()};
506
process_admin(_Host, #request{path = [<<"sortable.min.css">>]}, _) ->
507
    {200,
×
508
     [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
509
      cache_control_public()],
510
     sortable_css()};
511
process_admin(_Host, #request{path = [<<"sortable.min.js">>]}, _) ->
512
    {200,
×
513
     [{<<"Content-Type">>, <<"text/javascript">>},
514
      last_modified(), cache_control_public()],
515
     sortable_js()};
516

517
%% @format-begin
518

519
process_admin(global, #request{path = [<<"vhosts">> | RPath], lang = Lang} = R, AJID) ->
520
    Hosts =
×
521
        case make_command_raw_value(registered_vhosts, R, []) of
522
            Hs when is_list(Hs) ->
523
                Hs;
×
524
            _ ->
525
                {User, Server} = R#request.us,
×
526
                ?INFO_MSG("Access to WebAdmin page vhosts/ for account ~s@~s was denied",
×
527
                          [User, Server]),
×
528
                []
×
529
        end,
530
    Level = 1 + length(RPath),
×
531
    HostsAllowed = [Host || Host <- Hosts, can_user_access_host(Host, R)],
×
532
    Table =
×
533
        make_table(20,
534
                   RPath,
535
                   [<<"host">>, {<<"registered users">>, right}, {<<"online users">>, right}],
536
                   [{make_command(echo,
×
537
                                  R,
538
                                  [{<<"sentence">>, Host}],
539
                                  [{only, value},
540
                                   {result_links, [{sentence, host, Level, <<"">>}]}]),
541
                     make_command(stats_host,
542
                                  R,
543
                                  [{<<"name">>, <<"registeredusers">>}, {<<"host">>, Host}],
544
                                  [{only, value},
545
                                   {result_links, [{stat, arg_host, Level, <<"users/">>}]}]),
546
                     make_command(stats_host,
547
                                  R,
548
                                  [{<<"name">>, <<"onlineusers">>}, {<<"host">>, Host}],
549
                                  [{only, value},
550
                                   {result_links, [{stat, arg_host, Level, <<"online-users/">>}]}])}
551
                    || Host <- HostsAllowed]),
×
552
    VhostsElements =
×
553
        [make_command(registered_vhosts, R, [], [{only, presentation}]),
554
         make_command(stats_host, R, [], [{only, presentation}]),
555
         ?XE(<<"blockquote">>, [Table])],
556
    make_xhtml(?H1GL(translate:translate(Lang, ?T("Virtual Hosts")),
×
557
                     <<"basic/#xmpp-domains">>,
558
                     ?T("XMPP Domains"))
559
               ++ VhostsElements,
560
               global,
561
               R,
562
               AJID,
563
               Level);
564
process_admin(Host,
565
              #request{path = [<<"users">>, <<"diapason">>, Diap | RPath], lang = Lang} = R,
566
              AJID)
567
    when is_binary(Host) ->
568
    Level = 5 + length(RPath),
×
569
    RegisterEl = make_command(register, R, [{<<"host">>, Host}], []),
×
570
    Res = list_users_in_diapason(Host, Level, 30, RPath, R, Diap, RegisterEl),
×
571
    make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host, R, AJID, Level);
×
572
process_admin(Host,
573
              #request{path = [<<"users">>, <<"top">>, Attribute | RPath], lang = Lang} = R,
574
              AJID)
575
    when is_binary(Host) ->
576
    Level = 5 + length(RPath),
×
577
    RegisterEl = make_command(register, R, [{<<"host">>, Host}], []),
×
578
    Res = list_users_top(Host, Level, 30, RPath, R, Attribute, RegisterEl),
×
579
    make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host, R, AJID, Level);
×
580
process_admin(Host, #request{path = [<<"users">> | RPath], lang = Lang} = R, AJID)
581
    when is_binary(Host) ->
UNCOV
582
    Level = 3 + length(RPath),
8✔
UNCOV
583
    RegisterEl = make_command(register, R, [{<<"host">>, Host}], []),
8✔
UNCOV
584
    Res = list_users(Host, Level, 30, RPath, R, RegisterEl),
8✔
UNCOV
585
    make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host, R, AJID, Level);
8✔
586
process_admin(Host, #request{path = [<<"online-users">> | RPath], lang = Lang} = R, AJID)
587
    when is_binary(Host) ->
588
    Level = 3 + length(RPath),
×
589
    Set = [make_command(kick_users,
×
590
                        R,
591
                        [{<<"host">>, Host}],
592
                        [{style, danger}, {force_execution, false}])],
593
    timer:sleep(200), % small delay after kicking users before getting the updated list
×
594
    Get = [make_command(connected_users_vhost,
×
595
                        R,
596
                        [{<<"host">>, Host}],
597
                        [{table_options, {100, RPath}},
598
                         {result_links, [{sessions, user, Level, <<"">>}]}])],
599
    make_xhtml([?XCT(<<"h1">>, ?T("Online Users"))] ++ Set ++ Get, Host, R, AJID, Level);
×
600
process_admin(Host,
601
              #request{path = [<<"last-activity">>],
602
                       q = Query,
603
                       lang = Lang} =
604
                  R,
605
              AJID)
606
    when is_binary(Host) ->
607
    PageH1 =
×
608
        ?H1GL(translate:translate(Lang, ?T("Users Last Activity")),
609
              <<"modules/#mod_last">>,
610
              <<"mod_last">>),
611
    Res = make_command(webadmin_host_last_activity,
×
612
                       R,
613
                       [{<<"host">>, Host}, {<<"query">>, Query}, {<<"lang">>, Lang}],
614
                       []),
615
    make_xhtml(PageH1 ++ [Res], Host, R, AJID, 3);
×
616
process_admin(Host, #request{path = [<<"user">>, U], lang = Lang} = R, AJID) ->
UNCOV
617
    case ejabberd_auth:user_exists(U, Host) of
24✔
618
        true ->
UNCOV
619
            Res = user_info(U, Host, R),
24✔
UNCOV
620
            make_xhtml(Res, Host, U, R, AJID, 4);
24✔
621
        false ->
622
            make_xhtml([?XCT(<<"h1">>, ?T("Not Found"))], Host, R, AJID, 4)
×
623
    end;
624
process_admin(Host, #request{path = [<<"nodes">>]} = R, AJID) ->
625
    Level =
×
626
        case Host of
627
            global ->
628
                1;
×
629
            _ ->
630
                3
×
631
        end,
632
    Res = ?H1GLraw(<<"Nodes">>, <<"admin/guide/clustering/">>, <<"Clustering">>)
×
633
          ++ [make_command(list_cluster, R, [], [{result_links, [{node, node, 1, <<"">>}]}])],
634
    make_xhtml(Res, Host, R, AJID, Level);
×
635
process_admin(Host,
636
              #request{path = [<<"node">>, SNode | NPath], lang = Lang} = Request,
637
              AJID) ->
638
    case search_running_node(SNode) of
×
639
        false ->
640
            make_xhtml([?XCT(<<"h1">>, ?T("Node not found"))], Host, Request, AJID, 2);
×
641
        Node ->
642
            Res = get_node(Host, Node, NPath, Request#request{path = NPath}),
×
643
            Level =
×
644
                case Host of
645
                    global ->
646
                        2 + length(NPath);
×
647
                    _ ->
648
                        4 + length(NPath)
×
649
                end,
650
            make_xhtml(Res, Host, Node, Request, AJID, Level)
×
651
    end;
652
%%%==================================
653
%%%% process_admin default case
654
process_admin(Host, #request{path = Path} = Request, AJID) ->
655
    {Username, RPath} =
×
656
        case Path of
657
            [<<"user">>, U | UPath] ->
658
                {U, UPath};
×
659
            _ ->
660
                {unspecified, Path}
×
661
        end,
662
    Request2 = Request#request{path = RPath},
×
663
    Res = case {Host, Username} of
×
664
              {global, _} ->
665
                  ejabberd_hooks:run_fold(webadmin_page_main, Host, [], [Request2]);
×
666
              {_, unspecified} ->
667
                  ejabberd_hooks:run_fold(webadmin_page_host, Host, [], [Host, Request2]);
×
668
              {_Host, Username} ->
669
                  ejabberd_hooks:run_fold(webadmin_page_hostuser,
×
670
                                          Host,
671
                                          [],
672
                                          [Host, Username, Request2])
673
          end,
674
    Level =
×
675
        case Host of
676
            global ->
677
                length(Request#request.path);
×
678
            _ ->
679
                2 + length(Request#request.path)
×
680
        end,
681
    case Res of
×
682
        [] ->
683
            setelement(1,
×
684
                       make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Request, AJID, Level),
685
                       404);
686
        _ ->
687
            make_xhtml(Res, Host, Username, Request, AJID, Level)
×
688
    end.
689
%% @format-end
690

UNCOV
691
term_to_id([]) -> <<>>;
960✔
692
term_to_id(T) -> base64:encode((term_to_binary(T))).
×
693
id_to_term(<<>>) -> [];
×
694
id_to_term(I) -> binary_to_term(base64:decode(I)).
×
695

696
can_user_access_host(Host, #request{auth = Auth,
697
                                    host = HostHTTP,
698
                                    method = Method}) ->
699
    Path = [<<"server">>, Host],
×
700
    case get_auth_admin(Auth, HostHTTP, Path, Method) of
×
701
      {ok, _} ->
702
          true;
×
703
      {unauthorized, _Error} ->
704
          false
×
705
    end.
706

707

708
%%%==================================
709
%%%% list_vhosts
710

711
list_vhosts_allowed(JID) ->
UNCOV
712
    Hosts = ejabberd_option:hosts(),
48✔
UNCOV
713
    lists:filter(fun (Host) ->
48✔
UNCOV
714
                                        any_rules_allowed(Host,
480✔
715
                                                     [configure],
716
                                                     JID)
717
                                end,
718
                                Hosts).
719

720
maybe_disclaimer_not_admin(MenuItems, AJID, _Lang) ->
UNCOV
721
    case {MenuItems, list_vhosts_allowed(AJID)} of
8✔
722
        {[_], []} ->
723
            [?BR,
×
724
             ?DIVRES([?C(<<"Apparently your account has no administration rights in "
725
                            "this server. Please check how to grant admin rights: ">>),
726
                      ?AC(<<"https://docs.ejabberd.im/admin/install/next-steps/#administration-account">>,
727
                          <<"ejabberd Docs: Administration Account">>)])
728
            ];
729
        _ ->
UNCOV
730
            []
8✔
731
    end.
732

733
%%%==================================
734
%%%% list_users
735

736
%% @format-begin
737

738
list_users(Host, Level, PageSize, RPath, R, RegisterEl) ->
UNCOV
739
    Usernames =
8✔
740
        case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of
741
            As when is_list(As) ->
UNCOV
742
                As;
8✔
743
            _ ->
744
                {Aser, Aerver} = R#request.us,
×
745
                ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied",
×
746
                          [Aser, Aerver]),
×
747
                []
×
748
        end,
UNCOV
749
    case length(Usernames) of
8✔
750
        N when N =< 10 ->
UNCOV
751
            list_users(Host, Level, PageSize, RPath, R, Usernames, RegisterEl);
8✔
752
        N when N > 10 ->
753
            list_users_diapason(Host, R, Usernames, N, RegisterEl)
×
754
    end.
755

756
list_users(Host, Level, PageSize, RPath, R, Usernames, RegisterEl) ->
UNCOV
757
    IsOffline = gen_mod:is_loaded(Host, mod_offline),
8✔
UNCOV
758
    IsMam = gen_mod:is_loaded(Host, mod_mam),
8✔
UNCOV
759
    IsRoster = gen_mod:is_loaded(Host, mod_roster),
8✔
UNCOV
760
    IsLast = gen_mod:is_loaded(Host, mod_last),
8✔
UNCOV
761
    Columns =
8✔
762
        [<<"user">>,
763
         list_users_element(IsOffline, column, offline, {}),
764
         list_users_element(IsMam, column, mam, {}),
765
         list_users_element(IsRoster, column, roster, {}),
766
         list_users_element(IsLast, column, timestamp, {}),
767
         list_users_element(IsLast, column, status, {})],
UNCOV
768
    Rows =
8✔
UNCOV
769
        [list_to_tuple(lists:flatten([make_command(echo,
24✔
770
                                                   R,
771
                                                   [{<<"sentence">>,
772
                                                     jid:encode(
773
                                                         jid:make(Username, Host))}],
774
                                                   [{only, raw_and_value},
775
                                                    {result_links,
776
                                                     [{sentence, user, Level, <<"">>}]}]),
777
                                      list_users_element(IsOffline,
778
                                                         row,
779
                                                         offline,
780
                                                         {R, Username, Host, Level}),
781
                                      list_users_element(IsMam,
782
                                                         row,
783
                                                         mam,
784
                                                         {R, Username, Host, Level}),
785
                                      list_users_element(IsRoster,
786
                                                         row,
787
                                                         roster,
788
                                                         {R, Username, Host, Level}),
789
                                      list_users_element(IsLast, row, last, {R, Username, Host})]))
UNCOV
790
         || Username <- Usernames],
8✔
UNCOV
791
    Table = make_table(PageSize, RPath, lists:flatten(Columns), Rows),
8✔
UNCOV
792
    Result =
8✔
793
        [RegisterEl,
794
         make_command(registered_users, R, [], [{only, presentation}]),
795
         list_users_element(IsOffline, presentation, offline, R),
796
         list_users_element(IsMam, presentation, mam, R),
797
         list_users_element(IsRoster, presentation, roster, R),
798
         list_users_element(IsLast, presentation, last, R),
799
         Table],
UNCOV
800
    lists:flatten(Result).
8✔
801

802
list_users_element(false, _, _, _) ->
803
    [];
×
804
list_users_element(_, column, offline, _) ->
UNCOV
805
    {<<"offline">>, right};
8✔
806
list_users_element(_, column, mam, _) ->
UNCOV
807
    {<<"mam">>, right};
8✔
808
list_users_element(_, column, roster, _) ->
UNCOV
809
    {<<"roster">>, right};
8✔
810
list_users_element(_, column, timestamp, _) ->
UNCOV
811
    {<<"timestamp">>, left};
8✔
812
list_users_element(_, column, status, _) ->
UNCOV
813
    {<<"status">>, left};
8✔
814
list_users_element(_, row, offline, {R, Username, Host, Level}) ->
UNCOV
815
    make_command(get_offline_count,
24✔
816
                 R,
817
                 [{<<"user">>, Username}, {<<"host">>, Host}],
818
                 [{only, raw_and_value},
819
                  {result_links,
820
                   [{value, arg_host, Level, <<"user/", Username/binary, "/queue/">>}]}]);
821
list_users_element(_, row, mam, {R, Username, Host, Level}) ->
UNCOV
822
    make_command(get_mam_count,
24✔
823
                 R,
824
                 [{<<"user">>, Username}, {<<"host">>, Host}],
825
                 [{only, raw_and_value},
826
                  {result_links,
827
                   [{value, arg_host, Level, <<"user/", Username/binary, "/mam/">>}]}]);
828
list_users_element(_, row, roster, {R, Username, Host, Level}) ->
UNCOV
829
    make_command(get_roster_count,
24✔
830
                 R,
831
                 [{<<"user">>, Username}, {<<"host">>, Host}],
832
                 [{only, raw_and_value},
833
                  {result_links,
834
                   [{value, arg_host, Level, <<"user/", Username/binary, "/roster/">>}]}]);
835
list_users_element(_, row, last, {R, Username, Host}) ->
UNCOV
836
    [?C(element(1,
24✔
837
                make_command_raw_value(get_last, R, [{<<"user">>, Username}, {<<"host">>, Host}]))),
838
     ?C(element(2,
839
                make_command_raw_value(get_last,
840
                                       R,
841
                                       [{<<"user">>, Username}, {<<"host">>, Host}])))];
842
list_users_element(_, presentation, offline, R) ->
UNCOV
843
    make_command(get_offline_count, R, [], [{only, presentation}]);
8✔
844
list_users_element(_, presentation, mam, R) ->
UNCOV
845
    make_command(get_mam_count, R, [], [{only, presentation}]);
8✔
846
list_users_element(_, presentation, roster, R) ->
UNCOV
847
    make_command(get_roster_count, R, [], [{only, presentation}]);
8✔
848
list_users_element(_, presentation, last, R) ->
UNCOV
849
    make_command(get_last, R, [], [{only, presentation}]).
8✔
850

851
list_users_diapason(Host, R, Usernames, N, RegisterEl) ->
852
    URLFunc = fun url_func/1,
×
853
    SUsers = [{Host, U} || U <- Usernames],
×
854
    NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1,
×
855
    M = trunc(N / NParts) + 1,
×
856
    FUsers =
×
857
        lists:flatmap(fun(K) ->
858
                         L = K + M - 1,
×
859
                         Last =
×
860
                             if L < N ->
861
                                    su_to_list(lists:nth(L, SUsers));
×
862
                                true ->
863
                                    su_to_list(lists:last(SUsers))
×
864
                             end,
865
                         Name =
×
866
                             <<(su_to_list(lists:nth(K, SUsers)))/binary,
867
                               $\s,
868
                               226,
869
                               128,
870
                               148,
871
                               $\s,
872
                               Last/binary>>,
873
                         [?AC(URLFunc({user_diapason, K, L}), Name), ?BR]
×
874
                      end,
875
                      lists:seq(1, N, M)),
876
    [RegisterEl,
877
     make_command(get_offline_count, R, [], [{only, presentation}]),
878
     ?AC(<<"top/offline/">>, <<"View Top Offline Queues">>),
879
     make_command(get_roster_count, R, [], [{only, presentation}]),
880
     ?AC(<<"top/roster/">>, <<"View Top Rosters">>),
881
     make_command(get_last, R, [], [{only, presentation}]),
882
     ?AC(<<"top/last/">>, <<"View Top-Oldest Last Activity">>),
883
     make_command(registered_users, R, [], [{only, presentation}])]
884
    ++ FUsers.
×
885

886
list_users_in_diapason(Host, Level, PageSize, RPath, R, Diap, RegisterEl) ->
887
    Usernames =
×
888
        case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of
889
            As when is_list(As) ->
890
                As;
×
891
            _ ->
892
                {Aser, Aerver} = R#request.us,
×
893
                ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied",
×
894
                          [Aser, Aerver]),
×
895
                []
×
896
        end,
897
    SUsers = lists:sort([{Host, U} || U <- Usernames]),
×
898
    [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
×
899
    N1 = binary_to_integer(S1),
×
900
    N2 = binary_to_integer(S2),
×
901
    Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
×
902
    Usernames2 = [U || {_, U} <- Sub],
×
903
    list_users(Host, Level, PageSize, RPath, R, Usernames2, RegisterEl).
×
904

905
list_users_top(Host, Level, PageSize, RPath, R, Operation, RegisterEl) ->
906
    Usernames =
×
907
        case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of
908
            As when is_list(As) ->
909
                As;
×
910
            _ ->
911
                {Aser, Aerver} = R#request.us,
×
912
                ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied",
×
913
                          [Aser, Aerver]),
×
914
                []
×
915
        end,
916
    {Command, Reverse} =
×
917
        case Operation of
918
            <<"roster">> ->
919
                {get_roster_count, true};
×
920
            <<"offline">> ->
921
                {get_offline_count, true};
×
922
            <<"last">> ->
923
                {get_last, false}
×
924
        end,
925
    UsernamesCounts =
×
926
        [{U,
×
927
          make_command(Command,
928
                       R,
929
                       [{<<"user">>, U}, {<<"host">>, Host}],
930
                       [{only, raw_value},
931
                        {result_links,
932
                         [{value, arg_host, Level, <<"user/", U/binary, "/roster/">>}]}])}
933
         || U <- Usernames],
×
934
    USorted = lists:keysort(2, UsernamesCounts),
×
935
    UReversed =
×
936
        case Reverse of
937
            true ->
938
                lists:reverse(USorted);
×
939
            false ->
940
                USorted
×
941
        end,
942
    Usernames2 = [U || {U, _} <- lists:sublist(UReversed, 100)],
×
943
    list_users(Host, Level, PageSize, RPath, R, Usernames2, RegisterEl).
×
944

945
get_lastactivity_menuitem_list(Server) ->
UNCOV
946
    case gen_mod:is_loaded(Server, mod_last) of
32✔
947
        true ->
UNCOV
948
            case mod_last_opt:db_type(Server) of
32✔
949
                mnesia ->
UNCOV
950
                    [{<<"last-activity">>, ?T("Last Activity")}];
8✔
951
                _ ->
UNCOV
952
                    []
24✔
953
            end;
954
        false ->
955
            []
×
956
    end.
957

958
us_to_list({User, Server}) ->
UNCOV
959
    jid:encode({User, Server, <<"">>}).
24✔
960

961
su_to_list({Server, User}) ->
962
    jid:encode({User, Server, <<"">>}).
×
963
%% @format-end
964

965
%%%==================================
966
%%%% last-activity
967

968
webadmin_host_last_activity(Host, Query, Lang) ->
969
    ?DEBUG("Query: ~p", [Query]),
×
970
    Month = case lists:keysearch(<<"period">>, 1, Query) of
×
971
                {value, {_, Val}} -> Val;
×
972
                _ -> <<"month">>
×
973
            end,
974
    Res = case lists:keysearch(<<"ordinary">>, 1, Query) of
×
975
              {value, {_, _}} ->
976
                  list_last_activity(Host, Lang, false, Month);
×
977
              _ -> list_last_activity(Host, Lang, true, Month)
×
978
          end,
979
       [?XAE(<<"form">>,
980
             [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
981
             [?CT(?T("Period: ")),
982
              ?XAE(<<"select">>, [{<<"name">>, <<"period">>}],
983
                   (lists:map(fun ({O, V}) ->
984
                                      Sel = if O == Month ->
×
985
                                                    [{<<"selected">>,
×
986
                                                      <<"selected">>}];
987
                                               true -> []
×
988
                                            end,
989
                                      ?XAC(<<"option">>,
×
990
                                           (Sel ++
991
                                                [{<<"value">>, O}]),
992
                                           V)
993
                              end,
994
                              [{<<"month">>, translate:translate(Lang, ?T("Last month"))},
995
                               {<<"year">>, translate:translate(Lang, ?T("Last year"))},
996
                               {<<"all">>,
997
                                translate:translate(Lang, ?T("All activity"))}]))),
998
              ?C(<<" ">>),
999
              ?INPUTT(<<"submit">>, <<"ordinary">>,
1000
                      ?T("Show Ordinary Table")),
1001
              ?C(<<" ">>),
1002
              ?INPUTT(<<"submit">>, <<"integral">>,
1003
                      ?T("Show Integral Table"))])]
1004
   ++ Res.
×
1005

1006
%%%==================================
1007
%%%% get_stats
1008

1009
user_info(User, Server, #request{q = Query, lang = Lang} = R) ->
UNCOV
1010
    LServer = jid:nameprep(Server),
24✔
UNCOV
1011
    US = {jid:nodeprep(User), LServer},
24✔
UNCOV
1012
    Res = user_parse_query(User, Server, Query),
24✔
UNCOV
1013
    UserItems = ejabberd_hooks:run_fold(webadmin_user,
24✔
1014
                                        LServer, [], [User, Server, R]),
UNCOV
1015
    Lasts = case gen_mod:is_loaded(Server, mod_last) of
24✔
1016
                true ->
UNCOV
1017
                    [make_command(get_last, R,
24✔
1018
                                  [{<<"user">>, User}, {<<"host">>, Server}],
1019
                                  []),
1020
                     make_command(set_last, R,
1021
                                  [{<<"user">>, User}, {<<"host">>, Server}],
1022
                                  [])];
1023
                false ->
1024
                    []
×
1025
            end,
1026
    [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("User ~ts"),
1027
                                                [us_to_list(US)])))]
UNCOV
1028
      ++
24✔
1029
      case Res of
1030
        ok -> [?XREST(?T("Submitted"))];
×
1031
        error -> [?XREST(?T("Bad format"))];
×
UNCOV
1032
        nothing -> []
24✔
1033
      end
1034
        ++
1035
        [?XAE(<<"form">>,
1036
              [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
1037
              ([make_command(user_sessions_info, R,
1038
                             [{<<"user">>, User}, {<<"host">>, Server}],
1039
                             [{result_links, [{node, node, 4, <<>>}]}]),
1040
                make_command(change_password, R,
1041
                             [{<<"user">>, User}, {<<"host">>, Server}],
1042
                             [{style, danger}])] ++
1043
                   Lasts ++
1044
                   UserItems ++
1045
                   [?P,
1046
                make_command(unregister, R,
1047
                             [{<<"user">>, User}, {<<"host">>, Server}],
1048
                             [{style, danger}])
1049
                    ]))].
1050

1051
user_parse_query(User, Server, Query) ->
UNCOV
1052
    lists:foldl(fun ({Action, _Value}, Acc)
24✔
1053
                        when Acc == nothing ->
UNCOV
1054
                        user_parse_query1(Action, User, Server, Query);
40✔
1055
                    ({_Action, _Value}, Acc) -> Acc
×
1056
                end,
1057
                nothing, Query).
1058

1059
user_parse_query1(Action, User, Server, Query) ->
UNCOV
1060
    case ejabberd_hooks:run_fold(webadmin_user_parse_query,
40✔
1061
                                 Server, [], [Action, User, Server, Query])
1062
        of
UNCOV
1063
      [] -> nothing;
40✔
1064
      Res -> Res
×
1065
    end.
1066

1067
list_last_activity(Host, Lang, Integral, Period) ->
1068
    TimeStamp = erlang:system_time(second),
×
1069
    case Period of
×
1070
      <<"all">> -> TS = 0, Days = infinity;
×
1071
      <<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
×
1072
      _ -> TS = TimeStamp - 31 * 86400, Days = 31
×
1073
    end,
1074
    case catch mnesia:dirty_select(last_activity,
×
1075
                                   [{{last_activity, {'_', Host}, '$1', '_'},
1076
                                     [{'>', '$1', TS}],
1077
                                     [{trunc,
1078
                                       {'/', {'-', TimeStamp, '$1'}, 86400}}]}])
1079
        of
1080
      {'EXIT', _Reason} -> [];
×
1081
      Vals ->
1082
          Hist = histogram(Vals, Integral),
×
1083
          if Hist == [] -> [?CT(?T("No Data"))];
×
1084
             true ->
1085
                 Left = if Days == infinity -> 0;
×
1086
                           true -> Days - length(Hist)
×
1087
                        end,
1088
                 Tail = if Integral ->
×
1089
                               lists:duplicate(Left, lists:last(Hist));
×
1090
                           true -> lists:duplicate(Left, 0)
×
1091
                        end,
1092
                 Max = lists:max(Hist),
×
1093
                 [?XAE(<<"ol">>,
×
1094
                       [{<<"id">>, <<"lastactivity">>},
1095
                        {<<"start">>, <<"0">>}],
1096
                       [?XAE(<<"li">>,
×
1097
                             [{<<"style">>,
1098
                               <<"width:",
1099
                                 (integer_to_binary(trunc(90 * V / Max)))/binary,
1100
                                 "%;">>}],
1101
                             [{xmlcdata, pretty_string_int(V)}])
1102
                        || V <- Hist ++ Tail])]
×
1103
          end
1104
    end.
1105

1106
histogram(Values, Integral) ->
1107
    histogram(lists:sort(Values), Integral, 0, 0, []).
×
1108

1109
histogram([H | T], Integral, Current, Count, Hist)
1110
    when Current == H ->
1111
    histogram(T, Integral, Current, Count + 1, Hist);
×
1112
histogram([H | _] = Values, Integral, Current, Count,
1113
          Hist)
1114
    when Current < H ->
1115
    if Integral ->
×
1116
           histogram(Values, Integral, Current + 1, Count,
×
1117
                     [Count | Hist]);
1118
       true ->
1119
           histogram(Values, Integral, Current + 1, 0,
×
1120
                     [Count | Hist])
1121
    end;
1122
histogram([], _Integral, _Current, Count, Hist) ->
1123
    if Count > 0 -> lists:reverse([Count | Hist]);
×
1124
       true -> lists:reverse(Hist)
×
1125
    end.
1126

1127
%%%==================================
1128
%%%% get_nodes
1129

1130
search_running_node(SNode) ->
1131
    RunningNodes = ejabberd_cluster:get_nodes(),
×
1132
    search_running_node(SNode, RunningNodes).
×
1133

1134
search_running_node(_, []) -> false;
×
1135
search_running_node(SNode, [Node | Nodes]) ->
1136
    case iolist_to_binary(atom_to_list(Node)) of
×
1137
      SNode -> Node;
×
1138
      _ -> search_running_node(SNode, Nodes)
×
1139
    end.
1140

1141
get_node(global, Node, [], #request{lang = Lang}) ->
1142
    Base = get_base_path(global, Node, 2),
×
1143
    BaseItems = [{<<"db">>, <<"Mnesia Tables">>},
×
1144
                 {<<"backup">>, <<"Mnesia Backup">>}],
1145
    MenuItems = make_menu_items(global, Node, Base, Lang, BaseItems),
×
1146
    [?XC(<<"h1">>,
1147
         (str:translate_and_format(Lang, ?T("Node ~p"), [Node])))]
1148
      ++
×
1149
        [?XE(<<"ul">>, MenuItems)];
1150
get_node(Host, Node, [], #request{lang = Lang}) ->
1151
    Base = get_base_path(Host, Node, 4),
×
1152
    MenuItems2 = make_menu_items(Host, Node, Base, Lang, []),
×
1153
    [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("Node ~p"), [Node]))),
×
1154
     ?XE(<<"ul">>, MenuItems2)];
1155

1156
get_node(global, Node, [<<"db">> | RPath], R) ->
1157
    PageTitle = <<"Mnesia Tables">>,
×
1158
    Title = ?XC(<<"h1">>, PageTitle),
×
1159
    Level = length(RPath),
×
1160
    [Title, ?BR | webadmin_db(Node, RPath, R, Level)];
×
1161

1162
get_node(global, Node, [<<"backup">>], #request{lang = Lang} = R) ->
1163
    Types = [{<<"#binary">>, <<"Binary">>},
×
1164
             {<<"#plaintext">>, <<"Plain Text">>},
1165
             {<<"#piefxis">>, <<"PIEXFIS (XEP-0227)">>},
1166
             {<<"#sql">>, <<"SQL">>},
1167
             {<<"#prosody">>, <<"Prosody">>},
1168
             {<<"#jabberd14">>, <<"jabberd 1.4">>}],
1169

1170
    [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("Backup of ~p"), [Node]))),
×
1171
     ?XCT(<<"p">>,
1172
          ?T("Please note that these options will "
1173
             "only backup the builtin Mnesia database. "
1174
             "If you are using the ODBC module, you "
1175
             "also need to backup your SQL database "
1176
             "separately.")),
1177
     ?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- Types]),
×
1178

1179
     ?X(<<"hr">>),
1180
     ?XAC(<<"h2">>, [{<<"id">>, <<"binary">>}], <<"Binary">>),
1181
     ?XCT(<<"p">>, ?T("Store binary backup:")),
1182
     ?XE(<<"blockquote">>, [make_command(backup, R)]),
1183
     ?XCT(<<"p">>, ?T("Restore binary backup immediately:")),
1184
     ?XE(<<"blockquote">>, [make_command(restore, R, [], [{style, danger}])]),
1185
     ?XCT(<<"p">>, ?T("Restore binary backup after next ejabberd "
1186
                      "restart (requires less memory):")),
1187
     ?XE(<<"blockquote">>, [make_command(install_fallback, R, [], [{style, danger}])]),
1188

1189
     ?X(<<"hr">>),
1190
     ?XAC(<<"h2">>, [{<<"id">>, <<"plaintext">>}], <<"Plain Text">>),
1191
     ?XCT(<<"p">>, ?T("Store plain text backup:")),
1192
     ?XE(<<"blockquote">>, [make_command(dump, R)]),
1193
     ?XCT(<<"p">>, ?T("Restore plain text backup immediately:")),
1194
     ?XE(<<"blockquote">>, [make_command(load, R, [], [{style, danger}])]),
1195

1196
     ?X(<<"hr">>),
1197
     ?XAC(<<"h2">>, [{<<"id">>, <<"piefxis">>}], <<"PIEFXIS (XEP-0227)">>),
1198
     ?XCT(<<"p">>, ?T("Import users data from a PIEFXIS file (XEP-0227):")),
1199
     ?XE(<<"blockquote">>, [make_command(import_piefxis, R)]),
1200
     ?XCT(<<"p">>, ?T("Export data of all users in the server to PIEFXIS files (XEP-0227):")),
1201
     ?XE(<<"blockquote">>, [make_command(export_piefxis, R)]),
1202
     ?XCT(<<"p">>, ?T("Export data of users in a host to PIEFXIS files (XEP-0227):")),
1203
     ?XE(<<"blockquote">>, [make_command(export_piefxis_host, R)]),
1204

1205
     ?X(<<"hr">>),
1206
     ?XAC(<<"h2">>, [{<<"id">>, <<"sql">>}], <<"SQL">>),
1207
     ?XCT(<<"p">>, ?T("Export all tables as SQL queries to a file:")),
1208
     ?XE(<<"blockquote">>, [make_command(export2sql, R)]),
1209

1210
     ?X(<<"hr">>),
1211
     ?XAC(<<"h2">>, [{<<"id">>, <<"prosody">>}], <<"Prosody">>),
1212
     ?XCT(<<"p">>, <<"Import data from Prosody:">>),
1213
     ?XE(<<"blockquote">>, [make_command(import_prosody, R)]),
1214

1215
     ?X(<<"hr">>),
1216
     ?XAC(<<"h2">>, [{<<"id">>, <<"jabberd14">>}], <<"jabberd 1.4">>),
1217
     ?XCT(<<"p">>, ?T("Import user data from jabberd14 spool file:")),
1218
     ?XE(<<"blockquote">>, [make_command(import_file, R)]),
1219
     ?XCT(<<"p">>, ?T("Import users data from jabberd14 spool directory:")),
1220
     ?XE(<<"blockquote">>, [make_command(import_dir, R)])
1221
    ];
1222
get_node(Host, Node, _NPath, Request) ->
1223
    Res = case Host of
×
1224
              global ->
1225
                  ejabberd_hooks:run_fold(webadmin_page_node, Host, [],
×
1226
                                          [Node, Request]);
1227
              _ ->
1228
                  ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [],
×
1229
                                          [Host, Node, Request])
1230
          end,
1231
    case Res of
×
1232
      [] -> [?XC(<<"h1">>, <<"Not Found">>)];
×
1233
      _ -> Res
×
1234
    end.
1235

1236
%%%==================================
1237
%%%% node parse
1238

1239
pretty_print_xml(El) ->
1240
    list_to_binary(pretty_print_xml(El, <<"">>)).
111,876✔
1241

1242
pretty_print_xml({xmlcdata, CData}, Prefix) ->
1243
    IsBlankCData = lists:all(
×
1244
                     fun($\f) -> true;
×
1245
                        ($\r) -> true;
×
1246
                        ($\n) -> true;
×
1247
                        ($\t) -> true;
×
1248
                        ($\v) -> true;
×
1249
                        ($\s) -> true;
×
1250
                        (_) -> false
×
1251
                     end, binary_to_list(CData)),
1252
    if IsBlankCData ->
×
1253
            [];
×
1254
       true ->
1255
            [Prefix, CData, $\n]
×
1256
    end;
1257
pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
1258
                        children = Els},
1259
                 Prefix) ->
1260
    [Prefix, $<, Name,
401,418✔
1261
     case Attrs of
1262
       [] -> [];
71,619✔
1263
       [{Attr, Val} | RestAttrs] ->
1264
           AttrPrefix = [Prefix,
329,799✔
1265
                         str:copies(<<" ">>, byte_size(Name) + 2)],
1266
           [$\s, Attr, $=, $', fxml:crypt(Val) | [$',
329,799✔
1267
                                                 lists:map(fun ({Attr1,
1268
                                                                 Val1}) ->
1269
                                                                   [$\n,
474,298✔
1270
                                                                    AttrPrefix,
1271
                                                                    Attr1, $=,
1272
                                                                    $',
1273
                                                                    fxml:crypt(Val1),
1274
                                                                    $']
1275
                                                           end,
1276
                                                           RestAttrs)]]
1277
     end,
1278
     if Els == [] -> <<"/>\n">>;
163,142✔
1279
        true ->
1280
            OnlyCData = lists:all(fun ({xmlcdata, _}) -> true;
238,276✔
1281
                                      (#xmlel{}) -> false
157,204✔
1282
                                  end,
1283
                                  Els),
1284
            if OnlyCData ->
238,276✔
1285
                   [$>, fxml:get_cdata(Els), $<, $/, Name, $>, $\n];
81,072✔
1286
               true ->
1287
                   [$>, $\n,
157,204✔
1288
                    lists:map(fun (E) ->
1289
                                      pretty_print_xml(E, [Prefix, <<"  ">>])
289,542✔
1290
                              end,
1291
                              Els),
1292
                    Prefix, $<, $/, Name, $>, $\n]
1293
            end
1294
     end].
1295

1296
url_func({user_diapason, From, To}) ->
1297
    <<"diapason/", (integer_to_binary(From))/binary, "-",
×
1298
      (integer_to_binary(To))/binary, "/">>.
1299

1300
last_modified() ->
1301
    {<<"Last-Modified">>,
×
1302
     <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
1303

1304
cache_control_public() ->
1305
    {<<"Cache-Control">>, <<"public">>}.
×
1306

1307
%% Transform 1234567890 into "1,234,567,890"
1308
pretty_string_int(Integer) when is_integer(Integer) ->
1309
    pretty_string_int(integer_to_binary(Integer));
×
1310
pretty_string_int(String) when is_binary(String) ->
1311
    {_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
×
1312
                                      {1, <<NewNumber, $,, Result/binary>>};
×
1313
                                  (NewNumber, {CountAcc, Result}) ->
1314
                                      {CountAcc + 1, <<NewNumber, Result/binary>>}
×
1315
                              end,
1316
                              {0, <<"">>}, lists:reverse(binary_to_list(String))),
1317
    Result.
×
1318

1319
%%%==================================
1320
%%%% mnesia table view
1321

1322
webadmin_node_db_table_page(Node, STable, PageNumber) ->
1323
    Table = misc:binary_to_atom(STable),
×
1324
    TInfo = ejabberd_cluster:call(Node, mnesia, table_info, [Table, all]),
×
1325
    {value, {storage_type, Type}} = lists:keysearch(storage_type, 1, TInfo),
×
1326
    {value, {size, Size}} = lists:keysearch(size, 1, TInfo),
×
1327
    PageSize = 500,
×
1328
    TableContentErl = get_table_content(Node, Table, Type, PageNumber, PageSize),
×
1329
    TableContent = str:format("~p", [TableContentErl]),
×
1330
    PagesLinks = build_elements_pages_list(Size, PageNumber, PageSize),
×
1331
    [?P] ++ PagesLinks ++ [?XC(<<"pre">>, TableContent)].
×
1332

1333
build_elements_pages_list(Size, PageNumber, PageSize) ->
1334
    PagesNumber = calculate_pages_number(Size, PageSize),
×
1335
    PagesSeq = lists:seq(1, PagesNumber),
×
1336
    PagesList = [?AC(<<"../", (integer_to_binary(N))/binary, "/">>,
×
1337
         <<(integer_to_binary(N))/binary, " ">>)
1338
     || N <- PagesSeq],
×
1339
    lists:keyreplace(
×
1340
        [?C(<<(integer_to_binary(PageNumber))/binary, " ">>)],
1341
        4,
1342
        PagesList,
1343
        ?C(<<" [", (integer_to_binary(PageNumber))/binary, "] ">>)).
1344

1345
calculate_pages_number(Size, PageSize) ->
1346
    Remainder = case Size rem PageSize of
×
1347
                   0 -> 0;
×
1348
                   _ -> 1
×
1349
               end,
1350
    case (Size div PageSize) + Remainder of
×
1351
        1 -> 0;
×
1352
        Res -> Res
×
1353
    end.
1354

1355
get_table_content(Node, Table, _Type, PageNumber, PageSize) ->
1356
    Keys1 = lists:sort(ejabberd_cluster:call(Node, mnesia, dirty_all_keys, [Table])),
×
1357
    FirstKeyPos = 1 - PageSize + PageNumber*PageSize,
×
1358
    Keys = lists:sublist(Keys1, FirstKeyPos, PageSize),
×
1359
    Res = [ejabberd_cluster:call(Node, mnesia, dirty_read, [Table, Key])
×
1360
           || Key <- Keys],
×
1361
    lists:flatten(Res).
×
1362

1363
%% @format-begin
1364

1365
webadmin_db(Node, [<<"table">>, TableName, <<"details">> | RPath], R, Level) ->
1366
    Service = <<"Mnesia Tables">>,
×
1367
    Breadcrumb =
×
1368
        make_breadcrumb({table_section, Level, Service, TableName, <<"Details">>, RPath}),
1369
    Get = [ejabberd_cluster:call(Node,
×
1370
                                 ejabberd_web_admin,
1371
                                 make_command,
1372
                                 [mnesia_table_details, R, [{<<"table">>, TableName}], []])],
1373
    Breadcrumb ++ Get;
×
1374
webadmin_db(Node,
1375
            [<<"table">>, TableName, <<"elements">>, PageNumber | RPath],
1376
            R,
1377
            Level) ->
1378
    Service = <<"Mnesia Tables">>,
×
1379
    Breadcrumb =
×
1380
        make_breadcrumb({table_section, Level, Service, TableName, <<"Elements">>, RPath}),
1381
    Get = [ejabberd_cluster:call(Node,
×
1382
                                 ejabberd_web_admin,
1383
                                 make_command,
1384
                                 [webadmin_node_db_table_page,
1385
                                  R,
1386
                                  [{<<"node">>, Node},
1387
                                   {<<"table">>, TableName},
1388
                                   {<<"page">>, PageNumber}],
1389
                                  []])],
1390
    Breadcrumb ++ Get;
×
1391
webadmin_db(Node, [<<"table">>, TableName, <<"change-storage">> | RPath], R, Level) ->
1392
    Service = <<"Mnesia Tables">>,
×
1393
    Breadcrumb =
×
1394
        make_breadcrumb({table_section, Level, Service, TableName, <<"Change Storage">>, RPath}),
1395
    Set = [ejabberd_cluster:call(Node,
×
1396
                                 ejabberd_web_admin,
1397
                                 make_command,
1398
                                 [mnesia_table_change_storage, R, [{<<"table">>, TableName}], []])],
1399
    Breadcrumb ++ Set;
×
1400
webadmin_db(Node, [<<"table">>, TableName, <<"clear">> | RPath], R, Level) ->
1401
    Service = <<"Mnesia Tables">>,
×
1402
    Breadcrumb =
×
1403
        make_breadcrumb({table_section, Level, Service, TableName, <<"Clear Content">>, RPath}),
1404
    Set = [ejabberd_cluster:call(Node,
×
1405
                                 ejabberd_web_admin,
1406
                                 make_command,
1407
                                 [mnesia_table_clear,
1408
                                  R,
1409
                                  [{<<"table">>, TableName}],
1410
                                  [{style, danger}]])],
1411
    Breadcrumb ++ Set;
×
1412
webadmin_db(Node, [<<"table">>, TableName, <<"destroy">> | RPath], R, Level) ->
1413
    Service = <<"Mnesia Tables">>,
×
1414
    Breadcrumb =
×
1415
        make_breadcrumb({table_section, Level, Service, TableName, <<"Destroy Table">>, RPath}),
1416
    Set = [ejabberd_cluster:call(Node,
×
1417
                                 ejabberd_web_admin,
1418
                                 make_command,
1419
                                 [mnesia_table_destroy,
1420
                                  R,
1421
                                  [{<<"table">>, TableName}],
1422
                                  [{style, danger}]])],
1423
    Breadcrumb ++ Set;
×
1424
webadmin_db(_Node, [<<"table">>, TableName | _RPath], _R, Level) ->
1425
    Service = <<"Mnesia Tables">>,
×
1426
    Breadcrumb = make_breadcrumb({table, Level, Service, TableName}),
×
1427
    MenuItems =
×
1428
        [{<<"details/">>, <<"Details">>},
1429
         {<<"elements/1/">>, <<"Elements">>},
1430
         {<<"change-storage/">>, <<"Change Storage">>},
1431
         {<<"clear/">>, <<"Clear Content">>},
1432
         {<<"destroy/">>, <<"Destroy Table">>}],
1433
    Get = [?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
1434
    Breadcrumb ++ Get;
×
1435
webadmin_db(Node, _RPath, R, _Level) ->
1436
    Service = <<"Mnesia Tables">>,
×
1437
    Breadcrumb = make_breadcrumb({service, Service}),
×
1438
    Get = [ejabberd_cluster:call(Node,
×
1439
                                 ejabberd_web_admin,
1440
                                 make_command,
1441
                                 [mnesia_list_tables,
1442
                                  R,
1443
                                  [],
1444
                                  [{result_links, [{name, mnesia_table, 3, <<"">>}]}]])],
1445
    Breadcrumb ++ Get.
×
1446

1447
make_breadcrumb({service, Service}) ->
1448
    make_breadcrumb([Service]);
×
1449
make_breadcrumb({table, Level, Service, Name}) ->
1450
    make_breadcrumb([{Level, Service}, separator, Name]);
×
1451
make_breadcrumb({table_section, Level, Service, Name, Section, RPath}) ->
1452
    make_breadcrumb([{Level, Service}, separator, {Level - 2, Name}, separator, Section
×
1453
                     | RPath]);
1454
make_breadcrumb(Elements) ->
1455
    lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
×
1456
                      Xmlel;
×
1457
                  (<<"sort">>) ->
1458
                      ?C(<<" +">>);
×
1459
                  (<<"page">>) ->
1460
                      ?C(<<" #">>);
×
1461
                  (separator) ->
1462
                      ?C(<<" > ">>);
×
1463
                  (Bin) when is_binary(Bin) ->
1464
                      ?C(Bin);
×
1465
                  ({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
1466
                      ?AC(binary:copy(<<"../">>, Level), Bin)
×
1467
              end,
1468
              Elements).
1469
%% @format-end
1470

1471
%%%==================================
1472
%%%% navigation menu
1473

1474
make_navigation(Host, Node, Username, Lang, JID, Level) ->
UNCOV
1475
    Menu = make_navigation_menu(Host, Node, Username, Lang, JID, Level),
40✔
UNCOV
1476
    make_menu_items(Lang, Menu).
40✔
1477

1478
-spec make_navigation_menu(Host::global | binary(),
1479
                           Node::cluster | atom(),
1480
                           Username::unspecified | binary(),
1481
                           Lang::binary(), JID::jid(), Level::integer()) ->
1482
    Menu::{URL::binary(), Title::binary()}
1483
    | {URL::binary(), Title::binary(), [Menu::any()]}.
1484
make_navigation_menu(Host, Node, Username, Lang, JID, Level) ->
UNCOV
1485
    HostNodeMenu = make_host_node_menu(Host, Node, Lang,
40✔
1486
                                       JID, Level),
UNCOV
1487
    HostUserMenu = make_host_user_menu(Host, Username, Lang,
40✔
1488
                                       JID, Level),
UNCOV
1489
    HostMenu = make_host_menu(Host, HostNodeMenu, HostUserMenu, Lang,
40✔
1490
                              JID, Level),
UNCOV
1491
    NodeMenu = make_node_menu(Host, Node, Lang, Level),
40✔
UNCOV
1492
    make_server_menu(HostMenu, NodeMenu, Lang, JID, Level).
40✔
1493

1494
make_menu_items(Host, Node, Base, Lang, Acc) ->
1495
    Place = case {Host, Node} of
×
1496
                {global, cluster} -> server;
×
1497
                {global, Node} -> {node, Node};
×
1498
                {Host, cluster} -> {host, Host};
×
1499
                {Host, Node} -> {hostnode, Host, Node}
×
1500
            end,
1501
    HookItems = get_menu_items_hook(Place, Lang),
×
1502
    Items = lists:keysort(2, HookItems ++ Acc),
×
1503
    make_menu_items(Lang, {Base, <<"">>, Items}).
×
1504

1505
make_host_node_menu(global, _, _Lang, _JID, _Level) ->
UNCOV
1506
    {<<"">>, <<"">>, []};
8✔
1507
make_host_node_menu(_, cluster, _Lang, _JID, _Level) ->
UNCOV
1508
    {<<"">>, <<"">>, []};
32✔
1509
make_host_node_menu(Host, Node, Lang, JID, Level) ->
1510
    HostNodeBase = get_base_path(Host, Node, Level),
×
1511
    HostNodeFixed = get_menu_items_hook({hostnode, Host, Node}, Lang),
×
1512
    HostNodeFixed2 = [Tuple
×
1513
                      || Tuple <- HostNodeFixed,
×
1514
                         is_allowed_path(Host, Tuple, JID)],
×
1515
    {HostNodeBase, iolist_to_binary(atom_to_list(Node)),
×
1516
     lists:keysort(2, HostNodeFixed2)}.
1517

1518
make_host_user_menu(global, _, _Lang, _JID, _Level) ->
UNCOV
1519
    {<<"">>, <<"">>, []};
8✔
1520
make_host_user_menu(_, unspecified, _Lang, _JID, _Level) ->
UNCOV
1521
    {<<"">>, <<"">>, []};
8✔
1522
make_host_user_menu(Host, Username, Lang, JID, Level) ->
UNCOV
1523
    HostNodeBase = get_base_path(Host, Username, Level),
24✔
UNCOV
1524
    HostNodeFixed = get_menu_items_hook({hostuser, Host, Username}, Lang),
24✔
UNCOV
1525
    HostNodeFixed2 = [Tuple
24✔
UNCOV
1526
                      || Tuple <- HostNodeFixed,
24✔
UNCOV
1527
                         is_allowed_path(Host, Tuple, JID)],
180✔
UNCOV
1528
    {HostNodeBase, Username,
24✔
1529
     lists:keysort(2, HostNodeFixed2)}.
1530

1531
make_host_menu(global, _HostNodeMenu, _HostUserMenu, _Lang, _JID, _Level) ->
UNCOV
1532
    {<<"">>, <<"">>, []};
8✔
1533
make_host_menu(Host, HostNodeMenu, HostUserMenu, Lang, JID, Level) ->
UNCOV
1534
    HostBase = get_base_path(Host, cluster, Level),
32✔
UNCOV
1535
    HostFixed = [{<<"users">>, ?T("Users"), HostUserMenu},
32✔
1536
                 {<<"online-users">>, ?T("Online Users")}],
UNCOV
1537
    HostFixedAdditional =
32✔
1538
                  get_lastactivity_menuitem_list(Host) ++
1539
                    [{<<"nodes">>, ?T("Nodes"), HostNodeMenu}]
1540
                      ++ get_menu_items_hook({host, Host}, Lang),
UNCOV
1541
    HostFixedAll = HostFixed ++ lists:keysort(2, HostFixedAdditional),
32✔
UNCOV
1542
    HostFixed2 = [Tuple
32✔
UNCOV
1543
                  || Tuple <- HostFixedAll,
32✔
UNCOV
1544
                     is_allowed_path(Host, Tuple, JID)],
136✔
UNCOV
1545
    {HostBase, Host, HostFixed2}.
32✔
1546

1547
make_node_menu(_Host, cluster, _Lang, _Level) ->
UNCOV
1548
    {<<"">>, <<"">>, []};
40✔
1549
make_node_menu(global, Node, Lang, Level) ->
1550
    NodeBase = get_base_path(global, Node, Level),
×
1551
    NodeFixed = [{<<"db">>, <<"Mnesia Tables">>},
×
1552
                 {<<"backup">>, <<"Mnesia Backup">>}]
1553
                  ++ get_menu_items_hook({node, Node}, Lang),
1554
    {NodeBase, iolist_to_binary(atom_to_list(Node)),
×
1555
     lists:keysort(2, NodeFixed)};
1556
make_node_menu(_Host, _Node, _Lang, _Level) ->
1557
    {<<"">>, <<"">>, []}.
×
1558

1559
make_server_menu(HostMenu, NodeMenu, Lang, JID, Level) ->
UNCOV
1560
    Base = get_base_path(global, cluster, Level),
48✔
UNCOV
1561
    Fixed = [{<<"vhosts">>, ?T("Virtual Hosts"), HostMenu},
48✔
1562
             {<<"nodes">>, ?T("Nodes"), NodeMenu}],
UNCOV
1563
    FixedAdditional = get_menu_items_hook(server, Lang),
48✔
UNCOV
1564
    FixedAll = Fixed ++ lists:keysort(2, FixedAdditional),
48✔
UNCOV
1565
    Fixed2 = [Tuple
48✔
UNCOV
1566
              || Tuple <- FixedAll,
48✔
UNCOV
1567
                 is_allowed_path(global, Tuple, JID)],
336✔
UNCOV
1568
    {Base, <<"">>, Fixed2}.
48✔
1569

1570
get_menu_items_hook({hostnode, Host, Node}, Lang) ->
1571
    ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host,
×
1572
                            [], [Host, Node, Lang]);
1573
get_menu_items_hook({hostuser, Host, Username}, Lang) ->
UNCOV
1574
    ejabberd_hooks:run_fold(webadmin_menu_hostuser, Host,
24✔
1575
                            [], [Host, Username, Lang]);
1576
get_menu_items_hook({host, Host}, Lang) ->
UNCOV
1577
    ejabberd_hooks:run_fold(webadmin_menu_host, Host, [],
32✔
1578
                            [Host, Lang]);
1579
get_menu_items_hook({node, Node}, Lang) ->
1580
    ejabberd_hooks:run_fold(webadmin_menu_node, [],
×
1581
                            [Node, Lang]);
1582
get_menu_items_hook(server, Lang) ->
UNCOV
1583
    ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
48✔
1584

1585
-spec make_menu_items(Lang::binary(),
1586
                      {MURI::binary(), MName::binary(),
1587
                       Items::[{IURI::binary(), IName::binary()}
1588
                               | {IURI::binary(), IName::binary(), Menu::any()}]}) ->
1589
    [xmlel()].
1590
make_menu_items(Lang, Menu) ->
UNCOV
1591
    lists:reverse(make_menu_items2(Lang, 1, Menu)).
40✔
1592

1593
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
UNCOV
1594
    Res = case MName of
184✔
UNCOV
1595
            <<"">> -> [];
128✔
UNCOV
1596
            _ -> [make_menu_item(header, Deep, MURI, MName, Lang)]
56✔
1597
          end,
UNCOV
1598
    make_menu_items2(Lang, Deep, Menu, Res).
184✔
1599

UNCOV
1600
make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res;
184✔
1601
make_menu_items2(Lang, Deep,
1602
                 {MURI, MName, [Item | Items]}, Res) ->
UNCOV
1603
    Res2 = case Item of
596✔
1604
             {IURI, IName} ->
UNCOV
1605
                 [make_menu_item(item, Deep,
452✔
1606
                                 <<MURI/binary, IURI/binary, "/">>, IName, Lang)
1607
                  | Res];
1608
             {IURI, IName, SubMenu} ->
UNCOV
1609
                 ResTemp = [make_menu_item(item, Deep,
144✔
1610
                                           <<MURI/binary, IURI/binary, "/">>,
1611
                                           IName, Lang)
1612
                            | Res],
UNCOV
1613
                 ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu),
144✔
UNCOV
1614
                 ResSubMenu ++ ResTemp
144✔
1615
           end,
UNCOV
1616
    make_menu_items2(Lang, Deep, {MURI, MName, Items},
596✔
1617
                     Res2).
1618

1619
make_menu_item(header, 1, URI, Name, _Lang) ->
1620
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}],
×
1621
              [?AC(URI, Name)])]);
1622
make_menu_item(header, 2, URI, Name, _Lang) ->
UNCOV
1623
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}],
32✔
1624
              [?AC(URI, Name)])]);
1625
make_menu_item(header, 3, URI, Name, _Lang) ->
UNCOV
1626
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}],
24✔
1627
              [?AC(URI, Name)])]);
1628
make_menu_item(item, 1, URI, Name, Lang) ->
UNCOV
1629
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}],
280✔
1630
              [?ACT(URI, Name)])]);
1631
make_menu_item(item, 2, URI, Name, Lang) ->
UNCOV
1632
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}],
136✔
1633
              [?ACT(URI, Name)])]);
1634
make_menu_item(item, 3, URI, Name, Lang) ->
UNCOV
1635
    ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}],
180✔
1636
              [?ACT(URI, Name)])]).
1637

1638
any_rules_allowed(Host, Access, Entity) ->
UNCOV
1639
    lists:any(
1,212✔
1640
      fun(Rule) ->
UNCOV
1641
              allow == acl:match_rule(Host, Rule, Entity)
1,212✔
1642
      end, Access).
1643

1644
%%%==================================
1645
%%%% login box
1646

1647
%%% @format-begin
1648

1649
make_login_items(#request{us = {Username, Host}} = R, Level, JID) ->
UNCOV
1650
    UserBin =
40✔
1651
        jid:encode(
1652
            jid:make(Username, Host, <<"">>)),
UNCOV
1653
    UserEl =
40✔
1654
        make_command(echo,
1655
                     R,
1656
                     [{<<"sentence">>, UserBin}],
1657
                     [{only, value}, {result_links, [{sentence, user, Level, <<"">>}]}]),
UNCOV
1658
    UserEl2 =
40✔
1659
        case UserEl of
1660
            {xmlcdata, <<>>} ->
1661
                {xmlel, <<"code">>, [], [{xmlel, <<"a">>, [], [{xmlcdata, UserBin}]}]};
×
1662
            _ ->
UNCOV
1663
                UserEl
40✔
1664
        end,
UNCOV
1665
    MenuPost1 =
40✔
1666
        case ejabberd_hooks:run_fold(webadmin_menu_system_post, [], [R, Level]) of
1667
            [] ->
UNCOV
1668
                [];
40✔
1669
            PostElements ->
1670
                [{xmlel,
×
1671
                  <<"div">>,
1672
                  [{<<"id">>, <<"navitemlogin">>}],
1673
                  [?XE(<<"ul">>, PostElements)]}]
1674
        end,
UNCOV
1675
    MenuInside1 = ejabberd_hooks:run_fold(webadmin_menu_system_inside, [], [R, Level]),
40✔
UNCOV
1676
    {MenuInside, MenuPost} =
40✔
1677
        case list_vhosts_allowed(JID) of
1678
            [] ->
1679
                {[], []};
×
1680
            [_ | _] ->
UNCOV
1681
                {MenuInside1, MenuPost1}
40✔
1682
        end,
UNCOV
1683
    [{xmlel,
40✔
1684
      <<"li">>,
1685
      [{<<"id">>, <<"navitemlogin-start">>}],
1686
      [{xmlel,
1687
        <<"div">>,
1688
        [{<<"id">>, <<"navitemlogin">>}],
1689
        [?XE(<<"ul">>,
1690
             [?LI([?C(unicode:characters_to_binary("👤")), UserEl2]),
1691
              ?LI([?C(unicode:characters_to_binary("🏭")),
1692
                   make_command(echo,
1693
                                R,
1694
                                [{<<"sentence">>, misc:atom_to_binary(node())}],
1695
                                [{only, value},
1696
                                 {result_links, [{sentence, node, Level, <<"">>}]}])])]
1697
             ++ MenuInside
1698
             ++ [?LI([?C(unicode:characters_to_binary("📤")),
1699
                      ?AC(<<(binary:copy(<<"../">>, Level))/binary, "logout/">>,
1700
                          <<"Logout">>)])])]}]
1701
      ++ MenuPost}].
1702

1703
%%%==================================
1704
%%%% menu_system
1705

1706
-spec make_menu_system(atom(), string(), string(), string()) -> [xmlel()].
1707
make_menu_system(Module, Icon, Text, Append) ->
UNCOV
1708
    [make_menu_system_el(Icon, Text, Append, UrlTuple) || UrlTuple <- get_urls(Module)].
40✔
1709

1710
get_urls(Module) ->
UNCOV
1711
    Urls = ejabberd_http:get_auto_urls(any, Module),
40✔
UNCOV
1712
    Host = ejabberd_config:get_myname(),
40✔
UNCOV
1713
    [{Tls, misc:expand_keyword(<<"@HOST@">>, Url, Host)} || {Tls, Url} <- Urls].
40✔
1714

1715
-spec make_menu_system_el(string(), string(), string(), {boolean(), binary()}) -> xmlel().
1716
make_menu_system_el(Icon, Text, Append, {ThisTls, Url}) ->
1717
    LockBinary =
×
1718
        case ThisTls of
1719
            true ->
1720
                unicode:characters_to_binary("🔒");
×
1721
            false ->
1722
                unicode:characters_to_binary("❗")
×
1723
        end,
1724
    AppendBin = iolist_to_binary(Append),
×
1725
    {ok, _Scheme, _UserInfo, _Host, _Port, Path, _Query} = misc:uri_parse(Url),
×
1726
    TextParsed = string:replace(Text, "{URLPATH}", Path),
×
1727
    ?LI([?C(<<(unicode:characters_to_binary(Icon))/binary, LockBinary/binary>>),
×
1728
         ?XAE(<<"a">>,
1729
              [{<<"href">>, <<Url/binary, AppendBin/binary>>}, {<<"target">>, <<"_blank">>}],
1730
              [?C(unicode:characters_to_binary(TextParsed))])]).
1731

1732
%%%==================================
1733

1734
%%%% make_command: API
1735

1736
-spec make_command(Name :: atom(), Request :: http_request()) -> xmlel().
1737
make_command(Name, Request) ->
UNCOV
1738
    make_command2(Name, Request, [], []).
8✔
1739

1740
-spec make_command(Name :: atom(),
1741
                   Request :: http_request(),
1742
                   BaseArguments :: [{ArgName :: binary(), ArgValue :: binary()}],
1743
                   [Option]) ->
1744
                      xmlel() | {xmlcdata, binary()} | {raw_and_value, any(), xmlel()}
1745
    when Option ::
1746
             {only, presentation | without_presentation | button | result | value | raw_and_value} |
1747
             {input_name_append, [binary()]} |
1748
             {force_execution, boolean()} |
1749
             {table_options, {PageSize :: integer(), RemainingPath :: [binary()]}} |
1750
             {result_named, boolean()} |
1751
             {result_links,
1752
              [{ResultName :: atom(),
1753
                LinkType :: host | node | user | room | shared_roster | arg_host | paragraph,
1754
                Level :: integer(),
1755
                Append :: binary()}]} |
1756
             {style, normal | danger}.
1757
make_command(Name, Request, BaseArguments, Options) ->
UNCOV
1758
    make_command2(Name, Request, BaseArguments, Options).
416✔
1759

1760
-spec make_command_raw_value(Name :: atom(),
1761
                             Request :: http_request(),
1762
                             BaseArguments :: [{ArgName :: binary(), ArgValue :: binary()}]) ->
1763
                                any().
1764
make_command_raw_value(Name, Request, BaseArguments) ->
UNCOV
1765
    make_command2(Name, Request, BaseArguments, [{only, raw_value}]).
56✔
1766

1767
%%%==================================
1768
%%%% make_command: main
1769

1770
-spec make_command2(Name :: atom(),
1771
                    Request :: http_request(),
1772
                    BaseArguments :: [{ArgName :: binary(), ArgValue :: binary()}],
1773
                    [Option]) ->
1774
                       xmlel() | any()
1775
    when Option ::
1776
             {only,
1777
              presentation |
1778
              without_presentation |
1779
              button |
1780
              result |
1781
              value |
1782
              raw_value |
1783
              raw_and_value} |
1784
             {input_name_append, [binary()]} |
1785
             {force_execution, boolean() | undefined} |
1786
             {table_options, {PageSize :: integer(), RemainingPath :: [binary()]}} |
1787
             {result_named, boolean()} |
1788
             {result_links,
1789
              [{ResultName :: atom(),
1790
                LinkType :: host | node | user | room | shared_roster | arg_host | paragraph,
1791
                Level :: integer(),
1792
                Append :: binary()}]} |
1793
             {style, normal | danger}.
1794
make_command2(Name, Request, BaseArguments, Options) ->
UNCOV
1795
    Only = proplists:get_value(only, Options, all),
480✔
UNCOV
1796
    ForceExecution = proplists:get_value(force_execution, Options, undefined),
480✔
UNCOV
1797
    InputNameAppend = proplists:get_value(input_name_append, Options, []),
480✔
UNCOV
1798
    Resultnamed = proplists:get_value(result_named, Options, false),
480✔
UNCOV
1799
    ResultLinks = proplists:get_value(result_links, Options, []),
480✔
UNCOV
1800
    TO = proplists:get_value(table_options, Options, {999999, []}),
480✔
UNCOV
1801
    Style = proplists:get_value(style, Options, normal),
480✔
UNCOV
1802
    #request{us = {RUser, RServer}, ip = RIp} = Request,
480✔
UNCOV
1803
    CallerInfo =
480✔
1804
        #{usr => {RUser, RServer, <<"">>},
1805
          ip => RIp,
1806
          caller_host => RServer,
1807
          caller_module => ?MODULE},
UNCOV
1808
    try {ejabberd_commands:get_command_definition(Name),
480✔
1809
         ejabberd_access_permissions:can_access(Name, CallerInfo)}
1810
    of
1811
        {C, allow} ->
UNCOV
1812
            make_command2(Name,
480✔
1813
                          Request,
1814
                          CallerInfo,
1815
                          BaseArguments,
1816
                          C,
1817
                          Only,
1818
                          ForceExecution,
1819
                          InputNameAppend,
1820
                          Resultnamed,
1821
                          ResultLinks,
1822
                          Style,
1823
                          TO);
1824
        {_C, deny} ->
1825
            ?DEBUG("Blocked access to command ~p for~n CallerInfo: ~p", [Name, CallerInfo]),
×
1826
            ?C(<<"">>)
×
1827
    catch
1828
        A:B ->
1829
            ?INFO_MSG("Problem preparing command ~p: ~p", [Name, {A, B}]),
×
1830
            ?C(<<"">>)
×
1831
    end.
1832

1833
make_command2(Name,
1834
              Request,
1835
              CallerInfo,
1836
              BaseArguments,
1837
              C,
1838
              Only,
1839
              ForceExecution,
1840
              InputNameAppend,
1841
              Resultnamed,
1842
              ResultLinks,
1843
              Style,
1844
              TO) ->
UNCOV
1845
    {ArgumentsFormat, _Rename, ResultFormatApi} = ejabberd_commands:get_command_format(Name),
480✔
UNCOV
1846
    Method =
480✔
1847
        case {ForceExecution, ResultFormatApi} of
1848
            {true, _} ->
1849
                auto;
×
1850
            {false, _} ->
1851
                manual;
×
1852
            {_, {_, rescode}} ->
UNCOV
1853
                manual;
48✔
1854
            {_, {_, restuple}} ->
UNCOV
1855
                manual;
32✔
1856
            _ ->
UNCOV
1857
                auto
400✔
1858
        end,
UNCOV
1859
    PresentationEls = make_command_presentation(Name, C#ejabberd_commands.tags),
480✔
UNCOV
1860
    Query = Request#request.q,
480✔
UNCOV
1861
    {ArgumentsUsed1, ExecRes} =
480✔
1862
        execute_command(Name,
1863
                        Query,
1864
                        BaseArguments,
1865
                        Method,
1866
                        ArgumentsFormat,
1867
                        CallerInfo,
1868
                        InputNameAppend),
UNCOV
1869
    ArgumentsFormatDetailed =
480✔
1870
        add_arguments_details(ArgumentsFormat,
1871
                              C#ejabberd_commands.args_desc,
1872
                              C#ejabberd_commands.args_example),
UNCOV
1873
    ArgumentsEls =
480✔
1874
        make_command_arguments(Name,
1875
                               Query,
1876
                               Only,
1877
                               Method,
1878
                               Style,
1879
                               ArgumentsFormatDetailed,
1880
                               BaseArguments,
1881
                               InputNameAppend),
UNCOV
1882
    Automated =
480✔
1883
        case ArgumentsEls of
1884
            [] ->
UNCOV
1885
                true;
352✔
1886
            _ ->
UNCOV
1887
                false
128✔
1888
        end,
UNCOV
1889
    ArgumentsUsed =
480✔
1890
        (catch lists:zip(
UNCOV
1891
                   lists:map(fun({A, _}) -> A end, ArgumentsFormat), ArgumentsUsed1)),
912✔
UNCOV
1892
    ResultEls =
480✔
1893
        make_command_result(ExecRes,
1894
                            ArgumentsUsed,
1895
                            ResultFormatApi,
1896
                            Automated,
1897
                            Resultnamed,
1898
                            ResultLinks,
1899
                            TO),
UNCOV
1900
    make_command3(Only, ExecRes, PresentationEls, ArgumentsEls, ResultEls).
480✔
1901

1902
make_command3(presentation, _ExecRes, PresentationEls, _ArgumentsEls, _ResultEls) ->
UNCOV
1903
    ?XAE(<<"p">>, [{<<"class">>, <<"api">>}], PresentationEls);
40✔
1904
make_command3(button, _ExecRes, _PresentationEls, [Button], _ResultEls) ->
1905
    Button;
×
1906
make_command3(result,
1907
              _ExecRes,
1908
              _PresentationEls,
1909
              _ArgumentsEls,
1910
              [{xmlcdata, _}, Xmlel]) ->
1911
    ?XAE(<<"p">>, [{<<"class">>, <<"api">>}], [Xmlel]);
×
1912
make_command3(value, _ExecRes, _PresentationEls, _ArgumentsEls, [{xmlcdata, _}, Xmlel]) ->
1913
    Xmlel;
×
1914
make_command3(value,
1915
              _ExecRes,
1916
              _PresentationEls,
1917
              _ArgumentsEls,
1918
              [{xmlel, _, _, _} = Xmlel]) ->
UNCOV
1919
    Xmlel;
80✔
1920
make_command3(raw_and_value,
1921
              ExecRes,
1922
              _PresentationEls,
1923
              _ArgumentsEls,
1924
              [{xmlel, _, _, _} = Xmlel]) ->
UNCOV
1925
    {raw_and_value, ExecRes, Xmlel};
96✔
1926
make_command3(raw_value, ExecRes, _PresentationEls, _ArgumentsEls, _ResultEls) ->
UNCOV
1927
    ExecRes;
56✔
1928
make_command3(without_presentation,
1929
              _ExecRes,
1930
              _PresentationEls,
1931
              ArgumentsEls,
1932
              ResultEls) ->
1933
    ?XAE(<<"p">>,
×
1934
         [{<<"class">>, <<"api">>}],
1935
         [?XE(<<"blockquote">>, ArgumentsEls ++ ResultEls)]);
1936
make_command3(all, _ExecRes, PresentationEls, ArgumentsEls, ResultEls) ->
UNCOV
1937
    ?XAE(<<"p">>,
208✔
1938
         [{<<"class">>, <<"api">>}],
1939
         PresentationEls ++ [?XE(<<"blockquote">>, ArgumentsEls ++ ResultEls)]).
1940

1941
add_arguments_details(ArgumentsFormat, Descriptions, none) ->
UNCOV
1942
    add_arguments_details(ArgumentsFormat, Descriptions, []);
168✔
1943
add_arguments_details(ArgumentsFormat, none, Examples) ->
UNCOV
1944
    add_arguments_details(ArgumentsFormat, [], Examples);
168✔
1945
add_arguments_details(ArgumentsFormat, Descriptions, Examples) ->
UNCOV
1946
    lists_zipwith3(fun({A, B}, C, D) -> {A, B, C, D} end,
480✔
1947
                   ArgumentsFormat,
1948
                   Descriptions,
1949
                   Examples,
1950
                   {pad, {none, "", ""}}).
1951

1952
-ifdef(OTP_BELOW_26).
1953

1954
lists_zipwith3(Combine, List1, List2, List3, {pad, {DefaultX, DefaultY, DefaultZ}}) ->
1955
    lists_zipwith3(Combine, List1, List2, List3, DefaultX, DefaultY, DefaultZ, []).
1956

1957
lists_zipwith3(_Combine, [], [], [], _DefaultX, _DefaultY, _DefaultZ, Res) ->
1958
    lists:reverse(Res);
1959
lists_zipwith3(Combine,
1960
               [E1 | List1],
1961
               [E2 | List2],
1962
               [E3 | List3],
1963
               DefX,
1964
               DefY,
1965
               DefZ,
1966
               Res) ->
1967
    E123 = Combine(E1, E2, E3),
1968
    lists_zipwith3(Combine, List1, List2, List3, DefX, DefY, DefZ, [E123 | Res]);
1969
lists_zipwith3(Combine, [E1 | List1], [], [], DefX, DefY, DefZ, Res) ->
1970
    E123 = Combine(E1, DefY, DefZ),
1971
    lists_zipwith3(Combine, List1, [], [], DefX, DefY, DefZ, [E123 | Res]);
1972
lists_zipwith3(Combine, [E1 | List1], [], [E3 | List3], DefX, DefY, DefZ, Res) ->
1973
    E123 = Combine(E1, DefY, E3),
1974
    lists_zipwith3(Combine, List1, [], List3, DefX, DefY, DefZ, [E123 | Res]);
1975
lists_zipwith3(Combine, [E1 | List1], [E2 | List2], [], DefX, DefY, DefZ, Res) ->
1976
    E123 = Combine(E1, E2, DefZ),
1977
    lists_zipwith3(Combine, List1, List2, [], DefX, DefY, DefZ, [E123 | Res]).
1978

1979
-endif.
1980

1981
-ifndef(OTP_BELOW_26).
1982

1983
lists_zipwith3(Combine, List1, List2, List3, How) ->
UNCOV
1984
    lists:zipwith3(Combine, List1, List2, List3, How).
480✔
1985

1986
-endif.
1987

1988
%%%==================================
1989
%%%% make_command: presentation
1990

1991
make_command_presentation(Name, Tags) ->
UNCOV
1992
    NameBin = misc:atom_to_binary(Name),
480✔
UNCOV
1993
    NiceNameBin = nice_this(Name),
480✔
UNCOV
1994
    Text = ejabberd_ctl:get_usage_command(atom_to_list(Name), 100, false, 1000000),
480✔
UNCOV
1995
    AnchorLink = [?ANCHORL(NameBin)],
480✔
UNCOV
1996
    MaybeDocsLink =
480✔
1997
        case lists:member(internal, Tags) of
1998
            true ->
UNCOV
1999
                [];
104✔
2000
            false ->
UNCOV
2001
                [?GL(<<"developer/ejabberd-api/admin-api/#", NameBin/binary>>, NameBin)]
376✔
2002
        end,
UNCOV
2003
    [?XE(<<"details">>,
480✔
2004
         [?XAE(<<"summary">>, [{<<"id">>, NameBin}], [?XC(<<"strong">>, NiceNameBin)])]
2005
         ++ MaybeDocsLink
2006
         ++ AnchorLink
2007
         ++ [?XC(<<"pre">>, list_to_binary(Text))])].
2008

2009
nice_this(This, integer) ->
UNCOV
2010
    {nice_this(This), right};
72✔
2011
nice_this(This, _Format) ->
UNCOV
2012
    nice_this(This).
144✔
2013

2014
-spec nice_this(This :: atom() | string() | binary()) -> NiceThis :: binary().
2015
nice_this(This) when is_atom(This) ->
UNCOV
2016
    nice_this(atom_to_list(This));
2,376✔
2017
nice_this(This) when is_binary(This) ->
UNCOV
2018
    nice_this(binary_to_list(This));
490✔
2019
nice_this(This) when is_list(This) ->
UNCOV
2020
    list_to_binary(lists:append([string:titlecase(Word)
2,866✔
UNCOV
2021
                                 || Word <- string:replace(This, "_", " ", all)])).
2,866✔
2022

2023
-spec long_this(These :: [This :: atom()]) -> Long :: binary().
2024
long_this(These) ->
UNCOV
2025
    list_to_binary(lists:join($/, [atom_to_list(This) || This <- These])).
1,080✔
2026

2027
%%%==================================
2028
%%%% make_command: arguments
2029

2030
make_command_arguments(Name,
2031
                       Query,
2032
                       Only,
2033
                       Method,
2034
                       Style,
2035
                       ArgumentsFormat,
2036
                       BaseArguments,
2037
                       InputNameAppend) ->
UNCOV
2038
    ArgumentsFormat2 = remove_base_arguments(ArgumentsFormat, BaseArguments),
480✔
UNCOV
2039
    ArgumentsFields = make_arguments_fields(Name, Query, ArgumentsFormat2),
480✔
UNCOV
2040
    Button = make_button_element(Name, Method, Style, InputNameAppend),
480✔
UNCOV
2041
    ButtonElement =
480✔
2042
        ?XE(<<"tr">>,
2043
            [?X(<<"td">>), ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], [Button])]),
UNCOV
2044
    case {(ArgumentsFields /= []) or (Method == manual), Only} of
480✔
2045
        {false, _} ->
UNCOV
2046
            [];
352✔
2047
        {true, button} ->
2048
            [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [Button])];
×
2049
        {true, _} ->
UNCOV
2050
            [?XAE(<<"form">>,
128✔
2051
                  [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2052
                  [?XE(<<"table">>, ArgumentsFields ++ [ButtonElement])])]
2053
    end.
2054

2055
remove_base_arguments(ArgumentsFormat, BaseArguments) ->
UNCOV
2056
    lists:filter(fun({ArgName, _ArgFormat, _ArgDesc, _ArgExample}) ->
480✔
UNCOV
2057
                    not
912✔
2058
                        lists:keymember(
2059
                            misc:atom_to_binary(ArgName), 1, BaseArguments)
2060
                 end,
2061
                 ArgumentsFormat).
2062

2063
make_button_element(Name, _, Style, InputNameAppend) ->
UNCOV
2064
    Id = term_to_id(InputNameAppend),
480✔
UNCOV
2065
    NameBin = <<(misc:atom_to_binary(Name))/binary, Id/binary>>,
480✔
UNCOV
2066
    NiceNameBin = nice_this(Name),
480✔
UNCOV
2067
    case Style of
480✔
2068
        danger ->
UNCOV
2069
            ?INPUTD(<<"submit">>, NameBin, NiceNameBin);
48✔
2070
        _ ->
UNCOV
2071
            ?INPUT(<<"submit">>, NameBin, NiceNameBin)
432✔
2072
    end.
2073

2074
make_arguments_fields(Name, Query, ArgumentsFormat) ->
UNCOV
2075
    lists:map(fun({ArgName, ArgFormat, _ArgDescription, ArgExample}) ->
480✔
UNCOV
2076
                 ArgExampleBin = format_result(ArgExample, {ArgName, ArgFormat}),
168✔
UNCOV
2077
                 ArgNiceNameBin = nice_this(ArgName),
168✔
UNCOV
2078
                 ArgLongNameBin = long_this([Name, ArgName]),
168✔
UNCOV
2079
                 ArgValue =
168✔
2080
                     case lists:keysearch(ArgLongNameBin, 1, Query) of
2081
                         {value, {ArgLongNameBin, V}} ->
UNCOV
2082
                             V;
24✔
2083
                         _ ->
UNCOV
2084
                             <<"">>
144✔
2085
                     end,
UNCOV
2086
                 ?XE(<<"tr">>,
168✔
2087
                     [?XC(<<"td">>, <<ArgNiceNameBin/binary, ":">>),
2088
                      ?XE(<<"td">>,
2089
                          [?INPUTPH(<<"text">>, ArgLongNameBin, ArgValue, ArgExampleBin)])])
2090
              end,
2091
              ArgumentsFormat).
2092

2093
%%%==================================
2094
%%%% make_command: execute
2095

2096
execute_command(Name,
2097
                Query,
2098
                BaseArguments,
2099
                Method,
2100
                ArgumentsFormat,
2101
                CallerInfo,
2102
                InputNameAppend) ->
UNCOV
2103
    try Args = prepare_arguments(Name, BaseArguments ++ Query, ArgumentsFormat),
480✔
UNCOV
2104
        {Args,
480✔
2105
         execute_command2(Name, Query, Args, Method, ArgumentsFormat, CallerInfo, InputNameAppend)}
2106
    of
2107
        R ->
UNCOV
2108
            R
480✔
2109
    catch
2110
        A:E ->
2111
            {error, {A, E}}
×
2112
    end.
2113

2114
execute_command2(Name,
2115
                 Query,
2116
                 Arguments,
2117
                 Method,
2118
                 ArgumentsFormat,
2119
                 CallerInfo,
2120
                 InputNameAppend) ->
UNCOV
2121
    AllArgumentsProvided = length(Arguments) == length(ArgumentsFormat),
480✔
UNCOV
2122
    PressedExecuteButton = is_this_to_execute(Name, Query, Arguments, InputNameAppend),
480✔
UNCOV
2123
    LetsExecute =
480✔
2124
        case {Method, PressedExecuteButton, AllArgumentsProvided} of
2125
            {auto, _, true} ->
UNCOV
2126
                true;
352✔
2127
            {manual, true, true} ->
UNCOV
2128
                true;
24✔
2129
            _ ->
UNCOV
2130
                false
104✔
2131
        end,
UNCOV
2132
    case LetsExecute of
480✔
2133
        true ->
UNCOV
2134
            catch ejabberd_commands:execute_command2(Name, Arguments, CallerInfo);
376✔
2135
        false ->
UNCOV
2136
            not_executed
104✔
2137
    end.
2138

2139
is_this_to_execute(Name, Query, Arguments, InputNameAppend) ->
UNCOV
2140
    NiceNameBin = nice_this(Name),
480✔
UNCOV
2141
    NameBin = misc:atom_to_binary(Name),
480✔
UNCOV
2142
    AppendBin = term_to_id(lists:sublist(Arguments, length(InputNameAppend))),
480✔
UNCOV
2143
    ArgumentsId = <<NameBin/binary, AppendBin/binary>>,
480✔
UNCOV
2144
    {value, {ArgumentsId, NiceNameBin}} == lists:keysearch(ArgumentsId, 1, Query).
480✔
2145

2146
prepare_arguments(ComName, Args, ArgsFormat) ->
UNCOV
2147
    lists:foldl(fun({ArgName, ArgFormat}, FinalArguments) ->
480✔
2148
                   %% Give priority to the value enforced in our code
2149
                   %% Otherwise use the value provided by the user
UNCOV
2150
                   case {lists:keyfind(
912✔
2151
                             misc:atom_to_binary(ArgName), 1, Args),
2152
                         lists:keyfind(long_this([ComName, ArgName]), 1, Args)}
2153
                   of
2154
                       %% Value enforced in our code
2155
                       {{_, Value}, _} ->
UNCOV
2156
                           [format_arg(Value, ArgFormat) | FinalArguments];
744✔
2157
                       %% User didn't provide value in the field
2158
                       {_, {_, <<>>}} ->
2159
                           FinalArguments;
×
2160
                       %% Value provided by the user in the form field
2161
                       {_, {_, Value}} ->
UNCOV
2162
                           [format_arg(Value, ArgFormat) | FinalArguments];
24✔
2163
                       {false, false} ->
UNCOV
2164
                           FinalArguments
144✔
2165
                   end
2166
                end,
2167
                [],
2168
                lists:reverse(ArgsFormat)).
2169

2170
format_arg(Value, any) ->
2171
    Value;
×
2172
format_arg(Value, atom) when is_atom(Value) ->
2173
    Value;
×
2174
format_arg(Value, binary) when is_binary(Value) ->
UNCOV
2175
    Value;
768✔
2176
format_arg(Value, ArgFormat) ->
2177
    ejabberd_ctl:format_arg(binary_to_list(Value), ArgFormat).
×
2178

2179
%%%==================================
2180
%%%% make_command: result
2181

2182
make_command_result(not_executed, _, _, _, _, _, _) ->
UNCOV
2183
    [];
104✔
2184
make_command_result({error, ErrorElement}, _, _, _, _, _, _) ->
2185
    [?DIVRES([?C(<<"Error: ">>),
×
2186
              ?XC(<<"code">>, list_to_binary(io_lib:format("~p", [ErrorElement])))])];
2187
make_command_result(Value,
2188
                    ArgumentsUsed,
2189
                    {ResName, _ResFormat} = ResultFormatApi,
2190
                    Automated,
2191
                    Resultnamed,
2192
                    ResultLinks,
2193
                    TO) ->
UNCOV
2194
    ResNameBin = nice_this(ResName),
376✔
UNCOV
2195
    ResultValueEl =
376✔
2196
        make_command_result_element(ArgumentsUsed, Value, ResultFormatApi, ResultLinks, TO),
UNCOV
2197
    ResultEls =
376✔
2198
        case Resultnamed of
2199
            true ->
2200
                [?C(<<ResNameBin/binary, ": ">>), ResultValueEl];
×
2201
            false ->
UNCOV
2202
                [ResultValueEl]
376✔
2203
        end,
UNCOV
2204
    case Automated of
376✔
2205
        true ->
UNCOV
2206
            ResultEls;
352✔
2207
        false ->
UNCOV
2208
            [?DIVRES(ResultEls)]
24✔
2209
    end.
2210

2211
make_command_result_element(ArgumentsUsed,
2212
                            ListOfTuples,
2213
                            {_ArgName, {list, {_ListElementsName, {tuple, TupleElements}}}},
2214
                            ResultLinks,
2215
                            {PageSize, RPath}) ->
UNCOV
2216
    HeadElements =
24✔
UNCOV
2217
        [nice_this(ElementName, ElementFormat) || {ElementName, ElementFormat} <- TupleElements],
216✔
UNCOV
2218
    ContentElements =
24✔
2219
        [list_to_tuple([make_result(format_result(V, {ElementName, ElementFormat}),
×
2220
                                    ElementName,
2221
                                    ArgumentsUsed,
2222
                                    ResultLinks)
2223
                        || {V, {ElementName, ElementFormat}}
2224
                               <- lists:zip(tuple_to_list(Tuple), TupleElements)])
×
UNCOV
2225
         || Tuple <- ListOfTuples],
24✔
UNCOV
2226
    make_table(PageSize, RPath, HeadElements, ContentElements);
24✔
2227
make_command_result_element(_ArgumentsUsed,
2228
                            Values,
2229
                            {_ArgName, {tuple, TupleElements}},
2230
                            _ResultLinks,
2231
                            _TO) ->
UNCOV
2232
    ?XE(<<"table">>,
72✔
2233
        [?XE(<<"thead">>,
2234
             [?XE(<<"tr">>,
UNCOV
2235
                  [?XC(<<"td">>, nice_this(ElementName))
144✔
UNCOV
2236
                   || {ElementName, _ElementFormat} <- TupleElements])]),
72✔
2237
         ?XE(<<"tbody">>,
2238
             [?XE(<<"tr">>,
UNCOV
2239
                  [?XE(<<"td">>,
144✔
2240
                       [?XAC(<<"span">>,
2241
                             [{<<"style">>, <<"white-space: pre-wrap;">>}],
2242
                             format_result(V, {ElementName, ElementFormat}))])
2243
                   || {V, {ElementName, ElementFormat}}
UNCOV
2244
                          <- lists:zip(tuple_to_list(Values), TupleElements)])])]);
72✔
2245
make_command_result_element(ArgumentsUsed,
2246
                            Value,
2247
                            {_ArgName, {list, {ElementsName, ElementsFormat}}},
2248
                            ResultLinks,
2249
                            {PageSize, RPath}) ->
UNCOV
2250
    HeadElements = [nice_this(ElementsName)],
8✔
UNCOV
2251
    ContentElements =
8✔
UNCOV
2252
        [{make_result(format_result(V, {ElementsName, ElementsFormat}),
24✔
2253
                      ElementsName,
2254
                      ArgumentsUsed,
2255
                      ResultLinks)}
UNCOV
2256
         || V <- Value],
8✔
UNCOV
2257
    make_table(PageSize, RPath, HeadElements, ContentElements);
8✔
2258
make_command_result_element(ArgumentsUsed, Value, ResultFormatApi, ResultLinks, _TO) ->
UNCOV
2259
    Res = make_result(format_result(Value, ResultFormatApi),
272✔
2260
                      unknown_element_name,
2261
                      ArgumentsUsed,
2262
                      ResultLinks),
UNCOV
2263
    Res2 =
272✔
2264
        case Res of
2265
            [{xmlel, _, _, _} | _] = X ->
2266
                X;
×
2267
            Z ->
UNCOV
2268
                [Z]
272✔
2269
        end,
UNCOV
2270
    ?XE(<<"code">>, Res2).
272✔
2271

2272
make_result(Binary, ElementName, ArgumentsUsed, [{ResultName, arg_host, Level, Append}])
2273
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
UNCOV
2274
    {_, Host} = lists:keyfind(host, 1, ArgumentsUsed),
144✔
UNCOV
2275
    UrlBinary =
144✔
2276
        replace_url_elements([<<"server/">>, host, <<"/">>, Append], [{host, Host}], Level),
UNCOV
2277
    ?AC(UrlBinary, Binary);
144✔
2278
make_result(Binary, ElementName, _ArgumentsUsed, [{ResultName, host, Level, Append}])
2279
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
2280
    UrlBinary =
×
2281
        replace_url_elements([<<"server/">>, host, <<"/">>, Append], [{host, Binary}], Level),
2282
    ?AC(UrlBinary, Binary);
×
2283
make_result(Binary,
2284
            ElementName,
2285
            _ArgumentsUsed,
2286
            [{ResultName, mnesia_table, Level, Append}])
2287
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
2288
    Node = misc:atom_to_binary(node()),
×
2289
    UrlBinary =
×
2290
        replace_url_elements([<<"node/">>, node, <<"/db/table/">>, tablename, <<"/">>, Append],
2291
                             [{node, Node}, {tablename, Binary}],
2292
                             Level),
2293
    ?AC(UrlBinary, Binary);
×
2294
make_result(Binary, ElementName, _ArgumentsUsed, [{ResultName, node, Level, Append}])
2295
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
UNCOV
2296
    UrlBinary =
40✔
2297
        replace_url_elements([<<"node/">>, node, <<"/">>, Append], [{node, Binary}], Level),
UNCOV
2298
    ?AC(UrlBinary, Binary);
40✔
2299
make_result(Binary, ElementName, _ArgumentsUsed, [{ResultName, user, Level, Append}])
2300
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
UNCOV
2301
    Jid = try jid:decode(Binary) of
64✔
2302
              #jid{} = J ->
UNCOV
2303
                  J
64✔
2304
          catch
2305
              _:{bad_jid, _} ->
2306
                  %% TODO: Find a method to be able to link to this user to delete it
2307
                  ?INFO_MSG("Error parsing Binary that is not a valid JID:~n  ~p", [Binary]),
×
2308
                  jid:decode(<<"unknown-username@localhost">>)
×
2309
          end,
UNCOV
2310
    {User, Host, _R} = jid:split(Jid),
64✔
UNCOV
2311
    case lists:member(Host, ejabberd_config:get_option(hosts)) of
64✔
2312
        true ->
UNCOV
2313
            UrlBinary =
64✔
2314
                replace_url_elements([<<"server/">>, host, <<"/user/">>, user, <<"/">>, Append],
2315
                                     [{user, misc:url_encode(User)}, {host, Host}],
2316
                                     Level),
UNCOV
2317
            ?AC(UrlBinary, Binary);
64✔
2318
        false ->
2319
            ?C(Binary)
×
2320
    end;
2321
make_result(Binary, ElementName, _ArgumentsUsed, [{ResultName, room, Level, Append}])
2322
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
2323
    Jid = jid:decode(Binary),
×
2324
    {Roomname, Service, _} = jid:split(Jid),
×
2325
    Host = ejabberd_router:host_of_route(Service),
×
2326
    case lists:member(Host, ejabberd_config:get_option(hosts)) of
×
2327
        true ->
2328
            UrlBinary =
×
2329
                replace_url_elements([<<"server/">>,
2330
                                      host,
2331
                                      <<"/muc/rooms/room/">>,
2332
                                      room,
2333
                                      <<"/">>,
2334
                                      Append],
2335
                                     [{room, misc:url_encode(Roomname)}, {host, Host}],
2336
                                     Level),
2337
            ?AC(UrlBinary, Binary);
×
2338
        false ->
2339
            ?C(Binary)
×
2340
    end;
2341
make_result(Binary,
2342
            ElementName,
2343
            ArgumentsUsed,
2344
            [{ResultName, shared_roster, Level, Append}])
2345
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
2346
    First = proplists:get_value(first, ArgumentsUsed),
×
2347
    Second = proplists:get_value(second, ArgumentsUsed),
×
2348
    FirstUrlencoded =
×
2349
        list_to_binary(string:replace(
2350
                           misc:url_encode(First), "%40", "@")),
2351
    {GroupId, Host} =
×
2352
        case jid:decode(FirstUrlencoded) of
2353
            #jid{luser = <<"">>, server = G} ->
2354
                {G, Second};
×
2355
            #jid{user = G, lserver = H} ->
2356
                {G, H}
×
2357
        end,
2358
    UrlBinary =
×
2359
        replace_url_elements([<<"server/">>,
2360
                              host,
2361
                              <<"/shared-roster/group/">>,
2362
                              srg,
2363
                              <<"/">>,
2364
                              Append],
2365
                             [{host, Host}, {srg, GroupId}],
2366
                             Level),
2367
    ?AC(UrlBinary, Binary);
×
2368
make_result([{xmlcdata, _, _, _} | _] = Any,
2369
            _ElementName,
2370
            _ArgumentsUsed,
2371
            _ResultLinks) ->
2372
    Any;
×
2373
make_result([{xmlel, _, _, _} | _] = Any, _ElementName, _ArgumentsUsed, _ResultLinks) ->
2374
    Any;
×
2375
make_result(Binary,
2376
            ElementName,
2377
            _ArgumentsUsed,
2378
            [{ResultName, paragraph, _Level, _Append}])
2379
    when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
2380
    ?XC(<<"pre">>, Binary);
×
2381
make_result(Binary, _ElementName, _ArgumentsUsed, _ResultLinks) ->
UNCOV
2382
    ?C(Binary).
48✔
2383

2384
replace_url_elements(UrlComponents, Replacements, Level) ->
UNCOV
2385
    Base = get_base_path_sum(0, 0, Level),
248✔
UNCOV
2386
    Binary2 =
248✔
2387
        lists:foldl(fun (El, Acc) when is_binary(El) ->
UNCOV
2388
                            [El | Acc];
808✔
2389
                        (El, Acc) when is_atom(El) ->
UNCOV
2390
                            {El, Value} = lists:keyfind(El, 1, Replacements),
312✔
UNCOV
2391
                            [Value | Acc]
312✔
2392
                    end,
2393
                    [],
2394
                    UrlComponents),
UNCOV
2395
    Binary3 =
248✔
2396
        binary:list_to_bin(
2397
            lists:reverse(Binary2)),
UNCOV
2398
    <<Base/binary, Binary3/binary>>.
248✔
2399

2400
format_result(Value, {_ResultName, integer}) when is_integer(Value) ->
UNCOV
2401
    integer_to_binary(Value);
168✔
2402
format_result(Value, {_ResultName, string}) when is_list(Value) ->
2403
    Value;
×
2404
format_result(Value, {_ResultName, string}) when is_binary(Value) ->
UNCOV
2405
    Value;
272✔
2406
format_result(Value, {_ResultName, atom}) when is_atom(Value) ->
2407
    misc:atom_to_binary(Value);
×
2408
format_result(Value, {_ResultName, any}) ->
2409
    Value;
×
2410
format_result({ok, String}, {_ResultName, restuple}) when is_list(String) ->
UNCOV
2411
    list_to_binary(String);
16✔
2412
format_result({error, Type, Code, Desc}, {_ResultName, restuple}) ->
2413
    <<"Error: ",
×
2414
      (misc:atom_to_binary(Type))/binary,
2415
      " ",
2416
      (integer_to_binary(Code))/binary,
2417
      ": ",
2418
      (list_to_binary(Desc))/binary>>;
2419
format_result([], {_Name, {list, _ElementsDef}}) ->
2420
    "";
×
2421
format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
2422
    Separator = ",",
×
2423
    Head = format_result(FirstElement, ElementsDef),
×
2424
    Tail =
×
2425
        lists:map(fun(Element) -> [Separator | format_result(Element, ElementsDef)] end,
×
2426
                  Elements),
2427
    [Head | Tail];
×
2428
format_result([], {_Name, {tuple, _ElementsDef}}) ->
2429
    "";
×
2430
format_result(Value, {_Name, {tuple, [FirstDef | ElementsDef]}}) ->
2431
    [FirstElement | Elements] = tuple_to_list(Value),
×
2432
    Separator = ":",
×
2433
    Head = format_result(FirstElement, FirstDef),
×
2434
    Tail =
×
2435
        lists:map(fun(Element) -> [Separator | format_result(Element, ElementsDef)] end,
×
2436
                  Elements),
2437
    [Head | Tail];
×
2438
format_result(Value, _ResultFormat) when is_atom(Value) ->
UNCOV
2439
    misc:atom_to_binary(Value);
8✔
2440
format_result(Value, _ResultFormat) when is_list(Value) ->
UNCOV
2441
    list_to_binary(Value);
48✔
2442
format_result(Value, _ResultFormat) when is_binary(Value) ->
UNCOV
2443
    Value;
96✔
2444
format_result(Value, _ResultFormat) ->
2445
    io_lib:format("~p", [Value]).
×
2446

2447
%%%==================================
2448
%%%% make_table
2449

2450
-spec make_table(PageSize :: integer(),
2451
                 RemainingPath :: [binary()],
2452
                 NameOptionList :: [Name :: binary() | {Name :: binary(), left | right}],
2453
                 Values :: [tuple()]) ->
2454
                    xmlel().
2455
make_table(PageSize, RPath, NameOptionList, Values1) ->
UNCOV
2456
    Values =
40✔
2457
        case lists:member(<<"sort">>, RPath) of
2458
            true ->
2459
                Values1;
×
2460
            false ->
UNCOV
2461
                GetXmlValue =
40✔
2462
                    fun ({xmlcdata, _} = X) ->
UNCOV
2463
                            X;
72✔
2464
                        ({xmlel, _, _, _} = X) ->
2465
                            X;
×
2466
                        ({raw_and_value, _V, X}) ->
UNCOV
2467
                            X
96✔
2468
                    end,
UNCOV
2469
                ConvertTupleToTuple =
40✔
UNCOV
2470
                    fun(Row1) -> list_to_tuple(lists:map(GetXmlValue, tuple_to_list(Row1))) end,
48✔
UNCOV
2471
                lists:map(ConvertTupleToTuple, Values1)
40✔
2472
        end,
UNCOV
2473
    make_table1(PageSize, RPath, <<"">>, <<"">>, 1, NameOptionList, Values).
40✔
2474

2475
make_table1(PageSize,
2476
            [<<"page">>, PageNumber | RPath],
2477
            PageUrlBase,
2478
            SortUrlBase,
2479
            _Start,
2480
            NameOptionList,
2481
            Values1) ->
2482
    make_table1(PageSize,
×
2483
                RPath,
2484
                <<PageUrlBase/binary, "../../">>,
2485
                <<SortUrlBase/binary, "../../">>,
2486
                1 + PageSize * binary_to_integer(PageNumber),
2487
                NameOptionList,
2488
                Values1);
2489
make_table1(PageSize,
2490
            [<<"sort">>, SortType | RPath],
2491
            PageUrlBase,
2492
            SortUrlBase,
2493
            Start,
2494
            NameOptionList,
2495
            Rows1) ->
2496
    ColumnToSort =
×
2497
        length(lists:takewhile(fun (A) when A == SortType ->
2498
                                       false;
×
2499
                                   ({A, _}) when A == SortType ->
2500
                                       false;
×
2501
                                   (_) ->
2502
                                       true
×
2503
                               end,
2504
                               NameOptionList))
2505
        + 1,
2506
    Direction =
×
2507
        case lists:nth(ColumnToSort, NameOptionList) of
2508
            {_, right} ->
2509
                descending;
×
2510
            {_, left} ->
2511
                ascending;
×
2512
            _ ->
2513
                ascending
×
2514
        end,
2515
    ColumnToSort = ColumnToSort,
×
2516
    GetRawValue =
×
2517
        fun ({xmlcdata, _} = X) ->
2518
                X;
×
2519
            ({xmlel, _, _, _} = X) ->
2520
                X;
×
2521
            ({raw_and_value, R, _X}) ->
2522
                R
×
2523
        end,
2524
    GetXmlValue =
×
2525
        fun ({xmlcdata, _} = X) ->
2526
                X;
×
2527
            ({xmlel, _, _, _} = X) ->
2528
                X;
×
2529
            ({raw_and_value, _R, X}) ->
2530
                X
×
2531
        end,
2532
    SortTwo =
×
2533
        fun(A1, B1) ->
2534
           A2 = GetRawValue(element(ColumnToSort, A1)),
×
2535
           B2 = GetRawValue(element(ColumnToSort, B1)),
×
2536
           case Direction of
×
2537
               ascending ->
2538
                   A2 < B2;
×
2539
               descending ->
2540
                   A2 > B2
×
2541
           end
2542
        end,
2543
    Rows1Sorted = lists:sort(SortTwo, Rows1),
×
2544
    ConvertTupleToTuple =
×
2545
        fun(Row1) -> list_to_tuple(lists:map(GetXmlValue, tuple_to_list(Row1))) end,
×
2546
    Rows = lists:map(ConvertTupleToTuple, Rows1Sorted),
×
2547
    make_table1(PageSize,
×
2548
                RPath,
2549
                PageUrlBase,
2550
                <<SortUrlBase/binary, "../../">>,
2551
                Start,
2552
                NameOptionList,
2553
                Rows);
2554
make_table1(PageSize, [], PageUrlBase, SortUrlBase, Start, NameOptionList, Values1) ->
UNCOV
2555
    Values = lists:sublist(Values1, Start, PageSize),
40✔
UNCOV
2556
    Table = make_table(NameOptionList, Values),
40✔
UNCOV
2557
    Size = length(Values1),
40✔
UNCOV
2558
    Remaining =
40✔
2559
        case Size rem PageSize of
2560
            0 ->
UNCOV
2561
                0;
24✔
2562
            _ ->
UNCOV
2563
                1
16✔
2564
        end,
UNCOV
2565
    NumPages = max(0, Size div PageSize + Remaining - 1),
40✔
UNCOV
2566
    PLinks1 =
40✔
2567
        lists:foldl(fun(N, Acc) ->
2568
                       NBin = integer_to_binary(N),
×
2569
                       Acc
2570
                       ++ [?C(<<", ">>),
×
2571
                           ?AC(<<PageUrlBase/binary, "page/", NBin/binary, "/">>, NBin)]
2572
                    end,
2573
                    [],
2574
                    lists:seq(1, NumPages)),
UNCOV
2575
    PLinks =
40✔
2576
        case PLinks1 of
2577
            [] ->
UNCOV
2578
                [];
40✔
2579
            _ ->
2580
                [?XE(<<"p">>, [?C(<<"Page: ">>), ?AC(<<PageUrlBase/binary>>, <<"0">>) | PLinks1])]
×
2581
        end,
2582

UNCOV
2583
    Names =
40✔
2584
        lists:map(fun ({Name, _}) ->
UNCOV
2585
                          Name;
112✔
2586
                      (Name) ->
UNCOV
2587
                          Name
160✔
2588
                  end,
2589
                  NameOptionList),
UNCOV
2590
    [_ | SLinks1] =
40✔
2591
        lists:foldl(fun(N, Acc) ->
UNCOV
2592
                       [?C(<<", ">>), ?AC(<<SortUrlBase/binary, "sort/", N/binary, "/">>, N) | Acc]
272✔
2593
                    end,
2594
                    [],
2595
                    lists:reverse(Names)),
UNCOV
2596
    SLinks =
40✔
2597
        case {PLinks, SLinks1} of
2598
            {_, []} ->
2599
                [];
×
2600
            {[], _} ->
UNCOV
2601
                [];
40✔
2602
            {_, [_]} ->
2603
                [];
×
2604
            {_, SLinks2} ->
2605
                [?XE(<<"p">>, [?C(<<"Sort all pages by: ">>) | SLinks2])]
×
2606
        end,
2607

UNCOV
2608
    ?XE(<<"div">>, [Table | PLinks ++ SLinks]).
40✔
2609

2610
-spec make_table(NameOptionList :: [Name :: binary() | {Name :: binary(), left | right}],
2611
                 Values :: [tuple()]) ->
2612
                    xmlel().
2613
make_table(NameOptionList, Values) ->
UNCOV
2614
    NamesAndAttributes = [make_column_attributes(NameOption) || NameOption <- NameOptionList],
40✔
UNCOV
2615
    {Names, ColumnsAttributes} = lists:unzip(NamesAndAttributes),
40✔
UNCOV
2616
    make_table(Names, ColumnsAttributes, Values).
40✔
2617

2618
make_table(Names, ColumnsAttributes, Values) ->
UNCOV
2619
    ?XAE(<<"table">>,
40✔
2620
         [{<<"class">>, <<"sortable">>}],
2621
         [?XE(<<"thead">>,
UNCOV
2622
              [?XE(<<"tr">>, [?XC(<<"th">>, nice_this(HeadElement)) || HeadElement <- Names])]),
272✔
2623
          ?XE(<<"tbody">>,
UNCOV
2624
              [?XE(<<"tr">>,
48✔
UNCOV
2625
                   [?XAE(<<"td">>, CAs, [V])
168✔
UNCOV
2626
                    || {CAs, V} <- lists:zip(ColumnsAttributes, tuple_to_list(ValueTuple))])
48✔
UNCOV
2627
               || ValueTuple <- Values])]).
40✔
2628

2629
make_column_attributes({Name, Option}) ->
UNCOV
2630
    {Name, [make_column_attribute(Option)]};
112✔
2631
make_column_attributes(Name) ->
UNCOV
2632
    {Name, []}.
160✔
2633

2634
make_column_attribute(left) ->
UNCOV
2635
    {<<"class">>, <<"alignleft">>};
16✔
2636
make_column_attribute(right) ->
UNCOV
2637
    {<<"class">>, <<"alignright">>}.
96✔
2638

2639
%%%==================================
2640
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc