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

processone / ejabberd / 1246

10 Dec 2025 12:12PM UTC coverage: 33.632% (+0.1%) from 33.528%
1246

push

github

badlop
ejabberd_web_admin: Show menu system only when can view vhosts

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

11084 existing lines in 174 files now uncovered.

15552 of 46242 relevant lines covered (33.63%)

1078.24 hits per line

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

66.3
/src/node_flat_sql.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : node_flat_sql.erl
3
%%% Author  : Christophe Romain <christophe.romain@process-one.net>
4
%%% Purpose : Standard PubSub node plugin with ODBC backend
5
%%% Created :  1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2025   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
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
27
%%% <p>It is used as a default for all unknown PubSub node type.  It can serve
28
%%% as a developer basis and reference to build its own custom pubsub node
29
%%% types.</p>
30
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
31

32
-module(node_flat_sql).
33
-behaviour(gen_pubsub_node).
34
-author('christophe.romain@process-one.net').
35

36

37
-include("pubsub.hrl").
38
-include_lib("xmpp/include/xmpp.hrl").
39
-include("ejabberd_sql_pt.hrl").
40
-include("translate.hrl").
41

42
-export([init/3, terminate/2, options/0, features/0,
43
    create_node_permission/6, create_node/2, delete_node/1, purge_node/2,
44
    subscribe_node/8, unsubscribe_node/4,
45
    publish_item/7, delete_item/4,
46
    remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
47
    get_entity_affiliations/2, get_node_affiliations/1,
48
    get_affiliation/2, set_affiliation/3,
49
    get_entity_subscriptions/2, get_node_subscriptions/1,
50
    get_subscriptions/2, set_subscriptions/4,
51
    get_pending_nodes/2, get_states/1, get_state/2,
52
    set_state/1, get_items/7, get_items/3, get_item/7,
53
    get_item/2, set_item/1, get_item_name/3, node_to_path/1,
54
    path_to_node/1,
55
    get_entity_subscriptions_for_send_last/2, get_last_items/3,
56
    get_only_item/2]).
57

58
-export([decode_jid/1, encode_jid/1, encode_jid_like/1,
59
         decode_affiliation/1, decode_subscriptions/1,
60
         encode_affiliation/1, encode_subscriptions/1,
61
         encode_host/1, encode_host_like/1]).
62

63
init(_Host, _ServerHost, _Opts) ->
64
    %%pubsub_subscription_sql:init(Host, ServerHost, Opts),
UNCOV
65
    ok.
12✔
66

67
terminate(_Host, _ServerHost) ->
UNCOV
68
    ok.
12✔
69

70
options() ->
UNCOV
71
    [{sql, true}, {rsm, true} | node_flat:options()].
2,142✔
72

73
features() ->
UNCOV
74
    [<<"rsm">> | node_flat:features()].
5,358✔
75

76
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
UNCOV
77
    node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
180✔
78

79
create_node(Nidx, Owner) ->
UNCOV
80
    {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
192✔
UNCOV
81
    J = encode_jid(OwnerKey),
192✔
UNCOV
82
    A = encode_affiliation(owner),
192✔
UNCOV
83
    S = encode_subscriptions([]),
192✔
UNCOV
84
    ejabberd_sql:sql_query_t(
192✔
UNCOV
85
      ?SQL("insert into pubsub_state("
260✔
86
           "nodeid, jid, affiliation, subscriptions) "
87
           "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
UNCOV
88
    {result, {default, broadcast}}.
192✔
89

90
delete_node(Nodes) ->
UNCOV
91
    Reply = lists:map(
192✔
92
              fun(#pubsub_node{id = Nidx} = PubsubNode) ->
UNCOV
93
                      Subscriptions =
192✔
94
                          case ejabberd_sql:sql_query_t(
UNCOV
95
                                 ?SQL("select @(jid)s, @(subscriptions)s "
256✔
96
                                      "from pubsub_state where nodeid=%(Nidx)d")) of
97
                              {selected, RItems} ->
UNCOV
98
                                  [{decode_jid(SJID), decode_subscriptions(Subs)}
192✔
UNCOV
99
                                   || {SJID, Subs} <- RItems];
192✔
100
                              _ ->
101
                                  []
×
102
                          end,
UNCOV
103
                      {PubsubNode, Subscriptions}
192✔
104
              end, Nodes),
UNCOV
105
    {result, {default, broadcast, Reply}}.
192✔
106

107
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
108
            SendLast, PresenceSubscription, RosterGroup, _Options) ->
UNCOV
109
    SubKey = jid:tolower(Subscriber),
102✔
UNCOV
110
    GenKey = jid:remove_resource(SubKey),
102✔
UNCOV
111
    Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
102✔
UNCOV
112
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
102✔
UNCOV
113
    Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
102✔
UNCOV
114
    PendingSubscription = lists:any(fun
102✔
115
                ({pending, _}) -> true;
×
116
                (_) -> false
×
117
            end,
118
            Subscriptions),
UNCOV
119
    Owner = Affiliation == owner,
102✔
UNCOV
120
    if not Authorized ->
102✔
121
            {error, mod_pubsub:extended_error(
×
122
                      xmpp:err_bad_request(), mod_pubsub:err_invalid_jid())};
123
        (Affiliation == outcast) or (Affiliation == publish_only) ->
UNCOV
124
            {error, xmpp:err_forbidden()};
12✔
125
        PendingSubscription ->
126
            {error, mod_pubsub:extended_error(
×
127
                      xmpp:err_not_authorized(),
128
                      mod_pubsub:err_pending_subscription())};
129
        (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
130
            {error, mod_pubsub:extended_error(
×
131
                      xmpp:err_not_authorized(),
132
                      mod_pubsub:err_presence_subscription_required())};
133
        (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
134
            {error, mod_pubsub:extended_error(
×
135
                      xmpp:err_not_authorized(),
136
                      mod_pubsub:err_not_in_roster_group())};
137
        (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
138
            {error, mod_pubsub:extended_error(
×
139
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
140
        %%MustPay ->
141
        %%        % Payment is required for a subscription
142
        %%        {error, ?ERR_PAYMENT_REQUIRED};
143
        %%ForbiddenAnonymous ->
144
        %%        % Requesting entity is anonymous
145
        %%        {error, ?ERR_FORBIDDEN};
146
        true ->
147
            %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Subscriber, Nidx, Options),
UNCOV
148
            {NewSub, SubId} = case Subscriptions of
90✔
149
                [{subscribed, Id}|_] ->
150
                    {subscribed, Id};
×
151
                [] ->
UNCOV
152
                    Id = pubsub_subscription_sql:make_subid(),
90✔
UNCOV
153
                    Sub = case AccessModel of
90✔
UNCOV
154
                        authorize -> pending;
12✔
UNCOV
155
                        _ -> subscribed
78✔
156
                    end,
UNCOV
157
                    update_subscription(Nidx, SubKey, [{Sub, Id} | Subscriptions]),
90✔
UNCOV
158
                    {Sub, Id}
90✔
159
            end,
UNCOV
160
            case {NewSub, SendLast} of
90✔
161
                {subscribed, never} ->
UNCOV
162
                    {result, {default, subscribed, SubId}};
24✔
163
                {subscribed, _} ->
UNCOV
164
                    {result, {default, subscribed, SubId, send_last}};
54✔
165
                {_, _} ->
UNCOV
166
                    {result, {default, pending, SubId}}
12✔
167
            end
168
    end.
169

170
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
UNCOV
171
    SubKey = jid:tolower(Subscriber),
42✔
UNCOV
172
    GenKey = jid:remove_resource(SubKey),
42✔
UNCOV
173
    Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
42✔
UNCOV
174
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
42✔
UNCOV
175
    SubIdExists = case SubId of
42✔
UNCOV
176
        <<>> -> false;
42✔
177
        Binary when is_binary(Binary) -> true;
×
178
        _ -> false
×
179
    end,
UNCOV
180
    if
42✔
181
        %% Requesting entity is prohibited from unsubscribing entity
182
        not Authorized ->
183
            {error, xmpp:err_forbidden()};
×
184
        %% Entity did not specify SubId
185
        %%SubId == "", ?? ->
186
        %%        {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
187
        %% Invalid subscription identifier
188
        %%InvalidSubId ->
189
        %%        {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
190
        %% Requesting entity is not a subscriber
191
        Subscriptions == [] ->
UNCOV
192
            {error, mod_pubsub:extended_error(
12✔
193
                      xmpp:err_unexpected_request(),
194
                      mod_pubsub:err_not_subscribed())};
195
        %% Subid supplied, so use that.
196
        SubIdExists ->
197
            Sub = first_in_list(fun
×
198
                        ({_, S}) when S == SubId -> true;
×
199
                        (_) -> false
×
200
                    end,
201
                    Subscriptions),
202
            case Sub of
×
203
                {value, S} ->
204
                    delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
×
205
                    {result, default};
×
206
                false ->
207
                    {error, mod_pubsub:extended_error(
×
208
                              xmpp:err_unexpected_request(),
209
                              mod_pubsub:err_not_subscribed())}
210
            end;
211
        %% Asking to remove all subscriptions to the given node
212
        SubId == all ->
213
            [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
×
214
                || S <- Subscriptions],
×
215
            {result, default};
×
216
        %% No subid supplied, but there's only one matching subscription
217
        length(Subscriptions) == 1 ->
UNCOV
218
            delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
30✔
UNCOV
219
            {result, default};
30✔
220
        %% No subid and more than one possible subscription match.
221
        true ->
222
            {error, mod_pubsub:extended_error(
×
223
                      xmpp:err_bad_request(), mod_pubsub:err_subid_required())}
224
    end.
225

226
delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
UNCOV
227
    NewSubs = Subscriptions -- [{Subscription, SubId}],
30✔
228
    %%pubsub_subscription_sql:unsubscribe_node(SubKey, Nidx, SubId),
UNCOV
229
    case {Affiliation, NewSubs} of
30✔
UNCOV
230
        {none, []} -> del_state(Nidx, SubKey);
30✔
231
        _ -> update_subscription(Nidx, SubKey, NewSubs)
×
232
    end.
233

234
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
235
             _PubOpts) ->
UNCOV
236
    SubKey = jid:tolower(Publisher),
198✔
UNCOV
237
    GenKey = jid:remove_resource(SubKey),
198✔
UNCOV
238
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
198✔
UNCOV
239
    Subscribed = case PublishModel of
198✔
240
        subscribers -> node_flat:is_subscribed(Subscriptions);
×
UNCOV
241
        _ -> undefined
198✔
242
    end,
UNCOV
243
    if not ((PublishModel == open) or
198✔
244
                    (PublishModel == publishers) and
245
                    ((Affiliation == owner)
246
                        or (Affiliation == publisher)
247
                        or (Affiliation == publish_only))
248
                    or (Subscribed == true)) ->
UNCOV
249
            {error, xmpp:err_forbidden()};
18✔
250
        true ->
UNCOV
251
            if MaxItems > 0;
180✔
252
               MaxItems == unlimited ->
UNCOV
253
                    Now = erlang:timestamp(),
180✔
UNCOV
254
                    case get_item(Nidx, ItemId) of
180✔
255
                        {result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
UNCOV
256
                            set_item(OldItem#pubsub_item{
6✔
257
                                        modification = {Now, SubKey},
258
                                        payload = Payload}),
UNCOV
259
                            {result, {default, broadcast, []}};
6✔
260
                        % Allow node owner to modify any item, he can also delete it and recreate
261
                        {result, #pubsub_item{creation = {CreationTime, _}} = OldItem} when Affiliation == owner->
262
                            set_item(OldItem#pubsub_item{
×
263
                                creation = {CreationTime, GenKey},
264
                                modification = {Now, SubKey},
265
                                payload = Payload}),
266
                            {result, {default, broadcast, []}};
×
267
                        {result, _} ->
268
                            {error, xmpp:err_forbidden()};
×
269
                        _ ->
UNCOV
270
                            OldIds = maybe_remove_extra_items(Nidx, MaxItems,
174✔
271
                                                              GenKey, ItemId),
UNCOV
272
                            set_item(#pubsub_item{
174✔
273
                                        itemid = {ItemId, Nidx},
274
                                        creation = {Now, GenKey},
275
                                        modification = {Now, SubKey},
276
                                        payload = Payload}),
UNCOV
277
                            {result, {default, broadcast, OldIds}}
174✔
278
                    end;
279
                true ->
280
                    {result, {default, broadcast, []}}
×
281
            end
282
    end.
283

284
remove_extra_items(Nidx, MaxItems) ->
285
    remove_extra_items(Nidx, MaxItems, itemids(Nidx)).
×
286

287
remove_extra_items(_Nidx, unlimited, ItemIds) ->
288
    {result, {ItemIds, []}};
×
289
remove_extra_items(Nidx, MaxItems, ItemIds) ->
UNCOV
290
    NewItems = lists:sublist(ItemIds, MaxItems),
174✔
UNCOV
291
    OldItems = lists:nthtail(length(NewItems), ItemIds),
174✔
UNCOV
292
    del_items(Nidx, OldItems),
174✔
UNCOV
293
    {result, {NewItems, OldItems}}.
174✔
294

295
remove_expired_items(_Nidx, infinity) ->
296
    {result, []};
×
297
remove_expired_items(Nidx, Seconds) ->
298
    ExpT = encode_now(
×
299
             misc:usec_to_now(
300
               erlang:system_time(microsecond) - (Seconds * 1000000))),
301
    case ejabberd_sql:sql_query_t(
×
302
           ?SQL("select @(itemid)s from pubsub_item where nodeid=%(Nidx)d "
×
303
                "and creation < %(ExpT)s")) of
304
        {selected, RItems} ->
305
            ItemIds = [ItemId || {ItemId} <- RItems],
×
306
            del_items(Nidx, ItemIds),
×
307
            {result, ItemIds};
×
308
        _ ->
309
            {result, []}
×
310
    end.
311

312
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
UNCOV
313
    SubKey = jid:tolower(Publisher),
48✔
UNCOV
314
    GenKey = jid:remove_resource(SubKey),
48✔
UNCOV
315
    {result, Affiliation} = get_affiliation(Nidx, GenKey),
48✔
UNCOV
316
    Allowed = Affiliation == publisher orelse
48✔
UNCOV
317
        Affiliation == owner orelse
42✔
UNCOV
318
        (PublishModel == open andalso
24✔
319
          case get_item(Nidx, ItemId) of
×
320
            {result, #pubsub_item{creation = {_, GenKey}}} -> true;
×
321
            _ -> false
×
322
          end),
UNCOV
323
    if not Allowed ->
48✔
UNCOV
324
            {error, xmpp:err_forbidden()};
24✔
325
        true ->
UNCOV
326
            Items = itemids(Nidx, GenKey),
24✔
UNCOV
327
            case lists:member(ItemId, Items) of
24✔
328
                true ->
UNCOV
329
                    case del_item(Nidx, ItemId) of
18✔
UNCOV
330
                        {updated, 1} -> {result, {default, broadcast}};
18✔
331
                        _ -> {error, xmpp:err_item_not_found()}
×
332
                    end;
333
                false ->
UNCOV
334
                    case Affiliation of
6✔
335
                        owner ->
UNCOV
336
                            case del_item(Nidx, ItemId) of
6✔
UNCOV
337
                                {updated, 1} -> {result, {default, broadcast}};
6✔
338
                                _ -> {error, xmpp:err_item_not_found()}
×
339
                            end;
340
                        _ ->
341
                            {error, xmpp:err_forbidden()}
×
342
                    end
343
            end
344
    end.
345

346
purge_node(Nidx, Owner) ->
UNCOV
347
    SubKey = jid:tolower(Owner),
42✔
UNCOV
348
    GenKey = jid:remove_resource(SubKey),
42✔
UNCOV
349
    GenState = get_state(Nidx, GenKey),
42✔
UNCOV
350
    case GenState of
42✔
351
        #pubsub_state{affiliation = owner} ->
UNCOV
352
            {result, States} = get_states(Nidx),
12✔
UNCOV
353
            lists:foreach(fun
12✔
UNCOV
354
                    (#pubsub_state{items = []}) -> ok;
12✔
UNCOV
355
                    (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
6✔
356
                end,
357
                States),
UNCOV
358
            {result, {default, broadcast}};
12✔
359
        _ ->
UNCOV
360
            {error, xmpp:err_forbidden()}
30✔
361
    end.
362

363
get_entity_affiliations(Host, Owner) ->
UNCOV
364
    SubKey = jid:tolower(Owner),
2,712✔
UNCOV
365
    GenKey = jid:remove_resource(SubKey),
2,712✔
UNCOV
366
    H = encode_host(Host),
2,712✔
UNCOV
367
    J = encode_jid(GenKey),
2,712✔
UNCOV
368
    {result,
2,712✔
369
     case ejabberd_sql:sql_query_t(
UNCOV
370
            ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(affiliation)s "
3,620✔
371
                 "from pubsub_state i, pubsub_node n where "
372
                 "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) of
373
         {selected, RItems} ->
UNCOV
374
             [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
2,712✔
UNCOV
375
               decode_affiliation(A)} || {N, T, I, A} <- RItems];
2,712✔
376
         _ ->
377
             []
×
378
     end}.
379

380
get_node_affiliations(Nidx) ->
UNCOV
381
    {result,
372✔
382
     case ejabberd_sql:sql_query_t(
UNCOV
383
            ?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
500✔
384
                 "where nodeid=%(Nidx)d")) of
385
         {selected, RItems} ->
UNCOV
386
             [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
372✔
387
         _ ->
388
             []
×
389
     end}.
390

391
get_affiliation(Nidx, Owner) ->
UNCOV
392
    SubKey = jid:tolower(Owner),
510✔
UNCOV
393
    GenKey = jid:remove_resource(SubKey),
510✔
UNCOV
394
    J = encode_jid(GenKey),
510✔
UNCOV
395
    {result,
510✔
396
     case ejabberd_sql:sql_query_t(
UNCOV
397
            ?SQL("select @(affiliation)s from pubsub_state "
684✔
398
                 "where nodeid=%(Nidx)d and jid=%(J)s")) of
399
         {selected, [{A}]} ->
UNCOV
400
             decode_affiliation(A);
474✔
401
         _ ->
UNCOV
402
             none
36✔
403
     end}.
404

405
set_affiliation(Nidx, Owner, Affiliation) ->
UNCOV
406
    SubKey = jid:tolower(Owner),
48✔
UNCOV
407
    GenKey = jid:remove_resource(SubKey),
48✔
UNCOV
408
    {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
48✔
UNCOV
409
    case {Affiliation, Subscriptions} of
48✔
UNCOV
410
        {none, []} -> {result, del_state(Nidx, GenKey)};
6✔
UNCOV
411
        _ -> {result, update_affiliation(Nidx, GenKey, Affiliation)}
42✔
412
    end.
413

414
get_entity_subscriptions(Host, Owner) ->
UNCOV
415
    SubKey = jid:tolower(Owner),
582✔
UNCOV
416
    GenKey = jid:remove_resource(SubKey),
582✔
UNCOV
417
    H = encode_host(Host),
582✔
UNCOV
418
    GJ = encode_jid(GenKey),
582✔
UNCOV
419
    Query = case SubKey of
582✔
420
              GenKey ->
UNCOV
421
                GJLike = <<(encode_jid_like(GenKey))/binary, "/%">>,
408✔
UNCOV
422
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
408✔
423
                     "from pubsub_state i, pubsub_node n "
424
                     "where i.nodeid = n.nodeid and "
425
                     "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s");
426
              _ ->
UNCOV
427
                SJ = encode_jid(SubKey),
174✔
UNCOV
428
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
174✔
429
                     "from pubsub_state i, pubsub_node n "
430
                     "where i.nodeid = n.nodeid and "
431
                     "jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
432
            end,
UNCOV
433
    {result,
582✔
434
     case ejabberd_sql:sql_query_t(Query) of
435
         {selected, RItems} ->
UNCOV
436
             lists:foldl(
582✔
437
               fun({N, T, I, J, S}, Acc) ->
UNCOV
438
                       Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
102✔
UNCOV
439
                       Jid = decode_jid(J),
102✔
UNCOV
440
                       lists:foldl(
102✔
441
                         fun({Sub, SubId}, Acc2) ->
UNCOV
442
                             [{Node, Sub, SubId, Jid} | Acc2]
48✔
443
                         end, Acc, decode_subscriptions(S))
444
               end, [], RItems);
445
         _ ->
446
             []
×
447
     end}.
448

449
-spec get_entity_subscriptions_for_send_last(Host :: mod_pubsub:hostPubsub(),
450
                                             Owner :: jid()) ->
451
                                                    {result, [{mod_pubsub:pubsubNode(),
452
                                                               mod_pubsub:subscription(),
453
                                                               mod_pubsub:subId(),
454
                                                               ljid()}]}.
455

456
get_entity_subscriptions_for_send_last(Host, Owner) ->
457
    SubKey = jid:tolower(Owner),
×
458
    GenKey = jid:remove_resource(SubKey),
×
459
    H = encode_host(Host),
×
460
    GJ = encode_jid(GenKey),
×
461
    Query = case SubKey of
×
462
              GenKey ->
463
                GJLike = <<(encode_jid_like(GenKey))/binary, "/%">>,
×
464
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
×
465
                     "from pubsub_state i, pubsub_node n, pubsub_node_option o "
466
                     "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and "
467
                     "name='send_last_published_item' and val='on_sub_and_presence' and "
468
                     "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s");
469
              _ ->
470
                SJ = encode_jid(SubKey),
×
471
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
×
472
                     "from pubsub_state i, pubsub_node n, pubsub_node_option o "
473
                     "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and "
474
                     "name='send_last_published_item' and val='on_sub_and_presence' and "
475
                     "jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
476
            end,
477
    {result,
×
478
     case ejabberd_sql:sql_query_t(Query) of
479
         {selected, RItems} ->
480
             lists:foldl(
×
481
               fun ({N, T, I, J, S}, Acc) ->
482
                       Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
×
483
                       Jid = decode_jid(J),
×
484
                       lists:foldl(
×
485
                         fun ({Sub, SubId}, Acc2) ->
486
                             [{Node, Sub, SubId, Jid}| Acc2]
×
487
                         end, Acc, decode_subscriptions(S))
488
               end, [], RItems);
489
         _ ->
490
             []
×
491
     end}.
492

493
get_node_subscriptions(Nidx) ->
UNCOV
494
    {result,
642✔
495
     case ejabberd_sql:sql_query_t(
UNCOV
496
            ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
860✔
497
                 "where nodeid=%(Nidx)d")) of
498
         {selected, RItems} ->
UNCOV
499
             lists:foldl(
642✔
500
               fun ({J, S}, Acc) ->
UNCOV
501
                       Jid = decode_jid(J),
624✔
UNCOV
502
                       lists:foldl(
624✔
503
                         fun ({Sub, SubId}, Acc2) ->
UNCOV
504
                             [{Jid, Sub, SubId} | Acc2]
96✔
505
                         end, Acc, decode_subscriptions(S))
506
               end, [], RItems);
507
         _ ->
508
             []
×
509
     end}.
510

511
get_subscriptions(Nidx, Owner) ->
UNCOV
512
    SubKey = jid:tolower(Owner),
12✔
UNCOV
513
    J = encode_jid(SubKey),
12✔
UNCOV
514
    {result,
12✔
515
     case ejabberd_sql:sql_query_t(
UNCOV
516
            ?SQL("select @(subscriptions)s from pubsub_state"
20✔
517
                 " where nodeid=%(Nidx)d and jid=%(J)s")) of
518
         {selected, [{S}]} ->
UNCOV
519
             decode_subscriptions(S);
12✔
520
         _ ->
521
             []
×
522
     end}.
523

524
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
UNCOV
525
    SubKey = jid:tolower(Owner),
36✔
UNCOV
526
    SubState = get_state_without_itemids(Nidx, SubKey),
36✔
UNCOV
527
    case {SubId, SubState#pubsub_state.subscriptions} of
36✔
528
        {_, []} ->
UNCOV
529
            case Subscription of
6✔
530
                none ->
531
                    {error, mod_pubsub:extended_error(
×
532
                              xmpp:err_bad_request(),
533
                              mod_pubsub:err_not_subscribed())};
534
                _ ->
UNCOV
535
                    new_subscription(Nidx, Owner, Subscription, SubState)
6✔
536
            end;
537
        {<<>>, [{_, SID}]} ->
UNCOV
538
            case Subscription of
18✔
UNCOV
539
                none -> unsub_with_subid(Nidx, SID, SubState);
6✔
UNCOV
540
                _ -> replace_subscription({Subscription, SID}, SubState)
12✔
541
            end;
542
        {<<>>, [_ | _]} ->
543
            {error, mod_pubsub:extended_error(
×
544
                      xmpp:err_bad_request(),
545
                      mod_pubsub:err_subid_required())};
546
        _ ->
UNCOV
547
            case Subscription of
12✔
UNCOV
548
                none -> unsub_with_subid(Nidx, SubId, SubState);
6✔
UNCOV
549
                _ -> replace_subscription({Subscription, SubId}, SubState)
6✔
550
            end
551
    end.
552

553
replace_subscription(NewSub, SubState) ->
UNCOV
554
    NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
18✔
UNCOV
555
    {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})}.
18✔
556

UNCOV
557
replace_subscription(_, [], Acc) -> Acc;
18✔
558
replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
UNCOV
559
    replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
18✔
560

561
new_subscription(_Nidx, _Owner, Subscription, SubState) ->
562
    %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Owner, Nidx, []),
UNCOV
563
    SubId = pubsub_subscription_sql:make_subid(),
6✔
UNCOV
564
    Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
6✔
UNCOV
565
    set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
6✔
UNCOV
566
    {result, {Subscription, SubId}}.
6✔
567

568
unsub_with_subid(Nidx, SubId, SubState) ->
569
    %%pubsub_subscription_sql:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
UNCOV
570
    NewSubs = [{S, Sid}
12✔
UNCOV
571
            || {S, Sid} <- SubState#pubsub_state.subscriptions,
12✔
UNCOV
572
                SubId =/= Sid],
12✔
UNCOV
573
    case {NewSubs, SubState#pubsub_state.affiliation} of
12✔
UNCOV
574
        {[], none} -> {result, del_state(Nidx, element(1, SubState#pubsub_state.stateid))};
12✔
575
        _ -> {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})}
×
576
    end.
577

578
get_pending_nodes(Host, Owner) ->
579
    GenKey = encode_jid(jid:remove_resource(jid:tolower(Owner))),
×
580
    PendingIdxs = case ejabberd_sql:sql_query_t(
×
581
                         ?SQL("select @(nodeid)d from pubsub_state "
×
582
                              "where subscriptions like '%p%' and affiliation='o'"
583
                              "and jid=%(GenKey)s")) of
584
        {selected, RItems} ->
585
            [Nidx || {Nidx} <- RItems];
×
586
        _ ->
587
            []
×
588
        end,
589
    NodeTree = mod_pubsub:tree(Host),
×
590
    Reply = lists:foldl(fun(Nidx, Acc) ->
×
591
                            case NodeTree:get_node(Nidx) of
×
592
                                #pubsub_node{nodeid = {_, Node}} -> [Node | Acc];
×
593
                                _ -> Acc
×
594
                            end
595
                        end,
596
                        [], PendingIdxs),
597
    {result, Reply}.
×
598

599
get_states(Nidx) ->
UNCOV
600
    case ejabberd_sql:sql_query_t(
12✔
UNCOV
601
           ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
20✔
602
                "from pubsub_state where nodeid=%(Nidx)d")) of
603
        {selected, RItems} ->
UNCOV
604
            {result,
12✔
605
             lists:map(
606
               fun({SJID, Aff, Subs}) ->
UNCOV
607
                       JID = decode_jid(SJID),
18✔
UNCOV
608
                       #pubsub_state{stateid = {JID, Nidx},
18✔
609
                                     nodeidx = Nidx,
610
                                     items = itemids(Nidx, JID),
611
                                     affiliation = decode_affiliation(Aff),
612
                                     subscriptions = decode_subscriptions(Subs)}
613
               end, RItems)};
614
        _ ->
615
            {result, []}
×
616
    end.
617

618
get_state(Nidx, JID) ->
UNCOV
619
    State = get_state_without_itemids(Nidx, JID),
42✔
UNCOV
620
    {SJID, _} = State#pubsub_state.stateid,
42✔
UNCOV
621
    State#pubsub_state{items = itemids(Nidx, SJID)}.
42✔
622

623
-spec get_state_without_itemids(Nidx :: mod_pubsub:nodeIdx(), Key :: ljid()) ->
624
                                       mod_pubsub:pubsubState().
625

626
get_state_without_itemids(Nidx, JID) ->
UNCOV
627
    J = encode_jid(JID),
78✔
UNCOV
628
    case ejabberd_sql:sql_query_t(
78✔
UNCOV
629
           ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
108✔
630
                "from pubsub_state "
631
                "where nodeid=%(Nidx)d and jid=%(J)s")) of
632
        {selected, [{SJID, Aff, Subs}]} ->
UNCOV
633
            #pubsub_state{stateid = {decode_jid(SJID), Nidx},
66✔
634
                          nodeidx = Nidx,
635
                          affiliation = decode_affiliation(Aff),
636
                          subscriptions = decode_subscriptions(Subs)};
637
        _ ->
UNCOV
638
            #pubsub_state{stateid = {JID, Nidx}, nodeidx = Nidx}
12✔
639
    end.
640

641
set_state(State) ->
UNCOV
642
    {_, Nidx} = State#pubsub_state.stateid,
24✔
UNCOV
643
    set_state(Nidx, State).
24✔
644

645
set_state(Nidx, State) ->
UNCOV
646
    {JID, _} = State#pubsub_state.stateid,
24✔
UNCOV
647
    J = encode_jid(JID),
24✔
UNCOV
648
    S = encode_subscriptions(State#pubsub_state.subscriptions),
24✔
UNCOV
649
    A = encode_affiliation(State#pubsub_state.affiliation),
24✔
UNCOV
650
    ?SQL_UPSERT_T(
24✔
651
       "pubsub_state",
652
       ["!nodeid=%(Nidx)d",
653
        "!jid=%(J)s",
654
        "affiliation=%(A)s",
655
        "subscriptions=%(S)s"
656
       ]),
UNCOV
657
    ok.
24✔
658

659
del_state(Nidx, JID) ->
UNCOV
660
    J = encode_jid(JID),
48✔
UNCOV
661
    catch ejabberd_sql:sql_query_t(
48✔
UNCOV
662
            ?SQL("delete from pubsub_state"
68✔
663
                 " where jid=%(J)s and nodeid=%(Nidx)d")),
UNCOV
664
    ok.
48✔
665

666
get_items(Nidx, _From, undefined) ->
UNCOV
667
    SNidx = misc:i2l(Nidx),
90✔
UNCOV
668
    case ejabberd_sql:sql_query_t(
90✔
669
           [<<"select itemid, publisher, creation, modification, payload",
670
              " from pubsub_item where nodeid='", SNidx/binary, "'",
671
              " order by creation asc">>]) of
672
        {selected, _, AllItems} ->
UNCOV
673
            {result, {[raw_to_item(Nidx, RItem) || RItem <- AllItems], undefined}};
90✔
674
        _ ->
675
            {result, {[], undefined}}
×
676
    end;
677
get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex,
678
                                'after' = After, before = Before}) ->
679
    Count = case catch ejabberd_sql:sql_query_t(
×
680
                    ?SQL("select @(count(itemid))d from pubsub_item"
×
681
                         " where nodeid=%(Nidx)d")) of
682
                {selected, [{C}]} -> C;
×
683
                _ -> 0
×
684
            end,
685
    Offset = case {IncIndex, Before, After} of
×
686
                {I, undefined, undefined} when is_integer(I) -> I;
×
687
                _ -> 0
×
688
             end,
689
    Limit = case Max of
×
690
                undefined -> ?MAXITEMS;
×
691
                _ -> Max
×
692
            end,
693
    Filters = rsm_filters(misc:i2l(Nidx), Before, After),
×
694
    Query = fun(mssql, _) ->
×
695
                    ejabberd_sql:sql_query_t(
×
696
                      [<<"select top ", (integer_to_binary(Limit))/binary,
697
                         " itemid, publisher, creation, modification, payload",
698
                         " from pubsub_item", Filters/binary>>]);
699
                         %OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
700
               (_, _) ->
701
                    ejabberd_sql:sql_query_t(
×
702
                      [<<"select itemid, publisher, creation, modification, payload",
703
                         " from pubsub_item", Filters/binary,
704
                         " limit ", (integer_to_binary(Limit))/binary,
705
                         " offset ", (integer_to_binary(Offset))/binary>>])
706
            end,
707
    case ejabberd_sql:sql_query_t(Query) of
×
708
        {selected, _, []} ->
709
            {result, {[], #rsm_set{count = Count}}};
×
710
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
711
                    <<"modification">>, <<"payload">>], RItems} ->
712
            Rsm = rsm_page(Count, IncIndex, Offset, RItems),
×
713
            {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], Rsm}};
×
714
        _ ->
715
            {result, {[], undefined}}
×
716
    end.
717

718
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
UNCOV
719
    SubKey = jid:tolower(JID),
72✔
UNCOV
720
    GenKey = jid:remove_resource(SubKey),
72✔
UNCOV
721
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
72✔
UNCOV
722
    Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
72✔
UNCOV
723
    if %%SubId == "", ?? ->
72✔
724
        %% Entity has multiple subscriptions to the node but does not specify a subscription ID
725
        %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
726
        %%InvalidSubId ->
727
        %% Entity is subscribed but specifies an invalid subscription ID
728
        %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
729
        (Affiliation == outcast) or (Affiliation == publish_only) ->
UNCOV
730
            {error, xmpp:err_forbidden()};
12✔
731
        (AccessModel == presence) and not PresenceSubscription ->
732
            {error, mod_pubsub:extended_error(
×
733
                      xmpp:err_not_authorized(),
734
                      mod_pubsub:err_presence_subscription_required())};
735
        (AccessModel == roster) and not RosterGroup ->
736
            {error, mod_pubsub:extended_error(
×
737
                      xmpp:err_not_authorized(),
738
                      mod_pubsub:err_not_in_roster_group())};
739
        (AccessModel == whitelist) and not Whitelisted ->
740
            {error, mod_pubsub:extended_error(
×
741
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
742
        (AccessModel == authorize) and not Whitelisted ->
743
            {error, xmpp:err_forbidden()};
×
744
        %%MustPay ->
745
        %%        % Payment is required for a subscription
746
        %%        {error, ?ERR_PAYMENT_REQUIRED};
747
        true ->
UNCOV
748
            get_items(Nidx, JID, RSM)
60✔
749
    end.
750

751
get_last_items(Nidx, _From, Limit) ->
UNCOV
752
    SNidx = misc:i2l(Nidx),
48✔
UNCOV
753
    Query = fun(mssql, _) ->
48✔
754
                    ejabberd_sql:sql_query_t(
×
755
                      [<<"select top ", (integer_to_binary(Limit))/binary,
756
                         " itemid, publisher, creation, modification, payload",
757
                         " from pubsub_item where nodeid='", SNidx/binary,
758
                         "' order by modification desc">>]);
759
               (_, _) ->
UNCOV
760
                    ejabberd_sql:sql_query_t(
48✔
761
                      [<<"select itemid, publisher, creation, modification, payload",
762
                         " from pubsub_item where nodeid='", SNidx/binary,
763
                         "' order by modification desc ",
764
                         " limit ", (integer_to_binary(Limit))/binary>>])
765
            end,
UNCOV
766
    case catch ejabberd_sql:sql_query_t(Query) of
48✔
767
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
768
                    <<"modification">>, <<"payload">>], RItems} ->
UNCOV
769
            {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
48✔
770
        _ ->
771
            {result, []}
×
772
    end.
773

774
get_only_item(Nidx, _From) ->
UNCOV
775
    SNidx = misc:i2l(Nidx),
6✔
UNCOV
776
    Query = fun(mssql, _) ->
6✔
777
        ejabberd_sql:sql_query_t(
×
778
            [<<"select  itemid, publisher, creation, modification, payload",
779
               " from pubsub_item where nodeid='", SNidx/binary, "'">>]);
780
               (_, _) ->
UNCOV
781
                   ejabberd_sql:sql_query_t(
6✔
782
                       [<<"select itemid, publisher, creation, modification, payload",
783
                          " from pubsub_item where nodeid='", SNidx/binary, "'">>])
784
            end,
UNCOV
785
    case catch ejabberd_sql:sql_query_t(Query) of
6✔
786
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
787
                    <<"modification">>, <<"payload">>], RItems} ->
UNCOV
788
            {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
6✔
789
        _ ->
790
            {result, []}
×
791
    end.
792

793
get_item(Nidx, ItemId) ->
UNCOV
794
    case catch ejabberd_sql:sql_query_t(
180✔
UNCOV
795
                 ?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
244✔
796
                      " @(modification)s, @(payload)s from pubsub_item"
797
                      " where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
798
    of
799
        {selected, [RItem]} ->
UNCOV
800
            {result, raw_to_item(Nidx, RItem)};
6✔
801
        {selected, []} ->
UNCOV
802
            {error, xmpp:err_item_not_found()};
174✔
803
        {'EXIT', _} ->
804
            {error, xmpp:err_internal_server_error(?T("Database failure"), ejabberd_option:language())}
×
805
    end.
806

807
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
808
    SubKey = jid:tolower(JID),
×
809
    GenKey = jid:remove_resource(SubKey),
×
810
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
×
811
    Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
×
812
    if %%SubId == "", ?? ->
×
813
        %% Entity has multiple subscriptions to the node but does not specify a subscription ID
814
        %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
815
        %%InvalidSubId ->
816
        %% Entity is subscribed but specifies an invalid subscription ID
817
        %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
818
        (Affiliation == outcast) or (Affiliation == publish_only) ->
819
            {error, xmpp:err_forbidden()};
×
820
        (AccessModel == presence) and not PresenceSubscription ->
821
            {error, mod_pubsub:extended_error(
×
822
                      xmpp:err_not_authorized(),
823
                      mod_pubsub:err_presence_subscription_required())};
824
        (AccessModel == roster) and not RosterGroup ->
825
            {error, mod_pubsub:extended_error(
×
826
                      xmpp:err_not_authorized(),
827
                      mod_pubsub:err_not_in_roster_group())};
828
        (AccessModel == whitelist) and not Whitelisted ->
829
            {error, mod_pubsub:extended_error(
×
830
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
831
        (AccessModel == authorize) and not Whitelisted ->
832
            {error, xmpp:err_forbidden()};
×
833
        %%MustPay ->
834
        %%        % Payment is required for a subscription
835
        %%        {error, ?ERR_PAYMENT_REQUIRED};
836
        true ->
837
            get_item(Nidx, ItemId)
×
838
    end.
839

840
set_item(Item) ->
UNCOV
841
    {ItemId, Nidx} = Item#pubsub_item.itemid,
180✔
UNCOV
842
    {C, _} = Item#pubsub_item.creation,
180✔
UNCOV
843
    {M, JID} = Item#pubsub_item.modification,
180✔
UNCOV
844
    P = encode_jid(JID),
180✔
UNCOV
845
    Payload = Item#pubsub_item.payload,
180✔
UNCOV
846
    XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
180✔
UNCOV
847
    SM = encode_now(M),
180✔
UNCOV
848
    SC = encode_now(C),
180✔
UNCOV
849
    ?SQL_UPSERT_T(
180✔
850
       "pubsub_item",
851
       ["!nodeid=%(Nidx)d",
852
        "!itemid=%(ItemId)s",
853
        "publisher=%(P)s",
854
        "modification=%(SM)s",
855
        "payload=%(XML)s",
856
        "-creation=%(SC)s"
857
       ]),
UNCOV
858
    ok.
180✔
859

860
del_item(Nidx, ItemId) ->
UNCOV
861
    catch ejabberd_sql:sql_query_t(
24✔
UNCOV
862
            ?SQL("delete from pubsub_item where itemid=%(ItemId)s"
36✔
863
                 " and nodeid=%(Nidx)d")).
864

865
del_items(_, []) ->
UNCOV
866
    ok;
174✔
867
del_items(Nidx, [ItemId]) ->
868
    del_item(Nidx, ItemId);
×
869
del_items(Nidx, ItemIds) ->
UNCOV
870
    I = str:join([ejabberd_sql:to_string_literal_t(X) || X <- ItemIds], <<",">>),
6✔
UNCOV
871
    SNidx = misc:i2l(Nidx),
6✔
UNCOV
872
    catch
6✔
873
    ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
874
            I, <<") and nodeid='">>, SNidx, <<"';">>]).
875

876
get_item_name(_Host, _Node, Id) ->
877
    {result, Id}.
×
878

879
node_to_path(Node) ->
UNCOV
880
    node_flat:node_to_path(Node).
192✔
881

882
path_to_node(Path) ->
883
    node_flat:path_to_node(Path).
×
884

885

886
first_in_list(_Pred, []) ->
887
    false;
×
888
first_in_list(Pred, [H | T]) ->
889
    case Pred(H) of
×
890
        true -> {value, H};
×
891
        _ -> first_in_list(Pred, T)
×
892
    end.
893

894
itemids(Nidx) ->
895
    case catch
×
896
        ejabberd_sql:sql_query_t(
897
          ?SQL("select @(itemid)s from pubsub_item where "
×
898
               "nodeid=%(Nidx)d order by modification desc"))
899
    of
900
        {selected, RItems} ->
901
            [ItemId || {ItemId} <- RItems];
×
902
        _ ->
903
            []
×
904
    end.
905

906
itemids(Nidx, {_U, _S, _R} = JID) ->
UNCOV
907
    SJID = encode_jid(JID),
258✔
UNCOV
908
    SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
258✔
UNCOV
909
    case catch
258✔
910
        ejabberd_sql:sql_query_t(
UNCOV
911
          ?SQL("select @(itemid)s from pubsub_item where "
348✔
912
               "nodeid=%(Nidx)d and (publisher=%(SJID)s"
913
               " or publisher like %(SJIDLike)s %ESCAPE) "
914
               "order by modification desc"))
915
    of
916
        {selected, RItems} ->
UNCOV
917
            [ItemId || {ItemId} <- RItems];
258✔
918
        _ ->
919
            []
×
920
    end.
921

922
select_affiliation_subscriptions(Nidx, JID) ->
UNCOV
923
    J = encode_jid(JID),
102✔
UNCOV
924
    case catch
102✔
925
        ejabberd_sql:sql_query_t(
UNCOV
926
          ?SQL("select @(affiliation)s, @(subscriptions)s from "
140✔
927
               " pubsub_state where nodeid=%(Nidx)d and jid=%(J)s"))
928
    of
929
        {selected, [{A, S}]} ->
UNCOV
930
            {decode_affiliation(A), decode_subscriptions(S)};
54✔
931
        _ ->
UNCOV
932
            {none, []}
48✔
933
    end.
934

935
select_affiliation_subscriptions(Nidx, JID, JID) ->
UNCOV
936
    select_affiliation_subscriptions(Nidx, JID);
12✔
937
select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
UNCOV
938
    GJ = encode_jid(GenKey),
360✔
UNCOV
939
    SJ = encode_jid(SubKey),
360✔
UNCOV
940
    case catch ejabberd_sql:sql_query_t(
360✔
UNCOV
941
        ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s from "
484✔
942
             " pubsub_state where nodeid=%(Nidx)d and jid in (%(GJ)s, %(SJ)s)"))
943
    of
944
        {selected, Res} ->
UNCOV
945
            lists:foldr(
360✔
946
                fun({Jid, A, S}, {_, Subs}) when Jid == GJ ->
UNCOV
947
                    {decode_affiliation(A), Subs ++ decode_subscriptions(S)};
324✔
948
                   ({_, _, S}, {Aff, Subs}) ->
949
                       {Aff, Subs ++ decode_subscriptions(S)}
×
950
                end, {none, []}, Res);
951
        _ ->
952
            {none, []}
×
953
    end.
954

955
update_affiliation(Nidx, JID, Affiliation) ->
UNCOV
956
    J = encode_jid(JID),
42✔
UNCOV
957
    A = encode_affiliation(Affiliation),
42✔
UNCOV
958
    ?SQL_UPSERT_T(
42✔
959
       "pubsub_state",
960
       ["!nodeid=%(Nidx)d",
961
        "!jid=%(J)s",
962
        "affiliation=%(A)s",
963
        "-subscriptions=''"
964
       ]).
965

966
update_subscription(Nidx, JID, Subscription) ->
UNCOV
967
    J = encode_jid(JID),
90✔
UNCOV
968
    S = encode_subscriptions(Subscription),
90✔
UNCOV
969
    ?SQL_UPSERT_T(
90✔
970
       "pubsub_state",
971
       ["!nodeid=%(Nidx)d",
972
        "!jid=%(J)s",
973
        "subscriptions=%(S)s",
974
        "-affiliation='n'"
975
       ]).
976

977
-spec maybe_remove_extra_items(mod_pubsub:nodeIdx(),
978
                               non_neg_integer() | unlimited, ljid(),
979
                               mod_pubsub:itemId()) -> [mod_pubsub:itemId()].
980
maybe_remove_extra_items(_Nidx, unlimited, _GenKey, _ItemId) ->
981
    [];
×
982
maybe_remove_extra_items(Nidx, MaxItems, GenKey, ItemId) ->
UNCOV
983
    ItemIds = [ItemId | itemids(Nidx, GenKey)],
174✔
UNCOV
984
    {result, {_NewIds, OldIds}} = remove_extra_items(Nidx, MaxItems, ItemIds),
174✔
UNCOV
985
    OldIds.
174✔
986

987
-spec decode_jid(SJID :: binary()) -> ljid().
988
decode_jid(SJID) ->
UNCOV
989
    jid:tolower(jid:decode(SJID)).
1,604✔
990

991
-spec decode_affiliation(Arg :: binary()) -> atom().
UNCOV
992
decode_affiliation(<<"o">>) -> owner;
1,080✔
UNCOV
993
decode_affiliation(<<"p">>) -> publisher;
90✔
UNCOV
994
decode_affiliation(<<"u">>) -> publish_only;
90✔
UNCOV
995
decode_affiliation(<<"m">>) -> member;
90✔
UNCOV
996
decode_affiliation(<<"c">>) -> outcast;
96✔
UNCOV
997
decode_affiliation(_) -> none.
102✔
998

999
-spec decode_subscription(Arg :: binary()) -> atom().
UNCOV
1000
decode_subscription(<<"s">>) -> subscribed;
174✔
UNCOV
1001
decode_subscription(<<"p">>) -> pending;
42✔
UNCOV
1002
decode_subscription(<<"u">>) -> unconfigured;
18✔
1003
decode_subscription(_) -> none.
×
1004

1005
-spec decode_subscriptions(Subscriptions :: binary()) -> [] | [{atom(), binary()},...].
1006
decode_subscriptions(Subscriptions) ->
UNCOV
1007
    lists:foldl(fun (Subscription, Acc) ->
1,292✔
UNCOV
1008
                case str:tokens(Subscription, <<":">>) of
234✔
UNCOV
1009
                    [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
234✔
1010
                    _ -> Acc
×
1011
                end
1012
        end,
1013
        [], str:tokens(Subscriptions, <<",">>)).
1014

1015
-spec encode_jid(JID :: ljid()) -> binary().
1016
encode_jid(JID) ->
UNCOV
1017
    jid:encode(JID).
8,388✔
1018

1019
-spec encode_jid_like(JID :: ljid()) -> binary().
1020
encode_jid_like(JID) ->
UNCOV
1021
    ejabberd_sql:escape_like_arg(jid:encode(JID)).
1,074✔
1022

1023
-spec encode_host(Host :: host()) -> binary().
UNCOV
1024
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
1,908✔
UNCOV
1025
encode_host(Host) -> Host.
4,710✔
1026

1027
-spec encode_host_like(Host :: host()) -> binary().
1028
encode_host_like({_U, _S, _R} = LJID) -> encode_jid_like(LJID);
×
1029
encode_host_like(Host) ->
UNCOV
1030
    ejabberd_sql:escape_like_arg(Host).
582✔
1031

1032
-spec encode_affiliation(Arg :: atom()) -> binary().
UNCOV
1033
encode_affiliation(owner) -> <<"o">>;
204✔
UNCOV
1034
encode_affiliation(publisher) -> <<"p">>;
6✔
UNCOV
1035
encode_affiliation(publish_only) -> <<"u">>;
6✔
UNCOV
1036
encode_affiliation(member) -> <<"m">>;
6✔
UNCOV
1037
encode_affiliation(outcast) -> <<"c">>;
12✔
UNCOV
1038
encode_affiliation(_) -> <<"n">>.
24✔
1039

1040
-spec encode_subscription(Arg :: atom()) -> binary().
UNCOV
1041
encode_subscription(subscribed) -> <<"s">>;
90✔
UNCOV
1042
encode_subscription(pending) -> <<"p">>;
18✔
UNCOV
1043
encode_subscription(unconfigured) -> <<"u">>;
6✔
1044
encode_subscription(_) -> <<"n">>.
×
1045

1046
-spec encode_subscriptions(Subscriptions :: [] | [{atom(), binary()},...]) -> binary().
1047
encode_subscriptions(Subscriptions) ->
UNCOV
1048
    str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
306✔
UNCOV
1049
            || {S, SubId} <- Subscriptions], <<",">>).
306✔
1050

1051
%%% record getter/setter
1052

1053
raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
UNCOV
1054
    raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
126✔
1055
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
UNCOV
1056
    JID = decode_jid(SJID),
132✔
UNCOV
1057
    Payload = case fxml_stream:parse_element(XML) of
132✔
1058
        {error, _Reason} -> [];
×
UNCOV
1059
        El -> [El]
132✔
1060
    end,
UNCOV
1061
    #pubsub_item{itemid = {ItemId, Nidx},
132✔
1062
        nodeidx = Nidx,
1063
        creation = {decode_now(Creation), jid:remove_resource(JID)},
1064
        modification = {decode_now(Modification), JID},
1065
        payload = Payload}.
1066

1067
rsm_filters(SNidx, undefined, undefined) ->
1068
    <<" where nodeid='", SNidx/binary, "'",
×
1069
      " order by creation asc">>;
1070
rsm_filters(SNidx, undefined, After)  ->
1071
    <<" where nodeid='", SNidx/binary, "'",
×
1072
      " and creation>'", (encode_stamp(After))/binary, "'",
1073
      " order by creation asc">>;
1074
rsm_filters(SNidx, <<>>, undefined) ->
1075
    %% 2.5 Requesting the Last Page in a Result Set
1076
    <<" where nodeid='", SNidx/binary, "'",
×
1077
      " order by creation desc">>;
1078
rsm_filters(SNidx, Before, undefined) ->
1079
    <<" where nodeid='", SNidx/binary, "'",
×
1080
      " and creation<'", (encode_stamp(Before))/binary, "'",
1081
      " order by creation desc">>.
1082

1083
rsm_page(Count, Index, Offset, Items) ->
1084
    First = decode_stamp(lists:nth(3, hd(Items))),
×
1085
    Last = decode_stamp(lists:nth(3, lists:last(Items))),
×
1086
    #rsm_set{count = Count, index = Index,
×
1087
             first = #rsm_first{index = Offset, data = First},
1088
             last = Last}.
1089

1090
encode_stamp(Stamp) ->
1091
    try xmpp_util:decode_timestamp(Stamp) of
×
1092
        Now ->
1093
            encode_now(Now)
×
1094
    catch _:{bad_timestamp, _} ->
1095
            Stamp % We should return a proper error to the client instead.
×
1096
    end.
1097
decode_stamp(Stamp) ->
1098
    xmpp_util:encode_timestamp(decode_now(Stamp)).
×
1099

1100
encode_now({T1, T2, T3}) ->
UNCOV
1101
    <<(misc:i2l(T1, 6))/binary, ":",
360✔
1102
      (misc:i2l(T2, 6))/binary, ":",
1103
      (misc:i2l(T3, 6))/binary>>.
1104
decode_now(NowStr) ->
UNCOV
1105
    [MS, S, US] = binary:split(NowStr, <<":">>, [global]),
264✔
UNCOV
1106
    {binary_to_integer(MS), binary_to_integer(S), binary_to_integer(US)}.
264✔
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

© 2025 Coveralls, Inc