aboutsummaryrefslogtreecommitdiff
path: root/src/mod_pubsub_ng
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_pubsub_ng')
-rw-r--r--src/mod_pubsub_ng/Makefile.in62
-rw-r--r--src/mod_pubsub_ng/Makefile.win3288
-rw-r--r--src/mod_pubsub_ng/exmpp_pubsub.erl685
-rw-r--r--src/mod_pubsub_ng/mod_pubsub_dev.erl863
-rw-r--r--src/mod_pubsub_ng/node_flat_dev.erl310
-rw-r--r--src/mod_pubsub_ng/pubsub_api.hrl101
-rw-r--r--src/mod_pubsub_ng/pubsub_broadcast.erl1240
-rw-r--r--src/mod_pubsub_ng/pubsub_core.erl1707
-rw-r--r--src/mod_pubsub_ng/pubsub_db.erl1575
-rw-r--r--src/mod_pubsub_ng/pubsub_db_mnesia.erl2129
-rw-r--r--src/mod_pubsub_ng/pubsub_dev.hrl146
-rw-r--r--src/mod_pubsub_ng/pubsub_disco.erl1437
-rw-r--r--src/mod_pubsub_ng/pubsub_groups.erl904
-rw-r--r--src/mod_pubsub_ng/pubsub_hooks.erl570
-rw-r--r--src/mod_pubsub_ng/pubsub_index_dev.erl113
-rw-r--r--src/mod_pubsub_ng/pubsub_options.erl3369
-rw-r--r--src/mod_pubsub_ng/pubsub_parser.erl1442
-rw-r--r--src/mod_pubsub_ng/pubsub_tools.erl685
-rw-r--r--src/mod_pubsub_ng/xmpp_xdata.erl108
19 files changed, 17534 insertions, 0 deletions
diff --git a/src/mod_pubsub_ng/Makefile.in b/src/mod_pubsub_ng/Makefile.in
new file mode 100644
index 000000000..9a114a1a3
--- /dev/null
+++ b/src/mod_pubsub_ng/Makefile.in
@@ -0,0 +1,62 @@
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+ERLANG_CFLAGS = @ERLANG_CFLAGS@
+ERLANG_LIBS = @ERLANG_LIBS@
+
+EFLAGS += -I ..
+EFLAGS += -pz ..
+
+# make debug=true to compile Erlang module with debug informations.
+ifdef debug
+ EFLAGS+=+debug_info
+endif
+
+OUTDIR = ..
+ERLBEHAVS = gen_pubsub_node.erl gen_pubsub_nodetree.erl
+SOURCES_ALL = $(wildcard *.erl)
+SOURCES = $(filter-out $(ERLBEHAVS),$(SOURCES_ALL))
+ERLBEHAVBEAMS = $(addprefix $(OUTDIR)/,$(ERLBEHAVS:.erl=.beam))
+BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
+
+
+all: $(ERLBEHAVBEAMS) $(BEAMS)
+
+$(BEAMS): $(ERLBEHAVBEAMS)
+
+$(OUTDIR)/%.beam: %.erl
+ @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
+
+clean:
+ rm -f $(BEAMS)
+
+distclean: clean
+ rm -f Makefile
+
+
+
+TAGS:
+ etags *.erl
+
+
+DEVDOCDIR=../../doc/devdoc
+DDTDIR=..
+HTMLS = $(SOURCES:%.erl=../../doc/devdoc/%.html)
+ERLHTMLS = $(SOURCES:%.erl=../../doc/devdoc/%.erl.html)
+SVGS = $(SOURCES:%.erl=../../doc/devdoc/%.svg)
+
+devdoc: $(HTMLS) $(ERLHTMLS) $(SVGS)
+
+$(DEVDOCDIR)/%.erl.html: %.erl
+ @ERL@ -noshell -pa $(DEVDOCDIR) -run escobar_run file $< $(DDTDIR) -s init stop
+
+$(DEVDOCDIR)/%.html: %.erl
+ @ERL@ -noshell -run edoc_run file $< \
+ '[{dir,"$(DDTDIR)"},{packages,false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"overview.edoc"}]' -s init stop
+
+$(DEVDOCDIR)/%.svg: %.erl
+ @ERL@ -noshell -pa $(DEVDOCDIR) -run funrelg dir $< $(DDTDIR) -s init stop
+
diff --git a/src/mod_pubsub_ng/Makefile.win32 b/src/mod_pubsub_ng/Makefile.win32
new file mode 100644
index 000000000..6d5707b23
--- /dev/null
+++ b/src/mod_pubsub_ng/Makefile.win32
@@ -0,0 +1,88 @@
+
+include ..\Makefile.inc
+
+EFLAGS = -I .. -pz ..
+
+OUTDIR = ..
+BEAMS = ..\exmpp_pubsub.beam ..\pubsub_disco.beam ..\mod_pubsub_dev.beam ..\pubsub_hooks.beam ..\mod_pubsub_odbc.beam ..\pubsub_index_dev.beam ..\pubsub_broadcast.beam ..\pubsub_options.beam ..\pubsub_core.beam ..\pubsub_parser.beam ..\pubsub_db.beam ..\xmpp_xdata.beam ..\pubsub_db_mnesia.beam
+
+
+ALL : $(BEAMS)
+
+CLEAN :
+ -@erase $(BEAMS)
+
+$(OUTDIR)\gen_pubsub_node.beam : gen_pubsub_node.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) gen_pubsub_node.erl
+
+$(OUTDIR)\gen_pubsub_nodetree.beam : gen_pubsub_nodetree.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) gen_pubsub_nodetree.erl
+
+$(OUTDIR)\mod_pubsub.beam : mod_pubsub.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) mod_pubsub.erl
+
+$(OUTDIR)\mod_pubsub_odbc.beam : mod_pubsub_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) mod_pubsub_odbc.erl
+
+$(OUTDIR)\node_buddy.beam : node_buddy.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_buddy.erl
+
+$(OUTDIR)\node_club.beam : node_club.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_club.erl
+
+$(OUTDIR)\node_dag.beam : node_dag.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_dag.erl
+
+$(OUTDIR)\node_dispatch.beam : node_dispatch.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_dispatch.erl
+
+$(OUTDIR)\node_flat.beam : node_flat.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_flat.erl
+
+$(OUTDIR)\node_flat_odbc.beam : node_flat_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_flat_odbc.erl
+
+$(OUTDIR)\node_hometree.beam : node_hometree.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_hometree.erl
+
+$(OUTDIR)\node_hometree_odbc.beam : node_hometree_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_hometree_odbc.erl
+
+$(OUTDIR)\node_mb.beam : node_mb.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_mb.erl
+
+$(OUTDIR)\node_pep.beam : node_pep.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_pep.erl
+
+$(OUTDIR)\node_pep_odbc.beam : node_pep_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_pep_odbc.erl
+
+$(OUTDIR)\node_private.beam : node_private.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_private.erl
+
+$(OUTDIR)\node_public.beam : node_public.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_public.erl
+
+$(OUTDIR)\nodetree_dag.beam : nodetree_dag.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_dag.erl
+
+$(OUTDIR)\nodetree_tree.beam : nodetree_tree.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_tree.erl
+
+$(OUTDIR)\nodetree_tree_odbc.beam : nodetree_tree_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_tree_odbc.erl
+
+$(OUTDIR)\nodetree_virtual.beam : nodetree_virtual.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_virtual.erl
+
+$(OUTDIR)\pubsub_db_odbc.beam : pubsub_db_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) pubsub_db_odbc.erl
+
+$(OUTDIR)\pubsub_index.beam : pubsub_index.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) pubsub_index.erl
+
+$(OUTDIR)\pubsub_subscription.beam : pubsub_subscription.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) pubsub_subscription.erl
+
+$(OUTDIR)\pubsub_subscription_odbc.beam : pubsub_subscription_odbc.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) pubsub_subscription_odbc.erl
diff --git a/src/mod_pubsub_ng/exmpp_pubsub.erl b/src/mod_pubsub_ng/exmpp_pubsub.erl
new file mode 100644
index 000000000..19bc1d76d
--- /dev/null
+++ b/src/mod_pubsub_ng/exmpp_pubsub.erl
@@ -0,0 +1,685 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(exmpp_pubsub).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+
+%% --------------------------------------------------------------------
+%% Documentation / type definitions.
+%% --------------------------------------------------------------------
+
+
+
+%-- Affiliations --%
+-export_type([
+ affiliation/0,
+ %%
+ affiliation_owner/0,
+ affiliation_publisher/0,
+ affiliation_publish_only/0,
+ affiliation_member/0,
+ affiliation_none/0,
+ affiliation_outcast/0
+]).
+
+%% @type affiliation_owner() = 'owner'.
+-type(affiliation_owner() :: 'owner').
+
+%% @type affiliation_publisher() = 'publisher'.
+-type(affiliation_publisher() :: 'publisher').
+
+%% @type affiliation_publish_only() = 'publish-only'.
+-type(affiliation_publish_only() :: 'publish-only').
+
+%% @type affiliation_member() = 'member'.
+-type(affiliation_member() :: 'member').
+
+%% @type affiliation_none() = 'none'.
+-type(affiliation_none() :: 'none').
+
+%% @type affiliation_outcast() = 'outcast'.
+-type(affiliation_outcast() :: 'outcast').
+
+%% @type affiliation() = Owner | Publisher | Publish_Only | Member | None | Outcast
+%% Owner = exmpp_pubsub:affiliation_owner()
+%% Publisher = exmpp_pubsub:affiliation_publisher()
+%% Publish_Only = exmpp_pubsub:affiliation_publish_only()
+%% Member = exmpp_pubsub:affiliation_member()
+%% None = exmpp_pubsub:affiliation_none()
+%% Outcast = exmpp_pubsub:affiliation_outcast().
+-type(affiliation() :: exmpp_pubsub:affiliation_owner()
+ | exmpp_pubsub:affiliation_publisher()
+ | exmpp_pubsub:affiliation_publish_only()
+ | exmpp_pubsub:affiliation_member()
+ | exmpp_pubsub:affiliation_none()
+ | exmpp_pubsub:affiliation_outcast()
+).
+
+
+%%
+
+%-- Things identifiers --%
+-export_type([
+ index/0,
+ itemId/0,
+ itemIds/0,
+ level/0,
+ nodeId/0,
+ nodeIdx/0,
+ subId/0,
+ plugin/0
+]).
+
+%% @type index() = 'node'.
+-type(index() :: 'node' ).
+
+%% @type itemId() = binary().
+-type(itemId() :: binary()).
+
+%% @type itemIds() = [ItemId::exmpp_pubsub:itemId(),...].
+-type(itemIds() :: [ItemId::exmpp_pubsub:itemId(),...]).
+
+%% @type level() = non_neg_integer().
+-type(level() :: non_neg_integer()).
+
+%% @type nodeId() = binary().
+-type(nodeId() :: binary()).
+
+%% @type nodeIdx() = non_neg_integer().
+-type(nodeIdx() :: non_neg_integer()).
+
+%% @type plugin() = module().
+-type(plugin() :: module()).
+
+%% @type subId() = binary().
+-type(subId() :: binary()).
+
+
+-export_type([
+ t_now/0
+]).
+
+%% Temporary -type, until calendar.erl -type is exported
+-type(t_now()
+ :: {MegaSec::non_neg_integer(),
+ Sec::non_neg_integer(),
+ MilliSec::non_neg_integer()}
+).
+
+
+%%
+-export_type([
+ feature/0,
+ features/0,
+ pubsub_feature/0,
+ pubsub_features/0
+]).
+
+-type(feature() :: binary()).
+
+-type(features() :: Features::[Feature::exmpp_pubsub:feature(),...]).
+
+-type(pubsub_feature() :: exmpp_pubsub:feature()).
+
+-type(pubsub_features() :: Pubsub_Features::[Pubsub_Feature::exmpp_pubsub:feature(),...]).
+
+%%
+
+%-- Payload types --%
+-export_type([
+ payload/0,
+ %%
+ payload_empty/0,
+ payload_full/0
+]).
+
+%% @type payload_empty() = [].
+-type(payload_empty() :: []).
+
+%% @type payload_full() = #xmlel{}.
+-type(payload_full() :: xmlel()).
+
+%% @type payload() = Empty_Payload | Full_Payload
+%% Empty_Payload = exmpp_pubsub:payload_empty()
+%% Full_Payload = exmpp_pubsub:payload_full().
+-type(payload() :: exmpp_pubsub:payload_empty()
+ | exmpp_pubsub:payload_full()
+).
+
+
+%%
+
+%-- Pusbub Host --%
+-export_type([
+ host/0,
+ %%
+ host_pubsub/0,
+ host_pep/0
+]).
+
+%% @type host_pubsub() = xmpp_jid:domain_jid().
+%% ```<<"pubsub.shakespeare.lit">>'''
+%% Identifier type of the Pubsub service
+-type(host_pubsub() :: binary()).
+
+%% @type host_pep() = xmpp_jid:usr_contact_bare().
+%% ```{<<"juliet">>, <<"capulet.lit">>, undefined}'''
+%% Identifier type of the Pubsub-On-Jid service
+-type(host_pep() :: xmpp_jid:usr_contact_bare()).
+
+%% @type host() = Pubsub_Host | Pubsub_On_Jid_Host
+%% Pubsub_Host = exmpp_pubsub:host_pubsub()
+%% Pubsub_On_Jid_Host = exmpp_pubsub:host_pep().
+%% Identifier types of the Pubsub and PEP (Pubsub-On-Jid) services
+%% ```* Pubsub_Host : <<"pubsub.shakespeare.lit">>
+%% * Pubsub_PEP : {<<"juliet">>, <<"capulet.lit">>, undefined}'''
+-type(host() :: exmpp_pubsub:host_pubsub()
+ | exmpp_pubsub:host_pep()
+).
+
+
+%%
+
+%-- Subscription States --%
+-export_type([
+ subscription_state/0,
+ %%
+ subscription_state_none/0,
+ subscription_state_pending/0,
+ subscription_state_unconfigured/0,
+ subscription_state_subscribed/0
+]).
+
+%% @type subscription_state_none() = 'none'.
+-type(subscription_state_none() :: 'none').
+
+%% @type subscription_state_pending() = 'pending'.
+-type(subscription_state_pending() :: 'pending').
+
+%% @type subscription_state_unconfigured() = 'unconfigured'.
+-type(subscription_state_unconfigured() :: 'unconfigured').
+
+%% @type subscription_state_subscribed() = 'subscribed'.
+-type(subscription_state_subscribed() :: 'subscribed').
+
+%% @type subscription_state() = None | Pending | Unconfigured | Subscribed
+%% None = exmpp_pubsub:subscription_state_none()
+%% Pending = exmpp_pubsub:subscription_state_pending()
+%% Unconfigured = exmpp_pubsub:subscription_state_unconfigured()
+%% Subscribed = exmpp_pubsub:subscription_state_subscribed()
+-type(subscription_state() :: exmpp_pubsub:subscription_state_pending()
+ | exmpp_pubsub:subscription_state_unconfigured()
+ | exmpp_pubsub:subscription_state_subscribed()
+).
+
+%%
+
+%-- Subscription -- %%
+-export_type([
+ subscriptions/0,
+ subscription/0,
+ %%
+ subscription_subscribed/0,
+ subscription_unconfigured/0,
+ subscription_pending/0
+]).
+
+-type(subscription_subscribed()
+ :: {Subscription_State :: exmpp_pubsub:subscription_state_subscribed(),
+ SubId :: exmpp_pubsub:subId(),
+ Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: [] | pubsub_options:options_subscription()}
+).
+
+-type(subscription_unconfigured()
+ :: {Subscription_State :: exmpp_pubsub:subscription_state_unconfigured(),
+ SubId :: exmpp_pubsub:subId(),
+ Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: []}
+).
+
+-type(subscription_pending()
+ :: {Subscription_State :: exmpp_pubsub:subscription_state_pending(),
+ SubId :: exmpp_pubsub:subId(),
+ Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: []}
+).
+
+-type(subscription()
+ :: {Subscription_State :: exmpp_pubsub:subscription_state(),
+ SubId :: exmpp_pubsub:subId(),
+ Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: [] | pubsub_options:options_subscription()}
+%% :: exmpp_pubsub:subscription_subscribed()
+%% | exmpp_pubsub:subscription_pending()
+%% | exmpp_pubsub:subscription_unconfigured()
+).
+
+-type(subscriptions()
+ :: [exmpp_pubsub:subscription(),...]
+%% :: [exmpp_pubsub:subscription_subscribed(),...]
+%% | [exmpp_pubsub:subscription_unconfigured(),...]
+%% | [exmpp_pubsub:subscription_pending(),...]
+).
+
+%% TODO : commented -type unions return the following dialyzer error (bug ?) :
+%% ...
+%% Adding information from /home/meta/otp-exmpp.plt to ejabberd.plt...
+%%=ERROR REPORT==== 29-Jul-2011::15:14:42 ===
+%Error in process <0.2604.0> with exit value: {function_clause,[{erl_types,inf_tuples_in_sets,4},{erl_types,inf_tuple_sets,4},{erl_types,inf_tuple_sets,3},{erl_types,t_inf,3},{erl_types,t_inf_lists_strict,4},{erl_types,t_inf,3},{dialyzer_typesig,solve_one_c...
+%%
+%%
+%%dialyzer: Analysis failed with error: {function_clause,[{erl_types,inf_tuples_in_sets,4},
+%% {erl_types,inf_tuple_sets,4},
+%% {erl_types,inf_tuple_sets,3},
+%% {erl_types,t_inf,3},
+%% {erl_types,t_inf_lists_strict,4},
+%% {erl_types,t_inf,3},
+%% {dialyzer_typesig,solve_one_c,...},
+%% {dialyzer_typesig,...}]}
+%%Last messages in the log cache:
+%% Typesig analysis for SCC: [{ejabberd_web_admin,make_xhtml,5}]
+%% Typesig analysis for SCC: [{ejabberd_web_admin,make_xhtml,4}]
+%% Typesig analysis for SCC: [{ejabberd_web_admin,process_admin,2}]
+%% Typesig analysis for SCC: [{ejabberd_web_admin,process,2}]
+%% Typesig analysis for SCC: [{mod_stats,get_local_stat,3}]
+%% Typesig analysis for SCC: [{mod_stats,get_local_stats,3}]
+%% Typesig analysis for SCC: [{mod_stats,process_local_iq,3}]
+%% Typesig analysis for SCC: [{mod_register_web,send_registration_notifications,2}]
+%% Typesig analysis for SCC: [{mod_register_web,process,2}]
+%% Typesig analysis for SCC: [{pubsub_db,create_node,5}]
+
+%%
+
+
+%% Pubsub #xmlel{} templates
+
+
+%% --------------------------------------------------------------------
+%% Functions.
+%% --------------------------------------------------------------------
+
+
+
+
+%% @spec nodeId() -> NodeId::exmpp_pubsub:nodeId()
+-spec(nodeId/0 :: () -> NodeId::exmpp_pubsub:nodeId()).
+
+nodeId() ->
+ randoms:get_string().
+
+%% @spec subId() -> SubId::exmpp_pubsub:subId()
+-spec(subId/0 :: () -> SubId::exmpp_pubsub:subId()).
+
+subId() ->
+ {T1, T2, T3} = now(),
+ list_to_binary(lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]))).
+
+%% @spec itemId() -> ItemId::exmpp_pubsub:itemId()
+-spec(itemId/0 :: () -> ItemId::exmpp_pubsub:itemId()).
+
+itemId() ->
+ {T1, T2, T3} = now(),
+ list_to_binary(lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]))).
+
+id() ->
+ {T1, T2, T3} = now(),
+ list_to_binary(lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]))).
+
+
+
+%%
+-spec(subscription_subscribed/2 ::
+(
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: [] | pubsub_options:options_subscription())
+ -> Subscription_Subscribed::exmpp_pubsub:subscription_subscribed()
+).
+
+subscription_subscribed(Resource, Subscription_Options) ->
+ {'subscribed', subId(), Resource, Subscription_Options}.
+
+%%
+-spec(subscription_pending/2 ::
+(
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: [] | pubsub_options:options_subscription())
+ -> Subscription_Pending::exmpp_pubsub:subscription_pending()
+).
+
+subscription_pending(Resource, Subscription_Options) ->
+ {'pending', subId(), Resource, Subscription_Options}.
+
+
+
+%% Pubsub Xmlel templates
+
+xmlcdata(CData) ->
+ {xmlcdata, CData}.
+
+xmlattr(Name, Value) ->
+ {Name, Value}.
+
+
+xmlel('disco#info', Name, Attrs, Children) ->
+ xmlel(?NS_DISCO_INFO, Name, Attrs, Children);
+xmlel('disco#items', Name, Attrs, Children) ->
+ xmlel(?NS_DISCO_ITEMS, Name, Attrs, Children);
+xmlel('jabber:client', Name, Attrs, Children) ->
+ xmlel(?NS_JABBER_CLIENT, Name, Attrs, Children);
+xmlel('pubsub', Name, Attrs, Children) ->
+ xmlel(?NS_PUBSUB, Name, Attrs, Children);
+xmlel('pubsub#event', Name, Attrs, Children) ->
+ xmlel(?NS_PUBSUB_EVENT, Name, Attrs, Children);
+xmlel('pubsub#owner', Name, Attrs, Children) ->
+ xmlel(?NS_PUBSUB_OWNER, Name, Attrs, Children);
+xmlel('shim', Name, Attrs, Children) ->
+ xmlel(?NS_SHIM, Name, Attrs, Children);
+xmlel(NS, Name, Attrs, Children) ->
+ #xmlel{name = Name, attrs = [{<<"xmlns">>, NS} | Attrs], children = Children}.
+%%
+xmlattr_affiliation(Affiliation) ->
+ xmlattr(<<"affiliation">>, list_to_binary(atom_to_list(Affiliation))).
+%%
+xmlattr_category(Category) ->
+ xmlattr(<<"category">>, Category).
+%%
+xmlattr_id(ItemId) ->
+ xmlattr(<<"id">>, ItemId).
+%%
+xmlattr_jid(Jid) ->
+ xmlattr(<<"jid">>, Jid).
+%%
+xmlattr_name(Name) ->
+ xmlattr(<<"name">>, Name).
+%%
+xmlattr_node(NodeId) ->
+ xmlattr(<<"node">>, NodeId).
+%%
+xmlattr_publisher(Publisher) ->
+ xmlattr(<<"publisher">>, Publisher).
+%%
+xmlattr_subid(SubId) ->
+ xmlattr(<<"subid">>, SubId).
+%%
+xmlattr_subscription(Subscription_State) ->
+ xmlattr(<<"subscription">>, list_to_binary(atom_to_list(Subscription_State))).
+%%
+xmlattr_type(Type) when is_atom(Type)->
+ xmlattr(<<"type">>, list_to_binary(atom_to_list(Type)));
+%%
+xmlattr_type(Type) ->
+ xmlattr(<<"type">>, Type).
+%%
+xmlattr_uri(URI) ->
+ xmlattr(<<"uri">>, URI).
+%%
+xmlattr_var(Var) ->
+ xmlattr(<<"var">>, Var).
+
+%%
+xmlel_affiliation('pubsub', NodeId, Affiliation) ->
+ xmlel('pubsub', <<"affiliation">>,
+ [xmlattr_node(NodeId),
+ xmlattr_affiliation(Affiliation)],
+ []);
+xmlel_affiliation('pubsub#owner', Jid, Affiliation) ->
+ xmlel('pubsub#owner', <<"affiliation">>,
+ [xmlattr_jid(Jid),
+ xmlattr_affiliation(Affiliation)],
+ []).
+%%
+xmlel_affiliations('pubsub', Xmlels) ->
+ xmlel('pubsub', <<"affiliations">>, [], Xmlels).
+
+xmlel_affiliations('pubsub#owner', NodeId, Xmlels) ->
+ xmlel('pubsub#owner', <<"affiliations">>, [xmlattr_node(NodeId)], Xmlels).
+%%
+xmlel_create(NodeId) ->
+ xmlel('pubsub', <<"create">>, [xmlattr_node(NodeId)], []).
+
+%%
+xmlel_configure('pubsub#owner', NodeId, Xmlels) ->
+ xmlel('pubsub#owner', <<"configure">>, [xmlattr_node(NodeId)], Xmlels).
+
+%%
+xmlel_default('pubsub', Xmlels) ->
+ xmlel('pubsub', <<"default">>, [], Xmlels);
+%%
+xmlel_default('pubsub#owner', Xmlels) ->
+ xmlel('pubsub#owner', <<"default">>, [], Xmlels).
+
+%%
+xmlel_default('pubsub', NodeId, Xmlels) ->
+ xmlel('pubsub', <<"default">>, [xmlattr_node(NodeId)], Xmlels).
+
+%%
+xmlel_delete(NodeId, RedirectURI) ->
+ xmlel('pubsub#event', <<"delete">>,
+ [xmlattr_node(NodeId)],
+ case RedirectURI of
+ undefined -> [];
+ _RedirectURI -> [xmlel_redirect(RedirectURI)]
+ end).
+%%
+xmlel_event(Xmlels) ->
+ xmlel('pubsub#event', <<"event">>, [], Xmlels).
+%%
+xmlel_feature('disco#info', Feature) ->
+ xmlel('disco#info', <<"feature">>, [xmlattr_var(Feature)], []).
+%%
+xmlel_header(SubId) ->
+ xmlel('shim', <<"header">>, [xmlattr_name(<<"SubID">>)], [xmlcdata(SubId)]).
+%%
+xmlel_headers(Xmlels_Header) ->
+ xmlel('shim', <<"headers">>, [], Xmlels_Header).
+
+
+xmlel_identity('disco#info', Category, Type, Name) ->
+ xmlel('disco#info', <<"identity">>,
+ case Name of
+ undefined ->
+ [xmlattr_category(Category),
+ xmlattr_type(Type)];
+ _ ->
+ [xmlattr_category(Category),
+ xmlattr_name(Name),
+ xmlattr_type(Type)]
+ end,
+ []).
+
+%%
+xmlel_item('pubsub', ItemId) ->
+ xmlel('pubsub', <<"item">>, [xmlattr_id(ItemId)], []).
+
+%%
+xmlel_item('disco#items', ItemId, Jid) ->
+ xmlel('disco#items', <<"item">>,
+ [xmlattr_name(ItemId),
+ xmlattr_jid(Jid)],
+ []);
+xmlel_item('pubsub', ItemId, Payload) ->
+ xmlel('pubsub', <<"item">>, [xmlattr_id(ItemId)],
+ case Payload of
+ [] -> [];
+ _Payload -> [Payload]
+ end).
+
+xmlel_item('disco#items', NodeId, Jid, Name) ->
+ xmlel('disco#items', <<"item">>,
+ case Name of
+ undefined ->
+ [xmlattr_jid(Jid),
+ xmlattr_node(NodeId)];
+ _ ->
+ [xmlattr_jid(Jid),
+ xmlattr_node(NodeId),
+ xmlattr_name(Name)]
+ end,
+ []);
+%%
+xmlel_item('pubsub#event', ItemId, Publisher, Payload) ->
+ xmlel('pubsub#event', <<"item">>,
+ case Publisher of
+ undefined ->
+ [xmlattr_id(ItemId)];
+ _Publisher ->
+ [xmlattr_id(ItemId),
+ xmlattr_publisher(Publisher)]
+ end,
+ case Payload of
+ [] -> [];
+ _Payload -> [Payload]
+ end).
+%%
+xmlel_items('pubsub', NodeId, Xmlels) ->
+ xmlel('pubsub', <<"items">>, [xmlattr_node(NodeId)], Xmlels);
+%%
+xmlel_items('pubsub#event', undefined = _NodeId, Xmlels) ->
+ xmlel('pubsub#event', <<"items">>, [], Xmlels);
+%%
+xmlel_items('pubsub#event', NodeId, Xmlels) ->
+ xmlel('pubsub#event', <<"items">>, [xmlattr_node(NodeId)], Xmlels).
+%%
+xmlel_items('pubsub#event', NodeId, undefined = _ItemId, _Publisher, _Payload) ->
+ xmlel('pubsub#event', <<"items">>, [xmlattr_node(NodeId)], []);
+xmlel_items('pubsub#event', NodeId, ItemId, Publisher, Payload) ->
+ xmlel('pubsub#event', <<"items">>,
+ [xmlattr_node(NodeId)],
+ [xmlel_item('pubsub#event', ItemId, Publisher, Payload)]).
+%%
+xmlel_message(undefined = _Type, Xmlels) ->
+ xmlel_message('normal', Xmlels);
+xmlel_message(Type, Xmlels) ->
+ xmlel('jabber:client', <<"message">>,
+ [xmlattr_type(Type),
+ xmlattr_id(_Id = id())],
+ Xmlels).
+%%
+xmlel_options('pubsub', NodeId, Jid, SubId, Xmlels) ->
+ xmlel('pubsub', <<"options">>,
+ case SubId of
+ undefined ->
+ [xmlattr_node(NodeId),
+ xmlattr_jid(Jid)];
+ _ ->
+ [xmlattr_node(NodeId),
+ xmlattr_jid(Jid),
+ xmlattr_subid(SubId)]
+ end,
+ Xmlels).
+%%
+xmlel_publish('pubsub', NodeId, Xmlels) ->
+ xmlel('pubsub', <<"publish">>, [xmlattr_node(NodeId)], Xmlels).
+%%
+xmlel_purge(NodeId) ->
+ xmlel('pubsub#event', <<"purge">>, [xmlattr_node(NodeId)], []).
+
+%%
+xmlel_query('disco#info', Xmlels) ->
+ xmlel('disco#info', <<"query">>, [], Xmlels);
+%%
+xmlel_query('disco#items', Xmlels) ->
+ xmlel('disco#items', <<"query">>, [], Xmlels).
+
+%%
+xmlel_query('disco#info', NodeId, Xmlels) ->
+ xmlel('disco#info', <<"query">>, [xmlattr_node(NodeId)], Xmlels);
+xmlel_query('disco#items', NodeId, Xmlels) ->
+ xmlel('disco#items', 'query', [xmlattr_node(NodeId)], Xmlels).
+
+%%
+xmlel_redirect(RedirectURI) ->
+ xmlel('pubsub#event', <<"redirect">>, [xmlattr_uri(RedirectURI)], []).
+%%
+xmlel_retract(undefined = _ItemId) ->
+ xmlel('pubsub#event', <<"retract">>, [], []);
+xmlel_retract(ItemId) ->
+ xmlel('pubsub#event', <<"retract">>, [xmlattr_id(ItemId)], []).
+%%
+xmlel_subscription('pubsub', NodeId, Jid, SubId, Subscription_State) ->
+ xmlel('pubsub', <<"subscription">>,
+ [xmlattr_node(NodeId),
+ xmlattr_jid(Jid),
+ xmlattr_subid(SubId),
+ xmlattr_subscription(Subscription_State)],
+ []);
+xmlel_subscription('pubsub#event', NodeId, Jid, SubId, Subscription_State) ->
+ xmlel('pubsub#event', <<"subscription">>,
+ case SubId of
+ undefined ->
+ [xmlattr_node(NodeId),
+ xmlattr_jid(Jid),
+ xmlattr_subscription(Subscription_State)];
+ _SubId ->
+ [xmlattr_node(NodeId),
+ xmlattr_jid(Jid),
+ xmlattr_subid(SubId),
+ xmlattr_subscription(Subscription_State)]
+ end,
+ []).
+%%
+xmlel_subscription('pubsub#owner', Jid, SubId, Subscription_State) ->
+ xmlel('pubsub#owner', <<"subscription">>,
+ case SubId of
+ undefined ->
+ [xmlattr_jid(Jid),
+ xmlattr_subscription(Subscription_State)];
+ _SubId ->
+ [xmlattr_jid(Jid),
+ xmlattr_subid(SubId),
+ xmlattr_subscription(Subscription_State)]
+ end,
+ []).
+%%
+xmlel_subscriptions('pubsub', Xmlels) ->
+ xmlel('pubsub', <<"subscriptions">>, [], Xmlels).
+
+xmlel_subscriptions('pubsub#owner', NodeId, Xmlels) ->
+ xmlel('pubsub#owner', <<"subscriptions">>, [xmlattr_node(NodeId)], Xmlels).
+%%
+xmlel_publish(NodeId, undefined = _ItemId) ->
+ xmlel('pubsub', <<"publish">>, [xmlattr_node(NodeId)], []);
+xmlel_publish(NodeId, ItemId) ->
+ xmlel('pubsub', <<"publish">>,
+ [xmlattr_node(NodeId)],
+ [xmlel_item('pubsub', ItemId)]).
+%%
+xmlel_pubsub('pubsub', Children) ->
+ xmlel('pubsub', <<"pubsub">>, [], Children);
+%%
+xmlel_pubsub('pubsub#owner', Children) ->
+ xmlel('pubsub#owner', <<"pubsub">>, [], Children).
+
diff --git a/src/mod_pubsub_ng/mod_pubsub_dev.erl b/src/mod_pubsub_ng/mod_pubsub_dev.erl
new file mode 100644
index 000000000..9faca6552
--- /dev/null
+++ b/src/mod_pubsub_ng/mod_pubsub_dev.erl
@@ -0,0 +1,863 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(mod_pubsub_dev).
+-author('christophe.romain@process-one.net').
+-author('karim.gemayel@process-one.net').
+-version('1.13-0').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+-compile(export_all).
+%%% Export Functions
+
+%% API and gen_server callbacks
+-export([
+ start_link/2,
+ start/2,
+ stop/1,
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3
+]).
+
+%%
+-export_type([
+ resource_show/0,
+ presence_cache/0
+]).
+
+-type(resource_show()
+ :: {Resource :: xmpp_jid:resource_jid(),
+ Show :: 'away' | 'chat' | 'dnd' | 'online' | 'xa' }
+).
+
+-type(presence_cache() :: [Resource_Show::resource_show()]).
+
+%%
+-export_type([
+ resource_subids/0,
+ resources_subids/0
+]).
+
+-type(resource_subids()
+ :: {Resource :: xmpp_jid:resource_jid(),
+ SubIds :: [SubId::exmpp_pubsub:subId(),...]}
+).
+
+-type(resources_subids() :: [Resource_SubIds::resource_subids(),...]).
+
+%%%
+-export_type([
+ entity/0,
+ n0de/0,
+ cache/0,
+ subids/0,
+ event/0
+]).
+
+-type(entity() :: #entity{}).
+% id :: xmpp_jid:usr_bare(),
+% affiliation :: 'member' | 'owner' | 'publisher',
+% subscriptions :: []%[exmpp_pubsub:subscription(),...]
+%}).
+
+%%%
+-type(n0de() :: #node{}).
+% id :: exmpp_pubsub:nodeId(),
+% owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+% access_model :: pubsub_options:access_model(),
+% itemreply :: 'owner' | 'publisher',
+% notification_type :: 'headline' | 'normal',
+% presence_based_delivery :: boolean(),
+% rosters_groups_allowed :: [] | pubsub_options:rosters_groups_allowed()
+%}).
+
+%%%
+-type(cache() :: #cache{}).
+% presence :: undefined | mod_pubsub_dev:presence_cache(),
+% presence_subscriptions :: undefined | boolean(),
+% rosters_groups :: undefined | boolean()
+%}).
+
+%%%
+-type(subids() :: #subids{}).
+% presence :: undefined | [] | mod_pubsub_dev:resources_subids(),
+% no_presence :: undefined | [] | mod_pubsub_dev:resources_subids()
+%}).
+
+%%
+-type(event() :: #event{
+% host :: xmpp_jid:raw_jid_component_bare(),
+% component :: xmpp_jid:component_bare(),
+% entity :: mod_pubsub_dev:entity(),
+% node :: mod_pubsub_dev:n0de(),
+% cache :: mod_pubsub_dev:cache(),
+% subids :: mod_pubsub_dev:subids()
+}).
+
+%%%
+-export_type([
+ item/0,
+ published_item/0,
+ retracted_item/0
+]).
+
+-type(published_item() :: #item{
+ access_model :: undefined | pubsub_options:access_model(),
+ presence_based_delivery :: undefined | boolean(),
+ rosters_groups_allowed :: [] | pubsub_options:rosters_groups_allowed(),
+ stanza :: xmlel()
+}).
+
+-type(retracted_item() :: #item{
+ access_model :: undefined | pubsub_options:access_model(),
+ presence_based_delivery :: undefined | boolean(),
+ rosters_groups_allowed :: [] | pubsub_options:rosters_groups_allowed(),
+ stanza :: xmlel()
+}).
+
+-type(item() :: published_item() | retracted_item()).
+
+
+%%
+
+-export_type([
+ pubsub_itemId/0,
+ pubsub_nodeId/0,
+ pubsub_stateId/0,
+ item_owners/0,
+ node_owners/0,
+ item_creation/0,
+ item_modification/0,
+ node_creation/0
+]).
+
+-type(pubsub_itemId()
+ :: {ItemId::exmpp_pubsub:itemId(), NodeIdx::exmpp_pubsub:nodeIdx()}
+).
+
+-type(pubsub_nodeId()
+ :: {Pubsub_Host :: exmpp_pubsub:host(), NodeId :: exmpp_pubsub:nodeId()}
+).
+
+-type(pubsub_stateId()
+ :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()}
+).
+
+-type(item_owners() :: [Entity::xmpp_jid:usr_bare(),...]).
+-type(node_owners() :: [Entity::xmpp_jid:usr_bare(),...]).
+
+-type(item_creation()
+ :: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()}
+).
+
+-type(item_modification()
+ :: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()}
+).
+
+-type(node_creation()
+ :: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()}
+).
+
+
+
+%%
+-export_type([
+ pubsub_state/0,
+ pubsub_states/0,
+ %
+ pubsub_state_owner/0,
+ pubsub_state_publisher/0,
+ pubsub_state_publish_only/0,
+ pubsub_state_member/0,
+ pubsub_state_none/0,
+ pubsub_state_outcast/0
+]).
+
+-type(pubsub_state_owner()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'owner',
+ access :: undefined | 'presence' | 'roster',
+ %subscriptions :: [exmpp_pubsub:subscription_subscribed() |
+ % exmpp_pubsub:subscription_unconfigured()],
+ subscriptions :: [exmpp_pubsub:subscription()],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+).
+
+%%
+-type(pubsub_state_publisher()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'publisher',
+ access :: undefined | 'presence' | 'roster',
+ %subscriptions :: [exmpp_pubsub:subscription_subscribed() |
+ % exmpp_pubsub:subscription_unconfigured()],
+ subscriptions :: [exmpp_pubsub:subscription()],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+).
+
+%%
+-type(pubsub_state_publish_only()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'publish-only',
+ access :: undefined | 'presence' | 'roster',
+ subscriptions :: [],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+).
+
+%%
+-type(pubsub_state_member()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'member',
+ access :: undefined | 'pending' | 'presence' | 'roster',
+ subscriptions :: [],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+ %%
+ | #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'member',
+ access :: undefined | 'pending' | 'presence' | 'roster',
+ %subscriptions :: [exmpp_pubsub:subscription_pending(),...],
+ subscriptions :: [exmpp_pubsub:subscription()],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+ %%
+ | #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'member',
+ subscriptions :: [exmpp_pubsub:subscription()],
+ %subscriptions :: [exmpp_pubsub:subscription_pending() |
+ % exmpp_pubsub:subscription_subscribed() |
+ % exmpp_pubsub:subscription_unconfigured()],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+).
+
+%%
+-type(pubsub_state_none()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'none',
+ access :: undefined | 'presence' | 'roster',
+ subscriptions :: [],
+ itemids :: []
+ }
+ %%
+ | #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'none',
+ access :: undefined | 'presence' | 'roster',
+ subscriptions :: [],
+ itemids :: exmpp_pubsub:itemIds()
+ }
+ %%
+ | #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'none',
+ access :: undefined | 'presence' | 'roster',
+ subscriptions :: [],
+ itemids :: exmpp_pubsub:itemIds()
+ }
+).
+
+%%
+-type(pubsub_state_outcast()
+ :: #pubsub_state_dev{
+ id :: mod_pubsub_dev:pubsub_stateId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ affiliation :: 'outcast',
+ access :: undefined | 'presence' | 'roster',
+ subscriptions :: [exmpp_pubsub:subscription()],
+ %subscriptions :: [exmpp_pubsub:subscription_pending() |
+ % exmpp_pubsub:subscription_subscribed() |
+ % exmpp_pubsub:subscription_unconfigured()],
+ itemids :: [] | exmpp_pubsub:itemIds()
+ }
+).
+
+
+%%
+-type(pubsub_state() :: mod_pubsub_dev:pubsub_state_owner()
+ | mod_pubsub_dev:pubsub_state_publisher()
+ | mod_pubsub_dev:pubsub_state_publish_only()
+ | mod_pubsub_dev:pubsub_state_member()
+ | mod_pubsub_dev:pubsub_state_none()
+ | mod_pubsub_dev:pubsub_state_outcast()
+).
+
+-type(pubsub_states() :: [Pubsub_State::mod_pubsub_dev:pubsub_state(),...]).
+
+
+%%
+-export_type([
+ pubsub_node/0,
+ pubsub_nodes/0
+]).
+
+-type(pubsub_node()
+ :: #pubsub_node_dev{
+ id :: mod_pubsub_dev:pubsub_nodeId(),
+ idx :: exmpp_pubsub:nodeIdx(),
+ creation :: mod_pubsub_dev:node_creation(),
+ level :: exmpp_pubsub:level(),
+ owners :: mod_pubsub_dev:node_owners(),
+ itemids :: [] | exmpp_pubsub:itemIds(),
+ options :: pubsub_options:options_node()
+ }
+).
+
+-type(pubsub_nodes() :: [Pubsub_Node::mod_pubsub_dev:pubsub_node(),...]).
+
+
+%%
+-export_type([
+ pubsub_item/0,
+ pubsub_items/0
+]).
+
+-type(pubsub_item()
+ :: #pubsub_item_dev{
+ id :: mod_pubsub_dev:pubsub_itemId(),
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ owners :: mod_pubsub_dev:item_owners(),
+ creation :: mod_pubsub_dev:item_creation(),
+ modification :: mod_pubsub_dev:item_modification(),
+ payload :: exmpp_pubsub:payload(),
+ options :: [] | pubsub_options:options_item()
+ }
+).
+
+-type(pubsub_items() :: [Pubsub_Item::mod_pubsub_dev:pubsub_item(),...]).
+
+%%
+-export_type([
+ pubsub_last_item/0
+]).
+
+-type(pubsub_last_item()
+ :: #pubsub_last_item_dev{
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ id :: exmpp_pubsub:itemId(),
+ owners :: mod_pubsub_dev:item_owners(),
+ creation :: mod_pubsub_dev:item_creation(),
+ payload :: exmpp_pubsub:payload(),
+ options :: [] | pubsub_options:options_item()
+ }
+).
+
+%%
+-export_type([
+ pubsub_subscription_pending/0
+]).
+
+-type(pubsub_subscription_pending()
+ :: #pubsub_subscription_pending{
+ id :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()},
+ nodeidx :: exmpp_pubsub:nodeIdx(),
+ subids :: [SubId::exmpp_pubsub:subId(),...]
+ }
+).
+
+
+
+
+
+-define(PROCNAME, ejabberd_mod_pubsub_dev).
+
+
+
+
+
+
+
+-record(state,
+{
+ host :: xmpp_jid:raw_jid_component_bare(),
+ pubsub_host :: xmpp_jid:raw_jid_component_bare(),
+ pubsub :: #capabilities{},
+ pubsub_on_jid :: #capabilities{}
+}).
+
+-type(state()
+ :: #state{
+ host :: xmpp_jid:raw_jid_component_bare(),
+ pubsub_host :: xmpp_jid:raw_jid_component_bare(),
+ pubsub :: #capabilities{},
+ pubsub_on_jid :: #capabilities{}
+ }
+).
+
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+
+%% @spec start_link(Host, Options) -> {'ok', pid()} | 'ignore' | {'error', atom()}
+%% Host = string()
+%% Options = {Option::atom(), Value::term()}
+-spec(start_link/2 ::
+(
+ Host :: string(),
+ Options :: [Option::{Key::atom(), Value::term()}])
+ -> {'ok', pid()} | 'ignore' | {'error', _}
+).
+
+start_link(Host, Options) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Options], []).
+
+%% @spec start(Host, Options) -> {'error',atom()} | {'ok','undefined' | pid()} | {'ok','undefined' | pid(),any()}
+%% Host = string()
+%% Options = {Option::atom(), Value::term()}
+-spec(start/2 ::
+(
+ Host :: string(),
+ Options :: [Option::{Key::atom(), Value::term()}])
+ -> {'error',_} | {'ok','undefined' | pid()} | {'ok','undefined' | pid(),_}
+).
+
+start(Host, Options) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc,
+ {?MODULE, start_link, [Host, Options]},
+ transient, 1000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+%% @spec stop(Host) -> 'ok' | {'error','not_found' | 'running' | 'simple_one_for_one'}
+%% Host = string()
+-spec(stop/1 ::
+(
+ Host :: string())
+ -> 'ok' | {'error','not_found' | 'running' | 'simple_one_for_one'}
+).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:call(Proc, stop),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+
+
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+-spec(init/1 ::
+(
+ Args :: [binary() | [{Option::atom(), Value::term()}]])
+ -> {'ok', State::state()}
+).
+
+init([Host, Options] = _Args) ->
+ ?DEBUG("pubsub init ~p ~p",[Host, Options]),
+ Pubsub_Host = gen_mod:expand_host_name(Host, Options, <<"pubsub">>),
+ IQDisc = gen_mod:get_opt('iqdisc', Options, 'one_queue'),
+
+ % mod_disco:register_feature(Server_HostB, ?NS_PUBSUB_s),
+ %%
+ %% Pubsub Hooks
+ %% - Service disco hooks
+ %% * disco_local_identity
+ %% * disco_local_features
+ %% * disco_local_items
+ %% * disco_local_forms
+ %% - Connectivity hooks
+ %% * presence_probe_hook
+ %% * sm_remove_connection_hook
+ %% - Roster hooks
+ %% * roster_in_subscription
+ %% * roster_out_subscription
+ %% - User managment hooks
+ %% * remove_user
+ %% * anonymous_purge_hook
+ %%
+ %% Pubsub On Jid hooks
+ %% - Service disco hooks
+ %% * disco_sm_identity
+ %% * disco_sm_features
+ %% * disco_sm_items
+ %% * disco_sm_forms
+ %% - Connectitivy
+ %% * caps_update
+
+
+ Pubsub_Features = node_flat_dev:pubsub_features(),
+ % mod_disco:register_feature(Server_HostB, ?NS_PUBSUB_s),
+ pubsub_db:init('mnesia', 'dev'),
+ %% Register hooks according to available Pubsub Features
+ register_hooks(Host, Pubsub_Features, pubsub_hooks),
+ %%
+ ejabberd_router:register_route(Pubsub_Host),
+ {ok,
+ #state{
+ host = Host,
+ pubsub_host = Pubsub_Host,
+ pubsub = node_flat_dev:capabilities()
+ }
+ }.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+%% @private
+
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+%% @private
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+%% @private
+
+handle_info({route, From, #jid{resource = undefined} = To, Stanza},
+ #state{host = Host, pubsub = Capabilities} = State) ->
+ do_route(Host, To, From, Stanza, Capabilities),
+ {noreply, State};
+%%
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+%% @private
+%% @todo : -spec
+-spec(terminate/2 ::
+(
+ Reason :: _,
+ State :: state())
+ -> 'ok'
+).
+
+terminate(_Reason, #state{host = Host, pubsub_host = Pubsub_Host}) ->
+ ejabberd_router:unregister_route(Pubsub_Host),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%
+-define(Is_Stanza_IQ(Stanza),
+(
+ Stanza#xmlel.name == <<"iq">>
+)).
+
+-define(Is_Stanza_Message(Stanza),
+(
+ Stanza#xmlel.name == <<"message">>
+)).
+
+-define(Is_Stanza_Presence(Stanza),
+(
+ Stanza#xmlel.name == <<"presence">>
+)).
+
+-define(Is_IQ_Ignore(IQ),
+(
+ IQ#iq.type == 'result'
+ orelse
+ IQ#iq.type == 'error'
+)).
+
+-define(Is_IQ_Pubsub(IQ),
+(
+ (IQ#iq.type == 'set'
+ orelse
+ IQ#iq.type == 'get')
+ andalso
+ (IQ#iq.xmlns == ?NS_PUBSUB
+ orelse
+ IQ#iq.xmlns == ?NS_PUBSUB_OWNER)
+ andalso
+ IQ#iq.sub_el#xmlel.name == <<"pubsub">>
+)).
+
+-define(Is_IQ_Get_Disco_Info(IQ),
+(
+ IQ#iq.type == 'get'
+ andalso
+ IQ#iq.xmlns == ?NS_DISCO_INFO
+ andalso
+ IQ#iq.sub_el#xmlel.name == <<"query">>
+% andalso
+% IQ#iq.payload#xmlel.children == []
+)).
+
+-define(Is_IQ_Get_Disco_Items(IQ),
+(
+ IQ#iq.type == 'get'
+ andalso
+ IQ#iq.xmlns == ?NS_DISCO_ITEMS
+ andalso
+ IQ#iq.sub_el#xmlel.name == <<"query">>
+% andalso
+% IQ#iq.payload#xmlel.children == []
+)).
+
+%%
+do_route(Host,
+ #jid{lserver = Pubsub_Host} = Pubsub_Component,
+ #jid{luser = U, lserver = S, lresource = R} = Entity, Stanza_IQ,
+ #capabilities{privacy = Privacy, plugin = Plugin} = Capabilities)
+ when ?Is_Stanza_IQ(Stanza_IQ) ->
+ case IQ = jlib:iq_query_or_response_info(Stanza_IQ) of
+ IQ when ?Is_IQ_Ignore(IQ) ->
+ ok;
+ IQ when ?Is_IQ_Get_Disco_Info(IQ) ->
+ case
+ pubsub_disco:iq_disco_info('dev', Host, Pubsub_Host,
+ Privacy, {U,S,undefined},
+ case S of
+ Host -> 'local';
+ _ -> 'remote'
+ end,
+ Plugin,
+ case xml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el) of
+ <<>> -> undefined;
+ NodeId -> NodeId
+ end,
+ xml:remove_cdata(IQ#iq.sub_el#xmlel.children))
+ of
+ {result, Xmlel_Query} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:iq_to_xml(IQ#iq{
+ type = result,
+ sub_el = [Xmlel_Query]
+ }));
+ {error, Error} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:make_error_reply(Stanza_IQ, Error))
+ end;
+ IQ when ?Is_IQ_Get_Disco_Items(IQ) ->
+ case
+ pubsub_disco:iq_disco_items('dev', Host, Pubsub_Host,
+ Privacy, {U,S,undefined},
+ case S of
+ Host -> 'local';
+ _ -> 'remote'
+ end,
+ Plugin,
+ case xml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el) of
+ <<>> -> undefined;
+ NodeId -> NodeId
+ end,
+ xml:remove_cdata(IQ#iq.sub_el#xmlel.children))
+ of
+ {result, Xmlel_Query} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:iq_to_xml(IQ#iq{
+ type = result,
+ sub_el = [Xmlel_Query]
+ }));
+ {error, Error} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:make_error_reply(Stanza_IQ, Error))
+ end;
+ IQ when ?Is_IQ_Pubsub(IQ) ->
+ case
+ iq_pubsub(Host, Pubsub_Host, {U,S,R}, IQ#iq.type,
+ _Rsm = lists:member(?NS_RSM, Plugin:features()),
+ xml:remove_cdata(IQ#iq.sub_el#xmlel.children),
+ IQ#iq.lang, Capabilities)
+ of
+ {result, []} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:iq_to_xml(IQ#iq{
+ type = result,
+ sub_el = []
+ }));
+ {result, Xmlels} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:iq_to_xml(IQ#iq{
+ type = result,
+ sub_el = Xmlels
+ }));
+ {error, Error} ->
+ ejabberd_router:route(Pubsub_Component, Entity,
+ jlib:make_error_reply(Stanza_IQ, Error))
+ end
+ end;
+do_route(Host,
+ #jid{lserver = Pubsub_Host} = Pubsub_Component,
+ #jid{luser = U, lserver = S, lresource = R} = Entity, Stanza_Message,
+ Capabilities)
+ when ?Is_Stanza_Message(Stanza_Message) ->
+ ok;
+do_route(Host,
+ #jid{lserver = Pubsub_Host} = Pubsub_Component,
+ #jid{luser = U, lserver = S, lresource = R} = Entity, Stanza_Presence,
+ Capabilities)
+ when ?Is_Stanza_Presence(Stanza_Presence) ->
+ ok.
+
+
+iq_pubsub(Host, Pubsub_Host, Entity, IQ_Type, Rsm, Xmlels, _Lang,
+ #capabilities{api = #api{parser = Parser, core = API_Core}} = Capabilities) ->
+ case Parser:parse(IQ_Type, Xmlels, Rsm, API_Core) of
+ {result, Module, Function, Parameters} ->
+ Module:Function(Host, Pubsub_Host, Entity, Parameters, Capabilities);
+ Error ->
+ Error
+ end.
+
+
+
+register_hooks(Host, Pubsub_Features, Module) ->
+ register_hooks('roster', Host, Pubsub_Features, Module),
+ register_hooks('presence', Host, Pubsub_Features, Module).
+
+ %ejabberd_hooks:add(roster_in_subscription, Host, pubsub_hooks, roster_in_subscription, 50),
+ %ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, roster_out_subscription, 50),
+ %ejabberd_hooks:add(roster_get, Host, ?MODULE, roster_get, 50),
+ %ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, roster_get_jid_info, 50),
+ %ejabberd_hooks:add(roster_get_subscription_lists, Host, ?MODULE, roster_get_subscription_lists, 50),
+ %%ejabberd_hooks:add(roster_process_item, Host, pubsub_hooks, roster_process_item, 50).
+
+
+register_hooks('roster', Host, Pubsub_Features, Module) ->
+ case lists:member("access-roster", Pubsub_Features) of
+ true ->
+ ejabberd_hooks:add(roster_process_item, Host, Module,
+ roster_process_item, 50),
+ ok;
+ false ->
+ ok
+ end;
+%%
+register_hooks('presence', Host, Pubsub_Features, Module) ->
+ case
+ lists:member(<<"access-presence">>, Pubsub_Features)
+ orelse
+ lists:member(<<"last-published">>, Pubsub_Features)
+ orelse
+ lists:member(<<"leased-subscription">>, Pubsub_Features)
+ orelse
+ lists:member(<<"presence-notifications">>, Pubsub_Features)
+ of
+ true ->
+ ejabberd_hooks:add(presence_probe_hook, Host, Module,
+ presence_online, 80),
+ ejabberd_hooks:add(sm_remove_connection_hook, Host, Module,
+ presence_offline, 80),
+ ok;
+ false ->
+ ok
+ end.
+
+
+roster_get(Acc, {User, Server}) ->
+ ?INFO_MSG("ROSTER_GET Acc ~p ~nUser ~p ~nServer ~p ~n",
+ [Acc, User, Server]).
+
+roster_get_jid_info(Acc, User, Server, JID) ->
+ ?INFO_MSG("ROSTER_GET_JID_INFO Acc ~p ~nUser ~p ~nServer ~p ~nJID ~p ~n",
+ [Acc, User, Server, JID]).
+
+roster_get_subscription_lists(Acc, User, Server) ->
+ ?INFO_MSG("ROSTER_GET_SUBSCRIPTION_LISTS Acc ~p ~nUser ~p ~nServer ~p ~n",
+ [Acc, User, Server]).
+
+roster_in_subscription(User, Server, JID, SubscriptionType, Reason, A) ->
+ ?INFO_MSG("ROSTER_IN_SUBSCRITION User ~p ~n Server ~p ~n JID ~p ~n SubscriptionType ~p ~n, Reason ~p ~nA ~p ~n",
+ [User, Server, JID, SubscriptionType, Reason, A]).
+
+roster_out_subscription(User, Server, JID, SubscriptionType) ->
+ ?INFO_MSG("ROSTER_OUT_SUBSCRITION User ~p ~n Server ~p ~n JID ~p ~n SubscriptionType ~p ~n",
+ [User, Server, JID, SubscriptionType]).
+
+roster_process_item(RosterItem, Server) ->
+ ?INFO_MSG("ROSTER_PROCESS_ITEM RosterItem ~p ~nServer ~p ~n",
+ [RosterItem, Server]),
+ RosterItem.
+
diff --git a/src/mod_pubsub_ng/node_flat_dev.erl b/src/mod_pubsub_ng/node_flat_dev.erl
new file mode 100644
index 000000000..9de5e5a1f
--- /dev/null
+++ b/src/mod_pubsub_ng/node_flat_dev.erl
@@ -0,0 +1,310 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(node_flat_dev).
+-author('karim.gemayel@process-one.net').
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+-compile(export_all).
+
+
+
+%%
+-spec(capabilities/0 :: () -> #capabilities{}).
+
+capabilities() ->
+ #capabilities{plugin = ?MODULE}.
+
+
+%%
+-spec(pubsub_features/0 :: () -> Pubsub_Features::exmpp_pubsub:pubsub_features()).
+
+pubsub_features() ->
+ [<<"access-authorize">>,
+ <<"access-open">>,
+ <<"access-presence">>,
+ <<"access-roster">>,
+ <<"access-whitelist">>,
+ <<"auto-create">>,
+ <<"auto-subscribe">>,
+ %<<"collections">>,
+ <<"config-node">>,
+ <<"create-and-configure">>,
+ <<"create-nodes">>,
+ <<"delete-items">>,
+ <<"delete-nodes">>,
+ %<<"filtered-notifications">>,
+ <<"get-pending">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"last-published">>,
+ <<"leased-subscription">>,
+ <<"manage-subscriptions">>,
+ <<"member-affiliation">>,
+ <<"meta-data">>,
+ <<"modify-affiliations">>,
+ %<<"multi-collections">>,
+ <<"multi-subscribe">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"presence-notifications">>,
+ %"presence-subscribe">>,
+ <<"publish">>,
+ <<"publish-only-affiliation">>,
+ <<"publish-options">>,
+ <<"publisher-affiliation">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-default">>,
+ <<"retrieve-default-sub">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>].
+
+%%
+-spec(pubsub_features/1 ::
+(
+ Entity_Type :: 'local' | 'remote')
+ -> Pubsub_Features::exmpp_pubsub:pubsub_features()
+).
+
+pubsub_features('local') ->
+ [<<"access-authorize">>,
+ <<"access-open">>,
+ <<"access-presence">>,
+ <<"access-roster">>,
+ <<"access-whitelist">>,
+ <<"auto-create">>,
+ <<"auto-subscribe">>,
+ %<<"collections">>,
+ <<"config-node">>,
+ <<"create-and-configure">>,
+ <<"create-nodes">>,
+ <<"delete-items">>,
+ <<"delete-nodes">>,
+ %<<"filtered-notifications">>,
+ <<"get-pending">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"last-published">>,
+ <<"leased-subscription">>,
+ <<"manage-subscriptions">>,
+ <<"member-affiliation">>,
+ <<"meta-data">>,
+ <<"modify-affiliations">>,
+ %<<"multi-collections">>,
+ <<"multi-subscribe">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"presence-notifications">>,
+ %<<"presence-subscribe">>,
+ <<"publish">>,
+ <<"publish-only-affiliation">>,
+ <<"publish-options">>,
+ <<"publisher-affiliation">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-default">>,
+ <<"retrieve-default-sub">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>];
+%%
+pubsub_features('remote') ->
+ [<<"access-authorize">>,
+ <<"access-open">>,
+ <<"access-presence">>,
+ <<"access-roster">>,
+ <<"access-whitelist">>,
+ <<"auto-create">>,
+ <<"auto-subscribe">>,
+ %<<"collections">>,
+ <<"config-node">>,
+ <<"create-and-configure">>,
+ <<"create-nodes">>,
+ <<"delete-items">>,
+ <<"delete-nodes">>,
+ %<<"filtered-notifications">>,
+ <<"get-pending">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"last-published">>,
+ %<<"leased-subscription">>,
+ <<"manage-subscriptions">>,
+ <<"member-affiliation">>,
+ <<"meta-data">>,
+ <<"modify-affiliations">>,
+ %<<"multi-collections">>,
+ <<"multi-subscribe">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"publish-only-affiliation">>,
+ <<"publish-options">>,
+ <<"publisher-affiliation">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-default">>,
+ <<"retrieve-default-sub">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>].
+
+%%
+%%
+-spec(node_options/1 ::
+(
+ Node_Type :: 'leaf' | 'collection')
+ -> Node_Options :: pubsub_options:options_node_leaf()
+ | pubsub_options:options_node_collection()
+).
+
+node_options('leaf') ->
+ [{'access_model', open},
+ {'contact', []},
+ %{'collection', []},
+ {'deliver_notifications', true},
+ {'deliver_payloads', true},
+ {'description', undefined},
+ {'item_expire', undefined},
+ {'itemreply', publisher},
+ {'language', <<"en">>},
+ {'max_items', 2},
+ {'max_payload_size', undefined},
+ {'node_type', leaf},
+ {'notification_type', normal},
+ {'notify_config', true},
+ {'notify_delete', true},
+ {'notify_retract', true},
+ {'notify_sub', true},
+ {'persist_items', true},
+ {'presence_based_delivery', true},
+ {'publish_model', open},
+ {'purge_offline', false},
+ {'roster_groups_allowed', []},
+ {'send_last_published_item', on_sub_and_presence},
+ {'subscribe', true},
+ {'tempsub', false},
+ {'title', undefined},
+ {'type', undefined}];
+%%
+node_options('collection') ->
+ [].
+
+item_options() ->
+ [{'access_model', open},
+ {'deliver_notifications', true},
+ {'deliver_payloads', true},
+ {'item_expire', undefined},
+ {'itemreply', publisher},
+% {'max_payload_size', undefined},
+ {'notification_type', normal},
+ {'notify_config', true},
+ {'notify_retract', true},
+ {'persist_items', true},
+ {'presence_based_delivery', true},
+% {'publish_model', open},
+ {'purge_offline', false},
+ {'roster_groups_allowed', []},
+ {'send_last_published_item', on_sub_and_presence},
+ {'type', undefined}].
+
+%%
+-spec(subscription_options/2 ::
+(
+ Entity_Type :: 'local' | 'remote',
+ Node_Type :: 'leaf' | 'collection')
+ -> Subscription_Options :: [] | pubsub_options:options_subscription()
+).
+
+subscription_options('local', 'leaf') ->
+ [];
+subscription_options('remote', 'leaf') ->
+ [];
+subscription_options('local', 'collection') ->
+ [];
+subscription_options('remote', 'collection') ->
+ [].
+
+%%
+-spec(default_subscription_options/2 ::
+(
+ Entity_Type :: 'local' | 'remote',
+ Node_Type :: 'leaf')%| 'collection')
+ -> Subscription_Options :: pubsub_options:options_subscription_leaf()
+).
+
+default_subscription_options('local', 'leaf') ->
+ [{'deliver', true},
+ {'expire', undefined},
+ %{'include_body', undefined},
+ {'show-values', ['away', 'chat', 'dnd', 'online', 'xa']}];
+%%
+default_subscription_options('remote', 'leaf') ->
+ [{'deliver', true}
+ %{'include_body', undefined}
+ ].
+%%
+%%default_subscription_options('local', 'collection') ->
+%% [{'deliver', true},
+%% %{'expire', undefined},
+%% %{'include_body', undefined},
+%% {'show-values', ['away', 'chat', 'dnd', 'online', 'xa']},
+%% {'subscription_type', 'all'},
+%% {'subscription_depth', 1}];
+%%
+%%default_subscription_options('remote', 'collection') ->
+%% [{'deliver', true},
+%% %{'include_body', undefined},
+%% {'show-values', ['away', 'chat', 'dnd', 'online', 'xa']},
+%% {'subscription_type', 'all'},
+%% {'subscription_depth', 1}].
+
+%%
+-spec(features/0 :: () -> Features::exmpp_pubsub:features()).
+
+features() ->
+ [%?NS_ADDRESS,
+ ?NS_DISCO_INFO,
+ ?NS_DISCO_ITEMS,
+ %?NS_RSM,
+ ?NS_VCARD].
+
+%%
+identity() ->
+ [{<<"pubsub">>, <<"Publish-Subscribe">>, <<"service">>},
+ {<<"pubsub">>, <<"ejabberd/mod_pubsub/flat">>, <<"service">>}].
diff --git a/src/mod_pubsub_ng/pubsub_api.hrl b/src/mod_pubsub_ng/pubsub_api.hrl
new file mode 100644
index 000000000..a948d3575
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_api.hrl
@@ -0,0 +1,101 @@
+%% API function definition
+
+-record(api_core,
+{
+ create_node = 'pubsub_core' :: module(),
+ delete_node = 'pubsub_core' :: module(),
+ purge_node = 'pubsub_core' :: module(),
+ get_configure_node = 'pubsub_core' :: module(),
+ get_configure_node_default = 'pubsub_core' :: module(),
+ %%
+ publish_item = 'pubsub_core' :: module(),
+ retract_item = 'pubsub_core' :: module(),
+ %%
+ subscribe_node = 'pubsub_core' :: module(),
+ unsubscribe_node = 'pubsub_core' :: module(),
+ set_configure_subscription = 'pubsub_core' :: module(),
+ get_configure_subscription = 'pubsub_core' :: module(),
+ get_configure_subscription_default = 'pubsub_core' :: module(),
+ get_items = 'pubsub_core' :: module(),
+ %%
+ get_entity_affiliations = 'pubsub_core' :: module(),
+ get_entity_subscriptions = 'pubsub_core' :: module(),
+ %%
+ get_node_affiliations = 'pubsub_core' :: module(),
+ get_node_subscriptions = 'pubsub_core' :: module()
+}).
+
+-record(api_db,
+{
+ create_node = 'pubsub_db' :: module(),
+ delete_node = 'pubsub_db' :: module(),
+ purge_node = 'pubsub_db' :: module(),
+ get_configure_node = 'pubsub_db' :: module(),
+ %get_configure_node_default = 'pubsub_db' :: module(),
+ %%
+ publish_item = 'pubsub_db' :: module(),
+ retract_item = 'pubsub_db' :: module(),
+ %%
+ subscribe_node = 'pubsub_db' :: module(),
+ unsubscribe_node = 'pubsub_db' :: module(),
+ set_configure_subscription = 'pubsub_db' :: module(),
+ get_configure_subscription = 'pubsub_db' :: module(),
+ get_configure_subscription_default = 'pubsub_db' :: module(),
+ get_items = 'pubsub_db' :: module(),
+ %%
+ get_entity_affiliations = 'pubsub_db' :: module(),
+ get_entity_subscriptions = 'pubsub_db' :: module(),
+ %%
+ get_node_affiliations = 'pubsub_db' :: module(),
+ get_node_subscriptions = 'pubsub_db' :: module()
+}).
+
+-record(api_broadcast,
+{
+ broadcast_publish = 'pubsub_broadcast' :: module(),
+ broadcast_publish_last = 'pubsub_broadcast' :: module(),
+ notify_create = 'pubsub_broadcast' :: module(),
+ notify_delete = 'pubsub_broadcast' :: module(),
+ notify_publish = 'pubsub_broadcast' :: module(),
+ notify_purge = 'pubsub_broadcast' :: module(),
+ notify_retract = 'pubsub_broadcast' :: module(),
+ notify_subscription = 'pubsub_broadcast' :: module(),
+ notify_subscriptions = 'pubsub_broadcast' :: module()
+}).
+
+-record(api,
+{
+ core = #api_core{} :: #api_core{},
+ db = #api_db{} :: #api_db{},
+ broadcast = #api_broadcast{} :: #api_broadcast{},
+ parser = 'pubsub_parser' :: module(),
+ options = 'pubsub_options' :: module()
+}).
+
+-record(capabilities,
+{
+ plugin :: exmpp_pubsub:plugin(),
+ privacy = false :: boolean(),
+ api = #api{} :: #api{}
+}).
+
+-record(api2,
+{
+ func,
+ core :: 'core',
+ db :: 'db',
+ bkd :: 'mnesia',
+ rtr :: 'router'
+}).
+
+-record(mod_pubsub,
+{
+ server,
+ component,
+ plugin,
+ entity,
+ parameters,
+ features,
+ parser,
+ options
+}).
diff --git a/src/mod_pubsub_ng/pubsub_broadcast.erl b/src/mod_pubsub_ng/pubsub_broadcast.erl
new file mode 100644
index 000000000..33dce8a49
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_broadcast.erl
@@ -0,0 +1,1240 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_broadcast).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1,
+ get_resources/2,
+ get_resources_show/2,
+ rosters_groups_allowed_cache/2
+]).
+
+
+-spec(notify_subscriptions/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notification_Type :: pubsub_options:notification_type(),
+ Entities_Subscriptions :: [Entity_Subscriptions :: {
+ Entity :: xmpp_jid:usr_entity(),
+ Subscriptions :: _%sub:subscriptions()
+ }])
+ -> 'ok'
+).
+
+notify_subscriptions(_Host, Pubsub_Host, NodeId, Notification_Type,
+ Entities_Subscriptions) ->
+ Pubsub_Component_Jid = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ lists:foreach(fun
+ (_Entity = {{U,S,_R}, Subscriptions}) ->
+ lists:foreach(fun
+ ({Subscription_State, SubId, Resource, _Subscription_Options}) ->
+ Recipient_Jid = jlib:make_jid(U,S,Resource),
+ ejabberd_router:route(_From = Pubsub_Component_Jid,
+ _To = Recipient_Jid,
+ _Stanza_Message = exmpp_pubsub:xmlel_message(
+ Notification_Type,
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_subscription('pubsub#event',
+ NodeId,
+ _Jid = pubsub_tools:jid_to_string(Recipient_Jid),
+ SubId,
+ Subscription_State
+ )
+ ])]
+ )
+ )
+
+ end, Subscriptions)
+ end, Entities_Subscriptions).
+
+%%
+-spec(notify_subscription/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Pubsub_Component_Jid :: xmpp_jid:component_bare(),
+ Recipient :: xmpp_jid:entity_bare() | [xmpp_jid:entity_bare(),...],
+ Notification_Type :: pubsub_options:notification_type(),
+ Subscription :: {
+ Subscriber :: xmpp_jid:raw_jid_entity(),
+ Subscription_State :: exmpp_pubsub:subscription_state(),
+ SubId :: exmpp_pubsub:subId()
+ })
+ -> 'ok'
+).
+
+notify_subscription(_Host, NodeId, Pubsub_Component_Jid, Recipients,
+ Notification_Type, {Subscriber, Subscription_State, SubId})
+ when is_list(Recipients) ->
+ Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_subscription('pubsub#event',
+ NodeId, Subscriber, SubId, Subscription_State)
+ ])
+ ]),
+ lists:foreach(fun
+ (Recipient) ->
+ ejabberd_router:route(_From = Pubsub_Component_Jid,
+ _To = pubsub_tools:make_jid(Recipient), Stanza_Message)
+ end, Recipients);
+%%
+notify_subscription(_Host, NodeId, Pubsub_Component_Jid, Recipient,
+ Notification_Type, {Subscriber, Subscription_State, SubId}) ->
+ Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_subscription('pubsub#event',
+ NodeId, Subscriber, SubId, Subscription_State)
+ ])
+ ]),
+ ejabberd_router:route(_From = Pubsub_Component_Jid,
+ _To = Recipient, Stanza_Message).
+
+%%
+-spec(notify_delete/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notification_Type :: pubsub_options:notification_type(),
+ RedirectURI :: undefined | binary(),
+ Recipients :: [Recipient::xmpp_jid:usr_bare(),...])
+ -> 'ok'
+).
+
+notify_delete(_Host, Pubsub_Host, NodeId, Notification_Type, RedirectURI,
+ Recipients) ->
+ Pubsub_Component_Jid = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_delete(NodeId, RedirectURI)])]
+ ),
+ lists:foreach(fun
+ (Recipient) ->
+ ejabberd_router:route(_To = Pubsub_Component_Jid,
+ _From = pubsub_tools:make_jid(Recipient),
+ Stanza_Message
+ )
+ end, Recipients).
+
+%%
+-spec(notify_purge/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notification_Type :: pubsub_options:notification_type(),
+ Recipients :: [Recipient::xmpp_jid:usr_bare(),...])
+ -> 'ok'
+).
+
+notify_purge(_Host, Pubsub_Host, NodeId, Notification_Type, Recipients) ->
+ Pubsub_Component_Jid = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_purge(NodeId)])]),
+ lists:foreach(fun
+ (Recipient) ->
+ ejabberd_router:route(_To = Pubsub_Component_Jid,
+ _From = pubsub_tools:make_jid(Recipient),
+ Stanza_Message
+ )
+ end, Recipients).
+
+%%
+-spec(notify_publish/8 ::
+(
+ From :: xmpp_jid:component_bare(),
+ To :: xmpp_jid:entity(),
+ Notification_Type :: 'headline' | 'normal',
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Publisher :: undefined | xmpp_jid:raw_jid_entity(),
+ Payload :: exmpp_pubsub:payload(),
+ SubIds :: [SubId::exmpp_pubsub:subId(),...])
+ -> 'ok'
+).
+
+notify_publish(From, To, Notification_Type, NodeId, ItemId, Publisher, Payload,
+ SubIds) ->
+ ejabberd_router:route(From, To,
+ _Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ case SubIds of
+ [_SubId] ->
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_items('pubsub#event', NodeId,
+ [exmpp_pubsub:xmlel_item('pubsub#event', ItemId,
+ Publisher, Payload)
+ ])
+ ])
+ ];
+ _SubIds ->
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_items('pubsub#event', NodeId,
+ [exmpp_pubsub:xmlel_item('pubsub#event', ItemId,
+ Publisher, Payload)
+ ])
+ ]),
+ exmpp_pubsub:xmlel_headers(
+ [exmpp_pubsub:xmlel_header(SubId) || SubId <- SubIds])
+ ]
+ end)
+ ).
+
+
+-define(Xmlel(NS, Name, Attrs, Children),
+(
+ #xmlel{
+ name = Name,
+ attrs = [{<<"xmlns">>, NS} | Attrs],
+ children = Children
+ }
+)).
+
+-define(CData(Data),
+(
+ {xmlcdata, Data}
+)).
+
+-define(Stanza_Message(Type, Id, Children),
+(
+ ?Xmlel(?NS_JABBER_CLIENT, <<"message">>, [{<<"type">>, Type}, {<<"id">>, Id}],
+ Children)
+)).
+
+%%
+-spec(stanza_notify_publish/5 ::
+(
+ Notification_Type :: pubsub_options:notification_type(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Publisher :: undefined | xmpp_jid:usr_entity() | xmpp_jid:raw_jid_entity(),
+ Payload :: exmpp_pubsub:payload())
+ -> Stanza_Notify_Publish::xmlel()
+).
+
+stanza_notify_publish(Notification_Type, NodeId, ItemId, Publisher, Payload)
+ when is_tuple(Publisher) ->
+ stanza_notify_publish(Notification_Type, NodeId, ItemId,
+ pubsub_tools:jid_to_string(Publisher), Payload);
+
+stanza_notify_publish(Notification_Type, NodeId, ItemId, Publisher, Payload) ->
+ ?Stanza_Message(list_to_binary(atom_to_list(Notification_Type)), exmpp_pubsub:id(),[
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"event">>, [], [
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"items">>, [{<<"node">>, NodeId}], [
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"item">>,
+ case Publisher of
+ undefined ->
+ [{<<"id">>, ItemId}];
+ _ ->
+ [{<<"id">>, ItemId}, {<<"publisher">>, Publisher}]
+ end,
+ case Payload of
+ [] -> [];
+ _ -> [Payload]
+ end)
+ ])
+ ])
+ ]).
+
+
+%%
+-spec(stanza_notify_retract/3 ::
+(
+ Notification_Type :: pubsub_options:notification_type(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId())
+ -> Stanza_Notify_Retract::xmlel()
+).
+
+stanza_notify_retract(Notification_Type, NodeId, ItemId) ->
+ ?Stanza_Message(list_to_binary(atom_to_list(Notification_Type)), exmpp_pubsub:id(),[
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"event">>, [], [
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"items">>, [{<<"node">>, NodeId}], [
+ ?Xmlel(?NS_PUBSUB_EVENT, <<"retract">>, [{<<"id">>, ItemId}], [])
+ ])
+ ])
+ ]).
+
+
+%%
+-spec(notify_retract/6 ::
+(
+ From :: xmpp_jid:component_bare(),
+ To :: xmpp_jid:entity(),
+ Notification_Type :: 'headline' | 'normal',
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId(),
+ SubIds :: [SubId::exmpp_pubsub:subId(),...])
+ -> none
+).
+
+notify_retract(From, To, Notification_Type, NodeId, ItemId, SubIds) ->
+ ejabberd_router:route(From, To,
+ _Stanza_Message = exmpp_pubsub:xmlel_message(Notification_Type,
+ case SubIds of
+ [_SubId] ->
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_items('pubsub#event', NodeId,
+ [exmpp_pubsub:xmlel_retract(ItemId)])
+ ])
+ ];
+ _SubIds ->
+ [exmpp_pubsub:xmlel_event([
+ exmpp_pubsub:xmlel_items('pubsub#event', NodeId,
+ [exmpp_pubsub:xmlel_retract(ItemId)])
+ ]),
+ exmpp_pubsub:xmlel_headers(
+ [exmpp_pubsub:xmlel_header(SubId) || SubId <- SubIds])
+ ]
+ end)
+ ).
+
+
+broadcast_item(From, To, Stanza, SubIds) ->
+ ejabberd_router:route(From, To,
+ case SubIds of
+ [_] -> Stanza;
+ _ -> exmpp_xml:append_child(Stanza, shim_headers(SubIds))
+ end).
+
+
+shim_headers(SubIds) ->
+ ?Xmlel(?NS_SHIM, <<"headers">>, [],
+ [?Xmlel(?NS_SHIM, <<"header">>, [{<<"name">>, <<"SubID">>}], [?CData(SubId)])
+ || SubId <- SubIds]
+ ).
+
+%%
+xmlel_delay(From, DateTime) ->
+ {T_string, Tz_string} = jlib:timestamp_to_iso(
+ calendar:now_to_datetime(DateTime), 'utc'),
+ ?Xmlel(?NS_DELAY, <<"delay">>,
+ [{<<"from">>, pubsub_tools:jid_to_string(From)},
+ {<<"stamp">>, list_to_binary(T_string ++ Tz_string)}],
+ []).
+
+%%
+-spec(broadcast_publish/8 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Broadcasted_Items :: {
+ Published_Items :: [{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Payload :: exmpp_pubsub:payload(),
+ Publisher :: xmpp_jid:usr_entity()
+ }],
+ %
+ Retracted_Items :: [{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item()
+ }]
+ },
+ Subscription :: undefined
+ | {Subscriber :: xmpp_jid:raw_jid_entity(),
+ Subscription_State :: _,%exmpp_pubsub:subscription_state(),
+ SubId :: exmpp_pubsub:subId()},
+ Pubsub_States :: [Pubsub_State::mod_pubsub_dev:pubsub_state(),...])
+ -> 'ok'
+).
+
+broadcast_publish(Host, Pubsub_Host, NodeId, Node_Options, Node_Owners,
+ {Published_Items, Retracted_Items}, Subscription, Pubsub_States) ->
+ Event = #event{
+ host = Host,
+ component = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ node = #node{
+ id = NodeId,
+ owners = Node_Owners,
+ access_model = get_value(Node_Options, 'access_model'),
+ rosters_groups_allowed = get_value(Node_Options, 'roster_groups_allowed')
+ }
+ },
+ Broadcasted_Items = lists:map(fun
+ %%
+ ({ItemId, _Item_Options = []}) ->
+ #item{
+ presence_based_delivery = get_value(Node_Options,
+ 'presence_based_delivery'),
+ stanza = stanza_notify_retract(
+ get_value(Node_Options, 'notification_type', 'headline'),
+ NodeId, ItemId)
+ };
+ %%
+ ({ItemId, Item_Options}) ->
+ #item{
+ access_model = get_value(Item_Options, 'access_model', undefined),
+ presence_based_delivery = get_value({Node_Options, Item_Options},
+ 'presence_based_delivery'),
+ rosters_groups_allowed = get_value(Item_Options,
+ 'roster_groups_allowed', []),
+ stanza = stanza_notify_retract(
+ get_value({Node_Options, Item_Options}, 'notification_type',
+ 'headline'),
+ NodeId, ItemId)
+ };
+ %%
+ ({ItemId, _Item_Options = [], Payload, Publisher}) ->
+ #item{
+ presence_based_delivery = get_value(Node_Options,
+ 'presence_based_delivery'),
+ stanza = stanza_notify_publish(
+ get_value(Node_Options, 'notification_type', 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value(Node_Options, 'itemreply', 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload)
+ };
+ %%
+ ({ItemId, Item_Options, Payload, Publisher}) ->
+ #item{
+ access_model = get_value(Item_Options, 'access_model'),
+ presence_based_delivery = get_value({Node_Options, Item_Options},
+ 'presence_based_delivery'),
+ rosters_groups_allowed = get_value(Item_Options,
+ 'roster_groups_allowed', []),
+ stanza = stanza_notify_publish(
+ get_value({Node_Options, Item_Options}, 'notification_type',
+ 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value({Node_Options, Item_Options}, 'itemreply',
+ 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload)
+ }
+ %
+ end, lists:append(Retracted_Items, Published_Items)),
+ %
+ lists:foreach(fun
+ %%
+ (#pubsub_state_dev{id = {{U,S,R} = _Entity, _NodeIdx},
+ affiliation = Affiliation, subscriptions = Subscriptions})
+ when (Affiliation == 'member'
+ orelse Affiliation == 'owner'
+ orelse Affiliation == 'publisher')
+ andalso Subscriptions =/= [] ->
+ case Subscription =/= undefined andalso Affiliation == 'owner' of
+ true ->
+ notify_subscription(Host, NodeId,
+ _Pubsub_Component_Jid = Event#event.component,
+ _Recipient = jlib:make_jid(U,S,<<>>),
+ _Notification_Type = get_value(Node_Options,
+ 'notification_type'),
+ Subscription);
+ false ->
+ ok
+ end,
+ lists:foldl(fun broadcast_item/2,
+ Event#event{
+ entity = #entity{
+ id = {U,S,R},
+ local = S == Event#event.host,
+ affiliation = Affiliation,
+ subscriptions = Subscriptions
+ },
+ cache = #cache{},
+ subids = #subids{}
+ },
+ Broadcasted_Items);
+ %%
+ (_Pubsub_State) ->
+ ok
+ %
+ end, Pubsub_States).
+
+%%
+
+-spec(broadcast_publish_last/9 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Published_Items :: [Published_Item :: {
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Payload :: exmpp_pubsub:payload(),
+ Publisher :: xmpp_jid:usr_entity(),
+ DateTime :: erlang:timestamp()
+ },...],
+ Entity :: xmpp_jid:usr_entity(),
+ Affiliation :: 'member' | 'owner' | 'publisher',
+ Subscription :: exmpp_pubsub:subscription())
+ -> none()
+).
+
+broadcast_publish_last(Host, Pubsub_Host, NodeId, Node_Options, Node_Owners,
+ Published_Items, {U,S,R} = Entity, Affiliation, Subscription) ->
+ Event = #event{
+ host = Host,
+ component = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ node = #node{
+ id = NodeId,
+ owners = Node_Owners,
+ access_model = get_value(Node_Options, 'access_model'),
+ rosters_groups_allowed = get_value(Node_Options, 'roster_groups_allowed')
+ }
+ },
+ Broadcasted_Items = lists:map(fun
+ %%
+ ({ItemId, _Item_Options = [], Payload, Publisher, DateTime}) ->
+ #item{
+ presence_based_delivery = get_value(Node_Options,
+ 'presence_based_delivery'),
+ stanza = exmpp_xml:append_child(stanza_notify_publish(
+ get_value(Node_Options, 'notification_type', 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value(Node_Options, 'itemreply', 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload),
+ xmlel_delay(Publisher, DateTime))
+ };
+ %%
+ ({ItemId, Item_Options, Payload, Publisher, DateTime}) ->
+ #item{
+ access_model = get_value(Item_Options, 'access_model'),
+ presence_based_delivery = get_value({Node_Options, Item_Options},
+ 'presence_based_delivery'),
+ rosters_groups_allowed = get_value(Item_Options,
+ 'roster_groups_allowed', []),
+ stanza = exmpp_xml:append_child(stanza_notify_publish(
+ get_value({Node_Options, Item_Options}, 'notification_type',
+ 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value({Node_Options, Item_Options}, 'itemreply',
+ 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload),
+ xmlel_delay(Publisher, DateTime))
+ }
+ %
+ end, Published_Items),
+ case get_value(Node_Options, 'notify_sub', false) of
+ true ->
+ {Subscription_State, SubId, Resource, _} = Subscription,
+ Subscriber = pubsub_tools:jid_to_string({U,S,Resource}),
+ Notification_Type = get_value(Node_Options, 'notification_type',
+ 'headline'),
+ lists:foreach(fun
+ (Node_Owner) ->
+ notify_subscription(Host, NodeId,
+ _Pubsub_Component_Jid = Event#event.component,
+ _Recipient = pubsub_tools:make_jid(Node_Owner),
+ Notification_Type,
+ {Subscriber, Subscription_State, SubId})
+
+ end, Node_Owners);
+ false ->
+ ok
+ end,
+ %%
+ lists:foldl(fun broadcast_item/2,
+ Event#event{
+ entity = #entity{
+ id = {U,S,R},
+ local = S == Event#event.host,
+ affiliation = Affiliation,
+ subscriptions = [Subscription]
+ },
+ cache = #cache{},
+ subids = #subids{}
+ },
+ Broadcasted_Items).
+
+
+-spec(broadcast_publish_last2/9 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Published_Items :: [Published_Item :: {
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Payload :: exmpp_pubsub:payload(),
+ Publisher :: xmpp_jid:usr_entity(),
+ DateTime :: erlang:timestamp()
+ },...],
+ Entity :: xmpp_jid:usr_entity(),
+ Affiliation :: 'member' | 'owner' | 'publisher',
+ Subscriptions :: exmpp_pubsub:subscription())
+ -> none()
+).
+
+broadcast_publish_last2(Host, Pubsub_Host, NodeId, Node_Options, Node_Owners,
+ Published_Items, {U,S,R} = Entity, Affiliation, Subscriptions) ->
+ Event = #event{
+ host = Host,
+ component = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ node = #node{
+ id = NodeId,
+ owners = Node_Owners,
+ access_model = get_value(Node_Options, 'access_model'),
+ rosters_groups_allowed = get_value(Node_Options, 'roster_groups_allowed')
+ }
+ },
+ Broadcasted_Items = lists:map(fun
+ %%
+ ({ItemId, _Item_Options = [], Payload, Publisher, DateTime}) ->
+ #item{
+ presence_based_delivery = get_value(Node_Options,
+ 'presence_based_delivery'),
+ stanza = exmpp_xml:append_child(stanza_notify_publish(
+ get_value(Node_Options, 'notification_type', 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value(Node_Options, 'itemreply', 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload),
+ xmlel_delay(Publisher, DateTime))
+ };
+ %%
+ ({ItemId, Item_Options, Payload, Publisher, DateTime}) ->
+ #item{
+ access_model = get_value(Item_Options, 'access_model'),
+ presence_based_delivery = get_value({Node_Options, Item_Options},
+ 'presence_based_delivery'),
+ rosters_groups_allowed = get_value(Item_Options,
+ 'roster_groups_allowed', []),
+ stanza = exmpp_xml:append_child(stanza_notify_publish(
+ get_value({Node_Options, Item_Options}, 'notification_type',
+ 'headline'),
+ NodeId,
+ ItemId,
+ _Publisher = case
+ get_value({Node_Options, Item_Options}, 'itemreply',
+ 'owner')
+ of
+ 'publisher' -> pubsub_tools:jid_to_string(Publisher);
+ _Owner -> undefined
+ end,
+ Payload),
+ xmlel_delay(Publisher, DateTime))
+ }
+ %
+ end, Published_Items),
+ %%
+ lists:foldl(fun broadcast_item/2,
+ Event#event{
+ entity = #entity{
+ id = {U,S,R},
+ local = S == Event#event.host,
+ affiliation = Affiliation,
+ subscriptions = Subscriptions
+ },
+ cache = #cache{},
+ subids = #subids{}
+ },
+ Broadcasted_Items).
+
+%%
+-define(Resources_Subids(Item, Event),
+ (Item#item.presence_based_delivery == false
+ andalso
+ Event#event.subids#subids.no_presence =/= undefined
+ andalso
+ Event#event.subids#subids.no_presence =/= [])
+ orelse
+ (Item#item.presence_based_delivery == true
+ andalso
+ Event#event.subids#subids.presence =/= undefined
+ andalso
+ Event#event.subids#subids.presence =/= [])
+).
+
+-define(Item_Access_Model(Item, Event),
+ Item#item.access_model == undefined
+ orelse
+ Item#item.access_model == 'open'
+ orelse
+ Item#item.access_model == 'authorize'
+ orelse
+ Item#item.access_model == 'whitelist'
+ orelse
+ (Item#item.access_model == Event#event.node#node.access_model
+ andalso
+ Item#item.access_model =/= 'roster')
+ orelse
+ (Item#item.access_model == 'presence'
+ andalso
+ (Event#event.entity#entity.affiliation == 'owner'
+ orelse
+% Event#event.entity#entity.affiliation == 'publisher'
+% orelse
+ Event#event.node#node.access_model == 'presence'
+ orelse
+ Event#event.node#node.access_model == 'roster'
+ orelse
+ Event#event.cache#cache.presence_subscriptions == true)
+ )
+ orelse
+ (Item#item.access_model == 'roster'
+ andalso
+ (Event#event.entity#entity.affiliation == 'owner'
+ orelse
+ Event#event.entity#entity.affiliation == 'publisher'
+ orelse
+ Event#event.cache#cache.rosters_groups == true)
+ )
+).
+
+
+-define(Broadcast_Event(Item, Event),
+ (?Resources_Subids(Item, Event)) and (?Item_Access_Model(Item, Event))
+).
+
+-define(Presence_Based_Delivery(Item, Event),
+ Item#item.presence_based_delivery == true
+ andalso
+ Event#event.subids#subids.presence == undefined
+).
+
+-define(No_Presence_Based_Delivery(Item, Event),
+ Item#item.presence_based_delivery == false
+ andalso
+ Event#event.subids#subids.no_presence == undefined
+).
+
+-define(Presence_Based_Delivery_Cache(Item, Event),
+ Item#item.presence_based_delivery == true
+ andalso
+ Event#event.cache#cache.presence == undefined
+).
+
+-define(Item_Access_Model_Presence(Item, Event),
+ Item#item.access_model == 'presence'
+ andalso
+ Event#event.cache#cache.presence_subscriptions == undefined
+).
+
+-define(Item_Access_Model_Roster(Item, Event),
+ Item#item.access_model == 'roster'
+ andalso
+ Event#event.cache#cache.rosters_groups == undefined
+).
+
+
+-spec(broadcast_item/2 ::
+(
+ Item :: mod_pubsub_dev:item(),
+ Event :: mod_pubsub_dev:event())
+ -> Event::mod_pubsub_dev:event()
+% Event::#event{cache::#cache{rosters_groups :: undefined}}
+).
+
+
+
+%%
+broadcast_item(#item{stanza = Stanza} = Item,
+ #event{entity = #entity{id = {U,S,_R}}} = Event)
+ when ?Broadcast_Event(Item, Event) ->
+ lists:foreach(fun
+ ({Resource, SubIds}) ->
+ broadcast_item(_From = Event#event.component,
+ _To = jlib:make_jid(U,S,Resource),
+ Stanza,
+ SubIds)
+ end,
+ _Resources_SubIds = case Item#item.presence_based_delivery of
+ true -> Event#event.subids#subids.presence;
+ false -> Event#event.subids#subids.no_presence
+ end),
+ Event#event{cache = Event#event.cache#cache{rosters_groups = undefined}};
+%%
+broadcast_item(Item, Event)
+ when ?No_Presence_Based_Delivery(Item, Event) ->
+ {Presence_Cache, Resources_SubIds} = resources_subids(
+ _Entity = Event#event.entity#entity.id,
+ _Local_Entity = Event#event.entity#entity.local,
+ _Presence_Based_Delivery = false,
+ _Subscriptions = Event#event.entity#entity.subscriptions,
+ {Event#event.cache#cache.presence, []}),
+ case Resources_SubIds of
+ [] ->
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = Presence_Cache,
+ rosters_groups = undefined
+ },
+ subids = Event#event.subids#subids{
+ no_presence = []
+ }
+ };
+ _Resources_SubIds ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = Presence_Cache
+ },
+ subids = Event#event.subids#subids{
+ no_presence = Resources_SubIds
+ }
+ }
+ )
+ end;
+%%
+broadcast_item(Item, #event{entity = #entity{id = {U,S,_R}}} = Event)
+ when ?Presence_Based_Delivery_Cache(Item, Event) ->
+ case get_resources_show(U,S) of
+ [] ->
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = [],
+ rosters_groups = undefined
+ }
+ };
+ Resources_Show ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = Resources_Show
+ }
+ })
+ end;
+%%
+broadcast_item(Item, Event)
+ when ?Presence_Based_Delivery(Item, Event) ->
+ {Presence_Cache, Resources_SubIds} = resources_subids(
+ _Entity = Event#event.entity#entity.id,
+ _Local_Entity = Event#event.entity#entity.local,
+ _Presence_Based_Delivery = true,
+ _Subscriptions = Event#event.entity#entity.subscriptions,
+ {Event#event.cache#cache.presence, []}),
+ case Resources_SubIds of
+ [] ->
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = Presence_Cache,
+ rosters_groups = undefined
+ },
+ subids = Event#event.subids#subids{
+ presence = []}
+ };
+ _Resources_SubIds ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence = Presence_Cache
+ },
+ subids = Event#event.subids#subids{
+ presence = Resources_SubIds
+ }
+ })
+ end;
+%%
+broadcast_item(Item, Event)
+ when ?Item_Access_Model_Presence(Item, Event) ->
+ case
+ is_contact_subscribed_to_node_owners(
+ _Host = Event#event.host,
+ _Entity = Event#event.entity#entity.id,
+ _Node_Owners = Event#event.node#node.owners
+ )
+ of
+ false ->
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence_subscriptions = false,
+ rosters_groups = undefined
+ }
+ };
+ _Node_Owner ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ presence_subscriptions = true
+ }
+ }
+ )
+ end;
+%%
+broadcast_item(Item, Event)
+ when ?Item_Access_Model_Roster(Item, Event) ->
+ case Event#event.node#node.access_model of
+ 'roster' ->
+ case Item#item.rosters_groups_allowed of
+ %%
+ [] ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ rosters_groups = true
+ }
+ }
+ );
+ %%
+ Item_Rosters_Groups_Allowed ->
+ case
+ is_contact_in_allowed_roster_groups(
+ _Contact = Event#event.entity#entity.id,
+ _Rosters_Groups_Allowed = Item_Rosters_Groups_Allowed
+ )
+ of
+ false ->
+ Event;
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{
+ rosters_groups = true
+ }
+ })
+ end
+ end;
+ _Node_Access_Model ->
+ case
+ is_contact_in_allowed_roster_groups(
+ _Contact = Event#event.entity#entity.id,
+ _Rosters_Groups_Allowed = case
+ Item#item.rosters_groups_allowed
+ of
+ [] ->
+ Event#event.node#node.rosters_groups_allowed;
+ Item_Rosters_Groups_Allowed ->
+ Item_Rosters_Groups_Allowed
+ end
+ )
+ of
+ false ->
+ Event;
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ broadcast_item(Item,
+ Event#event{
+ cache = Event#event.cache#cache{rosters_groups = true}
+ })
+ end
+ end;
+%%
+broadcast_item(_Item, Event) ->
+ Event#event{cache = Event#event.cache#cache{rosters_groups = undefined}}.
+
+
+%%
+-spec(resources_subids/5 ::
+(
+ Entity :: xmpp_jid:usr_bare(),
+ Local_Entity :: boolean(),
+ Presence_Based_Delivery :: boolean(),
+ Subscriptions :: _,%exmpp_pubsub:subscriptions() | [],
+ Cache :: {
+ Presence_Cache :: undefined | [] | mod_pubsub_dev:presence_cache(),
+ Resources_SubIds :: undefined | [] | mod_pubsub_dev:resources_subids()
+ })
+ -> {Presence_Cache :: undefined | [] | mod_pubsub_dev:presence_cache(),
+ Resources_SubIds :: [] | mod_pubsub_dev:resources_subids()}
+).
+
+resources_subids(_Entity, _Local_Entity, _Presence_Based_Delivery,
+ [] = _Subscriptions, {Presence_Cache, Resources_SubIds}) ->
+ {Presence_Cache, Resources_SubIds};
+%%
+resources_subids(Entity, Local_Entity, Presence_Based_Delivery,
+ [{'pending', _SubId, _Resource, _Subscription_Options} | Subscriptions],
+ {Presence_Cache, Resources_SubIds}) ->
+ resources_subids(Entity, Local_Entity, Presence_Based_Delivery, Subscriptions,
+ {Presence_Cache, Resources_SubIds});
+%%
+resources_subids(Entity, Local_Entity, Presence_Based_Delivery,
+ [{Subscription_State, SubId, {caps, Resource}, Subscription_Options}
+ | Subscriptions], {Presence_Cache, Resources_SubIds}) ->
+ resources_subids(Entity, Local_Entity, Presence_Based_Delivery,
+ [{Subscription_State, SubId, Resource, Subscription_Options}
+ | Subscriptions],
+ {Presence_Cache, Resources_SubIds});
+%% Presence_Based_Delivery = false
+%% Local_Entity = boolean()
+%% Subscription_Options = []
+resources_subids(Entity, Local_Entity, false = Presence_Based_Delivery,
+ [{_Subscription_State, SubId, Resource, [] = _Subscription_Options}
+ | Subscriptions], {Presence_Cache, Resources_SubIds}) ->
+ resources_subids(Entity, Local_Entity, Presence_Based_Delivery, Subscriptions,
+ {Presence_Cache,
+ _Resources_SubIds = resources_subids(Resource, SubId,
+ Resources_SubIds)});
+%% Presence_Based_Delivery = false
+%% Local_Entity = true
+%% Resource = undefined | xmpp_jid:resource_jid()
+%% Subscription_Options =/= []
+resources_subids({U,S,R} = _Entity, true = Local_Entity, false = Presence_Based_Delivery,
+ [{_Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions],
+ {Presence_Cache, Resources_SubIds}) ->
+ resources_subids({U,S,R}, Local_Entity, Presence_Based_Delivery,
+ Subscriptions,
+ case get_value(Subscription_Options, 'deliver', true) of
+ true ->
+ case get_value(Subscription_Options, 'show-values') of
+ 'none' -> %% 'show-values' NA
+ {Presence_Cache,
+ _Resources_SubIds = resources_subids(Resource, SubId,
+ Resources_SubIds)};
+ %%
+ Show_Values
+ when Show_Values == []
+ orelse Presence_Cache == [] ->
+ {Presence_Cache, Resources_SubIds};
+ %%
+ Show_Values
+ when Presence_Cache == undefined ->
+ case get_resources_show(U, S) of
+ %%%%
+ [] ->
+ {_Presence_Cache = [], Resources_SubIds};
+ %%%%
+ Resources_Show ->
+ {_Presence_Cache = Resources_Show,
+ _Resources_SubIds = lists:foldl(fun
+ (Resource_To_Deliver, Resources_SubIds_Bis) ->
+ resources_subids(Resource_To_Deliver,
+ SubId, Resources_SubIds_Bis)
+ end,
+ Resources_SubIds,
+ _Resources_To_Deliver = resources_to_deliver(
+ Resource, Show_Values, Resources_Show,
+ []))}
+ end;
+ %%
+ Show_Values ->
+ {Presence_Cache,
+ _Resources_SubIds = lists:foldl(fun
+ (Resource_To_Deliver, Resources_SubIds_Bis) ->
+ resources_subids(Resource_To_Deliver,
+ SubId, Resources_SubIds_Bis)
+ end,
+ Resources_SubIds,
+ _Resources_To_Deliver = resources_to_deliver(
+ Resource, Show_Values, Presence_Cache, []))}
+ end;
+ false ->
+ {Presence_Cache, Resources_SubIds}
+ end);
+%% Presence_Based_Delivery = false
+%% Local_Entity = false
+%% Subscription_Options =/= []
+resources_subids(Entity, false = Local_Entity, false = Presence_Based_Delivery,
+ [{_Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions],
+ {Presence_Cache, Resources_SubIds}) ->
+ resources_subids(Entity, Local_Entity, Presence_Based_Delivery, Subscriptions,
+ case get_value(Subscription_Options, 'deliver', true) of
+ true ->
+ {Presence_Cache,
+ _Resources_SubIds = resources_subids(Resource, SubId,
+ Resources_SubIds)};
+ false ->
+ {Presence_Cache, Resources_SubIds}
+ end);
+%% Presence_Based_Delivery = true
+%% Local_Entity = true
+%% Subscription_Options : NA
+%% Presence_Cache = 'none'
+resources_subids({U,S,R} = _Entity, true = _Local_Entity,
+ true = _Presence_Based_Delivery, Subscriptions,
+ {undefined = _Presence_Cache, Resources_SubIds}) ->
+ case get_resources_show(U, S) of
+ [] ->
+ {[], Resources_SubIds};
+ _Resources_Show = Presence_Cache ->
+ resources_subids({U,S,R}, true, true, Subscriptions,
+ {Presence_Cache, Resources_SubIds})
+ end;
+%% Presence_Based_Delivery = true
+%% Local_Entity = true
+%% Subscription_Options : NA
+%% Presence_Cache = []
+resources_subids(_Entity, true = _Local_Entity, true = _Presence_Based_Delivery,
+ _Subscriptions, {[] = Presence_Cache, Resources_SubIds}) ->
+ {Presence_Cache, Resources_SubIds};
+%% Presence_Based_Delivery = true
+%% Local_Entity = true
+%% Resource = undefined | xmpp_jid:resource_jid()
+%% Subscription_Options =/= []
+%% Presence_Cache =/= []
+resources_subids(Entity, true = _Local_Entity, true = Presence_Based_Delivery,
+ [{_Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions],
+ {Presence_Cache, Resources_SubIds}) ->
+ resources_subids(Entity, true, Presence_Based_Delivery, Subscriptions,
+ case
+ get_value(Subscription_Options, 'deliver', true) == true
+ andalso
+ (Resource == undefined
+ orelse lists:keymember(Resource, 1, Presence_Cache))
+ of
+ true ->
+ {Presence_Cache,
+ _Resources_SubIds = case
+ get_value(Subscription_Options, 'show-values')
+ of
+ 'none' when Resource == undefined -> %% 'show-values' NA
+ lists:foldl(fun
+ ({Resource_To_Deliver, _Show}, Resources_SubIds_Bis) ->
+ resources_subids(Resource_To_Deliver,
+ SubId, Resources_SubIds_Bis)
+ end, Resources_SubIds, Presence_Cache);
+ 'none' -> %% 'show-values' NA
+ resources_subids(Resource, SubId, Resources_SubIds);
+ [] ->
+ Resources_SubIds;
+ Show_Values ->
+ lists:foldl(fun
+ (Resource_To_Deliver, Resources_SubIds_Bis) ->
+ resources_subids(Resource_To_Deliver,
+ SubId, Resources_SubIds_Bis)
+ end,
+ Resources_SubIds,
+ _Resources_To_Deliver = resources_to_deliver(
+ Resource, Show_Values, Presence_Cache, []))
+ end};
+ false ->
+ {Presence_Cache, Resources_SubIds}
+ end);
+%% Presence_Based_Delivery = true
+%% Local_Entity = false
+%% Subscription_Options : NA
+resources_subids(_Entity, false = _Local_Entity, true = _Presence_Based_Delivery,
+ _Subscriptions, {Presence_Cache, Resources_SubIds}) ->
+ {Presence_Cache, Resources_SubIds}.
+
+%%
+-spec(resources_subids/3 ::
+(
+ Resource :: xmpp_jid:resource_jid(),
+ SubId :: exmpp_pubsub:subId(),
+ Resources_SubIds :: [] | mod_pubsub_dev:resources_subids())
+ -> %%
+ Resources_SubIds::mod_pubsub_dev:resources_subids()
+).
+
+resources_subids(Resource, SubId, Resources_SubIds) ->
+ _Resources_SubIds = case lists:keyfind(Resource, 1, Resources_SubIds) of
+ {_Resource, SubIds} ->
+ lists:keyreplace(Resource, 1, Resources_SubIds,
+ {Resource, [SubId | SubIds]});
+ false ->
+ [{Resource, [SubId]} | Resources_SubIds]
+ end.
+
+%%
+-spec(resources_to_deliver/4 ::
+(
+ Resource :: undefined | xmpp_jid:resource_jid(),
+ Show_Values :: ['away' | 'chat' | 'dnd' | 'online' | 'xa',...],
+ Presence_Cache :: [] | mod_pubsub_dev:presence_cache(),
+ Resources_To_Deliver :: [Resource::xmpp_jid:resource_jid()])
+ -> %%
+ Resources_To_Deliver::[Resource::xmpp_jid:resource_jid()]
+).
+
+resources_to_deliver(_Resource, _Show_Values, [] = _Presence_Cache,
+ Resources_To_Deliver) ->
+ Resources_To_Deliver;
+%%
+resources_to_deliver(Resource, Show_Values, [Resource_Show | Presence_Cache],
+ Resources_To_Deliver) ->
+ resources_to_deliver(Resource, Show_Values, Presence_Cache,
+ _Resources_To_Deliver = resource_to_deliver(Resource, Show_Values,
+ Resource_Show, Resources_To_Deliver)).
+
+%%
+-spec(resource_to_deliver/4 ::
+(
+ Resource :: undefined | xmpp_jid:resource_jid(),
+ Show_Values :: ['away' | 'chat' | 'dnd' | 'online' | 'xa',...],
+ Resource_Show :: mod_pubsub_dev:resource_show(),
+ Resources_To_Deliver :: [Resource::xmpp_jid:resource_jid()])
+ -> %%
+ Resources_To_Deliver::[Resource::xmpp_jid:resource_jid()]
+).
+
+resource_to_deliver(_Resource, [] = _Show_Values, _Resource_Show,
+ Resources_To_Deliver) ->
+ Resources_To_Deliver;
+%%
+resource_to_deliver(undefined = _Resource, [_Show_Value = Show | Show_Values],
+ {Resource, Show}, Resources_To_Deliver) ->
+ resource_to_deliver(_Resource = undefined, Show_Values, {Resource, Show},
+ [_Resource_To_Deliver = Resource | Resources_To_Deliver]);
+%%
+resource_to_deliver(Resource, [_Show_Value = Show | Show_Values],
+ {Resource, Show}, Resources_To_Deliver) ->
+ resource_to_deliver(Resource, Show_Values, {Resource, Show},
+ [_Resource_To_Deliver = Resource | Resources_To_Deliver]);
+%%
+resource_to_deliver(Resource, [_Show_Value | Show_Values], Resource_Show,
+ Resources_To_Deliver) ->
+ resource_to_deliver(Resource, Show_Values, Resource_Show,
+ Resources_To_Deliver).
+
diff --git a/src/mod_pubsub_ng/pubsub_core.erl b/src/mod_pubsub_ng/pubsub_core.erl
new file mode 100644
index 000000000..8914987f9
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_core.erl
@@ -0,0 +1,1707 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_core).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+-import(pubsub_tools,
+[
+ is_jid/1,
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+
+
+-spec(features/3 ::
+(
+ Function :: 'create_node'
+ | 'purge_node'
+ | 'publish_item'
+ | 'retract_item'
+ | 'get_items'
+ | 'subscribe_node'
+ | 'get_configure_subscription_default'
+ | 'get_configure_node_default',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Criteria :: %-- Create_Node --%
+ {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Config :: [] | [Xmlel::xmlel(),...]}
+ %% %-- Purge_Node --%
+ | 'none'
+ %% %-- Publish_Item --%
+ | {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Config :: [] | [Xmlel::xmlel(),...],
+ Publish_Options :: [] | [Xmlel::xmlel(),...]}
+ %% %-- Retract_Item --%
+ | 'none'
+ %% %-- Get_Items --%
+ | 'none'
+ %% %-- Subscribe_Node --%
+ | {Subscribe_Options :: [Xmlel::xmlel()]}
+ %% %-- Get_Configure_Subscription_Default --%
+ | 'none'
+ %% %-- Get_Configure_Node_Default --%
+ | 'none'
+ )
+ -> ok
+ %%%
+ %-- Create_Node --%
+ | {error, 'feature-not-implemented', 'create-nodes'}
+ | {error, 'feature-not-implemented', 'create-and-configure'}
+ %
+ | {error, 'not-acceptable', 'nodeid-required'}
+ %-- Purge_Node --%
+ | {error, 'feature-not-implemented', 'purge-nodes'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %-- Publish_Item --%
+ | {error, 'feature-not-implemented', 'publish'}
+ | {error, 'feature-not-implemented', 'auto-create'}
+ | {error, 'feature-not-implemented', 'config-node'}
+ | {error, 'feature-not-implemented', 'publish-options'}
+ %-- Retract_Item --%
+ | {error, 'feature-not-implemented', 'delete-items'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %-- Get_Items --%
+ | {error, 'feature-not-implemented', 'retrieve-items'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %-- Subscribe_Node --%
+ | {error, 'feature-not-implemented', 'subscribe'}
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %-- Get_Configure_Subscription_Default --%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ | {error, 'feature-not-implemented', 'retrieve-default-sub'}
+ %-- Get_Configure_Node_Default --%
+ | {error, 'feature-not-implemented', 'config-node'}
+ | {error, 'feature-not-implemented', 'retrieve-default'}
+).
+
+%-- Create_Node --%
+features('create_node', Pubsub_Features, {NodeId, [] = _Node_Config}) ->
+ case lists:member(<<"create-nodes">>, Pubsub_Features) of
+ true when NodeId =/= undefined ->
+ ok;
+ true ->
+ case lists:member(<<"instant-nodes">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'not-acceptable', 'nodeid-required'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'create-nodes'}
+ end;
+%
+features('create_node', Pubsub_Features, {NodeId, _Node_Config}) ->
+ case features('create_node', Pubsub_Features, {NodeId, []}) of
+ ok ->
+ case lists:member(<<"create-and-configure">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'create-and-configure'}
+ end;
+ Error ->
+ Error
+ end;
+%-- Purge_Node --%
+features('purge_node', Pubsub_Features, none) ->
+ case lists:member(<<"purge-nodes">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"persistent-items">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'persistent-items'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'purge-nodes'}
+ end;
+%-- Publish_Item --%
+features('publish_item', Pubsub_Features,
+ {NodeId, [] = _Node_Config, [] = _Publish_Options}) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true when NodeId =/= undefined ->
+ ok;
+ true ->
+ case lists:member(<<"auto-create">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'auto-create'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'publish'}
+ end;
+%%
+features('publish_item', Pubsub_Features,
+ {NodeId, _Node_Config, [] = _Publish_Options}) ->
+ case features('publish_item', Pubsub_Features, {NodeId, [], []}) of
+ ok ->
+ case lists:member(<<"config-node">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'config-node'}
+ end;
+ Error ->
+ Error
+ end;
+%
+features('publish_item', Pubsub_Features,
+ {NodeId, Node_Config, _Publish_Options}) ->
+ case features('publish_item', Pubsub_Features, {NodeId, Node_Config, []}) of
+ ok ->
+ case lists:member(<<"publish-options">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'publish-options'}
+ end;
+ Error ->
+ Error
+ end;
+%-- Purge_Node --%
+features('retract_item', Pubsub_Features, none) ->
+ case lists:member(<<"delete-items">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"persistent-items">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'persistent-items'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'delete-items'}
+ end;
+%-- Get_Items --%
+features('get_items', Pubsub_Features, none) ->
+ case lists:member(<<"retrieve-items">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"persistent-items">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'persistent-items'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-items'}
+ end;
+%-- Subscribe_Node --%
+features('subscribe_node', Pubsub_Features, {[] = _Subscribe_Options}) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-items'}
+ end;
+%%
+features('subscribe_node', Pubsub_Features, {_Subscribe_Options}) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"subscription-options">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'subscription-options'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-items'}
+ end;
+%-- Get_Configure_Subscription_Default --%
+features('get_configure_subscription_default', Pubsub_Features, 'none') ->
+ case lists:member(<<"subscription-options">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"retrieve-default-sub">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-default-sub'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'subscription-options'}
+ end;
+%-- Get_Configure_Node_Default --%
+features('get_configure_node_default', Pubsub_Features, 'none') ->
+ case lists:member(<<"config-node">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"retrieve-default">>, Pubsub_Features) of
+ true ->
+ ok;
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-default'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'config-node'}
+ end.
+
+
+%-- Create_Node --%
+-spec(create_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Config :: [] | [Xmlel::xmlel(),...]},
+ Capabilities :: #capabilities{})
+ -> %%
+ {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'create-nodes'}
+ | {error, 'feature-not-implemented', 'create-and-configure'}
+ %
+ | {error, 'not-acceptable', 'nodeid-required'}
+ %
+ | {error, 'forbidden'}
+ | {error, 'conflict'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+ %
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+
+create_node(Host, Pubsub_Host, {U,S,R} = Entity,
+ {NodeId, Node_Config} = _Parameters,
+ #capabilities{
+ plugin = Plugin,
+ api = #api{db = API_DB, broadcast = API_Broadcast, options = Options_Mod}}
+ = _Capabilities) ->
+ case
+ checks_on_create_node(Host, {U,S,R}, Plugin, {NodeId, Node_Config},
+ Options_Mod)
+ of
+ {ok, Pubsub_Features, Node_Options} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.create_node,
+ 'create_node',
+ [Host, Pubsub_Host, {U,S,undefined}, Pubsub_Features,
+ {NodeId, Node_Options}]
+ )
+ of
+ {result, NodeId_Bis, _Actions} ->
+ case
+ (_Actions =/= [])
+ andalso
+ lists:member(<<"subscription-notifications">>,
+ Pubsub_Features)
+ of
+ true ->
+ [{Notification_Type, Subscription}] = _Actions,
+ spawn(API_Broadcast
+ #api_broadcast.notify_subscriptions,
+ 'notify_subscriptions',
+ [Host, Pubsub_Host, NodeId_Bis, Notification_Type,
+ [{Entity, [Subscription]}]]);
+ false ->
+ ok
+ end,
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_create(NodeId_Bis)])]};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+
+checks_on_create_node(Host, {U,S,R} = _Entity, Plugin, {NodeId, Node_Config},
+ Options_Mod) ->
+ case
+ features('create_node', Pubsub_Features = Plugin:pubsub_features(),
+ {NodeId, Node_Config})
+ of
+ ok ->
+ case
+ acl:match_rule(Host, pubsub_createnode,
+ jlib:jid_to_string({U, S, <<>>}))
+ of
+ allow ->
+ case
+ Options_Mod:parse_xmlel_x(Plugin, Pubsub_Features,
+ {U,S,R}, 'node_config', Node_Config)
+ of
+ {ok, Node_Options} ->
+ {ok, Pubsub_Features, Node_Options};
+ Error ->
+ Error
+ end;
+ _Deny ->
+ {error, 'forbidden'}
+ end;
+ Error ->
+ Error
+ end.
+
+%-- Delete_Node --%
+-spec(delete_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ RedirectURI :: undefined | binary()},
+ Capabilities :: #capabilities{})
+ -> {result, []}
+ %%%
+ | {error, 'feature-not-implemented', 'delete-nodes'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+delete_node(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ _Parameters = {NodeId, RedirectURI},
+ #capabilities{plugin = Plugin, api = #api{db = API_DB, broadcast = API_Broadcast}}
+ = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case lists:member(<<"delete-nodes">>, Pubsub_Features) of
+ true ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.delete_node,
+ 'delete_node',
+ [Pubsub_Host, {U,S,undefined}, {NodeId}])
+ of
+ {result, _Actions = []} ->
+ {result, []};
+ {result,
+ _Actions = [{Notification_Type, Recipients}]} ->
+ spawn(API_Broadcast#api_broadcast.notify_delete,
+ 'notify_delete',
+ [Host, Pubsub_Host, NodeId, Notification_Type,
+ RedirectURI, Recipients]),
+ {result, []};
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'delete-nodes'}
+ end.
+
+%-- Purge_Node --%
+-spec(purge_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, []}
+ %%%
+ | {error, 'feature-not-implemented', 'purge-nodes'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+purge_node(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB, broadcast = API_Broadcast}}
+ = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('purge_node', Pubsub_Features, none) of
+ ok ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.purge_node,
+ 'purge_node',
+ [Pubsub_Host, {U,S,undefined}, {NodeId}])
+ of
+ {result, []} ->
+ {result, []};
+ {result,
+ _Actions = [{Notification_Type, Recipients}]} ->
+ spawn(API_Broadcast#api_broadcast.notify_purge,
+ 'notify_purge',
+ [Host, Pubsub_Host, NodeId, Notification_Type, Recipients]),
+ {result, []};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%
+-spec(publish_item/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Item :: 0 | 1,
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel()],
+ Node_Config :: [], %| [Xmlel::xmlel(),...],
+ Publish_Options :: [Xmlel::xmlel()]
+ },
+ Capabilities :: #capabilities{})
+ -> %%
+ {result, Xmlel_Pubsub::[Xmlel::xmlel()]}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+publish_item(Host, Pubsub_Host, {U,S,R} = _Entity,
+ {_NodeId, Item, _ItemId, Payload, [] = Node_Config, Publish_Options},
+ #capabilities{plugin = Plugin,
+ api = #api{options = Options_Module, db = API_DB,
+ broadcast = API_Broadcast}} = _Capabilities) ->
+ case
+ checks_on_publish_item(Host, {U,S,R}, Plugin, Options_Module,
+ {_NodeId, Node_Config, Publish_Options})
+ of
+ {ok, Pubsub_Features, _Node_Options, Item_Options} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.publish_item,
+ 'publish_item',
+ [Host, Pubsub_Host, {U,S,R}, Plugin, Pubsub_Features,
+ {_NodeId, Item, _ItemId, Payload, {Node_Config, Item_Options}}])
+ of
+ {result, NodeId, ItemId, Node_Options, Node_Owners, SubId,
+ Subscription, Broadcasted_Items, Pubsub_States} ->
+ case Pubsub_States of
+ [] ->
+ ok;
+ _Pubsub_States ->
+ spawn(API_Broadcast#api_broadcast.broadcast_publish,
+ broadcast_publish,
+ [Host, Pubsub_Host, NodeId, Node_Options,
+ Node_Owners, Broadcasted_Items,
+ case SubId of
+ undefined ->
+ undefined;
+ _SubId ->
+ {_Subscriber = jlib:jid_to_string({U, S, <<>>}),
+ _Subscription_State = 'subscribed',
+ SubId}
+ end,
+ Pubsub_States])
+ end,
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_publish('pubsub', NodeId,
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId)])]
+ )]
+ };
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%
+-spec(checks_on_publish_item/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_entity(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Options_Module :: module(),
+ Criteria :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Config :: [Xmlel_X::xmlel(),...]
+ | [Xmlel::xmlel()],
+ Publish_Options :: [Xmlel_X::xmlel(),...]
+ | [Xmlel::xmlel()]
+ })
+ -> {ok,
+ Pubsub_Features ::exmpp_pubsub:pubsub_features(),
+ Node_Options :: [] | pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}
+ %%%
+ | {error, 'forbidden'}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+checks_on_publish_item(Host, {U,S,R} = _Entity, Plugin, Options_Module,
+ {undefined = NodeId, Node_Config, Publish_Options}) ->
+ case
+ features('publish_item', Pubsub_Features = Plugin:pubsub_features(),
+ {NodeId, Node_Config, Publish_Options})
+ of
+ ok ->
+ case
+ acl:match_rule(Host, pubsub_createnode,
+ jlib:jid_to_string({U, S, <<>>}))
+ of
+ allow ->
+ case
+ {Options_Module:parse_xmlel_x(Plugin, Pubsub_Features,
+ {U,S,R}, 'node_config', Node_Config),
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features,
+ {U,S,R}, 'publish-options', Publish_Options)}
+ of
+ {{ok, Node_Options}, {ok, Item_Options}} ->
+ {ok, Pubsub_Features, Node_Options, Item_Options};
+ {Error, {ok, _Item_Options}} ->
+ Error;
+ {{ok, _Node_Options}, Error} ->
+ Error;
+ {Error, _Error} ->
+ Error
+ end;
+ _Deny ->
+ {error, 'forbidden'}
+ end;
+ Error ->
+ Error
+ end;
+%%
+checks_on_publish_item(_Host, Entity, Plugin, Options_Module,
+ {NodeId, Node_Config, Publish_Options}) ->
+ case
+ features('publish_item', Pubsub_Features = Plugin:pubsub_features(),
+ {NodeId, Node_Config, Publish_Options})
+ of
+ ok ->
+ case
+ {Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ 'node_config', Node_Config),
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ 'publish-options', Publish_Options)}
+ of
+ {{ok, Node_Options}, {ok, Item_Options}} ->
+ {ok, Pubsub_Features, Node_Options, Item_Options};
+ {Error, {ok, _Item_Options}} ->
+ Error;
+ {{ok, _Node_Options}, Error} ->
+ Error;
+ {Error, _Error} ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%-- Retract_Items --%
+-spec(retract_item/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Adhoc_Notify_Retract :: undefined | boolean()},
+ Capabilities :: #capabilities{})
+ -> {result, []}
+ %%%
+ | {error, 'feature-not-implemented', 'delete-items'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'bad-request', 'item-required'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+retract_item(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId, ItemId, Adhoc_Notify_Retract} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB, broadcast = API_Broadcast}}
+ = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('retract_item', Pubsub_Features, none) of
+ ok ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.retract_item,
+ 'retract_item',
+ [Pubsub_Host, {U,S,undefined},
+ {NodeId, ItemId, Adhoc_Notify_Retract}])
+ of
+ {result, []} ->
+ {result, []};
+ {result,
+ [_Notify_Retract = {
+ Node_Owners, Node_Options, Item_Options, Pubsub_States
+ }]} ->
+ spawn(API_Broadcast#api_broadcast.broadcast_publish,
+ broadcast_publish,
+ [Host, Pubsub_Host, NodeId, Node_Options, Node_Owners,
+ {[{ItemId, Item_Options}], []}, undefined, Pubsub_States]),
+ {result, []};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%-- Subscribe_Node --%
+-spec(subscribe_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'subscribe'}
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %
+ | {error, 'bad-request', 'invalid-jid'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'}
+ %
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+subscribe_node(Host, Pubsub_Host, {U,S,_} = Entity,
+ {NodeId, Jid, Subscribe_Options} = _Parameters,
+ #capabilities{plugin = Plugin,
+ api = #api{options = Options_Module, db = API_DB, broadcast = API_Broadcast}}
+ = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('subscribe_node', Pubsub_Features, {Subscribe_Options}) of
+ ok ->
+ case is_jid(Jid) of
+ #jid{luser = U, lserver = S, lresource = Resource} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.subscribe_node,
+ 'subscribe_node',
+ [Host, Pubsub_Host, {U,S,undefined}, Plugin, Options_Module,
+ Pubsub_Features,
+ {NodeId, Resource, Subscribe_Options}])
+ of
+ {result,
+ {Subscription_State, SubId, _Resource, _Subscription_Options}
+ = Subscription, Affiliation,
+ Node_Owners, Node_Options, Pubsub_Last_Item} ->
+ case Pubsub_Last_Item of
+ #pubsub_last_item_dev{
+ id = ItemId,
+ creation = {DateTime, Creator},
+ payload = Payload,
+ options = Item_Options
+ } ->
+ spawn(API_Broadcast#api_broadcast.broadcast_publish_last,
+ broadcast_publish_last,
+ [Host, Pubsub_Host, NodeId, Node_Options,
+ Node_Owners,
+ [{ItemId, Item_Options, Payload, Creator, DateTime}],
+ Entity, Affiliation, Subscription]);
+ _ ->
+ ok
+ end,
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_subscription('pubsub',
+ NodeId, Jid, SubId, Subscription_State)])]};
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'bad-request', 'invalid-jid'}
+ end;
+ Error ->
+ Error
+ end.
+
+%-- Unubscribe_Node --%
+-spec(unsubscribe_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ _SubId :: undefined | exmpp_pubsub:subId()},
+ Capabilities :: #capabilities{})
+ -> {result, []}
+ %%%
+ | {error, 'feature-not-implemented', 'subscribe'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'unexpected', 'not-subscribed'}
+ %
+ | {error, 'unexpected', 'not-subscribed'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+unsubscribe_node(Host, Pubsub_Host, {U,S,_} = Entity,
+ {NodeId, Jid, _SubId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB, broadcast = API_Broadcast}}
+ = _Capabilities) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features = Plugin:pubsub_features()) of
+ true ->
+ case is_jid(Jid) of
+ #jid{luser = U, lserver = S, lresource = Resource} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.unsubscribe_node,
+ 'unsubscribe_node',
+ [Host, Pubsub_Host, {U,S,undefined},
+ {NodeId, Resource, _SubId}])
+ of
+ {result, SubId} ->
+ {result, []};
+ {result, SubId, Node_Owners, Notification_Type} ->
+ spawn(API_Broadcast#api_broadcast.notify_subscription,
+ notify_subscription,
+ [Host, NodeId, jlib:jid_to_string({<<>>, Host, <<>>}),
+ Node_Owners, Notification_Type,
+ {Jid, 'none', SubId}]),
+ {result, []};
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'forbidden'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'subscribe'}
+ end.
+
+%-- Get_Items --%
+-spec(get_items/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: non_neg_integer(),
+ ItemIds :: undefined}
+ | {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: undefined,
+ ItemIds :: [] | exmpp_pubsub:itemIds()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'retrieve-items'}
+ | {error, 'feature-not-implemented', 'persistent-items'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+get_items(Host, Pubsub_Host, {U,S,_} = _Entity,
+ {NodeId, SubId, Max_Items, ItemIds},
+ #capabilities{plugin = Plugin, api = #api{db = API_DB}} = _Capabilities) ->
+ case features('get_items', Plugin:pubsub_features(), none) of
+ ok ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_items,
+ 'get_items',
+ [Host, Pubsub_Host, {U,S,undefined},
+ {NodeId, SubId, Max_Items, ItemIds}])
+ of
+ {result, Pubsub_Items, Cache, Node_Owners, Node_Access_Model,
+ Node_Groups, Affiliation, Access} ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_items('pubsub', NodeId,
+ result_get_items(Host, Pubsub_Items,
+ {{U,S,undefined}, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ Cache, []))])]};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%
+-spec(result_get_items/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Entity_Parameters :: {Entity :: xmpp_jid:usr_bare(),
+ Affiliation :: 'member' | 'none' | 'owner' | 'publisher',
+ Access :: undefined | 'presence' | 'roster' | 'authorize'},
+ Node_Parameters :: {Node_Owners :: mod_pubsub_dev:node_owners(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Node_Groups :: [Group::binary()]},
+ Cache :: {Presence_Cache :: undefined | boolean(),
+ Groups_Cache :: undefined
+ | [{Entity :: xmpp_jid:usr_bare(),
+ Groups :: [Group::binary()]}]},
+ Xmlels_Item :: [Xmlel_Item::#xmlel{name::binary(), children::[]}])
+ -> Xmlels_Item :: [Xmlel_Item::#xmlel{name::binary(), children::[]}]
+).
+
+result_get_items(_Host, [] = _Pubsub_Items, _Entity_Parameters, _Node_Parameters,
+ _Cache, Xmlels_Item) ->
+ Xmlels_Item;
+%%
+result_get_items(Host,
+ [#pubsub_item_dev{id = {ItemId, _NodeIdx}, payload = Payload, options = Item_Options}
+ | Pubsub_Items],
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ Cache, Xmlels_Item)
+ when Item_Options == []
+ orelse Affiliation == 'owner' ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ Cache,
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item]);
+%%
+result_get_items(Host,
+ [#pubsub_item_dev{id = {ItemId, _NodeIdx}, payload = Payload, options = Item_Options} | Pubsub_Items],
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache} = _Cache, Xmlels_Item) ->
+ Item_Groups = get_value(Item_Options, 'roster_groups_allowed', []),
+ case get_value(Item_Options, 'access_model', undefined) of
+ %%
+ Item_Access_Model
+ when Item_Access_Model == undefined
+ orelse Item_Access_Model == 'open'
+ orelse Item_Access_Model == 'authorize'
+ orelse Item_Access_Model == 'whitelist'
+ %
+ orelse (Item_Access_Model == Node_Access_Model
+ andalso
+ Item_Access_Model =/= 'roster')
+ %
+ orelse (Item_Access_Model == 'presence'
+ andalso
+ (Access == 'presence'
+ orelse
+ Access == 'roster'
+ orelse
+ Presence_Cache == true))
+ %
+ orelse (Item_Access_Model == 'roster'
+ andalso
+ Node_Access_Model == 'roster'
+ andalso
+ Item_Groups == [])
+ %
+ orelse ((Item_Access_Model == 'authorize'
+ orelse
+ Item_Access_Model == 'whitelist')
+ andalso
+ (Affiliation == 'member'
+ orelse
+ Affiliation == 'publisher')) ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache},
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item]);
+ %%
+ Item_Access_Model
+ when Item_Access_Model == 'presence'
+ andalso Presence_Cache == undefined ->
+ case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
+ false ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {_Presence_Cache = false, Groups_Cache},
+ Xmlels_Item);
+ _Node_Owner ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {_Presence_Cache = true, Groups_Cache},
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item])
+ end;
+ Item_Access_Model
+ when Item_Access_Model == 'roster'
+ andalso Groups_Cache == undefined ->
+ case is_contact_in_allowed_roster_groups(Entity, Item_Groups) of
+ {Node_Owner, Roster_Group} ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, _Groups_Cache = [{Node_Owner, [Roster_Group]}]},
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item]);
+ false ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache},
+ Xmlels_Item)
+ end;
+ Item_Access_Model
+ when Item_Access_Model == 'roster' ->
+ case
+ lists:any(fun
+ ({Owner, Groups}) ->
+ case lists:keyfind(Owner, 1, Item_Groups) of
+ {_Owner, Owner_Groups} ->
+ lists:any(fun
+ (Group) ->
+ lists:member(Group, Owner_Groups)
+ end, Groups);
+ false ->
+ false
+ end
+ end, Groups_Cache)
+ of
+ true ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache},
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item]);
+ false ->
+ case is_contact_in_allowed_roster_groups(Entity, Node_Groups) of
+ {Node_Owner, Roster_Group} ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache,
+ _Groups_Cache = case
+ lists:keyfind(Node_Owner, 1, Groups_Cache)
+ of
+ {_Node_Owner, Node_Owner_Groups} ->
+ lists:keyreplace(Node_Owner, 1, Groups_Cache,
+ {Node_Owner,
+ [Roster_Group
+ | lists:delete(Roster_Group, Node_Owner_Groups)]});
+ false ->
+ [{Node_Owner, [Roster_Group]} | Groups_Cache]
+ end},
+ [exmpp_pubsub:xmlel_item('pubsub', ItemId, Payload)
+ | Xmlels_Item]);
+ false ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache},
+ Xmlels_Item)
+ end
+ end;
+ _ ->
+ result_get_items(Host, Pubsub_Items,
+ {Entity, Affiliation, Access},
+ {Node_Owners, Node_Access_Model, Node_Groups},
+ {Presence_Cache, Groups_Cache},
+ Xmlels_Item)
+ end.
+
+%-- Set_Configure_Subscription --%
+-spec(set_configure_subscription/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]},
+ Capabilities :: #capabilities{})
+ -> {result, []}
+ %%%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %
+ | {error, 'bad-request', 'invalid-options'}
+ | {error, 'bad-request', 'jid-required'}
+ | {error, 'bad-request', 'invalid-jid'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ | {error, 'bad-request', 'invalid-options'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+set_configure_subscription(Host, Pubsub_Host, {U,S,_} = Entity,
+ {NodeId, Jid, SubId, Subscribe_Options},
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module, db = API_DB}}
+ = _Capabilities) ->
+ case
+ checks_on_set_configure_subscription(Entity, Jid, Plugin,
+ Subscribe_Options)
+ of
+ {ok, Pubsub_Features, Resource} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.set_configure_subscription,
+ 'set_configure_subscription',
+ [Host, Pubsub_Host, Entity, Plugin, Options_Module,
+ Pubsub_Features, {NodeId, SubId, Resource, Subscribe_Options}])
+ of
+ {result, []} ->
+ {result, []};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%
+-spec(checks_on_set_configure_subscription/4 ::
+(
+ Entity :: xmpp_jid:usr_entity(),
+ Subscriber_Jid :: binary() | undefined,
+ Plugin :: exmpp_pubsub:plugin(),
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()])
+ -> {ok,
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Resource :: undefined | xmpp_jid:resource_jid()}
+ %%%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %
+ | {error, 'bad-request', 'invalid-options'}
+ | {error, 'bad-request', 'jid-required'}
+ | {error, 'bad-request', 'invalid-jid'}
+).
+
+checks_on_set_configure_subscription({U,S,_} = _Entity, Subscriber_Jid, Plugin,
+ Subscribe_Options) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case lists:member(<<"subscription-options">>, Pubsub_Features) of
+ true ->
+ case Subscribe_Options of
+ [] ->
+ {error, 'bad-request', 'invalid-options'};
+ _ ->
+ case Subscriber_Jid of
+ undefined ->
+ {error, 'bad-request', 'jid-required'};
+ _ ->
+ case is_jid(Subscriber_Jid) of
+ #jid{luser = U, lserver = S, lresource = Resource} ->
+ {ok, Pubsub_Features, Resource};
+ _ ->
+ {error, 'bad-request', 'invalid-jid'}
+ end
+ end
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'subscription-options'}
+ end.
+
+%-- Get_Entity_Affiliations --%
+-spec(get_entity_affiliations/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'retrieve-affiliations'}
+).
+
+get_entity_affiliations(_Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB}} = _Capabilities) ->
+ case
+ lists:member(<<"retrieve-affiliations">>,
+ _Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ {result,
+ result_get_entity_affiliations(
+ _Entity_Affiliations = pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_entity_affiliations,
+ 'get_entity_affiliations',
+ [Pubsub_Host, {U,S,undefined}, {NodeId}]))};
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-affiliations'}
+ end.
+
+%%
+-spec(result_get_entity_affiliations/1 ::
+(
+ Entity_Affiliations :: [Entity_Affiliation :: {
+ NodeId :: exmpp_pubsub:nodeId(),
+ Affiliation :: exmpp_pubsub:affiliation()
+ }])
+ -> [Xmlel_Pubsub::xmlel(),...]
+).
+
+result_get_entity_affiliations(Entity_Affiliations) ->
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_affiliations('pubsub',
+ lists:map(fun({NodeId, Affiliation}) ->
+ exmpp_pubsub:xmlel_affiliation('pubsub', NodeId, Affiliation)
+ end, Entity_Affiliations)
+ )]
+ )].
+
+
+%-- Get_Entity_Subscriptions --%
+-spec(get_entity_subscriptions/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'retrieve-subscriptions'}
+).
+
+get_entity_subscriptions(_Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB}} = _Capabilities) ->
+ case
+ lists:member(<<"retrieve-subscriptions">>,
+ _Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ {result,
+ result_get_entity_subscriptions({U,S,undefined},
+ _Entity_Subscriptions = pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_entity_subscriptions,
+ 'get_entity_subscriptions',
+ [Pubsub_Host, {U,S,undefined}, {NodeId}]))
+ };
+ false ->
+ {error, 'feature-not-implemented', 'retrieve-subscriptions'}
+ end.
+
+%%
+-spec(result_get_entity_subscriptions/2 ::
+(
+ Entity :: xmpp_jid:usr_bare(),
+ Entity_Subscriptions :: [{NodeId::exmpp_pubsub:nodeId(),
+ Subscriptions::exmpp_pubsub:subscriptions()}])
+ -> [Xmlel_Pubsub::xmlel(),...]
+).
+
+result_get_entity_subscriptions({U,S,_R} = _Entity, Entity_Subscriptions) ->
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_subscriptions('pubsub',
+ lists:map(fun
+ ({NodeId, Subscriptions}) ->
+ lists:map(fun
+ %%
+ ({Subscription_State, SubId, {caps, Resource},
+ _Subscription_Options}) ->
+ exmpp_pubsub:xmlel_subscription('pubsub',
+ NodeId,
+ _Jid = jlib:jid_to_string({U, S, <<>>}),
+ SubId,
+ Subscription_State);
+ %%
+ ({Subscription_State, SubId, Resource,
+ _Subscription_Options}) ->
+ exmpp_pubsub:xmlel_subscription('pubsub',
+ NodeId,
+ _Jid = jlib:jid_to_string({U, S, Resource}),
+ SubId,
+ Subscription_State)
+ %
+ end, Subscriptions)
+ end, Entity_Subscriptions)
+ )]
+ )].
+
+
+%-- Get_Node_Affiliations --%
+-spec(get_node_affiliations/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'modify-affiliations'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_node_affiliations(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB}} = _Capabilities) ->
+ case
+ lists:member(<<"modify-affiliations">>,
+ _Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_node_affiliations,
+ 'get_node_affiliations',
+ [Host, Pubsub_Host, {U,S,undefined}, {NodeId}])
+ of
+ {result, Pubsub_States} ->
+ {result, result_get_node_affiliations(NodeId, Pubsub_States)};
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'modify-affiliations'}
+ end.
+
+%%
+-spec(result_get_node_affiliations/2 ::
+(
+ NodeId :: exmpp_pubsub:nodeId(),
+ Pubsub_States :: [Pusbub_State::#pubsub_state_dev{},...])
+ -> [Xmlel_Pubsub::xmlel(),...]
+).
+
+result_get_node_affiliations(NodeId, Pubsub_States) ->
+ [exmpp_pubsub:xmlel_pubsub('pubsub#owner',
+ [exmpp_pubsub:xmlel_affiliations('pubsub#owner', NodeId,
+ lists:map(fun
+ (#pubsub_state_dev{id = {Entity, _NodeIdx},
+ affiliation = Affiliation}) ->
+ exmpp_pubsub:xmlel_affiliation('pubsub#owner',
+ _Jid = pubsub_tools:jid_to_string(Entity),
+ Affiliation)
+ end, Pubsub_States)
+ )]
+ )].
+
+
+%-- Get_Node_Subscriptions --%
+-spec(get_node_subscriptions/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'manage-subscriptions'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_node_subscriptions(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{db = API_DB}} = _Capabilities) ->
+ case
+ lists:member(<<"manage-subscriptions">>,
+ _Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_node_subscriptions,
+ 'get_node_subscriptions',
+ [Host, Pubsub_Host, {U,S,undefined}, {NodeId}])
+ of
+ {result, Pubsub_States} ->
+ {result, result_get_node_subscriptions(NodeId, Pubsub_States)};
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'manage-subscriptions'}
+ end.
+
+%%
+-spec(result_get_node_subscriptions/2 ::
+(
+ NodeId :: exmpp_pubsub:nodeId(),
+ Pubsub_States :: [Pubsub_State::#pubsub_state_dev{}])
+ -> [Xmlel_Pubsub::xmlel(),...]
+).
+
+result_get_node_subscriptions(NodeId, Pubsub_States) ->
+ [exmpp_pubsub:xmlel_pubsub('pubsub#owner',
+ [exmpp_pubsub:xmlel_subscriptions('pubsub#owner', NodeId,
+ lists:foldl(fun
+ %%
+ (#pubsub_state_dev{affiliation = 'none'}, Xmlels_Subscription) ->
+ Xmlels_Subscription;
+ %%
+ (#pubsub_state_dev{id = {{U, S, _}, _NodeIdx},
+ subscriptions = Subscriptions}, Xmlels_Subscription) ->
+ Xmlels_Subscription
+ ++
+ lists:map(fun
+ %%
+ ({Subscription_State, SubId, {caps, Resource},
+ _Subscription_Options}) ->
+ exmpp_pubsub:xmlel_subscription('pubsub#owner',
+ _Jid = pubsub_tools:jid_to_string({U, S, Resource}),
+ SubId,
+ Subscription_State);
+ %%
+ ({Subscription_State, SubId, Resource,
+ _Subscription_Options}) ->
+ exmpp_pubsub:xmlel_subscription('pubsub#owner',
+ _Jid = pubsub_tools:jid_to_string({U, S, Resource}),
+ SubId,
+ Subscription_State)
+ end, Subscriptions)
+ %
+ end, [], Pubsub_States)
+ )]
+ )].
+
+%% Get_Configure_Subscription_Default
+-spec(get_configure_subscription_default/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ | {error, 'feature-not-implemented', 'retrieve-default-sub'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_configure_subscription_default(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {undefined = _NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module,
+ db = API_DB}} = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('get_configure_subscription_default', Pubsub_Features, none) of
+ ok ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_default('pubsub',
+ [Options_Module:xdata_x('subscribe_options', 'result',
+ Pubsub_Features, Host, {U,S,undefined},
+ _Subscription_Options = Plugin:default_subscription_options(
+ case Host of
+ S -> 'local';
+ _ -> 'remote'
+ end,
+ 'leaf')
+ )]
+ )]
+ )]
+ };
+ Error ->
+ Error
+ end;
+%%
+get_configure_subscription_default(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module,
+ db = API_DB}} = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('get_configure_subscription_default', Pubsub_Features, none) of
+ ok ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_configure_subscription_default,
+ 'get_configure_subscription_default',
+ [Host, Pubsub_Host, {U,S,undefined}, NodeId])
+ of
+ ok ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_default('pubsub', NodeId,
+ [Options_Module:xdata_x('subscribe_options', 'result',
+ Pubsub_Features, Host, {U,S,undefined},
+ _Subscription_Options = Plugin:default_subscription_options(
+ case Host of
+ S -> 'local';
+ _ -> 'remote'
+ end,
+ 'leaf')
+ )]
+ )]
+ )]
+ };
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%% Get_Configure_Subscription
+-spec(get_configure_subscription/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ SubId :: undefined | exmpp_pubsub:subId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+get_configure_subscription(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId, Jid, SubId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module,
+ db = API_DB}} = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case checks_on_get_configure_subscription({U,S,undefined}, Jid, Plugin) of
+ {ok, Pubsub_Features, Resource} ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_configure_subscription,
+ 'get_configure_subscription',
+ [Host, Pubsub_Host, {U,S,undefined}, Plugin,
+ Pubsub_Features, {NodeId, SubId, Resource}])
+ of
+ {result, {_, _, _, Subscription_Options} = Subscription} ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub',
+ [exmpp_pubsub:xmlel_options('pubsub', NodeId, Jid, SubId,
+ [Options_Module:xdata_x('subscribe_options', 'form',
+ Pubsub_Features, Host, {U,S,undefined},Subscription_Options,
+ _Default_Subscription_Options = Plugin:default_subscription_options(
+ case Host of
+ S -> 'local';
+ _ -> 'remote'
+ end,
+ 'leaf')
+ )]
+ )]
+ )]
+ };
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%
+-spec(checks_on_get_configure_subscription/3 ::
+(
+ Entity :: xmpp_jid:usr_entity(),
+ Subscriber_Jid :: binary() | undefined,
+ Plugin :: exmpp_pubsub:plugin())
+ -> {ok,
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Resource :: undefined | xmpp_jid:resource_jid()}
+ %%%
+ | {error, 'feature-not-implemented', 'subscription-options'}
+ %
+ | {error, 'bad-request', 'jid-required'}
+ | {error, 'bad-request', 'invalid-jid'}
+).
+
+checks_on_get_configure_subscription({U,S,_} = _Entity, Subscriber_Jid, Plugin) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case lists:member(<<"subscription-options">>, Pubsub_Features) of
+ true ->
+ case Subscriber_Jid of
+ undefined ->
+ {error, 'bad-request', 'jid-required'};
+ _ ->
+ case is_jid(Subscriber_Jid) of
+ #jid{luser = U, lserver = S, lresource = Resource} ->
+ {ok, Pubsub_Features, Resource};
+ _ ->
+ {error, 'bad-request', 'invalid-jid'}
+ end
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'subscription-options'}
+ end.
+
+%% Get_Configure_Node_Default
+-spec(get_configure_node_default/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'config-node'}
+ | {error, 'feature-not-implemented', 'retrieve-default'}
+).
+
+get_configure_node_default(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {undefined = _NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module,
+ db = API_DB}} = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case features('get_configure_node_default', Pubsub_Features, none) of
+ ok ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub#owner',
+ [exmpp_pubsub:xmlel_default('pubsub#owner',
+ [Options_Module:xdata_x('node_config', 'form',
+ Pubsub_Features, Host, {U,S,undefined},
+ _Node_Options = Plugin:node_options('leaf')
+ )]
+ )]
+ )]
+ };
+ Error ->
+ Error
+ end.
+
+%% Get_Configure_Node
+-spec(get_configure_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()},
+ Capabilities :: #capabilities{})
+ -> {result, [Xmlel_Pubsub::xmlel(),...]}
+ %%%
+ | {error, 'feature-not-implemented', 'config-node'}
+ %
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+
+).
+
+get_configure_node(Host, Pubsub_Host, {U,S,_R} = _Entity,
+ {NodeId} = _Parameters,
+ #capabilities{plugin = Plugin, api = #api{options = Options_Module,
+ db = API_DB}} = _Capabilities) ->
+ Pubsub_Features = Plugin:pubsub_features(),
+ case lists:member(<<"config-node">>, Pubsub_Features) of
+ true ->
+ case
+ pubsub_db:transaction('mnesia',
+ API_DB#api_db.get_configure_node,
+ 'get_configure_node',
+ [Host, Pubsub_Host, {U,S,undefined}, {NodeId}])
+ of
+ {result, Node_Options} ->
+ {result,
+ [exmpp_pubsub:xmlel_pubsub('pubsub#owner',
+ [exmpp_pubsub:xmlel_configure('pubsub#owner', NodeId,
+ [Options_Module:xdata_x('node_config', 'form',
+ Pubsub_Features, Host, {U,S,undefined}, Node_Options
+ )]
+ )]
+ )]
+ };
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'config-node'}
+ end.
+
diff --git a/src/mod_pubsub_ng/pubsub_db.erl b/src/mod_pubsub_ng/pubsub_db.erl
new file mode 100644
index 000000000..b08fe611b
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_db.erl
@@ -0,0 +1,1575 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http:%%www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http:%%www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_db).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+
+
+%% @doc Intialize mnesia
+
+-spec(init/2 ::
+(
+ Backend :: 'mnesia' | 'odbc',
+ Suffix :: atom())
+ -> 'ok'
+).
+
+init('mnesia', Suffix) ->
+ pubsub_db_mnesia:init(Suffix);
+init('odbc', _Suffix) ->
+ ok.
+
+
+transaction('mnesia', Module, Function, Arguments) ->
+ pubsub_db_mnesia:transaction(Module, Function, Arguments).
+
+
+%%
+db_transaction(Module, Function, Arguments) ->
+ case mnesia:transaction(
+ fun() ->
+ apply(Module, Function, Arguments)
+ end)
+ of
+ {atomic, Result} ->
+ Result;
+ {aborted, Reason} ->
+ ?INFO_MSG("DB TRANSACTION ERROR ~p", [Reason]),
+ {error, 'internal-server-error'}
+ end.
+
+%-- Create_Node --%
+-spec(create_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Entity :: xmpp_jid:usr_bare(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Options :: pubsub_options:options_node()})
+ -> %%
+ {result,
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notify :: []
+ | [Notify_Subscription :: {
+ Notification_Type :: pubsub_options:notification_type(),
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ },...]
+ }
+ %
+ %%%
+ | {error, 'conflict'}
+).
+
+create_node(Host, Pubsub_Host, {_U,S,_R} = Entity, Pubsub_Features,
+ _Parameters = {undefined = _NodeId, Node_Options}) ->
+ case
+ pubsub_db_mnesia:read_node('dev',
+ Pubsub_Host, NodeId = exmpp_pubsub:nodeId())
+ of
+ undefined ->
+ {result,
+ NodeId,
+ _Notify = case
+ pubsub_db_mnesia:create_node('dev', Pubsub_Host, Entity, NodeId,
+ _Level = 1, Node_Options, Pubsub_Features)
+ of
+ undefined ->
+ [];
+ Subscription ->
+ [_Notify_Subscription = {
+ _Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ Subscription
+ }]
+ end};
+ _Pubsub_Node ->
+ create_node(Host, Pubsub_Host, Entity, Pubsub_Features,
+ {_NodeId = undefined, Node_Options})
+ end;
+%%
+create_node(Host, Pubsub_Host, {_U,S,_R} = Entity, Pubsub_Features,
+ _Parameters = {NodeId, Node_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {result,
+ NodeId,
+ _Notify = case
+ pubsub_db_mnesia:create_node('dev', Pubsub_Host, Entity, NodeId,
+ _Level = 1, Node_Options, Pubsub_Features)
+ of
+ undefined ->
+ [];
+ Subscription ->
+ [_Notify_Subscription = {
+ _Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ Subscription
+ }]
+ end};
+ _Pubsub_Node ->
+ {error, 'conflict'}
+ end.
+
+%-- Delete_Node --%
+-spec(delete_node/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> %%
+ {result,
+ Notify :: []
+ | [Notify_Delete :: {
+ Notification_Type::pubsub_options:notification_type(),
+ Recipients::[Entity::xmpp_jid:usr_bare()]
+ }]
+ }
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+delete_node(_Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+delete_node(Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case
+ lists:member(Entity, Owners = Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ {result,
+ _Notify = case
+ pubsub_db_mnesia:delete_node('dev', Pubsub_Node)
+ of
+ undefined ->
+ [];
+ Subscribers ->
+ [_Notify_Delete = {
+ _Notification_Type = get_value(
+ Pubsub_Node#pubsub_node_dev.options,
+ 'notification_type', 'headline'),
+ _Recipients = lists:append(Owners, Subscribers)
+ }]
+ end};
+ false ->
+ {error, 'forbidden'}
+ end
+ end.
+
+%-- Purge_Node --%
+-spec(purge_node/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> %%
+ {result,
+ Notify :: []
+ | [Notify_Purge :: {
+ Notification_Type::pubsub_options:notification_type(),
+ Recipients::[Entity::xmpp_jid:usr_bare()]
+ }]
+ }
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'feature-not-implemented', 'persistent-items'}
+).
+
+purge_node(_Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+purge_node(Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case lists:member(Entity, Pubsub_Node#pubsub_node_dev.owners) of
+ true ->
+ case
+ _Persist_Items = get_value(
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ 'persist_items', true)
+ of
+ true ->
+ {result,
+ _Notify = case
+ pubsub_db_mnesia:purge_node('dev', Pubsub_Node)
+ of
+ undefined ->
+ [];
+ Subscribers ->
+ [_Notify_Purge = {
+ _Notification_Type = get_value(
+ Node_Options,
+ 'notification_type', 'headline'),
+ _Recipients = Subscribers
+ }]
+ end};
+ false ->
+ {error, 'feature-not-implemented', 'persistent-items'}
+ end;
+ false ->
+ {error, 'forbidden'}
+ end
+ end.
+
+%-- Publish_Item --%
+-spec(publish_item/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Entity :: xmpp_jid:usr_entity(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Item :: 0 | 1,
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...],
+ Options :: {Node_Options :: [] | pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}
+ })
+ -> {result,
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Subscription :: undefined | exmpp_pubsub:subscription_subscribed(),
+ Broadcasted_Items :: {
+ Published_Items :: [Published_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Payload :: exmpp_pubsub:payload(),
+ Publisher :: xmpp_jid:usr_entity()
+ }],
+ %
+ Retracted_Items :: [Retracted_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item()
+ }]
+ },
+ Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+%%
+publish_item(Host, Pubsub_Host, {U,S,R} = Entity, Plugin, Pubsub_Features,
+ {undefined = _NodeId, Item, _ItemId, Payload,
+ {[] = _Node_Options, Item_Options}}) ->
+ case
+ pubsub_db_mnesia:read_node('dev',
+ Pubsub_Host, NodeId = exmpp_pubsub:nodeId())
+ of
+ undefined ->
+ case
+ options_on_publish_item(none, {Item, Payload,
+ Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok ->
+ {ItemId, Pubsub_State} = pubsub_db_mnesia:publish_item1('dev',
+ Pubsub_Host, Entity, NodeId, _Level = 1, _ItemId, Payload,
+ Options, Pubsub_Features),
+ Subscription = case
+ Pubsub_State#pubsub_state_dev.subscriptions
+ of
+ [] -> undefined;
+ [_Subscription] -> _Subscription
+ end,
+ {result, NodeId, ItemId, Node_Options,
+ _Node_Owners = [{U,S,undefined}],
+ _SubId = case Subscription of
+ undefined ->
+ undefined;
+ {_Subscription_State, SubId, _Resource,
+ _Subscription_Options} ->
+ SubId
+ end,
+ Subscription,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ _Retracted_Items = []},
+ _Pubsub_States = [Pubsub_State]};
+ Error ->
+ Error
+ end;
+ _Pusbub_Node ->
+ publish_item(Host, Pubsub_Host, {U,S,R}, Plugin, Pubsub_Features,
+ {undefined, Item, _ItemId, Payload, {[], Item_Options}})
+ end;
+
+%%
+publish_item(Host, Pubsub_Host, {U,S,R} = Entity, Plugin, Pubsub_Features,
+ {NodeId, Item, _ItemId, Payload, {[] = _Node_Options, Item_Options}}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ case
+ checks_on_publish_item(Host, {U,S,undefined}, Plugin,
+ {Item, Payload, _Node_Options = [], Item_Options})
+ of
+ {ok, Pubsub_Features, Node_Options} ->
+ Options = {Node_Options, Item_Options},
+ {ItemId, Pubsub_State} = pubsub_db_mnesia:publish_item1('dev',
+ Pubsub_Host, Entity, NodeId, _Level = 1, _ItemId, Payload,
+ Options, Pubsub_Features),
+ Subscription = case
+ Pubsub_State#pubsub_state_dev.subscriptions
+ of
+ [] -> undefined;
+ [_Subscription] -> _Subscription
+ end,
+ {result, NodeId, ItemId, Node_Options,
+ _Node_Owners = [{U,S,undefined}],
+ _SubId = case Subscription of
+ undefined ->
+ undefined;
+ {_Subscription_State, SubId, _Resource,
+ _Subscription_Options} ->
+ SubId
+ end,
+ Subscription,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ _Retracted_Items = []},
+ _Pubsub_States = [Pubsub_State]};
+ Error ->
+ Error
+ end;
+ Pubsub_Node ->
+ NodeIdx = Pubsub_Node#pubsub_node_dev.idx,
+ Pubsub_State = pubsub_db_mnesia:read_state('dev',
+ {U,S,undefined}, NodeIdx),
+ case
+ options_on_publish_item(Host, Entity,
+ _Affiliation = case Pubsub_State of
+ undefined -> 'none';
+ _ -> Pubsub_State#pubsub_state_dev.affiliation
+ end,
+ _Subscriptions = case Pubsub_State of
+ undefined -> [];
+ _ -> Pubsub_State#pubsub_state_dev.subscriptions
+ end,
+ _Options = {
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ Item_Options
+ },
+ Node_Owners = Pubsub_Node#pubsub_node_dev.owners,
+ Item,
+ Payload)
+ of
+ ok ->
+ {ItemId, Retracted_Items, Pubsub_States} =
+ pubsub_db_mnesia:publish_item2('dev', Pubsub_Host,
+ {U,S,R}, Pubsub_State, Pubsub_Node,
+ case _ItemId of
+ undefined ->
+ undefined;
+ _ ->
+ case
+ pubsub_db_mnesia:read_item('dev',
+ NodeIdx, _ItemId)
+ of
+ undefined -> _ItemId;
+ Pubsub_Item -> Pubsub_Item
+ end
+ end,
+ Payload, Item_Options, Pubsub_Features),
+ {result, NodeId, ItemId, Node_Options, Node_Owners,
+ _SubId = undefined, _Subscription = undefined,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ Retracted_Items},
+ Pubsub_States};
+ Error ->
+ Error
+ end
+ end.
+
+
+%%
+-spec(options_on_publish_item/8 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()},
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...])
+ -> ok
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+options_on_publish_item(_Host, _Entity, 'owner' = _Affiliation, _Subscriptions,
+ Options, _Node_Owners, Item, Payload) ->
+ options_on_publish_item(none, {Item, Payload, Options});
+%%
+options_on_publish_item(Host, Entity, 'outcast' = _Affiliation, _Subscriptions,
+ {Node_Options, _Item_Options}, Node_Owners, _Item, _Payload) ->
+ case get_value(Node_Options, 'access_model') of
+ %%
+ 'open'->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed = get_value(Node_Options,
+ 'roster_groups_allowed'))
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ {error, 'item-not-found'}
+ end;
+%%
+options_on_publish_item(Host, Entity, Affiliation, Subscriptions,
+ {Node_Options, Item_Options} = _Options, Node_Owners, Item, Payload) ->
+ %% 'pubsub#access_model'
+ case
+ check_access_model(Host, Entity,
+ _Access_Model = get_value(Node_Options, 'access_model'),
+ Affiliation, Subscriptions, Node_Owners,
+ _Rosters_Groups_Allowed = get_value(Node_Options,
+ 'roster_groups_allowed'))
+ of
+ ok ->
+ %% 'pubsub#publish_model'
+ case
+ check_publish_model(
+ _Publish_Model = get_value(Node_Options, 'publish_model'),
+ Affiliation, Subscriptions)
+ of
+ ok ->
+ options_on_publish_item(none,
+ {Item, Payload, {Node_Options, Item_Options}});
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+
+ end.
+
+%%
+-spec(options_on_publish_item/2 ::
+(
+
+ Option :: none
+ | {pubsub_options:option_node_deliver_payloads(),
+ pubsub_options:option_node_persist_items()}
+ | pubsub_options:option_node_max_payload_size()
+ | pubsub_options:option_node_type(),
+ %%
+ Criteria :: {Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...],
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ | {Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ %
+ | {Payload :: exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ %
+ | {Payload :: exmpp_pubsub:payload()})
+ -> %%
+ ok
+ %%%
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'payload-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+%% Payloads count
+options_on_publish_item(none, {Item, [] = Payload, Options}) ->
+ options_on_publish_item(
+ {get_option(Options, 'deliver_payloads', false),
+ get_option(Options, 'persist_items', false)},
+ {Item, Payload, Options});
+%%
+options_on_publish_item(none, {Item, [Payload], Options}) ->
+ options_on_publish_item(
+ {get_option(Options, 'deliver_payloads', false),
+ get_option(Options, 'persist_items', false)},
+ {Item, Payload, Options});
+%%
+options_on_publish_item(none, _Parameters) ->
+ {error, 'bad-request', 'invalid-payload'};
+%%
+%% 'pubsub#deliver_payloads'
+%% 'pubsub#persist_items'
+options_on_publish_item(
+ {{'deliver_payloads', _Deliver_Payloads}, {'persist_items', true}},
+ {0 = _Item, _Payload, _Options}) ->
+ {error, 'bad-request', 'item-required'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', true}, {'persist_items', _Persist_Items}},
+ {_Item, [] = _Payload, _Options}) ->
+ {error, 'bad-request', 'payload-required'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', false}, {'persist_items', false}},
+ {1 = _Item, _Payload, _Options}) ->
+ {error, 'bad-request', 'item-forbidden'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', Deliver_Payloads}, {'persist_items', Persist_Items}},
+ {_Item, [] = _Payload, Options}) ->
+ ok;
+%%
+options_on_publish_item(
+ {{'deliver_payloads', Deliver_Payloads}, {'persist_items', Persist_Items}},
+ {_Item, Payload, {Node_Options, Item_Options} = _Options}) ->
+ options_on_publish_item(
+ _Max_Payload_Size = get_option(Node_Options, 'max_payload_size', 0),
+ {Payload, {Node_Options, Item_Options}});
+%% 'pubsub#max_payload_size'
+options_on_publish_item({'max_payload_size', Max_Payload_Size},
+ {Payload, Options}) ->
+ case byte_size(term_to_binary(Payload)) =< Max_Payload_Size of
+ true ->
+ options_on_publish_item(get_option(Options, 'type', undefined),
+ {Payload});
+ false ->
+ {error, 'not-acceptable', 'payload-too-big'}
+ end;
+%% 'pubsub#type'
+options_on_publish_item({'type', Type}, {Payload})
+ when Type == undefined ->
+ %%orelse Type == Payload#xmlel.ns ->
+ ok;
+%%
+options_on_publish_item(_, _) ->
+ {error, 'bad-request', 'invalid-payload'}.
+
+
+
+%%
+-spec(checks_on_publish_item/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_entity(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Parameters :: {Item :: 0 | 1,
+ Payload :: [Xmlel::xmlel()],
+ Node_Options :: [],
+ Item_Options :: [] | pubsub_options:options_item()})
+ -> {ok,
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Node_Options :: pubsub_options:options_node()}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+
+checks_on_publish_item({U,S,_} = _Host, {U,S,_} = _Entity, Plugin,
+ {Item, Payload, [] = _Node_Options, Item_Options}) ->
+ case
+ lists:member(<<"auto-create">>, Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ options_on_publish_item(none,
+ {Item,
+ Payload,
+ _Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok -> {ok, Pubsub_Features, Node_Options};
+ Error -> Error
+ end;
+ false ->
+ {error, 'item-not-found'}
+ end;
+%%
+checks_on_publish_item(_Host, _Entity, _Plugin, _Parameters)
+ when is_tuple(_Host) ->
+ {error, 'foribdden'};
+%%
+checks_on_publish_item(Host, {U,S,_R} = _Entity, Plugin,
+ {Item, Payload, [] = _Node_Options, Item_Options}) ->
+ case acl:match_rule(Host, pubsub_createnode, jlib:jid_to_string({U,S, <<>>})) of
+ allow ->
+ case
+ lists:member(<<"auto-create">>,
+ Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ options_on_publish_item(none,
+ {Item, Payload,
+ _Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok -> {ok, Pubsub_Features, Node_Options};
+ Error -> Error
+ end;
+ false ->
+ {error, 'item-not-found'}
+ end;
+ _Deny ->
+ {error, 'forbidden'}
+ end.
+
+%-- Retract_Item --%
+-spec(retract_item/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Adhoc_Notify_Retract :: undefined | boolean()})
+ -> {result,
+ Notify :: []
+ | [Notify_Retract :: {
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Pubsub_States :: mod_pubsub_dev:pubsub_states()
+ }]}
+ %%%
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'bad-request', 'item-required'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+retract_item(_Pubsub_Host, _Entity,
+ {undefined = _NodeId, _ItemId, _Adhoc_Notify_Retract}) ->
+ {error, 'bad-request', 'nodeid-required'};
+%%
+retract_item(_Pubsub_Host, _Entity,
+ {_NodeId, undefined = _ItemId, _Adhoc_Notify_Retract}) ->
+ {error, 'bad-request', 'item-required'};
+%%
+retract_item(Pubsub_Host, Entity, {NodeId, ItemId, Adhoc_Notify_Retract}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity,
+ Pubsub_Node#pubsub_node_dev.idx)
+ of
+ Pubsub_State
+ when Pubsub_State == undefined
+ orelse Pubsub_State#pubsub_state_dev.affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ Pubsub_State ->
+ case
+ pubsub_db_mnesia:retract_item('dev', Pubsub_Node,
+ Pubsub_State, ItemId, Adhoc_Notify_Retract)
+ of
+ {ok, undefined} ->
+ {result, _Notify = []};
+ {ok, Item_Options, Pubsub_States} ->
+ {result,
+ _Notify = [_Notify_Retract = {
+ _Node_Owners = Pubsub_Node#pubsub_node_dev.owners,
+ _Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ Item_Options,
+ Pubsub_States
+ }]
+ };
+ Error ->
+ Error
+ end
+ end
+ end.
+
+
+%-- Subscribe_Node --%
+-spec(subscribe_node/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Options_Module :: module(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: exmpp_pubsub:nodeId(),
+ Resource :: undefined | xmpp_jid:resource_jid(),
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]})
+ -> {result,
+ Subscription_Subscribed :: exmpp_pubsub:subscription_subscribed(),
+ Affiliation :: 'member' | 'owner' | 'publisher',
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Pubsub_Last_Item :: undefined | mod_pubsub_dev:pubsub_last_item()}
+ %
+ | {result,
+ Subscription_Pending :: exmpp_pubsub:subscription_pending(),
+ Affiliation :: 'member',
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Pubsub_Last_Item :: undefined}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'}
+ %
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+subscribe_node(_Host, _Pubsub_Host, _Entity, _Plugin, _Options_Module,
+ _Pubsub_Features, {undefined = _NodeId, _Resource, _Subscribe_Options}) ->
+ {error, 'item-not-found'};
+%%
+subscribe_node(Host, Pubsub_Host, {_, S,_} = Entity, Plugin, Options_Module,
+ Pubsub_Features, {NodeId, Resource, Subscribe_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ _Entity_Type = case S == Host of
+ true -> 'local';
+ false -> 'remote'
+ end,
+ 'subscribe_options',
+ _Node_Type = get_value(Node_Options, 'node_type', 'leaf'),
+ Subscribe_Options)
+ of
+ {ok, Subscription_Options} ->
+ Pubsub_State = case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ undefined ->
+ #pubsub_state_dev{
+ id = {Entity, NodeIdx},
+ nodeidx = NodeIdx
+ };
+ _Pubsub_State ->
+ _Pubsub_State
+ end,
+ case
+ pubsub_db_mnesia:subscribe_node('dev', Host, Node_Options,
+ Node_Owners, Pubsub_State, Resource,
+ Subscription_Options, Pubsub_Features)
+ of
+ {result, Subscription, Pubsub_Last_Item} ->
+ {result,
+ Subscription,
+ case Pubsub_State#pubsub_state_dev.affiliation of
+ 'none' -> 'member';
+ Affiliation -> Affiliation
+ end,
+ Node_Owners, Node_Options, Pubsub_Last_Item};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end
+ end.
+
+%-- Unsubscribe_Node --%
+-spec(unsubscribe_node/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: exmpp_pubsub:nodeId(),
+ _Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ _SubId :: exmpp_pubsub:subId()})
+ -> {result,
+ SubId :: exmpp_pubsub:subId()}
+ |
+ {result,
+ SubId :: exmpp_pubsub:subId(),
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Notification_Type :: pubsub_options:notification_type()}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'unexpected', 'not-subscribed'}
+ %
+ | {error, 'unexpected', 'not-subscribed'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+unsubscribe_node(_Host, _Pubsub_Host, _Entity,
+ {undefined = _NodeId, _Resource, _SubId}) ->
+ {error, 'item-not-found'};
+%%
+unsubscribe_node(Host, Pubsub_Host, Entity, {NodeId, _Resource, _SubId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ Pubsub_State
+ when Pubsub_State == undefined
+ orelse Pubsub_State#pubsub_state_dev.affiliation == 'none'
+ orelse Pubsub_State#pubsub_state_dev.subscriptions == [] ->
+ {error, 'unexpected', 'not-subscribed'};
+ Pubsub_State
+ when Pubsub_State#pubsub_state_dev.affiliation == 'publish-only'->
+ {error, 'forbidden'};
+ Pubsub_State ->
+ case
+ pubsub_db_mnesia:unsubscribe_node('dev', Host,
+ _Node_Access_Model = get_value(Node_Options, 'access_model'),
+ Pubsub_State, _Resource, _SubId)
+ of
+ {ok, Resource, SubId} ->
+ case get_value(Node_Options, 'notify_sub', false) of
+ true ->
+ {result, SubId, Node_Owners,
+ get_value(Node_Options, 'notification_type',
+ 'headline')};
+ false ->
+ {result, SubId}
+ end;
+ Error ->
+ Error
+ end
+ end
+ end.
+
+%-- Set_Configure_Subscription --%
+-spec(set_configure_subscription/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Options_Module :: module(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]})
+ -> {result, []}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ | {error, 'bad-request', 'invalid-options'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+set_configure_subscription(_Host, _Pubsub_Host, _Entity, _Plugin, _Options_Module,
+ _Pubsub_Features, {undefined = _NodeId, _SubId, _Resource, _Subscribe_Options}) ->
+ {error, 'item-not-found'};
+%%
+set_configure_subscription(Host, Pubsub_Host, {_,S,_} = Entity, Plugin, Options_Module,
+ Pubsub_Features, {NodeId, SubId, Resource, Subscribe_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'publish-only'
+ orelse Affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{subscriptions = []} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ Pubsub_State ->
+ case
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ _Entity_Type = case S == Host of
+ true -> 'local';
+ false -> 'remote'
+ end,
+ 'subscribe_options',
+ _Node_Type = get_value(Node_Options, 'node_type', 'leaf'),
+ Subscribe_Options)
+ of
+ {ok, Subscription_Options} ->
+ case
+ pubsub_db_mnesia:set_configure_subscription('dev',
+ Pubsub_State, SubId, Resource,
+ Subscription_Options)
+ of
+ ok ->
+ {result, []};
+ Error ->
+ Error
+ end;
+ _Error ->
+ {error, 'bad-request', 'invalid-options'}
+ end
+ end
+ end.
+
+%-- Get_Items --%
+-spec(get_items/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pusbub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: non_neg_integer(),
+ ItemIds :: undefined}
+ | {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: undefined,
+ ItemIds :: [] | exmpp_pubsub:itemIds()})
+ -> {result,
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Cache :: {Presence_Cache :: undefined,
+ Groups_Cache :: undefined}
+ | {Presence_Cache :: true,
+ Groups_Cache :: undefined}
+ | {Presence_Cache :: undefined,
+ Groups_Cache :: [{Node_Owner :: xmpp_jid:usr_bare(),
+ Groups :: [Group::binary()]}]},
+ Node_Owners :: mod_pubsub_dev:node_owners(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Node_Groups :: pubsub_options:rosters_groups_allowed(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Access :: undefined | 'presence' | 'roster' | 'pending' | 'authorize'}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+get_items(_Host, _Pubsub_Host, _Entity,
+ {undefined = _NodeId, _SubId, _Max_Items, _ItemIds}) ->
+ {error, 'item-not-found'};
+%%
+get_items(Host, Pubsub_Host, Entity, {NodeId, _SubId, Max_Items, ItemIds}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners,
+ options = Node_Options, itemids = Node_ItemIds} ->
+ {Affiliation, Access} = case lists:member(Entity, Node_Owners) of
+ true ->
+ {'owner', undefined};
+ false ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {'none', undefined};
+ #pubsub_state_dev{affiliation = _Affiliation,
+ access = _Access} ->
+ {_Affiliation, _Access}
+ end
+ end,
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ Node_Groups = get_value(Node_Options, 'roster_groups_allowed', []),
+ case
+ pubsub_db_mnesia:get_items('dev', Host,
+ {NodeIdx, Node_Owners, Node_ItemIds, Node_Access_Model, Node_Groups},
+ {Entity, Affiliation, Access},
+ {Max_Items, ItemIds, _SubId})
+ of
+ {ok, Pubsub_Items, Cache} ->
+ {result, Pubsub_Items, Cache, Node_Owners, Node_Access_Model,
+ Node_Groups, Affiliation, Access};
+ Error ->
+ Error
+ end
+ end.
+
+%-- Get_Entity_Affiliations --%
+-spec(get_entity_affiliations/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> Entity_Affiliations :: [Entity_Affiliation :: {
+ NodeId :: exmpp_pubsub:nodeId(),
+ Affiliation :: exmpp_pubsub:affiliation()
+ }]
+).
+
+get_entity_affiliations(Pubsub_Host, Entity, {NodeId}) ->
+ pubsub_db_mnesia:get_entity_affiliations('dev', Pubsub_Host, Entity, NodeId).
+
+%-- Get_Entity_Subscriptions --%
+-spec(get_entity_subscriptions/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> Entity_Subscriptions :: [{
+ NodeId :: exmpp_pubsub:nodeId(),
+ Subscriptions :: _%exmpp_pubsub:subscriptions()
+ }]
+).
+
+get_entity_subscriptions(Pubsub_Host, Entity, {NodeId}) ->
+ pubsub_db_mnesia:get_entity_subscriptions('dev', Pubsub_Host, Entity, NodeId).
+
+
+%-- Get_Node_Affiliations --%
+-spec(get_node_affiliations/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+
+).
+
+get_node_affiliations(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+get_node_affiliations(Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Owners, options = Options} ->
+ case lists:member(Entity, Owners) of
+ true ->
+ {result,
+ _Pubsub_States = pubsub_db_mnesia:index_read_state('dev',
+ NodeIdx)};
+ false ->
+ %% Depending on the node access_model, the returned error
+ %% can be 'forbidden' or 'item-not-found' to not disclose
+ %% private nodes
+ case get_value(Options, 'access_model') of
+ %%
+ 'open' ->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed = get_value(
+ Options, 'roster_groups_allowed'))
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'member'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ {error, 'forbidden'};
+ _Pubsub_State ->
+ {error, 'item-not-found'}
+ end
+ end
+ end
+ end.
+
+%-- Get_Node_Subscriptions --%
+-spec(get_node_subscriptions/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+
+).
+
+get_node_subscriptions(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+get_node_subscriptions(Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Owners, options = Options} ->
+ case lists:member(Entity, Owners) of
+ true ->
+ {result,
+ _Pubsub_States = pubsub_db_mnesia:index_read_state('dev',
+ NodeIdx)};
+ false ->
+ %% Depending on the node access_model, the returned error
+ %% can be 'forbidden' or 'item-not-found' to not disclose
+ %% private nodes
+ case get_value(Options, 'access_model') of
+ %%
+ 'open' ->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed = get_value(Options,
+ 'roster_groups_allowed'))
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'member'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ {error, 'forbidden'};
+ _Pubsub_State ->
+ {error, 'item-not-found'}
+ end
+ end
+ end
+ end.
+
+%% Get_Configure_Subscription_Default
+-spec(get_configure_subscription_default/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ NodeId :: exmpp_pubsub:nodeId())
+ -> ok
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_configure_subscription_default(Host, Pubsub_Host, Entity, NodeId) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case lists:member(Entity, Node_Owners) of
+ true ->
+ ok;
+ false ->
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined
+ when Node_Access_Model == 'open' ->
+ ok;
+ undefined
+ when Node_Access_Model == 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Node_Owners)
+ of
+ false -> {error, 'forbidden'};
+ _Node_Owner -> ok
+ end;
+ undefined
+ when Node_Access_Model == 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed =
+ get_value(Node_Options,
+ 'roster_groups_allowed', []))
+ of
+ false -> {error, 'forbidden'};
+ _ -> ok
+ end;
+ undefined ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{affiliation = Affiliation,
+ access = Access}
+ when Affiliation == 'publisher'
+ orelse Affiliation == 'member'
+ orelse (Affiliation == 'none'
+ andalso
+ Node_Access_Model == 'open')
+ orelse Access == 'presence'
+ orelse Access == 'roster' ->
+ ok;
+ #pubsub_state_dev{affiliation = 'none'}
+ when Node_Access_Model == 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Node_Owners)
+ of
+ false ->
+ {error, 'forbidden'};
+ _Node_Owner ->
+ ok
+ end;
+ #pubsub_state_dev{affiliation = 'none'}
+ when Node_Access_Model == 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed =
+ get_value(Node_Options,
+ 'roster_groups_allowed', []))
+ of
+ false ->
+ {error, 'forbidden'};
+ _ ->
+ ok
+ end;
+ _ ->
+ {error, 'forbidden'}
+
+ end
+ end
+ end.
+
+%-- Get_Configure_Subscription --%
+-spec(get_configure_subscription/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()}
+ })
+ -> {result,
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ | exmpp_pubsub:subscription_unconfigured()}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+get_configure_subscription(_Host, _Pubsub_Host, _Entity, _Plugin,
+ _Pubsub_Features, {undefined = _NodeId, _SubId, _Resource}) ->
+ {error, 'item-not-found'};
+%%
+get_configure_subscription(_Host, Pubsub_Host, Entity, Plugin,
+ Pubsub_Features, {NodeId, SubId, Resource}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'publish-only'
+ orelse Affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{subscriptions = []} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{subscriptions = Subscriptions} ->
+ case filter_subscription(Subscriptions, SubId, Resource) of
+ {ok, Subscription} -> {result, Subscription};
+ Error -> Error
+ end
+ end
+ end.
+
+-spec(filter_subscription/3 ::
+(
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()})
+ -> {ok,
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ | exmpp_pubsub:subscription_unconfigured()}
+ %%%
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+filter_subscription(Subscriptions, undefined = _SubId, Resource) ->
+ case lists:keytake(Resource, 3, Subscriptions) of
+ {value, {Subscription_State, SubId, Resource, Subscription_Options},
+ _Subscriptions} ->
+ case lists:keymember(Resource, 3, _Subscriptions) of
+ false
+ when Subscription_State == 'pending' ->
+ {error, 'forbidden'};
+ false ->
+ {ok,
+ {Subscription_State, SubId, Resource, Subscription_Options}};
+ true ->
+ {error, 'bad-request', 'subid-required'}
+ end;
+ false ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end;
+%%
+filter_subscription(Subscriptions, SubId, Resource) ->
+ case lists:keyfind(SubId, 2, Subscriptions) of
+ %%
+ {'pending', _, Resource, _} ->
+ {error, 'forbidden'};
+ %%
+ {Subscription_State, SubId, Resource, Subscription_Options} ->
+ {ok, {Subscription_State, SubId, Resource, Subscription_Options}};
+ %%
+ {_, SubId, _, _} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ false ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {'pending', _, Resource, _} ->
+ {error, 'forbidden'};
+ {_, _, Resource, _} ->
+ {error, 'not-acceptable', 'invalid-subid'};
+ _ ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end
+ end.
+
+%-- Get_Configure_Node --%
+-spec(get_configure_node/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Node_Options :: pubsub_options:options_node()}
+ %%%
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_configure_node(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'bad-request', 'nodeid-required'};
+%%
+get_configure_node(_Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{owners = Node_Owners, options = Node_Options} ->
+ case lists:member(Entity, Node_Owners) of
+ true -> {result, Node_Options};
+ false -> {error, 'forbidden'}
+ end
+ end.
diff --git a/src/mod_pubsub_ng/pubsub_db_mnesia.erl b/src/mod_pubsub_ng/pubsub_db_mnesia.erl
new file mode 100644
index 000000000..ac41c36a0
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_db_mnesia.erl
@@ -0,0 +1,2129 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http:%%www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http:%%www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_db_mnesia).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+
+-export_type([
+ suffix/0,
+ table/0
+]).
+
+-type(suffix() :: undefined | atom()).
+-type(table() :: atom()).
+
+
+%%
+-spec(init/1 ::
+(
+ Suffix :: undefined | atom())
+ -> 'ok'
+).
+
+init(Suffix) ->
+ create_table_pubsub_node(Suffix),
+ create_table_pubsub_state(Suffix),
+ create_table_pubsub_item(Suffix),
+ create_table_pubsub_last_item(Suffix),
+ create_table_pubsub_subscription_pending(Suffix),
+ pubsub_index_dev:init(Suffix),
+ pubsub_groups:create_table_pubsub_groups(Suffix).
+
+%%
+-spec(table/2 ::
+(
+ Base_Table :: atom(),
+ Suffix :: undefined | atom())
+ -> Table :: atom()
+).
+
+table(Base_Table, undefined = _Suffix) ->
+ _Table = Base_Table;
+table(Base_Table, Suffix) ->
+ _Table = list_to_atom(atom_to_list(Base_Table) ++ "_" ++ atom_to_list(Suffix)).
+
+
+%%
+create_table_pubsub_node(Suffix) ->
+ mnesia:create_table(table('pubsub_node', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_node_dev},
+ {attributes, record_info(fields, pubsub_node_dev)}]),
+ mnesia:add_table_index(pubsub_node_dev, idx).
+
+create_table_pubsub_state(Suffix) ->
+ mnesia:create_table(table('pubsub_state', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_state_dev},
+ {attributes, record_info(fields, pubsub_state_dev)}]),
+ mnesia:add_table_index(pubsub_state_dev, nodeidx).
+
+create_table_pubsub_item(Suffix) ->
+ mnesia:create_table(table('pubsub_item', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_item_dev},
+ {attributes, record_info(fields, pubsub_item_dev)}]),
+ mnesia:add_table_index(pubsub_item_dev, nodeidx).
+
+create_table_pubsub_last_item(Suffix) ->
+ mnesia:create_table(table('pubsub_last_item', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_last_item_dev},
+ {attributes, record_info(fields, pubsub_last_item_dev)}]).
+
+create_table_pubsub_subscription_pending(Suffix) ->
+ mnesia:create_table(table('pubsub_subscription_pending', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_subscription_pending},
+ {attributes, record_info(fields, pubsub_subscription_pending)}]),
+ mnesia:add_table_index(pubsub_subscription_pending, nodeidx).
+
+%%
+transaction(Module, Function, Arguments) ->
+ case mnesia:transaction(fun() -> apply(Module, Function, Arguments) end) of
+ {atomic, Result} ->
+ Result;
+ {aborted, Reason} ->
+ ?INFO_MSG("DB TRANSACTION ERROR ~p", [Reason]),
+ {error, 'internal-server-error'}
+ end.
+
+%%
+-spec(read_item/3 ::
+(
+ Suffix :: atom(),
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ ItemId :: exmpp_pubsub:itemId())
+ -> Pubsub_Item :: undefined | mod_pubsub_dev:pubsub_item()
+).
+
+read_item(Suffix, NodeIdx, ItemId) ->
+ _Pubsub_Item = case
+ mnesia:read({_Table = table('pubsub_item', Suffix), {ItemId, NodeIdx}})
+ of
+ [Pubsub_Item] -> Pubsub_Item;
+ [] -> undefined
+ end.
+
+%%
+-spec(read_node/3 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ NodeId :: exmpp_pubsub:nodeId())
+ -> Pubsub_Node :: undefined | mod_pubsub_dev:pubsub_node()
+).
+
+read_node(Suffix, Pubsub_Host, NodeId) ->
+ _Pubsub_Node = case
+ mnesia:read({_Table = table('pubsub_node', Suffix),
+ {Pubsub_Host, NodeId}})
+ of
+ [Pubsub_Node] -> Pubsub_Node;
+ [] -> undefined
+ end.
+
+%%
+-spec(read_state/3 ::
+(
+ Suffix :: atom(),
+ Entity :: xmpp_jid:usr_entity(),
+ NodeIdx :: exmpp_pubsub:nodeIdx())
+ -> Pubsub_State :: undefined | mod_pubsub_dev:pubsub_state()
+).
+
+read_state(Suffix, {U,S,_R} = _Entity, NodeIdx) ->
+ _Pubsub_State = case
+ mnesia:read({_Table = table('pubsub_state', Suffix),
+ {{U,S,undefined}, NodeIdx}})
+ of
+ [Pubsub_State] -> Pubsub_State;
+ [] -> undefined
+ end.
+
+%%
+-spec(index_read_node/2 ::
+(
+ Suffix :: atom(),
+ NodeIdx :: exmpp_pubsub:nodeIdx())
+ -> Pubsub_Nodes::[Pubsub_Node::mod_pubsub_dev:pubsub_node()]
+).
+
+index_read_node(Suffix, NodeIdx) ->
+ _Pubsub_Nodes = mnesia:index_read(_Table = table('pubsub_node', Suffix),
+ NodeIdx, idx).
+
+%%
+-spec(index_read_state/2 ::
+(
+ Suffix :: atom(),
+ NodeIdx :: exmpp_pubsub:nodeIdx())
+ -> Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state()]
+).
+
+index_read_state(Suffix, NodeIdx) ->
+ _Pubsub_States = mnesia:index_read(_Table = table('pubsub_state', Suffix),
+ NodeIdx, nodeidx).
+
+
+%-- Create_Node --%
+-spec(create_node/7 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Level :: exmpp_pubsub:level(),
+ Node_Options :: pubsub_options:options_node(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> Subscription :: undefined | exmpp_pubsub:subscription_subscribed()
+).
+
+create_node(Suffix, Pubsub_Host, {U,S,R} = _Entity, NodeId, Level, Node_Options,
+ Pubsub_Features) ->
+ %% Create Pubsub_Node
+ mnesia:write(table('pubsub_node', Suffix),
+ #pubsub_node_dev{
+ id = {Pubsub_Host, NodeId},
+ idx = NodeIdx = pubsub_index_dev:new(Suffix, 'node'),
+ level = Level,
+ creation = {_DateTime = now(), _Creator = {U,S,R}},
+ owners = [{U,S,undefined}],
+ options = Node_Options
+ },
+ write),
+ _Subscription = case
+ lists:member(<<"auto-subscribe">>, Pubsub_Features)
+ andalso
+ get_value(Node_Options, 'subscribe', false)
+ of
+ true ->
+ mnesia:write(table('pubsub_state', Suffix),
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ affiliation = 'owner',
+ subscriptions = [Subscription = {
+ _Subscription_State = 'subscribed',
+ _SubId = exmpp_pubsub:subId(),
+ _Resource = undefined,
+ _Subscription_Options = []
+ }]
+ },
+ write),
+ Subscription;
+ false ->
+ mnesia:write(table('pubsub_state', Suffix),
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ affiliation = 'owner'
+ },
+ write),
+ undefined
+ end.
+
+%-- Purge_Node --%
+%% TODO : purge Pubsub_Purge_Offline
+%% TODO : purge Pubsub_Expiry_Item
+-spec(purge_node/2 ::
+(
+ Suffix :: atom(),
+ Pubsub_Node :: mod_pubsub_dev:pubsub_node())
+ -> Recipients :: undefined | [Entity::xmpp_jid:usr_bare()]
+).
+
+purge_node(Suffix, Pubsub_Node) ->
+ %% Update Pubsub_Node
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{itemids = []}, write),
+ %% Delete Pubsub_Last_Item
+ mnesia:delete(table('pubsub_last_item', Suffix),
+ NodeIdx = Pubsub_Node#pubsub_node_dev.idx, write),
+ %%
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ Table_Pubsub_Item = table('pubsub_item', Suffix),
+ _Recipients = case
+ get_value(Pubsub_Node#pubsub_node_dev.options, 'notify_retract', false)
+ of
+ true ->
+ %% Delete Pubsub_Items
+ lists:foreach(fun
+ (Pubsub_Item) ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write)
+ %
+ end, _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx,
+ nodeidx)),
+ %% Update Pubsub_States
+ _Subscribers = lists:foldl(fun
+ %%
+ (Pubsub_State, Subscribers)
+ when (Pubsub_State#pubsub_state_dev.affiliation == 'owner'
+ orelse
+ Pubsub_State#pubsub_state_dev.affiliation == 'outcast'
+ orelse
+ Pubsub_State#pubsub_state_dev.affiliation == 'publish-only')
+ andalso Pubsub_State#pubsub_state_dev.itemids =/= [] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{itemids = []},
+ write),
+ Subscribers;
+ %%
+ (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State,
+ Subscribers)
+ when Pubsub_State#pubsub_state_dev.affiliation == 'publisher'
+ andalso Pubsub_State#pubsub_state_dev.itemids =/= [] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{itemids = []}, write),
+ case Pubsub_State#pubsub_state_dev.subscriptions of
+ [] = _Subscriptions ->
+ Subscribers;
+ _Subscriptions ->
+ [_Subscriber = Entity | Subscribers]
+ end;
+ %%
+ (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State,
+ Subscribers)
+ when Pubsub_State#pubsub_state_dev.affiliation == 'publisher'
+ andalso Pubsub_State#pubsub_state_dev.subscriptions =/= [] ->
+ [_Subscriber = Entity | Subscribers];
+ %%
+ (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State,
+ Subscribers)
+ when Pubsub_State#pubsub_state_dev.affiliation == 'member'
+ andalso Pubsub_State#pubsub_state_dev.itemids =/= [] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{itemids = []}, write),
+ case
+ (Pubsub_State#pubsub_state_dev.subscriptions == []
+ orelse
+ has_subscriptions(
+ Pubsub_State#pubsub_state_dev.subscriptions))
+ of
+ true -> [_Subscriber = Entity | Subscribers];
+ false -> Subscribers
+ end;
+ %%
+ (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State,
+ Subscribers)
+ when Pubsub_State#pubsub_state_dev.affiliation == 'member'->
+ case
+ (Pubsub_State#pubsub_state_dev.subscriptions == []
+ orelse
+ has_subscriptions(
+ Pubsub_State#pubsub_state_dev.subscriptions))
+ of
+ true -> [_Subscriber = Entity | Subscribers];
+ false -> Subscribers
+ end;
+ %%
+ (_Pubsub_State, Subscribers) ->
+ Subscribers
+ %
+ end,
+ [],
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State,
+ NodeIdx, nodeidx));
+ false ->
+ %% Update Pubsub_States
+ lists:foreach(fun
+ (_Items_Owner = Entity) ->
+ case
+ mnesia:read(Table_Pubsub_State, {Entity, NodeIdx},
+ write)
+ of
+ [Pubsub_State]
+ when Pubsub_State#pubsub_state_dev.itemids =/= [] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{itemids = []},
+ write);
+ _Pubsub_State ->
+ ok
+ end
+ end,
+ _Items_Owners = lists:foldl(fun
+ %% Delete Pubsub_Items
+ (Pubsub_Item, Items_Owners) ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item,
+ write),
+ lists:usort(lists:append(Items_Owners,
+ Pubsub_Item#pubsub_item_dev.owners))
+ end, [],
+ _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx,
+ nodeidx))
+ ),
+ _Subscribers = undefined
+ end.
+
+%-- Delete_Node --%
+-spec(delete_node/2 ::
+(
+ Suffix :: atom(),
+ Pubsub_Node :: mod_pubsub_dev:pubsub_node())
+ -> Recipients :: undefined | [Entity::xmpp_jid:usr_bare()]
+).
+
+delete_node(Suffix, Pubsub_Node) ->
+ %% Delete Pubsub_Node
+ mnesia:delete_object(table('pubsub_node', Suffix), Pubsub_Node, write),
+ pubsub_index_dev:free(Suffix, 'node',
+ NodeIdx = Pubsub_Node#pubsub_node_dev.idx),
+ %% Delete Pubsub_Last_Item
+ mnesia:delete(table('pubsub_last_item', Suffix), NodeIdx, write),
+ %% Delete Pubsub_Items
+ Table_Pubsub_Item = table('pubsub_item', Suffix),
+ lists:foreach(fun
+ (Pubsub_Item) ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write)
+ %
+ end,
+ _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx,
+ nodeidx)),
+ %% Delete Pubsub_States
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ _Recipients = case
+ get_value(Pubsub_Node#pubsub_node_dev.options, 'notify_delete', false)
+ of
+ true ->
+ _Subscribers = lists:foldl(fun
+ (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State,
+ Subscribers) ->
+ mnesia:delete_object(Table_Pubsub_State, Pubsub_State,
+ write),
+ case
+ {Pubsub_State#pubsub_state_dev.affiliation,
+ Pubsub_State#pubsub_state_dev.subscriptions}
+ of
+ %%
+ {'publisher' = _Affiliation, Subscriptions}
+ when Subscriptions =/= [] ->
+ [_Subscriber = Entity | Subscribers];
+ %%
+ {'member' = _Affiliation, _Subscriptions} ->
+ case
+ (_Subscriptions == []
+ orelse
+ has_subscriptions(_Subscriptions))
+ of
+ true ->
+ [_Subscriber = Entity | Subscribers];
+ false ->
+ Subscribers
+ end;
+ %%
+ {_Affiliation, _Subscriptions} ->
+ Subscribers
+ end
+ %
+ end, [],
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx,
+ nodeidx));
+ false ->
+ lists:foreach(fun
+ (Pubsub_State) ->
+ mnesia:delete_object(Table_Pubsub_State, Pubsub_State,
+ write)
+ end,
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx,
+ nodeidx)),
+ _Subscribers = undefined
+ end.
+
+%-- Get_Entity_Subscriptions --%
+-spec(get_entity_subscriptions/4 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ NodeId :: undefined | exmpp_pubsub:nodeId())
+ -> Entity_Subscriptions :: [{
+ NodeId :: exmpp_pubsub:nodeId(),
+ Subscriptions :: exmpp_pubsub:subscriptions()
+ }]
+).
+
+get_entity_subscriptions(Suffix, Pubsub_Host, {U,S,_R} = _Entity,
+ undefined = _NodeId) ->
+ Table_Pubsub_Node = table('pubsub_node', Suffix),
+ _Entity_Subscriptions = lists:foldl(fun
+ ([NodeIdx, Subscriptions], Entity_Subscriptions) ->
+ case mnesia:index_read(Table_Pubsub_Node, NodeIdx, idx) of
+ %% IMPORTANT : Pubsub_Host must be used in the matching,
+ %% else it'd return all NodeIds subscriptions (in case of VHosts)
+ %% and not only the ones from to the current Pubsub_Host
+ [#pubsub_node_dev{id = {Pubsub_Host, NodeId}}] ->
+ [{NodeId, Subscriptions} | Entity_Subscriptions];
+ _Pubsub_Node ->
+ Entity_Subscriptions
+ end
+ end, [],
+ mnesia:select(table('pubsub_state', Suffix),
+ [{#pubsub_state_dev{
+ id = {{U,S,undefined}, '$1'},
+ subscriptions = '$2',
+ _ = '_'},
+ [{'=/=', '$2', []}],
+ ['$$']}]));
+%%
+get_entity_subscriptions(Suffix, Pubsub_Host, {U,S,_R} = _Entity, NodeId) ->
+ _Entity_Subscriptions = case
+ mnesia:read(table('pubsub_node', Suffix), {Pubsub_Host, NodeId}, read)
+ of
+ [] ->
+ [];
+ [#pubsub_node_dev{idx = NodeIdx}] ->
+ case
+ mnesia:read(table('pubsub_state', Suffix),
+ {{U,S,undefined}, NodeIdx}, read)
+ of
+ [#pubsub_state_dev{subscriptions = Subscriptions}]
+ when Subscriptions =/= [] ->
+ [{NodeId, Subscriptions}];
+ _Pubsub_State ->
+ []
+ end
+ end.
+
+%-- Get_Entity_Affiliations --%
+-spec(get_entity_affiliations/4 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ NodeId :: undefined | exmpp_pubsub:nodeId())
+ -> Entity_Affiliations :: [Entity_Affiliation :: {
+ NodeId :: exmpp_pubsub:nodeId(),
+ Affiliation :: exmpp_pubsub:affiliation()
+ }]
+).
+
+get_entity_affiliations(Suffix, Pubsub_Host, {U,S,_R} = _Entity,
+ undefined = _NodeId) ->
+ Table_Pubsub_Node = table('pubsub_node', Suffix),
+ _Entity_Affiliations = lists:foldl(fun
+ ([NodeIdx, Affiliation], Entity_Affiliations) ->
+ case mnesia:index_read(Table_Pubsub_Node, NodeIdx, idx) of
+ %% IMPORTANT : Pubsub_Host must be used in the matching,
+ %% else it'd return all NodeIds affiliations (in case of VHosts)
+ %% and not only the ones from to the current Pubsub_Host
+ [#pubsub_node_dev{id = {Pubsub_Host, NodeId}}] ->
+ [{NodeId, Affiliation} | Entity_Affiliations];
+ _Pubsub_Node ->
+ Entity_Affiliations
+ end
+ end, [],
+ mnesia:select(table('pubsub_state', Suffix),
+ [{#pubsub_state_dev{
+ id = {{U,S,undefined}, '$1'},
+ affiliation = '$2',
+ _ = '_'},
+ [],
+ ['$$']}],
+ read)
+ );
+%%
+get_entity_affiliations(Suffix, Pubsub_Host, {U,S,_R} = _Entity, NodeId) ->
+ _Entity_Affiliations = case
+ mnesia:read(table('pubsub_node', Suffix), {Pubsub_Host, NodeId}, read)
+ of
+ [] ->
+ [];
+ [#pubsub_node_dev{idx = NodeIdx}] ->
+ case
+ mnesia:read(table('pubsub_state', Suffix),
+ {{U,S,undefined}, NodeIdx})
+ of
+ [#pubsub_state_dev{affiliation = Affiliation}] ->
+ [{NodeId, Affiliation}];
+ _Pubsub_State ->
+ []
+ end
+ end.
+
+%-- Publish_Item --%
+-spec(publish_item1/9 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Level :: exmpp_pubsub:level(),
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Payload :: [Xmlel::#xmlel{}] | exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()},
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> {ItemId :: exmpp_pubsub:itemId(),
+ Pubsub_State@Owner :: mod_pubsub_dev:pubsub_state_owner()}
+).
+
+publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, ItemId,
+ [Payload], Options, Pubsub_Features) ->
+ publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, ItemId, Payload,
+ Options, Pubsub_Features);
+
+publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, undefined = _ItemId,
+ Payload, Options, Pubsub_Features) ->
+ publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level,
+ exmpp_pubsub:itemId(), Payload, Options, Pubsub_Features);
+%%
+publish_item1(Suffix, Pubsub_Host, {U,S,R} = _Entity, NodeId, Level, ItemId,
+ Payload, {Node_Options, Item_Options} = Options, Pubsub_Features) ->
+ NodeIdx = pubsub_index_dev:new(Suffix, 'node'),
+ DateTime = now(),
+ Write_Item = get_value(Options, 'persist_items', false) andalso
+ (get_value(Node_Options, 'max_items', 0) > 0),
+ %% Create Pubsub_Node
+ mnesia:write(table('pubsub_node', Suffix),
+ #pubsub_node_dev{
+ id = {Pubsub_Host, NodeId},
+ idx = NodeIdx,
+ creation = {DateTime, _Creator = {U,S,R}},
+ level = Level,
+ owners = [{U,S,undefined}],
+ itemids = case Write_Item of
+ true -> [ItemId];
+ false -> []
+ end,
+ options = Node_Options
+ },
+ write),
+ %% Create Pubsub_Item
+ case Write_Item of
+ false ->
+ ok;
+ true ->
+ mnesia:write(table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {ItemId, NodeIdx},
+ nodeidx = NodeIdx,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, _Creator = {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write),
+ %%TODO : Register pubsub#item_expire
+ case get_value(Options, 'item_expire', undefined) of
+ undefined ->
+ ko;
+ Delay ->
+ ok
+ end
+ end,
+ %% Create Pubsub_Last_Item
+ case get_value(Options, 'send_last_published_item', 'never') of
+ 'never' ->
+ ok;
+ _Send_Last_Published_Item ->
+ mnesia:write(table('pubsub_last_item', Suffix),
+ #pubsub_last_item_dev{
+ nodeidx = NodeIdx,
+ id = ItemId,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, _Creator = {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write)
+ end,
+ %% Create Pubsub_State
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State = #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ itemids = case Write_Item of
+ true -> [ItemId];
+ false -> []
+ end,
+ affiliation = 'owner',
+ subscriptions = case
+ lists:member(<<"auto-subscribe">>, Pubsub_Features)
+ andalso
+ get_value(Node_Options, 'subscribe', false)
+ of
+ true ->
+ [{_Subscription_State = 'subscribed',
+ _SubId = exmpp_pubsub:subId(),
+ _Resource = undefined,
+ _Subscription_Options = []}];
+ false ->
+ []
+ end
+ },
+ write),
+ {ItemId, Pubsub_State}.
+
+%%
+-spec(publish_item2/9 ::
+(
+ Suffix :: atom(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_entity(),
+ Pubsub_State :: undefined | mod_pubsub_dev:pubsub_state(),
+ Pubsub_Node :: mod_pubsub_dev:pubsub_node(),
+ _ :: undefined | exmpp_pubsub:itemId() | mod_pubsub_dev:pubsub_item(),
+ Payload :: [Xmlel::#xmlel{}] | exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()},
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> {ItemId :: exmpp_pubsub:itemId(),
+ Retracted_Items :: [Retracted_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_node()
+ }],
+ Pubsub_States :: [Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+).
+
+
+publish_item2(Suffix, Pubsub_Host, Entity, Pubsub_State, Pubsub_Node, Pubsub_Item,
+ [Payload], Item_Options, Pubsub_Features) ->
+ publish_item2(Suffix, Pubsub_Host, Entity, Pubsub_State, Pubsub_Node,
+ Pubsub_Item, Payload, Item_Options, Pubsub_Features);
+%%
+publish_item2(Suffix, Pubsub_Host, {U,S,R} = _Entity, Pubsub_State, Pubsub_Node,
+ #pubsub_item_dev{id = {ItemId, NodeIdx}} = Pubsub_Item, Payload, Item_Options,
+ Pubsub_Features) ->
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ DateTime = now(),
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ _Retracted_Items = case
+ {get_value(Node_Options, 'persist_items', false),
+ get_value(Node_Options, 'max_items', 0)}
+ of
+ %%
+ {Persist_Items, Max_Items}
+ when Persist_Items == false
+ orelse Max_Items == 0 ->
+ case Pubsub_State of
+ undefined ->
+ mnesia:write(table('pubsub_state', Suffix),
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx
+ },
+ write),
+ [];
+ _Pubsub_State ->
+ []
+ end;
+ %%
+ {true, undefined} ->
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = lists:reverse([ItemId
+ | lists:reverse(lists:delete(ItemId,
+ Pubsub_Node#pubsub_node_dev.itemids))])
+ },
+ write),
+ %%
+ mnesia:write(Table_Pubsub_State,
+ case Pubsub_State of
+ undefined ->
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ itemids = [ItemId]
+ };
+ _ ->
+ Pubsub_State#pubsub_state_dev{
+ itemids = [ItemId | lists:delete(ItemId,
+ Pubsub_State#pubsub_state_dev.itemids)]
+ }
+ end,
+ write),
+ mnesia:write(table('pubsub_item', Suffix),
+ Pubsub_Item#pubsub_item_dev{
+ modification = {DateTime, {U,S,R}},
+ payload = Payload,
+ options = Item_Options,
+ owners = [{U,S,undefined} |
+ lists:delete({U,S,undefined},
+ Pubsub_Item#pubsub_item_dev.owners)]
+ },
+ write),
+ [];
+ {true, Max_Items} ->
+ {New_ItemIds, Old_ItemIds} = max_items(Max_Items,
+ lists:reverse([ItemId
+ | lists:reverse(lists:delete(ItemId,
+ Pubsub_Node#pubsub_node_dev.itemids))])),
+ {Retracted_Items, Publisher_Retracted_ItemIds} =
+ delete_old_items(Suffix,
+ _Publisher = {U,S,undefined},
+ NodeIdx,
+ _Notify_Retract = get_value(Node_Options,
+ 'notify_retract', false),
+ Old_ItemIds, {[], {[], []}}),
+ %%
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = New_ItemIds
+ },
+ write),
+ mnesia:write(table('pubsub_item', Suffix),
+ Pubsub_Item#pubsub_item_dev{
+ modification = {DateTime, {U,S,R}},
+ payload = Payload,
+ options = Item_Options,
+ owners = [{U,S,undefined} |
+ lists:delete({U,S,undefined},
+ Pubsub_Item#pubsub_item_dev.owners)]
+ },
+ write),
+ mnesia:write(Table_Pubsub_State,
+ case Pubsub_State of
+ undefined ->
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ itemids = [ItemId]
+ };
+ _ ->
+ Pubsub_State#pubsub_state_dev{
+ itemids = [ItemId
+ | lists:foldl(fun
+ (Publisher_Retracted_ItemId,
+ Publisher_ItemIds) ->
+ lists:delete(
+ Publisher_Retracted_ItemId,
+ Publisher_ItemIds)
+ end,
+ lists:delete(ItemId,
+ Pubsub_State#pubsub_state_dev.itemids),
+ Publisher_Retracted_ItemIds)]
+ }
+ end,
+ write),
+ Retracted_Items
+
+ end,
+ case get_value(Node_Options, 'send_last_published_item', 'never') of
+ 'never' ->
+ ok;
+ _Send_Last_Published_Item ->
+ mnesia:write(table('pubsub_last_item', Suffix),
+ #pubsub_last_item_dev{
+ nodeidx = NodeIdx,
+ id = ItemId,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, _Creator = {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write)
+ end,
+ {ItemId, _Retracted_Items,
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)};
+%%
+publish_item2(Suffix, Pubsub_Host, {U,S,R} = Entity, Pubsub_State, Pubsub_Node,
+ _ItemId, Payload, Item_Options, Pubsub_Features) ->
+ NodeIdx = Pubsub_Node#pubsub_node_dev.idx,
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ ItemId = case _ItemId of
+ undefined -> exmpp_pubsub:itemId();
+ _ -> _ItemId
+ end,
+ DateTime = now(),
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ _Retracted_Items = case
+ {get_value(Node_Options, 'persist_items', false),
+ get_value(Node_Options, 'max_items', 0)}
+ of
+ %%
+ {Persist_Items, Max_Items}
+ when Persist_Items == false
+ orelse Max_Items == 0 ->
+ case Pubsub_State of
+ undefined ->
+ mnesia:write(Table_Pubsub_State,
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx
+ },
+ write),
+ [];
+ _Pubsub_State ->
+ []
+ end;
+ %%
+ {true, undefined} ->
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = lists:reverse([ItemId
+ | lists:reverse(ItemId,
+ Pubsub_Node#pubsub_node_dev.itemids)])
+ },
+ write),
+ %%
+ mnesia:write(Table_Pubsub_State,
+ case Pubsub_State of
+ undefined ->
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ itemids = [ItemId]
+ };
+ _ ->
+ Pubsub_State#pubsub_state_dev{
+ itemids = [ItemId |
+ Pubsub_State#pubsub_state_dev.itemids]
+ }
+ end,
+ write),
+ mnesia:write(table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {ItemId, NodeIdx},
+ nodeidx = NodeIdx,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write),
+ [];
+ {true, Max_Items} ->
+ {New_ItemIds, Old_ItemIds} = max_items(Max_Items,
+ lists:reverse([ItemId
+ | lists:reverse(Pubsub_Node#pubsub_node_dev.itemids)])),
+ {Retracted_Items, Publisher_Retracted_ItemIds} =
+ delete_old_items(Suffix,
+ _Publisher = {U,S,undefined},
+ NodeIdx,
+ _Notify_Retract = get_value(Node_Options,
+ 'notify_retract', false),
+ Old_ItemIds, {[], {[], []}}),
+ %%
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = New_ItemIds
+ },
+ write),
+ mnesia:write(table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {ItemId, NodeIdx},
+ nodeidx = NodeIdx,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write),
+ %%
+ mnesia:write(Table_Pubsub_State,
+ case Pubsub_State of
+ undefined ->
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, NodeIdx},
+ nodeidx = NodeIdx,
+ itemids = [ItemId]
+ };
+ _ ->
+ Pubsub_State#pubsub_state_dev{
+ itemids = [ItemId
+ | lists:foldl(fun
+ (Publisher_Retracted_ItemId,
+ Publisher_ItemIds) ->
+ lists:delete(
+ Publisher_Retracted_ItemId,
+ Publisher_ItemIds)
+ end,
+ Pubsub_State#pubsub_state_dev.itemids,
+ Publisher_Retracted_ItemIds)]
+ }
+ end,
+ write),
+ Retracted_Items
+
+ end,
+ case get_value(Node_Options, 'send_last_published_item', 'never') of
+ 'never' ->
+ ok;
+ _Send_Last_Published_Item ->
+ mnesia:write(table('pubsub_last_item', Suffix),
+ #pubsub_last_item_dev{
+ nodeidx = NodeIdx,
+ id = ItemId,
+ owners = [{U,S,undefined}],
+ creation = {DateTime, _Creator = {U,S,R}},
+ payload = Payload,
+ options = Item_Options
+ },
+ write)
+ end,
+ {ItemId, _Retracted_Items,
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)}.
+
+%%
+-spec(max_items/2 ::
+(
+ Max_Items :: undefined | non_neg_integer(),
+ New_ItemIds :: [ItemId::exmpp_pubsub:itemId()])
+ -> ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()],
+ Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]}
+).
+
+max_items(undefined = _Max_Items, New_ItemIds) ->
+ _ItemIds = {New_ItemIds, _Old_ItemIds = []};
+max_items(0 = _Max_Items, New_ItemIds) ->
+ _ItemIds = {_New_ItemIds = [], _Old_ItemIds = New_ItemIds};
+max_items(Max_Items, New_ItemIds) ->
+ _ItemIds = case Max_Items >= (Length_Items = length(New_ItemIds)) of
+ true ->
+ {New_ItemIds, _Old_ItemIds = []};
+ false ->
+ diff_items((Length_Items - Max_Items), _Count = 0, {New_ItemIds, []})
+ end.
+
+%%
+-spec(diff_items/3 ::
+(
+ Diff_Items :: pos_integer(),
+ Count :: non_neg_integer(),
+ ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()],
+ Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]})
+ -> ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()],
+ Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]}
+).
+
+diff_items(Diff_Items, _Count = Diff_Items, ItemIds) ->
+ ItemIds;
+diff_items(Diff_Items, Count, {[ItemId | New_ItemIds], Old_ItemIds}) ->
+ diff_items(Diff_Items, Count + 1, {New_ItemIds, [ItemId | Old_ItemIds]}).
+
+
+%%
+-spec(delete_old_items/6 ::
+(
+ Suffix :: atom(),
+ Publisher :: xmpp_jid:usr_bare(),
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Node_Notify_Retract :: boolean(),
+ ItemIds :: [ItemId::exmpp_pubsub:itemId()],
+ %%
+ {Retracted_Items :: [Retracted_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item()
+ }],
+ %
+ Publishers_Retracted_ItemIds :: {
+ %%
+ Entities_Retracted_ItemIds :: [Entity_Retracted_ItemIds::{
+ Entity :: xmpp_jid:usr_bare(),
+ ItemIds :: [ItemId::exmpp_pubsub:itemId()]
+ }],
+ %%
+ Publisher_Retracted_ItemIds :: [ItemId::exmpp_pubsub:itemId()]
+ }
+ })
+ -> {Retracted_Items :: [Retracted_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item()
+ }],
+ Publisher_Retracted_ItemIds :: [ItemId::exmpp_pubsub:itemId()]}
+).
+
+delete_old_items(Suffix, _Publisher, NodeIdx, _Notify_Retract, [] = _ItemIds,
+ {Retracted_Items, {Entities_ItemIds, Publisher_ItemIds}}) ->
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ lists:foreach(fun
+ ({Entity, Entity_ItemIds}) ->
+ case mnesia:read(Table_Pubsub_State, {Entity, NodeIdx}, write) of
+ [Pubsub_State] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ itemids = lists:foldl(fun
+ (Retracted_ItemId, ItemIds) ->
+ lists:delete(Retracted_ItemId, ItemIds)
+ end,
+ Pubsub_State#pubsub_state_dev.itemids,
+ Entity_ItemIds)
+ },
+ write);
+ [] -> %% shouldn't happen
+ ok
+ end
+ %
+ end, Entities_ItemIds),
+ {Retracted_Items, Publisher_ItemIds};
+%%
+delete_old_items(Suffix, Publisher, NodeIdx, Notify_Retract, [ItemId | ItemIds],
+ {Retracted_Items, Publishers_ItemIds}) ->
+ Table_Pubsub_Item = table('pubsub_item', Suffix),
+ delete_old_items(Suffix, Publisher, NodeIdx, Notify_Retract, ItemIds,
+ case mnesia:read(Table_Pubsub_Item, {ItemId, NodeIdx}, write) of
+ [Pubsub_Item] ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write),
+ %% @TODO: Unregister 'pubsub#item_expire'
+ {_Retracted_Items = case
+ get_value(Pubsub_Item#pubsub_item_dev.options,
+ 'notify_retract', Notify_Retract)
+ of
+ true ->
+ [{ItemId, Pubsub_Item#pubsub_item_dev.options}
+ | Retracted_Items];
+ false ->
+ Retracted_Items
+ end,
+ %%
+ _Publishers_ItemIds = lists:foldl(fun
+ %%
+ (Item_Owner, {Entities_ItemIds, Publisher_ItemIds})
+ when Item_Owner == Publisher ->
+ {Entities_ItemIds,
+ [ItemId | Publisher_ItemIds]};
+ %%
+ (_Item_Owner = Entity,
+ {Entities_ItemIds, Publisher_ItemIds}) ->
+ {_Entities_ItemIds = case
+ lists:keyfind(Entity, 1, Entities_ItemIds)
+ of
+ {_Entity, Entity_ItemIds} ->
+ lists:keyreplace(Entity, 1, Entities_ItemIds,
+ {Entity, [ItemId | Entity_ItemIds]});
+ false ->
+ [{Entity, [ItemId]} | Entities_ItemIds]
+ end,
+ Publisher_ItemIds}
+ end, Publishers_ItemIds, Pubsub_Item#pubsub_item_dev.owners)};
+ [] ->
+ {Retracted_Items, Publishers_ItemIds}
+ end).
+
+
+%-- Retract_Item --%
+-spec(retract_item/5 ::
+(
+ Suffix :: atom(),
+ Pubsub_Node :: mod_pubsub_dev:pubsub_node(),
+ Pubsub_State :: mod_pubsub_dev:pubsub_state_owner()
+ | mod_pubsub_dev:pubsub_state_member()
+ | mod_pubsub_dev:pubsub_state_publish_only()
+ | mod_pubsub_dev:pubsub_state_publisher()
+ | mod_pubsub_dev:pubsub_state_none(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Adhoc_Notify_Retract :: undefined | boolean())
+ -> {ok, undefined}
+ %
+ | {ok,
+ Item_Options :: [] | pubsub_options:options_item(),
+ Pubsub_States :: mod_pubsub_dev:pubsub_states()}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+).
+
+retract_item(Suffix, Pubsub_Node,
+ #pubsub_state_dev{id = {Entity, NodeIdx}} = Pubsub_State, ItemId,
+ Adhoc_Notify_Retract) ->
+ Table_Pubsub_Item = table('pubsub_item', Suffix),
+ case mnesia:read(Table_Pubsub_Item, {ItemId, NodeIdx}, write) of
+ [Pubsub_Item]
+ when Pubsub_State#pubsub_state_dev.affiliation == 'owner'
+ orelse Pubsub_State#pubsub_state_dev.affiliation == 'publisher' ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write),
+ Table_Pubsub_Last_Item = table('pubsub_last_item', Suffix),
+ case mnesia:read(Table_Pubsub_Last_Item, NodeIdx, read) of
+ [Pubsub_Last_Item]
+ when Pubsub_Last_Item#pubsub_last_item_dev.id == ItemId ->
+ mnesia:delete_object(Table_Pubsub_Last_Item,
+ Pubsub_Last_Item, write);
+ _ ->
+ ok
+ end,
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ case
+ retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity,
+ _Item_Owners = Pubsub_Item#pubsub_item_dev.owners, false)
+ of
+ true ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ itemids = lists:delete(ItemId,
+ Pubsub_State#pubsub_state_dev.itemids)
+ },
+ write);
+ false ->
+ ok
+ end,
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = lists:delete(ItemId,
+ Pubsub_Node#pubsub_node_dev.itemids)
+ },
+ write),
+ case
+ Adhoc_Notify_Retract == true
+ orelse
+ get_value(
+ {Pubsub_Node#pubsub_node_dev.options,
+ Pubsub_Item#pubsub_item_dev.options},
+ 'notify_retract', false)
+ of
+ true ->
+ {ok,
+ _Item_Options = Pubsub_Item#pubsub_item_dev.options,
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)};
+ false ->
+ {ok, undefined}
+ end;
+ %%
+ [#pubsub_item_dev{creation = {_DateTime, _Creator = Entity}} = Pubsub_Item] ->
+ mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write),
+ Table_Pubsub_Last_Item = table('pubsub_last_item', Suffix),
+ case mnesia:read(Table_Pubsub_Last_Item, NodeIdx, read) of
+ [Pubsub_Last_Item]
+ when Pubsub_Last_Item#pubsub_last_item_dev.id == ItemId ->
+ mnesia:delete_object(Table_Pubsub_Last_Item,
+ Pubsub_Last_Item, write);
+ _ ->
+ ok
+ end,
+ Table_Pubsub_State = table('pubsub_state', Suffix),
+ case
+ retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity,
+ _Item_Owners = Pubsub_Item#pubsub_item_dev.owners, false)
+ of
+ true ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ itemids = lists:delete(ItemId,
+ Pubsub_State#pubsub_state_dev.itemids)
+ },
+ write);
+ false ->
+ ok
+ end,
+ mnesia:write(table('pubsub_node', Suffix),
+ Pubsub_Node#pubsub_node_dev{
+ itemids = lists:delete(ItemId,
+ Pubsub_Node#pubsub_node_dev.itemids)
+ },
+ write),
+ case
+ Adhoc_Notify_Retract == true
+ orelse
+ get_value(
+ {Pubsub_Node#pubsub_node_dev.options,
+ Pubsub_Item#pubsub_item_dev.options},
+ 'notify_retract', false)
+ of
+ true ->
+ {ok,
+ _Item_Options = Pubsub_Item#pubsub_item_dev.options,
+ _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)};
+ false ->
+ {ok, undefined}
+ end;
+ [_Pubsub_Item] ->
+ {error, 'forbidden'};
+ %%
+ [] ->
+ {error, 'item-not-found'}
+ end.
+
+
+%%
+-spec(retract_item/6 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Table_Pubsub_State :: atom(),
+ Entity :: xmpp_jid:usr_bare() | undefined,
+ Item_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Retract_ItemId :: boolean())
+ -> Retract_ItemId :: boolean()
+).
+
+retract_item(_NodeIdx, _ItemId, _Table_Pubsub_State, _Entity, [] = _Item_Owners,
+ Retract_ItemId) ->
+ Retract_ItemId;
+%%
+retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity,
+ [_Item_Owner = Entity | Item_Owners], _Retract_ItemId) ->
+ retract_item(NodeIdx, ItemId, Table_Pubsub_State, undefined, Item_Owners,
+ true);
+%%
+retract_item(NodeIdx, ItemId, Table_Pubsub_State, _Entity,
+ [_Item_Owner = Entity | Item_Owners], Retract_ItemId) ->
+ case mnesia:read(Table_Pubsub_State, {Entity, NodeIdx}, write) of
+ [Pubsub_State] ->
+ mnesia:write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ itemids = lists:delete(ItemId,
+ Pubsub_State#pubsub_state_dev.itemids)
+ },
+ write);
+ [] ->
+ ok
+ end,
+ retract_item(NodeIdx, ItemId, Table_Pubsub_State, _Entity, Item_Owners,
+ Retract_ItemId).
+
+%-- Subscribe_Node --%
+-spec(subscribe_node/8 ::
+(
+ Suffix :: atom(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Pubsub_State :: mod_pubsub_dev:pubsub_state(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: [] | pubsub_options:options_subscription(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> {result,
+ Subscription_Subscribed :: exmpp_pubsub:subscription_subscribed(),
+ Pubsub_Last_Item :: undefined | mod_pubsub_dev:pubsub_last_item()}
+ %
+ | {result,
+ Subscription_Pending :: exmpp_pubsub:subscription_pending(),
+ Pubsub_Last_Item :: undefined}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+subscribe_node(Suffix, Host, Node_Options, Node_Owners,
+ #pubsub_state_dev{id = {{_U,S,_R} = Entity, _NodeIdx}} = Pubsub_State, Resource,
+ Subscription_Options, Pubsub_Features) ->
+ Affiliation = Pubsub_State#pubsub_state_dev.affiliation,
+ Subscriptions = Pubsub_State#pubsub_state_dev.subscriptions,
+ case
+ options_on_subscribe_node(Host, Node_Options, Node_Owners, Entity,
+ Resource, Affiliation, Subscriptions, Subscription_Options,
+ Pubsub_Features)
+ of
+ %%
+ {ok, 'new',
+ {'pending', SubId, _Resource, _Subscription_Options} = Subscription} ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ affiliation = case Affiliation of
+ 'none' -> 'member';
+ Affiliation -> Affiliation
+ end,
+ subscriptions = [Subscription | Subscriptions]
+ },
+ write),
+ Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending',
+ Suffix),
+ case
+ mnesia:read(Table_Pubsub_Subscription_Pending,
+ Pubsub_State#pubsub_state_dev.id, write)
+ of
+ [] ->
+ mnesia:write(Table_Pubsub_Subscription_Pending,
+ #pubsub_subscription_pending{
+ id = Pubsub_State#pubsub_state_dev.id,
+ nodeidx = Pubsub_State#pubsub_state_dev.nodeidx,
+ subids = [SubId]
+ },
+ write);
+ [Pubsub_Subscription_Pending] ->
+ mnesia:write(Table_Pubsub_Subscription_Pending,
+ Pubsub_Subscription_Pending#pubsub_subscription_pending{
+ subids = [SubId
+ | Pubsub_Subscription_Pending
+ #pubsub_subscription_pending.subids]
+ },
+ write)
+ end,
+ {result, Subscription, _Pubsub_Last_Item = undefined};
+ %%
+ {ok, Type, Subscription} ->
+ case Type of
+ 'new' ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ affiliation = case Affiliation of
+ 'none' -> 'member';
+ Affiliation -> Affiliation
+ end,
+ subscriptions = [Subscription | Subscriptions]
+ },
+ write);
+ 'old' ->
+ ok
+ end,
+ {result,
+ Subscription,
+ _Pubsub_Last_Item = case
+ (get_value(Subscription_Options, 'deliver', true) == true)
+ andalso
+ (get_value(Subscription_Options, 'show-values') =/= [])
+ andalso
+ (get_value(Node_Options, 'send_last_published_item', 'never')
+ =/= 'never')
+ of
+ true ->
+ case
+ mnesia:read(table('pubsub_last_item', Suffix),
+ Pubsub_State#pubsub_state_dev.nodeidx,
+ read)
+ of
+ [] -> undefined;
+ [Pubsub_Last_Item] -> Pubsub_Last_Item
+ end;
+ false ->
+ undefined
+ end};
+ Error ->
+ Error
+ end.
+
+%%
+-spec(options_on_subscribe_node/9 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Entity :: xmpp_jid:usr_bare(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Subscription_Options :: [] | pubsub_options:options_subscription(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> {ok,
+ Type :: 'new' | 'old',
+ Subscription :: exmpp_pubsub:subscription_subscribed()}
+ %
+ | {ok,
+ Type :: 'new',
+ Subscription :: exmpp_pubsub:subscription_pending()}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+options_on_subscribe_node(Host, Node_Options, Node_Owners, Entity,
+ Resource, Affiliation, Subscriptions, Subscription_Options, Pubsub_Features) ->
+ options_on_subscribe_node(get_option(Node_Options, 'subscribe'), Host,
+ Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options,
+ _Multi_Subscribe = lists:member(<<"multi-subscribe">>, Pubsub_Features)).
+
+%%
+-spec(options_on_subscribe_node/10 ::
+(
+ Option :: pubsub_options:option_node_access_model()
+ | pubsub_options:option_node_subscribe(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Entity :: xmpp_jid:usr_bare(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Subscription_Options :: [] | pubsub_options:options_subscription(),
+ Multi_Subscribe :: boolean())
+ -> {ok,
+ Type :: 'new' | 'old',
+ Subscription :: exmpp_pubsub:subscription_subscribed()}
+ %
+ | {ok,
+ Type :: 'new',
+ Subscription :: exmpp_pubsub:subscription_pending()}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+options_on_subscribe_node({'subscribe', Subscribe}, _Host, _Node_Options,
+ _Node_Owners, _Entity, _Resource, Affiliation, _Subscriptions,
+ _Subscription_Options, _Multi_Subscribe)
+ when Subscribe == false
+ orelse Affiliation == 'outcast'
+ orelse Affiliation == 'publish-only' ->
+ {error, 'forbidden'};
+%%
+options_on_subscribe_node({'subscribe', true}, Host, Node_Options,
+ Node_Owners, Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, Multi_Subscribe) ->
+ options_on_subscribe_node(get_option(Node_Options, 'access_model'), Host,
+ Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, Multi_Subscribe);
+
+options_on_subscribe_node({'access_model', open}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, _Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe) ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', open}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, _Affiliation, _Subscriptions,
+ Subscription_Options, true = _Multi_Subscribe) ->
+ {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)};
+%%
+options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions,
+ Subscription_Options, true = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)};
+%%
+options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', presence}, Host, _Node_Options,
+ Node_Owners, Entity, Resource, _Affiliation, _Subscriptions,
+ Subscription_Options, _Multi_Subscribe) ->
+ case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
+ false ->
+ {error, 'not-authorized', 'presence-subscription-required'};
+ _Node_Owner ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', roster}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions,
+ Subscription_Options, true = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)};
+%%
+options_on_subscribe_node({'access_model', roster}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', roster}, _Host, Node_Options,
+ _Node_Owners, Entity, Resource, _Affiliation, _Subscriptions,
+ Subscription_Options, _Multi_Subscribe) ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ get_value(Node_Options, 'roster_groups_allowed', []))
+ of
+ false ->
+ {error, 'not-authorized', 'not-in-roster-group'};
+ {_Node_Owner, _Roster_Group} ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions,
+ Subscription_Options, true = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)};
+%%
+options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, _Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe) ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, _Subscription} ->
+ {error, 'not-authorized', 'pending-subscription'};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_pending(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions,
+ Subscription_Options, true = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)};
+%%
+options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options,
+ _Node_Owners, _Entity, Resource, Affiliation, Subscriptions,
+ Subscription_Options, false = _Multi_Subscribe)
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publisher'
+ orelse Affiliation == 'member' ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {_Resource, Subscription} ->
+ {ok, 'old', Subscription};
+ false ->
+ {ok,
+ 'new',
+ exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}
+ end;
+%%
+options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options,
+ _Node_Owners, _Entity, _Resource, _Affiliation, _Subscriptions,
+ _Subscription_Options, _Multi_Subscribe) ->
+ {error, 'not-allowed', 'closed-node'}.
+
+
+%-- Unsubscribe_Node --%
+-spec(unsubscribe_node/6 ::
+(
+ Suffix :: atom(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Pubsub_State :: mod_pubsub_dev:pubsub_state_member()
+ | mod_pubsub_dev:pubsub_state_outcast()
+ | mod_pubsub_dev:pubsub_state_owner()
+ | mod_pubsub_dev:pubsub_state_publisher(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ SubId :: undefined | exmpp_pubsub:subId())
+ -> {ok,
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ SubId :: exmpp_pubsub:subId()}
+ %%%
+ | {error, 'unexpected', 'not-subscribed'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+unsubscribe_node(Suffix, Host, Node_Access_Model,
+ #pubsub_state_dev{subscriptions = [{Subscription_State, SubId, Resource, _}]}
+ = Pubsub_State, Resource, _SubId)
+ when _SubId == undefined orelse _SubId == SubId ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = []
+ },
+ write),
+ %% unregister pending subscription
+ case Subscription_State of
+ 'pending' ->
+ unregister_pending_subscription(Suffix,
+ Pubsub_State#pubsub_state_dev.id, SubId);
+ _ ->
+ ok
+ end,
+ case Node_Access_Model of
+ 'roster' ->
+ ok;
+ _ ->
+ ok
+ end,
+ {ok, Resource, SubId};
+%%
+unsubscribe_node(Suffix, Host, Node_Access_Model,
+ #pubsub_state_dev{subscriptions = [{_Subscription_State, SubId, _Resource, _}]}
+ = Pubsub_State, Resource, SubId) ->
+ {error, 'unexpected', 'not-subscribed'};
+%%
+unsubscribe_node(Suffix, Host, Node_Access_Model,
+ #pubsub_state_dev{subscriptions = [{_Subscription_State, _SubId, Resource, _}]}
+ = Pubsub_State, Resource, SubId) ->
+ {error, 'not-acceptable', 'invalid-subid'};
+%%
+unsubscribe_node(Suffix, Host, Node_Access_Model, Pubsub_State, Resource,
+ undefined = _SubId) ->
+ case lists:keytake(Resource, 3, Pubsub_State#pubsub_state_dev.subscriptions) of
+ {value,
+ {Subscription_State, SubId, Resource, _} = Subscription, Subscriptions} ->
+ case lists:keymember(Resource, 3, Subscriptions) of
+ false ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = Subscriptions
+ },
+ write),
+ %% unregister pending subscription
+ case Subscription_State of
+ 'pending' ->
+ unregister_pending_subscription(Suffix,
+ Pubsub_State#pubsub_state_dev.id, SubId);
+ _ ->
+ ok
+ end,
+ case Node_Access_Model of
+ 'roster' ->
+ ok;
+ _ ->
+ ok
+ end,
+ %% TODO : unregister 'pubsub#tempsub'
+ {ok, Resource, SubId};
+ true ->
+ {error, 'bad-request', 'subid-required'}
+ end;
+ false ->
+ {error, 'unexpected', 'not-subscribed'}
+ end;
+%%
+unsubscribe_node(Suffix, Host, Node_Access_Model, Pubsub_State, Resource, SubId) ->
+ case lists:keytake(SubId, 2, Pubsub_State#pubsub_state_dev.subscriptions) of
+ {value,
+ {Subscription_State, SubId, Resource, _} = Subscription, Subscriptions} ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = Subscriptions
+ },
+ write),
+ %% unregister pending subscription
+ case Subscription_State of
+ 'pending' ->
+ unregister_pending_subscription(Suffix,
+ Pubsub_State#pubsub_state_dev.id, SubId);
+ _ ->
+ ok
+ end,
+ case Node_Access_Model of
+ 'roster' ->
+ ok;
+ _ ->
+ ok
+ end,
+ {ok, Resource, SubId};
+ {value, {_Subscription_State, SubId, _Resource, _} = Subscription, _} ->
+ {error, 'unexpected', 'not-subscribed'};
+ false ->
+ {error, 'not-acceptable', 'invalid-subid'}
+ end.
+
+%%
+-spec(register_pending_subscription/3 ::
+(
+ Suffix :: atom(),
+ StateId :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()},
+ SubId :: exmpp_pubsub:subId())
+ -> 'ok'
+).
+
+register_pending_subscription(Suffix, {Entity, NodeIdx} = _StateId, SubId) ->
+ Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending', Suffix),
+ case mnesia:read(Table_Pubsub_Subscription_Pending, {Entity, NodeIdx}, write) of
+ [] ->
+ mnesia:write(Table_Pubsub_Subscription_Pending,
+ #pubsub_subscription_pending{
+ id = {Entity, NodeIdx},
+ nodeidx = NodeIdx,
+ subids = [SubId]
+ },
+ write);
+ [Pubsub_Subscription_Pending] ->
+ mnesia:write(Table_Pubsub_Subscription_Pending,
+ Pubsub_Subscription_Pending#pubsub_subscription_pending{
+ subids = [SubId
+ | Pubsub_Subscription_Pending
+ #pubsub_subscription_pending.subids]
+ },
+ write)
+ end.
+
+%%
+-spec(unregister_pending_subscription/3 ::
+(
+ Suffix :: atom(),
+ StateId :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()},
+ SubId :: exmpp_pubsub:subId())
+ -> 'ok'
+).
+
+unregister_pending_subscription(Suffix, StateId, SubId) ->
+ Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending', Suffix),
+ case mnesia:read(Table_Pubsub_Subscription_Pending, StateId, write) of
+ %%
+ [#pubsub_subscription_pending{subids = [SubId]} = Pubsub_Subscription_Pending] ->
+ mnesia:delete_object(Table_Pubsub_Subscription_Pending,
+ Pubsub_Subscription_Pending, write);
+ %%
+ [Pubsub_Subscription_Pending] ->
+ mnesia:write(Table_Pubsub_Subscription_Pending,
+ Pubsub_Subscription_Pending#pubsub_subscription_pending{
+ subids = lists:delete(SubId,
+ Pubsub_Subscription_Pending#pubsub_subscription_pending.subids)
+ },
+ write);
+ [] ->
+ ok
+ end.
+
+%-- Set_Configure_Subscription --%
+-spec(set_configure_subscription/5 ::
+(
+ Suffix :: atom(),
+ Pubsub_State :: mod_pubsub_dev:pubsub_state_member()
+ | mod_pubsub_dev:pubsub_state_owner()
+ | mod_pubsub_dev:pubsub_state_publisher(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscription_Options :: pubsub_options:options_subscription())
+ -> 'ok'
+ %%%
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+set_configure_subscription(Suffix, Pubsub_State, undefined = _SubId, Resource,
+ Subscription_Options) ->
+ case
+ lists:keytake(Resource, 3, Pubsub_State#pubsub_state_dev.subscriptions)
+ of
+ {value, {Subscription_State, SubId, Resource, _Subscription_Options},
+ Subscriptions} ->
+ case lists:keymember(Resource, 3, Subscriptions) of
+ false
+ when Subscription_State == 'pending' ->
+ {error, 'forbidden'};
+ false ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = [
+ {Subscription_State, SubId, Resource,
+ Subscription_Options}
+ | Subscriptions]
+ },
+ write),
+ 'ok';
+ true ->
+ {error, 'bad-request', 'subid-required'}
+ end;
+ false ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end;
+%%
+set_configure_subscription(Suffix, Pubsub_State, SubId, Resource,
+ Subscription_Options) ->
+ case lists:keytake(SubId, 2, Pubsub_State#pubsub_state_dev.subscriptions) of
+ {value, {'pending', SubId, Resource, _Subscription_Options},
+ _Subscriptions} ->
+ {error, 'forbidden'};
+ {value, {Subscription_State, SubId, Resource, _Subscription_Options},
+ Subscriptions} ->
+ mnesia:write(table('pubsub_state', Suffix),
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = [
+ {Subscription_State, SubId, Resource,
+ Subscription_Options}
+ | Subscriptions]
+ },
+ write),
+ 'ok';
+ {value, {_Subscription_State, SubId, _Resource, _Subscription_Options},
+ _Subscriptions} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ false ->
+ case
+ lists:keytake(Resource, 3,
+ Pubsub_State#pubsub_state_dev.subscriptions)
+ of
+ {value, {'pending', _SubId, Resource, _Subscription_Options},
+ _Subscriptions} ->
+ {error, 'forbidden'};
+ {value, {_Subscription_State, _SubId, _Resource, _Subscription_Options},
+ _Subscriptions} ->
+ {error, 'not-acceptable', 'invalid-subid'};
+ false ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end
+ end.
+
+%-- Get_Items --%
+-spec(get_items/5 ::
+(
+ Suffix :: pubsub_db_mnesia:table(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Node_Parameters :: {NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Node_Owners :: mod_pubsub_dev:node_owners(),
+ Node_ItemIds :: [] | exmpp_pubsub:itemIds(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Node_Groups :: pubsub_options:rosters_groups_allowed()},
+ %%
+ Entity_Parameters :: {Entity :: xmpp_jid:usr_bare(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Access :: undefined | 'pending' | 'roster' | 'presence' | 'authorize'},
+ %%
+ Criteria :: {Max_Items :: non_neg_integer(),
+ ItemIds :: undefined,
+ _SubId :: exmpp_pubsub:subId()}
+ | {Max_Items :: undefined,
+ ItemIds :: exmpp_pubsub:itemIds(),
+ _SubId :: exmpp_pubsub:subId()})
+ -> {ok,
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Cache :: {Presence_Cache :: undefined,
+ Groups_Cache :: undefined}}
+ %
+ | {ok,
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Cache :: {Presence_Cache :: true,
+ Groups_Cache :: undefined}}
+ %
+ | {ok,
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Cache :: {Presence_Cache :: undefined,
+ Groups_Cache :: [{Node_Owner :: xmpp_jid:usr_bare(),
+ Groups :: [Group::binary()]}]}}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+get_items(_Suffix, _Host, _Node_Parameters, {_Entity, Affiliation, Access}, _Criteria)
+ when Affiliation == 'outcast'
+ orelse Affiliation == 'publish-only'
+ orelse Access == 'pending' ->
+ {error, 'forbidden'};
+%%
+get_items(Suffix, _Host,
+ {NodeIdx, _Node_Owners, Node_ItemIds, Node_Access_Model, _Node_Groups},
+ {_Entity, Affiliation, Access}, {Max_Items, ItemIds, _SubId})
+ when Affiliation == 'owner'
+ %
+ orelse Affiliation == 'publisher'
+ %
+ orelse Affiliation == 'member'
+ %
+ orelse Node_Access_Model == 'open'
+ %
+ orelse (Node_Access_Model == 'presence'
+ andalso
+ Access == 'presence'
+ orelse Access == 'roster')
+ %
+ orelse (Node_Access_Model == 'roster'
+ andalso
+ Access == 'roster')
+ %
+ orelse (Node_Access_Model == 'authorize'
+ andalso
+ Access == 'authorize') ->
+ {ok,
+ _Pusbub_Items = get_items(Suffix, NodeIdx,
+ get_max_itemids(Max_Items, ItemIds, Node_ItemIds)),
+ _Cache = {undefined, undefined}};
+%
+get_items(Suffix, Host,
+ {NodeIdx, Node_Owners, Node_ItemIds, 'presence' = _Node_Access_Model, _Node_Groups},
+ {Entity, _Affiliation, _Access}, {Max_Items, ItemIds, _SubId}) ->
+ case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
+ false ->
+ {error, 'not-authorized', 'presence-subscription-required'};
+ Node_Owner ->
+ {ok,
+ _Pusbub_Items = get_items(Suffix, NodeIdx,
+ get_max_itemids(Max_Items, ItemIds, Node_ItemIds)),
+ _Cache = {_Presence_Cache = true, _Groups_Cache = undefined}}
+ end;
+%%
+get_items(Suffix, _Host,
+ {NodeIdx, _Node_Owners, Node_ItemIds, 'roster' = _Node_Access_Model, Node_Groups},
+ {Entity, _Affiliation, _Access}, {Max_Items, ItemIds, _SubId}) ->
+ case is_contact_in_allowed_roster_groups(Entity, Node_Groups) of
+ {Node_Owner, Roster_Group} ->
+ {ok,
+ _Pusbub_Items = get_items(Suffix, NodeIdx,
+ get_max_itemids(Max_Items, ItemIds, Node_ItemIds)),
+ _Cache = {
+ _Presence_Cache = undefined,
+ _Groups_Cache = [{Node_Owner, [Roster_Group]}]
+ }
+ };
+ false ->
+ {error, 'not-authorized', 'not-in-roster-group'}
+ end;
+%%
+get_items(_Suffix, _Host,
+ {_NodeIdx, _Node_Owners, _Node_ItemIds, 'authorize' = _Node_Access_Model, _Node_Groups},
+ _Entity_Parameters, _Criteria) ->
+ {error, 'not-allowed', 'closed-node'};
+%%
+get_items(_Suffix, _Host, _Node_Parameters, _Entity_Parameters, _Criteria) ->
+ {error, 'forbidden'}.
+
+%%
+-spec(get_max_itemids/3 ::
+(
+ Max_Items :: undefined | non_neg_integer(),
+ ItemIds :: [] | exmpp_pubsub:itemIds(),
+ Node_ItemIds :: [] | exmpp_pubsub:itemIds())
+ -> Max_ItemIds :: undefined | [] | exmpp_pubsub:itemIds()
+).
+
+get_max_itemids(undefined = _Max_Items, [] = _ItemIds, _Node_ItemIds) ->
+ _Max_ItemIds = undefined;
+%%
+get_max_itemids(undefined = _Max_Items, ItemIds, Node_ItemIds) ->
+ _Max_ItemIds = lists:filter(fun
+ (ItemId) ->
+ lists:member(ItemId, Node_ItemIds)
+ end, ItemIds);
+%%
+get_max_itemids(Max_Items, undefined = _ItemIds, Node_ItemIds) ->
+ _Max_ItemIds = get_max_itemids(Max_Items, [], Node_ItemIds);
+%%
+get_max_itemids(_Max_Items, ItemIds, _Node_ItemIds)
+ when _Max_Items == 0 orelse _Node_ItemIds == [] ->
+ _Max_ItemIds = lists:reverse(ItemIds);
+%%
+get_max_itemids(Max_Items, ItemIds, [ItemId | Node_ItemIds]) ->
+ get_max_itemids(Max_Items - 1, [ItemId | ItemIds], Node_ItemIds).
+
+%%
+-spec(get_items/3 ::
+(
+ Suffix :: pubsub_db_mnesia:suffix(),
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ ItemIds :: undefined | [] | exmpp_pubsub:itemIds())
+ -> Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items()
+).
+
+get_items(Suffix, NodeIdx, undefined = _ItemIds) ->
+ _Pubsub_Items = mnesia:index_read(table('pubsub_item', Suffix), NodeIdx, nodeidx);
+%%
+get_items(Suffix, NodeIdx, ItemIds) ->
+ Table_Pubsub_Item = table('pubsub_item', Suffix),
+ _Pubsub_Items = lists:foldl(fun
+ (ItemId, Pubsub_Items) ->
+ case
+ mnesia:index_match_object(Table_Pubsub_Item,
+ #pubsub_item_dev{
+ id = {ItemId, NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx,
+ read)
+ of
+ [Pubsub_Item] -> [Pubsub_Item | Pubsub_Items];
+ _ -> Pubsub_Items
+ end
+ end, [], ItemIds).
diff --git a/src/mod_pubsub_ng/pubsub_dev.hrl b/src/mod_pubsub_ng/pubsub_dev.hrl
new file mode 100644
index 000000000..9a695ca16
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_dev.hrl
@@ -0,0 +1,146 @@
+
+
+%-include_lib("exmpp/include/exmpp.hrl").
+%-include_lib("exmpp/include/exmpp_jid.hrl").
+-include("ejabberd.hrl").
+-include("mod_roster.hrl").
+-include("jlib.hrl").
+%-include("pubsub_api.hrl").
+
+-define(NS_JABBER_CLIENT, <<"jabber:client">>).
+
+%%
+%% Pubsub Node
+-record(pubsub_node_dev,
+{
+ id ,%:: {Pubsub_Host :: exmpp_pubsub:host(),
+ % NodeId :: undefined | exmpp_pubsub:nodeId()},
+ idx ,%:: exmpp_pubsub:nodeIdx(),
+ creation ,%:: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()},
+ level = 0 ,%:: exmpp_pubsub:level(),
+ owners = [] ,%:: [Owner::xmpp_jid:usr_bare(),...],
+ itemids = [] ,%:: [ItemId::exmpp_pubsub:itemId()],
+ options = [] %:: pubsub_options:options_node()
+}).
+
+%%
+%% Pubsub State
+-record(pubsub_state_dev,
+{
+ id ,%:: {Entity :: xmpp_jid:usr_bare(),
+ % NodeIdx :: exmpp_pubsub:nodeIdx()},
+ nodeidx ,%:: exmpp_pubsub:nodeIdx(),
+ affiliation = 'none' ,%:: exmpp_pubsub:affiliation(),
+ access ,%:: pubsub_options:access_model(),
+ subscriptions = [] ,%:: [] | exmpp_pubsub:subscriptions(),
+ %groups = [] ,%:: [Roster_Group::binary()],
+ itemids = [] %:: [ItemId::exmpp_pubsub:itemId()]
+}).
+
+%%
+%% Pubsub Item
+-record(pubsub_item_dev,
+{
+ id ,%:: {ItemId::exmpp_pubsub:itemId(), NodeIdx::exmpp_pubsub:nodeIdx()},
+ nodeidx ,%:: exmpp_pubsub:nodeIdx(),
+ owners = [] ,%:: [Owner::xmpp_jid:usr_bare(),...],
+ creation ,%:: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()},
+ modification ,%:: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()},
+ payload ,%:: exmpp_pubsub:payload(),
+ options = [] %:: [] | pubsub_options:options_item()
+}).
+
+%%
+%% Pubsub Last Item
+-record(pubsub_last_item_dev,
+{
+ nodeidx ,%:: exmpp_pubsub:nodeIdx(),
+ id ,%:: exmpp_pubsub:itemId(),
+ owners = [] ,%:: [Owner::xmpp_jid:usr_bare(),...],
+ creation ,%:: {DateTime::erlang:timestamp(), Entity::xmpp_jid:usr_entity()},
+ payload ,%:: exmpp_pubsub:payload(),
+ options = [] %:: [] | pubsub_options:options_item()
+}).
+
+%%
+%% Pubsub Index
+-record(pubsub_index_dev,
+{
+ index :: exmpp_pubsub:index(),
+ last :: exmpp_pubsub:nodeIdx(),
+ free :: [exmpp_pubsub:nodeIdx()]
+}).
+
+%%
+%% Pubsub_Subscription_Pending
+-record(pubsub_subscription_pending,
+{
+ id ,%:: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()}
+ nodeidx ,%:: exmpp_pubsub:nodeIdx()
+ subids %:: [SubId::exmpp_pubsub:subId(),...]
+}).
+
+
+
+%%
+%% Internal data structures
+%%
+-record(entity,
+{
+ id :: xmpp_jid:usr_bare(),
+ local :: boolean(),
+ affiliation :: 'member' | 'owner' | 'publisher',
+ subscriptions :: []%[exmpp_pubsub:subscription(),...]
+}).
+
+%%
+-record(node,
+{
+ id :: exmpp_pubsub:nodeId(),
+ owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ access_model :: pubsub_options:access_model(),
+% itemreply :: 'owner' | 'publisher',
+% notification_type :: 'headline' | 'normal',
+% presence_based_delivery :: boolean(),
+ rosters_groups_allowed = [] :: [] | pubsub_options:rosters_groups_allowed()
+}).
+
+%%
+-record(item,
+{
+ access_model ,%:: undefined | pubsub_options:access_model(),
+ presence_based_delivery ,%:: undefined | boolean(),
+ rosters_groups_allowed = [],%:: [] | pubsub_options:rosters_groups_allowed()
+ stanza %::#xmlel{}
+}).
+
+%%
+-record(cache,
+{
+ %% Entity is online
+ presence :: undefined | mod_pubsub_dev:presence_cache(),
+ %% Entity has presence subscriptions with at least one node owner
+ presence_subscriptions :: undefined | boolean(),
+ %% Entity has presence subscriptions with at least one node owner
+ %% and is contained in at least one roster group from the node owner
+ rosters_groups :: undefined | boolean() %% TODO : use a list
+}).
+
+%%
+-record(subids,
+{
+ presence = undefined :: undefined | [] | mod_pubsub_dev:resources_subids(),
+ no_presence = undefined :: undefined | [] | mod_pubsub_dev:resources_subids()
+}).
+
+%%
+-record(event,
+{
+ host :: xmpp_jid:raw_jid_component_bare(),
+ component :: xmpp_jid:component_bare(),
+ entity :: mod_pubsub_dev:entity(),
+ node :: mod_pubsub_dev:n0de(),
+ cache :: mod_pubsub_dev:cache(),
+ subids :: mod_pubsub_dev:subids()
+}).
+
diff --git a/src/mod_pubsub_ng/pubsub_disco.erl b/src/mod_pubsub_ng/pubsub_disco.erl
new file mode 100644
index 000000000..935e4f47e
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_disco.erl
@@ -0,0 +1,1437 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_disco).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+
+-import(pubsub_db_mnesia,
+[
+ table/2
+]).
+
+-import(xmpp_xdata,
+[
+ xmlel_field/3,
+ xmlel_field_jid_multi/3,
+ xmlel_field_jid_single/3,
+ xmlel_field_list_single/4,
+ xmlel_field_text_single/3,
+ xmlel_x/2
+]).
+
+%%
+-spec(iq_disco_info/9 ::
+(
+ Suffix :: undefined | atom(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Privacy :: boolean(),
+ Entity :: xmpp_jid:usr_bare(),
+ Entity_Type :: 'local' | 'remote',
+ Plugin :: exmpp_pubsub:plugin(),
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Xmlels :: [] | [Xmlel_Set::xmlel(),...])
+ -> {result, Xmlel_Query::xmlel()}
+ %%%
+% | {error, 'feature-not-implemented', ?NS_DISCO_INFO}
+% | {error, 'feature-not-implemented', ?NS_PUBSUB_META_DATA}
+% %
+% | {error, 'item-not-found'}
+).
+
+iq_disco_info(_Suffix, Host, Pubsub_Host, _Privacy, _Entity, Entity_Type,
+ Plugin, undefined = _NodeId, _Xmlels) ->
+ Features = Plugin:features(),
+ case lists:member(?NS_DISCO_INFO, Plugin:features()) of
+ true ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#info',
+ lists:map(fun
+ ({Category, Name, Type}) ->
+ exmpp_pubsub:xmlel_identity('disco#info',
+ Category, Type, Name)
+ end, Plugin:identity())
+ ++
+ lists:map(fun
+ (Feature) ->
+ exmpp_pubsub:xmlel_feature('disco#info', Feature)
+ end, Features)
+ ++
+ lists:map(fun
+ (Pubsub_Feature) ->
+ exmpp_pubsub:xmlel_feature('disco#info',
+ <<?NS_PUBSUB/binary, "#", Pubsub_Feature/binary>>)
+ end, lists:sort(Plugin:pubsub_features(Entity_Type)))
+ )
+ };
+ false ->
+ {error, 'feature-not-implemented', ?NS_DISCO_INFO}
+ end;
+%%
+iq_disco_info(Suffix, Host, Pubsub_Host, false = _Privacy, {_,S,_} = _Entity,
+ Entity_Type, Plugin, NodeId, _Xmlels) ->
+ Features = Plugin:features(),
+ case lists:member(?NS_DISCO_INFO, Features) of
+ true ->
+ case
+ mnesia:dirty_read(table('pubsub_node', Suffix),
+ {Pubsub_Host, NodeId})
+ of
+ [#pubsub_node_dev{idx = NodeIdx, owners = Node_Owners,
+ options = Node_Options, creation = Creation}] ->
+ Pubsub_Features = Plugin:pubsub_features(Entity_Type),
+ {result,
+ exmpp_pubsub:xmlel_query('disco#info', NodeId,
+ [exmpp_pubsub:xmlel_identity('disco#info', <<"pubsub">>,
+ get_value(Node_Options, 'node_type'),
+ get_value(Node_Options, 'title', undefined))]
+ ++
+ [exmpp_pubsub:xmlel_identity('disco#info', Category, Type, Name)
+ || {Category, Name, Type} <- Plugin:identity()]
+ ++
+ [exmpp_pubsub:xmlel_feature('disco#info', Feature)
+ || Feature <- Features]
+ ++
+ [exmpp_pubsub:xmlel_feature('disco#info',
+ <<?NS_PUBSUB/binary, "#", Pubsub_Feature/binary>>)
+ || Pubsub_Feature <- Pubsub_Features]
+ ++
+ case lists:member(<<"meta-data">>, Pubsub_Features) of
+ true ->
+ [xmlel_x_metadata(table('pubsub_state', Suffix),
+ NodeIdx, Node_Owners, Creation,
+ Node_Options)];
+ false ->
+ []
+ end)};
+ _ ->
+ {error, 'item-not-found'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', ?NS_DISCO_INFO}
+ end.
+
+%%
+-spec(xmlel_x_metadata/5 ::
+(
+ Table_Pubsub_State :: atom(),
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Node_Owners :: mod_pubsub_dev:node_owners(),
+ Creation :: mod_pubsub_dev:node_creation(),
+ Node_Options :: pubsub_options:options_node())
+ -> Xmlel::xmlel()
+).
+
+%% <x xmlns='jabber:x:data' type='result'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#meta-data</value>
+%% </field>
+%% <field var='pubsub#type' label='Payload type' type='text-single'>
+%% <value>http://www.w3.org/2005/Atom</value>
+%% </field>
+%% <field var='pubsub#creator' label='Node creator' type='jid-single'>
+%% <value>hamlet@denmark.lit</value>
+%% </field>
+%% <field var='pubsub#creation_date' label='Creation date' type='text-single'>
+%% <value>2003-07-29T22:56Z</value>
+%% </field>
+%% <field var='pubsub#title' label='A short name for the node' type='text-single'>
+%% <value>Princely Musings (Atom)</value>
+%% </field>
+%% <field var='pubsub#description' label='A description of the node' type='text-single'>
+%% <value>Updates for Hamlet&apos;s Princely Musings weblog.</value>
+%% </field>
+%% <field var='pubsub#language' label='Default language' type='list-single'>
+%% <value>en</value>
+%% </field>
+%% <field var='pubsub#contact' label='People to contact with questions' type='jid-multi'>
+%% <value>bard@shakespeare.lit</value>
+%% </field>
+%% <field var='pubsub#owner' label='Node owners' type='jid-multi'>
+%% <value>hamlet@denmark.lit</value>
+%% </field>
+%% <field var='pubsub#publisher' label='Publishers to this node' type='jid-multi'>
+%% <value>hamlet@denmark.lit</value>
+%% </field>
+%% <field var='pubsub#num_subscribers' label='Number of subscribers to this node' type='text-single'>
+%% <value>1066</value>
+%% </field>
+%% </x>
+
+xmlel_x_metadata(_Table_Pubsub_State, _NodeIdx, Node_Owners,
+ {TimeStamp, Creator} = _Creation, Node_Options) ->
+ {DateTime, _} = jlib:timestamp_to_iso(calendar:now_to_datetime(TimeStamp), utc),
+ Creation_Date = list_to_binary(DateTime ++ "Z"),
+ xmlel_x(<<"form">>, [
+ xmlel_field(<<"FORM_TYPE">>, <<"result">>, [?NS_PUBSUB_META_DATA]),
+ %
+ xmlel_field_text_single(<<"pubsub#type">>, <<"Payload type">>,
+ get_value(Node_Options, 'type', undefined)),
+ %
+ xmlel_field_jid_single(<<"pubsub#creator">>, <<"Node creator">>,
+ pubsub_tools:jid_to_string(Creator)),
+ %
+ xmlel_field_text_single(<<"pubsub#creation_date">>, <<"Creation date">>,
+ Creation_Date),
+ %
+ xmlel_field_text_single(<<"pubsub#title">>, <<"Node title">>,
+ get_value(Node_Options, 'title', undefined)),
+ %
+ xmlel_field_text_single(<<"pubsub#description">>, <<"Node description">>,
+ get_value(Node_Options, 'description', undefined)),
+ %
+ xmlel_field_list_single(<<"pubsub#language">>, <<"Default language">>,
+ get_value(Node_Options, 'language', <<"en">>), []),
+ %
+ xmlel_field_jid_multi(<<"pubsub#contact">>,
+ <<"People to contact with questions">>,
+ get_value(Node_Options, 'contact', [])),
+ %
+ xmlel_field_jid_multi(<<"pubsub#owner">>, <<"Node owners">>,
+ [pubsub_tools:jid_to_string(Node_Owner) || Node_Owner <- Node_Owners])
+ %
+% xmlel_field_jid_multi(<<"pubsub#publisher">>,
+% <<"Publishers to this node">>,
+% [pubsub_tools:jid_to_string(Publisher)
+% || Publisher
+% <- mnesia:dirty_select(Table_Pubsub_State,
+% [{#pubsub_state_dev{
+% id = {'$1', '_'},
+% nodeidx = NodeIdx,
+% itemids = '$2',
+% _ = '_'},
+% [{'=/=', '$2', []}],
+% ['$1']}])
+% ]
+% ),
+% %
+% xmlel_field_text_single(<<"pubsub#num_subscribers">>,
+% <<"Number of subscribers to this node">>,
+% list_to_binary(integer_to_list(length(
+% mnesia:dirty_select(Table_Pubsub_State,
+% [{#pubsub_state_dev{
+% id = {'$1', '_'},
+% nodeidx = NodeIdx,
+% access = '$2',
+% subscriptions = '$3',
+% _ = '_'},
+% [{'=/=', '$2', 'pending'},
+% {'=/=', '$3', []}],
+% ['$1']}])
+% )))
+% )
+ ]).
+
+%%
+-spec(iq_disco_items/9 ::
+(
+ Suffix :: undefined | atom(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Privacy :: boolean(),
+ Entity :: xmpp_jid:usr_bare(),
+ Entity_Type :: 'local' | 'remote',
+ Plugin :: exmpp_pubsub:plugin(),
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Xmlels :: [] | [Xmlel_Set::xmlel(),...])
+ -> {result, Xmlel_Query::xmlel()}
+ %%%
+% | {error, 'feature-not-implemented', ?NS_DISCO_ITEMS}
+% %
+% | {error, 'item-not-found'}
+).
+
+iq_disco_items(Suffix, Host, Pubsub_Host, Privacy, Entity, _Entity_Type,
+ Plugin, NodeId, _Xmlels) ->
+ case lists:member(?NS_DISCO_ITEMS, Plugin:features()) of
+ true -> disco_nodes(Privacy, NodeId, Suffix, Host, Pubsub_Host, Entity);
+ false -> {error, 'feature-not-implemented', ?NS_DISCO_ITEMS}
+ end.
+
+%%
+-spec(disco_nodes/6 ::
+(
+ Privacy :: boolean(),
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Suffix :: undefined | atom(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Entity :: xmpp_jid:usr_bare())
+ -> {result, Xmlel_Query::xmlel()}
+ %%%
+ | {error, 'item-not-found'}
+).
+
+disco_nodes(false, undefined, Suffix, _, Pubsub_Host, _) ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ [exmpp_pubsub:xmlel_item('disco#items', NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ || #pubsub_node_dev{id = {_, NodeId}, options = Node_Options}
+ <- mnesia:dirty_match_object(table('pubsub_node', Suffix),
+ #pubsub_node_dev{
+ id = {Pubsub_Host, '_'},
+ level = 1,
+ _ = '_'
+ })
+ ])};
+%%
+disco_nodes(true, undefined, Suffix, Host, Pubsub_Host, Entity) ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ disco_nodes(
+ mnesia:dirty_match_object(table('pubsub_node', Suffix),
+ #pubsub_node_dev{
+ id = {Pubsub_Host, '_'},
+ _ = '_'
+ }),
+ Host, Entity, table('pubsub_state', Suffix), {[], [], []}))};
+%%
+disco_nodes(false, NodeId, Suffix, _Host, Pubsub_Host, _Entity) ->
+ Table_Pubsub_Node = table('pubsub_node', Suffix),
+ case mnesia:dirty_read(Table_Pubsub_Node, {Pubsub_Host, NodeId}) of
+ [#pubsub_node_dev{idx = NodeIdx, options = Node_Options}] ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ [exmpp_pubsub:xmlel_item('disco#items', ItemId,
+ Pubsub_Host)
+ || #pubsub_item_dev{id = {ItemId, _}}
+ <- mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx)
+ ];
+ 'collection' ->
+ lists:foldr(fun
+ (_NodeId, Xmlels_Item) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [#pubsub_node_dev{options = _Node_Options}] ->
+ [exmpp_pubsub:xmlel_item('disco#items',
+ _NodeId, Pubsub_Host,
+ get_value(_Node_Options, 'title',
+ undefined))
+ |Xmlels_Item];
+ _ ->
+ Xmlels_Item
+ end
+ end, [], get_value(Node_Options, 'children', []))
+ end)};
+ _ ->
+ {error, 'item_not-found'}
+ end;
+%%
+disco_nodes(true, NodeId, Suffix, Host, Pubsub_Host, Entity) ->
+ Table_Pubsub_Node = table('pubsub_node', Suffix),
+ case mnesia:dirty_read(Table_Pubsub_Node, {Pubsub_Host, NodeId}) of
+ [#pubsub_node_dev{idx = NodeIdx, owners = Node_Owners,
+ options = Node_Options}] ->
+ case lists:member(Entity, Node_Owners) of
+ true ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options, 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [], get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ false ->
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ case
+ mnesia:dirty_index_match_object(
+ table('pubsub_state', Suffix),
+ #pubsub_state_dev{
+ id = {Entity, NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx)
+ of
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'open'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'none'
+ orelse
+ Affiliation == 'publisher') ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ []
+ when Node_Access_Model == 'open' ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ _
+ when Node_Access_Model == 'open' ->
+ {error, 'item-not-found'};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'presence'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'publisher'
+ orelse
+ Access == 'presence'
+ orelse
+ Access == 'roster') ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'presence'
+ andalso (Affiliation == 'outcast'
+ orelse
+ Affiliation == 'publish-only') ->
+ {error, 'item-not-found'};
+ %%
+ _
+ when Node_Access_Model == 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity,
+ Node_Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ Node_Owner ->
+ {result, exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [Node_Owner], []})
+ end)}
+ end;
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'roster'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'publisher'
+ orelse
+ (Access == 'roster'
+ andalso
+ Affiliation == 'none')) ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'roster'
+ andalso (Affiliation == 'outcast'
+ orelse
+ Affiliation == 'publish-only') ->
+ {error, 'item-not-found'};
+ %%
+ _
+ when Node_Access_Model == 'roster' ->
+ Rosters_Groups_Allowed = get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Rosters_Groups_Allowed)
+ of
+ false ->
+ {error, 'item-not-found'};
+ {Node_Owner, Roster_Group} ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ Rosters_Groups_Allowed,
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(
+ Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options,
+ 'children', [])),
+ Host, Entity,
+ table('pubsub_state', Suffix),
+ {[], [], [{Node_Owner, [Roster_Group]}]})
+ end)}
+ end;
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'authorize'
+ andalso ((Affiliation =/= 'publish-only'
+ andalso
+ Affiliation =/= 'outcast')
+ orelse
+ Access == 'authorize') ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ []
+ when Node_Access_Model == 'authorize' ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ _
+ when Node_Access_Model == 'authorize' ->
+ {error, 'item-not-found'};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'whitelist'
+ andalso (Access == 'whitelist'
+ orelse
+ (Affiliation =/= 'none'
+ andalso
+ Affiliation =/= 'outcast'
+ andalso
+ Affiliation =/= 'publish-only')) ->
+ {result,
+ exmpp_pubsub:xmlel_query('disco#items',
+ case get_value(Node_Options, 'node_type') of
+ 'leaf' ->
+ disco_items(
+ mnesia:dirty_index_match_object(
+ table('pubsub_item', Suffix),
+ #pubsub_item_dev{
+ id = {'_', NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx),
+ Host, Pubsub_Host,
+ get_value(Node_Options, 'access_model'),
+ get_value(Node_Options,
+ 'roster_groups_allowed', []),
+ Node_Owners,
+ Entity,
+ 'owner',
+ undefined,
+ {[], [], []});
+ 'collection' ->
+ disco_nodes(
+ lists:foldr(fun
+ (_NodeId, Pubsub_Nodes) ->
+ case
+ mnesia:dirty_read(
+ Table_Pubsub_Node,
+ {Pubsub_Host, _NodeId})
+ of
+ [Pubsub_Node] ->
+ [Pubsub_Node | Pubsub_Nodes];
+ _ ->
+ Pubsub_Nodes
+ end
+ end, [],
+ get_value(Node_Options, 'children', [])),
+ Host, Entity, table('pubsub_state', Suffix),
+ {[], [], []})
+ end)};
+ %%
+ _ ->
+ {error, 'item-not-found'}
+ end
+ end;
+ _ ->
+ {error, 'item_not-found'}
+ end.
+
+%%
+-spec(disco_nodes/5 ::
+(
+ Pubsub_Nodes :: [] | mod_pubsub_dev:pubsub_nodes(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Table_Pubsub_State :: atom(),
+ Cache :: {
+ Xmlels_Items :: [Xmlel_Item::xmlel()],
+ Presence_Cache :: [Entity::xmpp_jid:usr_bare()],
+ Groups_Cache :: [{Entity::xmpp_jid:usr_bare(), Groups::[Group::binary(),...]}]
+ })
+ -> Xmlels_Items::[Xmlel_Item::xmlel()]
+).
+
+disco_nodes([] = _Pubsub_Nodes, _Host, _Entity, _Table_Pubsub_State,
+ {Xmlels_Item, _Presence_Cache, _Groups_Cache}) ->
+ Xmlels_Item;
+%%
+disco_nodes([#pubsub_node_dev{id = {Pubsub_Host, NodeId},
+ idx = NodeIdx, owners = Node_Owners, options = Node_Options} | Pubsub_Nodes],
+ Host, Entity, Table_Pubsub_State,
+ {Xmlels_Item, Presence_Cache, Groups_Cache}) ->
+ disco_nodes(Pubsub_Nodes, Host, Entity, Table_Pubsub_State,
+ case lists:member(Entity, Node_Owners) of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ case
+ mnesia:dirty_index_match_object(Table_Pubsub_State,
+ #pubsub_state_dev{
+ id = {Entity, NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx)
+ of
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'open'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'none'
+ orelse
+ Affiliation == 'publisher') ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ []
+ when Node_Access_Model == 'open' ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ _
+ when Node_Access_Model == 'open' ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'presence'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'publisher'
+ orelse
+ Access == 'presence'
+ orelse
+ Access == 'roster') ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'presence'
+ andalso (Affiliation == 'outcast'
+ orelse
+ Affiliation == 'publish-only') ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ %%
+ _
+ when Node_Access_Model == 'presence'
+ andalso Presence_Cache == [] ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity,
+ Node_Owners)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ Node_Owner ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ [Node_Owner | Presence_Cache], Groups_Cache}
+ end;
+ %%
+ _
+ when Node_Access_Model == 'presence' ->
+ case presence_cache(Presence_Cache, Node_Owners) of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Node_Owners)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ Node_Owner ->
+ {[exmpp_pubsub:xmlel_item('disco#items',
+ NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ [Node_Owner | Presence_Cache], Groups_Cache}
+ end
+ end;
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'roster'
+ andalso (Affiliation == 'member'
+ orelse
+ Affiliation == 'publisher'
+ orelse
+ (Access == 'roster'
+ andalso
+ Affiliation == 'none')) ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation}]
+ when Node_Access_Model == 'roster'
+ andalso (Affiliation == 'outcast'
+ orelse
+ Affiliation == 'publish-only') ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ %%
+ _
+ when Node_Access_Model == 'roster'
+ andalso Groups_Cache == [] ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ get_value(Node_Options, 'roster_groups_allowed'))
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ {Node_Owner, Roster_Group} ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache,
+ [{Node_Owner, [Roster_Group]} | Groups_Cache]}
+ end;
+ %%
+ _
+ when Node_Access_Model == 'roster' ->
+ Rosters_Groups_Allowed = get_value(Node_Options,
+ 'roster_groups_allowed'),
+ case groups_cache(Groups_Cache, Rosters_Groups_Allowed) of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Rosters_Groups_Allowed)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ {Node_Owner, Roster_Group} ->
+ {[exmpp_pubsub:xmlel_item('disco#items',
+ NodeId, Pubsub_Host,
+ get_value(Node_Options, 'title',
+ undefined))
+ |Xmlels_Item],
+ Presence_Cache,
+ case
+ lists:keyfind(Node_Owner, 1,
+ Groups_Cache)
+ of
+ {_, Groups} ->
+ lists:keyreplace(Node_Owner, 1,
+ Groups_Cache,
+ {Node_Owner,
+ [Roster_Group | Groups]});
+ false ->
+ [{Node_Owner, [Roster_Group]}
+ | Groups_Cache]
+ end}
+ end
+ end;
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'authorize'
+ andalso ((Affiliation =/= 'publish-only'
+ andalso
+ Affiliation =/= 'outcast')
+ orelse
+ Access == 'authorize') ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ []
+ when Node_Access_Model == 'authorize' ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ _
+ when Node_Access_Model == 'authorize' ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ %%
+ [#pubsub_state_dev{affiliation = Affiliation, access = Access}]
+ when Node_Access_Model == 'whitelist'
+ andalso (Access == 'whitelist'
+ orelse
+ (Affiliation =/= 'none'
+ andalso
+ Affiliation =/= 'outcast'
+ andalso
+ Affiliation =/= 'publish-only')) ->
+ {[exmpp_pubsub:xmlel_item('disco#items', NodeId,
+ Pubsub_Host,
+ get_value(Node_Options, 'title', undefined))
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ %%
+ _ ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache}
+ end
+ end).
+
+%%
+-spec(disco_items/10 ::
+(
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed(),
+ Node_Owners :: mod_pubsub_dev:node_owners(),
+ Entity :: xmpp_jid:usr_bare(),
+ Affiliation :: 'member' | 'none' | 'owner' | 'publisher',
+ Access :: undefined|'authorize'|'presence'|'roster'|'whitelist',
+ Cache :: {
+ Xmlels_Items :: [Xmlel_Item::xmlel()],
+ Presence_Cache :: [Entity::xmpp_jid:usr_bare()],
+ Groups_Cache :: [{Entity::xmpp_jid:usr_bare(), Groups::[Group::binary(),...]}]
+ })
+ -> Xmlels_Items::[Xmlel_Item::xmlel()]
+).
+
+disco_items([] = _Pubsub_Items, _Host, _Pubsub_Host, _Node_Access_Model,
+ _Rosters_Groups_Allowed, _Node_Owners, _Entity, _Affiliation, _Access,
+ {Xmlels_Items, _, _}) ->
+ Xmlels_Items;
+%%
+disco_items([#pubsub_item_dev{id = {ItemId, _}, options = Item_Options} | Pubsub_Items],
+ Host, Pubsub_Host, Node_Access_Model, Rosters_Groups_Allowed, Node_Owners, Entity,
+ Affiliation, Access,
+ {Xmlels_Item, Presence_Cache, Groups_Cache})
+ when Item_Options == []
+ orelse Affiliation == 'owner'
+ orelse Affiliation == 'publisher' ->
+ disco_items(Pubsub_Items, Host, Pubsub_Host, Node_Access_Model,
+ Rosters_Groups_Allowed, Node_Owners, Entity, Affiliation, Access,
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId, Pubsub_Host)
+ | Xmlels_Item], Presence_Cache, Groups_Cache});
+%%
+disco_items([#pubsub_item_dev{id = {ItemId, _}, options = Item_Options} | Pubsub_Items],
+ Host, Pubsub_Host, Node_Access_Model, Rosters_Groups_Allowed, Node_Owners, Entity,
+ Affiliation, Access,
+ {Xmlels_Item, Presence_Cache, Groups_Cache}) ->
+ disco_items(Pubsub_Items, Host, Pubsub_Host, Node_Access_Model,
+ Rosters_Groups_Allowed, Node_Owners, Entity, Affiliation, Access,
+ case get_value(Item_Options, 'access_model') of
+ Item_Access_Model
+ when Item_Access_Model == 'none'
+ orelse Item_Access_Model == 'open'
+ orelse (Item_Access_Model =/= 'roster'
+ andalso
+ Item_Access_Model == Node_Access_Model)
+ orelse (Item_Access_Model == 'presence'
+ andalso
+ (Node_Access_Model == 'roster'
+ orelse
+ Access == 'presence'
+ orelse
+ Access == 'roster'))
+ orelse Item_Access_Model == 'authorize'
+ orelse Item_Access_Model == 'whitelist' ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId, Pubsub_Host)
+ | Xmlels_Item], Presence_Cache, Groups_Cache};
+ %%
+ 'presence'
+ when Access == 'presence'
+ orelse Access == 'roster' ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId, Pubsub_Host)
+ | Xmlels_Item], Presence_Cache, Groups_Cache};
+ 'presence'
+ when Presence_Cache == [] ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ Node_Owner ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId, Pubsub_Host)
+ |Xmlels_Item],
+ [Node_Owner | Presence_Cache], Groups_Cache}
+ end;
+ 'presence' ->
+ case presence_cache(Presence_Cache, Node_Owners) of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId, Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity,
+ Node_Owners)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ Node_Owner ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId,
+ Pubsub_Host)
+ |Xmlels_Item],
+ [Node_Owner | Presence_Cache], Groups_Cache}
+ end
+ end;
+ 'roster'
+ when Rosters_Groups_Allowed == [] ->
+ case get_value(Item_Options, 'roster_groups_allowed') of
+ Item_Roster_Groups_Allowed
+ when Item_Roster_Groups_Allowed == 'none'
+ orelse Item_Roster_Groups_Allowed == [] ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ Item_Roster_Groups_Allowed ->
+ case
+ groups_cache(Groups_Cache, Item_Roster_Groups_Allowed)
+ of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId,
+ Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Item_Roster_Groups_Allowed)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ {Node_Owner, Roster_Group} ->
+ {[exmpp_pubsub:xmlel_item('disco#items',
+ ItemId, Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache,
+ case
+ lists:keyfind(Node_Owner, 1,
+ Groups_Cache)
+ of
+ {_, Groups} ->
+ lists:keyreplace(Node_Owner, 1,
+ Groups_Cache,
+ {Node_Owner,
+ [Roster_Group | Groups]});
+ false ->
+ [{Node_Owner, [Roster_Group]}
+ | Groups_Cache]
+ end}
+ end
+ end
+ end;
+ 'roster' ->
+ case get_value(Item_Options, 'roster_groups_allowed') of
+ Item_Rosters_Groups_Allowed
+ when Item_Rosters_Groups_Allowed == 'none'
+ orelse Item_Rosters_Groups_Allowed == [] ->
+ case
+ groups_cache(Groups_Cache, Rosters_Groups_Allowed)
+ of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId,
+ Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Rosters_Groups_Allowed)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ {Node_Owner, Roster_Group} ->
+ {[exmpp_pubsub:xmlel_item('disco#items',
+ ItemId, Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache,
+ case
+ lists:keyfind(Node_Owner, 1,
+ Groups_Cache)
+ of
+ {_, Groups} ->
+ lists:keyreplace(Node_Owner, 1,
+ Groups_Cache,
+ {Node_Owner,
+ [Roster_Group | Groups]});
+ false ->
+ [{Node_Owner, [Roster_Group]}
+ | Groups_Cache]
+ end}
+ end
+ end;
+ Item_Rosters_Groups_Allowed ->
+ case
+ groups_cache(Groups_Cache, Item_Rosters_Groups_Allowed)
+ of
+ true ->
+ {[exmpp_pubsub:xmlel_item('disco#items', ItemId,
+ Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache, Groups_Cache};
+ false ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Item_Rosters_Groups_Allowed)
+ of
+ false ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache};
+ {Node_Owner, Roster_Group} ->
+ {[exmpp_pubsub:xmlel_item('disco#items',
+ ItemId, Pubsub_Host)
+ |Xmlels_Item],
+ Presence_Cache,
+ case
+ lists:keyfind(Node_Owner, 1,
+ Groups_Cache)
+ of
+ {_, Groups} ->
+ lists:keyreplace(Node_Owner, 1,
+ Groups_Cache,
+ {Node_Owner,
+ [Roster_Group | Groups]});
+ false ->
+ [{Node_Owner, [Roster_Group]}
+ | Groups_Cache]
+ end}
+ end
+ end
+ end;
+ _ ->
+ {Xmlels_Item, Presence_Cache, Groups_Cache}
+ end).
+
+
+%%
+-spec(presence_cache/2 ::
+(
+ Presence_Cache :: [Entity::xmpp_jid:usr_bare()],
+ Node_Owners :: [] | [Entity::xmpp_jid:usr_bare(),...])
+ -> Presence_Cache::boolean()
+).
+
+presence_cache([], _) ->
+ false;
+%%
+presence_cache([Entity | Presence_Cache], Node_Owners) ->
+ case lists:member(Entity, Node_Owners) of
+ true -> true;
+ false -> presence_cache(Presence_Cache, Node_Owners)
+ end.
+
+%%
+-spec(groups_cache/2 ::
+(
+ Groups_Cache :: [Group_Cache :: {
+ Entity::xmpp_jid:usr_bare(),
+ Groups::[Group::binary(),...]
+ }],
+ Rosters_Groups :: [Roster_Groups :: {
+ Entity::xmpp_jid:usr_bare(),
+ Groups::[Group::binary(),...]
+ },...])
+ -> Groups_Cache::boolean()
+).
+
+groups_cache([], _) ->
+ false;
+%%
+groups_cache([{Entity, Groups} | Groups_Cache], Rosters_Groups) ->
+ case lists:keyfind(Entity, 1, Rosters_Groups) of
+ {_, Roster_Groups} -> group_cache(Groups, Roster_Groups);
+ false -> groups_cache(Groups_Cache, Rosters_Groups)
+ end.
+
+%%
+-spec(group_cache/2 ::
+(
+ Groups :: [] | [Group::binary(),...],
+ Roster_Groups :: [Group::binary(),...])
+ -> Group_Cache::boolean()
+).
+
+group_cache([], _) ->
+ false;
+%%
+group_cache([Group | Groups], Roster_Groups) ->
+ case lists:member(Group, Roster_Groups) of
+ true -> true;
+ false -> group_cache(Groups, Roster_Groups)
+ end.
diff --git a/src/mod_pubsub_ng/pubsub_groups.erl b/src/mod_pubsub_ng/pubsub_groups.erl
new file mode 100644
index 000000000..c0ff2e96a
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_groups.erl
@@ -0,0 +1,904 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_groups).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_acces_model/3,
+ check_acces_model/7,
+ check_publish_model/3,
+ get_entity_roster/1,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1,
+ get_resources/2,
+ get_resources_show/2,
+ rosters_groups_allowed_cache/2
+]).
+
+-import(pubsub_db_mnesia,
+[
+ table/2
+]).
+
+
+-type(group() :: binary()).
+
+-type(node_group() :: group()).
+
+-type(roster_group() :: group()).
+
+-type(node_groups() :: [Node_Group::node_group(),...]).
+
+-type(roster_groups() :: [Roster_Group::roster_group(),...]).
+
+-type(nodeIdxs() :: [NodeIdx::exmpp_pubsub:nodeIdx(),...]).
+
+-type(n0de()
+ :: {NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Groups :: node_groups()}
+).
+
+-type(n0des() :: [Node::n0de(),...]).
+
+-type(subscription()
+ :: {NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Groups :: roster_groups()}
+).
+
+-type(subscriptions() :: [Subscription::subscription(),...]).
+
+-type(contact()
+ :: {Entity :: xmpp_jid:usr_bare(),
+ Subscriptions :: subscriptions()}
+).
+
+-type(contacts() :: [Contact::contact(),...]).
+
+
+-record(pubsub_groups,
+{
+ owner :: xmpp_jid:usr_bare(),
+ nodes = [] :: n0des(),
+ contacts = [] :: [] | contacts()
+}).
+
+
+create_table_pubsub_groups(Suffix) ->
+ mnesia:create_table(table('pubsub_groups', Suffix),
+ [{type, set},
+ {disc_copies, [node()]},
+ {record_name, pubsub_groups},
+ {attributes, record_info(fields, pubsub_groups)}]).
+
+
+
+%%
+-spec(register_groups/4 ::
+(
+ Suffix :: atom(),
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Owner :: xmpp_jid:usr_bare(),
+ Groups :: [] | node_groups())
+ -> undefined
+ %
+ | {Subscribers :: [Entity::xmpp_jid:usr_bare()],
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+register_groups(Suffix, NodeIdx, Owner, [] = _Groups) ->
+ case
+ mnesia:read(Table_Pubsub_Groups = table('pubsub_groups', 'dev'),
+ Owner, write)
+ of
+ %%
+ [] ->
+ undefined;
+ %%
+ [#pubsub_groups{nodes = [{NodeIdx, _Node_Groups}]} = Pubsub_Groups] ->
+ mnesia:delete_object(Table_Pubsub_Groups, Pubsub_Groups, write),
+ %
+ {_Subscribers = [],
+ _Unsubscribers = lists:foldl(fun
+ ({Entity, Subscriptions}, Unsubscribers) ->
+ case lists:keymember(NodeIdx, 1, Subscriptions) of
+ true -> [Entity | Unsubscribers];
+ false -> Unsubscribers
+ end
+ %
+ end, [], Pubsub_Groups#pubsub_groups.contacts)};
+ %%
+ [Pubsub_Groups] ->
+ case lists:keyfind(NodeIdx, 1, Pubsub_Groups#pubsub_groups.nodes) of
+ {_NodeIdx, Node_Groups} ->
+ {Contacts, Unsubscribers} = subscriptions1(NodeIdx,
+ Pubsub_Groups#pubsub_groups.contacts),
+ %
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = Contacts
+ },
+ write),
+ %
+ {_Subscribers = [],
+ _Unsubscribers = Unsubscribers};
+ false ->
+ undefined
+ end
+ end;
+%%
+register_groups(Suffix, NodeIdx, Owner, Groups) ->
+ case
+ mnesia:read(Table_Pubsub_Groups = table('pubsub_groups', 'dev'), Owner,
+ write)
+ of
+ [] ->
+ {Contacts, Subscribers} = subscriptions2(NodeIdx, Owner, Groups),
+ %
+ mnesia:write(Table_Pubsub_Groups,
+ #pubsub_groups{
+ owner = Owner,
+ nodes = [{NodeIdx, Groups}],
+ contacts = Contacts
+ },
+ write),
+ %
+ {_Subscribers = Subscribers,
+ _Unsubscribers = []};
+ [Pubsub_Groups] ->
+ case lists:keyfind(NodeIdx, 1, Pubsub_Groups#pubsub_groups.nodes) of
+ {_NodeIdx, Node_Groups} ->
+ case diff_groups(Groups, Node_Groups) of
+ %%
+ {[] = _New_Groups, [] = _Old_Groups} ->
+ {_Subscribers = [],
+ _Unsubscribers = []};
+ %%
+ {[] = _New_Groups, Old_Groups} ->
+ {Contacts, Unsubscribers}
+ = subscriptions3(NodeIdx, Old_Groups,
+ Pubsub_Groups#pubsub_groups.contacts),
+ %
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ nodes = lists:keyreplace(NodeIdx, 1,
+ Pubsub_Groups#pubsub_groups.nodes,
+ {NodeIdx, Groups}),
+ contacts = Contacts
+ },
+ write),
+ %
+ {_Subscribers = [],
+ _Unsubscribers = Unsubscribers};
+ %%
+ {_New_Groups, _Old_Groups} ->
+ {Contacts, Subscribers, Unsubscribers}
+ = subscriptions4(NodeIdx, Owner, Node_Groups,
+ Pubsub_Groups#pubsub_groups.contacts),
+ %
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ nodes = lists:keyreplace(NodeIdx, 1,
+ Pubsub_Groups#pubsub_groups.nodes,
+ {NodeIdx, Node_Groups}),
+ contacts = Contacts
+ },
+ write),
+ %
+ {Subscribers = Subscribers,
+ _Unsubscribers = Unsubscribers}
+ end;
+ false ->
+ {Contacts, Subscribers}
+ = subscriptions5(NodeIdx, Owner, Groups,
+ _Contacts = Pubsub_Groups#pubsub_groups.contacts),
+ %
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ nodes = [{NodeIdx, Groups}
+ | Pubsub_Groups#pubsub_groups.nodes],
+ contacts = Contacts
+ },
+ write),
+ %
+ {_Subscribers = Subscribers,
+ _Unsubscribers = []}
+ end
+ end.
+
+
+%% TODO : fix this hook managment
+-spec(monitor_contacts/3 ::
+(
+ _ :: 'subscribed',
+ Entity :: xmpp_jid:usr_bare(),
+ Jid_Contacts :: xmpp_jid:entity_bare())
+ -> undefined
+ %
+ | {Server :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Contact :: xmpp_jid:usr_bare(),
+ _ :: {'subscribed', NodeIdxs :: nodeIdxs()}}
+).
+
+monitor_contacts('subscribed', {_, Server, _} = Entity, Jid_Contact) ->
+ case roster_groups(Entity, Contact = jlid:jid_to_lower(Jid_Contact)) of
+ [] ->
+ undefined;
+ Roster_Groups ->
+ case
+ mnesia:read(Table_Pubsub_Groups = table('pubsub_groups', dev),
+ _Owner = Entity, write)
+ of
+ [] ->
+ undefined;
+ [Pubsub_Groups] ->
+ case
+ filter_subscriptions(Pubsub_Groups#pubsub_groups.nodes,
+ Roster_Groups)
+ of
+ [] ->
+ undefined;
+ Subscriptions ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = case
+ lists:keyreplace(Contact, 1,
+ Pubsub_Groups#pubsub_groups.contacts,
+ {Contact, Subscriptions})
+ of
+ _Contacts
+ when _Contacts ==
+ Pubsub_Groups#pubsub_groups.contacts ->
+ [{Contact, Subscriptions}
+ | Pubsub_Groups#pubsub_groups.contacts];
+ Contacts ->
+ Contacts
+ end
+ },
+ write),
+ {Server, Entity, Contact,
+ {'subscribed',
+ _NodeIdx = lists:map(fun
+ ({NodeIdx, _Groups}) ->
+ NodeIdx
+ end, Subscriptions)}}
+ end
+ end
+ end.
+
+
+%%
+-spec(monitor_groups/2 ::
+(
+ Server :: xmpp_jid:raw_jid_component_bare(),
+ Roster :: #roster{groups :: [] | roster_groups()})
+ -> undefined
+ %
+ | {Server :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Diff_NodeIdxs :: {NodeIdxs_Subscriptions :: [] | nodeIdxs(),
+ NodeIdxs_Unsubscriptions :: [] | nodeIdxs()}}
+).
+
+monitor_groups(Server, #roster{us = {U,S}} = _Roster) ->
+ case
+ mnesia:read(Table_Pubsub_Groups = table('pubsub_groups', dev),
+ _Owner = {U,S,undefined}, write)
+ of
+ [] ->
+ undefined;
+ [Pubsub_Groups] ->
+ case
+ lists:keyfind(_Roster#roster.jid, 1,
+ Pubsub_Groups#pubsub_groups.contacts)
+ of %%
+ {_Entity, Old_Subscriptions}
+ when _Roster#roster.subscription == 'delete'
+ orelse _Roster#roster.subscription == 'none' ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = lists:keydelete(_Roster#roster.jid, 1,
+ Pubsub_Groups#pubsub_groups.contacts)
+ },
+ write),
+ {Server,
+ _Entity = _Roster#roster.jid,
+ _Diff_NodeIdxs = diff_nodeidxs(_New_Subscriptions = [],
+ Old_Subscriptions)};
+ %%
+ {_Entity, Old_Subscriptions}
+ when (_Roster#roster.subscription == 'both'
+ orelse
+ _Roster#roster.subscription == 'from')
+ andalso _Roster#roster.groups == [] ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = lists:keydelete(_Roster#roster.jid, 1,
+ Pubsub_Groups#pubsub_groups.contacts)
+ },
+ write),
+ {Server,
+ _Roster#roster.jid,
+ _Diff_NodeIdxs = diff_nodeidxs(_New_Subscriptions = [],
+ Old_Subscriptions)};
+ %%
+ {_Entity, Old_Subscriptions}
+ when _Roster#roster.subscription == 'both'
+ orelse _Roster#roster.subscription == 'from' ->
+ case
+ filter_subscriptions(Pubsub_Groups#pubsub_groups.nodes,
+ _Roster#roster.groups)
+ of
+ [] ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = lists:keydelete(_Roster#roster.jid,
+ 1, Pubsub_Groups#pubsub_groups.contacts)
+ },
+ write),
+ {Server, _Roster#roster.jid,
+ diff_nodeidxs(_New_Subscriptions = [],
+ Old_Subscriptions)};
+ New_Subscriptions ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = lists:keyreplace(_Roster#roster.jid,
+ 1, Pubsub_Groups#pubsub_groups.contacts,
+ {_Roster#roster.jid, New_Subscriptions})
+ },
+ write),
+ {Server,
+ _Entity = _Roster#roster.jid,
+ _Diff_NodeIdxs = diff_nodeidxs(New_Subscriptions,
+ Old_Subscriptions)}
+ end;
+ %%
+ false
+ when (_Roster#roster.subscription == 'both'
+ orelse
+ _Roster#roster.subscription == 'from')
+ andalso _Roster#roster.groups =/= [] ->
+ case
+ filter_subscriptions(Pubsub_Groups#pubsub_groups.nodes,
+ _Roster#roster.groups)
+ of
+ [] ->
+ undefined;
+ New_Subscriptions ->
+ mnesia:write(Table_Pubsub_Groups,
+ Pubsub_Groups#pubsub_groups{
+ contacts = [{_Roster#roster.jid, New_Subscriptions}
+ | Pubsub_Groups#pubsub_groups.contacts]
+ },
+ write),
+ {Server,
+ _Entity = _Roster#roster.jid,
+ _Diff_NodeIdxs = diff_nodeidxs(New_Subscriptions,
+ _Old_Subscriptions = [])}
+ end;
+ %%
+ _ ->
+ undefined
+ end
+ end.
+
+
+
+%%
+%%
+-spec(subscriptions1/2 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Old_Contacts :: [] | contacts())
+ -> Diff_Contacts :: {New_Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions1(NodeIdx, Contacts) ->
+ _Diff_Contacts = subscriptions1(NodeIdx, _Old_Contacts = Contacts,
+ {_New_Contacts = [], _Unsubscribers = []}).
+
+
+%%
+-spec(subscriptions1/3 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Old_Contacts :: [] | contacts(),
+ Diff_Contacts :: {Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]})
+ -> Diff_Contacts :: {New_Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions1(_NodeIdx, [] = _Old_Contacts, Diff_Contacts) ->
+ Diff_Contacts;
+%%
+subscriptions1(NodeIdx, [{Entity, Subscriptions} | Old_Contacts],
+ {Contacts, Unsubscribers}) ->
+ subscriptions1(NodeIdx, Old_Contacts,
+ _Diff_Contacts = case lists:keydelete(NodeIdx, 1, Subscriptions) of
+ [] ->
+ {_Contacts = Contacts,
+ _Unsubscribers = [Entity | Unsubscribers]};
+ Subscriptions ->
+ {_Contacts = [{Entity, Subscriptions} | Contacts],
+ _Unsubscribers = Unsubscribers};
+ New_Subscriptions ->
+ {_Contacts = [{Entity, New_Subscriptions} | Contacts],
+ _Unsubscribers = [Entity | Unsubscribers]}
+ end).
+
+
+%%
+-spec(subscriptions2/3 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Owner :: xmpp_jid:usr_bare(),
+ Node_Groups :: node_groups())
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions2(NodeIdx, Owner, Node_Groups) ->
+ subscriptions2(NodeIdx, _Rosters = get_entity_roster(Owner), Node_Groups,
+ {_New_Contacts = [], _Subscribers = []}).
+
+%%
+-spec(subscriptions2/4 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Rosters :: [Roster::#roster{groups :: [] | roster_groups()}],
+ Node_Groups :: node_groups(),
+ Diff_Contacts :: {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()]})
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions2(_NodeIdx, [] = _Rosters, _Node_Groups, Diff_Contacts) ->
+ Diff_Contacts;
+%%
+subscriptions2(NodeIdx, [_Roster | Rosters], Node_Groups, {Contacts, Subscribers})
+ when _Roster#roster.groups =/= [] ->
+ subscriptions2(NodeIdx, Rosters, Node_Groups,
+ _Diff_Contacts = case
+ filter_groups(_Roster#roster.groups, Node_Groups, [])
+ of
+ [] ->
+ {_Contacts = Contacts,
+ _Subscribers = Subscribers};
+ Roster_Groups ->
+ {_Contacts = [{_Roster#roster.jid, [{NodeIdx, Roster_Groups}]}
+ | Contacts],
+ _Subscribers = [_Roster#roster.jid | Subscribers]}
+ end);
+%%
+subscriptions2(NodeIdx, [_Roster | Rosters], Node_Groups, Diff_Contacts) ->
+ subscriptions2(NodeIdx, Rosters, Node_Groups, Diff_Contacts).
+
+
+%%
+-spec(subscriptions3/3 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Old_Groups :: roster_groups(),
+ Contacts :: contacts() | [])
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+subscriptions3(NodeIdx, Old_Groups, Contacts) ->
+ subscriptions3(NodeIdx, Contacts, Old_Groups, {[],[]}).
+
+%%
+-spec(subscriptions3/4 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ Old_Contacts :: contacts() | [],
+ Old_Groups :: roster_groups(),
+ Diff_Contacts :: {Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]})
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions3(_NodeIdx, [] = _Contacts, _Old_Groups, Diff_Contacts) ->
+ Diff_Contacts;
+%%
+subscriptions3(NodeIdx, [{Entity, Subscriptions} | Old_Contacts], Old_Groups,
+ {Contacts, Unsubscribers}) ->
+ subscriptions3(NodeIdx, Old_Contacts, Old_Groups,
+ _Diff_Contacts = case lists:keyfind(NodeIdx, 1, Subscriptions) of
+ {_NodeIdx, Roster_Groups} ->
+ case delete_groups(Old_Groups, Roster_Groups) of
+ [] ->
+ {_Contacts = Contacts,
+ _Unsubscribers = [Entity | Unsubscribers]};
+ Groups ->
+ {_Contacts = [{Entity,
+ lists:keyreplace(NodeIdx, 1,
+ Subscriptions, {NodeIdx, Groups})}
+ | Contacts],
+ _Unsubscribers = Unsubscribers}
+ end;
+ false ->
+ {_Contacts = [{Entity, Subscriptions} | Contacts],
+ _Unsubscribers = Unsubscribers}
+ end).
+
+
+%%
+-spec(subscriptions4/4 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ _ :: xmpp_jid:usr_bare()
+ | [Roster::#roster{groups :: [] | roster_groups()}],
+ Node_Groups :: node_groups(),
+ _ :: [] | contacts()
+ | {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()],
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]})
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()],
+ Unsubscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions4(_NodeIdx, [] = _Rosters, _Node_Groups, Diff_Contacts) ->
+ Diff_Contacts;
+%%
+subscriptions4(NodeIdx, [_Roster | Rosters], Node_Groups,
+ {Contacts, Subscribers, Unsubscribers})
+ when _Roster#roster.groups =/= [] ->
+ subscriptions4(NodeIdx, Rosters, Node_Groups,
+ _Diff_Contacts = case
+ {lists:keyfind(Entity = _Roster#roster.jid, 1, Contacts),
+ filter_groups(_Roster#roster.groups, Node_Groups, [])}
+ of
+ %%
+ {false, [] = _Groups} ->
+ {_Contacts = Contacts,
+ _Subscribers = Subscribers,
+ _Unsubscribers = Unsubscribers};
+ %%
+ {false, Groups} ->
+ {_Contacts = [{Entity, [{NodeIdx, Groups}]} | Contacts],
+ _Subscribers = [Entity | Subscribers],
+ _Unsubscribers = Unsubscribers};
+ %%
+ {{_Entity, [{NodeIdx, _Roster_Groups}] = _Subscriptions}, [] = _Groups} ->
+ {_Contacts = lists:keydelete(Entity, 1, Contacts),
+ _Subscribers = Subscribers,
+ _Unsubscribers = [Entity | Unsubscribers]};
+ %%
+ {{_Entity, Subscriptions}, [] = _Groups} ->
+ case lists:keydelete(NodeIdx, 1, Subscriptions) of
+ Subscriptions ->
+ {_Contacts = Contacts,
+ _Subscribers = Subscribers,
+ _Unsubscribers = Unsubscribers};
+ New_Subscriptions ->
+ {_Contacts = lists:keyreplace(Entity, 1, Contacts,
+ {Entity, New_Subscriptions}),
+ _Subscribers = Subscribers,
+ _Unsubscribers = [Entity | Unsubscribers]}
+ end;
+ {{_Entity, Subscriptions}, Groups} ->
+ case lists:keyreplace(NodeIdx, 1, Subscriptions, {NodeIdx, Groups}) of
+ Subscriptions ->
+ {_Contacts = lists:keyreplace(Entity, 1, Contacts,
+ {Entity,
+ [{NodeIdx, Groups} | Subscriptions]}),
+ _Subscribers = [Entity | Subscribers],
+ _Unsubscribers = Unsubscribers};
+ New_Subscriptions ->
+ {_Contacts = lists:keyreplace(Entity, 1, Contacts,
+ {Entity, New_Subscriptions}),
+ _Subscribers = Subscribers,
+ _Unsubscribers = Unsubscribers}
+ end
+ end);
+%%
+subscriptions4(NodeIdx, [_Roster | Rosters], Node_Groups, Diff_Contacts) ->
+ subscriptions4(NodeIdx, Rosters, Node_Groups, Diff_Contacts);
+%%
+subscriptions4(NodeIdx, Owner, Node_Groups, Contacts) ->
+ subscriptions4(NodeIdx, _Rosters = get_entity_roster(Owner), Node_Groups,
+ _Diff_Contacts = {Contacts, _Subscribers = [], _Unsubscribers = []}).
+
+
+
+-spec(subscriptions5/4 ::
+(
+ NodeIdx :: exmpp_pubsub:nodeIdx(),
+ _ :: xmpp_jid:usr_bare()
+ | [Roster::#roster{groups :: [] | roster_groups()}],
+ Node_Groups :: node_groups(),
+ _ :: [] | contacts()
+ | {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()]})
+ -> Diff_Contacts :: {Contacts :: [] | contacts(),
+ Subscribers :: [Entity::xmpp_jid:usr_bare()]}
+).
+
+subscriptions5(NodeIdx, [_Roster | Rosters], Node_Groups, {Contacts, Subscribers})
+ when _Roster#roster.groups =/= [] ->
+ subscriptions5(NodeIdx, Rosters, Node_Groups,
+ _Diff_Contacts = case
+ filter_groups(_Roster#roster.groups, Node_Groups, [])
+ of
+ [] ->
+ {_Contacts = Contacts,
+ _Subscribers = Subscribers};
+ Groups ->
+ {_Contacts = case
+ lists:keyfind(_Roster#roster.jid, 1, Contacts)
+ of
+ false ->
+ [{_Roster#roster.jid, [{NodeIdx, Groups}]} | Contacts];
+ {Entity, Subscriptions} ->
+ lists:keyreplace(Entity, 1, Contacts,
+ {Entity, [{NodeIdx, Groups} | Subscriptions]})
+ end,
+ _Subscribers = [_Roster#roster.jid | Subscribers]}
+ end);
+%%
+subscriptions5(NodeIdx, [_Roster | Rosters], Node_Groups, Diff_Contacts) ->
+ subscriptions5(NodeIdx, Rosters, Node_Groups, Diff_Contacts);
+%%
+subscriptions5(NodeIdx, Owner, Node_Groups, Contacts) ->
+ subscriptions5(NodeIdx, _Rosters = get_entity_roster(Owner), Node_Groups,
+ _Diff_Contacts = {Contacts, _Subscribers = []}).
+
+
+%%
+-spec(delete_groups/2 ::
+(
+ Old_Groups :: roster_groups(),
+ Groups :: [] | roster_groups())
+ -> Groups :: [] | roster_groups()
+).
+
+delete_groups(_Old_Groups, [] = _Groups) ->
+ _Groups = [];
+%%
+delete_groups([] = _Old_Groups, Groups) ->
+ Groups;
+%%
+delete_groups([Old_Group | Old_Groups], Groups) ->
+ _Groups = delete_groups(Old_Groups, lists:delete(Old_Group, Groups)).
+
+
+%%
+-spec(diff_groups/2 ::
+(
+ Groups :: node_groups() | [],
+ _ :: node_groups()
+ | {New_Groups :: [] | node_groups(),
+ Old_Groups :: [] | node_groups()})
+ -> Diff_Groups :: {New_Groups :: [] | node_groups(),
+ Old_Groups :: [] | node_groups()}
+).
+
+diff_groups([] = _Groups, Diff_Groups) -> %% when is_tuple(Diff_Groups)
+ Diff_Groups;
+%%
+diff_groups([Group | Groups], {New_Groups, Old_Groups}) ->
+ diff_groups(Groups,
+ _Diff_Groups = case lists:delete(Group, Old_Groups) of
+ Old_Groups -> {[Group | New_Groups], Old_Groups};
+ Old_Groups2 -> {New_Groups, Old_Groups2}
+ end);
+%%
+diff_groups(New_Groups, Old_Groups) ->
+ diff_groups(New_Groups, {[], Old_Groups}).
+
+
+%%
+-spec(filter_groups/3 ::
+(
+ Roster_Groups :: roster_groups() | [],
+ Node_Groups :: node_groups(),
+ Groups :: [] | roster_groups())
+ -> Groups :: [] | roster_groups()
+).
+
+filter_groups([] = _Roster_Groups, _Node_Groups, Groups) ->
+ Groups;
+%%
+filter_groups([Roster_Group | Roster_Groups], Node_Groups, Groups) ->
+ filter_groups(Roster_Groups, Node_Groups,
+ _Groups = case lists:member(Roster_Group, Node_Groups) of
+ true -> [Roster_Group | Groups];
+ false -> Groups
+ end).
+
+
+%%
+-spec(filter_subscriptions/2 ::
+(
+ Nodes :: n0des(),
+ Roster_Groups :: roster_groups())
+ -> Subscriptions :: [] | subscriptions()
+).
+
+filter_subscriptions(Nodes, Roster_Groups) ->
+ _Subscriptions = filter_subscriptions(Nodes, Roster_Groups, []).
+
+%%
+-spec(filter_subscriptions/3 ::
+(
+ Nodes :: n0des(),
+ Roster_Groups :: roster_groups(),
+ Subscriptions :: [] | subscriptions())
+ -> Subscriptions :: [] | subscriptions()
+).
+
+filter_subscriptions([] = _Nodes, _Roster_Groups, Subscriptions) ->
+ Subscriptions;
+%%
+filter_subscriptions([{NodeIdx, Node_Groups} | Nodes], Roster_Groups,
+ Subscriptions) ->
+ filter_subscriptions(Nodes, Roster_Groups,
+ _Subscriptions = case filter_groups(Roster_Groups, Node_Groups, []) of
+ [] -> Subscriptions;
+ Groups -> [{NodeIdx, Groups} | Subscriptions]
+ end).
+
+
+%%
+-spec(diff_nodeidxs/2 ::
+(
+ New_Subscriptions :: [] | subscriptions(),
+ Old_Subscriptions :: [] | subscriptions())
+ -> Diff_NodeIdxs :: {NodeIdxs_Subscriptions :: [] | nodeIdxs(),
+ NodeIdxs_Unsubscriptions :: [] | nodeIdxs()}
+).
+
+diff_nodeidxs(New_Subscriptions, Old_Subscriptions) ->
+ _Diff_NodeIdxs = diff_nodeidxs(New_Subscriptions, Old_Subscriptions, []).
+
+%%
+-spec(diff_nodeidxs/3 ::
+(
+ New_Subscriptions :: [] | subscriptions(),
+ Old_Subscriptions :: [] | subscriptions(),
+ NodeIdxs_Subscriptions :: [] | nodeIdxs())
+ -> Diff_NodeIdxs :: {NodeIdxs_Subscriptions :: [] | nodeIdxs(),
+ NodeIdxs_Unsubscriptions :: [] | nodeIdxs()}
+).
+
+diff_nodeidxs([] = _New_Subscriptions, Old_Subscriptions,
+ NodeIdxs_Subscriptions) ->
+ _Diff_NodeIdxs = {
+ _NodeIdxs_Subscriptions = NodeIdxs_Subscriptions,
+ _NodeIdxs_Unsubscriptions = lists:map(fun
+ ({NodeIdx, _Roster_Groups}) ->
+ NodeIdx
+ end, Old_Subscriptions)};
+%%
+diff_nodeidxs([{NodeIdx, _Groups} | New_Subscriptions], [] = Old_Subscriptions,
+ NodeIdxs_Subscriptions) ->
+ diff_nodeidxs(New_Subscriptions, Old_Subscriptions,
+ [NodeIdx | NodeIdxs_Subscriptions]);
+%%
+diff_nodeidxs([{NodeIdx, _Groups} | New_Subscriptions], Old_Subscriptions,
+ NodeIdxs_Subscriptions) ->
+ case lists:keydelete(NodeIdx, 1, Old_Subscriptions) of
+ Old_Subscriptions ->
+ diff_nodeidxs(New_Subscriptions, Old_Subscriptions,
+ [NodeIdx | NodeIdxs_Subscriptions]);
+ Subscriptions ->
+ diff_nodeidxs(New_Subscriptions, _Old_Subscriptions = Subscriptions,
+ NodeIdxs_Subscriptions)
+ end.
+
+
+%% TODO : use a new mod_roster_api for this call
+-spec(roster_groups/2 ::
+(
+ Entity :: xmpp_jid:usr_bare(),
+ Contact :: xmpp_jid:usr_bare())
+ -> Roster_Groups :: [] | roster_groups()
+).
+
+roster_groups({User, Server, _} = _Entity, Contact) ->
+ _Roster_Groups = case
+ gen_storage:transaction(Server, rosteritem,
+ fun() ->
+ gen_storage:select(Server, rostergroup,
+ [{'=', user_host_jid, {User, Server, Contact}}])
+ end)
+ of
+ {atomic, _RosterGroups} ->
+ lists:map(fun
+ (_RosterGroup) ->
+ _RosterGroup#roster.groups
+ end, _RosterGroups);
+ _Error
+ -> {error, 'internal-server-error'}
+ end.
+
+
+%%
+-spec(select_roster_groups/1 ::
+(
+ Entity :: xmpp_jid:usr_entity())
+ -> Roster_Groups :: [] | roster_groups()
+).
+
+select_roster_groups({User, Server, _} = Entity) ->
+ _Roster_Groups = case
+ gen_storage:transaction(Server, rosteritem,
+ fun() ->
+ gen_storage:select(Server, rostergroup,
+ [{'=', user_host_jid, {User, Server, '_'}}])
+ end)
+ of
+ {atomic, _RosterGroups} ->
+ lists:map(fun
+ (_RosterGroup) ->
+ _RosterGroup#roster.groups
+ end, _RosterGroups);
+ _Error
+ -> {error, 'internal-server-error'}
+ end.
+
+
+%% TESTS
+
+%%
+monitor_roster_groups(Server, Roster) ->
+ ?INFO_MSG("SERVER ~p ROSTER ~p", [Server, Roster]),
+ Result = pubsub_db:transaction('mnesia', ?MODULE, monitor_groups,
+ [Server, Roster]),
+ ?INFO_MSG("MONITOR ROSTER GROUPS ~p", [Result]).
+
+
+monitor_contacts2('subscribed', Entity, Jid_Contact) ->
+ Result = pubsub_db:transaction('mnesia', ?MODULE, monitor_contacts,
+ ['subscribed', Entity, Jid_Contact]),
+ ?INFO_MSG("MONITOR CONTACTS2 ~p", [Result]).
+
+register1(NodeIdx, User, Groups) ->
+ Server = <<"localhost">>,
+ Owner = {list_to_binary(atom_to_list(User)), Server, undefined},
+ Groups_B = lists:map(fun(Group) -> list_to_binary(atom_to_list(Group)) end, Groups),
+ register_groups(dev, NodeIdx, Owner, Groups_B).
+
+register(NodeIdx, User, Groups) ->
+ pubsub_db:transaction('mnesia', ?MODULE, register1, [NodeIdx, User, Groups]).
diff --git a/src/mod_pubsub_ng/pubsub_hooks.erl b/src/mod_pubsub_ng/pubsub_hooks.erl
new file mode 100644
index 000000000..c5c1fc28a
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_hooks.erl
@@ -0,0 +1,570 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_hooks).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+-import(pubsub_db_mnesia,
+[
+ table/2
+]).
+
+%% 'node_config#send_last_published_item'
+-spec(presence_online/3 ::
+(
+ From :: xmpp_jid:entity_full(),
+ To :: xmpp_jid:entity_full(),
+ C2SPid :: pid())
+ -> 'ok'
+).
+
+presence_online(#jid{luser = U, lserver = S, lresource = R} = Jid, Jid, C2SPid) ->
+ ?INFO_MSG("PRESENCE ONLINE From ~p ~n, To ~p ~n, C2SPid ~p ~n",
+ [Jid, Jid, C2SPid]),
+ Pubsub_Features = node_flat_dev:pubsub_features(),
+ case lists:member(<<"last-published">>, Pubsub_Features) of
+ true ->
+ spawn(?MODULE, last_published_items,
+ [_Host = <<"localhost">>, {U,S,R}]);
+ false ->
+ ok
+ end;
+%%
+presence_online(_, _, _) ->
+ ok.
+
+
+%%
+-spec(last_published_items/2 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_full())
+ -> 'ok'
+).
+
+last_published_items(Host, {U,S,_R} = Entity) ->
+ Table_Pubsub_State = table('pubsub_state', 'dev'),
+ Table_Pubsub_Node = table('pubsub_node', 'dev'),
+ Table_Pubsub_Last_Item = table('pubsub_last_item', 'dev'),
+ lists:foreach(fun
+ (State) ->
+ last_published_items(Host, Entity, Table_Pubsub_Node,
+ Table_Pubsub_Last_Item, State)
+ end,
+ mnesia:dirty_select(Table_Pubsub_State,
+ [{#pubsub_state_dev{
+ id = {{U,S,undefined}, '$1'},
+ affiliation = '$2',
+ access = '$3',
+ subscriptions = '$4',
+ _ = '_'
+ },
+ [{'=/=', '$2', 'outcast'},
+ {'=/=', '$3', 'pending'},
+ {'=/=', '$4', []}],
+ ['$$']}])).
+
+%%
+-spec(last_published_items/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_full(),
+ Table_Pubsub_Node :: atom(),
+ Table_Pubsub_Last_Item :: atom(),
+ State :: [exmpp_pubsub:nodeIdx() |
+ 'member' | 'owner' | 'publisher' |
+ _ |
+ exmpp_pubsub:subscriptions(),... ])
+ -> 'ok'
+).
+
+last_published_items(Host, {_, _, Online_Resource} = Entity,
+ Table_Pubsub_Node, Table_Pubsub_Last_Item,
+ [NodeIdx, Affiliation, _Access, Subscriptions]) ->
+ case mnesia:dirty_index_read(Table_Pubsub_Node, NodeIdx, idx) of
+ [#pubsub_node_dev{
+ id = {Pubsub_Host, NodeId},
+ owners = Node_Owners,
+ options = Node_Options
+ }] ->
+ case get_value(Node_Options, 'send_last_published_item') of
+ 'on_sub_and_presence' ->
+ case mnesia:dirty_read(Table_Pubsub_Last_Item, NodeIdx) of
+ [#pubsub_last_item_dev{
+ id = ItemId,
+ creation = {DateTime, Publisher},
+ payload = Payload,
+ options = Item_Options
+ }] ->
+ spawn(pubsub_broadcast, broadcast_publish_last2,
+ [Host, Pubsub_Host, NodeId, Node_Options,
+ Node_Owners,
+ [{ItemId, Item_Options, Payload, Publisher, DateTime}],
+ Entity, Affiliation,
+ lists:foldl(fun
+ ({Subscription_State, SubId, Resource, Subscription_Options},
+ Acc)
+ when Subscription_State =/= 'pending'
+ andalso (Resource == undefined
+ orelse
+ Resource == Online_Resource) ->
+ [{Subscription_State, SubId,
+ Online_Resource, Subscription_Options}
+ | Acc];
+ (_Subscription, Acc) ->
+ Acc
+ end, [], Subscriptions)]);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+%% 'node_config#purge_offline'
+%% 'node_config#tempsub'
+%% 'subscribe_options#expire'
+
+-spec(presence_offline/3 ::
+(
+ _ :: _,
+ Jid :: xmpp_jid:entity_full(),
+ _ :: _)
+ -> 'ok'
+).
+
+presence_offline({_DateTime, _Pid},
+ #jid{luser = U, lserver = S, lresource = Offline_Resource} = _Jid, _) ->
+ Host = <<"localhost">>,
+ Pubsub_Features = node_flat_dev:pubsub_features(),
+ Subscribe = lists:member(<<"subscribe">>, Pubsub_Features),
+ Leased_Subscription = lists:member(<<"leased-subscription">>, Pubsub_Features),
+ Purge_Nodes = lists:member(<<"purge-nodes">>, Pubsub_Features),
+ Persistent_Items = lists:member(<<"persistent-items">>, Pubsub_Features),
+ Subscription_Notifications = lists:member(<<"subscription-notifications">>,
+ Pubsub_Features),
+ Table_Pubsub_State = table('pubsub_state', 'dev'),
+ Table_Pubsub_Node = table('pubsub_node', 'dev'),
+ Online_Resources = case ejabberd_sm:get_user_resources(U, S) of
+ [] -> false;
+ _ -> true
+ end,
+ lists:foreach(fun
+ (Pubsub_State) ->
+ presence_offline(Host,
+ _Entity = {U,S,undefined},
+ Table_Pubsub_State,
+ Pubsub_State,
+ Table_Pubsub_Node,
+ Offline_Resource,
+ Online_Resources,
+ {Subscribe,
+ Leased_Subscription,
+ Purge_Nodes,
+ Persistent_Items,
+ Subscription_Notifications})
+ end,
+ mnesia:dirty_match_object(Table_Pubsub_State,
+ #pubsub_state_dev{
+ id = {{U,S,undefined}, '_'},
+ _ = '_'
+ })).
+
+%%
+-spec(presence_offline/8 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Table_Pubsub_State :: atom(),
+ Pubsub_State :: mod_pubsub_dev:pubsub_state(),
+ Table_Pubsub_Node :: atom(),
+ Offline_Resource :: xmpp_jid:resource_jid(),
+ Online_Resources :: boolean(),
+ Pubsub_Features :: {Subscribe :: boolean(),
+ Leased_Subscription :: boolean(),
+ Purge_Nodes :: boolean(),
+ Persistent_Items :: boolean(),
+ Subscription_Notifications :: boolean()})
+ -> 'ok'
+).
+
+presence_offline(Host, Entity, Table_Pubsub_State, Pubsub_State,
+ Table_Pubsub_Node, Offline_Resource, Online_Resources,
+ {Subscribe, Leased_Subscription, Purge_Nodes, Persistent_Items,
+ Subscription_Notifications}) ->
+ case
+ mnesia:dirty_index_read(Table_Pubsub_Node,
+ Pubsub_State#pubsub_state_dev.nodeidx, idx)
+ of
+ [#pubsub_node_dev{id = {Pubsub_Host, NodeId}} = Pubsub_Node] ->
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+
+ case
+ Subscribe == true
+ andalso
+ get_value(Node_Options, 'tempsub') == true
+ of
+ true ->
+ case
+ filter_tempsub_subscriptions(
+ get_value(Node_Options, 'notify_sub'),
+ Pubsub_State#pubsub_state_dev.subscriptions,
+ Offline_Resource, Online_Resources, {[], []})
+ of
+ %%
+ {_Unexpired_Subscriptions, [] = _Expired_Subscriptions} ->
+ ok;
+ %%
+ {[] = _Unexpired_Subscriptions, Expired_Subscriptions}
+ when Pubsub_State#pubsub_state_dev.affiliation == 'member'
+ andalso Pubsub_State#pubsub_state_dev.itemids == [] ->
+ mnesia:dirty_delete_object(Table_Pubsub_State,
+ Pubsub_State),
+ Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ notify_subscriptions(Host, Pubsub_Host,
+ Entity, NodeId, Notification_Type,
+ _Recipients = case Subscription_Notifications of
+ true ->
+ case
+ lists:member(Entity,
+ Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ Pubsub_Node#pubsub_node_dev.owners;
+ false ->
+ [Entity
+ | Pubsub_Node#pubsub_node_dev.owners]
+ end;
+ false ->
+ Pubsub_Node#pubsub_node_dev.owners
+ end,
+ Expired_Subscriptions),
+ ok;
+ %%
+ {Unexpired_Subscriptions, Expired_Subscriptions} ->
+ mnesia:dirty_write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = Unexpired_Subscriptions
+ }),
+ Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ notify_subscriptions(Host, Pubsub_Host,
+ Entity, NodeId, Notification_Type,
+ _Recipients = case Subscription_Notifications of
+ true ->
+ case
+ lists:member(Entity,
+ Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ Pubsub_Node#pubsub_node_dev.owners;
+ false ->
+ [Entity
+ | Pubsub_Node#pubsub_node_dev.owners]
+ end;
+ false ->
+ Pubsub_Node#pubsub_node_dev.owners
+ end,
+ Expired_Subscriptions),
+ ok
+ end;
+ _ ->
+ case Subscribe == true andalso Leased_Subscription == true of
+ true ->
+ case
+ filter_expired_subscriptions(
+ get_value(Node_Options, 'notify_sub'),
+ Pubsub_State#pubsub_state_dev.subscriptions,
+ Offline_Resource, Online_Resources, {[], []})
+ of
+ %%
+ {_Unexpired_Subscriptions, [] = _Expired_Subscriptions} ->
+ ok;
+ %%
+ {[] = _Unexpired_Subscriptions, Expired_Subscriptions}
+ when Pubsub_State#pubsub_state_dev.affiliation == 'member' ->
+ mnesia:dirty_delete_object(Table_Pubsub_State,
+ Pubsub_State),
+ Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ notify_subscriptions(Host, Pubsub_Host,
+ Entity, NodeId, Notification_Type,
+ _Recipients = case
+ Subscription_Notifications
+ of
+ true ->
+ case
+ lists:member(Entity,
+ Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ Pubsub_Node#pubsub_node_dev.owners;
+ false ->
+ [Entity
+ |Pubsub_Node#pubsub_node_dev.owners]
+ end;
+ false ->
+ Pubsub_Node#pubsub_node_dev.owners
+ end,
+ Expired_Subscriptions),
+ ok;
+ %%
+ {Unexpired_Subscriptions, Expired_Subscriptions} ->
+ mnesia:dirty_write(Table_Pubsub_State,
+ Pubsub_State#pubsub_state_dev{
+ subscriptions = Unexpired_Subscriptions
+ }),
+ Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ notify_subscriptions(Host, Pubsub_Host,
+ Entity, NodeId, Notification_Type,
+ _Recipients = case
+ Subscription_Notifications
+ of
+ true ->
+ case
+ lists:member(Entity,
+ Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ Pubsub_Node#pubsub_node_dev.owners;
+ false ->
+ [Entity
+ | Pubsub_Node#pubsub_node_dev.owners]
+ end;
+ false ->
+ Pubsub_Node#pubsub_node_dev.owners
+ end,
+ Expired_Subscriptions),
+ ok
+ end;
+ false ->
+ ok
+ end
+ end;
+ [] ->
+ ok
+ end.
+
+%%
+-spec(notify_subscriptions/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notification_Type :: 'message' | 'headline',
+ Recipients :: [Entity::xmpp_jid:usr_bare(),...],
+ Subscriptions :: [Subscription::exmpp_pubsub:subscription(),...])
+ -> 'ok'
+).
+
+notify_subscriptions(Host, Pubsub_Host, {U,S,_} = _Entity, NodeId,
+ Notification_Type, Recipients, Subscriptions) ->
+ Pubsub_Component_Jid = jlib:make_jid(<<>>, Pubsub_Host, <<>>),
+ Jids = [pubsub_tools:make_jid(Recipient) || Recipient <- Recipients],
+ lists:foreach(fun
+ ({_, SubId, Resource, _}) ->
+ Subscriber = jlib:jid_to_string({U,S,Resource}),
+ lists:foreach(fun
+ (Recipient) ->
+ spawn(pubsub_broadcast, notify_subscription,
+ [Host, NodeId, Pubsub_Component_Jid, Recipient,
+ Notification_Type, {Subscriber, 'none', SubId}])
+ end, Jids)
+ end, Subscriptions).
+
+%%
+filter_purged_offline_itemids([Publisher_ItemId | Publisher_ItemIds],
+ Table_Pubsub_Item, NodeIdx, {U,S,R} = _Entity,
+ {Node_ItemIds, Unpurged_ItemIds, Purged_ItemIds}) ->
+ filter_purged_offline_itemids(Publisher_ItemIds, Table_Pubsub_Item, NodeIdx,
+ {U,S,R},
+ case
+ mnesia:dirty_index_match_object(Table_Pubsub_Item,
+ #pubsub_item_dev{
+ id = {Publisher_ItemId, NodeIdx},
+ nodeidx = NodeIdx,
+ _ = '_'
+ },
+ nodeidx)
+ of
+ [#pubsub_item_dev{creation = {_DateTime, {U,S,_}}} = Pubsub_Item] ->
+ mnesia:dirty_delete_object(Table_Pubsub_Item, Pubsub_Item);
+ _ ->
+ {Node_ItemIds,
+ [Publisher_ItemId | Unpurged_ItemIds],
+ Purged_ItemIds}
+ end).
+
+
+%%
+-spec(filter_tempsub_subscriptions/5 ::
+(
+ Notify_Sub :: boolean() | 'none',
+ Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Offline_Resource :: xmpp_jid:resource_jid(),
+ Online_Resources :: boolean(),
+ Filtered_TempSub_Subscriptions :: {
+ Unexpired_Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Expired_Subscriptions :: [] | exmpp_pubsub:subscriptions()
+ })
+ -> Filtered_TempSub_Subscriptions :: {
+ Unexpired_Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Expired_Subscriptions :: [] | exmpp_pubsub:subscriptions()
+ }
+).
+
+filter_tempsub_subscriptions(_Notify_Sub, [] = _Subscriptions,
+ _Offline_Resource, _Online_Resources, Filtered_TempSub_Subscriptions) ->
+ Filtered_TempSub_Subscriptions;
+%%
+filter_tempsub_subscriptions(Notify_Sub,
+ [{Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions],
+ Offline_Resource, Online_Resources,
+ {Unexpired_Subscriptions, Expired_Subscriptions})
+ when ((Online_Resources == false
+ andalso
+ (Resource == undefined orelse Resource == Offline_Resource))
+ orelse
+ (Online_Resources == true
+ andalso
+ Resource == Offline_Resource)) ->
+ filter_tempsub_subscriptions(Notify_Sub, Subscriptions, Offline_Resource,
+ Online_Resources,
+ {Unexpired_Subscriptions,
+ _Expired_Subscriptions = case Notify_Sub of
+ true ->
+ [{Subscription_State, SubId, Resource, Subscription_Options}
+ | Expired_Subscriptions];
+ _ ->
+ Expired_Subscriptions
+ end});
+%%
+filter_tempsub_subscriptions(Notify_Sub, [Subscription | Subscriptions],
+ Offline_Resource, Online_Resources,
+ {Unexpired_Subscriptions, Expired_Subscriptions}) ->
+ filter_tempsub_subscriptions(Notify_Sub, Subscriptions, Offline_Resource,
+ Online_Resources,
+ {[Subscription | Unexpired_Subscriptions], Expired_Subscriptions}).
+
+%%
+-spec(filter_expired_subscriptions/5 ::
+(
+ Notify_Sub :: boolean() | 'none',
+ Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Offline_Resource :: xmpp_jid:resource_jid(),
+ Online_Resources :: boolean(),
+ Filtered_Expired_Subscriptions :: {
+ Unexpired_Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Expired_Subscriptions :: [] | exmpp_pubsub:subscriptions()
+ })
+ -> Filtered_Expired_Subscriptions :: {
+ Unexpired_Subscriptions :: [] | exmpp_pubsub:subscriptions(),
+ Expired_Subscriptions :: [] | exmpp_pubsub:subscriptions()
+ }
+).
+
+filter_expired_subscriptions(_Notify_Sub, [] = _Subscriptions, _Offline_Resource,
+ _Online_Resources, Filtered_Expired_Subscriptions) ->
+ Filtered_Expired_Subscriptions;
+%%
+filter_expired_subscriptions(Notify_Sub,
+ [{Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions],
+ Offline_Resource, Online_Resources,
+ {Unexpired_Subscriptions, Expired_Subscriptions})
+ when Subscription_Options =/= []
+ andalso ((Online_Resources == false
+ andalso
+ (Resource == undefined orelse Resource == Offline_Resource))
+ orelse
+ (Online_Resources == true
+ andalso
+ Resource == Offline_Resource)) ->
+ filter_expired_subscriptions(Notify_Sub, Subscriptions, Offline_Resource,
+ Online_Resources,
+ case get_value(Subscription_Options, 'expire') of
+ 'presence' ->
+ {Unexpired_Subscriptions,
+ _Expired_Subscriptions = case Notify_Sub of
+ true ->
+ [{Subscription_State, SubId, Resource, Subscription_Options}
+ | Expired_Subscriptions];
+ false ->
+ Expired_Subscriptions
+ end};
+ _ ->
+ {[{Subscription_State, SubId, Resource, Subscription_Options}
+ | Unexpired_Subscriptions],
+ Expired_Subscriptions}
+ end);
+%%
+filter_expired_subscriptions(Notify_Sub, [Subscription | Subscriptions],
+ Offline_Resource, Online_Resources,
+ {Unexpired_Subscriptions, Expired_Subscriptions}) ->
+ filter_expired_subscriptions(Notify_Sub, Subscriptions, Offline_Resource,
+ Online_Resources,
+ {[Subscription | Unexpired_Subscriptions], Expired_Subscriptions}).
+
+
+%%
+roster_process_item(Roster_Item, Server) ->
+ spawn(pubsub_groups, monitor_roster_groups, [Server, Roster_Item]),
+ Roster_Item.
+
+
+
+roster_in_subscription(Boolean, User, Server, Jid_Contact,
+ 'subscribed' = _Subscription_Type) ->
+ spawn(pubsub_groups, monitor_contacts2,
+ ['subscribed', {User, Server, undefined}, Jid_Contact]),
+ Boolean;
+roster_in_subscription(Boolean, _User, _Server, _JID, _SubscriptionType) ->
+ Boolean.
diff --git a/src/mod_pubsub_ng/pubsub_index_dev.erl b/src/mod_pubsub_ng/pubsub_index_dev.erl
new file mode 100644
index 000000000..72a8c82e6
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_index_dev.erl
@@ -0,0 +1,113 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%% important note:
+%% new/1 and free/2 MUST be called inside a transaction bloc
+
+-module(pubsub_index_dev).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub_dev.hrl").
+
+-export([
+ init/1,
+ new/2,
+ free/3
+]).
+
+-import(pubsub_db_mnesia,
+[
+ table/2
+]).
+
+
+%%
+-spec(init/1 ::
+(
+ Suffix::atom()) -> 'ok'
+).
+
+init(Suffix) ->
+ mnesia:create_table(table('pubsub_index_dev', Suffix),
+ [{disc_copies, [node()]},
+ {record_name, pubsub_index_dev},
+ {attributes, record_info(fields, pubsub_index_dev)}]).
+
+%%
+-spec(new/2 ::
+(
+ Suffix :: atom(),
+ Index :: exmpp_pubsub:index())
+ -> Idx::exmpp_pubsub:nodeIdx()
+).
+
+new(Suffix, Index) ->
+ case
+ mnesia:read(Table_Pubsub_Index = table('pubsub_index_dev', Suffix),
+ Index, write)
+ of
+ [#pubsub_index_dev{free = []} = Pubsub_Index] ->
+ mnesia:write(Table_Pubsub_Index,
+ Pubsub_Index#pubsub_index_dev{
+ last = Idx = Pubsub_Index#pubsub_index_dev.last + 1
+ },
+ write),
+ Idx;
+ [#pubsub_index_dev{free = [Idx|Free]} = Pubsub_Index] ->
+ mnesia:write(Table_Pubsub_Index,
+ Pubsub_Index#pubsub_index_dev{
+ free = Free
+ },
+ write),
+ Idx;
+ _ ->
+ mnesia:write(Table_Pubsub_Index,
+ #pubsub_index_dev{index = Index, last = 1, free = []}, write),
+ 1
+ end.
+
+%%
+-spec(free/3 ::
+(
+ Suffix :: atom(),
+ Index :: exmpp_pubsub:index(),
+ Idx :: exmpp_pubsub:nodeIdx())
+ -> 'ok'
+).
+
+free(Suffix, Index, Idx) ->
+ case
+ mnesia:read(Table_Pubsub_Index = table('pubsub_index_dev', Suffix),
+ Index, write)
+ of
+ [Pubsub_Index] ->
+ mnesia:write(Table_Pubsub_Index,
+ Pubsub_Index#pubsub_index_dev{
+ free = [Idx | Pubsub_Index#pubsub_index_dev.free]
+ },
+ write);
+ _ ->
+ ok
+ end.
diff --git a/src/mod_pubsub_ng/pubsub_options.erl b/src/mod_pubsub_ng/pubsub_options.erl
new file mode 100644
index 000000000..46c9c2dd4
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_options.erl
@@ -0,0 +1,3369 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_options).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+-import(xml,
+[
+ get_tag_cdata/1,
+ get_tag_attr_s/2,
+ remove_cdata/1
+]).
+
+
+-import(pubsub_tools,
+[
+ get_entity_roster/1,
+ is_jid/1
+]).
+
+
+%% --------------------------------------------------------------------
+%% Documentation / type definitions.
+%% --------------------------------------------------------------------
+
+%% Node Options
+% pubsub#node_config FORM_TYPE
+
+% Defined options values
+
+%-- Access Model --%
+-export_type([
+ access_model/0,
+ access_model_open/0,
+ access_model_presence/0,
+ access_model_roster/0,
+ access_model_authorize/0,
+ access_model_whitelist/0
+]).
+
+%% @type access_model_open() = 'open'.
+-type(access_model_open() :: 'open').
+
+%% @type access_model_presence() = 'presence'.
+-type(access_model_presence() :: 'presence').
+
+%% @type access_model_roster() = 'roster'.
+-type(access_model_roster() :: 'roster').
+
+%% @type access_model_authorize() = 'authorize'.
+-type(access_model_authorize() :: 'authorize').
+
+%% @type access_model_whitelist() = 'whitelist'.
+-type(access_model_whitelist() :: 'whitelist').
+
+%% @type access_model() = Open | Presence | Roster | Authorize | Whitelist
+%% Open = pubsub_options:access_model_open()
+%% Presence = pubsub_options:access_model_presence()
+%% Roster = pubsub_options:access_model_roster()
+%% Authorize = pubsub_options:access_model_authorize()
+%% Whitelist = pubsub_options:access_model_whitelist().
+-type(access_model() :: pubsub_options:access_model_open()
+ | pubsub_options:access_model_presence()
+ | pubsub_options:access_model_roster()
+ | pubsub_options:access_model_authorize()
+ | pubsub_options:access_model_whitelist()
+).
+
+%%
+
+%-- Children_Association_Policy --%
+-export_type([
+ children_association_policy/0,
+ %%
+ children_association_policy_all/0,
+ children_association_policy_owners/0,
+ children_association_policy_whitelist/0
+]).
+
+%% @type children_association_policy_all() = 'all'.
+-type(children_association_policy_all() :: 'all').
+
+%% @type children_association_policy_owners() = 'owners'.
+-type(children_association_policy_owners() :: 'owners').
+
+%% @type children_association_policy_whitelist() = 'whitelist'.
+-type(children_association_policy_whitelist() :: 'whitelist').
+
+%% @type children_association_policy() = All | Owners | Whitelist
+%% All = pubsub_options:children_association_policy_all()
+%% Owners = pubsub_options:children_association_policy_owners()
+%% Whitelist = pubsub_options:children_association_policy_whitelist().
+-type(children_association_policy()
+ :: pubsub_options:children_association_policy_all()
+ | pubsub_options:children_association_policy_owners()
+ | pubsub_options:children_association_policy_whitelist()
+).
+
+%%
+
+%-- ItemReply -- %%
+-export_type([
+ item_reply/0,
+ item_reply_owner/0,
+ item_reply_publisher/0
+]).
+
+%% @type(item_reply_owner() = 'owner').
+-type(item_reply_owner() :: 'owner').
+
+%% @type(item_reply_publisher() = 'publisher').
+-type(item_reply_publisher() :: 'publisher').
+
+%% @type(item_reply() = Owner | Publisher).
+-type(item_reply() :: pubsub_options:item_reply_owner()
+ | pubsub_options:item_reply_publisher()
+).
+
+
+-export_type([
+ language/0,
+ description/0,
+ title/0
+]).
+
+-type(language() :: binary()).
+
+-type(description() :: binary()).
+
+-type(title() :: binary()).
+
+%%
+
+%-- Node types --%%
+
+-export_type([
+ node_type/0,
+ node_type_leaf/0,
+ node_type_collection/0
+]).
+
+%% @type node_type_collection() = 'collection'.
+-type(node_type_collection() :: 'collection').
+
+%% @type node_type_leaf() = 'leaf'.
+-type(node_type_leaf() :: 'leaf').
+
+%% @type node_type() = Leaf | Collection
+%% Leaf = pubsub_options:node_type_leaf()
+%% Collection = pubsub_options:node_type_collection()
+-type(node_type() :: pubsub_options:node_type_leaf()
+ | pubsub_options:node_type_collection()
+).
+
+%%
+
+%-- Notification Types --%%
+-export_type([
+ notification_type/0,
+ notification_type_chat/0,
+ notification_type_headline/0,
+ notification_type_normal/0
+]).
+
+-type(notification_type_chat() :: 'chat').
+
+-type(notification_type_headline() :: 'headline').
+
+-type(notification_type_normal() :: 'normal').
+
+-type(notification_type()
+ :: pubsub_options:notification_type_headline()
+ | pubsub_options:notification_type_normal()
+ %| pubsub_options:notification_type_chat()
+).
+
+%%
+
+%-- Publish Model --%
+-export_type([
+ publish_model/0,
+ publish_model_publishers/0,
+ publish_model_subscribers/0,
+ publish_model_open/0
+]).
+
+%% @type publish_model_publishers() = 'publishers'.
+-type(publish_model_publishers() :: 'publishers').
+
+%% @type publish_model_subscribers() = 'subscribers'.
+-type(publish_model_subscribers() :: 'subscribers').
+
+%% @type publish_model_open() = 'open'.
+-type(publish_model_open() :: 'open').
+
+%% @type publish_model() = Publishers | Subscribers | Open
+%% Publishers = pubsub_options:publish_model_publishers()
+%% Subscribers = pubsub_options:publish_model_subscribers()
+%% Open = pubsub_options:publish_model_open()
+-type(publish_model() :: pubsub_options:publish_model_publishers()
+ | pubsub_options:publish_model_subscribers()
+ | pubsub_options:publish_model_open()
+).
+
+
+-export_type([
+ roster_group/0,
+ roster_groups/0,
+ roster_groups_allowed/0,
+ rosters_groups_allowed/0
+]).
+
+-type(roster_group() :: binary()).
+-type(roster_groups() :: [pubsub_options:roster_group()]).
+-type(roster_groups_allowed()
+ :: {Node_Owner::xmpp_jid:usr_bare(),
+ Roster_Groups::pubsub_options:roster_groups()}
+).
+-type(rosters_groups_allowed()
+ :: [Roster_Group_Allowed::pubsub_options:roster_groups_allowed()]
+).
+
+%%
+
+%-- Send Last Published Item --%
+-export_type([
+ send_last_published_item/0,
+ %%
+ send_last_published_item_never/0,
+ send_last_published_item_on_sub/0,
+ send_last_published_item_on_sub_and_presence/0
+]).
+
+%% @type send_last_published_item_never() = 'never'.
+-type(send_last_published_item_never() :: 'never').
+
+%% @type send_last_published_item_on_sub() = 'on_sub'.
+-type(send_last_published_item_on_sub() :: 'on_sub').
+
+%% @type send_last_published_item_on_sub_and_presence() = 'on_sub_and_presence'.
+-type(send_last_published_item_on_sub_and_presence() :: 'on_sub_and_presence').
+
+%% @type send_last_published_item() = Never | On_Sub | On_Sub_And_Presence
+%% Never = pubsub_options:send_last_published_item_never()
+%% On_Sub = pubsub_options:send_last_published_item_on_sub()
+%% On_Sub_And_Presence = pubsub_options:send_last_published_item_on_sub_and_presence().
+-type(send_last_published_item()
+ :: pubsub_options:send_last_published_item_never()
+ | pubsub_options:send_last_published_item_on_sub()
+ | pubsub_options:send_last_published_item_on_sub_and_presence()
+).
+
+%%
+
+
+-export_type([
+ option_node_access_model/0,
+ option_node_children_association_policy/0,
+ option_node_children_association_whitelist/0,
+ option_node_children/0,
+ option_node_children_max/0,
+ option_node_collections/0,
+ option_node_children_max_collections/0,
+ option_node_children_max_multi_collections/0,
+ option_node_contact/0,
+ option_node_deliver_notifications/0,
+ option_node_deliver_payloads/0,
+ option_node_description/0,
+ option_node_item_expire/0,
+ option_node_itemreply/0,
+ option_node_language/0,
+ option_node_max_items/0,
+ option_node_max_payload_size/0,
+ option_node_node_type_leaf/0,
+ option_node_node_type_collection/0,
+ option_node_node_type/0,
+ option_node_notification_type/0,
+ option_node_notify_config/0,
+ option_node_notify_delete/0,
+ option_node_notify_retract/0,
+ option_node_notify_sub/0,
+ option_node_persist_items/0,
+ option_node_presence_based_delivery/0,
+ option_node_publish_model/0,
+ option_node_purge_offline/0,
+ option_node_roster_groups_allowed/0,
+ option_node_send_last_published_item/0,
+ option_node_tempsub/0,
+ option_node_subscribe/0,
+ option_node_title/0,
+ option_node_type/0
+]).
+
+-type(option_node_access_model() ::
+ {Key :: 'access_model',
+ Value :: pubsub_options:access_model()}
+).
+
+%-type(option_node_body_xslt() ::
+% {Key :: 'body_xslt(',
+% Value :: binary()}
+%).
+
+-type(option_node_children_association_policy() ::
+ {Key :: 'children_association_policy',
+ Value :: pubsub_options:children_association_policy()}
+).
+
+-type(option_node_children_association_whitelist() ::
+ {Key :: 'children_association_whitelist',
+ Value :: [Jid_Entity::xmpp_jid:raw_jid_entity_bare()]}
+).
+
+-type(option_node_children() ::
+ {Key :: 'children',
+ Value :: [NodeId::exmpp_pubsub:nodeId()]}
+).
+
+-type(option_node_children_max() ::
+ {Key :: 'children_max',
+ Value :: undefined | non_neg_integer()}
+).
+
+-type(option_node_children_max_collections() ::
+ {Key :: 'children_max',
+ Value :: undefined | 0..1}
+).
+
+-type(option_node_children_max_multi_collections() ::
+ {Key :: 'children_max',
+ Value :: undefined | non_neg_integer()}
+).
+
+
+-type(option_node_collections() ::
+ {Key :: 'collection',
+ Value :: [NodeId::exmpp_pubsub:nodeId()]}
+).
+
+-type(option_node_contact() ::
+ {Key :: 'contact',
+ Value :: [Jid_Entity::xmpp_jid:raw_jid_entity()]}
+).
+
+%-type(option_node_dataform_xslt() ::
+% {Key :: 'dataform_xslt',
+% Value :: binary()}
+%).
+
+-type(option_node_deliver_notifications() ::
+ {Key :: 'deliver_notifications',
+ Value :: boolean()}
+).
+
+-type(option_node_deliver_payloads() ::
+ {Key :: 'deliver_payloads',
+ Value :: boolean()}
+).
+
+-type(option_node_description() ::
+ {Key :: 'description',
+ Value :: undefined | pubsub_options:description()}
+).
+
+-type(option_node_item_expire() ::
+ {Key :: 'item_expire',
+ Value :: undefined | non_neg_integer()}
+).
+
+-type(option_node_itemreply() ::
+ {Key :: 'itemreply',
+ Value :: pubsub_options:item_reply()}
+).
+
+-type(option_node_language() ::
+ {Key :: 'language',
+ Value :: undefined | pubsub_options:language()}
+).
+
+-type(option_node_max_items() ::
+ {Key :: 'max_items',
+ Value :: undefined | non_neg_integer()}
+).
+
+-type(option_node_max_payload_size() ::
+ {Key :: 'max_payload_size',
+ Value :: undefined | non_neg_integer()}
+).
+
+-type(option_node_node_type_leaf() ::
+ {Key :: 'node_type',
+ Value :: pubsub_options:node_type_leaf()}
+).
+
+-type(option_node_node_type_collection() ::
+ {Key :: 'node_type',
+ Value :: pubsub_options:node_type_collection()}
+).
+
+-type(option_node_node_type()
+ :: pubsub_options:option_node_node_type_leaf()
+ | pubsub_options:option_node_node_type_collection()
+).
+
+-type(option_node_notification_type() ::
+ {Key :: 'notification_type',
+ Value :: pubsub_options:notification_type()}
+).
+
+-type(option_node_notify_config() ::
+ {Key :: 'notify_config',
+ Value :: boolean()}
+).
+
+-type(option_node_notify_delete() ::
+ {Key :: 'notify_delete',
+ Value :: boolean()}
+).
+
+-type(option_node_notify_retract() ::
+ {Key :: 'notify_retract',
+ Value :: boolean()}
+).
+
+-type(option_node_notify_sub() ::
+ {Key :: 'notify_sub',
+ Value :: boolean()}
+).
+
+-type(option_node_persist_items() ::
+ {Key :: 'persist_items',
+ Value :: boolean()}
+).
+
+-type(option_node_presence_based_delivery() ::
+ {Key :: 'presence_based_delivery',
+ Value :: boolean()}
+).
+
+-type(option_node_publish_model() ::
+ {Key :: 'publish_model',
+ Value :: pubsub_options:publish_model()}
+).
+
+-type(option_node_purge_offline() ::
+ {Key :: 'purge_offline',
+ Value :: boolean()}
+).
+
+-type(option_node_roster_groups_allowed() ::
+ {Key :: 'roster_groups_allowed',
+ Value :: pubsub_options:rosters_groups_allowed()}
+).
+
+-type(option_node_send_last_published_item() ::
+ {Key :: 'send_last_published_item',
+ Value :: pubsub_options:send_last_published_item()}
+).
+
+-type(option_node_tempsub() ::
+ {Key :: 'tempsub',
+ Value :: boolean()}
+).
+
+-type(option_node_subscribe() ::
+ {Key :: 'subscribe',
+ Value :: boolean()}
+).
+
+-type(option_node_title() ::
+ {Key :: 'title',
+ Value :: undefined | pubsub_options:title()}
+).
+
+-type(option_node_type() ::
+ {Key :: 'type',
+ Value :: undefined | binary()}
+).
+
+
+
+%-- Node Option Type (local or remote user ; leaf or collection node) --%
+-export_type([
+ option_node/0,
+ options_node/0,
+ %%
+ option_node_leaf/0,
+ option_node_collection/0,
+ %%
+ options_node_leaf/0,
+ options_node_collection/0
+]).
+
+-type(option_node_leaf()
+ :: pubsub_options:option_node_access_model()
+ | pubsub_options:option_node_collections()
+ | pubsub_options:option_node_contact()
+ | pubsub_options:option_node_deliver_notifications()
+ | pubsub_options:option_node_deliver_payloads()
+ | pubsub_options:option_node_description()
+ | pubsub_options:option_node_item_expire()
+ | pubsub_options:option_node_itemreply()
+ | pubsub_options:option_node_language()
+ | pubsub_options:option_node_max_items()
+ | pubsub_options:option_node_max_payload_size()
+ | pubsub_options:option_node_node_type_leaf()
+ | pubsub_options:option_node_notification_type()
+ | pubsub_options:option_node_notify_config()
+ | pubsub_options:option_node_notify_delete()
+ | pubsub_options:option_node_notify_retract()
+ | pubsub_options:option_node_notify_sub()
+ | pubsub_options:option_node_persist_items()
+ | pubsub_options:option_node_presence_based_delivery()
+ | pubsub_options:option_node_publish_model()
+ | pubsub_options:option_node_purge_offline()
+ | pubsub_options:option_node_roster_groups_allowed()
+ | pubsub_options:option_node_send_last_published_item()
+ | pubsub_options:option_node_tempsub()
+ | pubsub_options:option_node_subscribe()
+ | pubsub_options:option_node_title()
+ | pubsub_options:option_node_type()
+).
+
+%%
+-type(option_node_collection()
+ :: pubsub_options:option_node_access_model()
+ | pubsub_options:option_node_children_association_policy()
+ | pubsub_options:option_node_children_association_whitelist()
+ | pubsub_options:option_node_children()
+ | pubsub_options:option_node_children_max()
+ | pubsub_options:option_node_collections()
+ | pubsub_options:option_node_contact()
+ | pubsub_options:option_node_deliver_notifications()
+ | pubsub_options:option_node_deliver_payloads()
+ | pubsub_options:option_node_description()
+ | pubsub_options:option_node_item_expire()
+ | pubsub_options:option_node_itemreply()
+ | pubsub_options:option_node_language()
+ | pubsub_options:option_node_max_items()
+ | pubsub_options:option_node_max_payload_size()
+ | pubsub_options:option_node_node_type_collection()
+ | pubsub_options:option_node_notification_type()
+ | pubsub_options:option_node_notify_config()
+ | pubsub_options:option_node_notify_delete()
+ | pubsub_options:option_node_notify_retract()
+ | pubsub_options:option_node_notify_sub()
+ | pubsub_options:option_node_persist_items()
+ | pubsub_options:option_node_presence_based_delivery()
+ | pubsub_options:option_node_publish_model()
+ | pubsub_options:option_node_purge_offline()
+ | pubsub_options:option_node_roster_groups_allowed()
+ | pubsub_options:option_node_send_last_published_item()
+ | pubsub_options:option_node_tempsub()
+ | pubsub_options:option_node_subscribe()
+ | pubsub_options:option_node_title()
+ | pubsub_options:option_node_type()
+).
+
+%%
+-type(option_node()
+ :: pubsub_options:option_node_leaf()
+ | pubsub_options:option_node_collection()
+).
+
+%%
+-type(options_node_leaf()
+ :: [pubsub_options:option_node_leaf(),...]
+).
+
+%%
+-type(options_node_collection()
+ :: [pubsub_options:option_node_collection(),...]
+).
+
+%%
+-type(options_node()
+ :: pubsub_options:options_node_leaf()
+ | pubsub_options:options_node_collection()
+).
+
+%-- Publish-Options --%
+%% pubsub#publish-options FORM_TYPE
+-export_type([
+ option_item_access_model/0,
+ option_item_deliver_notifications/0,
+ option_item_deliver_payloads/0,
+ option_item_item_expire/0,
+ option_item_itemreply/0,
+%%option_item_max_payload_size/0,
+ option_item_notification_type/0,
+ option_item_notify_config/0,
+ option_item_notify_retract/0,
+ option_item_persist_items/0,
+ option_item_presence_based_delivery/0,
+ option_item_publish_model/0,
+ option_item_purge_offline/0,
+ option_item_roster_groups_allowed/0,
+ option_item_send_last_published_item/0,
+ option_item_type/0
+]).
+
+-type(option_item_access_model() ::
+ {Key :: 'access_model',
+ Value :: pubsub_options:access_model()}
+).
+
+-type(option_item_deliver_notifications() ::
+ {Key :: 'deliver_notifications',
+ Value :: boolean()}
+).
+
+-type(option_item_deliver_payloads() ::
+ {Key :: 'deliver_payloads',
+ Value :: boolean()}
+).
+
+-type(option_item_item_expire() ::
+ {Key :: 'item_expire',
+ Value :: undefined | non_neg_integer()}
+).
+
+-type(option_item_itemreply() ::
+ {Key :: 'itemreply',
+ Value :: pubsub_options:item_reply()}
+).
+
+%-type(option_item_max_payload_size() ::
+% {Key :: 'max_payload_size',
+% Value :: undefined | non_neg_integer()}
+%).
+
+-type(option_item_notification_type() ::
+ {Key :: 'notification_type',
+ Value :: pubsub_options:notification_type()}
+).
+
+-type(option_item_notify_config() ::
+ {Key :: 'notify_config',
+ Value :: boolean()}
+).
+
+-type(option_item_notify_retract() ::
+ {Key :: 'notify_retract',
+ Value :: boolean()}
+).
+
+-type(option_item_persist_items() ::
+ {Key :: 'persist_items',
+ Value :: boolean()}
+).
+
+-type(option_item_presence_based_delivery() ::
+ {Key :: 'presence_based_delivery',
+ Value :: boolean()}
+).
+
+-type(option_item_publish_model() ::
+ {Key :: 'publish_model',
+ Value :: pubsub_options:publish_model()}
+).
+
+-type(option_item_purge_offline() ::
+ {Key :: 'purge_offline',
+ Value :: boolean()}
+).
+
+-type(option_item_roster_groups_allowed() ::
+ {Key :: 'roster_groups_allowed',
+ Value :: pubsub_options:rosters_groups_allowed()}
+).
+
+-type(option_item_send_last_published_item() ::
+ {Key :: 'send_last_published_item',
+ Value :: pubsub_options:send_last_published_item()}
+).
+
+-type(option_item_type() ::
+ {Key :: 'type',
+ Value :: undefined | binary()}
+).
+
+-export_type([
+ option_item/0,
+ options_item/0
+]).
+
+-type(option_item()
+ :: pubsub_options:option_item_access_model()
+ | pubsub_options:option_item_deliver_notifications()
+ | pubsub_options:option_item_deliver_payloads()
+ | pubsub_options:option_item_item_expire()
+ | pubsub_options:option_item_itemreply()
+%% | pubsub_options:option_item_max_payload_size()
+ | pubsub_options:option_item_notification_type()
+ | pubsub_options:option_item_notify_config()
+ | pubsub_options:option_item_notify_retract()
+ | pubsub_options:option_item_persist_items()
+ | pubsub_options:option_item_presence_based_delivery()
+ | pubsub_options:option_item_publish_model()
+ | pubsub_options:option_item_purge_offline()
+ | pubsub_options:option_item_roster_groups_allowed()
+ | pubsub_options:option_item_send_last_published_item()
+ | pubsub_options:option_item_type()
+).
+
+%%
+-type(options_item()
+ :: [pubsub_options:option_item(),...]
+).
+
+
+%-- Subscription Options --%%
+-export_type([
+ option_subscription_deliver/0,
+ option_subscription_expire/0,
+ option_subscription_expire_presence/0,
+ option_subscription_expire_datetime/0,
+ option_subscription_include_body/0,
+ option_subscription_show_values/0,
+ option_subscription_subscription_type/0,
+ option_subscription_subscription_depth/0
+]).
+
+%%
+-type(option_subscription_deliver() ::
+ {Key :: 'deliver',
+ Value :: boolean()}
+).
+
+%-type(option_subscription_digest() ::
+% {Key :: 'digest',
+% Value :: boolean()}
+%).
+
+%-type(option_subscription_digest_frequency() ::
+% {Key :: 'digest_frequency',
+% Value :: undefined | non_neg_integer()}
+%).
+
+-type(option_subscription_expire_presence() ::
+ {Key :: 'expire',
+ Value :: undefined | 'presence'}
+).
+
+-type(option_subscription_expire_datetime() ::
+ {Key :: 'expire',
+ Value :: undefined | erlang:timestamp()}
+).
+
+-type(option_subscription_expire()
+ :: pubsub_options:option_subscription_expire_presence()
+ | pubsub_options:option_subscription_expire_datetime()
+).
+
+-type(option_subscription_include_body() ::
+ {Key :: 'include_body',
+ Value :: boolean()}
+).
+
+-type(option_subscription_show_values() ::
+ {Key :: 'show-values',
+ Value :: ['away' | 'chat' | 'dnd' | 'online' | 'xa']}
+).
+
+-type(option_subscription_subscription_type() ::
+ {Key :: 'subscription_type',
+ Value :: 'all' | 'items' | 'nodes'}
+).
+
+-type(option_subscription_subscription_depth() ::
+ {Key :: 'subscription_depth',
+ Value :: 'all' | exmpp_pubsub:level()}
+).
+
+
+-export_type([
+ option_subscription/0,
+ %%
+ option_subscription_leaf/0,
+ option_subscription_collection/0,
+ %%
+ option_subscription_leaf_local/0,
+ option_subscription_leaf_remote/0,
+ %%
+ option_subscription_collection_local/0,
+ option_subscription_collection_remote/0
+]).
+
+%%
+-type(option_subscription_leaf_local()
+ :: pubsub_options:option_subscription_deliver()
+ | pubsub_options:option_subscription_expire()
+ % | pubsub_options:option_subscription_include_body()
+ | pubsub_options:option_subscription_show_values()
+).
+
+%%
+-type(option_subscription_leaf_remote()
+ :: pubsub_options:option_subscription_deliver()
+ % | pubsub_options:option_subscription_expire_datetime()
+ % | pubsub_options:option_subscription_include_body()
+).
+
+%%
+-type(option_subscription_collection_local()
+ :: pubsub_options:option_subscription_deliver()
+ | pubsub_options:option_subscription_expire()
+ | pubsub_options:option_subscription_expire_presence()
+ | pubsub_options:option_subscription_expire_datetime()
+ %| pubsub_options:option_subscription_include_body()
+ | pubsub_options:option_subscription_show_values()
+ | pubsub_options:option_subscription_subscription_type()
+ | pubsub_options:option_subscription_subscription_depth()
+).
+
+%%
+-type(option_subscription_collection_remote()
+ :: pubsub_options:option_subscription_deliver()
+ | pubsub_options:option_subscription_expire_datetime()
+ | pubsub_options:option_subscription_include_body()
+ | pubsub_options:option_subscription_subscription_type()
+ | pubsub_options:option_subscription_subscription_depth()
+).
+
+%%
+-type(option_subscription_leaf()
+ :: pubsub_options:option_subscription_leaf_local()
+ | pubsub_options:option_subscription_leaf_remote()
+).
+
+%%
+-type(option_subscription_collection()
+ :: pubsub_options:option_subscription_collection_local()
+ | pubsub_options:option_subscription_collection_remote()
+).
+
+
+%%
+-type(option_subscription()
+ :: pubsub_options:option_subscription_leaf()
+ | pubsub_options:option_subscription_collection()
+).
+
+
+-export_type([
+ options_subscription/0,
+ %%
+ options_subscription_leaf/0,
+ options_subscription_collection/0,
+ %%
+ options_subscription_local/0,
+ options_subscription_remote/0,
+ %%
+ options_subscription_leaf_local/0,
+ options_subscription_leaf_remote/0,
+ %%
+ options_subscription_collection_local/0,
+ options_subscription_collection_remote/0
+]).
+
+%%
+-type(options_subscription_leaf_local()
+ :: [pubsub_options:options_subscription_leaf_local(),...]
+).
+
+%%
+-type(options_subscription_leaf_remote()
+ :: [pubsub_options:options_subscription_leaf_remote(),...]
+).
+
+%%
+-type(options_subscription_collection_local()
+ :: [pubsub_options:options_subscription_collection_local(),...]
+).
+
+%%
+-type(options_subscription_collection_remote()
+ :: [pubsub_options:options_subscription_collection_remote(),...]
+).
+
+%%
+-type(options_subscription_leaf()
+ :: pubsub_options:options_subscription_leaf_local()
+ | pubsub_options:options_subscription_leaf_remote()
+).
+
+%%
+-type(options_subscription_collection()
+ :: pubsub_options:options_subscription_collection_local()
+ | pubsub_options:options_subscription_collection_remote()
+).
+
+%%
+-type(options_subscription_local()
+ :: pubsub_options:options_subscription_leaf_local()
+ | pubsub_options:options_subscription_collection_local()
+).
+
+%%
+-type(options_subscription_remote()
+ :: pubsub_options:options_subscription_leaf_remote()
+ | pubsub_options:options_subscription_collection_remote()
+).
+
+-type(options_subscription()
+ :: pubsub_options:options_subscription_leaf()
+ | pubsub_options:options_subscription_collection()
+).
+
+%% --------------------------------------------------------------------
+%% Functions.
+%% --------------------------------------------------------------------
+
+
+xmlns(Xmlel) ->
+ get_tag_attr_s(<<"xmlns">>, Xmlel).
+
+
+-define(Is_Xmlel_Field(Xmlel),
+(
+ Xmlel#xmlel.name == <<"field">>
+)).
+
+-define(Is_Xmlel_Value(Xmlel),
+(
+ Xmlel#xmlel.name == <<"value">> andalso
+ Xmlel#xmlel.attrs == []
+)).
+
+-define(Is_Xmlel_X(Xmlel),
+(
+ Xmlel#xmlel.name == <<"x">>
+)).
+
+-define(Is_Xmlel_X_Submit(Xmlel),
+(
+ %Xmlel#xmlel.ns == 'jabber:x:data' andalso
+ Xmlel#xmlel.name == <<"x">> % andalso
+ % Xmlel#xmlel.attrs == [#xmlattr{name = <<"type">>, value = <<"submit">>}]
+)).
+
+-define(Is_Xmlel_X_Form(Xmlel),
+(
+ %Xmlel#xmlel.ns == 'jabber:x:data' andalso
+ Xmlel#xmlel.name == <<"x">> andalso
+ Xmlel#xmlel.attrs == [{<<"type">>, <<"form">>}]
+)).
+
+
+
+%%
+%%
+-define(Is_Default_Subscribe_Option(Option),
+(
+ Option == {'deliver', true}
+ orelse
+ Option == {'expire', undefined}
+ orelse
+ Option == {'include_body', false}
+ orelse
+ Option == {'show-values', ['away' , 'chat' , 'dnd' , 'online' , 'xa']}
+)).
+
+
+%%
+-spec(trim_subscription_options/1 ::
+(
+ Subscription_Options::pubsub_options:options_subscription())
+ -> Subscription_Options::pubsub_options:options_subscription()
+).
+
+trim_subscription_options(Subscription_Options) ->
+ trim_subscription_options(Subscription_Options, []).
+
+%%
+-spec(trim_subscription_options/2 ::
+(
+ Options :: [] | pubsub_options:options_subscription(),
+ Subscription_Options :: [] | pubsub_options:options_subscription())
+ -> Subscription_Options :: [] | pubsub_options:options_subscription()
+).
+
+trim_subscription_options([] = _Options, Subscription_Options) ->
+ Subscription_Options;
+%%
+trim_subscription_options([Option | Options], Subscription_Options)
+ when ?Is_Default_Subscribe_Option(Option) ->
+ trim_subscription_options(Options, Subscription_Options);
+%%
+trim_subscription_options([Option | Options], Subscription_Options) ->
+ trim_subscription_options(Options, [Option | Subscription_Options]).
+
+%%
+-spec(parse_xmlel_x/5 ::
+(
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Form_Type :: 'node_config' | 'publish-options',
+ Xmlels :: [Xmlel_X::xmlel(),...]
+ | [Xmlel::xmlel()]
+ | [])
+ -> {ok,
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_item()}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+parse_xmlel_x(Plugin, _Pubsub_Features, _Entity, 'node_config', []) ->
+ {ok, Plugin:node_options('leaf')};
+parse_xmlel_x(_Plugin, _Pubsub_Features, _Entity, 'publish-options', []) ->
+ {ok, []};
+%%
+parse_xmlel_x(Plugin, Pubsub_Features, {U,S,_} = _Entity, Form_Type, [Xmlel_X])
+ when ?Is_Xmlel_X(Xmlel_X) ->
+ case
+ {xmlns(Xmlel_X),
+ options_type(remove_cdata(Xmlel_X#xmlel.children), Pubsub_Features)}
+ of
+ {?NS_XDATA, {ok, 'node_config' = Form_Type, Node_Type, Xmlels}} ->
+ parse_xmlels_field(Form_Type, Node_Type, Pubsub_Features,
+ {U,S,undefined}, Xmlels, Plugin:node_options(Node_Type));
+ %%
+ {?NS_XDATA, {ok, 'publish-options' = Form_Type, Node_Type, Xmlels}} ->
+ parse_xmlels_field(Form_Type, Node_Type, Pubsub_Features,
+ {U,S,undefined}, Xmlels, Plugin:item_options());
+ %%
+ {?NS_XDATA, Error} ->
+ Error;
+ _ ->
+ {error, 'invalid-options'}
+ end;
+parse_xmlel_x(_Plugin, _Pubsub_Features, _Entity, _Form_Type, _Xmlels) ->
+ {error, 'invalid-options'}.
+
+%%
+-spec(parse_xmlel_x/7 ::
+(
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Entity_Type :: 'local' | 'remote',
+ Form_Type :: 'subscribe_options',
+ Node_Type :: 'leaf' | 'collection',
+ Xmlels :: [Xmlel_X::xmlel(),...]
+ | [Xmlel::xmlel()]
+ | [])
+ -> {ok,
+ Subscription_Options :: [] | pubsub_options:options_subscription_leaf()}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'}
+).
+
+parse_xmlel_x(Plugin, _Pubsub_Features, _Entity, Entity_Type, 'subscribe_options',
+ Node_Type, []) ->
+ {ok, Plugin:subscription_options(Entity_Type, Node_Type)};
+%%
+parse_xmlel_x(Plugin, Pubsub_Features, {U,S,_} = _Entity, Entity_Type,
+ 'subscribe_options' = Form_Type, Node_Type, [Xmlel_X])
+ when ?Is_Xmlel_X(Xmlel_X) ->
+ case
+ {xmlns(Xmlel_X),
+ options_type(remove_cdata(Xmlel_X#xmlel.children), 'subscribe_options', [])}
+ of
+ {?NS_XDATA, {ok, 'subscribe_options', Xmlels}} ->
+ parse_xmlels_field(Form_Type, Node_Type, Pubsub_Features,
+ {U,S,undefined}, Entity_Type, Xmlels,
+ Plugin:default_subscription_options(Entity_Type, Node_Type));
+ {?NS_XDATA, Error} ->
+ Error;
+ _ ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_xmlel_x(_Plugin, _Pubsub_Features, _Entity, _Entity_Type, 'subscribe_options',
+ _Node_Type, _Xmlels) ->
+ {error, 'invalid-options'}.
+
+%%
+-spec(options_type/3 ::
+(
+ Xmlels :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()],
+ Form_Type :: 'subscribe_options',
+ Xmlels_Fields :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()])
+ -> {ok,
+ Form_Type :: 'subscribe_options',
+ Xmlels_Field :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()]}
+ %%%
+ | {error, 'not-acceptable'}
+).
+
+options_type([] = _Xmlels_Field, 'subscribe_options', _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+options_type([Xmlel_Field | Xmlels_Field], 'subscribe_options', Xmlels)
+ when ?Is_Xmlel_Field(Xmlel_Field) ->
+ case {xmlns(Xmlel_Field), get_tag_attr_s(<<"var">>, Xmlel_Field)} of
+ {_, <<>>} ->
+ {error, 'not-acceptable'};
+ {?NS_XDATA, <<"FORM_TYPE">>} ->
+ case remove_cdata(Xmlel_Field#xmlel.children) of
+ [Xmlel_Value] when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, ?NS_PUBSUB_SUBSCRIBE_OPTIONS} ->
+ {ok, 'subscribe_options', Xmlels ++ Xmlels_Field};
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ {?NS_XDATA, _} ->
+ options_type(Xmlels_Field, 'subscribe_options',
+ [Xmlel_Field | Xmlels]);
+ _ ->
+ {error, 'not-acceptable'}
+ end.
+
+%%
+-spec(options_type/2 ::
+(
+ Xmlels :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()],
+ Pubsub_Features :: exmpp_pubsub:pubsub_features())
+ -> {ok,
+ Form_Type :: 'node_config' | 'publish-options',
+ Node_Type :: 'leaf' | 'collection',
+ Xmlels_Field :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()]}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+).
+
+options_type(Xmlels, Pubsub_Features) ->
+ options_type(Xmlels,
+ lists:member(<<"collections">>, Pubsub_Features), undefined, undefined, []).
+
+%%
+-spec(options_type/5 ::
+(
+ Xmlels :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()],
+ Collections :: boolean(),
+ Form_Type :: undefined | 'node_config' | 'publish-options',
+ Node_Type :: undefined | 'leaf' | 'collection',
+ Xmlels_Fields :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()])
+ -> {ok,
+ Form_Type :: 'node_config' | 'publish-options',
+ Node_Type :: 'leaf' | 'collection',
+ Xmlels_Field :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()]}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'feature-not-implemented', 'collections'}
+).
+
+options_type([] = _Xmlels, _Collections, Form_Type, undefined = _Node_Type,
+ Xmlels_Field)
+ when Form_Type =/= undefined ->
+ {ok, Form_Type, 'leaf', Xmlels_Field};
+%%
+options_type([] = _Xmlels, _Collections, Form_Type, Node_Type, Xmlels_Field)
+ when Form_Type =/= undefined ->
+ {ok, Form_Type, Node_Type, Xmlels_Field};
+%%
+options_type([] = _Xmlels, _Collections, undefined = _Form_Type, _Node_Type,
+ _Xmlels_Field) ->
+ {error, 'bad-options'};
+%%
+options_type(Xmlels, _Collections, Form_Type, Node_Type, Xmlels_Field)
+ when Form_Type =/= undefined andalso Node_Type =/= undefined ->
+ {ok, Form_Type, Node_Type, Xmlels ++ Xmlels_Field};
+%%
+options_type([Xmlel_Field | Xmlels_Field], Collections, Form_Type, Node_Type,
+ Xmlels)
+ when ?Is_Xmlel_Field(Xmlel_Field) ->
+ case {xmlns(Xmlel_Field), get_tag_attr_s(<<"var">>, Xmlel_Field)} of
+ {_, <<>>} ->
+ {error, 'not-acceptable'};
+ {?NS_XDATA, <<"FORM_TYPE">>} ->
+ case remove_cdata(Xmlel_Field#xmlel.children) of
+ [Xmlel_Value] when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, ?NS_PUBSUB_NODE_CONFIG} ->
+ options_type(Xmlels_Field, Collections, 'node_config',
+ Node_Type, Xmlels);
+ {?NS_XDATA, ?NS_PUBSUB_PUBLISH_OPTIONS} ->
+ options_type(Xmlels_Field, Collections,
+ 'publish-options', Node_Type, Xmlels);
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ {?NS_XDATA, <<"pubsub#node_type">>} ->
+ case remove_cdata(Xmlel_Field#xmlel.children) of
+ [Xmlel_Value] when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<"leaf">>} ->
+ options_type(Xmlels_Field, Collections, Form_Type,
+ 'leaf', Xmlels);
+ {?NS_XDATA, <<"collection">>} ->
+ case Collections of
+ true ->
+ options_type(Xmlels_Field, Collections,
+ Form_Type, 'collection', Xmlels);
+ false ->
+ {error,
+ 'feature-not-implemented', 'collections'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end
+ end;
+ {?NS_XDATA, _Xmlattr} ->
+ options_type(Xmlels_Field, Collections, Form_Type, Node_Type,
+ [Xmlel_Field | Xmlels]);
+ _ ->
+ {error, 'not-acceptable'}
+ end.
+
+%%
+-spec(parse_xmlels_field/6 ::
+(
+ Options_Type :: 'node_config' | 'publish-options',
+ Node_Type :: 'leaf' | 'collection',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Xmlels :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()],
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_item()
+ %%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'})
+ -> {ok,
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_item()}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+%%
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Features, _Entity,
+ _Xmlels, Error)
+ when is_tuple(Error) ->
+ Error;
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Features, _Entity,
+ [] = _Xmlels, Options) ->
+ {ok, Options};
+%%
+parse_xmlels_field(Options_Type, Node_Type, Pubsub_Features, Entity,
+ [Xmlel_Field | Xmlels], Options)
+ when ?Is_Xmlel_Field(Xmlel_Field) ->
+ case xmlns(Xmlel_Field) of
+ ?NS_XDATA ->
+ parse_xmlels_field(Options_Type, Node_Type, Pubsub_Features, Entity,
+ Xmlels,
+ parse_xmlel_field_attrs(
+ get_tag_attr_s(<<"var">>, Xmlel_Field),
+ Options_Type, Node_Type, Pubsub_Features, Entity,
+ remove_cdata(Xmlel_Field#xmlel.children),
+ Options));
+ _ ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Feature, _Entity,
+ _Xmlels, _Options) ->
+ {error, 'invalid-options'}.
+
+%%
+-spec(parse_xmlels_field/7 ::
+(
+ Options_Type :: 'subscribe_options',
+ Node_Type :: 'leaf' | 'collection',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Entity_Type :: 'local' | 'remote',
+ Xmlels :: [Xmlel_Field::xmlel()]
+ | [Xmlel::xmlel()],
+ Options :: pubsub_options:options_subscription()
+ %
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'})
+ -> {ok,
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_item()}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'}
+).
+
+%%
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Features, _Entity,
+ _Entity_Type, _Xmlels, Error)
+ when is_tuple(Error) ->
+ Error;
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Features, _Entity,
+ _Entity_Type, [] = _Xmlels, Options) ->
+ {ok, trim_subscription_options(Options)};
+%%
+parse_xmlels_field(Options_Type, Node_Type, Pubsub_Features, Entity,
+ Entity_Type, [Xmlel_Field | Xmlels], Options)
+ when ?Is_Xmlel_Field(Xmlel_Field) ->
+ case xmlns(Xmlel_Field) of
+ ?NS_XDATA ->
+ parse_xmlels_field(Options_Type, Node_Type, Pubsub_Features, Entity,
+ Entity_Type,
+ Xmlels,
+ parse_xmlel_field_attrs(
+ get_tag_attr_s(<<"var">>, Xmlel_Field),
+ Options_Type, Node_Type, Pubsub_Features, Entity, Entity_Type,
+ remove_cdata(Xmlel_Field#xmlel.children),
+ Options));
+ _ ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_xmlels_field(_Options_Type, _Node_Type, _Pubsub_Feature, _Entity,
+ _Entity_Type, _Xmlels, _Options) ->
+ {error, 'invalid-options'}.
+
+%%
+-spec(parse_xmlel_field_attrs/7 ::
+(
+ Pubsub_Option :: binary(),
+ Options_Type :: 'node_config' | 'publish-options',
+ Node_Type :: 'leaf' | 'collection',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Xmlels :: [Xmlel_Value::xmlel(),...]
+ | [Xmlel::xmlel(),...],
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_item())
+ -> Options :: pubsub_options:options_node()
+ | pubsub_options:options_item()
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+parse_xmlel_field_attrs(<<"pubsub#roster_groups_allowed">>, Options_Type,
+ Node_Type, Pubsub_Features, Entity, Xmlels, Options)
+ when (Options_Type == 'node_config' orelse Options_Type == 'publish-options')
+ andalso (Node_Type == 'leaf' orelse Node_Type == 'collection') ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true ->
+ case
+ case Options_Type of
+ 'node_config' ->
+ parse_pubsub_option_node(<<"roster_groups_allowed">>,
+ Node_Type, Pubsub_Features, Xmlels);
+ 'publish-options' ->
+ parse_pubsub_option_item(<<"roster_groups_allowed">>,
+ Pubsub_Features, Xmlels)
+ end
+
+ of
+ {ok, Roster_Groups_Allowed} ->
+ case lists:keyfind('roster_groups_allowed', 1, Options) of
+ false ->
+ [{Entity, Roster_Groups_Allowed} | Options];
+ {'roster_groups_allowed', Rosters_Groups_Allowed} ->
+ lists:keyreplace('roster_groups_allowed', 1, Options,
+ {'roster_groups_allowed',
+ _New_Rosters_Groups_Allowed =
+ case
+ lists:keyreplace(Entity, 1,
+ Rosters_Groups_Allowed,
+ {Entity, Roster_Groups_Allowed})
+ of
+ Rosters_Groups_Allowed ->
+ [{Entity, Roster_Groups_Allowed}
+ | Rosters_Groups_Allowed];
+ New_Rosters_Groups_Allowed ->
+ New_Rosters_Groups_Allowed
+ end})
+ end;
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_xmlel_field_attrs(<<"pubsub#", Pubsub_Option/binary>>,
+ 'node_config' = _Options_Type, Node_Type, Pubsub_Features, _Entity, Xmlels,
+ Node_Options) ->
+ case
+ lists:keymember(
+ Key = list_to_atom(binary_to_list(Pubsub_Option)), 1, Node_Options)
+ of
+ true ->
+ case
+ parse_pubsub_option_node(Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ of
+ {ok, Value} ->
+ lists:keyreplace(Key, 1, Node_Options, {Key, Value});
+ Error ->
+ Error
+ end;
+ false ->
+ Node_Options%{error, 'invalid-options'}
+ end;
+%%
+parse_xmlel_field_attrs(<<"pubsub#", Pubsub_Option/binary>>,
+ 'publish-options' = _Options_Type, _Node_Type, Pubsub_Features, _Entity, Xmlels,
+ Item_Options) ->
+ case parse_pubsub_option_item(Pubsub_Option, Pubsub_Features, Xmlels) of
+ {ok, Value}
+ when Value == undefined
+ orelse Value == [] ->
+ lists:keydelete(_Key = list_to_atom(binary_to_list(Pubsub_Option)),
+ 1, Item_Options);
+ {ok, Value} ->
+ Key = list_to_atom(binary_to_list(Pubsub_Option)),
+ case lists:keyreplace(Key, 1, Item_Options, {Key, Value}) of
+ Item_Options -> [{Key, Value} | Item_Options];
+ New_Item_Options -> New_Item_Options
+ end;
+ Error ->
+ Error
+ end;
+%%
+parse_xmlel_field_attrs(_Xmlattr_value, _Options_Type, _Node_Type,
+ _Pubsub_Features, _Entity, _Xmlels, _Options) ->
+ {error, 'invalid-options'}.
+
+%%
+-spec(parse_xmlel_field_attrs/8 ::
+(
+ Pubsub_Option :: binary(),
+ Options_Type :: 'subscribe_options',
+ Node_Type :: 'leaf' | 'collection',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Entity :: xmpp_jid:usr_entity(),
+ Entity_Type :: 'local' | 'remote',
+ Xmlels :: [Xmlel_Value::xmlel(),...]
+ | [Xmlel::xmlel(),...],
+ Subscription_Options :: pubsub_options:options_subscription())
+ -> Subscription_Options :: pubsub_options:options_subscription()
+ %%%
+ | {error, 'invalid-options'}
+).
+
+parse_xmlel_field_attrs(<<"pubsub#", Pubsub_Option/binary>>,
+ 'subscribe_options' = _Options_Type, Node_Type, Pubsub_Features, _Entity,
+ Entity_Type, Xmlels, Subscription_Options) ->
+ case
+ lists:keymember(Key = list_to_atom(binary_to_list(Pubsub_Option)), 1,
+ Subscription_Options)
+ of
+ true ->
+ case
+ parse_pubsub_option_subscription(Pubsub_Option, Node_Type,
+ Entity_Type, Pubsub_Features, Xmlels)
+ of
+ {ok, Value}
+ when ?Is_Default_Subscribe_Option({Key, Value}) ->
+ lists:keydelete(Key, 1, Subscription_Options);
+ {ok, Value} ->
+ lists:keyreplace(Key, 1, Subscription_Options, {Key, Value});
+ Error ->
+ Error
+ end;
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_xmlel_field_attrs(_Xmlattr_value, _Options_Type, _Node_Type,
+ _Pubsub_Features, _Entity, _Entity_Type, _Xmlels, _Options) ->
+ {error, 'invalid-options'}.
+
+
+%%
+%%
+-spec(parse_pubsub_option_node/4 ::
+(
+ Pubsub_Option :: binary(),
+ Node_Type :: 'collection' | 'leaf',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Xmlels :: [Xmlel_Value::xmlel()]
+ | [Xmlel::xmlel()])
+ -> {ok, Access_Model :: pubsub_options:access_model()}
+ % | {ok, Body_XSLT :: undefined | binary()}
+ | {ok, Children_Association_Policy :: 'all' | 'owners' | 'whitelist'}
+ | {ok, Children_Association_Whitelist :: [Jid::xmpp_jid:raw_jid_entity_bare()]}
+ | {ok, Children :: [Child_NodeId::exmpp_pubsub:nodeId()]}
+ | {ok, Children_Max :: undefined | non_neg_integer()}
+ | {ok, Collection :: [Parent_NodeId::exmpp_pubsub:nodeId()]}
+ | {ok, Contact :: [Jid::xmpp_jid:raw_jid_entity()]}
+ % | {ok, Dataform_XSLT :: undefined | binary()}
+ | {ok, Deliver_Notifications :: boolean()}
+ | {ok, Deliver_Payloads :: boolean()}
+ | {ok, Description :: undefined | binary()}
+ | {ok, Item_Expire :: undefined | non_neg_integer()}
+ | {ok, ItemReply :: pubsub_options:item_reply()}
+ | {ok, Language :: undefined | binary()}
+ | {ok, Max_Items :: undefined | non_neg_integer()}
+ | {ok, Max_Payload_Size :: undefined | non_neg_integer()}
+ | {ok, Node_Type :: pubsub_options:node_type()}
+ | {ok, Notification_Type :: pubsub_options:notification_type()}
+ | {ok, Notify_Config :: boolean()}
+ | {ok, Notify_Delete :: boolean()}
+ | {ok, Notify_Retract :: boolean()}
+ | {ok, Notify_Sub :: boolean()}
+ | {ok, Persist_Items :: boolean()}
+ | {ok, Presence_Based_Delivery :: boolean()}
+ | {ok, Publish_Model :: pubsub_options:publish_model()}
+ | {ok, Purge_Offline :: boolean()}
+ | {ok, Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed()}
+ | {ok, Send_Last_Published_Item :: pubsub_options:send_last_published_item()}
+ | {ok, Tempsub :: boolean()}
+ | {ok, Subscribe :: boolean()}
+ | {ok, Title :: undefined | binary()}
+ | {ok, Type :: undefined | atom()}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+ %
+ | {error, 'feature-not-implemented', 'collections'}
+ | {error, 'feature-not-implemented', 'multi-collections'}
+).
+
+parse_pubsub_option_node(<<"access_model">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ parse_xmlel_value({'access_model', Pubsub_Features}, Xmlels);
+%%
+%%parse_pubsub_option_node(<<"pubsub#body_xslt">> = _Pubsub_Option,
+%% 'leaf' = _Node_Type, Pusbub_Features, Xmlels) ->
+%% case lists:member(<<"publish">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value('text-single', Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+parse_pubsub_option_node(<<"children_association_policy">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, Xmlels) ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['all', 'owners', 'whitelist']}, Xmlels);
+%%
+parse_pubsub_option_node(<<"children_association_whitelist">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, [] = _Xmlels) ->
+ {ok, []};
+parse_pubsub_option_node(<<"children_association_whitelist">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, Xmlels) ->
+ parse_xmlel_value({'jid-multi', 'bare'}, Xmlels);
+%%
+%%
+parse_pubsub_option_node(<<"children">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, [] = Xmlels) ->
+ parse_xmlel_value('text-multi', Xmlels);
+parse_pubsub_option_node(<<"children">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, [_] = Xmlels) ->
+ parse_xmlel_value('text-multi', Xmlels);
+parse_pubsub_option_node(<<"children">> = _Pubsub_Option,
+ 'collection' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"multi-collections">>, Pubsub_Features) of
+ true -> parse_xmlel_value('text-multi', Xmlels);
+ false -> {error, 'feature-not-implemented', 'multi-collections'}
+ end;
+%%
+%%
+parse_pubsub_option_node(<<"children_max">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, [] = _Xmlels) ->
+ {ok, undefined};
+parse_pubsub_option_node(<<"children_max">> = _Pubsub_Option,
+ 'collection' = _Node_Type, Pubsub_Features, [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ {ok, undefined};
+ {?NS_XDATA, CData} ->
+ case catch list_to_integer(binary_to_list(CData)) of
+ Integer when Integer == 0 orelse Integer == 1 ->
+ {ok, Integer};
+ Integer when is_integer(Integer) andalso Integer > 1 ->
+ case
+ lists:member(<<"multi-collections">>, Pubsub_Features)
+ of
+ true -> {ok, Integer};
+ false -> {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+parse_pubsub_option_node(<<"children_max">> = _Pubsub_Option,
+ 'collection' = _Node_Type, _Pubsub_Features, _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+parse_pubsub_option_node(<<"collection">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"collections">>, Pubsub_Features) of
+ true -> {ok, []};
+ false -> {error, 'feature-not-implemented', 'collections'}
+ end;
+parse_pubsub_option_node(<<"collection">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, [_] = Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"collections">>, Pubsub_Features) of
+ true -> parse_xmlel_value('text-multi', Xmlels);
+ false -> {error, 'feature-not-implemented', 'collections'}
+ end;
+parse_pubsub_option_node(<<"collection">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"collections">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"multi-collections">>, Pubsub_Features) of
+ true -> parse_xmlel_value('text-multi', Xmlels);
+ false -> {error, 'feature-not-implemented', 'multi-collections'}
+ end;
+ false ->
+ {error, 'feature-not-implemented', 'collections'}
+ end;
+%%
+parse_pubsub_option_node(<<"contact">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ {ok, []};
+parse_pubsub_option_node(<<"contact">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ parse_xmlel_value({'jid-multi', 'full'}, Xmlels);
+%%
+%%parse_pubsub_option_node(<<"pubsub#dataform_xslt">> = _Pubsub_Option,
+%% 'leaf' = _Node_Type, Pusbub_Features, Xmlels) ->
+%% case lists:member(<<"publish">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value('text-single', Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+parse_pubsub_option_node(<<"deliver_notifications">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"deliver_payloads">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"description">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ {ok, undefined};
+parse_pubsub_option_node(<<"description">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ parse_xmlel_value('text-single', Xmlels);
+%%
+parse_pubsub_option_node(<<"item_expire">> = _Pubsub_Option, 'leaf' = _Node_Type,
+ Pubsub_Features, [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_node(<<"item_expire">> = _Pubsub_Option, 'leaf' = _Node_Type,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"itemreply">> = _Pubsub_Option, 'leaf' = _Node_Type,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['owner', 'publisher']}, Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"language">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ {ok, undefined};
+parse_pubsub_option_node(<<"language">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ parse_xmlel_value('text-single', Xmlels);
+%%
+parse_pubsub_option_node(<<"max_items">> = _Pubsub_Option, 'leaf' = _Node_Type,
+ Pubsub_Features, [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_node(<<"max_items">> = _Pubsub_Option, 'leaf' = _Node_Type,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"max_payload_size">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_node(<<"max_payload_size">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"notification_type">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['headline', 'normal']}, Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"notify_config">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"config-node">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"notify_delete">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"delete-nodes">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"notify_retract">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"delete-items">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"notify_sub">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"persist_items">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"persistent-items">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"presence_based_delivery">> = _Pubsub_Option,
+ Node_Type, Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"publish_model">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['open', 'publishers', 'subscribers']},
+ Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"purge_offline">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"purge-nodes">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"roster_groups_allowed">> = _Pubsub_Option,
+ Node_Type, Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true -> {ok, []};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_node(<<"roster_groups_allowed">> = _Pubsub_Option,
+ Node_Type, Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true -> parse_xmlel_value('list-multi', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"send_last_published_item">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"last-published">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = case
+ lists:member(<<"presence-notifications">>, Pubsub_Features)
+ of
+ true -> ['never', 'on_sub', 'on_sub_and_presence'];
+ false -> ['never', 'on_sub']
+ end},
+ Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"tempsub">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"subscribe">> = _Pubsub_Option, Node_Type,
+ Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(<<"title">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ {ok, undefined};
+parse_pubsub_option_node(<<"title">> = _Pubsub_Option, Node_Type,
+ _Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ parse_xmlel_value('text-single', Xmlels);
+%%
+parse_pubsub_option_node(<<"type">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_node(<<"type">> = _Pubsub_Option,
+ 'leaf' = _Node_Type, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'atom'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_node(_Pubsub_Option, _Node_Type, _Pubsub_Features, _Xmlels) ->
+ {error, 'invalid-options'}.
+
+%%
+%%
+-spec(parse_pubsub_option_item/3 ::
+(
+ Pubsub_Option :: binary(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Xmlels :: [Xmlel_Value::xmlel()]
+ | [Xmlel::xmlel()])
+ -> {ok, Access_Model :: pubsub_options:access_model()}
+ % | {ok, Body_XSLT :: undefined | binary()}
+ % | {ok, Dataform_XSLT :: undefined | binary()}
+ | {ok, Deliver_Notifications :: boolean()}
+ | {ok, Deliver_Payloads :: boolean()}
+ | {ok, Item_Expire :: undefined | non_neg_integer()}
+ | {ok, ItemReply :: pubsub_options:item_reply()}
+%% | {ok, Max_Payload_Size :: undefined | non_neg_integer()}
+ | {ok, Notification_Type :: pubsub_options:notification_type()}
+ | {ok, Notify_Config :: boolean()}
+ | {ok, Notify_Retract :: boolean()}
+ | {ok, Persist_Items :: boolean()}
+ | {ok, Presence_Based_Delivery :: boolean()}
+% | {ok, Publish_Model :: pubsub_options:publish_model()}
+ | {ok, Purge_Offline :: boolean()}
+ | {ok, Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed()}
+ | {ok, Send_Last_Published_Item :: pubsub_options:send_last_published_item()}
+ | {ok, Type :: undefined | atom()}
+ %%%
+ | {error, 'invalid-options'}
+ %
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+).
+
+parse_pubsub_option_item(<<"access_model">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ parse_xmlel_value({'access_model', Pubsub_Features}, Xmlels);
+%%
+%%parse_pubsub_option_item(<<"pubsub#body_xslt">> = _Pubsub_Option,
+%% Pusbub_Features, Xmlels) ->
+%% case lists:member(<<"publish">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value('text-single', Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+%%parse_pubsub_option_item(<<"pubsub#dataform_xslt">> = _Pubsub_Option,
+%% Pusbub_Features, Xmlels) ->
+%% case lists:member(<<"publish">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value('text-single', Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+parse_pubsub_option_item(<<"deliver_notifications">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"deliver_payloads">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"item_expire">> = _Pubsub_Option, Pubsub_Features,
+ [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_item(<<"item_expire">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"itemreply">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['owner', 'publisher']}, Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+%parse_pubsub_option_item(<<"max_payload_size">> = _Pubsub_Option,
+% Pubsub_Features, Xmlels) ->
+% case lists:member(<<"publish">>, Pubsub_Features) of
+% true ->
+% parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+% false ->
+% {error, 'invalid-options'}
+% end;
+%%
+parse_pubsub_option_item(<<"notification_type">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = ['headline', 'normal']}, Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"notify_config">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"config-node">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"notify_retract">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"delete-items">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"persist_items">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"persistent-items">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"presence_based_delivery">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+%parse_pubsub_option_item(<<"publish_model">> = _Pubsub_Option,
+% Pubsub_Features, Xmlels) ->
+% case lists:member(<<"publish">>, Pubsub_Features) of
+% true ->
+% parse_xmlel_value({'list-single', 'atom',
+% _Default_Values = ['open', 'publishers', 'subscribers']},
+% Xmlels);
+% false ->
+% {error, 'invalid-options'}
+% end;
+%%
+parse_pubsub_option_item(<<"purge_offline">> = _Pubsub_Option, Pubsub_Features,
+ Xmlels) ->
+ case lists:member(<<"purge-nodes">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"roster_groups_allowed">> = _Pubsub_Option,
+ Pubsub_Features, [] = _Xmlels) ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true -> {ok, []};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_item(<<"roster_groups_allowed">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true -> parse_xmlel_value('list-multi', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"send_last_published_item">> = _Pubsub_Option,
+ Pubsub_Features, Xmlels) ->
+ case lists:member(<<"last-published">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-single', 'atom',
+ _Default_Values = case
+ lists:member(<<"presence-notifications">>, Pubsub_Features)
+ of
+ true -> ['never', 'on_sub', 'on_sub_and_presence'];
+ false -> ['never', 'on_sub']
+ end},
+ Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(<<"type">> = _Pubsub_Option, Pubsub_Features,
+ [] = _Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_item(<<"type">> = _Pubsub_Option, Pubsub_Features, Xmlels) ->
+ case lists:member(<<"publish">>, Pubsub_Features) of
+ true -> parse_xmlel_value({'text-single', 'atom'}, Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_item(_Pubsub_Option, _Pubsub_Features, _Xmlels) ->
+ {error, 'invalid-options'}.
+
+%%
+%%
+-spec(parse_pubsub_option_subscription/5 ::
+(
+ Pubsub_Option :: binary(),
+ Node_Type :: 'collection' | 'leaf',
+ Entity_Type :: 'local' | 'remote',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Xmlels :: [Xmlel_Value::xmlel()]
+ | [Xmlel::xmlel()])
+ ->
+ % {ok, Digest :: boolean()}
+ % | {ok, Digest_Frequency :: undefined | non_neg_integer()}
+ {ok, Deliver :: boolean()}
+ | {ok, Expire :: undefined | 'presence' | erlang:timestamp()}
+% | {ok, Include_Body :: boolean()}
+ | {ok, Show_Values :: ['away' | 'chat' | 'dnd' | 'online' | 'xa']}
+% | {ok, Subscription_Type :: 'all' | 'items' | 'nodes'}
+% | {ok, Subscription_Depth :: 'all' | non_neg_integer()}
+ %%%
+ | {error, 'invalid-options'}
+ %
+ | {error, 'not-acceptable'}
+).
+
+%% parse_pubsub_option_subscription(<<"digest">> = _Pubsub_Option,
+%% 'leaf' = _Node_Type, _Entity_Type, Pubsub_Features, Xmlels) ->
+%% case lists:member(<<"subscribe">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value('boolean', Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+%% parse_pubsub_option_subscription(<<"digest_frequency">> = _Pubsub_Option,
+%% 'leaf' = _Node_Type, _Entity_Type, Pubsub_Features, Xmlels) ->
+%% case lists:member(<<"subscribe">>, Pubsub_Features) of
+%% true ->
+%% parse_xmlel_value({'text-single', 'integer'}, Xmlels);
+%% false ->
+%% {error, 'invalid-options'}
+%% end;
+%%
+parse_pubsub_option_subscription(<<"deliver">> = _Pubsub_Option, Node_Type,
+ _Entity_Type, Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"subscribe">>, Pubsub_Features) of
+ true -> parse_xmlel_value('boolean', Xmlels);
+ false -> {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_subscription(<<"expire">> = _Pubsub_Option, Node_Type,
+ 'local' = _Entity_Type, Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case
+ lists:member(<<"presence-notifications">>, Pubsub_Features)
+ orelse
+ lists:member(<<"leased-subscriptions">>, Pubsub_Features)
+ of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_subscription(<<"expire">> = _Pubsub_Option, Node_Type,
+ 'local' = _Entity_Type, Pubsub_Features, [Xmlel_Value])
+ when Node_Type == 'collection' orelse Node_Type == 'leaf'
+ andalso ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ {ok, undefined};
+ {?NS_XDATA, <<"presence">>} ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true -> {ok, 'presence'};
+ false -> {error, 'invalid-options'}
+ end;
+ {?NS_XDATA, CData} ->
+ case lists:member(<<"leased-subscriptions">>, Pubsub_Features) of
+ true ->
+ case jlib:datetime_string_to_timestamp(CData) of
+ undefined -> {error, 'not-acceptable'};
+ DateTime -> {ok, DateTime}
+ end;
+ false ->
+ {error, 'invalid-options'}
+ end;
+ _ ->
+ {error, 'invalid-options'}
+ end;
+%
+parse_pubsub_option_subscription(<<"expire">> = _Pubsub_Option, Node_Type,
+ 'remote' = _Entity_Type, Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"leased-subscriptions">>, Pubsub_Features) of
+ true -> {ok, undefined};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_subscription(<<"expire">> = _Pubsub_Option, Node_Type,
+ 'remote' = _Entity_Type, Pubsub_Features, [Xmlel_Value])
+ when Node_Type == 'collection' orelse Node_Type == 'leaf'
+ andalso ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ {ok, undefined};
+ {?NS_XDATA, <<"presence">>} ->
+ {error, 'invalid-options'};
+ {?NS_XDATA, CData} ->
+ case lists:member(<<"leased-subscriptions">>, Pubsub_Features) of
+ true ->
+ case jlib:datetime_string_to_timestamp(CData) of
+ undefined -> {error, 'not-acceptable'};
+ DateTime -> {ok, DateTime}
+ end;
+ false ->
+ {error, 'invalid-options'}
+ end;
+ _ ->
+ {error, 'invalid-options'}
+ end;
+%%
+%parse_pubsub_option_subscription(<<"include_body">> = _Pubsub_Option,
+% 'leaf' = _Node_Type, _Entity_Type, Pubsub_Features, Xmlels) ->
+% case lists:member(<<"subscribe">>, Pubsub_Features) of
+% true ->
+% parse_xmlel_value('boolean', Xmlels);
+% false ->
+% {error, 'invalid-options'}
+% end;
+%%
+parse_pubsub_option_subscription(<<"show-values">> = _Pubsub_Option, Node_Type,
+ 'local' = _Entity_Type, Pubsub_Features, [] = _Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true -> {ok, []};
+ false -> {error, 'invalid-options'}
+ end;
+parse_pubsub_option_subscription(<<"show-values">> = _Pubsub_Option, Node_Type,
+ 'local' = _Entity_Type, Pubsub_Features, Xmlels)
+ when Node_Type == 'collection' orelse Node_Type == 'leaf' ->
+ case lists:member(<<"presence-notifications">>, Pubsub_Features) of
+ true ->
+ parse_xmlel_value({'list-multi', 'atom',
+ _Default_Values = ['away' ,'chat', 'dnd', 'online', 'xa']},
+ Xmlels);
+ false ->
+ {error, 'invalid-options'}
+ end;
+%%
+parse_pubsub_option_subscription(<<"show-values">> = _Pubsub_Option, _Node_Type,
+ 'remote' = _Entity_Type, _Pubsub_Features, _Xmlels) ->
+ {error, 'invalid-options'};
+%%
+%parse_pubsub_option_subscription(<<"subscription_type">> = _Pubsub_Option,
+% 'collection' = _Node_Type, _Entity_Type, _Pubsub_Features, Xmlels) ->
+% parse_xmlel_value({'list-single', 'atom',
+% _Default_Values = ['all', 'items', 'nodes']}, Xmlels);
+%%
+%parse_pubsub_option_subscription(<<"subscription_depth">> = _Pubsub_Option,
+% 'collection' = _Node_Type, _Entity_Type, _Pubsub_Features, Xmlels) ->
+% case Xmlels of
+% [] ->
+% {ok, 0};
+% [Xmlel_Value] when ?Is_Xmlel_Value(Xmlel_Value) ->
+% case {Xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+% {?NS_XDATA, <<>>} ->
+% {ok, 0};
+% {?NS_XDATA, <<"all">>} ->
+% {ok, 'all'};
+% {?NS_XDATA, CData} ->
+% case catch list_to_integer(binary_to_list(CData)) of
+% Integer
+% when is_integer(Integer) andalso Integer > -1 ->
+% {ok, Integer};
+% _Error ->
+% {error, 'not-acceptable'}
+% end;
+% _ ->
+% {error, 'not-acceptable'}
+% end;
+% _Xmlels ->
+% {error, 'not-acceptable'}
+% end;
+%%
+parse_pubsub_option_subscription(_Pubsub_Option, _Node_Type, _Entity_Type,
+ _Pubsub_Features, _Xmlels) ->
+ {error, 'invalid-options'}.
+
+
+-spec(parse_xmlel_value/2 ::
+(
+ DataType :: {'jid-multi', 'bare'}
+ | {'jid-multi', 'full'}
+ | {'list-multi', 'atom', Default_Values::[Default_Value::atom(),...]}
+ | 'list-multi'
+ | 'text-multi'
+ | {'access_model', Pubsub_Features::exmpp_pubsub:pubsub_features()}
+ | 'boolean'
+ | 'text-single'
+ | {'text-single', 'atom'}
+ | {'text-single', 'integer'}
+ | {'list-single', 'atom', Default_Values::[Default_Value::atom(),...]},
+ Xmlels :: [Xmlel_Value::xmlel()]
+ | [Xmlel::xmlel()])
+ -> %%
+ {ok, Values :: []
+ | [Value::binary()]
+ | [Value::atom()]
+ | [Value::xmpp_jid:raw_jid_entity_bare()]
+ | [Value::xmpp_jid:raw_jid_entity()]}
+ | {ok, Value :: atom()
+ | binary()
+ | boolean()
+ | non_neg_integer()
+ | undefined}
+ %%%
+ | {error, 'invalid-options'}
+ | {error, 'jid-malformed'}
+ | {error, 'not-acceptable'}
+ | {error, 'not-acceptable', 'unsupported-access-model'}
+).
+
+%%
+parse_xmlel_value({'jid-multi', 'bare'}, [] = _Xmlels) ->
+ {ok, []};
+%%
+parse_xmlel_value({'jid-multi', 'bare'}, Xmlels) ->
+ parse_xmlel_value({'jid-multi', 'bare'}, Xmlels, []);
+%%
+parse_xmlel_value({'jid-multi', 'full'}, [] = _Xmlels) ->
+ {ok, []};
+%%
+parse_xmlel_value({'jid-multi', 'full'}, Xmlels) ->
+ parse_xmlel_value({'jid-multi', 'full'}, Xmlels, []);
+%%
+%%
+parse_xmlel_value({'list-multi', 'atom', _Default_Values}, [] = _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+parse_xmlel_value({'list-multi', 'atom', Default_Values}, Xmlels) ->
+ parse_xmlel_value({'list-multi', 'atom', Default_Values}, Xmlels, []);
+%%
+parse_xmlel_value('list-multi', Xmlels) ->
+ parse_xmlel_value('list-multi', Xmlels, []);
+%%
+%%
+parse_xmlel_value('text-multi', [] = _Xmlels) ->
+ {ok, []};
+%%
+parse_xmlel_value('text-multi', Xmlels) ->
+ parse_xmlel_value('text-multi', Xmlels, []);
+%%
+
+parse_xmlel_value({'access_model', Pubsub_Features}, [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<"authorize">>} ->
+ case lists:member(<<"access-authorize">>, Pubsub_Features) of
+ true ->
+ {ok, 'authorize'};
+ false ->
+ {error, 'not-acceptable', 'unsupported-access-model'}
+ end;
+ %%
+ {?NS_XDATA, <<"open">>} ->
+ case lists:member(<<"access-open">>, Pubsub_Features) of
+ true ->
+ {ok, 'open'};
+ false ->
+ {error, 'not-acceptable', 'unsupported-access-model'}
+ end;
+ %%
+ {?NS_XDATA, <<"presence">>} ->
+ case lists:member(<<"access-presence">>, Pubsub_Features) of
+ true ->
+ {ok, 'presence'};
+ false ->
+ {error, 'not-acceptable', 'unsupported-access-model'}
+ end;
+ %%
+ {?NS_XDATA, <<"roster">>} ->
+ case lists:member(<<"access-roster">>, Pubsub_Features) of
+ true ->
+ {ok, 'roster'};
+ false ->
+ {error, 'not-acceptable', 'unsupported-access-model'}
+ end;
+ %%
+ {?NS_XDATA, <<"whitelist">>} ->
+ case lists:member(<<"access-whitelist">>, Pubsub_Features) of
+ true ->
+ {ok, 'whitelist'};
+ false ->
+ {error, 'not-acceptable', 'unsupported-access-model'}
+ end;
+ %%
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+parse_xmlel_value({'access_model', _Pubsub_Features}, _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+%%
+parse_xmlel_value('boolean', [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, False}
+ when False == <<"0">>
+ orelse False == <<"false">> ->
+ {ok, false};
+ {?NS_XDATA, True}
+ when True == <<"1">>
+ orelse True == <<"true">> ->
+ {ok, true};
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+parse_xmlel_value('boolean', _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+%%
+parse_xmlel_value('text-single', [] = _Xmlels) ->
+ {ok, undefined};
+parse_xmlel_value('text-single', [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} -> {ok, undefined};
+ {?NS_XDATA, CData} -> {ok, CData};
+ _ -> {error, 'not-acceptable'}
+ end;
+parse_xmlel_value('text-single', _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+%%
+parse_xmlel_value({'text-single', 'atom'}, [] = _Xmlels) ->
+ {ok, undefined};
+parse_xmlel_value({'text-single', 'atom'}, [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} -> {ok, undefined};
+ {?NS_XDATA, CData} -> {ok, list_to_atom(binary_to_list(CData))};
+ _ -> {error, 'not-acceptable'}
+ end;
+parse_xmlel_value({'text-single', 'atom'}, _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+%%
+parse_xmlel_value({'text-single', 'integer'}, [] = _Xmlels) ->
+ {ok, undefined};
+parse_xmlel_value({'text-single', 'integer'}, [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ {ok, undefined};
+ {?NS_XDATA, CData} ->
+ case catch list_to_integer(binary_to_list(CData)) of
+ Integer when is_integer(Integer) andalso Integer > -1 ->
+ {ok, Integer};
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+parse_xmlel_value({'text-single', 'integer'}, _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+%%
+parse_xmlel_value({'list-single', 'atom', Default_Values}, [Xmlel_Value])
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {_, <<>>} ->
+ {error, 'not-acceptable'};
+ {?NS_XDATA, CData} ->
+ case
+ lists:member(Value = list_to_atom(binary_to_list(CData)),
+ Default_Values)
+ of
+ true ->
+ {ok, Value};
+ false ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+parse_xmlel_value({'list-single', 'atom', _Default_Values}, _Xmlels) ->
+ {error, 'not-acceptable'};
+%%
+parse_xmlel_value({_DataType, _Default_Values}, _Xmlels) ->
+ {error, 'not-acceptable'}.
+%%
+
+%%
+-spec(parse_xmlel_value/3 ::
+(
+ DataType :: {'list-multi', 'atom', Default_Values::[Default_Value::atom(),...]}
+ | 'list-multi'
+ | 'text-multi'
+ | {'jid-multi', 'full'}
+ | {'jid-multi', 'bare'},
+ Xmlels :: [Xmlel_Value::xmlel()]
+ | [Xmlel::xmlel()],
+ Values :: [Value::binary()]
+ | [Value::atom()]
+ | [Value::xmpp_jid:raw_jid_entity_bare()]
+ | [Value::xmpp_jid:raw_jid_entity_full()])
+ -> {ok,
+ Values :: [Value::binary()]
+ | [Value::atom()]
+ | [Value::xmpp_jid:raw_jid_entity_bare()]
+ | [Value::xmpp_jid:raw_jid_entity_full()]}
+ %%%
+ | {error, 'not-acceptable'}
+ | {error, 'jid-malformed'}
+).
+
+parse_xmlel_value(_DataType, _Xmlels = [], Values) ->
+ {ok, lists:usort(Values)};
+%%
+%%
+parse_xmlel_value({'list-multi', 'atom', Default_Values},
+ [Xmlel_Value | Xmlels], Values)
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ parse_xmlel_value({'list-multi', 'atom', Default_Values},
+ Xmlels, Values);
+ {?NS_XDATA, CData} ->
+ case
+ lists:member(Value = list_to_atom(binary_to_list(CData)),
+ Default_Values)
+ of
+ true ->
+ parse_xmlel_value({'list-multi', 'atom', Default_Values},
+ Xmlels, [Value | Values]);
+ false ->
+ {error, 'not-acceptable'}
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+%%
+%%
+parse_xmlel_value('list-multi', [Xmlel_Value | Xmlels], Values)
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ parse_xmlel_value('list-multi', Xmlels,
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} -> Values;
+ {?NS_XDATA, CData} -> [CData | Values];
+ _ -> {error, 'not-acceptable'}
+ end);
+%%
+%%
+parse_xmlel_value('text-multi', [Xmlel_Value | Xmlels] , Values)
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ parse_xmlel_value('text-multi', Xmlels,
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} -> Values;
+ {?NS_XDATA, CData} -> [_Value = CData | Values];
+ _ -> {error, 'not-acceptable'}
+ end);
+%%
+%%
+parse_xmlel_value({'jid-multi', 'full'}, [Xmlel_Value | Xmlels], Values)
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ parse_xmlel_value({'jid-multi', 'full'}, Xmlels, Values);
+ {?NS_XDATA, CData} ->
+ case is_jid(CData) of
+ #jid{} = Jid ->
+ parse_xmlel_value({'jid-multi', 'full'}, Xmlels,
+ [jlib:jid_to_string(Jid) | Values]);
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+%%
+parse_xmlel_value({'jid-multi', 'bare'}, [Xmlel_Value | Xmlels], Values)
+ when ?Is_Xmlel_Value(Xmlel_Value) ->
+ case {xmlns(Xmlel_Value), get_tag_cdata(Xmlel_Value)} of
+ {?NS_XDATA, <<>>} ->
+ parse_xmlel_value({'jid-multi', 'bare'}, Xmlels, Values);
+ {?NS_XDATA, CData} ->
+ case is_jid(CData) of
+ #jid{} = Jid ->
+ parse_xmlel_value({'jid-multi', 'bare'}, Xmlels,
+ [jlib:jid_to_string(Jid#jid{resource = <<>>, lresource = <<>>})
+ | Values]);
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'not-acceptable'}
+ end;
+%%
+parse_xmlel_value(_Data_Type, _Xmlels, _Values) ->
+ {error, 'not-acceptable'}.
+
+%%
+%%
+-spec(xdata_x/6 ::
+(
+ Options_Type :: 'node_config' | 'subscribe_options',
+ Form_Type :: 'form' | 'result',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Options :: pubsub_options:options_node()
+ | pubsub_options:options_subscription())
+ -> Xmlel_X::xmlel()
+).
+
+xdata_x('node_config', 'form', Pubsub_Features, Host, Entity, Node_Options) ->
+ xmpp_xdata:xmlel_x(<<"form">>,
+ _Xmlels_Field = [
+ xmpp_xdata:xmlel_field(<<"FORM_TYPE">>, <<"hidden">>,
+ [?NS_PUBSUB_NODE_CONFIG])
+ |xdata_field(Pubsub_Features, Host, Entity, Node_Options, [])
+ ]);
+
+xdata_x('subscribe_options', 'form', Pubsub_Features, Host, Entity,
+ Subscription_Options) ->
+ xmpp_xdata:xmlel_x(<<"form">>,
+ _Xmlels_Field = [
+ xmpp_xdata:xmlel_field(<<"FORM_TYPE">>, <<"hidden">>,
+ [?NS_PUBSUB_SUBSCRIBE_OPTIONS])
+ |xdata_field(Pubsub_Features, Host, Entity, Subscription_Options, [])
+ ]);
+
+xdata_x('subscribe_options', 'result', Pubsub_Features, Host, Entity,
+ Subscription_Options) ->
+ xmpp_xdata:xmlel_x(<<"result">>,
+ _Xmlels_Field = [
+ xmpp_xdata:xmlel_field(<<"FORM_TYPE">>, <<"hidden">>,
+ [?NS_PUBSUB_SUBSCRIBE_OPTIONS])
+ |xdata_field(Pubsub_Features, Host, Entity, Subscription_Options, [])
+ ]).
+
+%%
+%%
+-spec(xdata_x/7 ::
+(
+ Options_Type :: 'subscribe_options',
+ Form_Type :: 'form',
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Subscription_Options :: [] | pubsub_options:options_subscription(),
+ Default_Subscription_Options :: pubsub_options:options_subscription())
+ -> Xmlel_X::xmlel()
+).
+
+
+%%
+xdata_x('subscribe_options', 'form', Pubsub_Features, Host, Entity,
+ Subscription_Options, Default_Subscription_Options) ->
+ xmpp_xdata:xmlel_x(<<"form">>,
+ _Xmlels_Field = [
+ xmpp_xdata:xmlel_field(<<"FORM_TYPE">>, <<"hidden">>,
+ [?NS_PUBSUB_SUBSCRIBE_OPTIONS])
+ |xdata_field(Pubsub_Features, Host, Entity,
+ filter_subscription_options(Subscription_Options,
+ Default_Subscription_Options, []),
+ [])
+ ]).
+
+%%
+-spec(filter_subscription_options/3 ::
+(
+ Options :: [] | pubsub_options:options_subscription(),
+ Default_Subscription_Options :: pubsub_options:options_subscription(),
+ Subscription_Options :: [] | pubsub_options:options_subscription())
+ -> Subscription_Options::pubsub_options:options_subscription()
+).
+
+filter_subscription_options([] = _Options, Default_Subscription_Options,
+ Subscription_Options) ->
+ Default_Subscription_Options ++ Subscription_Options;
+%%
+filter_subscription_options([{Key, Value} | Options], Default_Subscription_Options,
+ Subscription_Options) ->
+ filter_subscription_options(Options,
+ lists:keydelete(Key, 1, Default_Subscription_Options),
+ [{Key, Value} | Subscription_Options]).
+
+
+-define(Label_Deliver, <<"Enable delivery ?">>).
+-define(Label_Digest, <<"Receive digest notifications ?">>).
+-define(Label_Digest_Frequency, <<"Receive digest notifications (approx. one per day)?">>).
+-define(Label_Expire, <<"Requested lease period">>).
+-define(Label_Include_Body, <<"Receive message body in addition to payload ?">>).
+-define(Label_Show_Values, <<"Select the presence types which are allowed to receive event notifications">>).
+
+%%
+%% pubsub#subscribe_options
+%%
+-spec(xdata_field/5 ::
+(
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Options :: [] | pubsub_options:options_subscription(),
+ Xmlels_Field :: [] | [Xmlel_Field::xmlel(),...])
+ -> Xmlels_Field::[Xmlel_Field::xmlel(),...]
+).
+%%
+xdata_field(_Pubsub_Features, _Host, _Entity, [] = _Options, Xmlels_Field) ->
+ lists:sort(Xmlels_Field);
+%% 'pubsub#deliver'
+xdata_field(Pubsub_Features, Host, Entity, [{'deliver', Deliver} | Options],
+ Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#deliver">>,
+ <<"Enable delivery?">>,
+ xdata_value('boolean', Deliver))
+ | Xmlels_Field]);
+%% 'pubsub#digest'
+xdata_field(Pubsub_Features, Host, Entity, [{'digest', Digest} | Options],
+ Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#digest">>,
+ <<"Receive digest notifications (approx. one per day)?">>,
+ xdata_value('boolean', Digest))
+ | Xmlels_Field]);
+%% 'pubsub#digest_frequency'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'digest_frequency', Digest_Frequency} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#digest_frequency">>,
+ <<"Receive digest notifications (approx. one per day)?">>,
+ xdata_value('integer', Digest_Frequency))
+ | Xmlels_Field]);
+%% 'pubsub#expire'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'expire', Expire} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#expire">>,
+ <<"Requested lease period">>,
+ case is_atom(Expire) of
+ true -> xdata_value('atom', Expire);
+ false -> xdata_value('date', Expire)
+ end)
+ | Xmlels_Field]);
+%% 'pubsub#include_body'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'include_body', Include_Body} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#include_body">>,
+ <<"Receive message body in addition to payload?">>,
+ xdata_value('boolean', Include_Body))
+ | Xmlels_Field]);
+%% 'pubsub#show-values'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'show-values', Show_Values} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_multi(
+ <<"pubsub#show-values">>,
+ <<"Select the presence types which are allowed to receive event notifications">>,
+ lists:map(fun
+ ('away') -> <<"away">>;
+ ('chat') -> <<"chat">>;
+ ('dnd') -> <<"dnd">>;
+ ('online') -> <<"online">>;
+ ('xa') -> <<"xa">>
+ end, Show_Values),
+ [{<<"away">>, <<"Away">>},
+ {<<"chat">>, <<"Want to Chat">>},
+ {<<"dnd">>, <<"Do Not Disturb">>},
+ {<<"online">>, <<"Available">>},
+ {<<"xa">>, <<"Extended Away">>}])
+ | Xmlels_Field]);
+%% 'pubsub#subscription_type'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'subscription_type', Subscription_Type} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#subscription_type">>,
+ <<"Subscription Type">>,
+ case Subscription_Type of
+ undefined -> <<"items">>;
+ 'items' -> <<"items">>;
+ 'nodes' -> <<"nodes">>;
+ 'all' -> <<"all">>
+ end,
+ [{<<"items">>, <<"Receive notification of items only">>},
+ {<<"nodes">>, <<"Receive notification of new nodes only">>},
+ {<<"all">>, <<"Receive notification of items and nodes">>}])
+ | Xmlels_Field]);
+%% 'pubsub#subscription_depth'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'subscription_depth', Subscription_Depth} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#subscription_depth">>,
+ <<"How far to traverse the node graph for notifications">>,
+ case Subscription_Depth of
+ undefined -> <<"1">>;
+ 'all' -> <<"all">>;
+ _ -> xdata_value('integer', Subscription_Depth)
+
+ end,
+ [{<<"1">>, <<"Receive notification from direct child nodes only">>},
+ {<<"all">>, <<"Receive notification from all descendent nodes">>}])
+ | Xmlels_Field]);
+%%
+%% pubsub#node_config
+%%
+%% 'pubsub#access_model'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'access_model', Access_Model} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#access_model">>,
+ <<"Specify the subscriber model">>,
+ case Access_Model of
+ % undefined -> <<"open">>;
+ 'authorize' -> <<"authorize">>;
+ 'open' -> <<"open">>;
+ 'presence' -> <<"presence">>;
+ 'roster' -> <<"roster">>;
+ 'whitelist' -> <<"whitelist">>
+ end,
+ xdata_access_model(Pubsub_Features,
+ ['authorize', 'open', 'presence', 'roster', 'whitelist']))
+ | Xmlels_Field]);
+%% 'pubsub#body_xslt'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'body_xslt', Xslt} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#body_xslt">>,
+ <<"The URL of an XSL transformation which can be applied to "
+ "payloads in order to generate an appropriate message body element">>,
+ Xslt)
+ | Xmlels_Field]);
+%% 'pubsub#children_association_policy'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'children_association_policy', Children_Association_Policy} | Options],
+ Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#children_association_policy">>,
+ <<"Who may associate leaf nodes with a collection">>,
+ case Children_Association_Policy of
+ undefined -> <<"owners">>;
+ 'owners' -> <<"owners">>;
+ 'all' -> <<"all">>;
+ 'whitelist' -> <<"whitelist">>
+ end,
+ [{<<"all">>, <<"Anyone may associate leaf nodes with the collection">>},
+ {<<"owners">>, <<"Only collection node owners may associate leaf"
+ " nodes with the collection">>},
+ {<<"whitelist">>, <<"Only those on a whitelist may associate leaf "
+ "nodes with the collection">>}])
+ | Xmlels_Field]);
+%% 'pubsub#children_association_whitelist'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'children_association_whitelist', Children_Association_Whitelist}
+ | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_jid_multi(
+ <<"pubsub#children_association_whitelist">>,
+ <<"The list of JIDs that may associate leaf nodes with a collection">>,
+ Children_Association_Whitelist)
+ | Xmlels_Field]);
+%% 'pubsub#children'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'children', Children} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_multi(
+ <<"pubsub#children">>,
+ <<"The child nodes (leaf or collection) associated with a collection">>,
+ Children)
+ | Xmlels_Field]);
+%% 'pubsub#children_max'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'children_max', Children_Max} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#children_max">>,
+ <<"The maximum number of child nodes that can be associated with a collection">>,
+ xdata_value('integer', Children_Max))
+ | Xmlels_Field]);
+%% 'pubsub#collection'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'collection', Collection} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_multi(
+ <<"pubsub#collection">>,
+ <<"The collection(s) with which a node is affiliated">>,
+ Collection)
+ | Xmlels_Field]);
+%% 'pubsub#contact'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'contact', Contacts} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_jid_multi(
+ <<"pubsub#contact">>,
+ <<"The JIDs of those to contact with questions">>,
+ Contacts)
+ | Xmlels_Field]);
+%% 'pubsub#dataform_xslt'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'dataform_xslt', Dataform_Xslt} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#dataform_xslt">>,
+ <<"Payload XSLT">>,
+ xdata_value({'text-multi', 'atom'}, Dataform_Xslt))
+ | Xmlels_Field]);
+%% 'pubsub#deliver_notifications'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'deliver_notifications', Deliver_Notifications} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#deliver_notifications">>,
+ <<"Whether to deliver event notifications">>,
+ xdata_value('boolean', Deliver_Notifications))
+ | Xmlels_Field]);
+%% 'pubsub#deliver_payloads'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'deliver_payloads', Deliver_Payloads} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#deliver_payloads">>,
+ <<"Whether to deliver payloads with event notifications">>,
+ xdata_value('boolean', Deliver_Payloads))
+ | Xmlels_Field]);
+%% 'pubsub#description'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'description', Description} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#description">>,
+ <<"A description of the node">>,
+ Description)
+ | Xmlels_Field]);
+%% 'pubsub#item_expire'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'item_expire', Item_Expire} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#item_expire">>,
+ <<"Number of seconds after which to automatically purge items">>,
+ xdata_value('integer', Item_Expire))
+ | Xmlels_Field]);
+%% 'pubsub#itemreply'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'itemreply', ItemReply} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#itemreply">>,
+ <<"Whether owners or publisher should receive replies to items">>,
+ case ItemReply of
+ undefined -> <<"publisher">>;
+ 'owner' -> <<"owner">>;
+ 'publisher' -> <<"publisher">>
+ end,
+ [{<<"owner">>, <<"Statically specify a replyto of the node owner(s)">>},
+ {<<"publisher">>, <<"Dynamically specify a replyto of the item publisher">>}])
+ | Xmlels_Field]);
+%% 'pubsub#language'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'language', Language} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#language">>,
+ <<"The default language of the node">>,
+ case Language of
+ undefined -> <<"en">>;
+ _Language -> Language
+ end,
+ [{<<"en">>, <<"English">>}
+ %{<<"es">>, <<"Español">>},
+ %{<<"fr">>, <<"Français">>},
+ %{<<"pl">>, <<"Polski">>},
+ %{<<"ru">>, <<"Русский">>}
+ ])
+ | Xmlels_Field]);
+%% 'pubsub#max_items'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'max_items', Max_Items} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#max_items">>,
+ <<"The maximum number of items to persist">>,
+ xdata_value('integer', Max_Items))
+ | Xmlels_Field]);
+%% 'pubsub#max_payload_size'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'max_payload_size', Max_Payload_Size} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#max_payload_size">>,
+ <<"The maximum payload size in bytes">>,
+ xdata_value('integer', Max_Payload_Size))
+ | Xmlels_Field]);
+%% 'pubsub#node_type'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'node_type', Node_Type} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host,
+ Entity, Options,
+ [case lists:member(<<"collections">>, Pubsub_Features) of
+ true ->
+ xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#node_type">>,
+ <<"Whether the node is a leaf (default) or a collection">>,
+ case Node_Type of
+ undefined -> <<"leaf">>;
+ 'leaf' -> <<"leaf">>;
+ 'collection' -> <<"collection">>
+ end,
+ [{<<"leaf">>, <<"The node is a leaf node (default)">>},
+ {<<"collection">>, <<"The node is a collection node">>}]);
+ false ->
+ %% Collections not supported -> non-modifiable field
+% xmpp_xdata:xmlel_field_fixed(
+% <<"pubsub#node_type">>,
+% <<"The node is a leaf node (default)">>,
+% <<"leaf">>)
+ xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#node_type">>,
+ <<"Whether the node is a leaf (default) or a collection">>,
+ <<"leaf">>,
+ [{<<"leaf">>, <<"The node is a leaf node (default)">>}])
+ end
+ |Xmlels_Field]);
+%% 'pubsub#notification_type'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'notification_type', Notification_Type} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#notification_type">>,
+ <<"Specify the delivery style for event notifications">>,
+ case Notification_Type of
+ undefined -> <<"headline">>;
+ 'headline' -> <<"headline">>;
+ 'normal' -> <<"normal">>;
+ 'chat' -> <<"chat">>
+ end,
+ [{<<"headline">>, <<"Messages of type headline">>},
+ {<<"normal">>, <<"Messages of type normal">>}
+ %{<<"chat">>, <<"Messages of type chat">>}
+ %{<<"presence">>, <<"Presence">>}
+ %{<<"iq">>, <<"Iq">>}
+ ])
+ | Xmlels_Field]);
+%% 'pubsub#notify_config'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'notify_config', Notify_Config} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#notify_config">>,
+ <<"Notify owners and subscribers when the node configuration changes">>,
+ xdata_value('boolean', Notify_Config))
+ | Xmlels_Field]);
+%% 'pubsub#notify_delete'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'notify_delete', Notify_Delete} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#notify_delete">>,
+ <<"Notify owners and subscribers when the node is deleted">>,
+ xdata_value('boolean', Notify_Delete))
+ | Xmlels_Field]);
+%% 'pubsub#notify_retract'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'notify_retract', Notify_Retract} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#notify_retract">>,
+ <<"Notify subscribers when items are removed from the node">>,
+ xdata_value('boolean', Notify_Retract))
+ | Xmlels_Field]);
+%% 'pubsub#notify_sub'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'notify_sub', Notify_Sub} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#notify_sub">>,
+ <<"Notify owners about new subscribers and unsubscribes">>,
+ xdata_value('boolean', Notify_Sub))
+ | Xmlels_Field]);
+%% 'pubsub#persist_items'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'persist_items', Persist_Items} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#persist_items">>,
+ <<"Persist items to storage">>,
+ xdata_value('boolean', Persist_Items))
+ | Xmlels_Field]);
+%% 'pubsub#presence_based_delivery'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'presence_based_delivery', Presence_Based_Delivery} | Options]
+ , Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#presence_based_delivery">>,
+ <<"Deliver event notifications only to available users">>,
+ xdata_value('boolean', Presence_Based_Delivery))
+ | Xmlels_Field]);
+%% 'pubsub#publish_model'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'publish_model', Publish_Model} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#publish_model">>,
+ <<"Specify the publisher model">>,
+ case Publish_Model of
+ % undefined -> <<"open">>;
+ 'open' -> <<"open">>;
+ 'publishers' -> <<"publishers">>;
+ 'subscribers' -> <<"subscribers">>
+ end,
+ [{<<"publishers">>, <<"Only publishers may publish">>},
+ {<<"subscribers">>, <<"Subscribers may publish">>},
+ {<<"open">>, <<"Anyone may publish">>}])
+ | Xmlels_Field]);
+%% 'pubsub#purge_offline'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'purge_offline', Purge_Offline} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#purge_offline">>,
+ <<"Purge all items when the relevant publisher goes offline ?">>,
+ xdata_value('boolean', Purge_Offline))
+ | Xmlels_Field]);
+%% 'pubsub#roster_groups_allowed'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'roster_groups_allowed', Rosters_Groups_Allowed} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_multi(
+ <<"pubsub#roster_groups_allowed">>,
+ <<"Roster group(s) allowed to subscribe and retrieve items">>,
+ case lists:keyfind(Entity, 1, Rosters_Groups_Allowed) of
+ false -> [];
+ {_Entity, Roster_Groups} -> Roster_Groups
+ end,
+ xdata_roster(Host, Entity))
+ | Xmlels_Field]);
+%% 'pubsub#send_last_published_item'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'send_last_published_item', Send_Last_Published_Item} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_list_single(
+ <<"pubsub#send_last_published_item">>,
+ <<"When to send the last published item">>,
+ case Send_Last_Published_Item of
+ undefined -> <<"on_sub_and_presence">>;
+ 'never' -> <<"never">>;
+ 'on_sub_and_presence' -> <<"on_sub_and_presence">>;
+ 'on_sub' -> <<"on_sub">>
+ end,
+ [{<<"never">>, <<"Never">>},
+ {<<"on_sub">>, <<"When a new subscription is processed">>},
+ {<<"on_sub_and_presence">>, <<"When a new subscription is processed "
+ "and whenever a subscriber comes online">>}])
+ | Xmlels_Field]);
+%% 'pubsub#subscribe'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'subscribe', Subscribe} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#subscribe">>,
+ <<"Whether to allow subscriptions">>,
+ xdata_value('boolean', Subscribe))
+ | Xmlels_Field]);
+%% 'pubsub#tempsub'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'tempsub', Tempsub} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_boolean(
+ <<"pubsub#tempsub">>,
+ <<"Make all subscriptions temporary, based on subscriber presence">>,
+ xdata_value('boolean', Tempsub))
+ | Xmlels_Field]);
+%% 'pubsub#title'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'title', Title} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#title">>,
+ <<"A friendly name for the node">>,
+ Title)
+ | Xmlels_Field]);
+%% 'pubsub#type'
+xdata_field(Pubsub_Features, Host, Entity,
+ [{'type', Type} | Options], Xmlels_Field) ->
+ xdata_field(Pubsub_Features, Host, Entity, Options,
+ [xmpp_xdata:xmlel_field_text_single(
+ <<"pubsub#type">>,
+ <<"Specify the type of payload data to be provided at this node">>,
+ Type)
+ | Xmlels_Field]).
+
+
+
+%%
+-spec(xdata_value/2 ::
+(
+ DataType :: 'atom'
+ | 'boolean'
+ | 'date'
+ | 'integer'
+ | {'text-multi', 'atom'},
+ Value :: undefined
+ | atom()
+ | boolean()
+ | erlang:timestamp()
+ | non_neg_integer()
+ | [atom()])
+ -> Xdata_Value :: binary() | [binary(),...]
+).
+
+xdata_value(_, undefined) -> undefined;
+%%
+xdata_value('atom', Value) -> list_to_binary(atom_to_list(Value));
+%%
+xdata_value('boolean', false) -> <<"false">>;
+xdata_value('boolean', true) -> <<"true">>;
+%%
+xdata_value('date', TimeStamp) ->
+ {DateTime, _} = jlib:timestamp_to_iso(calendar:now_to_datetime(TimeStamp), utc),
+ list_to_binary(DateTime ++ "Z");
+%%
+xdata_value('integer', Value) -> list_to_binary(integer_to_list(Value));
+%%
+xdata_value({'text-multi', 'atom'}, Values) ->
+ [list_to_binary(atom_to_list(Value)) || Value <- Values].
+
+%%
+-spec(xdata_access_model/3 ::
+(
+ Access_Model :: pubsub_options:access_model(),
+ Pubsub_Feature :: boolean(),
+ Access_Models :: [{binary(), binary()}])
+ -> Access_Models :: [{binary(), binary()}]
+).
+
+xdata_access_model(_Access_Model, false, Access_Models) ->
+ Access_Models;
+%%
+xdata_access_model('authorize' = _Access_Model, true, Access_Models) ->
+ [{<<"authorize">>, <<"Subscription requests must be approved and only "
+ "subscribers may retrieve items">>}
+ | Access_Models];
+%%
+xdata_access_model('open' = _Access_Model, true, Access_Models) ->
+ [{<<"open">>, <<"Anyone may subscribe and retrieve items">>}
+ | Access_Models];
+%%
+xdata_access_model('presence' = _Access_Model, true, Access_Models) ->
+ [{<<"presence">>, <<"Anyone with a presence subscription of both or from may "
+ "subscribe and retrieve items">>}
+ | Access_Models];
+%%
+xdata_access_model('roster' = _Access_Model, true, Access_Models) ->
+ [{<<"roster">>, <<"Anyone in the specified roster group(s) may subscribe and "
+ "retrieve items">>}
+ | Access_Models];
+%%
+xdata_access_model('whitelist' = _Access_Model, true, Access_Models) ->
+ [{<<"whitelist">>, <<"Only those on a whitelist may subscribe and retrieve items">>}
+ | Access_Models].
+
+%%
+-spec(xdata_access_model/2 ::
+(
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Default_Access_Model :: ['authorize'|'open'|'presence'|'roster'|'whitelist',...])
+ -> Access_Models :: [{binary(), binary()},...]
+).
+
+xdata_access_model(Pubsub_Features, Default_Access_Models) ->
+ lists:foldr(fun
+ ('authorize', Access_Models) ->
+ xdata_access_model('authorize',
+ lists:member(<<"access-authorize">>, Pubsub_Features), Access_Models);
+ %%
+ ('open', Access_Models) ->
+ xdata_access_model('open',
+ lists:member(<<"access-open">>, Pubsub_Features), Access_Models);
+ %%
+ ('presence', Access_Models) ->
+ xdata_access_model('presence',
+ lists:member(<<"access-presence">>, Pubsub_Features), Access_Models);
+ %%
+ ('roster', Access_Models) ->
+ xdata_access_model('roster',
+ lists:member(<<"access-roster">>, Pubsub_Features), Access_Models);
+ %%
+ ('whitelist', Access_Models) ->
+ xdata_access_model('whitelist',
+ lists:member(<<"access-whitelist">>, Pubsub_Features), Access_Models)
+ %
+ end, [], Default_Access_Models).
+
+%%
+
+%% Local Entity
+-spec(xdata_roster/2 ::
+(
+ Host :: binary(),
+ Entity :: xmpp_jid:usr_bare())
+ -> Roster_Groups :: [Roster_Group::binary()]
+).
+
+xdata_roster(Host, {_, Host, _} = Entity) ->
+ _Roster_Groups = lists:usort(lists:foldl(fun
+ (#roster{groups = Groups}, Roster_Groups) ->
+ lists:foldl(fun
+ (Group, Roster_Groups_Bis) ->
+ [Group | Roster_Groups_Bis]
+ end, Roster_Groups, Groups)
+ end, [], _Roster = get_entity_roster(Entity)));
+%% Remote Entity
+xdata_roster(_Host, _Entity) ->
+ [].
diff --git a/src/mod_pubsub_ng/pubsub_parser.erl b/src/mod_pubsub_ng/pubsub_parser.erl
new file mode 100644
index 000000000..9bc7de36d
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_parser.erl
@@ -0,0 +1,1442 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_parser).
+-author('karim.gemayel@process-one.net').
+
+-compile({no_auto_import,[node/1]}).
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+-import(xml,
+[
+ get_tag_attr_s/2,
+ get_attr_s/2,
+ remove_cdata/1
+]).
+
+
+%-type('xmlel_affiliations#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'affiliations', children::[]}
+%).
+
+%-type('xmlel_affiliations#owner'() ::
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'affiliations', children::[]}
+%).
+
+%-type('xmlel_configure#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'configure'}
+%).
+
+%-type('xmlel_configure#owner'() ::
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'configure', children::[]}
+%).
+
+%-type('xmlel_create#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'create', children::[]}
+%).
+
+%-type('xmlel_default#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'default'}
+%).
+
+%-type('xmlel_default#owner'() ::
+%% #xmlel{ns::?NS_PUBSUB_OWNER, name::'default', attrs::[], children::[]}
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'default', children::[]}
+%).
+
+%-type('xmlel_delete#owner'() ::
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'delete'}
+%).
+
+%-type('xmlel_items#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'items'}
+%).
+
+%-type('xmlel_options1#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'options', attrs::[]}
+%).
+
+%-type('xmlel_options2#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'options'}
+%).
+
+%-type('xmlel_publish#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'publish'}
+%).
+
+%-type('xmlel_publish-options#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'publish-options'}
+%).
+
+%-type('xmlel_purge#owner'() ::
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'purge', children::[]}
+%).
+
+%-type('xmlel_retract#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'retract'}
+%).
+
+%-type('xmlel_subscribe#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'subscribe', children::[]}
+%).
+
+%-type('xmlel_subscriptions#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'subscriptions', children::[]}
+%).
+
+%-type('xmlel_subscriptions#owner'() ::
+% #xmlel{ns::?NS_PUBSUB_OWNER, name::'subscriptions', children::[]}
+%).
+
+%-type('xmlel_unsubscribe#'() ::
+% #xmlel{ns::?NS_PUBSUB, name::'unsubscribe', children::[]}
+%).
+
+
+-define(Is_Xmlel_Affiliations(Xmlel),
+(
+ Xmlel#xmlel.name == <<"affiliations">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Configure(Xmlel),
+(
+ Xmlel#xmlel.name == <<"configure">> andalso
+ Xmlel#xmlel.attrs == []
+)).
+
+-define(Is_Xmlel_Create(Xmlel),
+(
+ Xmlel#xmlel.name == <<"create">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Default(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"default">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Delete(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB_OWNER andalso
+ Xmlel#xmlel.name == <<"delete">>
+)).
+
+-define(Is_Xmlel_Item(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"item">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Items1(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"items">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Items2(Xmlel),
+(
+%Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"items">>
+)).
+
+-define(Is_Xmlel_Options1(Xmlel),
+(
+%Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"options">> andalso
+ Xmlel#xmlel.attrs == []
+)).
+
+-define(Is_Xmlel_Options2(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"options">>
+)).
+
+-define(Is_Xmlel_Publish(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"publish">>
+)).
+
+-define(Is_Xmlel_Publish_Options(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"publish-options">> andalso
+ Xmlel#xmlel.attrs == []
+)).
+
+-define(Is_Xmlel_Purge(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB_OWNER andalso
+ Xmlel#xmlel.name == <<"purge">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Redirect(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB_OWNER andalso
+ Xmlel#xmlel.name == <<"redirect">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Retract(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"retract">> andalso
+ Xmlel#xmlel.children =/= []
+)).
+
+-define(Is_Xmlel_Subscribe(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"subscribe">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Subscriptions(Xmlel),
+(
+ %Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"subscriptions">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+-define(Is_Xmlel_Unsubscribe(Xmlel),
+(
+%Xmlel#xmlel.ns == ?NS_PUBSUB andalso
+ Xmlel#xmlel.name == <<"unsubscribe">> andalso
+ Xmlel#xmlel.children == []
+)).
+
+
+%%
+-spec(parse/4 ::
+(
+ IQ_Type :: 'get' | 'set',
+ Xmlels :: %-- Create_Node --%
+% ['xmlel_create#'(),...] |
+% %%
+% ['xmlel_create#'()
+% |'xmlel_configure#'(),...] |
+% %% %-- Delete_Node --%
+% ['xmlel_delete#owner'(),...] |
+% %% %-- Purge_Node --%
+% ['xmlel_purge#owner'(),...] |
+% %% %-- Publish_Item --%
+% ['xmlel_publish#'(),...] |
+% %%
+% ['xmlel_publish#'()
+% |'xmlel_configure#'(),...] |
+% %%
+% ['xmlel_publish#'()
+% |'xmlel_publish-options#'(),...] |
+% %%
+% ['xmlel_publish#'()
+% |'xmlel_configure#'()
+% |'xmlel_publish-options#'(),...] |
+% %% %-- Retract_Item --%
+% ['xmlel_retract#'(),...] |
+% %% %-- Subscribe_Node --%
+% ['xmlel_subscribe#'(),...] |
+% ['xmlel_subscribe#'()
+% |'xmlel_options1#'(),...] |
+% %% %-- Unsubscribe_Node --%
+% ['xmlel_unsubscribe#'(),...] |
+% %% %-- Set_Configure_Subscription --%
+% ['xmlel_options2#'(),...] |
+% %% %-- Get_Items --%
+% ['xmlel_items#'(),...] |
+% %% %-- Get_Entity_Affiliations --%
+% ['xmlel_affiliations#'(),...] |
+% %% %-- Get_Entity_Subscriptions --%
+% ['xmlel_subscriptions#'(),...] |
+% %% %-- Get_Node_Affiliations --%
+% ['xmlel_affiliations#owner'(),...] |
+% %% %-- Get_Node_Subscriptions --%
+% ['xmlel_subscriptions#owner'(),...] |
+% %% %-- Get_Configure_Subscription_Default --%
+% ['xmlel_default#'(),...] |
+% %% %-- Get_Configure_Subscription --%
+% ['xmlel_options2#'(),...] |
+% %% %-- Get_Configure_Node_Default --%
+% ['xmlel_default#owner'(),...] |
+% %% %-- Get_Configure_Node --%
+% ['xmlel_configure#owner'(),...] |
+ %% %-- Other Stanza --%
+ [xmlel()],
+ %%
+ Rsm :: boolean(),
+ API_Core :: #api_core{})
+ -> %-- Create_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'create_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Config :: [Xmlel::xmlel()]
+ }
+ }
+ | %-- Delete_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'delete_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ RedirectURI :: undefined | binary()
+ }
+ }
+ | %-- Purge_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'purge_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Publish_Item --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'publish_item',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Item :: 0,
+ ItemId :: undefined,
+ Payload :: exmpp_pubsub:payload_empty(),
+ Node_Config :: [Xmlel::xmlel()],
+ Publish_Options :: [Xmlel::xmlel()]
+ }
+ }
+ |
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'publish_item',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Item :: 1,
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...],
+ Node_Config :: [Xmlel::xmlel()],
+ Publish_Options :: [Xmlel::xmlel()]
+ }
+ }
+ | %-- Retract_Item --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'retract_item',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Notify :: undefined | boolean()
+ }
+ }
+ | %-- Subscribe_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'subscribe_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ Subscribe_Options :: [Xmlel::xmlel()]
+ }
+ }
+ | %-- Unsubscribe_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'unsubscribe_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ SubId :: undefined | exmpp_pubsub:subId()
+ }
+ }
+ | %-- Set_Configure_Subscription --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'set_configure_subscription',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Subscribe_Options :: [Xmlel::xmlel()]
+ }
+ }
+ | %-- Get_Items --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_items',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: non_neg_integer(),
+ ItemIds :: undefined
+ }
+ }
+ |
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_items',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: undefined,
+ ItemIds :: [] | exmpp_pubsub:itemIds()
+ }
+ }
+ | %-- Get_Entity_Affiliations --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_entity_affiliations',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Entity_Subscriptions --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_entity_subscriptions',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Node_Affiliations --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_node_affiliations',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Node_Subscriptions --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_node_subscriptions',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Configure_Subscription_Default --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_configure_subscription_default',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Configure_Subscription --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_configure_subscription',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Jid :: binary() | undefined,
+ SubId :: undefined | exmpp_pubsub:subId()
+ }
+ }
+ | %-- Get_Configure_Node_Default --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_configure_node_default',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ | %-- Get_Configure_Node --%
+ {result,
+ Module :: 'pubsub_core' | module(),
+ Function :: 'get_configure_node',
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId()
+ }
+ }
+ %%%
+ | {error, 'invalid-payload'}
+).
+
+%-- Create_Node --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <create node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Create], _Rsm, API_Core)
+ when ?Is_Xmlel_Create(Xmlel_Create) ->
+ case xmlns(Xmlel_Create) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.create_node,
+ _Function = 'create_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Create),
+ _Node_Config = []
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <create node='princely_musings'/>
+%% <configure>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#node_config</value>
+%% </field>
+%% <field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
+%% </x>
+%% </configure>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Create, Xmlel_Configure], _Rsm, API_Core)
+ when ?Is_Xmlel_Create(Xmlel_Create)
+ andalso ?Is_Xmlel_Configure(Xmlel_Configure) ->
+ case {xmlns(Xmlel_Create), xmlns(Xmlel_Configure)} of
+ {NS, NS} when NS == ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.create_node,
+ _Function = 'create_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Create),
+ _Node_Config = remove_cdata(Xmlel_Configure#xmlel.children)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Delete_Node --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <delete node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <delete node='princely_musings'>
+%% <redirect uri='xmpp:hamlet@denmark.lit?;node=blog'/>
+%% </delete>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Delete], _Rsm, API_Core)
+ when ?Is_Xmlel_Delete(Xmlel_Delete) ->
+ case {xmlns(Xmlel_Delete), remove_cdata(Xmlel_Delete#xmlel.children)} of
+ {?NS_PUBSUB_OWNER, []} ->
+ {result,
+ _Module = API_Core#api_core.delete_node,
+ _Function = 'delete_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Delete),
+ _RedirectURI = undefined
+ }
+ };
+ {?NS_PUBSUB_OWNER, [Xmlel_Redirect]}
+ when ?Is_Xmlel_Redirect(Xmlel_Redirect) ->
+ case xmlns(Xmlel_Redirect) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.delete_node,
+ _Function = 'delete_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Delete),
+ _RedirectURI = uri(Xmlel_Redirect)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Purge_Node --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <purge node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Purge], _Rsm, API_Core)
+ when ?Is_Xmlel_Purge(Xmlel_Purge) ->
+ case xmlns(Xmlel_Purge) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.purge_node,
+ _Function = 'purge_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Purge)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Publish_Item --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <publish node='princely_musings'/>
+%% <configure>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#node_config</value>
+%% </field>
+%% <field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
+%% </x>
+%% </configure>
+%% <publish-options>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#publish-options</value>
+%% </field>
+%% <field var='pubsub#access_model'><value>presence</value></field>
+%% </x>
+%% </publish-options>
+%% </pubsub>
+%% </iq>
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <publish node='princely_musings'>
+%% <item id='bnd81g37d61f49fgn581'/>
+%% </publish>
+%% <configure>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#node_config</value>
+%% </field>
+%% <field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
+%% </x>
+%% </configure>
+%% <publish-options>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#publish-options</value>
+%% </field>
+%% <field var='pubsub#access_model'><value>presence</value></field>
+%% </x>
+%% </publish-options>
+%% </pubsub>
+%% </iq>
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <publish node='princely_musings'>
+%% <item id='bnd81g37d61f49fgn581'>
+%% <entry xmlns='http://www.w3.org/2005/Atom'>
+%% <title>Soliloquy</title>
+%% </entry>
+%% </item>
+%% </publish>
+%% <publish-options>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#publish-options</value>
+%% </field>
+%% <field var='pubsub#access_model'><value>presence</value></field>
+%% </x>
+%% </publish-options>
+%% </pubsub>
+%% </iq>
+
+%%
+parse('set' = _IQ_Type, [Xmlel_Publish], _Rsm, API_Core)
+ when ?Is_Xmlel_Publish(Xmlel_Publish) ->
+ case {xmlns(Xmlel_Publish), remove_cdata(Xmlel_Publish#xmlel.children)} of
+ %%
+ {?NS_PUBSUB, []} ->
+ {result,
+ _Module = API_Core#api_core.publish_item,
+ _Function = 'publish_item',
+ _Parameters = {
+ _NodeId = node(Xmlel_Publish),
+ _Item = 0,
+ _ItemId = undefined,
+ _Payload = [],
+ _Node_Config = [],
+ _Publish_Options = []
+ }
+ };
+ %%
+% [#xmlel{ns = ?NS_PUBSUB, name = 'item'} = Xmlel_Item] ->
+ {?NS_PUBSUB, [#xmlel{name = <<"item">>} = Xmlel_Item]} ->
+ {result,
+ _Module = API_Core#api_core.publish_item,
+ _Function = 'publish_item',
+ _Parameters = {
+ _NodeId = node(Xmlel_Publish),
+ _Item = 1,
+ _ItemId = id(Xmlel_Item),
+ _Payload = remove_cdata(Xmlel_Item#xmlel.children),
+ _Node_Config = [],
+ _Publish_Options = []
+ }
+ };
+ %%
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+%%
+parse('set' = _IQ_Type, [Xmlel_Publish, Xmlel_Configure], _Rsm, API_Core)
+ when ?Is_Xmlel_Publish(Xmlel_Publish)
+ andalso ?Is_Xmlel_Configure(Xmlel_Configure) ->
+ case {xmlns(Xmlel_Publish), xmlns(Xmlel_Configure)} of
+ {NS, NS} when NS == ?NS_PUBSUB ->
+ case parse('set', [Xmlel_Publish], _Rsm, API_Core) of
+ {result, Module, Function, {NodeId, Item, ItemId, Payload, _, _}} ->
+ {result,
+ Module,
+ Function,
+ _Parameters = {
+ NodeId,
+ Item,
+ ItemId,
+ Payload,
+ _Node_Config = remove_cdata(Xmlel_Configure#xmlel.children),
+ _Publish_Options = []
+ }
+ };
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'invalid-paylaod'}
+ end;
+%%
+parse('set' = _IQ_Type, [Xmlel_Publish, Xmlel_Publish_Options], _Rsm, API_Core)
+ when ?Is_Xmlel_Publish(Xmlel_Publish)
+ andalso ?Is_Xmlel_Publish_Options(Xmlel_Publish_Options) ->
+ case {xmlns(Xmlel_Publish), xmlns(Xmlel_Publish_Options)} of
+ {NS, NS} when NS == ?NS_PUBSUB ->
+ case parse('set', [Xmlel_Publish], _Rsm, API_Core) of
+ {result, Module, Function, {NodeId, Item, ItemId, Payload, _, _}} ->
+ {result,
+ Module,
+ Function,
+ _Parameters = {
+ NodeId,
+ Item,
+ ItemId,
+ Payload,
+ _Node_Config = [],
+ _Publish_Options = remove_cdata(
+ Xmlel_Publish_Options#xmlel.children)
+ }
+ };
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%%
+parse('set' = _IQ_Type, [Xmlel_Publish, Xmlel_Configure, Xmlel_Publish_Options],
+ _Rsm, API_Core)
+ when ?Is_Xmlel_Publish(Xmlel_Publish)
+ andalso ?Is_Xmlel_Configure(Xmlel_Configure)
+ andalso ?Is_Xmlel_Publish_Options(Xmlel_Publish_Options) ->
+ case {xmlns(Xmlel_Publish), xmlns(Xmlel_Configure), xmlns(Xmlel_Publish_Options)} of
+ {NS, NS, NS} when NS == ?NS_PUBSUB ->
+ case parse('set', [Xmlel_Publish, Xmlel_Configure], _Rsm, API_Core) of
+ {result, Module, Function,
+ {NodeId, Item, ItemId, Payload, Node_Config, _}} ->
+ {result,
+ Module,
+ Function,
+ _Parameters = {
+ NodeId,
+ Item,
+ ItemId,
+ Payload,
+ Node_Config,
+ _Publish_Options = remove_cdata(
+ Xmlel_Publish_Options#xmlel.children)
+ }
+ };
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+%%
+%%
+parse('set' = _IQ_Type, [Xmlel_Publish, Xmlel_Publish_Options, Xmlel_Configure],
+ Rsm, API_Core)
+ when ?Is_Xmlel_Publish(Xmlel_Publish)
+ andalso ?Is_Xmlel_Publish_Options(Xmlel_Publish_Options)
+ andalso ?Is_Xmlel_Configure(Xmlel_Configure) ->
+ case {xmlns(Xmlel_Publish), xmlns(Xmlel_Publish_Options), xmlns(Xmlel_Configure)} of
+ {NS, NS, NS} when NS == ?NS_PUBSUB ->
+ parse('set', [Xmlel_Publish, Xmlel_Configure, Xmlel_Publish_Options],
+ Rsm, API_Core);
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Entity_Affiliations --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <affiliations/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <affiliations node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Affiliations], _Rsm, API_Core)
+ when ?Is_Xmlel_Affiliations(Xmlel_Affiliations) ->
+ case xmlns(Xmlel_Affiliations) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.get_entity_affiliations,
+ _Function = 'get_entity_affiliations',
+ _Parameters = {
+ _NodeId = node(Xmlel_Affiliations)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%% TODO : RSM
+
+%-- Get_Entity_Subscriptions --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <subscriptions/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <subscriptions node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Subscriptions], _Rsm, API_Core)
+ when ?Is_Xmlel_Subscriptions(Xmlel_Subscriptions) ->
+ case xmlns(Xmlel_Subscriptions) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.get_entity_subscriptions,
+ _Function = 'get_entity_subscriptions',
+ _Parameters = {
+ _NodeId = node(Xmlel_Subscriptions)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%% TODO : RSM
+
+%-- Get_Node_Affiliations --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <affiliations node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Affiliations], _Rsm, API_Core)
+ when ?Is_Xmlel_Affiliations(Xmlel_Affiliations) ->
+ case xmlns(Xmlel_Affiliations) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.get_node_affiliations,
+ _Function = 'get_node_affiliations',
+ _Parameters = {
+ _NodeId = node(Xmlel_Affiliations)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%% TODO : RSM
+
+%-- Get_Node_Subscriptions --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <subscriptions node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Subscriptions], _Rsm, API_Core)
+ when ?Is_Xmlel_Subscriptions(Xmlel_Subscriptions) ->
+ case xmlns(Xmlel_Subscriptions) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.get_node_subscriptions,
+ _Function = 'get_node_subscriptions',
+ _Parameters = {
+ _NodeId = node(Xmlel_Subscriptions)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%% TODO : RSM
+
+%-- Retract_Item --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <retract node='princely_musings'>
+%% <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
+%% </retract>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <retract node='princely_musings' notify='true'>
+%% <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
+%% </retract>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Retract], _Rsm, API_Core)
+ when ?Is_Xmlel_Retract(Xmlel_Retract) ->
+ case {xmlns(Xmlel_Retract), notify(Xmlel_Retract)} of
+ {?NS_PUBSUB, undefined} ->
+ {result,
+ _Module = API_Core#api_core.retract_item,
+ _Function = 'retract_item',
+ _Parameters = {
+ _NodeId = node(Xmlel_Retract),
+ _ItemId = case remove_cdata(Xmlel_Retract#xmlel.children) of
+ [Xmlel_Item]
+ when ?Is_Xmlel_Item(Xmlel_Item) ->
+ case xmlns(Xmlel_Item) of
+ ?NS_PUBSUB -> id(Xmlel_Item);
+ _ -> undefined
+ end;
+ _ ->
+ undefined
+
+ end,
+ _Notify = undefined
+ }
+ };
+ {?NS_PUBSUB, False}
+ when False == <<"false">> orelse False == <<"0">> ->
+ {result,
+ _Module = API_Core#api_core.retract_item,
+ _Function = 'retract_item',
+ _Parameters = {
+ _NodeId = node(Xmlel_Retract),
+ _ItemId = case remove_cdata(Xmlel_Retract#xmlel.children) of
+ [Xmlel_Item]
+ when ?Is_Xmlel_Item(Xmlel_Item) ->
+ case xmlns(Xmlel_Item) of
+ ?NS_PUBSUB -> id(Xmlel_Item);
+ _ -> undefined
+ end;
+ _ ->
+ undefined
+
+ end,
+ _Notify = false
+ }
+ };
+ {?NS_PUBSUB, True}
+ when True == <<"true">> orelse True == <<"1">> ->
+ {result,
+ _Module = API_Core#api_core.retract_item,
+ _Function = 'retract_item',
+ _Parameters = {
+ _NodeId = node(Xmlel_Retract),
+ _ItemId = case remove_cdata(Xmlel_Retract#xmlel.children) of
+ [Xmlel_Item]
+ when ?Is_Xmlel_Item(Xmlel_Item) ->
+ case xmlns(Xmlel_Item) of
+ ?NS_PUBSUB -> id(Xmlel_Item);
+ _ -> undefined
+ end;
+ _ ->
+ undefined
+
+ end,
+ _Notify = true
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Subscribe_Node --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <subscribe node='princely_musings' jid='francisco@denmark.lit'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <subscribe node='princely_musings' jid='francisco@denmark.lit'/>
+%% <options>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
+%% </field>
+%% <field var='pubsub#deliver'><value>1</value></field>
+%% <field var='pubsub#show-values'>
+%% <value>chat</value>
+%% <value>online</value>
+%% <value>away</value>
+%% </field>
+%% </x>
+%% </options>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Subscribe], _Rsm, API_Core)
+ when ?Is_Xmlel_Subscribe(Xmlel_Subscribe) ->
+ case xmlns(Xmlel_Subscribe) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.subscribe_node,
+ _Function = 'subscribe_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Subscribe),
+ _Jid = id(Xmlel_Subscribe),
+ _Subscribe_Options = []
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+%%
+parse('set' = _IQ_Type, [Xmlel_Subscribe, Xmlel_Options], _Rsm, API_Core)
+ when ?Is_Xmlel_Subscribe(Xmlel_Subscribe)
+ andalso ?Is_Xmlel_Options1(Xmlel_Options) ->
+ case {xmlns(Xmlel_Subscribe), xmlns(Xmlel_Options)} of
+ {NS, NS} when NS == ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.subscribe_node,
+ _Function = 'subscribe_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Subscribe),
+ _Jid = jid(Xmlel_Subscribe),
+ _Subscribe_Options = remove_cdata(Xmlel_Options#xmlel.children)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Unsubscribe_Node --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <unsubscribe node='princely_musings' jid='francisco@denmark.lit'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <unsubscribe node='princely_musings' jid='francisco@denmark.lit' subid='SubId'/>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Unsubscribe], _Rsm, API_Core)
+ when ?Is_Xmlel_Unsubscribe(Xmlel_Unsubscribe) ->
+ case xmlns(Xmlel_Unsubscribe) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.unsubscribe_node,
+ _Function = 'unsubscribe_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Unsubscribe),
+ _Jid = jid(Xmlel_Unsubscribe),
+ _SubId = subid(Xmlel_Unsubscribe)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Set_Configure_Subscription --%
+
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <options node='princely_musings' jid='francisco@denmark.lit'>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
+%% </field>
+%% <field var='pubsub#deliver'><value>1</value></field>
+%% <field var='pubsub#show-values'>
+%% <value>chat</value>
+%% <value>online</value>
+%% <value>away</value>
+%% </field>
+%% </x>
+%% </options>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='set'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <options node='princely_musings' jid='francisco@denmark.lit' subid='SubId'>
+%% <x xmlns='jabber:x:data' type='submit'>
+%% <field var='FORM_TYPE' type='hidden'>
+%% <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
+%% </field>
+%% <field var='pubsub#deliver'><value>1</value></field>
+%% <field var='pubsub#show-values'>
+%% <value>chat</value>
+%% <value>online</value>
+%% <value>away</value>
+%% </field>
+%% </x>
+%% </options>
+%% </pubsub>
+%% </iq>
+
+parse('set' = _IQ_Type, [Xmlel_Options], _Rsm, API_Core)
+ when ?Is_Xmlel_Options2(Xmlel_Options) ->
+ case xmlns(Xmlel_Options) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.set_configure_subscription,
+ _Function = 'set_configure_subscription',
+ _Parameters = {
+ _NodeId = node(Xmlel_Options),
+ _Jid = jid(Xmlel_Options),
+ _SubId = subid(Xmlel_Options),
+ _Subscribe_Options = remove_cdata(Xmlel_Options#xmlel.children)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Items --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <items node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <items node='princely_musings' max_items='2'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <items node='princely_musings'>
+%% <item id='368866411b877c30064a5f62b917cffe'/>
+%% <item id='4e30f35051b7b8b42abe083742187228'/>
+%% </items>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Items], _Rsm, API_Core)
+ when ?Is_Xmlel_Items1(Xmlel_Items) ->
+ case {xmlns(Xmlel_Items), max_items(Xmlel_Items)} of
+ {?NS_PUBSUB, undefined} ->
+ {result,
+ _Module = API_Core#api_core.get_items,
+ _Function = 'get_items',
+ _Parameters = {
+ _NodeId = node(Xmlel_Items),
+ _SubId = subid(Xmlel_Items),
+ _Max_Items = undefined,
+ _ItemIds = []
+ }
+ };
+ {?NS_PUBSUB, Binary} ->
+ case catch list_to_integer(binary_to_list(Binary)) of
+ Integer
+ when is_integer(Integer) andalso Integer >= 0 ->
+ {result,
+ _Module = API_Core#api_core.get_items,
+ _Function = 'get_items',
+ _Parameters = {
+ _NodeId = node(Xmlel_Items),
+ _SubId = subid(Xmlel_Items),
+ _Max_Items = Integer,
+ _ItemIds = undefined
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+%%
+parse('get' = _IQ_Type, [Xmlel_Items], _Rsm, API_Core)
+ when ?Is_Xmlel_Items2(Xmlel_Items) ->
+ case
+ {xmlns(Xmlel_Items),
+ parse_xmlels_item(remove_cdata(Xmlel_Items#xmlel.children))}
+ of
+ {?NS_PUBSUB, {ok, ItemIds}} ->
+ {result,
+ _Module = API_Core#api_core.get_items,
+ _Function = 'get_items',
+ _Parameters = {
+ _NodeId = node(Xmlel_Items),
+ _SubId = subid(Xmlel_Items),
+ _Max_Items = undefined,
+ ItemIds
+ }
+ };
+ {_, _Error} ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Configure_Subscription_Default --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <default/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <default node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Default], _Rsm, API_Core)
+ when ?Is_Xmlel_Default(Xmlel_Default) ->
+ case xmlns(Xmlel_Default) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.get_configure_subscription_default,
+ _Function = 'get_configure_subscription_default',
+ _Parameters = {
+ _NodeId = node(Xmlel_Default)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Configure_Subscription --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <options node='princely_musings' jid='francisco@denmark.lit'/>
+%% </pubsub>
+%% </iq>
+%%
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+%% <options node='princely_musings' jid='francisco@denmark.lit' subid='SubId'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Options], _Rsm, API_Core)
+ when ?Is_Xmlel_Options2(Xmlel_Options) ->
+ case xmlns(Xmlel_Options) of
+ ?NS_PUBSUB ->
+ {result,
+ _Module = API_Core#api_core.get_configure_subscription,
+ _Function = 'get_configure_subscription',
+ _Parameters = {
+ _NodeId = node(Xmlel_Options),
+ _Jid = jid(Xmlel_Options),
+ _SubId = subid(Xmlel_Options)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Configure_Node_Default --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <default/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Default], _Rsm, API_Core)
+ when ?Is_Xmlel_Default(Xmlel_Default) ->
+ case xmlns(Xmlel_Default) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.get_configure_node_default,
+ _Function = 'get_configure_node_default',
+ _Parameters = {
+ _NodeId = node(Xmlel_Default)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%-- Get_Configure_Node --%
+
+%% <iq type='get'>
+%% <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+%% <configure node='princely_musings'/>
+%% </pubsub>
+%% </iq>
+
+parse('get' = _IQ_Type, [Xmlel_Configure], _Rsm, API_Core)
+ when ?Is_Xmlel_Configure(Xmlel_Configure) ->
+ case xmlns(Xmlel_Configure) of
+ ?NS_PUBSUB_OWNER ->
+ {result,
+ _Module = API_Core#api_core.get_configure_node,
+ _Function = 'get_configure_node',
+ _Parameters = {
+ _NodeId = node(Xmlel_Configure)
+ }
+ };
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+
+%%
+parse(_IQ_Type, _Xmlels, _Rsm, _API_Core) ->
+ {error, 'invalid-payload'}.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%
+%% Parsing Helpers %%
+%%%%%%%%%%%%%%%%%%%%%
+
+
+%%
+-spec(parse_xmlels_item/1 ::
+(
+ Xmlels :: [Xmlel_Item::xmlel(),...])
+ -> {ok, ItemIds :: [] | exmpp_pubsub:itemIds()}
+ %%%
+ | {error, 'invalid-payload'}
+).
+
+parse_xmlels_item(Xmlels) ->
+ parse_xmlels_item(Xmlels, []).
+
+%%
+-spec(parse_xmlels_item/2 ::
+(
+ Xmlels :: [Xmlel_Item::xmlel(),...],
+ ItemIds :: [] | exmpp_pubsub:itemIds())
+ -> {ok, ItemIds :: [] | exmpp_pubsub:itemIds()}
+ %%%
+ | {error, 'invalid-payload'}
+).
+
+
+%%
+parse_xmlels_item([Xmlel_Item | Xmlels], ItemIds)
+ when ?Is_Xmlel_Item(Xmlel_Item) ->
+ case {xmlns(Xmlel_Item), id(Xmlel_Item)} of
+ {?NS_PUBSUB, ItemId}
+ when ItemId /= undefined ->
+ parse_xmlels_item(Xmlels, [ItemId | ItemIds]);
+ _ ->
+ {error, 'invalid-payload'}
+ end;
+parse_xmlels_item([] = _Xmlels_Item, ItemIds) ->
+ {ok, ItemIds};
+%%
+parse_xmlels_item(_Xmlels, _ItemIds) ->
+ {error, 'invalid-payload'}.
+
+
+xmlns(Xmlel) ->
+ get_tag_attr_s(<<"xmlns">>, Xmlel).
+
+node(Xmlel) ->
+ case get_tag_attr_s(<<"node">>, Xmlel) of
+ <<>> -> undefined;
+ NodeId -> NodeId
+ end.
+
+id(Xmlel) ->
+ case get_tag_attr_s(<<"id">>, Xmlel) of
+ <<>> -> undefined;
+ ItemId -> ItemId
+ end.
+
+uri(Xmlel) ->
+ case get_tag_attr_s(<<"uri">>, Xmlel) of
+ <<>> -> undefined;
+ URI -> URI
+ end.
+
+jid(Xmlel) ->
+ case get_tag_attr_s(<<"jid">>, Xmlel) of
+ <<>> -> undefined;
+ Jid -> Jid
+ end.
+
+subid(Xmlel) ->
+ case get_tag_attr_s(<<"subid">>, Xmlel) of
+ <<>> -> undefined;
+ SubId -> SubId
+ end.
+
+notify(Xmlel) ->
+ case get_tag_attr_s(<<"notify">>, Xmlel) of
+ <<>> -> undefined;
+ Notify -> Notify
+ end.
+
+max_items(Xmlel) ->
+ case get_tag_attr_s(<<"max_items">>, Xmlel) of
+ <<>> -> undefined;
+ Max_Items -> Max_Items
+ end.
diff --git a/src/mod_pubsub_ng/pubsub_tools.erl b/src/mod_pubsub_ng/pubsub_tools.erl
new file mode 100644
index 000000000..9bb5d2504
--- /dev/null
+++ b/src/mod_pubsub_ng/pubsub_tools.erl
@@ -0,0 +1,685 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is ProcessOne.
+%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne
+%%% All Rights Reserved.''
+%%% This software is copyright 2006-2011, ProcessOne.
+%%%
+%%% @copyright 2006-2011 ProcessOne
+%%% @author Karim Gemayel <karim.gemayel@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @headerfile "pubsub_dev.hrl"
+
+-module(pubsub_tools).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+
+jid_to_string({undefined, S, undefined}) ->
+ jlib:jid_to_string({<<>>, S, <<>>});
+jid_to_string({undefined, S, R}) ->
+ jlib:jid_to_string({<<>>, S, R});
+jid_to_string({U, S, undefined}) ->
+ jlib:jid_to_string({U, S, <<>>});
+jid_to_string(USR) ->
+ jlib:jid_to_string(USR).
+
+make_jid({undefined, S, undefined}) ->
+ jlib:make_jid(<<>>, S, <<>>);
+make_jid({U, S, undefined}) ->
+ jlib:make_jid({U, S, <<>>});
+make_jid(Entity) ->
+ jlib:make_jid(Entity).
+
+%% @doc Determine if data is a JID
+-spec(is_jid/1 ::
+(
+ Data :: binary() | undefined)
+ -> xmpp_jid:entity() | {error, 'jid-malformed'}
+).
+
+is_jid(Data) ->
+ try jlib:string_to_jid(Data) of
+ #jid{} = Jid -> Jid
+ catch
+ _Error -> {error, 'jid-malformed'}
+ end.
+
+%%%
+%-spec(get_value/2 ::
+%(
+% Options :: {Node_Options :: pubsub_options:options_node(),
+% Item_Options :: [] | pubsub_options:options_item()}
+% | pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription()
+% | [],
+% Key :: atom())
+% -> Value :: atom()
+% | binary()
+% | boolean()
+% | non_neg_integer()
+% | undefined
+% | []
+% | 'none'
+% | [atom(),...]
+% | [binary(),...]
+% | [boolean(),...]
+% | [non_neg_integer(),...]
+%).
+
+get_value({Node_Options, [] = _Item_Options}, Key) ->
+ get_value(Node_Options, Key, 0);
+%%
+get_value({Node_Options, Item_Options}, Key) ->
+ case get_value(Item_Options, Key, 'none') of
+ 'none' -> get_value(Node_Options, Key, false);
+ Value -> Value
+ end;
+%%
+get_value([], _Key) -> 'none';
+%%
+get_value(Options, Key) ->
+ get_value(Options, Key, 'none').
+
+%%%
+%-spec(get_value/3 ::
+%(
+% Options :: {Node_Options :: pubsub_options:options_node(),
+% Item_Options :: [] | pubsub_options:options_item()}
+% | pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription(),
+% Key :: atom(),
+% Default :: term())
+% -> Value :: atom()
+% | binary()
+% | boolean()
+% | non_neg_integer()
+% | undefined
+% | []
+% | [atom(),...]
+% | [binary(),...]
+% | [boolean(),...]
+% | [non_neg_integer(),...]
+%).
+get_value({Node_Options, [] = _Item_Options}, Key, Default) ->
+ get_value(Node_Options, Key, Default);
+%%
+get_value({Node_Options, Item_Options}, Key, Default) ->
+ case get_value(Item_Options, Key, 'none') of
+ 'none' -> get_value(Node_Options, Key, Default);
+ Value -> Value
+ end;
+%%
+get_value(Options, Key, Default) ->
+ case lists:keyfind(Key, 1, Options) of
+ {_Key, Value} -> Value;
+ false -> Default
+ end.
+
+%%%
+%-spec(set_value/3 ::
+%(
+% Options :: pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription(),
+% Key :: atom(),
+% Value :: atom()
+% | binary()
+% | boolean()
+% | non_neg_integer()
+% | undefined
+% | []
+% | [atom(),...]
+% | [binary(),...]
+% | [boolean(),...]
+% | [non_neg_integer(),...])
+% -> Options :: pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription()
+%).
+
+set_value(Options, Key, Value) ->
+ lists:keyreplace(Key, 1, Options, {Key, Value}).
+
+%%
+
+%-spec(get_option/2 ::
+%(
+% Options :: {Node_Options :: pubsub_options:options_node(),
+% Item_Options :: [] | pubsub_options:options_item()}
+% | pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription()
+% | [],
+% Key :: atom())
+% -> Option :: pubsub_options:option_item()
+% | pubsub_options:option_node()
+% | pubsub_options:option_subscription()
+% | 'none'
+%).
+
+get_option({Node_Options, [] = _Item_Options}, Key) ->
+ get_option(Node_Options, Key, 'none');
+%%
+get_option({Node_Options, Item_Options}, Key) ->
+ case get_option(Item_Options, Key, 'none') of
+ 'none' -> get_option(Node_Options, Key, 'none');
+ Option -> Option
+ end;
+%%
+get_option([], _Key) -> 'none';
+%%
+get_option(Options, Key) ->
+ get_option(Options, Key, 'none').
+
+%%%
+%-spec(get_option/3 ::
+%(
+% Options :: {Node_Options :: pubsub_options:options_node(),
+% Item_Options :: [] | pubsub_options:options_item()}
+% | pubsub_options:options_item()
+% | pubsub_options:options_node()
+% | pubsub_options:options_subscription(),
+% Key :: atom(),
+% Default :: term())
+% -> Option :: pubsub_options:option_item()
+% | pubsub_options:option_node()
+% | pubsub_options:option_subscription()
+% | term()
+%).
+
+get_option({Node_Options, [] = _Item_Options}, Key, Default) ->
+ get_option(Node_Options, Key, Default);
+%%
+get_option({Node_Options, Item_Options}, Key, Default) ->
+ case get_option(Item_Options, Key, 'none') of
+ 'none' -> get_option(Node_Options, Key, Default);
+ Option -> Option
+ end;
+%%
+get_option(Options, Key, Default) ->
+ case lists:keyfind(Key, 1, Options) of
+ {_Key, Value} -> _Option = {Key, Value};
+ false -> Default
+ end.
+
+%%
+-spec(get_entity_roster/1 ::
+(
+ Entity :: xmpp_jid:usr_entity()
+ | xmpp_jid:entity())
+ -> Roster::[Roster_Item::#roster{}]
+).
+
+get_entity_roster({U,S, _R} = _USR_Entity) ->
+ _Roster = ejabberd_hooks:run_fold(roster_get, S, [], [{U,S}]);
+get_entity_roster(#jid{luser = U, lserver = S} = _Jid_Entity) ->
+ _Roster = get_entity_roster({U,S, undefined}).
+
+%%
+-spec(check_access_model/3 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_entity(),
+ Criteria :: {Access_Model :: pubsub_options:access_model(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed()})
+ -> ok
+ %%%
+ | {error, 'forbidden'}
+).
+
+check_access_model(_Host, _Entity,
+ {_Access_Model, 'outcast' = _Affiliation, _Subscriptions, _Node_Owners,
+ _Rosters_Groups_Allowed}) ->
+ {error, 'forbidden'};
+%%
+check_access_model(_Host, _Entity,
+ {'open' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
+ _Rosters_Groups_Allowed}) ->
+ ok;
+%%
+check_access_model(Host, Entity,
+ {'presence' = _Access_Model, _Affiliation, _Subscriptions, Node_Owners,
+ _Rosters_Groups_Allowed}) ->
+ case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
+ false ->
+ {error, 'forbidden'};
+ _Node_Owner ->
+ ok
+ end;
+%%
+check_access_model(_Host, Entity,
+ {'roster' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
+ Rosters_Groups_Allowed}) ->
+ case is_contact_in_allowed_roster_groups(Entity, Rosters_Groups_Allowed) of
+ false ->
+ {error, 'forbidden'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ ok
+ end;
+%%
+check_access_model(_Host, _Entity,
+ {'whitelist' = _Access_Model, Affiliation, _Subscriptions, _Node_Owners,
+ _Roster_Groups_Allowed})
+ when Affiliation == 'member'
+ %%orelse Affiliation == 'owner'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ ok;
+check_access_model(_Host, _Entity,
+ {'whitelist' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
+ _Roster_Groups_Allowed}) ->
+ {error, 'forbidden'};
+%%
+check_access_model(_Host, _Entity,
+ {'authorize' = _Access_Model, _Affiliation, Subscriptions, _Node_Owners,
+ _Roster_Groups_Allowed}) ->
+ case has_subscriptions(Subscriptions) of
+ true -> ok;
+ false -> {error, 'forbidden'}
+ end.
+
+
+
+%%
+-spec(check_access_model/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_entity(),
+ Access_Model :: pubsub_options:access_model(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed())
+ -> ok
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+).
+
+check_access_model(_Host, _Entity, _Access_Model, 'owner' = _Affiliation,
+ _Subscriptions, _Node_Owners, _Rosters_Groups_Allowed) ->
+ ok;
+%%
+check_access_model(Host, Entity, Access_Model, 'outcast' = _Affiliation,
+ _Subscriptions, Node_Owners, Rosters_Groups_Allowed) ->
+ case Access_Model of
+ %%
+ 'open'->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ Rosters_Groups_Allowed)
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ {error, 'item-not-found'}
+ end;
+%%
+check_access_model(_Host, _Entity, 'open' = _Access_Model, _Affiliation,
+ _Subscriptions, _Node_Owners, _Rosters_Groups_Allowed) ->
+ ok;
+%%
+check_access_model(Host, Entity, 'presence' = _Access_Model, _Affiliation,
+ _Subscriptions, Node_Owners, _Rosters_Groups_Allowed) ->
+ case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ ok
+ end;
+%%
+check_access_model(_Host, Entity, 'roster' = _Access_Model, _Affiliation,
+ _Subscriptions, _Node_Owners, Rosters_Groups_Allowed) ->
+ case is_contact_in_allowed_roster_groups(Entity, Rosters_Groups_Allowed) of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ ok
+ end;
+%%
+check_access_model(_Host, _Entity, 'whitelist' = _Access_Model, Affiliation,
+ _Subscriptions, _Node_Owners, _Roster_Groups_Allowed) ->
+ case Affiliation of
+ Affiliation
+ when Affiliation == 'member'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ ok;
+ _Affiliation ->
+ {error, 'item-not-found'}
+ end;
+%%
+check_access_model(_Host, _Entity, 'authorize' = _Access_Model, _Affiliation,
+ Subscriptions, _Node_Owners, _Roster_Groups_Allowed) ->
+ case has_subscriptions(Subscriptions) of
+ true ->
+ ok;
+ false ->
+ {error, 'item-not-found'}
+ end.
+
+
+-spec(check_publish_model/3 ::
+(
+ Publish_Model :: pubsub_options:publish_model(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: exmpp_pubsub:subscriptions())
+ -> ok
+ %%%
+ | {error, 'forbidden'}
+).
+
+check_publish_model('open' = _Publish_Model, _Affiliation, _Subscriptions) ->
+ ok;
+%%
+check_publish_model('publishers' = _Publish_Model, Affiliation, _Subscriptions) ->
+ case Affiliation of
+ Affiliation
+ when Affiliation == 'owner'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ ok;
+ _Affiliation ->
+ {error, 'forbidden'}
+ end;
+%%
+check_publish_model('subscribers' = _Publish_Model, Affiliation, Subscriptions) ->
+ case Affiliation of
+ 'owner' ->
+ ok;
+ _Affiliation ->
+ case has_subscriptions(Subscriptions) of
+ true ->
+ ok;
+ false ->
+ {error, 'forbidden'}
+ end
+ end.
+
+%%
+-spec(has_subscriptions/1 ::
+(
+ Subscriptions :: exmpp_pubsub:subscriptions())
+ -> Has_Subscriptions::boolean()
+).
+
+has_subscriptions([] = _Subscriptions) ->
+ false;
+has_subscriptions(
+ [{'pending' = _Subscription_State, _SubId, _Resource, _Subscription_Options}]) ->
+ false;
+has_subscriptions(_Subscriptions) ->
+ true.
+
+%%
+%% @doc Check if a contact is subscribed to at least one local node owner
+%% amongst a list of (remote and local) node owners
+-spec(is_contact_subscribed_to_node_owners/3 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Contact :: xmpp_jid:usr_entity(),
+ Node_Owners :: [] | [Node_Owner::xmpp_jid:usr_bare(),...])
+ -> Is_Contact_Subscribed_To_Node_Owners :: false | xmpp_jid:usr_bare()
+).
+
+is_contact_subscribed_to_node_owners(_Host, _Contact, [] = _Node_Owners) ->
+ false;
+%% The node owner is a local entity, check if the contact is subscribed to it
+is_contact_subscribed_to_node_owners(Host, Contact,
+ [{_U, Host, _R} = Local_Node_Owner | Node_Owners] = _Node_Owners) ->
+ _Is_Contact_Subscribed_To_Node_Owners = case
+ is_contact_subscribed_to_entity(Contact,
+ _Local_Node_Owner_Roster = get_entity_roster(Local_Node_Owner))
+ of
+ true ->
+ Local_Node_Owner;
+ false ->
+ is_contact_subscribed_to_node_owners(Host, Contact, Node_Owners)
+ end;
+%% The node owner is a remote entity, don't check if the contact is subscribed to it
+is_contact_subscribed_to_node_owners(Host, Contact,
+ [_Remote_Node_Owner | Node_Owners] = _Node_Owners) ->
+ is_contact_subscribed_to_node_owners(Host, Contact, Node_Owners).
+
+%%
+%% @doc Check if an entity is in a #roster{}
+%% with #roster.subscription == 'from' or
+%% with #roster.subscription == 'both'
+-spec(is_contact_subscribed_to_entity/2 ::
+(
+ Contact :: xmpp_jid:usr_entity(),
+ Entity_Roster :: [] | [Roster_Item::#roster{},...])
+ -> Is_Contact_Subscribed::boolean()
+).
+
+is_contact_subscribed_to_entity(_Contact, [] = _Entity_Roster) ->
+ _Is_Contact_Subscribed = false;
+%% Contact is included in a #roster{} item with
+%% a subscription of type 'from' and 'both' (i.e. is subscribed to it)
+is_contact_subscribed_to_entity({U, S, _R} = _Contact,
+ [#roster{jid = {U, S, _}, subscription = Subscription} = _Roster_Item
+ | _Roster_Items] = _Entity_Roster)
+ when Subscription == 'from' orelse Subscription == 'both' ->
+ _Is_Contact_Subscribed = true;
+%% Contact is not included in a #roster{} item
+%% or has a subscription of type different than 'from' and 'both'
+%% ( i.e. is not subscribed to it),
+%% check next roster items
+is_contact_subscribed_to_entity(Contact,
+ [_Roster_Item | Roster_Items] = _Entity_Roster) ->
+ _Is_Contact_Subscribed = is_contact_subscribed_to_entity(Contact,
+ Roster_Items).
+
+%%
+-spec(is_contact_in_allowed_roster_groups/2 ::
+(
+ Contact :: xmpp_jid:usr_entity(),
+ Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed())
+ -> Roster_Group_Allowed :: false
+ | {Entity :: xmpp_jid:usr_bare(),
+ Roster_Group :: pubsub_options:roster_group()}
+).
+
+is_contact_in_allowed_roster_groups(_Contact, [] = _Rosters_Groups_Allowed) ->
+ false;
+%%
+is_contact_in_allowed_roster_groups(Contact,
+ [{Entity, Entity_Roster_Groups_Allowed} | Rosters_Groups_Allowed]
+ = _Rosters_Groups_Allowed) ->
+ case
+ is_contact_in_allowed_roster_group(Contact,
+ _Entity_Roster = get_entity_roster(Entity),
+ Entity_Roster_Groups_Allowed)
+ of
+ false ->
+ is_contact_in_allowed_roster_groups(Contact, Rosters_Groups_Allowed);
+ Roster_Group ->
+ {Entity, Roster_Group}
+ end.
+
+%%
+-spec(is_contact_in_allowed_roster_group/3 ::
+(
+ Contact :: xmpp_jid:usr_entity(),
+ Entity_Roster :: [] | [Roster_Item::#roster{groups::pubsub_options:roster_groups()}],
+ Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group(),...])
+ -> Roster_Group :: false | pubsub_options:roster_group()
+).
+
+is_contact_in_allowed_roster_group(_Contact, [] = _Entity_Roster,
+ _Roster_Groups_Allowed) ->
+ false;
+%%
+is_contact_in_allowed_roster_group({U, S, _R} = _Contact,
+ [#roster{jid = {U, S, _}, subscription = Subscription, groups = Roster_Groups}
+ | _Other_Roster_Items] = _Entity_Roster, Roster_Groups_Allowed)
+ when Subscription == 'from' orelse Subscription == 'both' ->
+ is_contact_in_allowed_roster_group(Roster_Groups, Roster_Groups_Allowed);
+%%
+is_contact_in_allowed_roster_group(Contact,
+ [_Roster_Item | Roster_Items] = _Entity_Roster, Roster_Groups_Allowed) ->
+ is_contact_in_allowed_roster_group(Contact, Roster_Items,
+ Roster_Groups_Allowed).
+
+%%
+-spec(is_contact_in_allowed_roster_group/2 ::
+(
+ Roster_Groups :: [] | [Roster_Group::pubsub_options:roster_group()],
+ Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group(),...])
+ -> Roster_Group :: false | pubsub_options:roster_group()
+).
+
+is_contact_in_allowed_roster_group([] = _Roster_Groups, _Roster_Groups_Allowed) ->
+ false;
+%%
+is_contact_in_allowed_roster_group([Roster_Group | Roster_Groups]
+ = _Roster_Groups, Roster_Groups_Allowed) ->
+ case lists:member(Roster_Group, Roster_Groups_Allowed) of
+ true ->
+ Roster_Group;
+ false ->
+ is_contact_in_allowed_roster_group(Roster_Groups,
+ Roster_Groups_Allowed)
+ end.
+
+%%
+-spec(get_user_resources/2 ::
+(
+ User :: binary(),
+ Server :: binary())
+ -> Resources::[Resource::xmpp_jid:resource_jid()]
+).
+
+get_user_resources(User, Server) ->
+ ejabberd_sm:get_user_resources(User, Server).
+
+%%
+-spec(get_resources_show/2 ::
+(
+ User :: binary(),
+ Server :: binary())
+ -> Resources_Show :: [{Resource :: xmpp_jid:resource_jid(),
+ Show :: 'away' | 'chat' | 'dnd' | 'online' | 'xa' }]
+).
+
+get_resources_show(User, Server) ->
+ _Resources_Show = lists:foldl(fun
+ (Resource, Resources_Show) ->
+ case ejabberd_sm:get_session_pid(User, Server, Resource) of
+ C2SPid when is_pid(C2SPid) ->
+ case ejabberd_c2s:get_presence(C2SPid) of
+ {_User, _Resource, Show, _} ->
+ [{Resource, list_to_atom(Show)} | Resources_Show];
+ _ ->
+ Resources_Show
+ end;
+ _ ->
+ Resources_Show
+ end
+ end, [], _Resources = ejabberd_sm:get_user_resources(User, Server)).
+
+
+%%
+-spec(rosters_groups_allowed_cache/2 ::
+(
+ Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed(),
+ Rosters_Groups_Allowed_Cache :: pubsub_options:rosters_groups_allowed())
+ -> Is_Roster_Groups_Cached::boolean()
+).
+
+rosters_groups_allowed_cache(_Rosters_Groups_Allowed,
+ [] = _Rosters_Groups_Allowed_Cache) ->
+ false;
+%%
+rosters_groups_allowed_cache([] = _Rosters_Groups_Allowed,
+ _Rosters_Groups_Allowed_Cache) ->
+ false;
+%%
+rosters_groups_allowed_cache(
+ [{Entity, Roster_Groups_Allowed} | Rosters_Groups_Allowed],
+ Rosters_Groups_Allowed_Cache) ->
+ case lists:keyfind(Entity, 1, Rosters_Groups_Allowed_Cache) of
+ {_Entity, Roster_Groups_Allowed_Cache} ->
+ case
+ roster_groups_allowed_cache(Roster_Groups_Allowed,
+ Roster_Groups_Allowed_Cache)
+ of
+ true ->
+ true;
+ false ->
+ rosters_groups_allowed_cache(Rosters_Groups_Allowed,
+ Rosters_Groups_Allowed_Cache)
+ end;
+ false ->
+ rosters_groups_allowed_cache(Rosters_Groups_Allowed,
+ Rosters_Groups_Allowed_Cache)
+ end.
+
+%%
+-spec(roster_groups_allowed_cache/2 ::
+(
+ Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group()],
+ Roster_Groups_Allowed_Cache :: [Roster_Group_Allowed_Cache::pubsub_options:roster_group()])
+ -> Is_Roster_Groups_Cached :: boolean()
+).
+
+roster_groups_allowed_cache(_Roster_Groups_Allowed,
+ [] = _Roster_Groups_Allowed_Cache) ->
+ false;
+%%
+roster_groups_allowed_cache([] = _Roster_Groups_Allowed,
+ _Roster_Groups_Allowed_Cache) ->
+ false;
+%%
+roster_groups_allowed_cache([Roster_Group_Allowed | Roster_Groups_Allowed],
+ Roster_Groups_Allowed_Cache) ->
+ case lists:member(Roster_Group_Allowed, Roster_Groups_Allowed_Cache) of
+ true ->
+ true;
+ false ->
+ roster_groups_allowed_cache(Roster_Groups_Allowed,
+ Roster_Groups_Allowed_Cache)
+ end.
+
diff --git a/src/mod_pubsub_ng/xmpp_xdata.erl b/src/mod_pubsub_ng/xmpp_xdata.erl
new file mode 100644
index 000000000..879549c46
--- /dev/null
+++ b/src/mod_pubsub_ng/xmpp_xdata.erl
@@ -0,0 +1,108 @@
+-module(xmpp_xdata).
+
+
+-include("jlib.hrl").
+-include("ejabberd.hrl").
+-compile(export_all).
+
+xmlcdata(CData) ->
+ {xmlcdata, CData}.
+
+xmlattr(Name, Value) ->
+% #xmlattr{name = Name, value = Value}.
+ {Name, Value}.
+
+xmlattr_label(Label) ->
+ xmlattr(<<"label">>, Label).
+
+xmlattr_type(Type) ->
+ xmlattr(<<"type">>, Type).
+
+xmlattr_var(Var) ->
+ xmlattr(<<"var">>, Var).
+
+
+xmlel(NS, Name, Attrs, Children) ->
+ #xmlel{name = Name, attrs = [{<<"xmlns">>, NS} | Attrs], children = Children}.
+
+xmlel(Name, Attrs, Children) ->
+ xmlel(?NS_XDATA, Name, Attrs, Children).
+
+xmlel_desc(CData) ->
+ xmlel(<<"desc">>, [], [xmlcdata(CData)]).
+
+xmlel_value(Value) ->
+ xmlel(<<"value">>, [], [xmlcdata(Value)]).
+
+xmlel_option({Value, Label}) ->
+ xmlel(<<"option">>, [xmlattr_label(Label)], [xmlel_value(Value)]);
+xmlel_option(Value) ->
+ xmlel(<<"option">>, [], [xmlel_value(Value)]).
+
+xmlel_field(Var, Type, Values) ->
+ xmlel(<<"field">>,
+ [xmlattr_var(Var),
+ xmlattr_type(Type)],
+ [xmlel_value(Value) || Value <- Values]).
+
+xmlel_field(Var, Type, Label, Values, Options) ->
+ xmlel(<<"field">>,
+ [xmlattr_var(Var),
+ xmlattr_label(Label),
+ xmlattr_type(Type)],
+ [xmlel_option(Option) || Option <- Options]
+ ++
+ [xmlel_value(Value) || Value <- Values]).
+
+xmlel_field_boolean(Var, Label, Value) ->
+ xmlel_field(Var, <<"boolean">>, Label, [Value], []).
+
+%%
+xmlel_field_fixed(Var, Label, Value) ->
+ xmlel_field(Var, <<"fixed">>, Label, [Value], []).
+
+%%
+xmlel_field_hidden(Var, Value) ->
+ xmlel_field(Var, <<"hidden">>, [Value]).
+
+%%
+xmlel_field_list_single(Var, Label, Value, Options) ->
+ xmlel_field(Var, <<"list-single">>, Label, [Value], Options).
+
+%%
+xmlel_field_list_multi(Var, Label, Values, Options) ->
+ xmlel_field(Var, <<"list-multi">>, Label, Values, Options).
+
+%%
+xmlel_field_text_single(Var, Label, undefined = _Text) ->
+ xmlel_field(Var, <<"text-single">>, Label, [], []);
+%%
+xmlel_field_text_single(Var, Label, Text) ->
+ xmlel_field(Var, <<"text-single">>, Label, [Text], []).
+
+%%
+xmlel_field_text_multi(Var, Label, Values) ->
+ xmlel_field(Var, <<"text-multi">>, Label, Values, []).
+
+%%
+xmlel_field_jid_multi(Var, Label, Jids) ->
+ xmlel_field(Var, <<"jid-multi">>, Label, Jids, []).
+
+%%
+xmlel_field_jid_single(Var, Label, Jid) ->
+ xmlel_field(Var, <<"jid-single">>, Label, [Jid], []).
+
+%%
+xmlel_x(Type, Fields) ->
+ xmlel(<<"x">>, [xmlattr_type(Type)], Fields).
+
+%%
+xmlel_title(Title) ->
+ xmlel(<<"title">>, [], [xmlcdata(Title)]).
+
+%%
+xmlel_instructions(Instructions) when is_list(Instructions) ->
+ lists:map(fun xmlel_instructions/1, Instructions);
+%%
+xmlel_instructions(Instruction) when is_binary(Instruction) ->
+ xmlel(<<"instructions">>, [], [xmlcdata(Instruction)]).