diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2013-04-08 11:12:54 +0200 |
---|---|---|
committer | Christophe Romain <christophe.romain@process-one.net> | 2013-06-13 11:11:02 +0200 |
commit | 4d8f7706240a1603468968f47fc7b150b788d62f (patch) | |
tree | 92d55d789cc7ac979b3c9e161ffb7f908eba043a /src/mod_pubsub | |
parent | Fix Guide: ejabberd_service expects a shaper_rule, not a shaper (diff) |
Switch to rebar build tool
Use dynamic Rebar configuration
Make iconv dependency optional
Disable transient_supervisors compile option
Add hipe compilation support
Only compile ibrowse and lhttpc when needed
Make it possible to generate an OTP application release
Add --enable-debug compile option
Add --enable-all compiler option
Add --enable-tools configure option
Add --with-erlang configure option.
Add --enable-erlang-version-check configure option.
Add lager support
Improve the test suite
Diffstat (limited to 'src/mod_pubsub')
30 files changed, 0 insertions, 20648 deletions
diff --git a/src/mod_pubsub/Makefile.in b/src/mod_pubsub/Makefile.in deleted file mode 100644 index 88bf2ba0c..000000000 --- a/src/mod_pubsub/Makefile.in +++ /dev/null @@ -1,46 +0,0 @@ -# $Id$ - -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: mod_pubsub_odbc.erl $(ERLBEHAVBEAMS) $(BEAMS) - -$(BEAMS): $(ERLBEHAVBEAMS) - -$(OUTDIR)/%.beam: %.erl - @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< - -clean: - rm -f $(BEAMS) - -distclean: clean - rm -f Makefile - -mod_pubsub_odbc.erl: - patch -o mod_pubsub_odbc.erl mod_pubsub.erl pubsub_odbc.patch - -TAGS: - etags *.erl - diff --git a/src/mod_pubsub/Makefile.win32 b/src/mod_pubsub/Makefile.win32 deleted file mode 100644 index 8308f242a..000000000 --- a/src/mod_pubsub/Makefile.win32 +++ /dev/null @@ -1,87 +0,0 @@ - -include ..\Makefile.inc - -EFLAGS = -I .. -pz .. - -OUTDIR = .. -BEAMS = ..\gen_pubsub_node.beam ..\gen_pubsub_nodetree.beam ..\mod_pubsub.beam ..\mod_pubsub_odbc.beam ..\node_buddy.beam ..\node_club.beam ..\node_dag.beam ..\node_dispatch.beam ..\node_flat.beam ..\node_flat_odbc.beam ..\node_hometree.beam ..\node_hometree_odbc.beam ..\node_mb.beam ..\node_pep.beam ..\node_pep_odbc.beam ..\node_private.beam ..\node_public.beam ..\nodetree_dag.beam ..\nodetree_tree.beam ..\nodetree_tree_odbc.beam ..\nodetree_virtual.beam ..\pubsub_db_odbc.beam ..\pubsub_index.beam ..\pubsub_subscription.beam ..\pubsub_subscription_odbc.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/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl deleted file mode 100644 index 0cf1fd2ff..000000000 --- a/src/mod_pubsub/gen_pubsub_node.erl +++ /dev/null @@ -1,267 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @private -%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node -%%% plugin behaviour. This behaviour is used to check that a PubSub plugin -%%% respects the current ejabberd PubSub plugin API.</p> - --module(gen_pubsub_node). - --include("jlib.hrl"). - --type(host() :: mod_pubsub:host() - | mod_pubsub_odbc:host() -). - --type(nodeId() :: mod_pubsub:nodeId() - | mod_pubsub_odbc:nodeId() -). - --type(nodeIdx() :: mod_pubsub:nodeIdx() - | mod_pubsub_odbc:nodeIdx() -). - --type(itemId() :: mod_pubsub:itemId() - | mod_pubsub_odbc:itemId() -). - --type(pubsubNode() :: mod_pubsub:pubsubNode() - | mod_pubsub_odbc:pubsubNode() -). - --type(pubsubState() :: mod_pubsub:pubsubState() - | mod_pubsub_odbc:pubsubState() -). - --type(pubsubItem() :: mod_pubsub:pubsubItem() - | mod_pubsub_odbc:pubsubItem() -). - --type(nodeOptions() :: mod_pubsub:nodeOptions() - | mod_pubsub_odbc:nodeOptions() -). - --type(subOptions() :: mod_pubsub:subOptions() - | mod_pubsub_odbc:subOptions() -). - --type(affiliation() :: mod_pubsub:affiliation() - | mod_pubsub_odbc:affiliation() -). - --type(subscription() :: mod_pubsub:subscription() - | mod_pubsub_odbc:subscription() -). - --type(subId() :: mod_pubsub:subId() - | mod_pubsub_odbc:subId() -). - --type(accessModel() :: mod_pubsub:accessModel() - | mod_pubsub_odbc:accessModel() -). - --type(publishModel() :: mod_pubsub:publishModel() - | mod_pubsub_odbc:publishModel() -). - --type(payload() :: mod_pubsub:payload() - | mod_pubsub_odbc:payload() -). - --callback init(Host :: binary(), - ServerHost :: binary(), - Opts :: [any()]) -> atom(). - --callback terminate(Host :: host(), - ServerHost :: binary()) -> atom(). - --callback options() -> [{atom(), any()}]. - --callback features() -> [binary()]. - --callback create_node_permission(Host :: host(), - ServerHost :: binary(), - Node :: nodeId(), - ParentNode :: nodeId(), - Owner :: jid(), Access :: atom()) -> - {result, boolean()}. - --callback create_node(NodeIdx :: nodeIdx(), - Owner :: jid()) -> - {result, {default, broadcast}}. - --callback delete_node(Nodes :: [pubsubNode(),...]) -> - {result, - {default, broadcast, - [{pubsubNode(), - [{ljid(), [{subscription(), subId()}]},...]},...] - } - } - | - {result, - {[], - [{pubsubNode(), - [{ljid(), [{subscription(), subId()}]},...]},...] - } - }. - --callback purge_node(NodeIdx :: nodeIdx(), - Owner :: jid()) -> - {result, {default, broadcast}} | - {error, xmlel()}. - --callback subscribe_node(NodeIdx :: nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - AccessModel :: accessModel(), - SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - Options :: subOptions()) -> - {result, {default, subscribed, subId()}} | - {result, {default, subscribed, subId(), send_last}} | - {result, {default, pending, subId()}} | - {error, xmlel()}. - --callback unsubscribe_node(NodeIdx :: nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - SubId :: subId()) -> - {result, default} | - {error, xmlel()}. - --callback publish_item(NodeId :: nodeIdx(), - Publisher :: jid(), - PublishModel :: publishModel(), - Max_Items :: non_neg_integer(), - ItemId :: <<>> | itemId(), - Payload :: payload()) -> - {result, {default, broadcast, [itemId()]}} | - {error, xmlel()}. - --callback delete_item(NodeIdx :: nodeIdx(), - Publisher :: jid(), - PublishModel :: publishModel(), - ItemId :: <<>> | itemId()) -> - {result, {default, broadcast}} | - {error, xmlel()}. - --callback remove_extra_items(NodeIdx :: nodeIdx(), - Max_Items :: unlimited | non_neg_integer(), - ItemIds :: [itemId()]) -> - {result, {[itemId()], [itemId()]} - }. - --callback get_node_affiliations(NodeIdx :: nodeIdx()) -> - {result, [{ljid(), affiliation()}]}. - --callback get_entity_affiliations(Host :: host(), - Owner :: jid()) -> - {result, [{pubsubNode(), affiliation()}]}. - --callback get_affiliation(NodeIdx :: nodeIdx(), - Owner :: jid()) -> - {result, affiliation()}. - --callback set_affiliation(NodeIdx :: nodeIdx(), - Owner :: ljid(), - Affiliation :: affiliation()) -> - ok | - {error, xmlel()}. - --callback get_node_subscriptions(NodeIdx :: nodeIdx()) -> - {result, - [{ljid(), subscription(), subId()}] | - [{ljid(), none},...] - }. - --callback get_entity_subscriptions(Host :: host(), - Owner :: jid()) -> - {result, [{pubsubNode(), subscription(), subId(), ljid()}] - }. - --callback get_subscriptions(NodeIdx :: nodeIdx(), - Owner :: ljid()) -> - {result, [{subscription(), subId()}]}. - --callback get_pending_nodes(Host :: host(), - Owner :: jid()) -> - {result, [nodeId()]}. - --callback get_states(NodeIdx::nodeIdx()) -> - {result, [pubsubState()]}. - --callback get_state(NodeIdx :: nodeIdx(), - JID :: ljid()) -> - pubsubState(). - --callback set_state(State::pubsubState()) -> - ok | - {error, xmlel()}. - --callback get_items(NodeIdx :: nodeIdx(), - JID :: jid(), - AccessModel :: accessModel(), - Presence_Subscription :: boolean(), - RosterGroup :: boolean(), - SubId :: subId()) -> - {result, [pubsubItem()]} | - {error, xmlel()}. - --callback get_items(NodeIdx :: nodeIdx(), - From :: jid()) -> - {result, [pubsubItem()]}. - --callback get_item(NodeIdx :: nodeIdx(), - ItemId :: itemId(), - JID :: jid(), - AccessModel :: accessModel(), - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - SubId :: subId()) -> - {result, pubsubItem()} | - {error, xmlel()}. - --callback get_item(NodeIdx :: nodeIdx(), - ItemId :: itemId()) -> - {result, pubsubItem()} | - {error, xmlel()}. - --callback set_item(Item :: pubsubItem()) -> - ok. -% | {error, _}. - --callback get_item_name(Host :: host(), - ServerHost :: binary(), - Node :: nodeId()) -> - itemId(). - --callback node_to_path(Node :: nodeId()) -> - [nodeId()]. - --callback path_to_node(Node :: [nodeId()]) -> - nodeId(). diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl deleted file mode 100644 index 8acba659c..000000000 --- a/src/mod_pubsub/gen_pubsub_nodetree.erl +++ /dev/null @@ -1,124 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @private -%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node -%%% tree plugin behaviour. This behaviour is used to check that a PubSub -%%% node tree plugin respects the current ejabberd PubSub plugin API.</p> - --module(gen_pubsub_nodetree). - --include("jlib.hrl"). - --type(host() :: mod_pubsub:host() - | mod_pubsub_odbc:host() -). - --type(nodeId() :: mod_pubsub:nodeId() - | mod_pubsub_odbc:nodeId() -). - --type(nodeIdx() :: mod_pubsub:nodeIdx() - | mod_pubsub_odbc:nodeIdx() -). - --type(itemId() :: mod_pubsub:itemId() - | mod_pubsub_odbc:itemId() -). - --type(pubsubNode() :: mod_pubsub:pubsubNode() - | mod_pubsub_odbc:pubsubNode() -). - --type(nodeOptions() :: mod_pubsub:nodeOptions() - | mod_pubsub_odbc:nodeOptions() -). - --callback init(Host :: host(), - ServerHost :: binary(), - Opts :: [any()]) -> atom(). - --callback terminate(Host :: host(), ServerHost :: binary()) -> atom(). - --callback options() -> nodeOptions(). - --callback set_node(PubsubNode :: pubsubNode()) -> - ok | {result, NodeIdx::mod_pubsub_odbc:nodeIdx()} | {error, xmlel()}. - --callback get_node(Host :: host(), - NodeId :: nodeId(), - From :: jid()) -> - pubsubNode() | - {error, xmlel()}. - --callback get_node(Host :: host(), - NodeId :: nodeId()) -> - pubsubNode() | - {error, xmlel()}. - --callback get_node(NodeIdx :: nodeIdx()) -> - pubsubNode() | - {error, xmlel()}. - --callback get_nodes(Host :: host(), - From :: jid())-> - [pubsubNode()]. - --callback get_nodes(Host :: host())-> - [pubsubNode()]. - --callback get_parentnodes(Host :: host(), - NodeId :: nodeId(), - From :: jid()) -> - [pubsubNode()] | - {error, xmlel()}. - --callback get_parentnodes_tree(Host :: host(), - NodeId :: nodeId(), - From :: jid()) -> - [{0, [pubsubNode(),...]}]. - --callback get_subnodes(Host :: host(), - NodeId :: nodeId(), - From :: ljid()) -> - [pubsubNode()]. - --callback get_subnodes_tree(Host :: host(), - NodeId :: nodeId(), - From :: ljid()) -> - [pubsubNode()]. - --callback create_node(Host :: host(), - NodeId :: nodeId(), - Type :: binary(), - Owner :: jid(), - Options :: nodeOptions(), - Parents :: [nodeId()]) -> - {ok, NodeIdx::nodeIdx()} | - {error, xmlel()}. - --callback delete_node(Host :: host(), - NodeId :: nodeId()) -> - [pubsubNode()]. diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl deleted file mode 100644 index 53b32d9de..000000000 --- a/src/mod_pubsub/mod_pubsub.erl +++ /dev/null @@ -1,5402 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the core of the PubSub -%%% extension. It relies on PubSub plugins for a large part of its functions. -%%% -%%% @headerfile "pubsub.hrl" -%%% -%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for -%%% the latest version of the PubSub specification. -%%% This module uses version 1.12 of the specification as a base. -%%% Most of the specification is implemented. -%%% Functions concerning configuration should be rewritten. -%%% -%%% Support for subscription-options and multi-subscribe features was -%%% added by Brian Cully (bjc AT kublai.com). Subscriptions and options are -%%% stored in the pubsub_subscription table, with a link to them provided -%%% by the subscriptions field of pubsub_state. For information on -%%% subscription-options and mulit-subscribe see XEP-0060 sections 6.1.6, -%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see -%%% XEP-0060 section 12.18. - --module(mod_pubsub). - --author('christophe.romain@process-one.net'). - --version('1.13-0'). - --behaviour(gen_server). - --behaviour(gen_mod). - --include("ejabberd.hrl"). - --include("adhoc.hrl"). - --include("jlib.hrl"). - --include("pubsub.hrl"). - --define(STDTREE, <<"tree">>). - --define(STDNODE, <<"flat">>). - --define(PEPNODE, <<"pep">>). - -%% exports for hooks --export([presence_probe/3, caps_update/3, - in_subscription/6, out_subscription/4, - on_user_offline/3, remove_user/2, - disco_local_identity/5, disco_local_features/5, - disco_local_items/5, disco_sm_identity/5, - disco_sm_features/5, disco_sm_items/5]). - -%% exported iq handlers --export([iq_sm/3]). - -%% exports for console debug manual use --export([create_node/5, - delete_node/3, - subscribe_node/5, - unsubscribe_node/5, - publish_item/6, - delete_item/4, - send_items/6, - get_items/2, - get_item/3, - get_cached_item/2, - broadcast_stanza/9, - get_configure/5, - set_configure/5, - tree_action/3, - node_action/4 - ]). - -%% general helpers for plugins --export([subscription_to_string/1, affiliation_to_string/1, - string_to_subscription/1, string_to_affiliation/1, - extended_error/2, extended_error/3, - rename_default_nodeplugin/0]). - -%% 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]). - -%% calls for parallel sending of last items --export([send_loop/1]). - --define(PROCNAME, ejabberd_mod_pubsub). - --define(LOOPNAME, ejabberd_mod_pubsub_loop). - -%%==================================================================== -%% API -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- --define(PLUGIN_PREFIX, <<"node_">>). - --define(TREE_PREFIX, <<"nodetree_">>). - -% --export_type([ - host/0, - hostPubsub/0, - hostPEP/0, - %% - nodeIdx/0, - nodeId/0, - itemId/0, - subId/0, - payload/0, - %% - nodeOption/0, - nodeOptions/0, - subOption/0, - subOptions/0, - %% - affiliation/0, - subscription/0, - accessModel/0, - publishModel/0 -]). - -%% -type payload() defined here because the -type xmlel() is not accessible -%% from pubsub.hrl --type(payload() :: [] | [xmlel(),...]). - --export_type([ - pubsubNode/0, - pubsubState/0, - pubsubItem/0, - pubsubSubscription/0, - pubsubLastItem/0 -]). - --type(pubsubNode() :: - #pubsub_node{ - nodeid :: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()}, - id :: mod_pubsub:nodeIdx(), - parents :: [Parent_NodeId::mod_pubsub:nodeId()], - type :: binary(), - owners :: [Owner::ljid(),...], - options :: mod_pubsub:nodeOptions() - } -). - --type(pubsubState() :: - #pubsub_state{ - stateid :: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()}, - items :: [ItemId::mod_pubsub:itemId()], - affiliation :: mod_pubsub:affiliation(), - subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}] - } -). - --type(pubsubItem() :: - #pubsub_item{ - itemid :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()}, - creation :: {erlang:timestamp(), ljid()}, - modification :: {erlang:timestamp(), ljid()}, - payload :: mod_pubsub:payload() - } -). - --type(pubsubSubscription() :: - #pubsub_subscription{ - subid :: mod_pubsub:subId(), - options :: [] | mod_pubsub:subOptions() - } -). - --type(pubsubLastItem() :: - #pubsub_last_item{ - nodeid :: mod_pubsub:nodeIdx(), - itemid :: mod_pubsub:itemId(), - creation :: {erlang:timestamp(), ljid()}, - payload :: mod_pubsub:payload() - } -). - --record(state, -{ - server_host, - host, - access, - pep_mapping = [], - ignore_pep_from_offline = true, - last_item_cache = false, - max_items_node = ?MAXITEMS, - nodetree = ?STDTREE, - plugins = [?STDNODE] -}). - --type(state() :: - #state{ - server_host :: binary(), - host :: mod_pubsub:hostPubsub(), - access :: atom(), - pep_mapping :: [{binary(), binary()}], - ignore_pep_from_offline :: boolean(), - last_item_cache :: boolean(), - max_items_node :: non_neg_integer(), - nodetree :: binary(), - plugins :: [binary(),...] - } - -). - - -start_link(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, - [Host, Opts], []). - -start(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, - transient, 1000, worker, [?MODULE]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:call(Proc, stop), - supervisor:delete_child(ejabberd_sup, Proc). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- --spec(init/1 :: -( - _:: _) - -> {ok, state()} -). - -init([ServerHost, Opts]) -> - ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), - Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), - Access = gen_mod:get_opt(access_createnode, Opts, - fun(A) when is_atom(A) -> A end, all), - PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, - fun(A) when is_boolean(A) -> A end, true), - IQDisc = gen_mod:get_opt(iqdisc, Opts, - fun(A) when is_atom(A) -> A end, one_queue), - LastItemCache = gen_mod:get_opt(last_item_cache, Opts, - fun(A) when is_boolean(A) -> A end, false), - MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, - fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS), - pubsub_index:init(Host, ServerHost, Opts), - ets:new(gen_mod:get_module_proc(Host, config), - [set, named_table]), - ets:new(gen_mod:get_module_proc(ServerHost, config), - [set, named_table]), - {Plugins, NodeTree, PepMapping} = init_plugins(Host, - ServerHost, Opts), - mnesia:create_table(pubsub_last_item, - [{ram_copies, [node()]}, - {attributes, record_info(fields, pubsub_last_item)}]), - mod_disco:register_feature(ServerHost, ?NS_PUBSUB), - ets:insert(gen_mod:get_module_proc(Host, config), - {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(Host, config), - {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(Host, config), - {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(Host, config), - {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {pep_mapping, PepMapping}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {ignore_pep_from_offline, PepOffline}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {host, Host}), - ejabberd_hooks:add(sm_remove_connection_hook, - ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:add(disco_local_identity, ServerHost, - ?MODULE, disco_local_identity, 75), - ejabberd_hooks:add(disco_local_features, ServerHost, - ?MODULE, disco_local_features, 75), - ejabberd_hooks:add(disco_local_items, ServerHost, - ?MODULE, disco_local_items, 75), - ejabberd_hooks:add(presence_probe_hook, ServerHost, - ?MODULE, presence_probe, 80), - ejabberd_hooks:add(roster_in_subscription, ServerHost, - ?MODULE, in_subscription, 50), - ejabberd_hooks:add(roster_out_subscription, ServerHost, - ?MODULE, out_subscription, 50), - ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, ServerHost, - ?MODULE, remove_user, 50), - case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, - caps_update, 80), - ejabberd_hooks:add(disco_sm_identity, ServerHost, - ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:add(disco_sm_features, ServerHost, - ?MODULE, disco_sm_features, 75), - ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, - disco_sm_items, 75), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, - ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, - ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, - IQDisc); - false -> ok - end, - ejabberd_router:register_route(Host), - update_node_database(Host, ServerHost), - update_state_database(Host, ServerHost), - put(server_host, ServerHost), - init_nodes(Host, ServerHost, NodeTree, Plugins), - State = #state{host = Host, server_host = ServerHost, - access = Access, pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, nodetree = NodeTree, - plugins = Plugins}, - init_send_loop(ServerHost, State), - {ok, State}. - -init_send_loop(ServerHost, State) -> - Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME), - SendLoop = spawn(?MODULE, send_loop, [State]), - register(Proc, SendLoop), - SendLoop. - -%% @spec (Host, ServerHost, Opts) -> Plugins -%% Host = mod_pubsub:host() Opts = [{Key,Value}] -%% ServerHost = host() -%% Key = atom() -%% Value = term() -%% Plugins = [Plugin::string()] -%% @doc Call the init/1 function for each plugin declared in the config file. -%% The default plugin module is implicit. -%% <p>The Erlang code for the plugin is located in a module called -%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p> -%% <p>The modules are initialized in alphetical order and the list is checked -%% and sorted to ensure that each module is initialized only once.</p> -%% <p>See {@link node_hometree:init/1} for an example implementation.</p> -init_plugins(Host, ServerHost, Opts) -> - TreePlugin = - jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, - (gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end, - ?STDTREE))/binary>>), - ?DEBUG("** tree plugin is ~p", [TreePlugin]), - TreePlugin:init(Host, ServerHost, Opts), - Plugins = gen_mod:get_opt(plugins, Opts, - fun(A) when is_list(A) -> A end, [?STDNODE]), - PepMapping = gen_mod:get_opt(pep_mapping, Opts, - fun(A) when is_list(A) -> A end, []), - ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]), - PluginsOK = lists:foldl(fun (Name, Acc) -> - Plugin = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Name/binary>>), - case catch apply(Plugin, init, - [Host, ServerHost, Opts]) - of - {'EXIT', _Error} -> Acc; - _ -> - ?DEBUG("** init ~s plugin", [Name]), - [Name | Acc] - end - end, - [], Plugins), - {lists:reverse(PluginsOK), TreePlugin, PepMapping}. - -terminate_plugins(Host, ServerHost, Plugins, - TreePlugin) -> - lists:foreach(fun (Name) -> - ?DEBUG("** terminate ~s plugin", [Name]), - Plugin = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Name/binary>>), - Plugin:terminate(Host, ServerHost) - end, - Plugins), - TreePlugin:terminate(Host, ServerHost), - ok. - -init_nodes(Host, ServerHost, _NodeTree, Plugins) -> - case lists:member(<<"hometree">>, Plugins) of - true -> - create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>), - create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), - <<"hometree">>); - false -> ok - end. - -update_node_database(Host, ServerHost) -> - mnesia:del_table_index(pubsub_node, type), - mnesia:del_table_index(pubsub_node, parentid), - case catch mnesia:table_info(pubsub_node, attributes) of - [host_node, host_parent, info] -> - ?INFO_MSG("upgrade node pubsub tables", []), - F = fun () -> - {Result, LastIdx} = lists:foldl(fun ({pubsub_node, - NodeId, ParentId, - {nodeinfo, Items, - Options, - Entities}}, - {RecList, - NodeIdx}) -> - ItemsList = - lists:foldl(fun - ({item, - IID, - Publisher, - Payload}, - Acc) -> - C = - {unknown, - Publisher}, - M = - {now(), - Publisher}, - mnesia:write(#pubsub_item{itemid - = - {IID, - NodeIdx}, - creation - = - C, - modification - = - M, - payload - = - Payload}), - [{Publisher, - IID} - | Acc] - end, - [], - Items), - Owners = - dict:fold(fun - (JID, - {entity, - Aff, - Sub}, - Acc) -> - UsrItems = - lists:foldl(fun - ({P, - I}, - IAcc) -> - case - P - of - JID -> - [I - | IAcc]; - _ -> - IAcc - end - end, - [], - ItemsList), - mnesia:write({pubsub_state, - {JID, - NodeIdx}, - UsrItems, - Aff, - Sub}), - case - Aff - of - owner -> - [JID - | Acc]; - _ -> - Acc - end - end, - [], - Entities), - mnesia:delete({pubsub_node, - NodeId}), - {[#pubsub_node{nodeid - = - NodeId, - id - = - NodeIdx, - parents - = - [element(2, - ParentId)], - owners - = - Owners, - options - = - Options} - | RecList], - NodeIdx + 1} - end, - {[], 1}, - mnesia:match_object({pubsub_node, - {Host, - '_'}, - '_', - '_'})), - mnesia:write(#pubsub_index{index = node, last = LastIdx, - free = []}), - Result - end, - {atomic, NewRecords} = mnesia:transaction(F), - {atomic, ok} = mnesia:delete_table(pubsub_node), - {atomic, ok} = mnesia:create_table(pubsub_node, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, - pubsub_node)}]), - FNew = fun () -> - lists:foreach(fun (Record) -> mnesia:write(Record) end, - NewRecords) - end, - case mnesia:transaction(FNew) of - {atomic, Result} -> - ?INFO_MSG("Pubsub node tables updated correctly: ~p", - [Result]); - {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", - [Reason]) - end; - [nodeid, parentid, type, owners, options] -> - F = fun ({pubsub_node, NodeId, {_, Parent}, Type, - Owners, Options}) -> - #pubsub_node{nodeid = NodeId, id = 0, - parents = [Parent], type = Type, - owners = Owners, options = Options} - end, - mnesia:transform_table(pubsub_node, F, - [nodeid, id, parents, type, owners, options]), - FNew = fun () -> - LastIdx = lists:foldl(fun (#pubsub_node{nodeid = - NodeId} = - PubsubNode, - NodeIdx) -> - mnesia:write(PubsubNode#pubsub_node{id - = - NodeIdx}), - lists:foreach(fun - (#pubsub_state{stateid - = - StateId} = - State) -> - {JID, - _} = - StateId, - mnesia:delete({pubsub_state, - StateId}), - mnesia:write(State#pubsub_state{stateid - = - {JID, - NodeIdx}}) - end, - mnesia:match_object(#pubsub_state{stateid - = - {'_', - NodeId}, - _ - = - '_'})), - lists:foreach(fun - (#pubsub_item{itemid - = - ItemId} = - Item) -> - {IID, - _} = - ItemId, - {M1, - M2} = - Item#pubsub_item.modification, - {C1, - C2} = - Item#pubsub_item.creation, - mnesia:delete({pubsub_item, - ItemId}), - mnesia:write(Item#pubsub_item{itemid - = - {IID, - NodeIdx}, - modification - = - {M2, - M1}, - creation - = - {C2, - C1}}) - end, - mnesia:match_object(#pubsub_item{itemid - = - {'_', - NodeId}, - _ - = - '_'})), - NodeIdx + 1 - end, - 1, - mnesia:match_object({pubsub_node, - {Host, '_'}, - '_', '_', - '_', '_', - '_'}) - ++ - mnesia:match_object({pubsub_node, - {{'_', - ServerHost, - '_'}, - '_'}, - '_', '_', - '_', '_', - '_'})), - mnesia:write(#pubsub_index{index = node, - last = LastIdx, free = []}) - end, - case mnesia:transaction(FNew) of - {atomic, Result} -> - rename_default_nodeplugin(), - ?INFO_MSG("Pubsub node tables updated correctly: ~p", - [Result]); - {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", - [Reason]) - end; - [nodeid, id, parent, type, owners, options] -> - F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners, - Options}) -> - #pubsub_node{nodeid = NodeId, id = Id, - parents = [Parent], type = Type, - owners = Owners, options = Options} - end, - mnesia:transform_table(pubsub_node, F, - [nodeid, id, parents, type, owners, options]), - rename_default_nodeplugin(); - _ -> ok - end, - mnesia:transaction(fun () -> - case catch mnesia:first(pubsub_node) of - {_, L} when is_binary(L) -> - lists:foreach(fun ({H, N}) - when is_binary(N) -> - [Node] = - mnesia:read({pubsub_node, - {H, - N}}), - Type = - Node#pubsub_node.type, - BN = element(2, - node_call(Type, - path_to_node, - [N])), - BP = case [element(2, - node_call(Type, - path_to_node, - [P])) - || P - <- Node#pubsub_node.parents] - of - [<<>>] -> []; - Parents -> - Parents - end, - mnesia:write(Node#pubsub_node{nodeid - = - {H, - BN}, - parents - = - BP}), - mnesia:delete({pubsub_node, - {H, - N}}); - (_) -> ok - end, - mnesia:all_keys(pubsub_node)); - _ -> ok - end - end). - -rename_default_nodeplugin() -> - lists:foreach(fun (Node) -> - mnesia:dirty_write(Node#pubsub_node{type = - <<"hometree">>}) - end, - mnesia:dirty_match_object(#pubsub_node{type = - <<"default">>, - _ = '_'})). - -update_state_database(_Host, _ServerHost) -> - case catch mnesia:table_info(pubsub_state, attributes) of - [stateid, items, affiliation, subscription] -> - ?INFO_MSG("upgrade state pubsub tables", []), - F = fun ({pubsub_state, {JID, NodeID}, Items, Aff, Sub}, Acc) -> - Subs = case Sub of - none -> - []; - _ -> - {result, SubID} = pubsub_subscription:subscribe_node(JID, NodeID, []), - [{Sub, SubID}] - end, - NewState = #pubsub_state{stateid = {JID, NodeID}, - items = Items, - affiliation = Aff, - subscriptions = Subs}, - [NewState | Acc] - end, - {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3, - [F, [], pubsub_state]), - {atomic, ok} = mnesia:delete_table(pubsub_state), - {atomic, ok} = mnesia:create_table(pubsub_state, - [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_state)}]), - FNew = fun () -> - lists:foreach(fun mnesia:write/1, NewRecs) - end, - case mnesia:transaction(FNew) of - {atomic, Result} -> - ?INFO_MSG("Pubsub state tables updated correctly: ~p", - [Result]); - {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub state tables:~n~p", - [Reason]) - end; - _ -> - ok - end. - -send_loop(State) -> - receive - {presence, JID, Pid} -> - Host = State#state.host, - ServerHost = State#state.server_host, - LJID = jlib:jid_tolower(JID), - BJID = jlib:jid_remove_resource(LJID), - lists:foreach(fun (PType) -> - {result, Subscriptions} = node_action(Host, - PType, - get_entity_subscriptions, - [Host, - JID]), - lists:foreach(fun ({Node, subscribed, _, - SubJID}) -> - if (SubJID == LJID) or - (SubJID == BJID) -> - #pubsub_node{nodeid - = - {H, - N}, - type = - Type, - id = - NodeId, - options - = - Options} = - Node, - case - get_option(Options, - send_last_published_item) - of - on_sub_and_presence -> - send_items(H, - N, - NodeId, - Type, - LJID, - last); - _ -> ok - end; - true -> - % resource not concerned about that subscription - ok - end; - (_) -> ok - end, - Subscriptions) - end, - State#state.plugins), - if not State#state.ignore_pep_from_offline -> - {User, Server, Resource} = jlib:jid_tolower(JID), - case catch ejabberd_c2s:get_subscribed(Pid) of - Contacts when is_list(Contacts) -> - lists:foreach(fun ({U, S, R}) -> - case S of - ServerHost -> %% local contacts - case user_resources(U, S) of - [] -> %% offline - PeerJID = - jlib:make_jid(U, S, - R), - self() ! - {presence, User, - Server, [Resource], - PeerJID}; - _ -> %% online - % this is already handled by presence probe - ok - end; - _ -> %% remote contacts - % we can not do anything in any cases - ok - end - end, - Contacts); - _ -> ok - end; - true -> ok - end, - send_loop(State); - {presence, User, Server, Resources, JID} -> - spawn(fun () -> - Host = State#state.host, - Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), - lists:foreach(fun (#pubsub_node{nodeid = {_, Node}, - type = Type, - id = NodeId, - options = Options}) -> - case get_option(Options, - send_last_published_item) - of - on_sub_and_presence -> - lists:foreach(fun - (Resource) -> - LJID = - {User, - Server, - Resource}, - Subscribed = - case - get_option(Options, - access_model) - of - open -> - true; - presence -> - true; - whitelist -> - false; % subscribers are added manually - authorize -> - false; % likewise - roster -> - Grps = - get_option(Options, - roster_groups_allowed, - []), - {OU, - OS, - _} = - Owner, - element(2, - get_roster_info(OU, - OS, - LJID, - Grps)) - end, - if - Subscribed -> - send_items(Owner, - Node, - NodeId, - Type, - LJID, - last); - true -> - ok - end - end, - Resources); - _ -> ok - end - end, - tree_action(Host, get_nodes, - [Owner, JID])) - end), - send_loop(State); - stop -> ok - end. - -%% ------- -%% disco hooks handling functions -%% - --spec(disco_local_identity/5 :: -( - Acc :: [xmlel()], - _From :: jid(), - To :: jid(), - NodeId :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) - -> [xmlel()] -). -disco_local_identity(Acc, _From, To, <<>>, _Lang) -> - case lists:member(?PEPNODE, plugins(To#jid.lserver)) of - true -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []} - | Acc]; - false -> Acc - end; -disco_local_identity(Acc, _From, _To, _Node, _Lang) -> - Acc. - --spec(disco_local_features/5 :: -( - Acc :: [xmlel()], - _From :: jid(), - To :: jid(), - NodeId :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) - -> [binary(),...] -). -disco_local_features(Acc, _From, To, <<>>, _Lang) -> - Host = To#jid.lserver, - Feats = case Acc of - {result, I} -> I; - _ -> [] - end, - {result, - Feats ++ - lists:map(fun (Feature) -> - <<(?NS_PUBSUB)/binary, "#", Feature/binary>> - end, - features(Host, <<>>))}; -disco_local_features(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc; -disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc. - -%disco_sm_identity(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_identity(Acc, From, To, iolist_to_binary(Node), -% Lang); --spec(disco_sm_identity/5 :: -( - Acc :: empty | [xmlel()], - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> [xmlel()] -). -disco_sm_identity(empty, From, To, Node, Lang) -> - disco_sm_identity([], From, To, Node, Lang); -disco_sm_identity(Acc, From, To, Node, _Lang) -> - disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) - ++ Acc. - -disco_identity(_Host, <<>>, _From) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []}]; -disco_identity(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []}, - #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"leaf">>} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end], - children = []}]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - --spec(disco_sm_features/5 :: -( - Acc :: empty | {result, Features::[Feature::binary()]}, - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> {result, Features::[Feature::binary()]} -). -%disco_sm_features(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_features(Acc, From, To, iolist_to_binary(Node), -% Lang); -disco_sm_features(empty, From, To, Node, Lang) -> - disco_sm_features({result, []}, From, To, Node, Lang); -disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) -> - {result, - OtherFeatures ++ - disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)}; -disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. - -disco_features(_Host, <<>>, _From) -> - [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", Feature/binary>> - || Feature <- features(<<"pep">>)]]; -disco_features(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, - [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", - Feature/binary>> - || Feature <- features(<<"pep">>)]]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - --spec(disco_sm_items/5 :: -( - Acc :: empty | {result, [xmlel()]}, - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> {result, [xmlel()]} -). -%disco_sm_items(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_items(Acc, From, To, iolist_to_binary(Node), -% Lang); -disco_sm_items(empty, From, To, Node, Lang) -> - disco_sm_items({result, []}, From, To, Node, Lang); -disco_sm_items({result, OtherItems}, From, To, Node, _Lang) -> - {result, - lists:usort(OtherItems ++ - disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))}; -disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. - --spec(disco_items/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid()) - -> [xmlel()] -). -disco_items(Host, <<>>, From) -> - Action = fun (#pubsub_node{nodeid = {_, NodeID}, - options = Options, type = Type, id = Idx, - owners = Owners}, - Acc) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - [#xmlel{name = <<"item">>, - attrs = - [{<<"node">>, (NodeID)}, - {<<"jid">>, - case Host of - {_, _, _} -> - jlib:jid_to_string(Host); - _Host -> Host - end} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end], - children = []} - | Acc]; - _ -> Acc - end - end, - case transaction(Host, Action, sync_dirty) of - {result, Items} -> Items; - _ -> [] - end; -disco_items(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, - Options, Owners) - of - {result, Items} -> - {result, - [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - case Host of - {_, _, _} -> - jlib:jid_to_string(Host); - _Host -> Host - end}, - {<<"name">>, ItemID}], - children = []} - || #pubsub_item{itemid = {ItemID, _}} <- Items]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - -%% ------- -%% presence hooks handling functions -%% - -caps_update(#jid{luser = U, lserver = S, lresource = R} = From, To, _Features) -> - Pid = ejabberd_sm:get_session_pid(U, S, R), - presence_probe(From, To, Pid). - -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, - JID, Pid) -> - presence(Server, {presence, JID, Pid}), - presence(Server, {presence, User, Server, [Resource], JID}); -presence_probe(#jid{luser = User, lserver = Server}, - #jid{luser = User, lserver = Server}, _Pid) -> - %% ignore presence_probe from other ressources for the current user - %% this way, we do not send duplicated last items if user already connected with other clients - ok; -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, - #jid{lserver = Host} = JID, _Pid) -> - presence(Host, {presence, User, Server, [Resource], JID}). - -presence(ServerHost, Presence) -> - SendLoop = case - whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) - of - undefined -> - Host = host(ServerHost), - Plugins = plugins(Host), - PepOffline = case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, - config), - ignore_pep_from_offline) - of - [{ignore_pep_from_offline, PO}] -> PO; - _ -> true - end, - State = #state{host = Host, server_host = ServerHost, - ignore_pep_from_offline = PepOffline, - plugins = Plugins}, - init_send_loop(ServerHost, State); - Pid -> Pid - end, - SendLoop ! Presence. - -%% ------- -%% subscription hooks handling functions -%% - -out_subscription(User, Server, JID, subscribed) -> - Owner = jlib:make_jid(User, Server, <<"">>), - {PUser, PServer, PResource} = jlib:jid_tolower(JID), - PResources = case PResource of - <<>> -> user_resources(PUser, PServer); - _ -> [PResource] - end, - presence(Server, - {presence, PUser, PServer, PResources, Owner}), - true; -out_subscription(_, _, _, _) -> true. - -in_subscription(_, User, Server, Owner, unsubscribed, - _) -> - unsubscribe_user(jlib:make_jid(User, Server, <<"">>), - Owner), - true; -in_subscription(_, _, _, _, _, _) -> true. - -unsubscribe_user(Entity, Owner) -> - BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - Host = host(element(2, BJID)), - spawn(fun () -> - lists:foreach(fun (PType) -> - {result, Subscriptions} = - node_action(Host, PType, - get_entity_subscriptions, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{options - = - Options, - owners - = - Owners, - id = - NodeId}, - subscribed, _, - JID}) -> - case - get_option(Options, - access_model) - of - presence -> - case - lists:member(BJID, - Owners) - of - true -> - node_action(Host, - PType, - unsubscribe_node, - [NodeId, - Entity, - JID, - all]); - false -> - {result, - ok} - end; - _ -> - {result, ok} - end; - (_) -> ok - end, - Subscriptions) - end, - plugins(Host)) - end). - -%% ------- -%% user remove hook handling function -%% - -remove_user(User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - Entity = jlib:make_jid(LUser, LServer, <<"">>), - Host = host(LServer), -%%-------------------------------------------------------------------- -%% 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 - HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>, - spawn(fun () -> - lists:foreach(fun (PType) -> - {result, Subscriptions} = - node_action(Host, PType, - get_entity_subscriptions, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{id = - NodeId}, - _, _, JID}) -> - node_action(Host, - PType, - unsubscribe_node, - [NodeId, - Entity, - JID, - all]) - end, - Subscriptions), - {result, Affiliations} = - node_action(Host, PType, - get_entity_affiliations, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{nodeid - = - {H, - N}, - parents - = - []}, - owner}) -> - delete_node(H, N, - Entity); - ({#pubsub_node{nodeid - = - {H, - N}, - type = - <<"hometree">>}, - owner}) - when N == - HomeTreeBase -> - delete_node(H, N, - Entity); - ({#pubsub_node{id = - NodeId}, - publisher}) -> - node_action(Host, - PType, - set_affiliation, - [NodeId, - Entity, - none]); - (_) -> ok - end, - Affiliations) - end, - plugins(Host)) - end). - -handle_call(server_host, _From, State) -> - {reply, State#state.server_host, State}; -handle_call(plugins, _From, State) -> - {reply, State#state.plugins, State}; -handle_call(pep_mapping, _From, State) -> - {reply, State#state.pep_mapping, State}; -handle_call(nodetree, _From, State) -> - {reply, State#state.nodetree, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -%% @private -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -%% @private -handle_cast(_Msg, State) -> {noreply, State}. - --spec(handle_info/2 :: -( - _ :: {route, From::jid(), To::jid(), Packet::xmlel()}, - State :: state()) - -> {noreply, state()} -). - -handle_info({route, From, To, Packet}, - #state{server_host = ServerHost, access = Access, - plugins = Plugins} = - State) -> - case catch do_route(ServerHost, Access, Plugins, - To#jid.lserver, From, To, Packet) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok - end, - {noreply, State}; -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -%% @private -handle_info(_Info, State) -> {noreply, State}. - -terminate(_Reason, - #state{host = Host, server_host = ServerHost, - nodetree = TreePlugin, plugins = Plugins}) -> - ejabberd_router:unregister_route(Host), - case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, - caps_update, 80), - ejabberd_hooks:delete(disco_sm_identity, ServerHost, - ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:delete(disco_sm_features, ServerHost, - ?MODULE, disco_sm_features, 75), - ejabberd_hooks:delete(disco_sm_items, ServerHost, - ?MODULE, disco_sm_items, 75), - gen_iq_handler:remove_iq_handler(ejabberd_sm, - ServerHost, ?NS_PUBSUB), - gen_iq_handler:remove_iq_handler(ejabberd_sm, - ServerHost, ?NS_PUBSUB_OWNER); - false -> ok - end, - ejabberd_hooks:delete(sm_remove_connection_hook, - ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:delete(disco_local_identity, ServerHost, - ?MODULE, disco_local_identity, 75), - ejabberd_hooks:delete(disco_local_features, ServerHost, - ?MODULE, disco_local_features, 75), - ejabberd_hooks:delete(disco_local_items, ServerHost, - ?MODULE, disco_local_items, 75), - ejabberd_hooks:delete(presence_probe_hook, ServerHost, - ?MODULE, presence_probe, 80), - ejabberd_hooks:delete(roster_in_subscription, - ServerHost, ?MODULE, in_subscription, 50), - ejabberd_hooks:delete(roster_out_subscription, - ServerHost, ?MODULE, out_subscription, 50), - ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, - ?MODULE, remove_user, 50), - mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB), - gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! stop, -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -%% @private - terminate_plugins(Host, ServerHost, Plugins, - TreePlugin). - -code_change(_OldVsn, State, _Extra) -> {ok, State}. - --spec(do_route/7 :: -( - ServerHost :: binary(), - Access :: atom(), - Plugins :: [binary(),...], - Host :: mod_pubsub:hostPubsub(), - From :: jid(), - To :: jid(), - Packet :: xmlel()) - -> ok -). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> - #xmlel{name = Name, attrs = Attrs} = Packet, - case To of - #jid{luser = <<"">>, lresource = <<"">>} -> - case Name of - <<"iq">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, - lang = Lang} = - IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = xml:get_attr_s(<<"node">>, QAttrs), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - Res = case iq_disco_info(Host, Node, From, Lang) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = - <<"query">>, - attrs = - QAttrs, - children = - IQRes ++ - Info}]}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = ?NS_DISCO_ITEMS, - sub_el = SubEl} = - IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = xml:get_attr_s(<<"node">>, QAttrs), - Res = case iq_disco_items(Host, Node, From) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = - <<"query">>, - attrs = - QAttrs, - children = - IQRes}]}) -% {error, Error} -> -% jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, - sub_el = SubEl} = - IQ -> - Res = case iq_pubsub(Host, ServerHost, From, IQType, - SubEl, Lang, Access, Plugins) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, - lang = Lang, sub_el = SubEl} = - IQ -> - Res = case iq_pubsub_owner(Host, ServerHost, From, - IQType, SubEl, Lang) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang, sub_el = _SubEl} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> - Res = case iq_command(Host, ServerHost, From, IQ, - Access, Plugins) - of - {error, Error} -> - jlib:make_error_reply(Packet, Error); - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}) - end, - ejabberd_router:route(To, From, Res); - #iq{} -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - <<"message">> -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - case find_authorization_response(Packet) of - none -> ok; - invalid -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_BAD_REQUEST)); - XFields -> - handle_authorization_response(Host, From, To, - Packet, XFields) - end - end; - _ -> ok - end; - _ -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end - end. - -command_disco_info(_Host, ?NS_COMMANDS, _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}], - children = []}, - {result, [IdentityEl]}; -command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, - _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}], - children = []}, - FeaturesEl = #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - {result, [IdentityEl, FeaturesEl]}. - -node_disco_info(Host, Node, From) -> - node_disco_info(Host, Node, From, true, true). - -node_disco_info(Host, Node, From, Identity, Features) -> -% Action = -% fun(#pubsub_node{type = Type, id = NodeId}) -> -% I = case Identity of -% false -> -% []; -% true -> -% Types = -% case tree_call(Host, get_subnodes, [Host, Node, From]) of -% [] -> -% [<<"leaf">>]; %% No sub-nodes: it's a leaf node -% _ -> -% case node_call(Type, get_items, [NodeId, From]) of -% {result, []} -> [<<"collection">>]; -% {result, _} -> [<<"leaf">>, <<"collection">>]; -% _ -> [] -% end -% end, -% lists:map(fun(T) -> -% #xmlel{name = <<"identity">>, -% attrs = -% [{<<"category">>, -% <<"pubsub">>}, -% {<<"type">>, T}], -% children = []} -% end, Types) -% end, -% F = case Features of -% false -> -% []; -% true -> -% [#xmlel{name = <<"feature">>, -% attrs = [{<<"var">>, ?NS_PUBSUB}], -% children = []} -% | lists:map(fun (T) -> -% #xmlel{name = <<"feature">>, -% attrs = -% [{<<"var">>, -% <<(?NS_PUBSUB)/binary, -% "#", -% T/binary>>}], -% children = []} -% end, -% features(Type))] -% end, -% %% TODO: add meta-data info (spec section 5.4) -% {result, I ++ F} -% end, -% case transaction(Host, Node, Action, sync_dirty) of -% {result, {_, Result}} -> {result, Result}; -% Other -> Other -% end. - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - I = Types = case tree_call(Host, get_subnodes, - [Host, Node, From]) - of - [] -> [<<"leaf">>]; - _ -> - case node_call(Type, get_items, - [NodeId, From]) - of - {result, []} -> - [<<"collection">>]; - {result, _} -> - [<<"leaf">>, - <<"collection">>]; - _ -> [] - end - end, - lists:map(fun (T) -> - #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, - <<"pubsub">>}, - {<<"type">>, T}], - children = []} - end, - Types), - F = [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}], - children = []} - | lists:map(fun (T) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, - <<(?NS_PUBSUB)/binary, - "#", - T/binary>>}], - children = []} - end, - features(Type))], - {result, I ++ F} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end. - -iq_disco_info(Host, SNode, From, Lang) -> - [Node | _] = case SNode of - <<>> -> [<<>>]; - _ -> str:tokens(SNode, <<"!">>) - end, - % Node = string_to_node(RealSNode), - case Node of - <<>> -> - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"service">>}, - {<<"name">>, - translate:translate(Lang, <<"Publish-Subscribe">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}] - ++ - lists:map(fun (Feature) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}], - children = []} - end, - features(Host, Node))}; - ?NS_COMMANDS -> command_disco_info(Host, Node, From); - ?NS_PUBSUB_GET_PENDING -> - command_disco_info(Host, Node, From); - _ -> node_disco_info(Host, Node, From) - end. - --spec(iq_disco_items/3 :: -( - Host :: mod_pubsub:host(), - NodeId :: <<>> | mod_pubsub:nodeId(), - From :: jid()) - -> {result, [xmlel()]} -). -iq_disco_items(Host, <<>>, From) -> - {result, - lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, - options = Options}) -> - Attrs = case get_option(Options, title) of - false -> - [{<<"jid">>, Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, Host}, - {<<"name">>, Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, attrs = Attrs, - children = []} - end, - tree_action(Host, get_subnodes, [Host, <<>>, From]))}; -% case tree_action(Host, get_subnodes, [Host, <<>>, From]) of -% Nodes when is_list(Nodes) -> -% {result, -% lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, -% options = Options}) -> -% Attrs = case get_option(Options, title) of -% false -> -% [{<<"jid">>, Host} -% | nodeAttr(SubNode)]; -% Title -> -% [{<<"jid">>, Host}, -% {<<"name">>, Title} -% | nodeAttr(SubNode)] -% end, -% #xmlel{name = <<"item">>, attrs = Attrs, -% children = []} -% end, -% Nodes)}; -% Other -> Other -% end; -iq_disco_items(Host, ?NS_COMMANDS, _From) -> - CommandItems = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, - {<<"node">>, ?NS_PUBSUB_GET_PENDING}, - {<<"name">>, <<"Get Pending">>}], - children = []}], - {result, CommandItems}; -iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> - CommandItems = [], {result, CommandItems}; -iq_disco_items(Host, Item, From) -> - case str:tokens(Item, <<"!">>) of - [_Node, _ItemID] -> {result, []}; - [Node] -> -% Node = string_to_node(SNode), - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options, owners = Owners}) -> - NodeItems = case get_allowed_items_call(Host, Idx, - From, Type, - Options, - Owners) - of - {result, R} -> R; - _ -> [] - end, - Nodes = lists:map(fun (#pubsub_node{nodeid = - {_, SubNode}, - options = - SubOptions}) -> - Attrs = case - get_option(SubOptions, - title) - of - false -> - [{<<"jid">>, - Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, - Host}, - {<<"name">>, - Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, - attrs = Attrs, - children = []} - end, - tree_call(Host, get_subnodes, - [Host, Node, From])), - Items = lists:map(fun (#pubsub_item{itemid = - {RN, _}}) -> - {result, Name} = - node_call(Type, - get_item_name, - [Host, Node, - RN]), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - Host}, - {<<"name">>, - Name}], - children = []} - end, - NodeItems), - {result, Nodes ++ Items} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - --spec(iq_sm/3 :: -( - From :: jid(), - To :: jid(), - IQ :: iq_request()) - -> iq_result() | iq_error() -). -iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) -> - ServerHost = To#jid.lserver, - LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)), - Res = case XMLNS of - ?NS_PUBSUB -> - iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); - ?NS_PUBSUB_OWNER -> - iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, - Lang) - end, - case Res of - {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes}; - {error, Error} -> - IQ#iq{type = error, sub_el = [Error, SubEl]} - end. - -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd Publish-Subscribe module">>))/binary, - "\nCopyright (c) 2004-2013 ProcessOne">>}]}]. - --spec(iq_pubsub/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary()) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> - iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). - --spec(iq_pubsub/8 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary(), - Access :: atom(), - Plugins :: [binary(),...]) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> - #xmlel{children = SubEls} = SubEl, - case xml:remove_cdata(SubEls) of - [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] -> - Node = xml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {set, <<"create">>} -> - Config = case Rest of - [#xmlel{name = <<"configure">>, children = C}] -> C; - _ -> [] - end, - Type = case xml:get_attr_s(<<"type">>, Attrs) of - <<>> -> hd(Plugins); - T -> T - end, - case lists:member(Type, Plugins) of - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"create-nodes">>)}; - true -> - create_node(Host, ServerHost, Node, From, Type, Access, Config) - end; - {set, <<"publish">>} -> - case xml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs, - children = Payload}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), - publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access); - [] -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)} - end; - {set, <<"retract">>} -> - ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) - of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - case xml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), - delete_item(Host, Node, From, ItemId, ForceNotify); - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)} - end; - {set, <<"subscribe">>} -> - Config = case Rest of - [#xmlel{name = <<"options">>, children = C}] -> C; - _ -> [] - end, - JID = xml:get_attr_s(<<"jid">>, Attrs), - subscribe_node(Host, Node, From, JID, Config); - {set, <<"unsubscribe">>} -> - JID = xml:get_attr_s(<<"jid">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), - unsubscribe_node(Host, Node, From, JID, SubId); - {get, <<"items">>} -> - MaxItems = xml:get_attr_s(<<"max_items">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), - ItemIDs = lists:foldl(fun (#xmlel{name = <<"item">>, - attrs = ItemAttrs}, - Acc) -> - case xml:get_attr_s(<<"id">>, - ItemAttrs) - of - <<"">> -> Acc; - ItemID -> [ItemID | Acc] - end; - (_, Acc) -> Acc - end, - [], xml:remove_cdata(Els)), - get_items(Host, Node, From, SubId, MaxItems, ItemIDs); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From, Plugins); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From, Plugins); - {get, <<"options">>} -> - SubID = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), - get_options(Host, Node, JID, SubID, Lang); - {set, <<"options">>} -> - SubID = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), - set_options(Host, Node, JID, SubID, Els); - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - Other -> - ?INFO_MSG("Too many actions: ~p", [Other]), - {error, ?ERR_BAD_REQUEST} - end. - - --spec(iq_pubsub_owner/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary()) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). -iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - #xmlel{children = SubEls} = SubEl, - Action = xml:remove_cdata(SubEls), - case Action of - [#xmlel{name = Name, attrs = Attrs, children = Els}] -> - Node = xml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {get, <<"configure">>} -> - get_configure(Host, ServerHost, Node, From, Lang); - {set, <<"configure">>} -> - set_configure(Host, Node, From, Els, Lang); - {get, <<"default">>} -> - get_default(Host, Node, From, Lang); - {set, <<"delete">>} -> delete_node(Host, Node, From); - {set, <<"purge">>} -> purge_node(Host, Node, From); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From); - {set, <<"subscriptions">>} -> - set_subscriptions(Host, Node, From, - xml:remove_cdata(Els)); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From); - {set, <<"affiliations">>} -> - set_affiliations(Host, Node, From, xml:remove_cdata(Els)); - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ?INFO_MSG("Too many actions: ~p", [Action]), - {error, ?ERR_BAD_REQUEST} - end. - -iq_command(Host, ServerHost, From, IQ, Access, Plugins) -> - case adhoc:parse_request(IQ) of - Req when is_record(Req, adhoc_request) -> - case adhoc_request(Host, ServerHost, From, Req, Access, - Plugins) - of - Resp when is_record(Resp, adhoc_response) -> - {result, [adhoc:produce_response(Req, Resp)]}; - Error -> Error - end; - Err -> Err - end. - -%% @doc <p>Processes an Ad Hoc Command.</p> -adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - lang = Lang, action = <<"execute">>, - xdata = false}, - _Access, Plugins) -> - send_pending_node_form(Host, Owner, Lang, Plugins); -adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - action = <<"execute">>, xdata = XData}, - _Access, _Plugins) -> - ParseOptions = case XData of - #xmlel{name = <<"x">>} = XEl -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData2 -> - case set_xoption(Host, XData2, []) of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> Err - end - end; - _ -> - ?INFO_MSG("Bad XForm: ~p", [XData]), - {error, ?ERR_BAD_REQUEST} - end, - case ParseOptions of - {result, XForm} -> - case lists:keysearch(node, 1, XForm) of - {value, {_, Node}} -> - send_pending_auth_events(Host, Node, Owner); - false -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)} - end; - Error -> Error - end; -adhoc_request(_Host, _ServerHost, _Owner, - #adhoc_request{action = <<"cancel">>}, _Access, - _Plugins) -> - #adhoc_response{status = canceled}; -adhoc_request(Host, ServerHost, Owner, - #adhoc_request{action = <<>>} = R, Access, Plugins) -> - adhoc_request(Host, ServerHost, Owner, - R#adhoc_request{action = <<"execute">>}, Access, - Plugins); -adhoc_request(_Host, _ServerHost, _Owner, Other, - _Access, _Plugins) -> - ?DEBUG("Couldn't process ad hoc command:~n~p", [Other]), - {error, ?ERR_ITEM_NOT_FOUND}. - -%% @spec (Host, Owner, Lang, Plugins) -> iqRes() -%% @doc <p>Sends the process pending subscriptions XForm for Host to -%% Owner.</p> -send_pending_node_form(Host, Owner, _Lang, Plugins) -> - Filter = fun (Plugin) -> - lists:member(<<"get-pending">>, features(Plugin)) - end, - case lists:filter(Filter, Plugins) of - [] -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; - Ps -> - XOpts = lists:map(fun (Node) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Node}]}]} - end, - get_pending_nodes(Host, Owner, Ps)), - XForm = #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"var">>, <<"pubsub#node">>}], - children = lists:usort(XOpts)}]}, - #adhoc_response{status = executing, - defaultaction = <<"execute">>, elements = [XForm]} - end. - -get_pending_nodes(Host, Owner, Plugins) -> - Tr = fun (Type) -> - case node_call(Type, get_pending_nodes, [Host, Owner]) - of - {result, Nodes} -> Nodes; - _ -> [] - end - end, - case transaction(fun () -> - {result, lists:flatmap(Tr, Plugins)} - end, - sync_dirty) - of - {result, Res} -> Res; - Err -> Err - end. - -%% @spec (Host, Node, Owner) -> iqRes() -%% @doc <p>Send a subscription approval form to Owner for all pending -%% subscriptions on Host and Node.</p> -send_pending_auth_events(Host, Node, Owner) -> - ?DEBUG("Sending pending auth events for ~s on " - "~s:~s", - [jlib:jid_to_string(Owner), Host, Node]), - Action = fun (#pubsub_node{id = NodeID, type = Type}) -> - case lists:member(<<"get-pending">>, features(Type)) of - true -> - case node_call(Type, get_affiliation, - [NodeID, Owner]) - of - {result, owner} -> - node_call(Type, get_node_subscriptions, - [NodeID]); - _ -> {error, ?ERR_FORBIDDEN} - end; - false -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {N, Subscriptions}} -> - lists:foreach(fun ({J, pending, _SubID}) -> - send_authorization_request(N, jlib:make_jid(J)); - ({J, pending}) -> - send_authorization_request(N, jlib:make_jid(J)); - (_) -> ok - end, - Subscriptions), - #adhoc_response{}; - Err -> Err - end. - -%%% authorization handling - -send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, - Subscriber) -> - Lang = <<"en">>, - Stanza = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"PubSub subscriber request">>)}]}, - #xmlel{name = <<"instructions">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Choose whether to approve this entity's " - "subscription.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - ?NS_PUBSUB_SUB_AUTH}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"pubsub#node">>}, - {<<"type">>, - <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Node ID">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Node}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#subscriber_jid">>}, - {<<"type">>, <<"jid-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Subscriber Address">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - jlib:jid_to_string(Subscriber)}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#allow">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow this Jabber ID to subscribe to " - "this pubsub node?">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - <<"false">>}]}]}]}]}, - lists:foreach(fun (Owner) -> - ejabberd_router:route(service_jid(Host), - jlib:make_jid(Owner), Stanza) - end, - Owners). - -find_authorization_response(Packet) -> - #xmlel{children = Els} = Packet, - XData1 = lists:map(fun (#xmlel{name = <<"x">>, - attrs = XAttrs} = - XEl) -> - case xml:get_attr_s(<<"xmlns">>, XAttrs) of - ?NS_XDATA -> - case xml:get_attr_s(<<"type">>, XAttrs) of - <<"cancel">> -> none; - _ -> jlib:parse_xdata_submit(XEl) - end; - _ -> none - end; - (_) -> none - end, - xml:remove_cdata(Els)), - XData = lists:filter(fun (E) -> E /= none end, XData1), - case XData of - [invalid] -> invalid; - [] -> none; - [XFields] when is_list(XFields) -> - ?DEBUG("XFields: ~p", [XFields]), - case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of - {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields; - _ -> invalid - end - end. -%% @spec (Host, JID, Node, Subscription) -> void -%% Host = mod_pubsub:host() -%% JID = jlib:jid() -%% SNode = string() -%% Subscription = atom() | {atom(), mod_pubsub:subid()} -%% @doc Send a message to JID with the supplied Subscription -%% TODO : ask Christophe's opinion -send_authorization_approval(Host, JID, SNode, Subscription) -> - SubAttrs = case Subscription of -% {S, SID} -> -% [{<<"subscription">>, subscription_to_string(S)}, -% {<<"subid">>, SID}]; - S -> [{<<"subscription">>, subscription_to_string(S)}] - end, - Stanza = event_stanza([#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jlib:jid_to_string(JID)} - | nodeAttr(SNode)] - ++ SubAttrs, - children = []}]), - ejabberd_router:route(service_jid(Host), JID, Stanza). - -handle_authorization_response(Host, From, To, Packet, XFields) -> - case {lists:keysearch(<<"pubsub#node">>, 1, XFields), - lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields), - lists:keysearch(<<"pubsub#allow">>, 1, XFields)} - of - {{value, {_, [Node]}}, {value, {_, [SSubscriber]}}, - {value, {_, [SAllow]}}} -> -% Node = string_to_node(SNode), - Subscriber = jlib:string_to_jid(SSubscriber), - Allow = case SAllow of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - Action = fun (#pubsub_node{type = Type, owners = Owners, - id = NodeId}) -> - IsApprover = - lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), - Owners), - {result, Subscriptions} = node_call(Type, - get_subscriptions, - [NodeId, - Subscriber]), - if not IsApprover -> {error, ?ERR_FORBIDDEN}; - true -> - update_auth(Host, Node, Type, NodeId, - Subscriber, Allow, Subscriptions) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {error, Error} -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, Error)); - {result, {_, _NewSubscription}} -> - %% XXX: notify about subscription state change, section 12.11 - ok; - _ -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_INTERNAL_SERVER_ERROR)) - end; - _ -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_NOT_ACCEPTABLE)) - end. - -update_auth(Host, Node, Type, NodeId, Subscriber, Allow, - Subscriptions) -> - Subscription = lists:filter(fun ({pending, _}) -> true; - (_) -> false - end, - Subscriptions), - case Subscription of - [{pending, SubID}] -> - NewSubscription = case Allow of - true -> subscribed; - false -> none - end, - node_call(Type, set_subscriptions, - [NodeId, Subscriber, NewSubscription, SubID]), - send_authorization_approval(Host, Subscriber, Node, - NewSubscription), - {result, ok}; - _ -> {error, ?ERR_UNEXPECTED_REQUEST} - end. - --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, - case Val of - true -> <<"1">>; - _ -> <<"0">> - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). - --define(STRINGMXFIELD(Label, Var, Vals), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, V}]} - || V <- Vals]}). - --define(XFIELDOPT(Type, Label, Var, Val, Opts), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - lists:map(fun (Opt) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Opt}]}]} - end, - Opts) - ++ - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(LISTXFIELD(Label, Var, Val, Opts), - ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)). - --define(LISTMXFIELD(Label, Var, Vals, Opts), -%% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% @doc <p>Create new pubsub nodes</p> -%%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p> -%%<ul> -%%<li>The service does not support node creation.</li> -%%<li>Only entities that are registered with the service are allowed to create nodes but the requesting entity is not registered.</li> -%%<li>The requesting entity does not have sufficient privileges to create nodes.</li> -%%<li>The requested NodeID already exists.</li> -%%<li>The request did not include a NodeID and "instant nodes" are not supported.</li> -%%</ul> -%%<p>ote: node creation is a particular case, error return code is evaluated at many places:</p> -%%<ul> -%%<li>iq_pubsub checks if service supports node creation (type exists)</li> -%%<li>create_node checks if instant nodes are supported</li> -%%<li>create_node asks node plugin if entity have sufficient privilege</li> -%%<li>nodetree create_node checks if nodeid already exists</li> -%%<li>node plugin create_node just sets default affiliation/subscription</li> -%%</ul> - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - lists:map(fun (Opt) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Opt}]}]} - end, - Opts) - ++ - lists:map(fun (Val) -> - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]} - end, - Vals)}). - --spec(create_node/5 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), - Owner :: jid(), - Type :: binary()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). - -create_node(Host, ServerHost, Node, Owner, Type) -> - create_node(Host, ServerHost, Node, Owner, Type, all, []). - --spec(create_node/7 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), - Owner :: jid(), - Type :: binary(), - Access :: atom(), - Configuration :: [xmlel()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> - case lists:member(<<"instant-nodes">>, features(Type)) of - true -> - NewNode = randoms:get_string(), - case create_node(Host, ServerHost, NewNode, Owner, Type, - Access, Configuration) - of - {result, _} -> - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"create">>, - attrs = nodeAttr(NewNode), - children = []}]}]}; - Error -> Error - end; - false -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"nodeid-required">>)} - end; -create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> - Type = select_type(ServerHost, Host, Node, GivenType), - ParseOptions = case xml:remove_cdata(Configuration) of - [] -> {result, node_options(Type)}; - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData -> - case set_xoption(Host, XData, node_options(Type)) - of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> Err - end - end; - _ -> - ?INFO_MSG("Node ~p; bad configuration: ~p", - [Node, Configuration]), - {error, ?ERR_BAD_REQUEST} - end, - case ParseOptions of - {result, NodeOptions} -> - CreateNode = - fun() -> - Parent = case node_call(Type, node_to_path, [Node]) of - {result, [Node]} -> <<>>; - {result, Path} -> element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)])) - end, - Parents = case Parent of - <<>> -> []; - _ -> [Parent] - end, - case node_call(Type, create_node_permission, [Host, ServerHost, Node, Parent, Owner, Access]) of - {result, true} -> - case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of - {ok, NodeId} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], - case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, SubsByDepth, Result}}; - Error -> Error - end; - {error, {virtual, NodeId}} -> - case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, [], Result}}; - Error -> Error - end; - Error -> - Error - end; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"create">>, - attrs = nodeAttr(Node), - children = []}]}], - case transaction(CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {NodeId, _SubsByDepth, default}} -> - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - {result, Reply}; - {result, {NodeId, _SubsByDepth, Result}} -> - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - {result, Result}; - Error -> - %% in case we change transaction to sync_dirty... - %% node_call(Type, delete_node, [Host, Node]), - %% tree_call(Host, delete_node, [Host, Node]), - Error - end; - Error -> - Error - end. - -%% @spec (Host, Node, Owner) -> -%% {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% Owner = jid() -%% Reason = stanzaError() --spec(delete_node/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Owner :: jid()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -%% @doc <p>Delete specified node and all childs.</p> -%%<p>There are several reasons why the node deletion request might fail:</p> -%%<ul> -%%<li>The requesting entity does not have sufficient privileges to delete the node.</li> -%%<li>The node is the root collection node, which cannot be deleted.</li> -%%<li>The specified node does not exist.</li> -%%</ul> -delete_node(_Host, <<>>, _Owner) -> - {error, ?ERR_NOT_ALLOWED}; -delete_node(Host, Node, Owner) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> - case node_call(Type, get_affiliation, [NodeId, Owner]) of - {result, owner} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], - Removed = tree_call(Host, delete_node, [Host, Node]), - case node_call(Type, delete_node, [Removed]) of - {result, Res} -> {result, {SubsByDepth, Res}}; - Error -> Error - end; - _ -> - %% Entity is not an owner - {error, ?ERR_FORBIDDEN} - end - end, - Reply = [], - ServerHost = get(server_host), - case transaction(Host, Node, Action, transaction) of - {result, {_TNode, {SubsByDepth, {Result, broadcast, Removed}}}} -> - lists:foreach(fun({RNode, _RSubscriptions}) -> - {RH, RN} = RNode#pubsub_node.nodeid, - NodeId = RNode#pubsub_node.id, - Type = RNode#pubsub_node.type, - Options = RNode#pubsub_node.options, - broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth), - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, NodeId]) - end, Removed), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_TNode, {_, {Result, Removed}}}} -> - lists:foreach(fun({RNode, _RSubscriptions}) -> - {RH, RN} = RNode#pubsub_node.nodeid, - NodeId = RNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, NodeId]) - end, Removed), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {TNode, {_, default}}} -> - NodeId = TNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, NodeId]), - {result, Reply}; - {result, {TNode, {_, Result}}} -> - NodeId = TNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, NodeId]), - {result, Result}; - Error -> - Error - end. - -%% @spec (Host, Node, From, JID, Configuration) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% Host = host() -%% Node = pubsubNode() -%% From = jid() -%% JID = jid() --spec(subscribe_node/5 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - JID :: binary(), - Configuration :: [xmlel()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -%% @see node_hometree:subscribe_node/5 -%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> -%%<p>There are several reasons why the subscription request might fail:</p> -%%<ul> -%%<li>The bare JID portions of the JIDs do not match.</li> -%%<li>The node has an access model of "presence" and the requesting entity is not subscribed to the owner's presence.</li> -%%<li>The node has an access model of "roster" and the requesting entity is not in one of the authorized roster groups.</li> -%%<li>The node has an access model of "whitelist" and the requesting entity is not on the whitelist.</li> -%%<li>The service requires payment for subscriptions to the node.</li> -%%<li>The requesting entity is anonymous and the service does not allow anonymous entities to subscribe.</li> -%%<li>The requesting entity has a pending subscription.</li> -%%<li>The requesting entity is blocked from subscribing (e.g., because having an affiliation of outcast).</li> -%%<li>The node does not support subscriptions.</li> -%%<li>The node does not exist.</li> -%%</ul> -subscribe_node(Host, Node, From, JID, Configuration) -> - SubOpts = case - pubsub_subscription:parse_options_xform(Configuration) - of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> - case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - Action = fun (#pubsub_node{options = Options, - owners = Owners, type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member(<<"subscribe">>, Features), - OptionsFeature = lists:member(<<"subscription-options">>, Features), - HasOptions = not (SubOpts == []), - SubscribeConfig = get_option(Options, subscribe), - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, Subscriber, - Owners, AccessModel, AllowedGroups), - if not SubscribeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"subscribe">>)}; - not SubscribeConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"subscribe">>)}; - HasOptions andalso not OptionsFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)}; - SubOpts == invalid -> - {error, - extended_error(?ERR_BAD_REQUEST, - <<"invalid-options">>)}; - true -> - node_call(Type, subscribe_node, - [NodeId, From, Subscriber, AccessModel, - SendLast, PresenceSubscription, - RosterGroup, SubOpts]) - end - end, - Reply = fun (Subscription) -> - SubAttrs = case Subscription of - {subscribed, SubId} -> - [{<<"subscription">>, - subscription_to_string(subscribed)}, - {<<"subid">>, SubId}, {<<"node">>, Node}]; - Other -> - [{<<"subscription">>, - subscription_to_string(Other)}, - {<<"node">>, Node}] - end, - Fields = [{<<"jid">>, jlib:jid_to_string(Subscriber)} - | SubAttrs], - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"subscription">>, - attrs = Fields, children = []}]}] - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, - {TNode, {Result, subscribed, SubId, send_last}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - send_items(Host, Node, NodeId, Type, Subscriber, last), - case Result of - default -> {result, Reply({subscribed, SubId})}; - _ -> {result, Result} - end; - {result, {_TNode, {default, subscribed, SubId}}} -> - {result, Reply({subscribed, SubId})}; - {result, {_TNode, {Result, subscribed, _SubId}}} -> - {result, Result}; - {result, {TNode, {default, pending, _SubId}}} -> - send_authorization_request(TNode, Subscriber), - {result, Reply(pending)}; - {result, {TNode, {Result, pending}}} -> - send_authorization_request(TNode, Subscriber), - {result, Result}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host, Noce, From, JID, SubId) -> {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% From = jid() -%% JID = string() -%% SubId = string() -%% Reason = stanzaError() -%% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p> -%%<p>There are several reasons why the unsubscribe request might fail:</p> -%%<ul> -%%<li>The requesting entity has multiple subscriptions to the node but does not specify a subscription ID.</li> -%%<li>The request does not specify an existing subscriber.</li> -%%<li>The requesting entity does not have sufficient privileges to unsubscribe the specified JID.</li> -%%<li>The node does not exist.</li> -%%<li>The request specifies a subscription ID that is not valid or current.</li> -%%</ul> --spec(unsubscribe_node/5 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - JID :: binary() | ljid(), - SubId :: mod_pubsub:subId()) - -> {result, []} - %%% - | {error, xmlel()} -). -unsubscribe_node(Host, Node, From, JID, SubId) - when is_binary(JID) -> - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> - case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - unsubscribe_node(Host, Node, From, Subscriber, SubId); -unsubscribe_node(Host, Node, From, Subscriber, SubId) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, unsubscribe_node, - [NodeId, From, Subscriber, SubId]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, default}} -> {result, []}; -% {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% @doc <p>Publish item to a PubSub node.</p> -%% <p>The permission to publish an item must be verified by the plugin implementation.</p> -%%<p>There are several reasons why the publish request might fail:</p> -%%<ul> -%%<li>The requesting entity does not have sufficient privileges to publish.</li> -%%<li>The node does not support item publication.</li> -%%<li>The node does not exist.</li> -%%<li>The payload size exceeds a service-defined limit.</li> -%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li> -%%<li>The request does not match the node configuration.</li> -%%</ul> --spec(publish_item/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: mod_pubsub:nodeId(), - Publisher :: jid(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload) -> - publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, all); -publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> - publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all). -publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PublishFeature = lists:member(<<"publish">>, Features), - PublishModel = get_option(Options, publish_model), - DeliverPayloads = get_option(Options, deliver_payloads), - PersistItems = get_option(Options, persist_items), - MaxItems = case PersistItems of - false -> 0; - true -> max_items(Host, Options) - end, - PayloadCount = payload_xmlelements(Payload), - PayloadSize = byte_size(term_to_binary(Payload)) - 2, - PayloadMaxSize = get_option(Options, max_payload_size), - if not PublishFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"publish">>)}; - PayloadSize > PayloadMaxSize -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)}; - (PayloadCount == 0) and (Payload == []) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)}; - (PayloadCount > 1) or (PayloadCount == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}; - (DeliverPayloads == false) and (PersistItems == false) and - (PayloadSize > 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)}; - ((DeliverPayloads == true) or (PersistItems == true)) and - (PayloadSize == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; - true -> - node_call(Type, publish_item, [NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload]) - end - end, - ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]), - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node), - children = - [#xmlel{name = <<"item">>, - attrs = itemAttr(ItemId), - children = []}]}]}], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, Broadcast, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - case get_option(Options, deliver_notifications) of - true -> - BroadcastPayload = case Broadcast of - default -> Payload; - broadcast -> Payload; - PluginPayload -> PluginPayload - end, - broadcast_publish_item(Host, Node, NodeId, Type, Options, - Removed, ItemId, jlib:jid_tolower(Publisher), - BroadcastPayload); - false -> - ok - end, - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {TNode, {default, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed), - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - {result, Reply}; - {result, {TNode, {Result, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed), - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - {result, Result}; - {result, {_, default}} -> - {result, Reply}; - {result, {_, Result}} -> - {result, Result}; - {error, ?ERR_ITEM_NOT_FOUND} -> - %% handles auto-create feature - %% for automatic node creation. we'll take the default node type: - %% first listed into the plugins configuration option, or pep - Type = select_type(ServerHost, Host, Node), - case lists:member("auto-create", features(Type)) of - true -> - case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of - {result, [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"create">>, - attrs = [{<<"node">>, NewNode}], - children = []}]}]} -> - publish_item(Host, ServerHost, list_to_binary(NewNode), - Publisher, ItemId, Payload); - _ -> - {error, ?ERR_ITEM_NOT_FOUND} - end; - false -> - {error, ?ERR_ITEM_NOT_FOUND} - end; - Error -> - Error - end. - -%% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} --spec(delete_item/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Publisher :: jid(), - ItemId :: mod_pubsub:itemId()) - -> {result, []} - %%% - | {error, xmlel()} -). -%% @doc <p>Delete item from a PubSub node.</p> -%% <p>The permission to delete an item must be verified by the plugin implementation.</p> -%%<p>There are several reasons why the item retraction request might fail:</p> -%%<ul> -%%<li>The publisher does not have sufficient privileges to delete the requested item.</li> -%%<li>The node or item does not exist.</li> -%%<li>The request does not specify a node.</li> -%%<li>The request does not include an <item/> element or the <item/> element does not specify an ItemId.</li> -%%<li>The node does not support persistent items.</li> -%%<li>The service does not support the deletion of items.</li> -%%</ul> -delete_item(Host, Node, Publisher, ItemId) -> - delete_item(Host, Node, Publisher, ItemId, false). - - -delete_item(_, <<"">>, _, _, _) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"node-required">>)}; -delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - DeleteFeature = lists:member(<<"delete-items">>, Features), - PublishModel = get_option(Options, publish_model), - if %%-> iq_pubsub just does that matchs - %% %% Request does not specify an item - %% {error, extended_error(?ERR_BAD_REQUEST, "item-required")}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - not DeleteFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"delete-items">>)}; - true -> - node_call(Type, delete_item, - [NodeId, Publisher, PublishModel, ItemId]) - end - end, - Reply = [], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, - Options, [ItemId], ForceNotify), - case get_cached_item(Host, NodeId) of - #pubsub_item{itemid = {ItemId, NodeId}} -> - unset_cached_item(Host, NodeId); - _ -> ok - end, - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> {result, Reply}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host, JID, Node) -> -%% {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% JID = jid() -%% Reason = stanzaError() -%% @doc <p>Delete all items of specified node owned by JID.</p> -%%<p>There are several reasons why the node purge request might fail:</p> -%%<ul> -%%<li>The node or service does not support node purging.</li> -%%<li>The requesting entity does not have sufficient privileges to purge the node.</li> -%%<li>The node is not configured to persist items.</li> -%%<li>The specified node does not exist.</li> -%%</ul> --spec(purge_node/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Owner :: jid()) - -> {result, []} - %%% - | {error, xmlel()} -). -purge_node(Host, Node, Owner) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PurgeFeature = lists:member(<<"purge-nodes">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - PersistentConfig = get_option(Options, persist_items), - if not PurgeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"purge-nodes">>)}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - not PersistentConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - true -> node_call(Type, purge_node, [NodeId, Owner]) - end - end, - Reply = [], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_purge_node(Host, Node, NodeId, Type, Options), - unset_cached_item(Host, NodeId), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> {result, Reply}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @doc <p>Return the items of a given node.</p> -%% <p>The number of items to return is limited by MaxItems.</p> -%% <p>The permission are not checked in this function.</p> -%% @todo We probably need to check that the user doing the query has the right -%% to read the items. --spec(get_items/6 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - SubId :: mod_pubsub:subId(), - SMaxItems :: binary(), - ItemIDs :: [mod_pubsub:itemId()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) -> - MaxItems = if SMaxItems == <<"">> -> - get_max_items_node(Host); - true -> - case catch jlib:binary_to_integer(SMaxItems) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - Val -> Val - end - end, - case MaxItems of - {error, Error} -> {error, Error}; - _ -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId, - owners = Owners}) -> - Features = features(Type), - RetreiveFeature = lists:member(<<"retrieve-items">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, - AccessModel, AllowedGroups), - if not RetreiveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-items">>)}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - true -> - node_call(Type, get_items, - [NodeId, From, AccessModel, - PresenceSubscription, RosterGroup, - SubId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> - SendItems = case ItemIDs of - [] -> Items; - _ -> - lists:filter(fun (#pubsub_item{itemid = - {ItemId, - _}}) -> - lists:member(ItemId, - ItemIDs) - end, - Items) - end, - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = - itemsEls(lists:sublist(SendItems, - MaxItems))}]}]}; - Error -> Error - end - end. - -get_items(Host, Node) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_items, [NodeId, service_jid(Host)]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error - end. - -get_item(Host, Node, ItemId) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_item, [NodeId, ItemId]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error - end. - -get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, AccessModel, - AllowedGroups), - node_call(Type, get_items, - [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]). - -%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() -%% Host = pubsubHost() -%% Node = pubsubNode() -%% NodeId = pubsubNodeId() -%% Type = pubsubNodeType() -%% LJID = {U, S, []} -%% Number = last | integer() -%% @doc <p>Resend the items of a node to the user.</p> -%% @todo use cache-last-item feature -send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) -> - case get_cached_item(Host, NodeId) of - undefined -> - send_items(Host, Node, NodeId, Type, LJID, 1); - LastItem -> - {ModifNow, ModifUSR} = - LastItem#pubsub_item.modification, - Stanza = event_stanza_with_delay([#xmlel{name = - <<"items">>, - attrs = nodeAttr(Node), - children = - itemsEls([LastItem])}], - ModifNow, ModifUSR), - case is_tuple(Host) of - false -> - ejabberd_router:route(service_jid(Host), - jlib:make_jid(LJID), Stanza); - true -> - case ejabberd_sm:get_session_pid(U, S, R) of - C2SPid when is_pid(C2SPid) -> - ejabberd_c2s:broadcast(C2SPid, - {pep_message, - <<((Node))/binary, "+notify">>}, - _Sender = service_jid(Host), - Stanza); - _ -> ok - end - end - end; -send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, - Number) -> - ToSend = case node_action(Host, Type, get_items, - [NodeId, LJID]) - of - {result, []} -> []; - {result, Items} -> - case Number of - N when N > 0 -> lists:sublist(Items, N); - _ -> Items - end; - _ -> [] - end, - Stanza = case ToSend of - [LastItem] -> - {ModifNow, ModifUSR} = - LastItem#pubsub_item.modification, - event_stanza_with_delay([#xmlel{name = <<"items">>, - attrs = nodeAttr(Node), - children = - itemsEls(ToSend)}], - ModifNow, ModifUSR); - _ -> - event_stanza([#xmlel{name = <<"items">>, - attrs = nodeAttr(Node), - children = itemsEls(ToSend)}]) - end, - case is_tuple(Host) of -%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} -%% Host = host() -%% JID = jid() -%% Plugins = [Plugin::string()] -%% Reason = stanzaError() -%% Response = [pubsubIQResponse()] -%% @doc <p>Return the list of affiliations as an XMPP response.</p> - false -> - ejabberd_router:route(service_jid(Host), - jlib:make_jid(LJID), Stanza); - true -> - case ejabberd_sm:get_session_pid(U, S, R) of - C2SPid when is_pid(C2SPid) -> - ejabberd_c2s:broadcast(C2SPid, - {pep_message, - <<((Node))/binary, "+notify">>}, - _Sender = service_jid(Host), Stanza); - _ -> ok - end - end. - --spec(get_affiliations/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - JID :: jid(), - Plugins :: [binary()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_affiliations(Host, <<>>, JID, Plugins) - when is_list(Plugins) -> - Result = lists:foldl(fun (Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"retrieve-affiliations">>, Features), - if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, JID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, - Affiliation}) -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"affiliation">>, - affiliation_to_string(Affiliation)} - | nodeAttr(Node)], - children = []}] - end, - lists:usort(lists:flatten(Affiliations))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"affiliations">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end; -get_affiliations(Host, NodeId, JID, Plugins) - when is_list(Plugins) -> - Result = lists:foldl(fun (Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"retrieve-affiliations">>, - Features), - if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, JID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, - Affiliation}) - when NodeId == Node -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"affiliation">>, - affiliation_to_string(Affiliation)} - | nodeAttr(Node)], - children = []}]; - (_) -> [] - end, - lists:usort(lists:flatten(Affiliations))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"affiliations">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end. - --spec(get_affiliations/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - JID :: jid()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_affiliations(Host, Node, JID) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"modify-affiliations">>, Features), - {result, Affiliation} = node_call(Type, get_affiliation, - [NodeId, JID]), - if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"modify-affiliations">>)}; - Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; - true -> node_call(Type, get_node_affiliations, [NodeId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, []}} -> {error, ?ERR_ITEM_NOT_FOUND}; - {result, {_, Affiliations}} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({AJID, Affiliation}) -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"affiliation">>, - affiliation_to_string(Affiliation)}], - children = []}] - end, - Affiliations), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"affiliations">>, - attrs = nodeAttr(Node), children = Entities}]}]}; - Error -> Error - end. - --spec(set_affiliations/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - EntitiesEls :: [xmlel()]) - -> {result, []} - %%% - | {error, xmlel()} -). -set_affiliations(Host, Node, From, EntitiesEls) -> - Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = lists:foldl(fun (El, Acc) -> - case Acc of - error -> error; - _ -> - case El of - #xmlel{name = <<"affiliation">>, - attrs = Attrs} -> - JID = - jlib:string_to_jid(xml:get_attr_s(<<"jid">>, - Attrs)), - Affiliation = - string_to_affiliation(xml:get_attr_s(<<"affiliation">>, - Attrs)), - if (JID == error) or - (Affiliation == false) -> - error; - true -> - [{jlib:jid_tolower(JID), - Affiliation} - | Acc] - end - end - end - end, - [], EntitiesEls), - case Entities of - error -> {error, ?ERR_BAD_REQUEST}; - _ -> - Action = fun (#pubsub_node{owners = Owners, type = Type, - id = NodeId} = - N) -> - case lists:member(Owner, Owners) of - true -> - OwnerJID = jlib:make_jid(Owner), - FilteredEntities = case Owners of - [Owner] -> - [E - || E <- Entities, - element(1, E) =/= - OwnerJID]; - _ -> Entities - end, - lists:foreach(fun ({JID, Affiliation}) -> - node_call(Type, - set_affiliation, - [NodeId, JID, - Affiliation]), - case Affiliation of - owner -> - NewOwner = - jlib:jid_tolower(jlib:jid_remove_resource(JID)), - NewOwners = - [NewOwner - | Owners], - tree_call(Host, - set_node, - [N#pubsub_node{owners - = - NewOwners}]); - none -> - OldOwner = - jlib:jid_tolower(jlib:jid_remove_resource(JID)), - case - lists:member(OldOwner, - Owners) - of - true -> - NewOwners = - Owners -- - [OldOwner], - tree_call(Host, - set_node, - [N#pubsub_node{owners - = - NewOwners}]); - _ -> ok - end; - _ -> ok - end - end, - FilteredEntities), - {result, []}; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - -get_options(Host, Node, JID, SubID, Lang) -> - Action = fun (#pubsub_node{type = Type, id = NodeID}) -> - case lists:member(<<"subscription-options">>, features(Type)) of - true -> - get_options_helper(JID, Lang, Node, NodeID, SubID, Type); - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, XForm}} -> {result, [XForm]}; - Error -> Error - end. - -get_options_helper(JID, Lang, Node, NodeID, SubID, Type) -> - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - {result, Subs} = node_call(Type, get_subscriptions, - [NodeID, Subscriber]), - SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> - [SID | Acc]; - (_, Acc) -> Acc - end, - [], Subs), - case {SubID, SubIDs} of - {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)}; - {<<>>, [SID]} -> - read_sub(Subscriber, Node, NodeID, SID, Lang); - {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)}; - {_, _} -> - read_sub(Subscriber, Node, NodeID, SubID, Lang) - end. - -read_sub(Subscriber, Node, NodeID, SubID, Lang) -> - case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, #pubsub_subscription{options = Options}} -> - {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options), - OptionsEl = #xmlel{name = <<"options">>, - attrs = - [{<<"jid">>, jlib:jid_to_string(Subscriber)}, - {<<"subid">>, SubID} - | nodeAttr(Node)], - children = [XdataEl]}, - PubsubEl = #xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [OptionsEl]}, - {result, PubsubEl} - end. - -set_options(Host, Node, JID, SubID, Configuration) -> - Action = fun (#pubsub_node{type = Type, id = NodeID}) -> - case lists:member(<<"subscription-options">>, - features(Type)) - of - true -> - set_options_helper(Configuration, JID, NodeID, SubID, - Type); - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, Result}} -> {result, Result}; - Error -> Error - end. - -set_options_helper(Configuration, JID, NodeID, SubID, Type) -> - SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> jlib:jid_tolower(J) - end, - {result, Subs} = node_call(Type, get_subscriptions, - [NodeID, Subscriber]), - SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> - [SID | Acc]; - (_, Acc) -> Acc - end, - [], Subs), - case {SubID, SubIDs} of - {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"not-subscribed">>)}; - {<<>>, [SID]} -> - write_sub(Subscriber, NodeID, SID, SubOpts); - {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"subid-required">>)}; - {_, _} -> write_sub(Subscriber, NodeID, SubID, SubOpts) - end. - -write_sub(_Subscriber, _NodeID, _SubID, invalid) -> - {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; -write_sub(Subscriber, NodeID, SubID, Options) -> - case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, _} -> - {result, []} - end. - -%% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} -%% Host = host() -%% Node = pubsubNode() -%% JID = jid() -%% Plugins = [Plugin::string()] -%% Reason = stanzaError() -%% Response = [pubsubIQResponse()] -%% @doc <p>Return the list of subscriptions as an XMPP response.</p> -get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> - Result = lists:foldl( - fun(Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features), - if - not RetrieveFeature -> - %% Service does not support retreive subscriptions - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-subscriptions">>)}, Acc}; - true -> - Subscriber = jlib:jid_remove_resource(JID), - {result, Subscriptions} = node_action(Host, Type, get_entity_subscriptions, [Host, Subscriber]), - {Status, [Subscriptions|Acc]} - end - end, {ok, []}, Plugins), - case Result of - {ok, Subscriptions} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end; - ({_, none, _}) -> []; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription, SubID, SubJID}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subid">>, - SubID}, - {<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subid">>, - SubID}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription, SubJID}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end - end, - lists:usort(lists:flatten(Subscriptions))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"subscriptions">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end. - -get_subscriptions(Host, Node, JID) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"manage-subscriptions">>, Features), - {result, Affiliation} = node_call(Type, get_affiliation, - [NodeId, JID]), - if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"manage-subscriptions">>)}; - Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; - true -> - node_call(Type, get_node_subscriptions, [NodeId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Subscriptions}} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({_, pending, _}) -> []; - ({AJID, Subscription}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - ({AJID, Subscription, SubId}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}, - {<<"subid">>, SubId}], - children = []}] - end, - Subscriptions), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"subscriptions">>, - attrs = nodeAttr(Node), children = Entities}]}]}; - Error -> Error - end. - -set_subscriptions(Host, Node, From, EntitiesEls) -> - Owner = - jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = lists:foldl(fun (El, Acc) -> - case Acc of - error -> error; - _ -> - case El of - #xmlel{name = <<"subscription">>, - attrs = Attrs} -> - JID = - jlib:string_to_jid(xml:get_attr_s(<<"jid">>, - Attrs)), - Subscription = - string_to_subscription(xml:get_attr_s(<<"subscription">>, - Attrs)), - SubId = - xml:get_attr_s(<<"subid">>, - Attrs), - if (JID == error) or - (Subscription == false) -> - error; - true -> - [{jlib:jid_tolower(JID), - Subscription, SubId} - | Acc] - end - end - end - end, - [], EntitiesEls), - case Entities of - error -> {error, ?ERR_BAD_REQUEST}; - _ -> - Notify = fun (JID, Sub, _SubId) -> - Stanza = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"pubsub">>, - attrs = - [{<<"xmlns">>, - ?NS_PUBSUB}], - children = - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(JID)}, - {<<"subscription">>, - subscription_to_string(Sub)} - | nodeAttr(Node)], - children = - []}]}]}, - ejabberd_router:route(service_jid(Host), - jlib:make_jid(JID), Stanza) - end, - Action = fun (#pubsub_node{owners = Owners, type = Type, - id = NodeId}) -> - case lists:member(Owner, Owners) of - true -> - Result = lists:foldl(fun ({JID, Subscription, - SubId}, - Acc) -> - case - node_call(Type, - set_subscriptions, - [NodeId, - JID, - Subscription, - SubId]) - of - {error, Err} -> - [{error, - Err} - | Acc]; - _ -> - Notify(JID, - Subscription, - SubId), - Acc - end - end, - [], Entities), - case Result of - [] -> {result, []}; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - --spec(get_presence_and_roster_permissions/5 :: -( - Host :: mod_pubsub:host(), - From :: ljid(), - Owners :: [ljid(),...], - AccessModel :: mod_pubsub:accessModel(), - AllowedGroups :: [binary()]) - -> {PresenceSubscription::boolean(), RosterGroup::boolean()} -). - -get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) -> - if (AccessModel == presence) or (AccessModel == roster) -> - case Host of - {User, Server, _} -> - get_roster_info(User, Server, From, AllowedGroups); - _ -> - [{OUser, OServer, _} | _] = Owners, - get_roster_info(OUser, OServer, From, AllowedGroups) - end; - true -> {true, true} - end. - -%% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, SubscriberResource}, AllowedGroups) -%% -> {PresenceSubscription, RosterGroup} -get_roster_info(_, _, {<<"">>, <<"">>, _}, _) -> - {false, false}; -get_roster_info(OwnerUser, OwnerServer, - {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> - {Subscription, Groups} = - ejabberd_hooks:run_fold(roster_get_jid_info, - OwnerServer, {none, []}, - [OwnerUser, OwnerServer, - {SubscriberUser, SubscriberServer, <<"">>}]), - PresenceSubscription = Subscription == both orelse - Subscription == from orelse - {OwnerUser, OwnerServer} == - {SubscriberUser, SubscriberServer}, - RosterGroup = lists:any(fun (Group) -> - lists:member(Group, AllowedGroups) - end, - Groups), - {PresenceSubscription, RosterGroup}; -%% @spec (AffiliationStr) -> Affiliation -%% AffiliationStr = string() -%% Affiliation = atom() -%% @doc <p>Convert an affiliation type from string to atom.</p> -get_roster_info(OwnerUser, OwnerServer, JID, - AllowedGroups) -> - get_roster_info(OwnerUser, OwnerServer, - jlib:jid_tolower(JID), AllowedGroups). - -string_to_affiliation(<<"owner">>) -> owner; -string_to_affiliation(<<"publisher">>) -> publisher; -string_to_affiliation(<<"member">>) -> member; -string_to_affiliation(<<"outcast">>) -> outcast; -string_to_affiliation(<<"none">>) -> none; -string_to_affiliation(_) -> false. - -%% @spec (SubscriptionStr) -> Subscription -%% SubscriptionStr = string() -%% Subscription = atom() -%% @doc <p>Convert a subscription type from string to atom.</p> -string_to_subscription(<<"subscribed">>) -> subscribed; -string_to_subscription(<<"pending">>) -> pending; -string_to_subscription(<<"unconfigured">>) -> - unconfigured; -string_to_subscription(<<"none">>) -> none; -string_to_subscription(_) -> false. - -%% @spec (Affiliation) -> AffiliationStr -%% Affiliation = atom() -%% AffiliationStr = string() -%% @doc <p>Convert an affiliation type from atom to string.</p> -%% @spec (Subscription) -> SubscriptionStr -%% Subscription = atom() -%% SubscriptionStr = string() -%% @doc <p>Convert a subscription type from atom to string.</p> -%% @spec (Node) -> NodeStr -%% Node = pubsubNode() -%% NodeStr = string() -%% @doc <p>Convert a node type from pubsubNode to string.</p> -%% @spec (Host) -> jid() -%% Host = host() -%% @doc <p>Generate pubsub service JID.</p> -affiliation_to_string(owner) -> <<"owner">>; -affiliation_to_string(publisher) -> <<"publisher">>; -affiliation_to_string(member) -> <<"member">>; -affiliation_to_string(outcast) -> <<"outcast">>; -affiliation_to_string(_) -> <<"none">>. - -subscription_to_string(subscribed) -> <<"subscribed">>; -subscription_to_string(pending) -> <<"pending">>; -subscription_to_string(unconfigured) -> <<"unconfigured">>; -subscription_to_string(_) -> <<"none">>. - --spec(service_jid/1 :: -( - Host :: mod_pubsub:host()) - -> jid() -). -service_jid(Host) -> -%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() -%% LJID = jid() -%% NotifyType = items | nodes -%% Depth = integer() -%% NodeOptions = [{atom(), term()}] -%% SubOptions = [{atom(), term()}] -%% @doc <p>Check if a notification must be delivered or not based on -%% node and subscription options.</p> - case Host of - {U, S, _} -> {jid, U, S, <<"">>, U, S, <<"">>}; - _ -> {jid, <<"">>, Host, <<"">>, <<"">>, Host, <<"">>} - end. - -is_to_deliver(LJID, NotifyType, Depth, NodeOptions, - SubOptions) -> - sub_to_deliver(LJID, NotifyType, Depth, SubOptions) - andalso node_to_deliver(LJID, NodeOptions). - -sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> - lists:all(fun (Option) -> - sub_option_can_deliver(NotifyType, Depth, Option) - end, - SubOptions). - -sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; -sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; -sub_option_can_deliver(_, _, {subscription_depth, all}) -> true; -sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D; -sub_option_can_deliver(_, _, {deliver, false}) -> false; -sub_option_can_deliver(_, _, {expire, When}) -> now() < When; -sub_option_can_deliver(_, _, _) -> true. - -node_to_deliver(LJID, NodeOptions) -> - PresenceDelivery = get_option(NodeOptions, presence_based_delivery), - presence_can_deliver(LJID, PresenceDelivery). - --spec(presence_can_deliver/2 :: -( - Entity :: ljid(), - _ :: boolean()) - -> boolean() -). -presence_can_deliver(_, false) -> true; -presence_can_deliver({User, Server, Resource}, true) -> - case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of - [] -> false; - Ss -> - lists:foldl(fun(_, true) -> true; - ({session, _, _ , _, undefined, _}, _Acc) -> false; - ({session, _, {_, _, R}, _, _Priority, _}, _Acc) -> - case Resource of - [] -> true; - R -> true; - _ -> false - end - end, false, Ss) - end. - --spec(state_can_deliver/2 :: -( - Entity::ljid(), - SubOptions :: mod_pubsub:subOptions() | []) - -> [ljid()] -). -state_can_deliver({U, S, R}, []) -> [{U, S, R}]; -state_can_deliver({U, S, R}, SubOptions) -> - %% Check SubOptions for 'show_values' - case lists:keysearch('show_values', 1, SubOptions) of - %% If not in suboptions, item can be delivered, case doesn't apply - false -> [{U, S, R}]; - %% If in a suboptions ... - {_, {_, ShowValues}} -> - %% Get subscriber resources - Resources = case R of - %% If the subscriber JID is a bare one, get all its resources - <<>> -> user_resources(U, S); - %% If the subscriber JID is a full one, use its resource - R -> [R] - end, - %% For each resource, test if the item is allowed to be delivered - %% based on resource state - lists:foldl( - fun(Resource, Acc) -> - get_resource_state({U, S, Resource}, ShowValues, Acc) - end, [], Resources) - end. - --spec(get_resource_state/3 :: -( - Entity :: ljid(), - ShowValues :: [binary()], - JIDs :: [ljid()]) - -> [ljid()] -). -get_resource_state({U, S, R}, ShowValues, JIDs) -> - case ejabberd_sm:get_session_pid(U, S, R) of - %% If no PID, item can be delivered - none -> lists:append([{U, S, R}], JIDs); - %% If PID ... - Pid -> - %% Get user resource state - %% TODO : add a catch clause - Show = case ejabberd_c2s:get_presence(Pid) of - {_, _, <<"available">>, _} -> <<"online">>; - {_, _, State, _} -> State - end, - %% Is current resource state listed in 'show-values' suboption ? - case lists:member(Show, ShowValues) of %andalso Show =/= "online" of - %% If yes, item can be delivered - true -> lists:append([{U, S, R}], JIDs); - %% If no, item can't be delivered - false -> JIDs - end - end. - -%% @spec (Payload) -> int() -%% Payload = term() --spec(payload_xmlelements/1 :: -( - Payload :: mod_pubsub:payload()) - -> Count :: non_neg_integer() -). -%% @doc <p>Count occurence of XML elements in payload.</p> -payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0). -payload_xmlelements([], Count) -> Count; -payload_xmlelements([#xmlel{} | Tail], Count) -> - payload_xmlelements(Tail, Count + 1); -payload_xmlelements([_ | Tail], Count) -> - payload_xmlelements(Tail, Count). - -%% @spec (Els) -> stanza() -%% Els = [xmlelement()] -%% @doc <p>Build pubsub event stanza</p> -event_stanza(Els) -> event_stanza_withmoreels(Els, []). - -event_stanza_with_delay(Els, ModifNow, ModifUSR) -> - DateTime = calendar:now_to_datetime(ModifNow), - MoreEls = [jlib:timestamp_to_xml(DateTime, utc, - ModifUSR, <<"">>)], - event_stanza_withmoreels(Els, MoreEls). - -event_stanza_withmoreels(Els, MoreEls) -> - #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"event">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], - children = Els} - | MoreEls]}. - -%%%%%% broadcast functions - -broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> Payload; - false -> [] - end, - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), - children = Content}]}]), - broadcast_stanza(Host, From, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - case Removed of - [] -> - ok; - _ -> - case get_option(NodeOptions, notify_retract) of - true -> - RetractStanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, - items, RetractStanza, true); - _ -> - ok - end - end, - {result, true}; - _ -> - {result, false} - end. - -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) -> - broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false). -broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) -> - {result, false}; -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) -> - case (get_option(NodeOptions, notify_retract) or ForceNotify) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) -> - case get_option(NodeOptions, notify_retract) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - case get_option(NodeOptions, notify_delete) of - true -> - case SubsByDepth of - [] -> - {result, false}; - _ -> - Stanza = event_stanza( - [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true} - end; - _ -> - {result, false} - end. - -broadcast_created_node(_, _, _, _, _, []) -> - {result, false}; -broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), - {result, true}. - -broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) -> - case get_option(NodeOptions, notify_config) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> - [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = get_configure_xfields(Type, NodeOptions, Lang, [])}]; - false -> - [] - end, - Stanza = event_stanza( - [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -get_collection_subscriptions(Host, Node) -> - Action = fun() -> - {result, lists:map(fun({Depth, Nodes}) -> - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} - end, - case transaction(Action, sync_dirty) of - {result, CollSubs} -> CollSubs; - _ -> [] - end. - -get_node_subs(#pubsub_node{type = Type, - id = NodeID}) -> - case node_call(Type, get_node_subscriptions, [NodeID]) of - {result, Subs} -> get_options_for_subs(NodeID, Subs); - Other -> Other - end. - -get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> - case pubsub_subscription:read_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; - #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> - Acc - end, [], Subs). - -broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - NotificationType = get_option(NodeOptions, notification_type, headline), - BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = case NotificationType of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% Handles explicit subscriptions - SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), - lists:foreach(fun ({LJID, NodeName, SubIDs}) -> - LJIDs = case BroadcastAll of - true -> - {U, S, _} = LJID, - [{U, S, R} || R <- user_resources(U, S)]; - false -> - [LJID] - end, - %% Determine if the stanza should have SHIM ('SubID' and 'name') headers - StanzaToSend = case {SHIM, SubIDs} of - {false, _} -> - Stanza; - %% If there's only one SubID, don't add it - {true, [_]} -> - add_shim_headers(Stanza, collection_shim(NodeName)); - {true, SubIDs} -> - add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs))) - end, - lists:foreach(fun(To) -> - ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend) - end, LJIDs) - end, SubIDsByJID). - -broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM), - %% Handles implicit presence subscriptions - SenderResource = case LResource of - [] -> - case user_resources(LUser, LServer) of - [Resource|_] -> Resource; - _ -> "" - end; - _ -> - LResource - end, - case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of - C2SPid when is_pid(C2SPid) -> - Stanza = case get_option(NodeOptions, notification_type, headline) of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% set the from address on the notification to the bare JID of the account owner - %% Also, add "replyto" if entity has presence subscription to the account owner - %% See XEP-0163 1.1 section 4.3.1 - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = jlib:make_jid(LUser, LServer, ""), - _StanzaToSend = add_extended_headers(Stanza, - _ReplyTo = extended_headers([jlib:jid_to_string(Publisher)]))); - _ -> - ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza]) - end; -broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). - -subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> - NodesToDeliver = fun(Depth, Node, Subs, Acc) -> - NodeName = case Node#pubsub_node.nodeid of - {_, N} -> N; - Other -> Other - end, - NodeOptions = Node#pubsub_node.options, - lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> - case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of - true -> - %% If is to deliver : - case state_can_deliver(LJID, SubOptions) of - [] -> {JIDs, Recipients}; - JIDsToDeliver -> - lists:foldl( - fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> - case lists:member(JIDToDeliver, JIDs) of - %% check if the JIDs co-accumulator contains the Subscription Jid, - false -> - %% - if not, - %% - add the Jid to JIDs list co-accumulator ; - %% - create a tuple of the Jid, NodeId, and SubID (as list), - %% and add the tuple to the Recipients list co-accumulator - {[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]}; - true -> - %% - if the JIDs co-accumulator contains the Jid - %% get the tuple containing the Jid from the Recipient list co-accumulator - {_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc), - %% delete the tuple from the Recipients list - % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients), - % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, NodeId1, [SubID | SubIDs]}), - %% add the SubID to the SubIDs list in the tuple, - %% and add the tuple back to the Recipients list co-accumulator - % v1.1 : {JIDs, lists:append(Recipients1, [{LJID, NodeId1, lists:append(SubIDs, [SubID])}])} - % v1.2 : {JIDs, [{LJID, NodeId1, [SubID | SubIDs]} | Recipients1]} - % v2: {JIDs, Recipients1} - {JIDsAcc, lists:keyreplace(JIDToDeliver, 1, RecipientsAcc, {JIDToDeliver, NodeName1, [SubID | SubIDs]})} - end - end, {JIDs, Recipients}, JIDsToDeliver) - end; - false -> - {JIDs, Recipients} - end - end, Acc, Subs) - end, - DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) -> - lists:foldl(fun({Node, Subs}, Acc2) -> - NodesToDeliver(Depth, Node, Subs, Acc2) - end, Acc1, SubsByNode) - end, - {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), - JIDSubs. - -user_resources(User, Server) -> - ejabberd_sm:get_user_resources(User, Server). - -%%%%%%% Configuration handling - -%%<p>There are several reasons why the default node configuration options request might fail:</p> -%%<ul> -%%<li>The service does not support node configuration.</li> -%%<li>The service does not support retrieval of default node configuration.</li> -%%</ul> -get_configure(Host, ServerHost, Node, From, Lang) -> - Action = fun (#pubsub_node{options = Options, - type = Type, id = NodeId}) -> - case node_call(Type, get_affiliation, [NodeId, From]) of - {result, owner} -> - Groups = ejabberd_hooks:run_fold(roster_groups, - ServerHost, [], - [ServerHost]), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"configure">>, - attrs = nodeAttr(Node), - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, - <<"form">>}], - children = - get_configure_xfields(Type, - Options, - Lang, - Groups)}]}]}]}; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end. - -get_default(Host, Node, _From, Lang) -> - Type = select_type(Host, Host, Node), - Options = node_options(Type), -%% Get node option -%% The result depend of the node type plugin system. - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"default">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - get_configure_xfields(Type, Options, - Lang, [])}]}]}]}. - -get_option([], _) -> false; -get_option(Options, Var) -> - get_option(Options, Var, false). - -get_option(Options, Var, Def) -> - case lists:keysearch(Var, 1, Options) of - {value, {_Val, Ret}} -> Ret; - _ -> Def - end. - -%% Get default options from the module plugin. -node_options(Type) -> - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - case catch Module:options() of - {'EXIT', {undef, _}} -> - DefaultModule = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - (?STDNODE)/binary>>), - DefaultModule:options(); - Result -> Result - end. - -%% @spec (Host, Options) -> MaxItems -%% Host = host() -%% Options = [Option] -%% Option = {Key::atom(), Value::term()} -%% MaxItems = integer() | unlimited -%% @doc <p>Return the maximum number of items for a given node.</p> -%% <p>Unlimited means that there is no limit in the number of items that can -%% be stored.</p> -%% @todo In practice, the current data structure means that we cannot manage -%% millions of items on a given node. This should be addressed in a new -%% version. -max_items(Host, Options) -> - case get_option(Options, persist_items) of - true -> - case get_option(Options, max_items) of - false -> unlimited; - Result when Result < 0 -> 0; - Result -> Result - end; - false -> - case get_option(Options, send_last_published_item) of - never -> 0; - _ -> - case is_last_item_cache_enabled(Host) of - true -> 0; - false -> 1 - end - end - end. - --define(BOOL_CONFIG_FIELD(Label, Var), - ?BOOLXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var)))). - --define(STRING_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var, <<"">>)))). - --define(INTEGER_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (iolist_to_binary(integer_to_list(get_option(Options, - Var)))))). - --define(JLIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (jlib:jid_to_string(get_option(Options, Var))), - [jlib:jid_to_string(O) || O <- Opts])). - --define(ALIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (iolist_to_binary(atom_to_list(get_option(Options, - Var)))), - [iolist_to_binary(atom_to_list(O)) || O <- Opts])). - --define(LISTM_CONFIG_FIELD(Label, Var, Opts), - ?LISTMXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var)), Opts)). - --define(NLIST_CONFIG_FIELD(Label, Var), - ?STRINGMXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - get_option(Options, Var, []))). - -get_configure_xfields(_Type, Options, Lang, Groups) -> - [?XFIELD(<<"hidden">>, <<"">>, <<"FORM_TYPE">>, - (?NS_PUBSUB_NODE_CONFIG)), - ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>, - deliver_payloads), - ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>, - deliver_notifications), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuratio" - "n changes">>, - notify_config), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is " - "deleted">>, - notify_delete), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed " - "from the node">>, - notify_retract), - ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>, - persist_items), - ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>, - title), - ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>, - max_items), - ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>, - subscribe), - ?ALIST_CONFIG_FIELD(<<"Specify the access model">>, - access_model, - [open, authorize, presence, roster, whitelist]), - ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>, - roster_groups_allowed, Groups), - ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>, - publish_model, [publishers, subscribers, open]), - ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher " - "goes offline">>, - purge_offline), - ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>, - notification_type, [headline, normal]), - ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>, - max_payload_size), - ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>, - send_last_published_item, - [never, on_sub, on_sub_and_presence]), - ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available " - "users">>, - presence_based_delivery), - ?NLIST_CONFIG_FIELD(<<"The collections with which a node is " - "affiliated">>, - collection)]. - -%%<p>There are several reasons why the node configuration request might fail:</p> -%%<ul> -%%<li>The service does not support node configuration.</li> -%%<li>The requesting entity does not have sufficient privileges to configure the node.</li> -%%<li>The request did not specify a node.</li> -%%<li>The node has no configuration options.</li> -%%<li>The specified node does not exist.</li> -%%</ul> -set_configure(Host, Node, From, Els, Lang) -> - case xml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, []}; - {?NS_XDATA, <<"submit">>} -> - Action = fun (#pubsub_node{options = Options, - type = Type, id = NodeId} = - N) -> - case node_call(Type, get_affiliation, - [NodeId, From]) - of - {result, owner} -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData -> - OldOpts = case Options of - [] -> - node_options(Type); - _ -> Options - end, - case set_xoption(Host, XData, - OldOpts) - of - NewOpts - when is_list(NewOpts) -> - case tree_call(Host, - set_node, - [N#pubsub_node{options - = - NewOpts}]) - of - ok -> {result, ok}; - Err -> Err - end; - Err -> Err - end - end; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, transaction) of - {result, {TNode, ok}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_config_notification(Host, Node, NodeId, Type, - Options, Lang), - {result, []}; - Other -> Other - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end. - -add_opt(Key, Value, Opts) -> - Opts1 = lists:keydelete(Key, 1, Opts), - [{Key, Value} | Opts1]. - --define(SET_BOOL_XOPT(Opt, Val), - BoolVal = case Val of - <<"0">> -> false; - <<"1">> -> true; - <<"false">> -> false; - <<"true">> -> true; - _ -> error - end, - case BoolVal of - error -> {error, ?ERR_NOT_ACCEPTABLE}; - _ -> - set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) - end). - --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). - --define(SET_INTEGER_XOPT(Opt, Val, Min, Max), - case catch jlib:binary_to_integer(Val) of - IVal when is_integer(IVal), IVal >= Min, IVal =< Max -> - set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end). - --define(SET_ALIST_XOPT(Opt, Val, Vals), - case lists:member(Val, - [iolist_to_binary(atom_to_list(V)) || V <- Vals]) - of - true -> - set_xoption(Host, Opts, - add_opt(Opt, jlib:binary_to_atom(Val), NewOpts)); - false -> {error, ?ERR_NOT_ACCEPTABLE} - end). - --define(SET_LIST_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). - -set_xoption(_Host, [], NewOpts) -> NewOpts; -set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], - NewOpts) -> - set_xoption(Host, Opts, NewOpts); -set_xoption(Host, - [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], - NewOpts) -> - ?SET_LIST_XOPT(roster_groups_allowed, Value); -set_xoption(Host, - [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(deliver_payloads, Val); -set_xoption(Host, - [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(deliver_notifications, Val); -set_xoption(Host, - [{<<"pubsub#notify_config">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_config, Val); -set_xoption(Host, - [{<<"pubsub#notify_delete">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_delete, Val); -set_xoption(Host, - [{<<"pubsub#notify_retract">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_retract, Val); -set_xoption(Host, - [{<<"pubsub#persist_items">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(persist_items, Val); -set_xoption(Host, - [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) -> - MaxItems = get_max_items_node(Host), - ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems); -set_xoption(Host, - [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(subscribe, Val); -set_xoption(Host, - [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(access_model, Val, - [open, authorize, presence, roster, whitelist]); -set_xoption(Host, - [{<<"pubsub#publish_model">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(publish_model, Val, - [publishers, subscribers, open]); -set_xoption(Host, - [{<<"pubsub#notification_type">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(notification_type, Val, - [headline, normal]); -set_xoption(Host, - [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]); -set_xoption(Host, - [{<<"pubsub#max_payload_size">>, [Val]} | Opts], - NewOpts) -> - ?SET_INTEGER_XOPT(max_payload_size, Val, 0, - (?MAX_PAYLOAD_SIZE)); -set_xoption(Host, - [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(send_last_published_item, Val, - [never, on_sub, on_sub_and_presence]); -set_xoption(Host, - [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(presence_based_delivery, Val); -set_xoption(Host, - [{<<"pubsub#purge_offline">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(purge_offline, Val); -set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], - NewOpts) -> - ?SET_STRING_XOPT(title, Value); -set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], - NewOpts) -> - ?SET_STRING_XOPT(type, Value); -set_xoption(Host, - [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) -> - ?SET_STRING_XOPT(body_xslt, Value); -set_xoption(Host, - [{<<"pubsub#collection">>, Value} | Opts], NewOpts) -> -% NewValue = [string_to_node(V) || V <- Value], - ?SET_LIST_XOPT(collection, Value); -set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], - NewOpts) -> -% NewValue = string_to_node(Value), - ?SET_LIST_XOPT(node, Value); -set_xoption(Host, [_ | Opts], NewOpts) -> - set_xoption(Host, Opts, NewOpts). - -get_max_items_node({_, ServerHost, _}) -> - get_max_items_node(ServerHost); -get_max_items_node(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - max_items_node) - of - [{max_items_node, Integer}] -> Integer; - _ -> ?MAXITEMS - end. - -%%%% last item cache handling - -is_last_item_cache_enabled({_, ServerHost, _}) -> - is_last_item_cache_enabled(ServerHost); -is_last_item_cache_enabled(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - last_item_cache) - of - [{last_item_cache, true}] -> true; - _ -> false - end. - -set_cached_item({_, ServerHost, _}, NodeId, ItemId, - Publisher, Payload) -> - set_cached_item(ServerHost, NodeId, ItemId, Publisher, - Payload); -set_cached_item(Host, NodeId, ItemId, Publisher, - Payload) -> - case is_last_item_cache_enabled(Host) of - true -> - mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, - {now(), - jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, - Payload}); - _ -> ok - end. - -unset_cached_item({_, ServerHost, _}, NodeId) -> - unset_cached_item(ServerHost, NodeId); -unset_cached_item(Host, NodeId) -> - case is_last_item_cache_enabled(Host) of - true -> mnesia:dirty_delete({pubsub_last_item, NodeId}); - _ -> ok - end. - --spec(get_cached_item/2 :: -( - Host :: mod_pubsub:host(), - NodeIdx :: mod_pubsub:nodeIdx()) - -> undefined | mod_pubsub:pubsubItem() -). -get_cached_item({_, ServerHost, _}, NodeId) -> - get_cached_item(ServerHost, NodeId); -get_cached_item(Host, NodeIdx) -> - case is_last_item_cache_enabled(Host) of - true -> - case mnesia:dirty_read({pubsub_last_item, NodeIdx}) of - [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] -> -% [{pubsub_last_item, NodeId, ItemId, Creation, -% Payload}] -> - #pubsub_item{itemid = {ItemId, NodeIdx}, - payload = Payload, creation = Creation, - modification = Creation}; - _ -> undefined - end; - _ -> undefined - end. - -%%%% plugin handling - -host(ServerHost) -> - case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, config), - host) - of - [{host, Host}] -> Host; - _ -> <<"pubsub.", ServerHost/binary>> - end. - -plugins(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - plugins) - of - [{plugins, []}] -> [?STDNODE]; - [{plugins, PL}] -> PL; - _ -> [?STDNODE] - end. - -select_type(ServerHost, Host, Node, Type) -> - SelectedType = case Host of - {_User, _Server, _Resource} -> - case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, - config), - pep_mapping) - of - [{pep_mapping, PM}] -> - proplists:get_value(Node, PM, ?PEPNODE); - _ -> ?PEPNODE - end; - _ -> Type - end, - ConfiguredTypes = plugins(ServerHost), - case lists:member(SelectedType, ConfiguredTypes) of - true -> SelectedType; - false -> hd(ConfiguredTypes) - end. - -select_type(ServerHost, Host, Node) -> - select_type(ServerHost, Host, Node, - hd(plugins(ServerHost))). - -features() -> - [% see plugin "access-authorize", % OPTIONAL - <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree - <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep - <<"access-whitelist">>, % OPTIONAL - <<"collections">>, % RECOMMENDED - <<"config-node">>, % RECOMMENDED - <<"create-and-configure">>, % RECOMMENDED - <<"item-ids">>, % RECOMMENDED - <<"last-published">>, % RECOMMENDED - <<"member-affiliation">>, % RECOMMENDED - <<"presence-notifications">>, % OPTIONAL - <<"presence-subscribe">>, % RECOMMENDED - <<"publisher-affiliation">>, % RECOMMENDED - <<"retrieve-default">>]. - - % see plugin "retrieve-items", % RECOMMENDED - % see plugin "retrieve-subscriptions", % RECOMMENDED - %TODO "shim", % OPTIONAL - % see plugin "subscribe", % REQUIRED - % see plugin "subscription-options", % OPTIONAL - % see plugin "subscription-notifications" % OPTIONAL - -features(Type) -> - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - features() ++ - case catch Module:features() of - {'EXIT', {undef, _}} -> []; - Result -> Result - end. - -features(Host, <<>>) -> - lists:usort(lists:foldl(fun (Plugin, Acc) -> - Acc ++ features(Plugin) - end, - [], plugins(Host))); -features(Host, Node) -> - Action = fun (#pubsub_node{type = Type}) -> - {result, features(Type)} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, Features} -> - lists:usort(features() ++ Features); - _ -> features() - end. - -%% @doc <p>node tree plugin call.</p> -tree_call({_User, Server, _Resource}, Function, Args) -> - tree_call(Server, Function, Args); -tree_call(Host, Function, Args) -> - ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]), - Module = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> - jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, - (?STDTREE)/binary>>) - end, - catch apply(Module, Function, Args). - -tree_action(Host, Function, Args) -> - ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]), - Fun = fun () -> tree_call(Host, Function, Args) end, - catch mnesia:sync_dirty(Fun). - -%% @doc <p>node plugin call.</p> -node_call(Type, Function, Args) -> - ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]), - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - case apply(Module, Function, Args) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {'EXIT', {undef, Undefined}} -> - case Type of - ?STDNODE -> {error, {undef, Undefined}}; - _ -> node_call(?STDNODE, Function, Args) - end; - {'EXIT', Reason} -> {error, Reason}; - Result -> - {result, - Result} %% any other return value is forced as result - end. - -node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p", - [Host, Type, Function, Args]), - transaction(fun () -> node_call(Type, Function, Args) - end, - sync_dirty). - -%% @doc <p>plugin transaction handling.</p> -transaction(Host, Node, Action, Trans) -> - transaction(fun () -> - case tree_call(Host, get_node, [Host, Node]) of - N when is_record(N, pubsub_node) -> - case Action(N) of - {result, Result} -> {result, {N, Result}}; - {atomic, {result, Result}} -> - {result, {N, Result}}; - Other -> Other - end; - Error -> Error - end - end, - Trans). - -transaction(Host, Action, Trans) -> - transaction(fun () -> - {result, - lists:foldl(Action, [], - tree_call(Host, get_nodes, [Host]))} - end, - Trans). - -transaction(Fun, Trans) -> - case catch mnesia:Trans(Fun) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {atomic, {result, Result}} -> {result, Result}; - {atomic, {error, Error}} -> {error, Error}; - {aborted, Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {'EXIT', Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - Other -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [Other]), - {error, ?ERR_INTERNAL_SERVER_ERROR} - end. - -%%%% helpers - -%% Add pubsub-specific error element -extended_error(Error, Ext) -> - extended_error(Error, Ext, - [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]). - -extended_error(Error, unsupported, Feature) -> -%% Give a uniq identifier - extended_error(Error, <<"unsupported">>, - [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}, - {<<"feature">>, Feature}]); -extended_error(#xmlel{name = Error, attrs = Attrs, - children = SubEls}, - Ext, ExtAttrs) -> - #xmlel{name = Error, attrs = Attrs, - children = - lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs, - children = []} - | SubEls])}. - --spec(uniqid/0 :: () -> mod_pubsub:itemId()). -uniqid() -> - {T1, T2, T3} = now(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -nodeAttr(Node) -> [{<<"node">>, Node}]. - -itemAttr([]) -> []; -itemAttr(ItemId) -> [{<<"id">>, ItemId}]. - -itemsEls(Items) -> - lists:map(fun (#pubsub_item{itemid = {ItemId, _}, payload = Payload}) -> - #xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload} - end, Items). - -add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, - Type) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, Type} | Attrs], children = Els}; -add_message_type(XmlEl, _Type) -> XmlEl. - -%% Place of <headers/> changed at the bottom of the stanza -%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid -%% -%% "[SHIM Headers] SHOULD be included after the event notification information -%% (i.e., as the last child of the <message/> stanza)". - -add_shim_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls). - -add_extended_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, - HeaderEls). - -add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, - HeaderName, HeaderNS, HeaderEls) -> - HeaderEl = #xmlel{name = HeaderName, - attrs = [{<<"xmlns">>, HeaderNS}], - children = HeaderEls}, - #xmlel{name = Name, attrs = Attrs, - children = lists:append(Els, [HeaderEl])}. - -%% Removed multiple <header name=Collection>Foo</header/> elements -%% Didn't seem compliant, but not sure. Confirmation required. -%% cf. http://xmpp.org/extensions/xep-0248.html#notify -%% -%% "If an item is published to a node which is also included by a collection, -%% and an entity is subscribed to that collection with a subscription type of -%% "items" (Is there a way to check that currently ?), then the notifications -%% generated by the service MUST contain additional information. The <items/> -%% element contained in the notification message MUST specify the node -%% identifier of the node that generated the notification (not the collection) -%% and the <item/> element MUST contain a SHIM header that specifies the node -%% identifier of the collection". - -collection_shim(Node) -> - [#xmlel{name = <<"header">>, - attrs = [{<<"name">>, <<"Collection">>}], - children = [{xmlcdata, Node}]}]. - -subid_shim(SubIDs) -> - [#xmlel{name = <<"header">>, - attrs = [{<<"name">>, <<"SubID">>}], - children = [{xmlcdata, SubID}]} - || SubID <- SubIDs]. - -%% The argument is a list of Jids because this function could be used -%% with the 'pubsub#replyto' (type=jid-multi) node configuration. - -extended_headers(Jids) -> - [#xmlel{name = <<"address">>, - attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}], - children = []} - || Jid <- Jids]. - -on_user_offline(_, JID, _) -> - {User, Server, Resource} = jlib:jid_tolower(JID), - case ejabberd_sm:get_user_resources(User, Server) of - [] -> purge_offline({User, Server, Resource}); - _ -> true - end. - -purge_offline({User, Server, _} = LJID) -> - Host = host(element(2, LJID)), - Plugins = plugins(Host), - Result = lists:foldl(fun (Type, {Status, Acc}) -> - case lists:member(<<"retrieve-affiliations">>, - features(Type)) - of - false -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, LJID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - lists:foreach(fun ({#pubsub_node{nodeid = {_, NodeId}, - options = Options, type = Type}, - Affiliation}) - when Affiliation == owner orelse - Affiliation == publisher -> - Action = fun (#pubsub_node{type = NType, - id = NodeIdx}) -> - node_call(NType, get_items, - [NodeIdx, - service_jid(Host)]) - end, - case transaction(Host, NodeId, Action, - sync_dirty) - of - {result, {_, []}} -> true; - {result, {_, Items}} -> - Features = features(Type), - case {lists:member(<<"retract-items">>, - Features), - lists:member(<<"persistent-items">>, - Features), - get_option(Options, persist_items), - get_option(Options, purge_offline)} - of - {true, true, true, true} -> - ForceNotify = get_option(Options, - notify_retract), - lists:foreach(fun - (#pubsub_item{itemid - = - {ItemId, - _}, - modification - = - {_, - Modification}}) -> - case - Modification - of - {User, Server, - _} -> - delete_item(Host, - NodeId, - LJID, - ItemId, - ForceNotify); - _ -> true - end; - (_) -> true - end, - Items); - _ -> true - end; - Error -> Error - end; - (_) -> true - end, - lists:usort(lists:flatten(Affiliations))); - {Error, _} -> ?DEBUG("on_user_offline ~p", [Error]) - end. diff --git a/src/mod_pubsub/mod_pubsub_odbc.erl b/src/mod_pubsub/mod_pubsub_odbc.erl deleted file mode 100644 index 51b411dc0..000000000 --- a/src/mod_pubsub/mod_pubsub_odbc.erl +++ /dev/null @@ -1,5071 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the core of the PubSub -%%% extension. It relies on PubSub plugins for a large part of its functions. -%%% -%%% @headerfile "pubsub.hrl" -%%% -%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for -%%% the latest version of the PubSub specification. -%%% This module uses version 1.12 of the specification as a base. -%%% Most of the specification is implemented. -%%% Functions concerning configuration should be rewritten. -%%% -%%% Support for subscription-options and multi-subscribe features was -%%% added by Brian Cully (bjc AT kublai.com). Subscriptions and options are -%%% stored in the pubsub_subscription table, with a link to them provided -%%% by the subscriptions field of pubsub_state. For information on -%%% subscription-options and mulit-subscribe see XEP-0060 sections 6.1.6, -%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see -%%% XEP-0060 section 12.18. - --module(mod_pubsub_odbc). - --author('christophe.romain@process-one.net'). - --version('1.13-0'). - --behaviour(gen_server). - --behaviour(gen_mod). - --include("ejabberd.hrl"). - --include("adhoc.hrl"). - --include("jlib.hrl"). - --include("pubsub.hrl"). - --define(STDTREE, <<"tree_odbc">>). - --define(STDNODE, <<"flat_odbc">>). - --define(PEPNODE, <<"pep_odbc">>). - -%% exports for hooks --export([presence_probe/3, caps_update/3, - in_subscription/6, out_subscription/4, - on_user_offline/3, remove_user/2, - disco_local_identity/5, disco_local_features/5, - disco_local_items/5, disco_sm_identity/5, - disco_sm_features/5, disco_sm_items/5]). - -%% exported iq handlers --export([iq_sm/3]). - -%% exports for console debug manual use --export([create_node/5, - delete_node/3, - subscribe_node/5, - unsubscribe_node/5, - publish_item/6, - delete_item/4, - send_items/6, - get_items/2, - get_item/3, - get_cached_item/2, - broadcast_stanza/9, - get_configure/5, - set_configure/5, - tree_action/3, - node_action/4 - ]). - -%% general helpers for plugins --export([subscription_to_string/1, affiliation_to_string/1, - string_to_subscription/1, string_to_affiliation/1, - extended_error/2, extended_error/3, - escape/1]). - -%% 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]). - -%% calls for parallel sending of last items --export([send_loop/1]). - --define(PROCNAME, ejabberd_mod_pubsub_odbc). - --define(LOOPNAME, ejabberd_mod_pubsub_loop). - -%%==================================================================== -%% API -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- --define(PLUGIN_PREFIX, <<"node_">>). - --define(TREE_PREFIX, <<"nodetree_">>). - -% --export_type([ - host/0, - hostPubsub/0, - hostPEP/0, - %% - nodeIdx/0, - nodeId/0, - itemId/0, - subId/0, - payload/0, - %% - nodeOption/0, - nodeOptions/0, - subOption/0, - subOptions/0, - %% - affiliation/0, - subscription/0, - accessModel/0, - publishModel/0 -]). - -%% -type payload() defined here because the -type xmlel() is not accessible -%% from pubsub.hrl --type(payload() :: [] | [xmlel(),...]). - --export_type([ - pubsubNode/0, - pubsubState/0, - pubsubItem/0, - pubsubSubscription/0, - pubsubLastItem/0 -]). - --type(pubsubNode() :: - #pubsub_node{ - nodeid :: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()}, - id :: mod_pubsub:nodeIdx(), - parents :: [Parent_NodeId::mod_pubsub:nodeId()], - type :: binary(), - owners :: [Owner::ljid(),...], - options :: mod_pubsub:nodeOptions() - } -). - --type(pubsubState() :: - #pubsub_state{ - stateid :: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()}, - items :: [ItemId::mod_pubsub:itemId()], - affiliation :: mod_pubsub:affiliation(), - subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}] - } -). - --type(pubsubItem() :: - #pubsub_item{ - itemid :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()}, - creation :: {erlang:timestamp(), ljid()}, - modification :: {erlang:timestamp(), ljid()}, - payload :: mod_pubsub:payload() - } -). - --type(pubsubSubscription() :: - #pubsub_subscription{ - subid :: mod_pubsub:subId(), - options :: [] | mod_pubsub:subOptions() - } -). - --type(pubsubLastItem() :: - #pubsub_last_item{ - nodeid :: mod_pubsub:nodeIdx(), - itemid :: mod_pubsub:itemId(), - creation :: {erlang:timestamp(), ljid()}, - payload :: mod_pubsub:payload() - } -). - --record(state, -{ - server_host, - host, - access, - pep_mapping = [], - ignore_pep_from_offline = true, - last_item_cache = false, - max_items_node = ?MAXITEMS, - nodetree = ?STDTREE, - plugins = [?STDNODE] -}). - --type(state() :: - #state{ - server_host :: binary(), - host :: mod_pubsub:hostPubsub(), - access :: atom(), - pep_mapping :: [{binary(), binary()}], - ignore_pep_from_offline :: boolean(), - last_item_cache :: boolean(), - max_items_node :: non_neg_integer(), - nodetree :: binary(), - plugins :: [binary(),...] - } - -). - - -start_link(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, - [Host, Opts], []). - -start(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, - transient, 1000, worker, [?MODULE]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:call(Proc, stop), - supervisor:delete_child(ejabberd_sup, Proc). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- --spec(init/1 :: -( - _:: _) - -> {ok, state()} -). - -init([ServerHost, Opts]) -> - ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), - Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), - Access = gen_mod:get_opt(access_createnode, Opts, - fun(A) when is_atom(A) -> A end, all), - PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, - fun(A) when is_boolean(A) -> A end, true), - IQDisc = gen_mod:get_opt(iqdisc, Opts, - fun(A) when is_atom(A) -> A end, one_queue), - LastItemCache = gen_mod:get_opt(last_item_cache, Opts, - fun(A) when is_boolean(A) -> A end, false), - MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, - fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS), - pubsub_index:init(Host, ServerHost, Opts), - ets:new(gen_mod:get_module_proc(Host, config), - [set, named_table]), - ets:new(gen_mod:get_module_proc(ServerHost, config), - [set, named_table]), - {Plugins, NodeTree, PepMapping} = init_plugins(Host, - ServerHost, Opts), - mnesia:create_table(pubsub_last_item, - [{ram_copies, [node()]}, - {attributes, record_info(fields, pubsub_last_item)}]), - mod_disco:register_feature(ServerHost, ?NS_PUBSUB), - ets:insert(gen_mod:get_module_proc(Host, config), - {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(Host, config), - {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(Host, config), - {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(Host, config), - {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {pep_mapping, PepMapping}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {ignore_pep_from_offline, PepOffline}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), - {host, Host}), - ejabberd_hooks:add(sm_remove_connection_hook, - ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:add(disco_local_identity, ServerHost, - ?MODULE, disco_local_identity, 75), - ejabberd_hooks:add(disco_local_features, ServerHost, - ?MODULE, disco_local_features, 75), - ejabberd_hooks:add(disco_local_items, ServerHost, - ?MODULE, disco_local_items, 75), - ejabberd_hooks:add(presence_probe_hook, ServerHost, - ?MODULE, presence_probe, 80), - ejabberd_hooks:add(roster_in_subscription, ServerHost, - ?MODULE, in_subscription, 50), - ejabberd_hooks:add(roster_out_subscription, ServerHost, - ?MODULE, out_subscription, 50), - ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, ServerHost, - ?MODULE, remove_user, 50), - case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, - caps_update, 80), - ejabberd_hooks:add(disco_sm_identity, ServerHost, - ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:add(disco_sm_features, ServerHost, - ?MODULE, disco_sm_features, 75), - ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, - disco_sm_items, 75), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, - ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, - ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, - IQDisc); - false -> ok - end, - ejabberd_router:register_route(Host), - put(server_host, ServerHost), - init_nodes(Host, ServerHost, NodeTree, Plugins), - State = #state{host = Host, server_host = ServerHost, - access = Access, pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, nodetree = NodeTree, - plugins = Plugins}, - init_send_loop(ServerHost, State), - {ok, State}. - -init_send_loop(ServerHost, State) -> - Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME), - SendLoop = spawn(?MODULE, send_loop, [State]), - register(Proc, SendLoop), - SendLoop. - -%% @spec (Host, ServerHost, Opts) -> Plugins -%% Host = mod_pubsub:host() Opts = [{Key,Value}] -%% ServerHost = host() -%% Key = atom() -%% Value = term() -%% Plugins = [Plugin::string()] -%% @doc Call the init/1 function for each plugin declared in the config file. -%% The default plugin module is implicit. -%% <p>The Erlang code for the plugin is located in a module called -%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p> -%% <p>The modules are initialized in alphetical order and the list is checked -%% and sorted to ensure that each module is initialized only once.</p> -%% <p>See {@link node_hometree:init/1} for an example implementation.</p> -init_plugins(Host, ServerHost, Opts) -> - TreePlugin = - jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, - (gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end, - ?STDTREE))/binary>>), - ?DEBUG("** tree plugin is ~p", [TreePlugin]), - TreePlugin:init(Host, ServerHost, Opts), - Plugins = gen_mod:get_opt(plugins, Opts, - fun(A) when is_list(A) -> A end, [?STDNODE]), - PepMapping = gen_mod:get_opt(pep_mapping, Opts, - fun(A) when is_list(A) -> A end, []), - ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]), - PluginsOK = lists:foldl(fun (Name, Acc) -> - Plugin = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Name/binary>>), - case catch apply(Plugin, init, - [Host, ServerHost, Opts]) - of - {'EXIT', _Error} -> Acc; - _ -> - ?DEBUG("** init ~s plugin", [Name]), - [Name | Acc] - end - end, - [], Plugins), - {lists:reverse(PluginsOK), TreePlugin, PepMapping}. - -terminate_plugins(Host, ServerHost, Plugins, - TreePlugin) -> - lists:foreach(fun (Name) -> - ?DEBUG("** terminate ~s plugin", [Name]), - Plugin = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Name/binary>>), - Plugin:terminate(Host, ServerHost) - end, - Plugins), - TreePlugin:terminate(Host, ServerHost), - ok. - -init_nodes(Host, ServerHost, _NodeTree, Plugins) -> - case lists:member(<<"hometree_odbc">>, Plugins) of - true -> - create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>), - create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), - <<"hometree_odbc">>); - false -> ok - end. - -send_loop(State) -> - receive - {presence, JID, Pid} -> - Host = State#state.host, - ServerHost = State#state.server_host, - LJID = jlib:jid_tolower(JID), - BJID = jlib:jid_remove_resource(LJID), - lists:foreach(fun (PType) -> - {result, Subscriptions} = case catch node_action(Host, - PType, - get_entity_subscriptions_for_send_last, - [Host, JID]) of - {result, S} -> S; - _ -> [] - end, - lists:foreach(fun ({Node, subscribed, _, - SubJID}) -> - if (SubJID == LJID) or - (SubJID == BJID) -> - #pubsub_node{nodeid - = - {H, - N}, - type = - Type, - id = - NodeId} = - Node, - send_items(H, - N, - NodeId, - Type, - LJID, - last); - true -> - % resource not concerned about that subscription - ok - end; - (_) -> ok - end, - Subscriptions) - end, - State#state.plugins), - if not State#state.ignore_pep_from_offline -> - {User, Server, Resource} = jlib:jid_tolower(JID), - case catch ejabberd_c2s:get_subscribed(Pid) of - Contacts when is_list(Contacts) -> - lists:foreach(fun ({U, S, R}) -> - case S of - ServerHost -> %% local contacts - case user_resources(U, S) of - [] -> %% offline - PeerJID = - jlib:make_jid(U, S, - R), - self() ! - {presence, User, - Server, [Resource], - PeerJID}; - _ -> %% online - % this is already handled by presence probe - ok - end; - _ -> %% remote contacts - % we can not do anything in any cases - ok - end - end, - Contacts); - _ -> ok - end; - true -> ok - end, - send_loop(State); - {presence, User, Server, Resources, JID} -> - spawn(fun () -> - Host = State#state.host, - Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), - lists:foreach(fun (#pubsub_node{nodeid = {_, Node}, - type = Type, - id = NodeId, - options = Options}) -> - case get_option(Options, - send_last_published_item) - of - on_sub_and_presence -> - lists:foreach(fun - (Resource) -> - LJID = - {User, - Server, - Resource}, - Subscribed = - case - get_option(Options, - access_model) - of - open -> - true; - presence -> - true; - whitelist -> - false; % subscribers are added manually - authorize -> - false; % likewise - roster -> - Grps = - get_option(Options, - roster_groups_allowed, - []), - {OU, - OS, - _} = - Owner, - element(2, - get_roster_info(OU, - OS, - LJID, - Grps)) - end, - if - Subscribed -> - send_items(Owner, - Node, - NodeId, - Type, - LJID, - last); - true -> - ok - end - end, - Resources); - _ -> ok - end - end, - tree_action(Host, get_nodes, - [Owner, JID])) - end), - send_loop(State); - stop -> ok - end. - -%% ------- -%% disco hooks handling functions -%% - --spec(disco_local_identity/5 :: -( - Acc :: [xmlel()], - _From :: jid(), - To :: jid(), - NodeId :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) - -> [xmlel()] -). -disco_local_identity(Acc, _From, To, <<>>, _Lang) -> - case lists:member(?PEPNODE, plugins(To#jid.lserver)) of - true -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []} - | Acc]; - false -> Acc - end; -disco_local_identity(Acc, _From, _To, _Node, _Lang) -> - Acc. - --spec(disco_local_features/5 :: -( - Acc :: [xmlel()], - _From :: jid(), - To :: jid(), - NodeId :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) - -> [binary(),...] -). -disco_local_features(Acc, _From, To, <<>>, _Lang) -> - Host = To#jid.lserver, - Feats = case Acc of - {result, I} -> I; - _ -> [] - end, - {result, - Feats ++ - lists:map(fun (Feature) -> - <<(?NS_PUBSUB)/binary, "#", Feature/binary>> - end, - features(Host, <<>>))}; -disco_local_features(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc; -disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc. - -%disco_sm_identity(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_identity(Acc, From, To, iolist_to_binary(Node), -% Lang); --spec(disco_sm_identity/5 :: -( - Acc :: empty | [xmlel()], - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> [xmlel()] -). -disco_sm_identity(empty, From, To, Node, Lang) -> - disco_sm_identity([], From, To, Node, Lang); -disco_sm_identity(Acc, From, To, Node, _Lang) -> - disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) - ++ Acc. - -disco_identity(_Host, <<>>, _From) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []}]; -disco_identity(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options}) -> - Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}], - children = []}, - #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"leaf">>} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end], - children = []}]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - --spec(disco_sm_features/5 :: -( - Acc :: empty | {result, Features::[Feature::binary()]}, - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> {result, Features::[Feature::binary()]} -). -%disco_sm_features(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_features(Acc, From, To, iolist_to_binary(Node), -% Lang); -disco_sm_features(empty, From, To, Node, Lang) -> - disco_sm_features({result, []}, From, To, Node, Lang); -disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) -> - {result, - OtherFeatures ++ - disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)}; -disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. - -disco_features(_Host, <<>>, _From) -> - [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", Feature/binary>> - || Feature <- features(<<"pep">>)]]; -disco_features(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options}) -> - Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, - [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", - Feature/binary>> - || Feature <- features(<<"pep">>)]]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - --spec(disco_sm_items/5 :: -( - Acc :: empty | {result, [xmlel()]}, - From :: jid(), - To :: jid(), - Node :: mod_pubsub:nodeId(), - Lang :: binary()) - -> {result, [xmlel()]} -). -%disco_sm_items(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_items(Acc, From, To, iolist_to_binary(Node), -% Lang); -disco_sm_items(empty, From, To, Node, Lang) -> - disco_sm_items({result, []}, From, To, Node, Lang); -disco_sm_items({result, OtherItems}, From, To, Node, _Lang) -> - {result, - lists:usort(OtherItems ++ - disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))}; -disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. - --spec(disco_items/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid()) - -> [xmlel()] -). -disco_items(Host, <<>>, From) -> - Action = fun (#pubsub_node{nodeid = {_, NodeID}, - options = Options, type = Type, id = Idx}, - Acc) -> - Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - [#xmlel{name = <<"item">>, - attrs = - [{<<"node">>, (NodeID)}, - {<<"jid">>, - case Host of - {_, _, _} -> - jlib:jid_to_string(Host); - _Host -> Host - end} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end], - children = []} - | Acc]; - _ -> Acc - end - end, - case transaction_on_nodes(Host, Action, sync_dirty) of - {result, Items} -> Items; - _ -> [] - end; -disco_items(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options}) -> - Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, - Options, Owners) - of - {result, Items} -> - {result, - [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - case Host of - {_, _, _} -> - jlib:jid_to_string(Host); - _Host -> Host - end}, - {<<"name">>, ItemID}], - children = []} - || #pubsub_item{itemid = {ItemID, _}} <- Items]}; - _ -> {result, []} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] - end. - -%% ------- -%% presence hooks handling functions -%% - -caps_update(#jid{luser = U, lserver = S, lresource = R} = From, To, _Features) -> - Pid = ejabberd_sm:get_session_pid(U, S, R), - presence_probe(From, To, Pid). - -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, - JID, Pid) -> - presence(Server, {presence, JID, Pid}), - presence(Server, {presence, User, Server, [Resource], JID}); -presence_probe(#jid{luser = User, lserver = Server}, - #jid{luser = User, lserver = Server}, _Pid) -> - %% ignore presence_probe from other ressources for the current user - %% this way, we do not send duplicated last items if user already connected with other clients - ok; -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, - #jid{lserver = Host} = JID, _Pid) -> - presence(Host, {presence, User, Server, [Resource], JID}). - -presence(ServerHost, Presence) -> - SendLoop = case - whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) - of - undefined -> - Host = host(ServerHost), - Plugins = plugins(Host), - PepOffline = case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, - config), - ignore_pep_from_offline) - of - [{ignore_pep_from_offline, PO}] -> PO; - _ -> true - end, - State = #state{host = Host, server_host = ServerHost, - ignore_pep_from_offline = PepOffline, - plugins = Plugins}, - init_send_loop(ServerHost, State); - Pid -> Pid - end, - SendLoop ! Presence. - -%% ------- -%% subscription hooks handling functions -%% - -out_subscription(User, Server, JID, subscribed) -> - Owner = jlib:make_jid(User, Server, <<"">>), - {PUser, PServer, PResource} = jlib:jid_tolower(JID), - PResources = case PResource of - <<>> -> user_resources(PUser, PServer); - _ -> [PResource] - end, - presence(Server, - {presence, PUser, PServer, PResources, Owner}), - true; -out_subscription(_, _, _, _) -> true. - -in_subscription(_, User, Server, Owner, unsubscribed, - _) -> - unsubscribe_user(jlib:make_jid(User, Server, <<"">>), - Owner), - true; -in_subscription(_, _, _, _, _, _) -> true. - -unsubscribe_user(Entity, Owner) -> - BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - Host = host(element(2, BJID)), - spawn(fun () -> - lists:foreach(fun (PType) -> - {result, Subscriptions} = - node_action(Host, PType, - get_entity_subscriptions, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{options - = - Options, - id = - NodeId}, - subscribed, _, - JID}) -> - case - get_option(Options, - access_model) - of - presence -> - case - lists:member(BJID, - node_owners(Host, PType, NodeId)) - of - true -> - node_action(Host, - PType, - unsubscribe_node, - [NodeId, - Entity, - JID, - all]); - false -> - {result, - ok} - end; - _ -> - {result, ok} - end; - (_) -> ok - end, - Subscriptions) - end, - plugins(Host)) - end). - -%% ------- -%% user remove hook handling function -%% - -remove_user(User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - Entity = jlib:make_jid(LUser, LServer, <<"">>), - Host = host(LServer), -%%-------------------------------------------------------------------- -%% 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 - HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>, - spawn(fun () -> - lists:foreach(fun (PType) -> - {result, Subscriptions} = - node_action(Host, PType, - get_entity_subscriptions, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{id = - NodeId}, - _, _, JID}) -> - node_action(Host, - PType, - unsubscribe_node, - [NodeId, - Entity, - JID, - all]) - end, - Subscriptions), - {result, Affiliations} = - node_action(Host, PType, - get_entity_affiliations, - [Host, Entity]), - lists:foreach(fun ({#pubsub_node{nodeid - = - {H, - N}, - parents - = - []}, - owner}) -> - delete_node(H, N, - Entity); - ({#pubsub_node{nodeid - = - {H, - N}, - type = - <<"hometree">>}, - owner}) - when N == - HomeTreeBase -> - delete_node(H, N, - Entity); - ({#pubsub_node{id = - NodeId}, - publisher}) -> - node_action(Host, - PType, - set_affiliation, - [NodeId, - Entity, - none]); - (_) -> ok - end, - Affiliations) - end, - plugins(Host)) - end). - -handle_call(server_host, _From, State) -> - {reply, State#state.server_host, State}; -handle_call(plugins, _From, State) -> - {reply, State#state.plugins, State}; -handle_call(pep_mapping, _From, State) -> - {reply, State#state.pep_mapping, State}; -handle_call(nodetree, _From, State) -> - {reply, State#state.nodetree, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -%% @private -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -%% @private -handle_cast(_Msg, State) -> {noreply, State}. - --spec(handle_info/2 :: -( - _ :: {route, From::jid(), To::jid(), Packet::xmlel()}, - State :: state()) - -> {noreply, state()} -). - -handle_info({route, From, To, Packet}, - #state{server_host = ServerHost, access = Access, - plugins = Plugins} = - State) -> - case catch do_route(ServerHost, Access, Plugins, - To#jid.lserver, From, To, Packet) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok - end, - {noreply, State}; -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -%% @private -handle_info(_Info, State) -> {noreply, State}. - -terminate(_Reason, - #state{host = Host, server_host = ServerHost, - nodetree = TreePlugin, plugins = Plugins}) -> - ejabberd_router:unregister_route(Host), - case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, - caps_update, 80), - ejabberd_hooks:delete(disco_sm_identity, ServerHost, - ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:delete(disco_sm_features, ServerHost, - ?MODULE, disco_sm_features, 75), - ejabberd_hooks:delete(disco_sm_items, ServerHost, - ?MODULE, disco_sm_items, 75), - gen_iq_handler:remove_iq_handler(ejabberd_sm, - ServerHost, ?NS_PUBSUB), - gen_iq_handler:remove_iq_handler(ejabberd_sm, - ServerHost, ?NS_PUBSUB_OWNER); - false -> ok - end, - ejabberd_hooks:delete(sm_remove_connection_hook, - ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:delete(disco_local_identity, ServerHost, - ?MODULE, disco_local_identity, 75), - ejabberd_hooks:delete(disco_local_features, ServerHost, - ?MODULE, disco_local_features, 75), - ejabberd_hooks:delete(disco_local_items, ServerHost, - ?MODULE, disco_local_items, 75), - ejabberd_hooks:delete(presence_probe_hook, ServerHost, - ?MODULE, presence_probe, 80), - ejabberd_hooks:delete(roster_in_subscription, - ServerHost, ?MODULE, in_subscription, 50), - ejabberd_hooks:delete(roster_out_subscription, - ServerHost, ?MODULE, out_subscription, 50), - ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, - ?MODULE, remove_user, 50), - mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB), - gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! stop, -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -%% @private - terminate_plugins(Host, ServerHost, Plugins, - TreePlugin). - -code_change(_OldVsn, State, _Extra) -> {ok, State}. - --spec(do_route/7 :: -( - ServerHost :: binary(), - Access :: atom(), - Plugins :: [binary(),...], - Host :: mod_pubsub:hostPubsub(), - From :: jid(), - To :: jid(), - Packet :: xmlel()) - -> ok -). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> - #xmlel{name = Name, attrs = Attrs} = Packet, - case To of - #jid{luser = <<"">>, lresource = <<"">>} -> - case Name of - <<"iq">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, - lang = Lang} = - IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = xml:get_attr_s(<<"node">>, QAttrs), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - Res = case iq_disco_info(Host, Node, From, Lang) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = - <<"query">>, - attrs = - QAttrs, - children = - IQRes ++ - Info}]}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = ?NS_DISCO_ITEMS, - sub_el = SubEl} = - IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = xml:get_attr_s(<<"node">>, QAttrs), - Rsm = jlib:rsm_decode(IQ), - Res = case iq_disco_items(Host, Node, From, Rsm) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = - <<"query">>, - attrs = - QAttrs, - children = - IQRes}]}) -% {error, Error} -> -% jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, - sub_el = SubEl} = - IQ -> - Res = case iq_pubsub(Host, ServerHost, From, IQType, - SubEl, Lang, Access, Plugins) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, - lang = Lang, sub_el = SubEl} = - IQ -> - Res = case iq_pubsub_owner(Host, ServerHost, From, - IQType, SubEl, Lang) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang, sub_el = _SubEl} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> - Res = case iq_command(Host, ServerHost, From, IQ, - Access, Plugins) - of - {error, Error} -> - jlib:make_error_reply(Packet, Error); - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}) - end, - ejabberd_router:route(To, From, Res); - #iq{} -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - <<"message">> -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - case find_authorization_response(Packet) of - none -> ok; - invalid -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_BAD_REQUEST)); - XFields -> - handle_authorization_response(Host, From, To, - Packet, XFields) - end - end; - _ -> ok - end; - _ -> - case xml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end - end. - -command_disco_info(_Host, ?NS_COMMANDS, _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}], - children = []}, - {result, [IdentityEl]}; -command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, - _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}], - children = []}, - FeaturesEl = #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - {result, [IdentityEl, FeaturesEl]}. - -node_disco_info(Host, Node, From) -> - node_disco_info(Host, Node, From, true, true). - -node_disco_info(Host, Node, From, Identity, Features) -> -% Action = -% fun(#pubsub_node{type = Type, id = NodeId}) -> -% I = case Identity of -% false -> -% []; -% true -> -% Types = -% case tree_call(Host, get_subnodes, [Host, Node, From]) of -% [] -> -% [<<"leaf">>]; %% No sub-nodes: it's a leaf node -% _ -> -% case node_call(Type, get_items, [NodeId, From, none]) of -% {result, []} -> [<<"collection">>]; -% {result, _} -> [<<"leaf">>, <<"collection">>]; -% _ -> [] -% end -% end, -% lists:map(fun(T) -> -% #xmlel{name = <<"identity">>, -% attrs = -% [{<<"category">>, -% <<"pubsub">>}, -% {<<"type">>, T}], -% children = []} -% end, Types) -% end, -% F = case Features of -% false -> -% []; -% true -> -% [#xmlel{name = <<"feature">>, -% attrs = [{<<"var">>, ?NS_PUBSUB}], -% children = []} -% | lists:map(fun -% (<<"rsm">>)-> -% #xmlel{name = <<"feature">>, -% attrs = [{<<"var">>, ?NS_RSM}]}; -% (T) -> -% #xmlel{name = <<"feature">>, -% attrs = -% [{<<"var">>, -% <<(?NS_PUBSUB)/binary, -% "#", -% T/binary>>}], -% children = []} -% end, -% features(Type))] -% end, -% %% TODO: add meta-data info (spec section 5.4) -% {result, I ++ F} -% end, -% case transaction(Host, Node, Action, sync_dirty) of -% {result, {_, Result}} -> {result, Result}; -% Other -> Other -% end. - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - I = Types = case tree_call(Host, get_subnodes, - [Host, Node, From]) - of - [] -> [<<"leaf">>]; - _ -> - case node_call(Type, get_items, - [NodeId, From, none]) - of - {result, []} -> - [<<"collection">>]; - {result, _} -> - [<<"leaf">>, - <<"collection">>]; - _ -> [] - end - end, - lists:map(fun (T) -> - #xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, - <<"pubsub">>}, - {<<"type">>, T}], - children = []} - end, - Types), - F = [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}], - children = []} - | lists:map(fun - (<<"rsm">>)-> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_RSM}]}; - (T) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, - <<(?NS_PUBSUB)/binary, - "#", - T/binary>>}], - children = []} - end, - features(Type))], - {result, I ++ F} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end. - -iq_disco_info(Host, SNode, From, Lang) -> - [Node | _] = case SNode of - <<>> -> [<<>>]; - _ -> str:tokens(SNode, <<"!">>) - end, - % Node = string_to_node(RealSNode), - case Node of - <<>> -> - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"service">>}, - {<<"name">>, - translate:translate(Lang, <<"Publish-Subscribe">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}] - ++ - lists:map(fun - (<<"rsm">>)-> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_RSM}]}; - (Feature) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}], - children = []} - end, - features(Host, Node))}; - ?NS_COMMANDS -> command_disco_info(Host, Node, From); - ?NS_PUBSUB_GET_PENDING -> - command_disco_info(Host, Node, From); - _ -> node_disco_info(Host, Node, From) - end. - --spec(iq_disco_items/4 :: -( - Host :: mod_pubsub:host(), - NodeId :: <<>> | mod_pubsub:nodeId(), - From :: jid(), - Rsm :: any()) - -> {result, [xmlel()]} -). -iq_disco_items(Host, <<>>, From, _RSM) -> - {result, - lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, - options = Options}) -> - Attrs = case get_option(Options, title) of - false -> - [{<<"jid">>, Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, Host}, - {<<"name">>, Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, attrs = Attrs, - children = []} - end, - tree_action(Host, get_subnodes, [Host, <<>>, From]))}; -% case tree_action(Host, get_subnodes, [Host, <<>>, From]) of -% Nodes when is_list(Nodes) -> -% {result, -% lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, -% options = Options}) -> -% Attrs = case get_option(Options, title) of -% false -> -% [{<<"jid">>, Host} -% | nodeAttr(SubNode)]; -% Title -> -% [{<<"jid">>, Host}, -% {<<"name">>, Title} -% | nodeAttr(SubNode)] -% end, -% #xmlel{name = <<"item">>, attrs = Attrs, -% children = []} -% end, -% Nodes)}; -% Other -> Other -% end; -iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> - CommandItems = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, - {<<"node">>, ?NS_PUBSUB_GET_PENDING}, - {<<"name">>, <<"Get Pending">>}], - children = []}], - {result, CommandItems}; -iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> - CommandItems = [], {result, CommandItems}; -iq_disco_items(Host, Item, From, RSM) -> - case str:tokens(Item, <<"!">>) of - [_Node, _ItemID] -> {result, []}; - [Node] -> -% Node = string_to_node(SNode), - Action = fun (#pubsub_node{id = Idx, type = Type, - options = Options}) -> - Owners = node_owners_call(Type, Idx), - {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of - {result, R} -> R; - _ -> {[], none} - end, - Nodes = lists:map(fun (#pubsub_node{nodeid = - {_, SubNode}, - options = - SubOptions}) -> - Attrs = case - get_option(SubOptions, - title) - of - false -> - [{<<"jid">>, - Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, - Host}, - {<<"name">>, - Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, - attrs = Attrs, - children = []} - end, - tree_call(Host, get_subnodes, - [Host, Node, From])), - Items = lists:map(fun (#pubsub_item{itemid = - {RN, _}}) -> - {result, Name} = - node_call(Type, - get_item_name, - [Host, Node, - RN]), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - Host}, - {<<"name">>, - Name}], - children = []} - end, - NodeItems), - {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - --spec(iq_sm/3 :: -( - From :: jid(), - To :: jid(), - IQ :: iq_request()) - -> iq_result() | iq_error() -). -iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) -> - ServerHost = To#jid.lserver, - LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)), - Res = case XMLNS of - ?NS_PUBSUB -> - iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); - ?NS_PUBSUB_OWNER -> - iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, - Lang) - end, - case Res of - {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes}; - {error, Error} -> - IQ#iq{type = error, sub_el = [Error, SubEl]} - end. - -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd Publish-Subscribe module">>))/binary, - "\nCopyright (c) 2004-2013 ProcessOne">>}]}]. - --spec(iq_pubsub/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary()) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> - iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). - --spec(iq_pubsub/8 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary(), - Access :: atom(), - Plugins :: [binary(),...]) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> - #xmlel{children = SubEls} = SubEl, - case xml:remove_cdata(SubEls) of - [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] -> - Node = xml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {set, <<"create">>} -> - Config = case Rest of - [#xmlel{name = <<"configure">>, children = C}] -> C; - _ -> [] - end, - Type = case xml:get_attr_s(<<"type">>, Attrs) of - <<>> -> hd(Plugins); - T -> T - end, - case lists:member(Type, Plugins) of - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"create-nodes">>)}; - true -> - create_node(Host, ServerHost, Node, From, Type, Access, Config) - end; - {set, <<"publish">>} -> - case xml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs, - children = Payload}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), - publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access); - [] -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)} - end; - {set, <<"retract">>} -> - ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) - of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - case xml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> - ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), - delete_item(Host, Node, From, ItemId, ForceNotify); - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)} - end; - {set, <<"subscribe">>} -> - Config = case Rest of - [#xmlel{name = <<"options">>, children = C}] -> C; - _ -> [] - end, - JID = xml:get_attr_s(<<"jid">>, Attrs), - subscribe_node(Host, Node, From, JID, Config); - {set, <<"unsubscribe">>} -> - JID = xml:get_attr_s(<<"jid">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), - unsubscribe_node(Host, Node, From, JID, SubId); - {get, <<"items">>} -> - MaxItems = xml:get_attr_s(<<"max_items">>, Attrs), - SubId = xml:get_attr_s(<<"subid">>, Attrs), - ItemIDs = lists:foldl(fun (#xmlel{name = <<"item">>, - attrs = ItemAttrs}, - Acc) -> - case xml:get_attr_s(<<"id">>, - ItemAttrs) - of - <<"">> -> Acc; - ItemID -> [ItemID | Acc] - end; - (_, Acc) -> Acc - end, - [], xml:remove_cdata(Els)), - RSM = jlib:rsm_decode(SubEl), - get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From, Plugins); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From, Plugins); - {get, <<"options">>} -> - SubID = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), - get_options(Host, Node, JID, SubID, Lang); - {set, <<"options">>} -> - SubID = xml:get_attr_s(<<"subid">>, Attrs), - JID = xml:get_attr_s(<<"jid">>, Attrs), - set_options(Host, Node, JID, SubID, Els); - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - Other -> - ?INFO_MSG("Too many actions: ~p", [Other]), - {error, ?ERR_BAD_REQUEST} - end. - - --spec(iq_pubsub_owner/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - From :: jid(), - IQType :: 'get' | 'set', - SubEl :: xmlel(), - Lang :: binary()) - -> {result, [xmlel()]} - %%% - | {error, xmlel()} -). -iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - #xmlel{children = SubEls} = SubEl, - Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false; - (_) -> true - end, xml:remove_cdata(SubEls)), - case Action of - [#xmlel{name = Name, attrs = Attrs, children = Els}] -> - Node = xml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {get, <<"configure">>} -> - get_configure(Host, ServerHost, Node, From, Lang); - {set, <<"configure">>} -> - set_configure(Host, Node, From, Els, Lang); - {get, <<"default">>} -> - get_default(Host, Node, From, Lang); - {set, <<"delete">>} -> delete_node(Host, Node, From); - {set, <<"purge">>} -> purge_node(Host, Node, From); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From); - {set, <<"subscriptions">>} -> - set_subscriptions(Host, Node, From, - xml:remove_cdata(Els)); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From); - {set, <<"affiliations">>} -> - set_affiliations(Host, Node, From, xml:remove_cdata(Els)); - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ?INFO_MSG("Too many actions: ~p", [Action]), - {error, ?ERR_BAD_REQUEST} - end. - -iq_command(Host, ServerHost, From, IQ, Access, Plugins) -> - case adhoc:parse_request(IQ) of - Req when is_record(Req, adhoc_request) -> - case adhoc_request(Host, ServerHost, From, Req, Access, - Plugins) - of - Resp when is_record(Resp, adhoc_response) -> - {result, [adhoc:produce_response(Req, Resp)]}; - Error -> Error - end; - Err -> Err - end. - -%% @doc <p>Processes an Ad Hoc Command.</p> -adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - lang = Lang, action = <<"execute">>, - xdata = false}, - _Access, Plugins) -> - send_pending_node_form(Host, Owner, Lang, Plugins); -adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - action = <<"execute">>, xdata = XData}, - _Access, _Plugins) -> - ParseOptions = case XData of - #xmlel{name = <<"x">>} = XEl -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData2 -> - case set_xoption(Host, XData2, []) of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> Err - end - end; - _ -> - ?INFO_MSG("Bad XForm: ~p", [XData]), - {error, ?ERR_BAD_REQUEST} - end, - case ParseOptions of - {result, XForm} -> - case lists:keysearch(node, 1, XForm) of - {value, {_, Node}} -> - send_pending_auth_events(Host, Node, Owner); - false -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)} - end; - Error -> Error - end; -adhoc_request(_Host, _ServerHost, _Owner, - #adhoc_request{action = <<"cancel">>}, _Access, - _Plugins) -> - #adhoc_response{status = canceled}; -adhoc_request(Host, ServerHost, Owner, - #adhoc_request{action = <<>>} = R, Access, Plugins) -> - adhoc_request(Host, ServerHost, Owner, - R#adhoc_request{action = <<"execute">>}, Access, - Plugins); -adhoc_request(_Host, _ServerHost, _Owner, Other, - _Access, _Plugins) -> - ?DEBUG("Couldn't process ad hoc command:~n~p", [Other]), - {error, ?ERR_ITEM_NOT_FOUND}. - -%% @spec (Host, Owner, Lang, Plugins) -> iqRes() -%% @doc <p>Sends the process pending subscriptions XForm for Host to -%% Owner.</p> -send_pending_node_form(Host, Owner, _Lang, Plugins) -> - Filter = fun (Plugin) -> - lists:member(<<"get-pending">>, features(Plugin)) - end, - case lists:filter(Filter, Plugins) of - [] -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; - Ps -> - XOpts = lists:map(fun (Node) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Node}]}]} - end, - get_pending_nodes(Host, Owner, Ps)), - XForm = #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"var">>, <<"pubsub#node">>}], - children = lists:usort(XOpts)}]}, - #adhoc_response{status = executing, - defaultaction = <<"execute">>, elements = [XForm]} - end. - -get_pending_nodes(Host, Owner, Plugins) -> - Tr = fun (Type) -> - case node_call(Type, get_pending_nodes, [Host, Owner]) - of - {result, Nodes} -> Nodes; - _ -> [] - end - end, - case transaction(Host, - fun () -> - {result, lists:flatmap(Tr, Plugins)} - end, - sync_dirty) - of - {result, Res} -> Res; - Err -> Err - end. - -%% @spec (Host, Node, Owner) -> iqRes() -%% @doc <p>Send a subscription approval form to Owner for all pending -%% subscriptions on Host and Node.</p> -send_pending_auth_events(Host, Node, Owner) -> - ?DEBUG("Sending pending auth events for ~s on " - "~s:~s", - [jlib:jid_to_string(Owner), Host, Node]), - Action = fun (#pubsub_node{id = NodeID, type = Type}) -> - case lists:member(<<"get-pending">>, features(Type)) of - true -> - case node_call(Type, get_affiliation, - [NodeID, Owner]) - of - {result, owner} -> - node_call(Type, get_node_subscriptions, - [NodeID]); - _ -> {error, ?ERR_FORBIDDEN} - end; - false -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {N, Subscriptions}} -> - lists:foreach(fun ({J, pending, _SubID}) -> - send_authorization_request(N, jlib:make_jid(J)); - ({J, pending}) -> - send_authorization_request(N, jlib:make_jid(J)); - (_) -> ok - end, - Subscriptions), - #adhoc_response{}; - Err -> Err - end. - -%%% authorization handling - -send_authorization_request(#pubsub_node{nodeid = {Host, Node}, - type = Type, id = NodeId}, - Subscriber) -> - Lang = <<"en">>, - Stanza = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"PubSub subscriber request">>)}]}, - #xmlel{name = <<"instructions">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Choose whether to approve this entity's " - "subscription.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - ?NS_PUBSUB_SUB_AUTH}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"pubsub#node">>}, - {<<"type">>, - <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Node ID">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Node}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#subscriber_jid">>}, - {<<"type">>, <<"jid-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Subscriber Address">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - jlib:jid_to_string(Subscriber)}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#allow">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow this Jabber ID to subscribe to " - "this pubsub node?">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - <<"false">>}]}]}]}]}, - lists:foreach(fun (Owner) -> - ejabberd_router:route(service_jid(Host), - jlib:make_jid(Owner), Stanza) - end, - node_owners(Host, Type, NodeId)). - -find_authorization_response(Packet) -> - #xmlel{children = Els} = Packet, - XData1 = lists:map(fun (#xmlel{name = <<"x">>, - attrs = XAttrs} = - XEl) -> - case xml:get_attr_s(<<"xmlns">>, XAttrs) of - ?NS_XDATA -> - case xml:get_attr_s(<<"type">>, XAttrs) of - <<"cancel">> -> none; - _ -> jlib:parse_xdata_submit(XEl) - end; - _ -> none - end; - (_) -> none - end, - xml:remove_cdata(Els)), - XData = lists:filter(fun (E) -> E /= none end, XData1), - case XData of - [invalid] -> invalid; - [] -> none; - [XFields] when is_list(XFields) -> - ?DEBUG("XFields: ~p", [XFields]), - case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of - {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields; - _ -> invalid - end - end. -%% @spec (Host, JID, Node, Subscription) -> void -%% Host = mod_pubsub:host() -%% JID = jlib:jid() -%% SNode = string() -%% Subscription = atom() | {atom(), mod_pubsub:subid()} -%% @doc Send a message to JID with the supplied Subscription -%% TODO : ask Christophe's opinion -send_authorization_approval(Host, JID, SNode, Subscription) -> - SubAttrs = case Subscription of -% {S, SID} -> -% [{<<"subscription">>, subscription_to_string(S)}, -% {<<"subid">>, SID}]; - S -> [{<<"subscription">>, subscription_to_string(S)}] - end, - Stanza = event_stanza([#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jlib:jid_to_string(JID)} - | nodeAttr(SNode)] - ++ SubAttrs, - children = []}]), - ejabberd_router:route(service_jid(Host), JID, Stanza). - -handle_authorization_response(Host, From, To, Packet, XFields) -> - case {lists:keysearch(<<"pubsub#node">>, 1, XFields), - lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields), - lists:keysearch(<<"pubsub#allow">>, 1, XFields)} - of - {{value, {_, [Node]}}, {value, {_, [SSubscriber]}}, - {value, {_, [SAllow]}}} -> -% Node = string_to_node(SNode), - Subscriber = jlib:string_to_jid(SSubscriber), - Allow = case SAllow of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - Action = fun (#pubsub_node{type = Type, - id = NodeId}) -> - IsApprover = - lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), - node_owners_call(Type, NodeId)), - {result, Subscriptions} = node_call(Type, - get_subscriptions, - [NodeId, - Subscriber]), - if not IsApprover -> {error, ?ERR_FORBIDDEN}; - true -> - update_auth(Host, Node, Type, NodeId, - Subscriber, Allow, Subscriptions) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {error, Error} -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, Error)); - {result, {_, _NewSubscription}} -> - %% XXX: notify about subscription state change, section 12.11 - ok; - _ -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_INTERNAL_SERVER_ERROR)) - end; - _ -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, - ?ERR_NOT_ACCEPTABLE)) - end. - -update_auth(Host, Node, Type, NodeId, Subscriber, Allow, - Subscriptions) -> - Subscription = lists:filter(fun ({pending, _}) -> true; - (_) -> false - end, - Subscriptions), - case Subscription of - [{pending, SubID}] -> - NewSubscription = case Allow of - true -> subscribed; - false -> none - end, - node_call(Type, set_subscriptions, - [NodeId, Subscriber, NewSubscription, SubID]), - send_authorization_approval(Host, Subscriber, Node, - NewSubscription), - {result, ok}; - _ -> {error, ?ERR_UNEXPECTED_REQUEST} - end. - --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, - case Val of - true -> <<"1">>; - _ -> <<"0">> - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). - --define(STRINGMXFIELD(Label, Var, Vals), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, V}]} - || V <- Vals]}). - --define(XFIELDOPT(Type, Label, Var, Val, Opts), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - lists:map(fun (Opt) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Opt}]}]} - end, - Opts) - ++ - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(LISTXFIELD(Label, Var, Val, Opts), - ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)). - --define(LISTMXFIELD(Label, Var, Vals, Opts), -%% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% @doc <p>Create new pubsub nodes</p> -%%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p> -%%<ul> -%%<li>The service does not support node creation.</li> -%%<li>Only entities that are registered with the service are allowed to create nodes but the requesting entity is not registered.</li> -%%<li>The requesting entity does not have sufficient privileges to create nodes.</li> -%%<li>The requested NodeID already exists.</li> -%%<li>The request did not include a NodeID and "instant nodes" are not supported.</li> -%%</ul> -%%<p>ote: node creation is a particular case, error return code is evaluated at many places:</p> -%%<ul> -%%<li>iq_pubsub checks if service supports node creation (type exists)</li> -%%<li>create_node checks if instant nodes are supported</li> -%%<li>create_node asks node plugin if entity have sufficient privilege</li> -%%<li>nodetree create_node checks if nodeid already exists</li> -%%<li>node plugin create_node just sets default affiliation/subscription</li> -%%</ul> - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - lists:map(fun (Opt) -> - #xmlel{name = <<"option">>, attrs = [], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Opt}]}]} - end, - Opts) - ++ - lists:map(fun (Val) -> - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]} - end, - Vals)}). - --spec(create_node/5 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), - Owner :: jid(), - Type :: binary()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). - -create_node(Host, ServerHost, Node, Owner, Type) -> - create_node(Host, ServerHost, Node, Owner, Type, all, []). - --spec(create_node/7 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), - Owner :: jid(), - Type :: binary(), - Access :: atom(), - Configuration :: [xmlel()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> - case lists:member(<<"instant-nodes">>, features(Type)) of - true -> - NewNode = randoms:get_string(), - case create_node(Host, ServerHost, NewNode, Owner, Type, - Access, Configuration) - of - {result, _} -> - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"create">>, - attrs = nodeAttr(NewNode), - children = []}]}]}; - Error -> Error - end; - false -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"nodeid-required">>)} - end; -create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> - Type = select_type(ServerHost, Host, Node, GivenType), - ParseOptions = case xml:remove_cdata(Configuration) of - [] -> {result, node_options(Type)}; - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData -> - case set_xoption(Host, XData, node_options(Type)) - of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> Err - end - end; - _ -> - ?INFO_MSG("Node ~p; bad configuration: ~p", - [Node, Configuration]), - {error, ?ERR_BAD_REQUEST} - end, - case ParseOptions of - {result, NodeOptions} -> - CreateNode = - fun() -> - Parent = case node_call(Type, node_to_path, [Node]) of - {result, [Node]} -> <<>>; - {result, Path} -> element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)])) - end, - Parents = case Parent of - <<>> -> []; - _ -> [Parent] - end, - case node_call(Type, create_node_permission, [Host, ServerHost, Node, Parent, Owner, Access]) of - {result, true} -> - case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of - {ok, NodeId} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], - case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, SubsByDepth, Result}}; - Error -> Error - end; - {error, {virtual, NodeId}} -> - case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, [], Result}}; - Error -> Error - end; - Error -> - Error - end; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"create">>, - attrs = nodeAttr(Node), - children = []}]}], - case transaction(Host, CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {NodeId, _SubsByDepth, default}} -> - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - {result, Reply}; - {result, {NodeId, _SubsByDepth, Result}} -> - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), - {result, Result}; - Error -> - %% in case we change transaction to sync_dirty... - %% node_call(Type, delete_node, [Host, Node]), - %% tree_call(Host, delete_node, [Host, Node]), - Error - end; - Error -> - Error - end. - -%% @spec (Host, Node, Owner) -> -%% {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% Owner = jid() -%% Reason = stanzaError() --spec(delete_node/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Owner :: jid()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -%% @doc <p>Delete specified node and all childs.</p> -%%<p>There are several reasons why the node deletion request might fail:</p> -%%<ul> -%%<li>The requesting entity does not have sufficient privileges to delete the node.</li> -%%<li>The node is the root collection node, which cannot be deleted.</li> -%%<li>The specified node does not exist.</li> -%%</ul> -delete_node(_Host, <<>>, _Owner) -> - {error, ?ERR_NOT_ALLOWED}; -delete_node(Host, Node, Owner) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> - case node_call(Type, get_affiliation, [NodeId, Owner]) of - {result, owner} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], - Removed = tree_call(Host, delete_node, [Host, Node]), - case node_call(Type, delete_node, [Removed]) of - {result, Res} -> {result, {SubsByDepth, Res}}; - Error -> Error - end; - _ -> - %% Entity is not an owner - {error, ?ERR_FORBIDDEN} - end - end, - Reply = [], - ServerHost = get(server_host), - case transaction(Host, Node, Action, transaction) of - {result, {_TNode, {SubsByDepth, {Result, broadcast, Removed}}}} -> - lists:foreach(fun({RNode, _RSubscriptions}) -> - {RH, RN} = RNode#pubsub_node.nodeid, - NodeId = RNode#pubsub_node.id, - Type = RNode#pubsub_node.type, - Options = RNode#pubsub_node.options, - broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth), - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, NodeId]) - end, Removed), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_TNode, {_, {Result, Removed}}}} -> - lists:foreach(fun({RNode, _RSubscriptions}) -> - {RH, RN} = RNode#pubsub_node.nodeid, - NodeId = RNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, NodeId]) - end, Removed), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {TNode, {_, default}}} -> - NodeId = TNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, NodeId]), - {result, Reply}; - {result, {TNode, {_, Result}}} -> - NodeId = TNode#pubsub_node.id, - ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, NodeId]), - {result, Result}; - Error -> - Error - end. - -%% @spec (Host, Node, From, JID, Configuration) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% Host = host() -%% Node = pubsubNode() -%% From = jid() -%% JID = jid() --spec(subscribe_node/5 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - JID :: binary(), - Configuration :: [xmlel()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -%% @see node_hometree:subscribe_node/5 -%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> -%%<p>There are several reasons why the subscription request might fail:</p> -%%<ul> -%%<li>The bare JID portions of the JIDs do not match.</li> -%%<li>The node has an access model of "presence" and the requesting entity is not subscribed to the owner's presence.</li> -%%<li>The node has an access model of "roster" and the requesting entity is not in one of the authorized roster groups.</li> -%%<li>The node has an access model of "whitelist" and the requesting entity is not on the whitelist.</li> -%%<li>The service requires payment for subscriptions to the node.</li> -%%<li>The requesting entity is anonymous and the service does not allow anonymous entities to subscribe.</li> -%%<li>The requesting entity has a pending subscription.</li> -%%<li>The requesting entity is blocked from subscribing (e.g., because having an affiliation of outcast).</li> -%%<li>The node does not support subscriptions.</li> -%%<li>The node does not exist.</li> -%%</ul> -subscribe_node(Host, Node, From, JID, Configuration) -> - SubOpts = case - pubsub_subscription_odbc:parse_options_xform(Configuration) - of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> - case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - Action = fun (#pubsub_node{options = Options, - type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member(<<"subscribe">>, Features), - OptionsFeature = lists:member(<<"subscription-options">>, Features), - HasOptions = not (SubOpts == []), - SubscribeConfig = get_option(Options, subscribe), - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, Subscriber, - Owners, AccessModel, AllowedGroups), - if not SubscribeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"subscribe">>)}; - not SubscribeConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"subscribe">>)}; - HasOptions andalso not OptionsFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)}; - SubOpts == invalid -> - {error, - extended_error(?ERR_BAD_REQUEST, - <<"invalid-options">>)}; - true -> - node_call(Type, subscribe_node, - [NodeId, From, Subscriber, AccessModel, - SendLast, PresenceSubscription, - RosterGroup, SubOpts]) - end - end, - Reply = fun (Subscription) -> - SubAttrs = case Subscription of - {subscribed, SubId} -> - [{<<"subscription">>, - subscription_to_string(subscribed)}, - {<<"subid">>, SubId}, {<<"node">>, Node}]; - Other -> - [{<<"subscription">>, - subscription_to_string(Other)}, - {<<"node">>, Node}] - end, - Fields = [{<<"jid">>, jlib:jid_to_string(Subscriber)} - | SubAttrs], - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"subscription">>, - attrs = Fields, children = []}]}] - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, - {TNode, {Result, subscribed, SubId, send_last}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - send_items(Host, Node, NodeId, Type, Subscriber, last), - case Result of - default -> {result, Reply({subscribed, SubId})}; - _ -> {result, Result} - end; - {result, {_TNode, {default, subscribed, SubId}}} -> - {result, Reply({subscribed, SubId})}; - {result, {_TNode, {Result, subscribed, _SubId}}} -> - {result, Result}; - {result, {TNode, {default, pending, _SubId}}} -> - send_authorization_request(TNode, Subscriber), - {result, Reply(pending)}; - {result, {TNode, {Result, pending}}} -> - send_authorization_request(TNode, Subscriber), - {result, Result}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host, Noce, From, JID, SubId) -> {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% From = jid() -%% JID = string() -%% SubId = string() -%% Reason = stanzaError() -%% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p> -%%<p>There are several reasons why the unsubscribe request might fail:</p> -%%<ul> -%%<li>The requesting entity has multiple subscriptions to the node but does not specify a subscription ID.</li> -%%<li>The request does not specify an existing subscriber.</li> -%%<li>The requesting entity does not have sufficient privileges to unsubscribe the specified JID.</li> -%%<li>The node does not exist.</li> -%%<li>The request specifies a subscription ID that is not valid or current.</li> -%%</ul> --spec(unsubscribe_node/5 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - JID :: binary() | ljid(), - SubId :: mod_pubsub:subId()) - -> {result, []} - %%% - | {error, xmlel()} -). -unsubscribe_node(Host, Node, From, JID, SubId) - when is_binary(JID) -> - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> - case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - unsubscribe_node(Host, Node, From, Subscriber, SubId); -unsubscribe_node(Host, Node, From, Subscriber, SubId) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, unsubscribe_node, - [NodeId, From, Subscriber, SubId]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, default}} -> {result, []}; -% {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% @doc <p>Publish item to a PubSub node.</p> -%% <p>The permission to publish an item must be verified by the plugin implementation.</p> -%%<p>There are several reasons why the publish request might fail:</p> -%%<ul> -%%<li>The requesting entity does not have sufficient privileges to publish.</li> -%%<li>The node does not support item publication.</li> -%%<li>The node does not exist.</li> -%%<li>The payload size exceeds a service-defined limit.</li> -%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li> -%%<li>The request does not match the node configuration.</li> -%%</ul> --spec(publish_item/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: mod_pubsub:nodeId(), - Publisher :: jid(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload) -> - publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, all); -publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> - publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all). -publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PublishFeature = lists:member(<<"publish">>, Features), - PublishModel = get_option(Options, publish_model), - MaxItems = max_items(Host, Options), - DeliverPayloads = get_option(Options, deliver_payloads), - PersistItems = get_option(Options, persist_items), - PayloadCount = payload_xmlelements(Payload), - PayloadSize = byte_size(term_to_binary(Payload)) - 2, - PayloadMaxSize = get_option(Options, max_payload_size), - if not PublishFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"publish">>)}; - PayloadSize > PayloadMaxSize -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)}; - (PayloadCount == 0) and (Payload == []) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)}; - (PayloadCount > 1) or (PayloadCount == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}; - (DeliverPayloads == false) and (PersistItems == false) and - (PayloadSize > 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)}; - ((DeliverPayloads == true) or (PersistItems == true)) and - (PayloadSize == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; - true -> - node_call(Type, publish_item, [NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload]) - end - end, - ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]), - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node), - children = - [#xmlel{name = <<"item">>, - attrs = itemAttr(ItemId), - children = []}]}]}], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, Broadcast, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - case get_option(Options, deliver_notifications) of - true -> - BroadcastPayload = case Broadcast of - default -> Payload; - broadcast -> Payload; - PluginPayload -> PluginPayload - end, - broadcast_publish_item(Host, Node, NodeId, Type, Options, - Removed, ItemId, jlib:jid_tolower(Publisher), - BroadcastPayload); - false -> - ok - end, - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {TNode, {default, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed), - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - {result, Reply}; - {result, {TNode, {Result, Removed}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed), - set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - {result, Result}; - {result, {_, default}} -> - {result, Reply}; - {result, {_, Result}} -> - {result, Result}; - {error, ?ERR_ITEM_NOT_FOUND} -> - %% handles auto-create feature - %% for automatic node creation. we'll take the default node type: - %% first listed into the plugins configuration option, or pep - Type = select_type(ServerHost, Host, Node), - case lists:member("auto-create", features(Type)) of - true -> - case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of - {result, [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"create">>, - attrs = [{<<"node">>, NewNode}], - children = []}]}]} -> - publish_item(Host, ServerHost, list_to_binary(NewNode), - Publisher, ItemId, Payload); - _ -> - {error, ?ERR_ITEM_NOT_FOUND} - end; - false -> - {error, ?ERR_ITEM_NOT_FOUND} - end; - Error -> - Error - end. - -%% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) -> -%% {error, Reason::stanzaError()} | -%% {result, []} --spec(delete_item/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Publisher :: jid(), - ItemId :: mod_pubsub:itemId()) - -> {result, []} - %%% - | {error, xmlel()} -). -%% @doc <p>Delete item from a PubSub node.</p> -%% <p>The permission to delete an item must be verified by the plugin implementation.</p> -%%<p>There are several reasons why the item retraction request might fail:</p> -%%<ul> -%%<li>The publisher does not have sufficient privileges to delete the requested item.</li> -%%<li>The node or item does not exist.</li> -%%<li>The request does not specify a node.</li> -%%<li>The request does not include an <item/> element or the <item/> element does not specify an ItemId.</li> -%%<li>The node does not support persistent items.</li> -%%<li>The service does not support the deletion of items.</li> -%%</ul> -delete_item(Host, Node, Publisher, ItemId) -> - delete_item(Host, Node, Publisher, ItemId, false). - - -delete_item(_, <<"">>, _, _, _) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"node-required">>)}; -delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - DeleteFeature = lists:member(<<"delete-items">>, Features), - PublishModel = get_option(Options, publish_model), - if %%-> iq_pubsub just does that matchs - %% %% Request does not specify an item - %% {error, extended_error(?ERR_BAD_REQUEST, "item-required")}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - not DeleteFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"delete-items">>)}; - true -> - node_call(Type, delete_item, - [NodeId, Publisher, PublishModel, ItemId]) - end - end, - Reply = [], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, - Options, [ItemId], ForceNotify), - case get_cached_item(Host, NodeId) of - #pubsub_item{itemid = {ItemId, NodeId}} -> - unset_cached_item(Host, NodeId); - _ -> ok - end, - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> {result, Reply}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @spec (Host, JID, Node) -> -%% {error, Reason} | {result, []} -%% Host = host() -%% Node = pubsubNode() -%% JID = jid() -%% Reason = stanzaError() -%% @doc <p>Delete all items of specified node owned by JID.</p> -%%<p>There are several reasons why the node purge request might fail:</p> -%%<ul> -%%<li>The node or service does not support node purging.</li> -%%<li>The requesting entity does not have sufficient privileges to purge the node.</li> -%%<li>The node is not configured to persist items.</li> -%%<li>The specified node does not exist.</li> -%%</ul> --spec(purge_node/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - Owner :: jid()) - -> {result, []} - %%% - | {error, xmlel()} -). -purge_node(Host, Node, Owner) -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PurgeFeature = lists:member(<<"purge-nodes">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - PersistentConfig = get_option(Options, persist_items), - if not PurgeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"purge-nodes">>)}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - not PersistentConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - true -> node_call(Type, purge_node, [NodeId, Owner]) - end - end, - Reply = [], - case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_purge_node(Host, Node, NodeId, Type, Options), - unset_cached_item(Host, NodeId), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> {result, Reply}; - {result, {_, Result}} -> {result, Result}; - Error -> Error - end. - -%% @doc <p>Return the items of a given node.</p> -%% <p>The number of items to return is limited by MaxItems.</p> -%% <p>The permission are not checked in this function.</p> -%% @todo We probably need to check that the user doing the query has the right -%% to read the items. --spec(get_items/7 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - SubId :: mod_pubsub:subId(), - SMaxItems :: binary(), - ItemIDs :: [mod_pubsub:itemId()], - Rsm :: any()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) -> - MaxItems = if SMaxItems == <<"">> -> - get_max_items_node(Host); - true -> - case catch jlib:binary_to_integer(SMaxItems) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - Val -> Val - end - end, - case MaxItems of - {error, Error} -> {error, Error}; - _ -> - Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - RetreiveFeature = lists:member(<<"retrieve-items">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, - AccessModel, AllowedGroups), - if not RetreiveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-items">>)}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"persistent-items">>)}; - true -> - node_call(Type, get_items, - [NodeId, From, AccessModel, - PresenceSubscription, RosterGroup, - SubId, RSM]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, {Items, RSMOut}}} -> - SendItems = case ItemIDs of - [] -> Items; - _ -> - lists:filter(fun (#pubsub_item{itemid = - {ItemId, - _}}) -> - lists:member(ItemId, - ItemIDs) - end, - Items) - end, - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = - itemsEls(lists:sublist(SendItems, MaxItems))} - | jlib:rsm_encode(RSMOut)]}]}; - Error -> Error - end - end. - -get_items(Host, Node) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_items, [NodeId, service_jid(Host)]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error - end. - -get_item(Host, Node, ItemId) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_item, [NodeId, ItemId]) - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error - end. - -get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> - case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of - {result, {I, _}} -> {result, I}; - Error -> Error - end. -get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) -> - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, AccessModel, - AllowedGroups), - node_call(Type, get_items, - [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]). - -%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() -%% Host = pubsubHost() -%% Node = pubsubNode() -%% NodeId = pubsubNodeId() -%% Type = pubsubNodeType() -%% LJID = {U, S, []} -%% Number = last | integer() -%% @doc <p>Resend the items of a node to the user.</p> -%% @todo use cache-last-item feature -send_items(Host, Node, NodeId, Type, LJID, last) -> - Stanza = case get_cached_item(Host, NodeId) of - undefined -> - % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc - case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of - {result, [LastItem]} -> - {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, - event_stanza_with_delay( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = itemsEls([LastItem])}], ModifNow, ModifUSR); - _ -> - event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = itemsEls([])}]) - end; - LastItem -> - {ModifNow, ModifUSR} = - LastItem#pubsub_item.modification, - event_stanza_with_delay([#xmlel{name = - <<"items">>, - attrs = nodeAttr(Node), - children = - itemsEls([LastItem])}], - ModifNow, ModifUSR) - end, - ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); -send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, - Number) -> - ToSend = case node_action(Host, Type, get_items, - [NodeId, LJID]) - of - {result, []} -> []; - {result, Items} -> - case Number of - N when N > 0 -> lists:sublist(Items, N); - _ -> Items - end; - _ -> [] - end, - Stanza = case ToSend of - [LastItem] -> - {ModifNow, ModifUSR} = - LastItem#pubsub_item.modification, - event_stanza_with_delay([#xmlel{name = <<"items">>, - attrs = nodeAttr(Node), - children = - itemsEls(ToSend)}], - ModifNow, ModifUSR); - _ -> - event_stanza([#xmlel{name = <<"items">>, - attrs = nodeAttr(Node), - children = itemsEls(ToSend)}]) - end, - ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza). - -%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} -%% Host = host() -%% JID = jid() -%% Plugins = [Plugin::string()] -%% Reason = stanzaError() -%% Response = [pubsubIQResponse()] -%% @doc <p>Return the list of affiliations as an XMPP response.</p> --spec(get_affiliations/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - JID :: jid(), - Plugins :: [binary()]) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_affiliations(Host, <<>>, JID, Plugins) - when is_list(Plugins) -> - Result = lists:foldl(fun (Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"retrieve-affiliations">>, Features), - if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, JID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, - Affiliation}) -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"affiliation">>, - affiliation_to_string(Affiliation)} - | nodeAttr(Node)], - children = []}] - end, - lists:usort(lists:flatten(Affiliations))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"affiliations">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end; -get_affiliations(Host, NodeId, JID, Plugins) - when is_list(Plugins) -> - Result = lists:foldl(fun (Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"retrieve-affiliations">>, - Features), - if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, JID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, - Affiliation}) - when NodeId == Node -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"affiliation">>, - affiliation_to_string(Affiliation)} - | nodeAttr(Node)], - children = []}]; - (_) -> [] - end, - lists:usort(lists:flatten(Affiliations))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"affiliations">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end. - --spec(get_affiliations/3 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - JID :: jid()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} -). -get_affiliations(Host, Node, JID) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"modify-affiliations">>, Features), - {result, Affiliation} = node_call(Type, get_affiliation, - [NodeId, JID]), - if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"modify-affiliations">>)}; - Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; - true -> node_call(Type, get_node_affiliations, [NodeId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, []}} -> {error, ?ERR_ITEM_NOT_FOUND}; - {result, {_, Affiliations}} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({AJID, Affiliation}) -> - [#xmlel{name = <<"affiliation">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"affiliation">>, - affiliation_to_string(Affiliation)}], - children = []}] - end, - Affiliations), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"affiliations">>, - attrs = nodeAttr(Node), children = Entities}]}]}; - Error -> Error - end. - --spec(set_affiliations/4 :: -( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - EntitiesEls :: [xmlel()]) - -> {result, []} - %%% - | {error, xmlel()} -). -set_affiliations(Host, Node, From, EntitiesEls) -> - Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = lists:foldl(fun (El, Acc) -> - case Acc of - error -> error; - _ -> - case El of - #xmlel{name = <<"affiliation">>, - attrs = Attrs} -> - JID = - jlib:string_to_jid(xml:get_attr_s(<<"jid">>, - Attrs)), - Affiliation = - string_to_affiliation(xml:get_attr_s(<<"affiliation">>, - Attrs)), - if (JID == error) or - (Affiliation == false) -> - error; - true -> - [{jlib:jid_tolower(JID), - Affiliation} - | Acc] - end - end - end - end, - [], EntitiesEls), - case Entities of - error -> {error, ?ERR_BAD_REQUEST}; - _ -> - Action = fun (#pubsub_node{type = Type, - id = NodeId} = - N) -> - Owners = node_owners_call(Type, NodeId), - case lists:member(Owner, Owners) of - true -> - OwnerJID = jlib:make_jid(Owner), - FilteredEntities = case Owners of - [Owner] -> - [E - || E <- Entities, - element(1, E) =/= - OwnerJID]; - _ -> Entities - end, - lists:foreach(fun ({JID, Affiliation}) -> - node_call(Type, set_affiliation, [NodeId, JID, Affiliation]) - end, - FilteredEntities), - {result, []}; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - -get_options(Host, Node, JID, SubID, Lang) -> - Action = fun (#pubsub_node{type = Type, id = NodeID}) -> - case lists:member(<<"subscription-options">>, features(Type)) of - true -> - get_options_helper(JID, Lang, Node, NodeID, SubID, Type); - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, XForm}} -> {result, [XForm]}; - Error -> Error - end. - -get_options_helper(JID, Lang, Node, NodeID, SubID, Type) -> - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> case jlib:jid_tolower(J) of - error -> {<<"">>, <<"">>, <<"">>}; - J1 -> J1 - end - end, - {result, Subs} = node_call(Type, get_subscriptions, - [NodeID, Subscriber]), - SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> - [SID | Acc]; - (_, Acc) -> Acc - end, - [], Subs), - case {SubID, SubIDs} of - {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)}; - {<<>>, [SID]} -> - read_sub(Subscriber, Node, NodeID, SID, Lang); - {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)}; - {_, _} -> - read_sub(Subscriber, Node, NodeID, SubID, Lang) - end. - -read_sub(Subscriber, Node, NodeID, SubID, Lang) -> - case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, #pubsub_subscription{options = Options}} -> - {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options), - OptionsEl = #xmlel{name = <<"options">>, - attrs = - [{<<"jid">>, jlib:jid_to_string(Subscriber)}, - {<<"subid">>, SubID} - | nodeAttr(Node)], - children = [XdataEl]}, - PubsubEl = #xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [OptionsEl]}, - {result, PubsubEl} - end. - -set_options(Host, Node, JID, SubID, Configuration) -> - Action = fun (#pubsub_node{type = Type, id = NodeID}) -> - case lists:member(<<"subscription-options">>, - features(Type)) - of - true -> - set_options_helper(Configuration, JID, NodeID, SubID, - Type); - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"subscription-options">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, Result}} -> {result, Result}; - Error -> Error - end. - -set_options_helper(Configuration, JID, NodeID, SubID, Type) -> - SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, - Subscriber = case jlib:string_to_jid(JID) of - error -> {<<"">>, <<"">>, <<"">>}; - J -> jlib:jid_tolower(J) - end, - {result, Subs} = node_call(Type, get_subscriptions, - [NodeID, Subscriber]), - SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> - [SID | Acc]; - (_, Acc) -> Acc - end, - [], Subs), - case {SubID, SubIDs} of - {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"not-subscribed">>)}; - {<<>>, [SID]} -> - write_sub(Subscriber, NodeID, SID, SubOpts); - {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, - <<"subid-required">>)}; - {_, _} -> write_sub(Subscriber, NodeID, SubID, SubOpts) - end. - -write_sub(_Subscriber, _NodeID, _SubID, invalid) -> - {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; -write_sub(Subscriber, NodeID, SubID, Options) -> - case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, _} -> - {result, []} - end. - -%% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} -%% Host = host() -%% Node = pubsubNode() -%% JID = jid() -%% Plugins = [Plugin::string()] -%% Reason = stanzaError() -%% Response = [pubsubIQResponse()] -%% @doc <p>Return the list of subscriptions as an XMPP response.</p> -get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> - Result = lists:foldl( - fun(Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features), - if - not RetrieveFeature -> - %% Service does not support retreive subscriptions - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-subscriptions">>)}, Acc}; - true -> - Subscriber = jlib:jid_remove_resource(JID), - {result, Subscriptions} = node_action(Host, Type, get_entity_subscriptions, [Host, Subscriber]), - {Status, [Subscriptions|Acc]} - end - end, {ok, []}, Plugins), - case Result of - {ok, Subscriptions} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end; - ({_, none, _}) -> []; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription, SubID, SubJID}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subid">>, - SubID}, - {<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subid">>, - SubID}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end; - ({#pubsub_node{nodeid = {_, SubsNode}}, - Subscription, SubJID}) -> - case Node of - <<>> -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)} - | nodeAttr(SubsNode)], - children = []}]; - SubsNode -> - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(SubJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - _ -> [] - end - end, - lists:usort(lists:flatten(Subscriptions))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"subscriptions">>, attrs = [], - children = Entities}]}]}; - {Error, _} -> Error - end. - -get_subscriptions(Host, Node, JID) -> - Action = fun (#pubsub_node{type = Type, id = NodeId}) -> - Features = features(Type), - RetrieveFeature = - lists:member(<<"manage-subscriptions">>, Features), - {result, Affiliation} = node_call(Type, get_affiliation, - [NodeId, JID]), - if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"manage-subscriptions">>)}; - Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; - true -> - node_call(Type, get_node_subscriptions, [NodeId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Subscriptions}} -> - Entities = lists:flatmap(fun ({_, none}) -> []; - ({_, pending, _}) -> []; - ({AJID, Subscription}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}], - children = []}]; - ({AJID, Subscription, SubId}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(AJID)}, - {<<"subscription">>, - subscription_to_string(Subscription)}, - {<<"subid">>, SubId}], - children = []}] - end, - Subscriptions), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"subscriptions">>, - attrs = nodeAttr(Node), children = Entities}]}]}; - Error -> Error - end. - -set_subscriptions(Host, Node, From, EntitiesEls) -> - Owner = - jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = lists:foldl(fun (El, Acc) -> - case Acc of - error -> error; - _ -> - case El of - #xmlel{name = <<"subscription">>, - attrs = Attrs} -> - JID = - jlib:string_to_jid(xml:get_attr_s(<<"jid">>, - Attrs)), - Subscription = - string_to_subscription(xml:get_attr_s(<<"subscription">>, - Attrs)), - SubId = - xml:get_attr_s(<<"subid">>, - Attrs), - if (JID == error) or - (Subscription == false) -> - error; - true -> - [{jlib:jid_tolower(JID), - Subscription, SubId} - | Acc] - end - end - end - end, - [], EntitiesEls), - case Entities of - error -> {error, ?ERR_BAD_REQUEST}; - _ -> - Notify = fun (JID, Sub, _SubId) -> - Stanza = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"pubsub">>, - attrs = - [{<<"xmlns">>, - ?NS_PUBSUB}], - children = - [#xmlel{name = - <<"subscription">>, - attrs = - [{<<"jid">>, - jlib:jid_to_string(JID)}, - {<<"subscription">>, - subscription_to_string(Sub)} - | nodeAttr(Node)], - children = - []}]}]}, - ejabberd_router:route(service_jid(Host), - jlib:make_jid(JID), Stanza) - end, - Action = fun (#pubsub_node{type = Type, - id = NodeId}) -> - case lists:member(Owner, node_owners_call(Type, NodeId)) of - true -> - Result = lists:foldl(fun ({JID, Subscription, - SubId}, - Acc) -> - case - node_call(Type, - set_subscriptions, - [NodeId, - JID, - Subscription, - SubId]) - of - {error, Err} -> - [{error, - Err} - | Acc]; - _ -> - Notify(JID, - Subscription, - SubId), - Acc - end - end, - [], Entities), - case Result of - [] -> {result, []}; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end - end. - --spec(get_presence_and_roster_permissions/5 :: -( - Host :: mod_pubsub:host(), - From :: ljid(), - Owners :: [ljid(),...], - AccessModel :: mod_pubsub:accessModel(), - AllowedGroups :: [binary()]) - -> {PresenceSubscription::boolean(), RosterGroup::boolean()} -). - -get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) -> - if (AccessModel == presence) or (AccessModel == roster) -> - case Host of - {User, Server, _} -> - get_roster_info(User, Server, From, AllowedGroups); - _ -> - [{OUser, OServer, _} | _] = Owners, - get_roster_info(OUser, OServer, From, AllowedGroups) - end; - true -> {true, true} - end. - -%% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, SubscriberResource}, AllowedGroups) -%% -> {PresenceSubscription, RosterGroup} -get_roster_info(_, _, {<<"">>, <<"">>, _}, _) -> - {false, false}; -get_roster_info(OwnerUser, OwnerServer, - {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> - {Subscription, Groups} = - ejabberd_hooks:run_fold(roster_get_jid_info, - OwnerServer, {none, []}, - [OwnerUser, OwnerServer, - {SubscriberUser, SubscriberServer, <<"">>}]), - PresenceSubscription = Subscription == both orelse - Subscription == from orelse - {OwnerUser, OwnerServer} == - {SubscriberUser, SubscriberServer}, - RosterGroup = lists:any(fun (Group) -> - lists:member(Group, AllowedGroups) - end, - Groups), - {PresenceSubscription, RosterGroup}; -%% @spec (AffiliationStr) -> Affiliation -%% AffiliationStr = string() -%% Affiliation = atom() -%% @doc <p>Convert an affiliation type from string to atom.</p> -get_roster_info(OwnerUser, OwnerServer, JID, - AllowedGroups) -> - get_roster_info(OwnerUser, OwnerServer, - jlib:jid_tolower(JID), AllowedGroups). - -string_to_affiliation(<<"owner">>) -> owner; -string_to_affiliation(<<"publisher">>) -> publisher; -string_to_affiliation(<<"member">>) -> member; -string_to_affiliation(<<"outcast">>) -> outcast; -string_to_affiliation(<<"none">>) -> none; -string_to_affiliation(_) -> false. - -%% @spec (SubscriptionStr) -> Subscription -%% SubscriptionStr = string() -%% Subscription = atom() -%% @doc <p>Convert a subscription type from string to atom.</p> -string_to_subscription(<<"subscribed">>) -> subscribed; -string_to_subscription(<<"pending">>) -> pending; -string_to_subscription(<<"unconfigured">>) -> - unconfigured; -string_to_subscription(<<"none">>) -> none; -string_to_subscription(_) -> false. - -%% @spec (Affiliation) -> AffiliationStr -%% Affiliation = atom() -%% AffiliationStr = string() -%% @doc <p>Convert an affiliation type from atom to string.</p> -%% @spec (Subscription) -> SubscriptionStr -%% Subscription = atom() -%% SubscriptionStr = string() -%% @doc <p>Convert a subscription type from atom to string.</p> -%% @spec (Node) -> NodeStr -%% Node = pubsubNode() -%% NodeStr = string() -%% @doc <p>Convert a node type from pubsubNode to string.</p> -%% @spec (Host) -> jid() -%% Host = host() -%% @doc <p>Generate pubsub service JID.</p> -affiliation_to_string(owner) -> <<"owner">>; -affiliation_to_string(publisher) -> <<"publisher">>; -affiliation_to_string(member) -> <<"member">>; -affiliation_to_string(outcast) -> <<"outcast">>; -affiliation_to_string(_) -> <<"none">>. - -subscription_to_string(subscribed) -> <<"subscribed">>; -subscription_to_string(pending) -> <<"pending">>; -subscription_to_string(unconfigured) -> <<"unconfigured">>; -subscription_to_string(_) -> <<"none">>. - --spec(service_jid/1 :: -( - Host :: mod_pubsub:host()) - -> jid() -). -service_jid(Host) -> -%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() -%% LJID = jid() -%% NotifyType = items | nodes -%% Depth = integer() -%% NodeOptions = [{atom(), term()}] -%% SubOptions = [{atom(), term()}] -%% @doc <p>Check if a notification must be delivered or not based on -%% node and subscription options.</p> - case Host of - {U, S, _} -> {jid, U, S, <<"">>, U, S, <<"">>}; - _ -> {jid, <<"">>, Host, <<"">>, <<"">>, Host, <<"">>} - end. - -is_to_deliver(LJID, NotifyType, Depth, NodeOptions, - SubOptions) -> - sub_to_deliver(LJID, NotifyType, Depth, SubOptions) - andalso node_to_deliver(LJID, NodeOptions). - -sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> - lists:all(fun (Option) -> - sub_option_can_deliver(NotifyType, Depth, Option) - end, - SubOptions). - -sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; -sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; -sub_option_can_deliver(_, _, {subscription_depth, all}) -> true; -sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D; -sub_option_can_deliver(_, _, {deliver, false}) -> false; -sub_option_can_deliver(_, _, {expire, When}) -> now() < When; -sub_option_can_deliver(_, _, _) -> true. - -node_to_deliver(LJID, NodeOptions) -> - PresenceDelivery = get_option(NodeOptions, presence_based_delivery), - presence_can_deliver(LJID, PresenceDelivery). - --spec(presence_can_deliver/2 :: -( - Entity :: ljid(), - _ :: boolean()) - -> boolean() -). -presence_can_deliver(_, false) -> true; -presence_can_deliver({User, Server, Resource}, true) -> - case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of - [] -> false; - Ss -> - lists:foldl(fun(_, true) -> true; - ({session, _, _ , _, undefined, _}, _Acc) -> false; - ({session, _, {_, _, R}, _, _Priority, _}, _Acc) -> - case Resource of - [] -> true; - R -> true; - _ -> false - end - end, false, Ss) - end. - --spec(state_can_deliver/2 :: -( - Entity::ljid(), - SubOptions :: mod_pubsub:subOptions() | []) - -> [ljid()] -). -state_can_deliver({U, S, R}, []) -> [{U, S, R}]; -state_can_deliver({U, S, R}, SubOptions) -> - %% Check SubOptions for 'show_values' - case lists:keysearch('show_values', 1, SubOptions) of - %% If not in suboptions, item can be delivered, case doesn't apply - false -> [{U, S, R}]; - %% If in a suboptions ... - {_, {_, ShowValues}} -> - %% Get subscriber resources - Resources = case R of - %% If the subscriber JID is a bare one, get all its resources - <<>> -> user_resources(U, S); - %% If the subscriber JID is a full one, use its resource - R -> [R] - end, - %% For each resource, test if the item is allowed to be delivered - %% based on resource state - lists:foldl( - fun(Resource, Acc) -> - get_resource_state({U, S, Resource}, ShowValues, Acc) - end, [], Resources) - end. - --spec(get_resource_state/3 :: -( - Entity :: ljid(), - ShowValues :: [binary()], - JIDs :: [ljid()]) - -> [ljid()] -). -get_resource_state({U, S, R}, ShowValues, JIDs) -> - case ejabberd_sm:get_session_pid(U, S, R) of - %% If no PID, item can be delivered - none -> lists:append([{U, S, R}], JIDs); - %% If PID ... - Pid -> - %% Get user resource state - %% TODO : add a catch clause - Show = case ejabberd_c2s:get_presence(Pid) of - {_, _, <<"available">>, _} -> <<"online">>; - {_, _, State, _} -> State - end, - %% Is current resource state listed in 'show-values' suboption ? - case lists:member(Show, ShowValues) of %andalso Show =/= "online" of - %% If yes, item can be delivered - true -> lists:append([{U, S, R}], JIDs); - %% If no, item can't be delivered - false -> JIDs - end - end. - -%% @spec (Payload) -> int() -%% Payload = term() --spec(payload_xmlelements/1 :: -( - Payload :: mod_pubsub:payload()) - -> Count :: non_neg_integer() -). -%% @doc <p>Count occurence of XML elements in payload.</p> -payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0). -payload_xmlelements([], Count) -> Count; -payload_xmlelements([#xmlel{} | Tail], Count) -> - payload_xmlelements(Tail, Count + 1); -payload_xmlelements([_ | Tail], Count) -> - payload_xmlelements(Tail, Count). - -%% @spec (Els) -> stanza() -%% Els = [xmlelement()] -%% @doc <p>Build pubsub event stanza</p> -event_stanza(Els) -> event_stanza_withmoreels(Els, []). - -event_stanza_with_delay(Els, ModifNow, ModifUSR) -> - DateTime = calendar:now_to_datetime(ModifNow), - MoreEls = [jlib:timestamp_to_xml(DateTime, utc, - ModifUSR, <<"">>)], - event_stanza_withmoreels(Els, MoreEls). - -event_stanza_withmoreels(Els, MoreEls) -> - #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"event">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], - children = Els} - | MoreEls]}. - -%%%%%% broadcast functions - -broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> Payload; - false -> [] - end, - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), - children = Content}]}]), - broadcast_stanza(Host, From, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - case Removed of - [] -> - ok; - _ -> - case get_option(NodeOptions, notify_retract) of - true -> - RetractStanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, - items, RetractStanza, true); - _ -> - ok - end - end, - {result, true}; - _ -> - {result, false} - end. - -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) -> - broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false). -broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) -> - {result, false}; -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) -> - case (get_option(NodeOptions, notify_retract) or ForceNotify) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) -> - case get_option(NodeOptions, notify_retract) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - case get_option(NodeOptions, notify_delete) of - true -> - case SubsByDepth of - [] -> - {result, false}; - _ -> - Stanza = event_stanza( - [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true} - end; - _ -> - {result, false} - end. - -broadcast_created_node(_, _, _, _, _, []) -> - {result, false}; -broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]), - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), - {result, true}. - -broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) -> - case get_option(NodeOptions, notify_config) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> - [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = get_configure_xfields(Type, NodeOptions, Lang, [])}]; - false -> - [] - end, - Stanza = event_stanza( - [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -get_collection_subscriptions(Host, Node) -> - Action = fun() -> - {result, lists:map(fun({Depth, Nodes}) -> - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} - end, - case transaction(Host, Action, sync_dirty) of - {result, CollSubs} -> CollSubs; - _ -> [] - end. - -get_node_subs(#pubsub_node{type = Type, - id = NodeID}) -> - case node_call(Type, get_node_subscriptions, [NodeID]) of - {result, Subs} -> get_options_for_subs(NodeID, Subs); - Other -> Other - end. - -get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> - case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; - {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> - Acc - end, [], Subs). - -broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - NotificationType = get_option(NodeOptions, notification_type, headline), - BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = case NotificationType of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% Handles explicit subscriptions - SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), - lists:foreach(fun ({LJID, NodeName, SubIDs}) -> - LJIDs = case BroadcastAll of - true -> - {U, S, _} = LJID, - [{U, S, R} || R <- user_resources(U, S)]; - false -> - [LJID] - end, - %% Determine if the stanza should have SHIM ('SubID' and 'name') headers - StanzaToSend = case {SHIM, SubIDs} of - {false, _} -> - Stanza; - %% If there's only one SubID, don't add it - {true, [_]} -> - add_shim_headers(Stanza, collection_shim(NodeName)); - {true, SubIDs} -> - add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs))) - end, - lists:foreach(fun(To) -> - ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend) - end, LJIDs) - end, SubIDsByJID). - -broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM), - %% Handles implicit presence subscriptions - SenderResource = case LResource of - [] -> - case user_resources(LUser, LServer) of - [Resource|_] -> Resource; - _ -> "" - end; - _ -> - LResource - end, - case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of - C2SPid when is_pid(C2SPid) -> - Stanza = case get_option(NodeOptions, notification_type, headline) of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% set the from address on the notification to the bare JID of the account owner - %% Also, add "replyto" if entity has presence subscription to the account owner - %% See XEP-0163 1.1 section 4.3.1 - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = jlib:make_jid(LUser, LServer, ""), - _StanzaToSend = add_extended_headers(Stanza, - _ReplyTo = extended_headers([jlib:jid_to_string(Publisher)]))); - _ -> - ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza]) - end; -broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). - -subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> - NodesToDeliver = fun(Depth, Node, Subs, Acc) -> - NodeName = case Node#pubsub_node.nodeid of - {_, N} -> N; - Other -> Other - end, - NodeOptions = Node#pubsub_node.options, - lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> - case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of - true -> - %% If is to deliver : - case state_can_deliver(LJID, SubOptions) of - [] -> {JIDs, Recipients}; - JIDsToDeliver -> - lists:foldl( - fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> - case lists:member(JIDToDeliver, JIDs) of - %% check if the JIDs co-accumulator contains the Subscription Jid, - false -> - %% - if not, - %% - add the Jid to JIDs list co-accumulator ; - %% - create a tuple of the Jid, NodeId, and SubID (as list), - %% and add the tuple to the Recipients list co-accumulator - {[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]}; - true -> - %% - if the JIDs co-accumulator contains the Jid - %% get the tuple containing the Jid from the Recipient list co-accumulator - {_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc), - %% delete the tuple from the Recipients list - % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients), - % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, NodeId1, [SubID | SubIDs]}), - %% add the SubID to the SubIDs list in the tuple, - %% and add the tuple back to the Recipients list co-accumulator - % v1.1 : {JIDs, lists:append(Recipients1, [{LJID, NodeId1, lists:append(SubIDs, [SubID])}])} - % v1.2 : {JIDs, [{LJID, NodeId1, [SubID | SubIDs]} | Recipients1]} - % v2: {JIDs, Recipients1} - {JIDsAcc, lists:keyreplace(JIDToDeliver, 1, RecipientsAcc, {JIDToDeliver, NodeName1, [SubID | SubIDs]})} - end - end, {JIDs, Recipients}, JIDsToDeliver) - end; - false -> - {JIDs, Recipients} - end - end, Acc, Subs) - end, - DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) -> - lists:foldl(fun({Node, Subs}, Acc2) -> - NodesToDeliver(Depth, Node, Subs, Acc2) - end, Acc1, SubsByNode) - end, - {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), - JIDSubs. - -user_resources(User, Server) -> - ejabberd_sm:get_user_resources(User, Server). - -%%%%%%% Configuration handling - -%%<p>There are several reasons why the default node configuration options request might fail:</p> -%%<ul> -%%<li>The service does not support node configuration.</li> -%%<li>The service does not support retrieval of default node configuration.</li> -%%</ul> -get_configure(Host, ServerHost, Node, From, Lang) -> - Action = fun (#pubsub_node{options = Options, - type = Type, id = NodeId}) -> - case node_call(Type, get_affiliation, [NodeId, From]) of - {result, owner} -> - Groups = ejabberd_hooks:run_fold(roster_groups, - ServerHost, [], - [ServerHost]), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"configure">>, - attrs = nodeAttr(Node), - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, - <<"form">>}], - children = - get_configure_xfields(Type, - Options, - Lang, - Groups)}]}]}]}; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end. - -get_default(Host, Node, _From, Lang) -> - Type = select_type(Host, Host, Node), - Options = node_options(Type), -%% Get node option -%% The result depend of the node type plugin system. - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"default">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - get_configure_xfields(Type, Options, - Lang, [])}]}]}]}. - -get_option([], _) -> false; -get_option(Options, Var) -> - get_option(Options, Var, false). - -get_option(Options, Var, Def) -> - case lists:keysearch(Var, 1, Options) of - {value, {_Val, Ret}} -> Ret; - _ -> Def - end. - -%% Get default options from the module plugin. -node_options(Type) -> - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - case catch Module:options() of - {'EXIT', {undef, _}} -> - DefaultModule = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - (?STDNODE)/binary>>), - DefaultModule:options(); - Result -> Result - end. - -%% @spec (Host, Options) -> MaxItems -%% Host = host() -%% Options = [Option] -%% Option = {Key::atom(), Value::term()} -%% MaxItems = integer() | unlimited -%% @doc <p>Return the maximum number of items for a given node.</p> -%% <p>Unlimited means that there is no limit in the number of items that can -%% be stored.</p> -%% @todo In practice, the current data structure means that we cannot manage -%% millions of items on a given node. This should be addressed in a new -%% version. -max_items(Host, Options) -> - case get_option(Options, persist_items) of - true -> - case get_option(Options, max_items) of - false -> unlimited; - Result when Result < 0 -> 0; - Result -> Result - end; - false -> - case get_option(Options, send_last_published_item) of - never -> 0; - _ -> - case is_last_item_cache_enabled(Host) of - true -> 0; - false -> 1 - end - end - end. - --define(BOOL_CONFIG_FIELD(Label, Var), - ?BOOLXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var)))). - --define(STRING_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var, <<"">>)))). - --define(INTEGER_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (iolist_to_binary(integer_to_list(get_option(Options, - Var)))))). - --define(JLIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (jlib:jid_to_string(get_option(Options, Var))), - [jlib:jid_to_string(O) || O <- Opts])). - --define(ALIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (iolist_to_binary(atom_to_list(get_option(Options, - Var)))), - [iolist_to_binary(atom_to_list(O)) || O <- Opts])). - --define(LISTM_CONFIG_FIELD(Label, Var, Opts), - ?LISTMXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - (get_option(Options, Var)), Opts)). - --define(NLIST_CONFIG_FIELD(Label, Var), - ?STRINGMXFIELD(Label, - <<"pubsub#", - (iolist_to_binary(atom_to_list(Var)))/binary>>, - get_option(Options, Var, []))). - -get_configure_xfields(_Type, Options, Lang, Groups) -> - [?XFIELD(<<"hidden">>, <<"">>, <<"FORM_TYPE">>, - (?NS_PUBSUB_NODE_CONFIG)), - ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>, - deliver_payloads), - ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>, - deliver_notifications), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuratio" - "n changes">>, - notify_config), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is " - "deleted">>, - notify_delete), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed " - "from the node">>, - notify_retract), - ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>, - persist_items), - ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>, - title), - ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>, - max_items), - ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>, - subscribe), - ?ALIST_CONFIG_FIELD(<<"Specify the access model">>, - access_model, - [open, authorize, presence, roster, whitelist]), - ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>, - roster_groups_allowed, Groups), - ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>, - publish_model, [publishers, subscribers, open]), - ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher " - "goes offline">>, - purge_offline), - ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>, - notification_type, [headline, normal]), - ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>, - max_payload_size), - ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>, - send_last_published_item, - [never, on_sub, on_sub_and_presence]), - ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available " - "users">>, - presence_based_delivery), - ?NLIST_CONFIG_FIELD(<<"The collections with which a node is " - "affiliated">>, - collection)]. - -%%<p>There are several reasons why the node configuration request might fail:</p> -%%<ul> -%%<li>The service does not support node configuration.</li> -%%<li>The requesting entity does not have sufficient privileges to configure the node.</li> -%%<li>The request did not specify a node.</li> -%%<li>The node has no configuration options.</li> -%%<li>The specified node does not exist.</li> -%%</ul> -set_configure(Host, Node, From, Els, Lang) -> - case xml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, []}; - {?NS_XDATA, <<"submit">>} -> - Action = fun (#pubsub_node{options = Options, - type = Type, id = NodeId} = - N) -> - case node_call(Type, get_affiliation, - [NodeId, From]) - of - {result, owner} -> - case jlib:parse_xdata_submit(XEl) of - invalid -> {error, ?ERR_BAD_REQUEST}; - XData -> - OldOpts = case Options of - [] -> - node_options(Type); - _ -> Options - end, - case set_xoption(Host, XData, - OldOpts) - of - NewOpts - when is_list(NewOpts) -> - case tree_call(Host, - set_node, - [N#pubsub_node{options - = - NewOpts}]) - of - ok -> {result, ok}; - Err -> Err - end; - Err -> Err - end - end; - _ -> {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, transaction) of - {result, {TNode, ok}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_config_notification(Host, Node, NodeId, Type, - Options, Lang), - {result, []}; - Other -> Other - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end. - -add_opt(Key, Value, Opts) -> - Opts1 = lists:keydelete(Key, 1, Opts), - [{Key, Value} | Opts1]. - --define(SET_BOOL_XOPT(Opt, Val), - BoolVal = case Val of - <<"0">> -> false; - <<"1">> -> true; - <<"false">> -> false; - <<"true">> -> true; - _ -> error - end, - case BoolVal of - error -> {error, ?ERR_NOT_ACCEPTABLE}; - _ -> - set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) - end). - --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). - --define(SET_INTEGER_XOPT(Opt, Val, Min, Max), - case catch jlib:binary_to_integer(Val) of - IVal when is_integer(IVal), IVal >= Min, IVal =< Max -> - set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end). - --define(SET_ALIST_XOPT(Opt, Val, Vals), - case lists:member(Val, - [iolist_to_binary(atom_to_list(V)) || V <- Vals]) - of - true -> - set_xoption(Host, Opts, - add_opt(Opt, jlib:binary_to_atom(Val), NewOpts)); - false -> {error, ?ERR_NOT_ACCEPTABLE} - end). - --define(SET_LIST_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). - -set_xoption(_Host, [], NewOpts) -> NewOpts; -set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], - NewOpts) -> - set_xoption(Host, Opts, NewOpts); -set_xoption(Host, - [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], - NewOpts) -> - ?SET_LIST_XOPT(roster_groups_allowed, Value); -set_xoption(Host, - [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(deliver_payloads, Val); -set_xoption(Host, - [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(deliver_notifications, Val); -set_xoption(Host, - [{<<"pubsub#notify_config">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_config, Val); -set_xoption(Host, - [{<<"pubsub#notify_delete">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_delete, Val); -set_xoption(Host, - [{<<"pubsub#notify_retract">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(notify_retract, Val); -set_xoption(Host, - [{<<"pubsub#persist_items">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(persist_items, Val); -set_xoption(Host, - [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) -> - MaxItems = get_max_items_node(Host), - ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems); -set_xoption(Host, - [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(subscribe, Val); -set_xoption(Host, - [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(access_model, Val, - [open, authorize, presence, roster, whitelist]); -set_xoption(Host, - [{<<"pubsub#publish_model">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(publish_model, Val, - [publishers, subscribers, open]); -set_xoption(Host, - [{<<"pubsub#notification_type">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(notification_type, Val, - [headline, normal]); -set_xoption(Host, - [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]); -set_xoption(Host, - [{<<"pubsub#max_payload_size">>, [Val]} | Opts], - NewOpts) -> - ?SET_INTEGER_XOPT(max_payload_size, Val, 0, - (?MAX_PAYLOAD_SIZE)); -set_xoption(Host, - [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], - NewOpts) -> - ?SET_ALIST_XOPT(send_last_published_item, Val, - [never, on_sub, on_sub_and_presence]); -set_xoption(Host, - [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(presence_based_delivery, Val); -set_xoption(Host, - [{<<"pubsub#purge_offline">>, [Val]} | Opts], - NewOpts) -> - ?SET_BOOL_XOPT(purge_offline, Val); -set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], - NewOpts) -> - ?SET_STRING_XOPT(title, Value); -set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], - NewOpts) -> - ?SET_STRING_XOPT(type, Value); -set_xoption(Host, - [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) -> - ?SET_STRING_XOPT(body_xslt, Value); -set_xoption(Host, - [{<<"pubsub#collection">>, Value} | Opts], NewOpts) -> -% NewValue = [string_to_node(V) || V <- Value], - ?SET_LIST_XOPT(collection, Value); -set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], - NewOpts) -> -% NewValue = string_to_node(Value), - ?SET_LIST_XOPT(node, Value); -set_xoption(Host, [_ | Opts], NewOpts) -> - set_xoption(Host, Opts, NewOpts). - -get_max_items_node({_, ServerHost, _}) -> - get_max_items_node(ServerHost); -get_max_items_node(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - max_items_node) - of - [{max_items_node, Integer}] -> Integer; - _ -> ?MAXITEMS - end. - -%%%% last item cache handling - -is_last_item_cache_enabled({_, ServerHost, _}) -> - is_last_item_cache_enabled(ServerHost); -is_last_item_cache_enabled(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - last_item_cache) - of - [{last_item_cache, true}] -> true; - _ -> false - end. - -set_cached_item({_, ServerHost, _}, NodeId, ItemId, - Publisher, Payload) -> - set_cached_item(ServerHost, NodeId, ItemId, Publisher, - Payload); -set_cached_item(Host, NodeId, ItemId, Publisher, - Payload) -> - case is_last_item_cache_enabled(Host) of - true -> - mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, - {now(), - jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, - Payload}); - _ -> ok - end. - -unset_cached_item({_, ServerHost, _}, NodeId) -> - unset_cached_item(ServerHost, NodeId); -unset_cached_item(Host, NodeId) -> - case is_last_item_cache_enabled(Host) of - true -> mnesia:dirty_delete({pubsub_last_item, NodeId}); - _ -> ok - end. - --spec(get_cached_item/2 :: -( - Host :: mod_pubsub:host(), - NodeIdx :: mod_pubsub:nodeIdx()) - -> undefined | mod_pubsub:pubsubItem() -). -get_cached_item({_, ServerHost, _}, NodeId) -> - get_cached_item(ServerHost, NodeId); -get_cached_item(Host, NodeIdx) -> - case is_last_item_cache_enabled(Host) of - true -> - case mnesia:dirty_read({pubsub_last_item, NodeIdx}) of - [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] -> -% [{pubsub_last_item, NodeId, ItemId, Creation, -% Payload}] -> - #pubsub_item{itemid = {ItemId, NodeIdx}, - payload = Payload, creation = Creation, - modification = Creation}; - _ -> undefined - end; - _ -> undefined - end. - -%%%% plugin handling - -host(ServerHost) -> - case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, config), - host) - of - [{host, Host}] -> Host; - _ -> <<"pubsub.", ServerHost/binary>> - end. - -plugins(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, - config), - plugins) - of - [{plugins, []}] -> [?STDNODE]; - [{plugins, PL}] -> PL; - _ -> [?STDNODE] - end. - -select_type(ServerHost, Host, Node, Type) -> - SelectedType = case Host of - {_User, _Server, _Resource} -> - case catch - ets:lookup(gen_mod:get_module_proc(ServerHost, - config), - pep_mapping) - of - [{pep_mapping, PM}] -> - proplists:get_value(Node, PM, ?PEPNODE); - _ -> ?PEPNODE - end; - _ -> Type - end, - ConfiguredTypes = plugins(ServerHost), - case lists:member(SelectedType, ConfiguredTypes) of - true -> SelectedType; - false -> hd(ConfiguredTypes) - end. - -select_type(ServerHost, Host, Node) -> - select_type(ServerHost, Host, Node, - hd(plugins(ServerHost))). - -features() -> - [% see plugin "access-authorize", % OPTIONAL - <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree - <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep - <<"access-whitelist">>, % OPTIONAL - <<"collections">>, % RECOMMENDED - <<"config-node">>, % RECOMMENDED - <<"create-and-configure">>, % RECOMMENDED - <<"item-ids">>, % RECOMMENDED - <<"last-published">>, % RECOMMENDED - <<"member-affiliation">>, % RECOMMENDED - <<"presence-notifications">>, % OPTIONAL - <<"presence-subscribe">>, % RECOMMENDED - <<"publisher-affiliation">>, % RECOMMENDED - <<"retrieve-default">>]. - - % see plugin "retrieve-items", % RECOMMENDED - % see plugin "retrieve-subscriptions", % RECOMMENDED - %TODO "shim", % OPTIONAL - % see plugin "subscribe", % REQUIRED - % see plugin "subscription-options", % OPTIONAL - % see plugin "subscription-notifications" % OPTIONAL - -features(Type) -> - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - features() ++ - case catch Module:features() of - {'EXIT', {undef, _}} -> []; - Result -> Result - end. - -features(Host, <<>>) -> - lists:usort(lists:foldl(fun (Plugin, Acc) -> - Acc ++ features(Plugin) - end, - [], plugins(Host))); -features(Host, Node) -> - Action = fun (#pubsub_node{type = Type}) -> - {result, features(Type)} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, Features} -> - lists:usort(features() ++ Features); - _ -> features() - end. - -%% @spec (Host, Type, NodeId) -> [ljid()] -%% NodeId = pubsubNodeId() -%% @doc <p>Return list of node owners.</p> -node_owners(Host, Type, NodeId) -> - case node_action(Host, Type, get_node_affiliations, [NodeId]) of - {result, Affiliations} -> - lists:foldl( - fun({LJID, owner}, Acc) -> [LJID|Acc]; - (_, Acc) -> Acc - end, [], Affiliations); - _ -> - [] - end. -node_owners_call(Type, NodeId) -> - case node_call(Type, get_node_affiliations, [NodeId]) of - {result, Affiliations} -> - lists:foldl( - fun({LJID, owner}, Acc) -> [LJID|Acc]; - (_, Acc) -> Acc - end, [], Affiliations); - _ -> - [] - end. - -%% @doc <p>node tree plugin call.</p> -tree_call({_User, Server, _Resource}, Function, Args) -> - tree_call(Server, Function, Args); -tree_call(Host, Function, Args) -> - ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]), - Module = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> - jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, - (?STDTREE)/binary>>) - end, - catch apply(Module, Function, Args). - -tree_action(Host, Function, Args) -> - ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]), - Fun = fun () -> tree_call(Host, Function, Args) end, - case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of - {atomic, Result} -> - Result; - {aborted, Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n",[{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR} - end. - -%% @doc <p>node plugin call.</p> -node_call(Type, Function, Args) -> - ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]), - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - case apply(Module, Function, Args) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {'EXIT', {undef, Undefined}} -> - case Type of - ?STDNODE -> {error, {undef, Undefined}}; - _ -> node_call(?STDNODE, Function, Args) - end; - {'EXIT', Reason} -> {error, Reason}; - Result -> - {result, - Result} %% any other return value is forced as result - end. - -node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p", - [Host, Type, Function, Args]), - transaction(Host, fun () -> node_call(Type, Function, Args) end, - sync_dirty). - -%% @doc <p>plugin transaction handling.</p> -transaction(Host, Node, Action, Trans) -> - transaction(Host, fun () -> - case tree_call(Host, get_node, [Host, Node]) of - N when is_record(N, pubsub_node) -> - case Action(N) of - {result, Result} -> {result, {N, Result}}; - {atomic, {result, Result}} -> - {result, {N, Result}}; - Other -> Other - end; - Error -> Error - end - end, - Trans). - -transaction_on_nodes(Host, Action, Trans) -> - transaction(Host, fun () -> - {result, - lists:foldl(Action, [], - tree_call(Host, get_nodes, [Host]))} - end, - Trans). - -transaction(Host, Fun, Trans) -> - transaction_retry(Host, Fun, Trans, 2). -transaction_retry(Host, Fun, Trans, Count) -> - SqlFun = case Trans of - transaction -> sql_transaction; - _ -> sql_bloc - end, - case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {atomic, {result, Result}} -> {result, Result}; - {atomic, {error, Error}} -> {error, Error}; - {aborted, Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {'EXIT', {timeout, _} = Reason} -> - case Count of - 0 -> - ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - N -> - erlang:yield(), - transaction_retry(Host, Fun, Trans, N-1) - end; - {'EXIT', Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - Other -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [Other]), - {error, ?ERR_INTERNAL_SERVER_ERROR} - end. - -odbc_conn({_U, Host, _R})-> Host; -odbc_conn(<<$., Host/binary>>) -> Host; -odbc_conn(<<_, Host/binary>>) -> odbc_conn(Host). - -%% escape value for database storage -escape({_U, _H, _R}=JID)-> - ejabberd_odbc:escape(jlib:jid_to_string(JID)); -escape(Value)-> - ejabberd_odbc:escape(Value). - -%%%% helpers - -%% Add pubsub-specific error element -extended_error(Error, Ext) -> - extended_error(Error, Ext, - [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]). - -extended_error(Error, unsupported, Feature) -> -%% Give a uniq identifier - extended_error(Error, <<"unsupported">>, - [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}, - {<<"feature">>, Feature}]); -extended_error(#xmlel{name = Error, attrs = Attrs, - children = SubEls}, - Ext, ExtAttrs) -> - #xmlel{name = Error, attrs = Attrs, - children = - lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs, - children = []} - | SubEls])}. - --spec(uniqid/0 :: () -> mod_pubsub:itemId()). -uniqid() -> - {T1, T2, T3} = now(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -nodeAttr(Node) -> [{<<"node">>, Node}]. - -itemAttr([]) -> []; -itemAttr(ItemId) -> [{<<"id">>, ItemId}]. - -itemsEls(Items) -> - lists:map(fun (#pubsub_item{itemid = {ItemId, _}, payload = Payload}) -> - #xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload} - end, Items). - -add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, - Type) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, Type} | Attrs], children = Els}; -add_message_type(XmlEl, _Type) -> XmlEl. - -%% Place of <headers/> changed at the bottom of the stanza -%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid -%% -%% "[SHIM Headers] SHOULD be included after the event notification information -%% (i.e., as the last child of the <message/> stanza)". - -add_shim_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls). - -add_extended_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, - HeaderEls). - -add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, - HeaderName, HeaderNS, HeaderEls) -> - HeaderEl = #xmlel{name = HeaderName, - attrs = [{<<"xmlns">>, HeaderNS}], - children = HeaderEls}, - #xmlel{name = Name, attrs = Attrs, - children = lists:append(Els, [HeaderEl])}. - -%% Removed multiple <header name=Collection>Foo</header/> elements -%% Didn't seem compliant, but not sure. Confirmation required. -%% cf. http://xmpp.org/extensions/xep-0248.html#notify -%% -%% "If an item is published to a node which is also included by a collection, -%% and an entity is subscribed to that collection with a subscription type of -%% "items" (Is there a way to check that currently ?), then the notifications -%% generated by the service MUST contain additional information. The <items/> -%% element contained in the notification message MUST specify the node -%% identifier of the node that generated the notification (not the collection) -%% and the <item/> element MUST contain a SHIM header that specifies the node -%% identifier of the collection". - -collection_shim(Node) -> - [#xmlel{name = <<"header">>, - attrs = [{<<"name">>, <<"Collection">>}], - children = [{xmlcdata, Node}]}]. - -subid_shim(SubIDs) -> - [#xmlel{name = <<"header">>, - attrs = [{<<"name">>, <<"SubID">>}], - children = [{xmlcdata, SubID}]} - || SubID <- SubIDs]. - -%% The argument is a list of Jids because this function could be used -%% with the 'pubsub#replyto' (type=jid-multi) node configuration. - -extended_headers(Jids) -> - [#xmlel{name = <<"address">>, - attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}], - children = []} - || Jid <- Jids]. - -on_user_offline(_, JID, _) -> - {User, Server, Resource} = jlib:jid_tolower(JID), - case ejabberd_sm:get_user_resources(User, Server) of - [] -> purge_offline({User, Server, Resource}); - _ -> true - end. - -purge_offline({User, Server, _} = LJID) -> - Host = host(element(2, LJID)), - Plugins = plugins(Host), - Result = lists:foldl(fun (Type, {Status, Acc}) -> - case lists:member(<<"retrieve-affiliations">>, - features(Type)) - of - false -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, - <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affiliations} = - node_action(Host, Type, - get_entity_affiliations, - [Host, LJID]), - {Status, [Affiliations | Acc]} - end - end, - {ok, []}, Plugins), - case Result of - {ok, Affiliations} -> - lists:foreach(fun ({#pubsub_node{nodeid = {_, NodeId}, - options = Options, type = Type}, - Affiliation}) - when Affiliation == owner orelse - Affiliation == publisher -> - Action = fun (#pubsub_node{type = NType, - id = NodeIdx}) -> - node_call(NType, get_items, - [NodeIdx, - service_jid(Host)]) - end, - case transaction(Host, NodeId, Action, - sync_dirty) - of - {result, {_, []}} -> true; - {result, {_, Items}} -> - Features = features(Type), - case {lists:member(<<"retract-items">>, - Features), - lists:member(<<"persistent-items">>, - Features), - get_option(Options, persist_items), - get_option(Options, purge_offline)} - of - {true, true, true, true} -> - ForceNotify = get_option(Options, - notify_retract), - lists:foreach(fun - (#pubsub_item{itemid - = - {ItemId, - _}, - modification - = - {_, - Modification}}) -> - case - Modification - of - {User, Server, - _} -> - delete_item(Host, - NodeId, - LJID, - ItemId, - ForceNotify); - _ -> true - end; - (_) -> true - end, - Items); - _ -> true - end; - Error -> Error - end; - (_) -> true - end, - lists:usort(lists:flatten(Affiliations))); - {Error, _} -> ?DEBUG("on_user_offline ~p", [Error]) - end. diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template deleted file mode 100644 index b31d8a8e2..000000000 --- a/src/mod_pubsub/node.template +++ /dev/null @@ -1,194 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(__TO_BE_DEFINED__). --author(__TO_BE_DEFINED__). - --include("pubsub.hrl"). --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% Note on function definition -%% included is all defined plugin function -%% it's possible not to define some function at all -%% in that case, warning will be generated at compilation -%% and function call will fail, -%% then mod_pubsub will call function from node_hometree -%% (this makes code cleaner, but execution a little bit longer) - -%% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3 - ]). - - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> - ["create-nodes", - "delete-nodes", - "delete-items", - "instant-nodes", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications" - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> - node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> - node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl deleted file mode 100644 index 23269b1eb..000000000 --- a/src/mod_pubsub/node_buddy.erl +++ /dev/null @@ -1,184 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_buddy). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% Note on function definition -%% included is all defined plugin function -%% it's possible not to define some function at all -%% in that case, warning will be generated at compilation -%% and function call will fail, -%% then mod_pubsub will call function from node_hometree -%% (this makes code cleaner, but execution a little bit longer) - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, presence}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, <<"delete-nodes">>, - <<"delete-items">>, <<"instant-nodes">>, <<"item-ids">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, - Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl deleted file mode 100644 index 10849b36d..000000000 --- a/src/mod_pubsub/node_club.erl +++ /dev/null @@ -1,184 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_club). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% Note on function definition -%% included is all defined plugin function -%% it's possible not to define some function at all -%% in that case, warning will be generated at compilation -%% and function call will fail, -%% then mod_pubsub will call function from node_hometree -%% (this makes code cleaner, but execution a little bit longer) - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, authorize}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, <<"delete-nodes">>, - <<"delete-items">>, <<"instant-nodes">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, - Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_dag.erl b/src/mod_pubsub/node_dag.erl deleted file mode 100644 index 9a36a4c4a..000000000 --- a/src/mod_pubsub/node_dag.erl +++ /dev/null @@ -1,163 +0,0 @@ -%%% ==================================================================== -%%% ``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. -%%% -%%% @author Brian Cully <bjc@kublai.com> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_dag). - --author('bjc@kublai.com'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{node_type, leaf} | node_hometree:options()]. - -features() -> - [<<"multi-collection">> | node_hometree:features()]. - -create_node_permission(_Host, _ServerHost, _Node, - _ParentNode, _Owner, _Access) -> - {result, true}. - -create_node(NodeID, Owner) -> - node_hometree:create_node(NodeID, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeID, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeID, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeID, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeID, Sender, - Subscriber, SubID). - -publish_item(NodeID, Publisher, Model, MaxItems, ItemID, - Payload) -> - case nodetree_dag:get_node(NodeID) of - #pubsub_node{options = Options} -> - case find_opt(node_type, Options) of - collection -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"publish">>)}; - _ -> - node_hometree:publish_item(NodeID, Publisher, Model, - MaxItems, ItemID, Payload) - end; - Err -> Err - end. - -find_opt(_, []) -> false; -find_opt(Option, [{Option, Value} | _]) -> Value; -find_opt(Option, [_ | T]) -> find_opt(Option, T). - -remove_extra_items(NodeID, MaxItems, ItemIDs) -> - node_hometree:remove_extra_items(NodeID, MaxItems, - ItemIDs). - -delete_item(NodeID, Publisher, PublishModel, ItemID) -> - node_hometree:delete_item(NodeID, Publisher, - PublishModel, ItemID). - -purge_node(NodeID, Owner) -> - node_hometree:purge_node(NodeID, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeID) -> - node_hometree:get_node_affiliations(NodeID). - -get_affiliation(NodeID, Owner) -> - node_hometree:get_affiliation(NodeID, Owner). - -set_affiliation(NodeID, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeID, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeID) -> - node_hometree:get_node_subscriptions(NodeID). - -get_subscriptions(NodeID, Owner) -> - node_hometree:get_subscriptions(NodeID, Owner). - -set_subscriptions(NodeID, Owner, Subscription, SubID) -> - node_hometree:set_subscriptions(NodeID, Owner, - Subscription, SubID). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeID) -> node_hometree:get_states(NodeID). - -get_state(NodeID, JID) -> - node_hometree:get_state(NodeID, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeID, From) -> - node_hometree:get_items(NodeID, From). - -get_items(NodeID, JID, AccessModel, - PresenceSubscription, RosterGroup, SubID) -> - node_hometree:get_items(NodeID, JID, AccessModel, - PresenceSubscription, RosterGroup, SubID). - -get_item(NodeID, ItemID) -> - node_hometree:get_item(NodeID, ItemID). - -get_item(NodeID, ItemID, JID, AccessModel, - PresenceSubscription, RosterGroup, SubID) -> - node_hometree:get_item(NodeID, ItemID, JID, AccessModel, - PresenceSubscription, RosterGroup, SubID). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, ID) -> - node_hometree:get_item_name(Host, Node, ID). - -node_to_path(Node) -> node_hometree:node_to_path(Node). - -path_to_node(Path) -> node_hometree:path_to_node(Path). diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl deleted file mode 100644 index 9b72af7e7..000000000 --- a/src/mod_pubsub/node_dispatch.erl +++ /dev/null @@ -1,176 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_dispatch). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose -%%% goal is to republished each published item to all its children.</p> -%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to -%%% its children.</p> -%%% This module can not work with virtual nodetree - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, open}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, <<"delete-nodes">>, - <<"instant-nodes">>, <<"outcast-affiliation">>, - <<"persistent-items">>, <<"publish">>, - <<"retrieve-items">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, - Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(_NodeId, _Sender, _Subscriber, - _AccessModel, _SendLast, _PresenceSubscription, - _RosterGroup, _Options) -> - {error, ?ERR_FORBIDDEN}. - -unsubscribe_node(_NodeId, _Sender, _Subscriber, - _SubID) -> - {error, ?ERR_FORBIDDEN}. - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - lists:foreach(fun (SubNode) -> - node_hometree:publish_item(SubNode#pubsub_node.id, - Publisher, Model, MaxItems, - ItemId, Payload) - end, - nodetree_tree:get_subnodes(NodeId, Publisher, - Publisher)). - -remove_extra_items(_NodeId, _MaxItems, ItemIds) -> - {result, {ItemIds, []}}. - -delete_item(_NodeId, _Publisher, _PublishModel, - _ItemId) -> - {error, ?ERR_ITEM_NOT_FOUND}. - -purge_node(_NodeId, _Owner) -> {error, ?ERR_FORBIDDEN}. - -get_entity_affiliations(_Host, _Owner) -> {result, []}. - -get_node_affiliations(_NodeId) -> {result, []}. - -get_affiliation(_NodeId, _Owner) -> {result, []}. - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(_Host, _Owner) -> {result, []}. - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(_NodeId, _Owner) -> {result, []}. - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl deleted file mode 100644 index 836858520..000000000 --- a/src/mod_pubsub/node_flat.erl +++ /dev/null @@ -1,186 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_flat). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, open}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> node_hometree:features(). - -%% use same code as node_hometree, but do not limite node to -%% the home/localhost/user/... hierarchy -%% any node is allowed -create_node_permission(Host, ServerHost, _Node, - _ParentNode, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - acl:match_rule(ServerHost, Access, LOwner) =:= allow - end, - {result, Allowed}. - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> [(Node)]. - -path_to_node(Path) -> - case Path of - % default slot - [Node] -> iolist_to_binary(Node); - % handle old possible entries, used when migrating database content to new format - [Node | _] when is_binary(Node) -> - iolist_to_binary(str:join([<<"">> | Path], <<"/">>)); - % default case (used by PEP for example) - _ -> iolist_to_binary(Path) - end. diff --git a/src/mod_pubsub/node_flat_odbc.erl b/src/mod_pubsub/node_flat_odbc.erl deleted file mode 100644 index 4b686f65e..000000000 --- a/src/mod_pubsub/node_flat_odbc.erl +++ /dev/null @@ -1,214 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_flat_odbc). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, - get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, get_subscriptions/2, - set_subscriptions/4, get_pending_nodes/2, get_states/1, - get_state/2, set_state/1, get_items/7, get_items/6, - get_items/3, get_items/2, get_item/7, get_item/2, - set_item/1, get_item_name/3, get_last_items/3, - node_to_path/1, path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree_odbc:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree_odbc:terminate(Host, ServerHost). - -options() -> - [{node_type, flat}, - {deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, false}, {odbc, true}, - {rsm, true}]. - -features() -> node_hometree_odbc:features(). - -%% use same code as node_hometree_odbc, but do not limite node to -%% the home/localhost/user/... hierarchy -%% any node is allowed -create_node_permission(Host, ServerHost, _Node, - _ParentNode, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - acl:match_rule(ServerHost, Access, LOwner) =:= allow - end, - {result, Allowed}. - -create_node(NodeId, Owner) -> - node_hometree_odbc:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree_odbc:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree_odbc:subscribe_node(NodeId, Sender, - Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, - Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree_odbc:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree_odbc:publish_item(NodeId, Publisher, - Model, MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree_odbc:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree_odbc:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree_odbc:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree_odbc:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree_odbc:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree_odbc:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree_odbc:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree_odbc:get_entity_subscriptions(Host, - Owner). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, - Owner). - -get_node_subscriptions(NodeId) -> - node_hometree_odbc:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree_odbc:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree_odbc:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree_odbc:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> - node_hometree_odbc:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree_odbc:get_state(NodeId, JID). - -set_state(State) -> node_hometree_odbc:set_state(State). - -get_items(NodeId, From) -> - node_hometree_odbc:get_items(NodeId, From). - -get_items(NodeId, From, RSM) -> - node_hometree_odbc:get_items(NodeId, From, RSM). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, none). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree_odbc:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_item(NodeId, ItemId) -> - node_hometree_odbc:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree_odbc:get_item(NodeId, ItemId, JID, - AccessModel, PresenceSubscription, RosterGroup, - SubId). - -set_item(Item) -> node_hometree_odbc:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree_odbc:get_item_name(Host, Node, Id). - -get_last_items(NodeId, From, Count) -> - node_hometree_odbc:get_last_items(NodeId, From, Count). - -node_to_path(Node) -> [(Node)]. - -path_to_node(Path) -> - case Path of - % default slot - [Node] -> iolist_to_binary(Node); - % handle old possible entries, used when migrating database content to new format - [Node | _] when is_binary(Node) -> - iolist_to_binary(str:join([<<"">> | Path], <<"/">>)); - % default case (used by PEP for example) - _ -> iolist_to_binary(Path) - end. diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl deleted file mode 100644 index 57507e67b..000000000 --- a/src/mod_pubsub/node_hometree.erl +++ /dev/null @@ -1,1329 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @todo The item table should be handled by the plugin, but plugin that do -%%% not want to manage it should be able to use the default behaviour. -%%% @todo Plugin modules should be able to register to receive presence update -%%% send to pubsub. - -%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin. -%%% <p>It is used as a default for all unknown PubSub node type. It can serve -%%% as a developer basis and reference to build its own custom pubsub node -%%% types.</p> -%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p> -%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin -%%% development is still a work in progress. However, the system is already -%%% useable and useful as is. Please, send us comments, feedback and -%%% improvements.</p> - --module(node_hometree). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -%% ================ -%% API definition -%% ================ - -%% @spec (Host, ServerHost, Options) -> ok -%% Host = string() -%% ServerHost = string() -%% Options = [{atom(), term()}] -%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must -%% implement this function. It can return anything.</p> -%% <p>This function is mainly used to trigger the setup task necessary for the -%% plugin. It can be used for example by the developer to create the specific -%% module database schema if it does not exists yet.</p> -init(_Host, _ServerHost, _Options) -> - pubsub_subscription:init(), - mnesia:create_table(pubsub_state, - [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_state)}]), - mnesia:create_table(pubsub_item, - [{disc_only_copies, [node()]}, - {attributes, record_info(fields, pubsub_item)}]), - ItemsFields = record_info(fields, pubsub_item), - case mnesia:table_info(pubsub_item, attributes) of - ItemsFields -> ok; - _ -> - mnesia:transform_table(pubsub_item, ignore, ItemsFields) - end, - ok. - -%% @spec (Host, ServerHost) -> ok -%% Host = string() -%% ServerHost = string() -%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must -%% implement this function. It can return anything.</p> -terminate(_Host, _ServerHost) -> ok. - --spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). - -%% @spec () -> Options -%% Options = [mod_pubsub:nodeOption()] -%% @doc Returns the default pubsub node options. -%% <p>Example of function return value:</p> -%% ``` -%% [{deliver_payloads, true}, -%% {notify_config, false}, -%% {notify_delete, false}, -%% {notify_retract, true}, -%% {persist_items, true}, -%% {max_items, 10}, -%% {subscribe, true}, -%% {access_model, open}, -%% {publish_model, publishers}, -%% {max_payload_size, 100000}, -%% {send_last_published_item, never}, -%% {presence_based_delivery, false}]''' -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, open}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -%% @spec () -> Features -%% Features = [string()] -%% @doc Returns the node features --spec(features/0 :: () -> Features::[binary(),...]). -features() -> -%% @spec (Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> {result, Allowed} -%% Host = mod_pubsub:hostPubsub() -%% ServerHost = string() -%% NodeId = mod_pubsub:nodeId() -%% ParentNodeId = mod_pubsub:nodeId() -%% Owner = mod_pubsub:jid() -%% Access = all | atom() -%% Allowed = boolean() -%% @doc Checks if the current user has the permission to create the requested node -%% <p>In {@link node_default}, the permission is decided by the place in the -%% hierarchy where the user is creating the node. The access parameter is also -%% checked in the default module. This parameter depends on the value of the -%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p> -%% <p>This function also check that node can be created a a children of its -%% parent node</p> -%% <p>PubSub plugins can redefine the PubSub node creation rights as they -%% which. They can simply delegate this check to the {@link node_default} -%% module by implementing this function like this: -%% ```check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> -%% node_default:check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access).'''</p> - [<<"create-nodes">>, <<"auto-create">>, - <<"access-authorize">>, <<"delete-nodes">>, - <<"delete-items">>, <<"get-pending">>, - <<"instant-nodes">>, <<"manage-subscriptions">>, - <<"modify-affiliations">>, <<"multi-subscribe">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>, - <<"subscription-options">>]. - --spec(create_node_permission/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - NodeId :: mod_pubsub:nodeId(), - _ParentNodeId :: mod_pubsub:nodeId(), - Owner :: jid(), - Access :: atom()) - -> {result, boolean()} -). - -create_node_permission(Host, ServerHost, NodeId, _ParentNodeId, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - {User, Server, _Resource} = LOwner, - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case node_to_path(NodeId) of - [<<"home">>, Server, User | _] -> true; - _ -> false - end; - _ -> false - end - end, - {result, Allowed}. - -%% @spec (NodeIdx, Owner) -> {result, {default, broadcast}} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Owner = mod_pubsub:jid() -%% @doc <p></p> --spec(create_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} -). - -create_node(NodeIdx, Owner) -> - OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}), - {result, {default, broadcast}}. - -%% @spec (Nodes) -> {result, {default, broadcast, Reply}} -%% Nodes = [mod_pubsub:pubsubNode()] -%% Reply = [{mod_pubsub:pubsubNode(), -%% [{mod_pubsub:ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}] -%% @doc <p>purge items of deleted nodes after effective deletion.</p> --spec(delete_node/1 :: -( - Nodes :: [mod_pubsub:pubsubNode(),...]) - -> {result, - {default, broadcast, - [{mod_pubsub:pubsubNode(), - [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...] - } - } -). -delete_node(Nodes) -> - Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> - lists:map(fun (S) -> {J, S} end, Ss) - end, - Reply = lists:map(fun (#pubsub_node{id = NodeIdx} = PubsubNode) -> - {result, States} = get_states(NodeIdx), - lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) -> - del_items(NodeIdx, Items), - del_state(NodeIdx, LJID) - end, States), - {PubsubNode, lists:flatmap(Tr, States)} - end, Nodes), - {result, {default, broadcast, Reply}}. - -%% @spec (NodeIdx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> {error, Reason} | {result, Result} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Sender = mod_pubsub:jid() -%% Subscriber = mod_pubsub:jid() -%% AccessModel = mod_pubsub:accessModel() -%% SendLast = atom() -%% PresenceSubscription = boolean() -%% RosterGroup = boolean() -%% Options = [mod_pubsub:nodeOption()] -%% Reason = mod_pubsub:stanzaError() -%% Result = {result, {default, subscribed, mod_pubsub:subId()}} -%% | {result, {default, subscribed, mod_pubsub:subId(), send_last}} -%% | {result, {default, pending, mod_pubsub:subId()}} -%% -%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> -%% <p>The mechanism works as follow: -%% <ul> -%% <li>The main PubSub module prepares the subscription and passes the -%% result of the preparation as a record.</li> -%% <li>This function gets the prepared record and several other parameters and -%% can decide to:<ul> -%% <li>reject the subscription;</li> -%% <li>allow it as is, letting the main module perform the database -%% persistance;</li> -%% <li>allow it, modifying the record. The main module will store the -%% modified record;</li> -%% <li>allow it, but perform the needed persistance operations.</li></ul> -%% </li></ul></p> -%% <p>The selected behaviour depends on the return parameter: -%% <ul> -%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No -%% subscription will actually be performed.</li> -%% <li><tt>true</tt>: Subscribe operation is allowed, based on the -%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this -%% parameter contains an error, no subscription will be performed.</li> -%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but -%% the {@link mod_pubsub:pubsubState()} record returned replaces the value -%% passed in parameter <tt>SubscribeResult</tt>.</li> -%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the -%% {@link mod_pubsub:pubsubState()} will be considered as already stored and -%% no further persistance operation will be performed. This case is used, -%% when the plugin module is doing the persistance by itself or when it want -%% to completly disable persistance.</li></ul> -%% </p> -%% <p>In the default plugin module, the record is unchanged.</p> --spec(subscribe_node/8 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - AccessModel :: mod_pubsub:accessModel(), - SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - Options :: mod_pubsub:subOptions()) - -> {result, {default, subscribed, mod_pubsub:subId()}} - | {result, {default, subscribed, mod_pubsub:subId(), send_last}} - | {result, {default, pending, mod_pubsub:subId()}} - %%% - | {error, xmlel()} -). -subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - SubKey = jlib:jid_tolower(Subscriber), - GenKey = jlib:jid_remove_resource(SubKey), - Authorized = - jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == - GenKey, - GenState = get_state(NodeIdx, GenKey), - SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, - Affiliation = GenState#pubsub_state.affiliation, - Subscriptions = SubState#pubsub_state.subscriptions, - Whitelisted = lists:member(Affiliation, - [member, publisher, owner]), - PendingSubscription = lists:any(fun ({pending, _}) -> - true; - (_) -> false - end, - Subscriptions), - if not Authorized -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; - Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; - PendingSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"pending-subscription">>)}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - %%ForbiddenAnonymous -> - %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; - true -> - SubId = pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options), - NewSub = case AccessModel of - authorize -> pending; - _ -> subscribed - end, - set_state(SubState#pubsub_state{subscriptions = - [{NewSub, SubId} | Subscriptions]}), - case {NewSub, SendLast} of - {subscribed, never} -> - {result, {default, subscribed, SubId}}; - {subscribed, _} -> - {result, {default, subscribed, SubId, send_last}}; - {_, _} -> {result, {default, pending, SubId}} - end - end. - -%% @spec (NodeIdx, Sender, Subscriber, SubId) -> {error, Reason} | {result, default} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Sender = mod_pubsub:jid() -%% Subscriber = mod_pubsub:jid() -%% SubId = mod_pubsub:subId() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> --spec(unsubscribe_node/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - SubId :: subId()) - -> {result, default} - % - | {error, xmlel()} -). - -unsubscribe_node(NodeIdx, Sender, Subscriber, SubId) -> - SubKey = jlib:jid_tolower(Subscriber), - GenKey = jlib:jid_remove_resource(SubKey), - Authorized = - jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == - GenKey, - GenState = get_state(NodeIdx, GenKey), - SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, - Subscriptions = lists:filter(fun ({_Sub, _SubId}) -> - true; - (_SubId) -> false - end, - SubState#pubsub_state.subscriptions), - SubIdExists = case SubId of - <<>> -> false; - Binary when is_binary(Binary) -> true; - _ -> false - end, - if - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> {error, ?ERR_FORBIDDEN}; - %% Entity did not specify SubId - %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - %% Requesting entity is not a subscriber - Subscriptions == [] -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), - <<"not-subscribed">>)}; - %% Subid supplied, so use that. - SubIdExists -> - Sub = first_in_list(fun (S) -> - case S of - {_Sub, SubId} -> true; - _ -> false - end - end, - SubState#pubsub_state.subscriptions), - case Sub of - {value, S} -> - delete_subscriptions(SubKey, NodeIdx, [S], SubState), - {result, default}; - false -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), - <<"not-subscribed">>)} - end; - %% Asking to remove all subscriptions to the given node - SubId == all -> - delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), - {result, default}; - %% No subid supplied, but there's only one matching subscription - length(Subscriptions) == 1 -> - delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), - {result, default}; - %% No subid and more than one possible subscription match. - true -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} - end. - --spec(delete_subscriptions/4 :: -( - SubKey :: ljid(), - NodeIdx :: mod_pubsub:nodeIdx(), - Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}], - SubState :: mod_pubsub:pubsubState()) - -> ok -). -delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) -> - NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) -> - pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId), - Acc -- [{Subscription, SubId}] - end, SubState#pubsub_state.subscriptions, Subscriptions), - case {SubState#pubsub_state.affiliation, NewSubs} of - {none, []} -> del_state(NodeIdx, SubKey); - _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs}) - end. - -%% @spec (NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> -%% {result, {default, broadcast, ItemIds}} | {error, Reason} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Publisher = mod_pubsub:jid() -%% PublishModel = atom() -%% MaxItems = integer() -%% ItemId = mod_pubsub:itemId() -%% Payload = mod_pubsub:payload() -%% ItemIds = [mod_pubsub:itemId()] | [] -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Publishes the item passed as parameter.</p> -%% <p>The mechanism works as follow: -%% <ul> -%% <li>The main PubSub module prepares the item to publish and passes the -%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li> -%% <li>This function gets the prepared record and several other parameters and can decide to:<ul> -%% <li>reject the publication;</li> -%% <li>allow the publication as is, letting the main module perform the database persistance;</li> -%% <li>allow the publication, modifying the record. The main module will store the modified record;</li> -%% <li>allow it, but perform the needed persistance operations.</li></ul> -%% </li></ul></p> -%% <p>The selected behaviour depends on the return parameter: -%% <ul> -%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No -%% publication is actually performed.</li> -%% <li><tt>true</tt>: Publication operation is allowed, based on the -%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt> -%% parameter contains an error, no subscription will actually be -%% performed.</li> -%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the -%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed -%% in parameter <tt>Item</tt>. The persistance will be performed by the main -%% module.</li> -%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the -%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and -%% no further persistance operation will be performed. This case is used, -%% when the plugin module is doing the persistance by itself or when it want -%% to completly disable persistance.</li></ul> -%% </p> -%% <p>In the default plugin module, the record is unchanged.</p> --spec(publish_item/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - Max_Items :: non_neg_integer(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, {default, broadcast, [mod_pubsub:itemId()]}} - %%% - | {error, xmlel()} -). - -publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> - SubKey = jlib:jid_tolower(Publisher), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, - Affiliation = GenState#pubsub_state.affiliation, - Subscribed = case PublishModel of - subscribers -> - is_subscribed(SubState#pubsub_state.subscriptions); - _ -> undefined - end, - if not - ((PublishModel == open) or - (PublishModel == publishers) and - ((Affiliation == owner) or (Affiliation == publisher)) - or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; - true -> - if MaxItems > 0 -> - Now = now(), - PubId = {Now, SubKey}, - Item = case get_item(NodeIdx, ItemId) of - {result, OldItem} -> - OldItem#pubsub_item{modification = PubId, - payload = Payload}; - _ -> - #pubsub_item{itemid = {ItemId, NodeIdx}, - creation = {Now, GenKey}, - modification = PubId, - payload = Payload} - end, - Items = [ItemId | GenState#pubsub_state.items -- - [ItemId]], - {result, {NI, OI}} = remove_extra_items(NodeIdx, - MaxItems, Items), - set_item(Item), - set_state(GenState#pubsub_state{items = NI}), - {result, {default, broadcast, OI}}; - true -> {result, {default, broadcast, []}} - end - end. - -%% @spec (NodeIdx, MaxItems, ItemIds) -> {result, {NewItemIds,OldItemIds}} -%% NodeIdx = mod_pubsub:nodeIdx() -%% MaxItems = integer() | unlimited -%% ItemIds = [mod_pubsub:itemId()] -%% NewItemIds = [mod_pubsub:itemId()] -%% OldItemIds = [mod_pubsub:itemId()] | [] -%% @doc <p>This function is used to remove extra items, most notably when the -%% maximum number of items has been reached.</p> -%% <p>This function is used internally by the core PubSub module, as no -%% permission check is performed.</p> -%% <p>In the default plugin module, the oldest items are removed, but other -%% rules can be used.</p> -%% <p>If another PubSub plugin wants to delegate the item removal (and if the -%% plugin is using the default pubsub storage), it can implements this function like this: -%% ```remove_extra_items(NodeIdx, MaxItems, ItemIds) -> -%% node_default:remove_extra_items(NodeIdx, MaxItems, ItemIds).'''</p> --spec(remove_extra_items/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Max_Items :: unlimited | non_neg_integer(), - ItemIds :: [mod_pubsub:itemId()]) - -> {result, - {NewItems::[mod_pubsub:itemId()], - OldItems::[mod_pubsub:itemId()]} - } -). -remove_extra_items(_NodeIdx, unlimited, ItemIds) -> - {result, {ItemIds, []}}; -remove_extra_items(NodeIdx, MaxItems, ItemIds) -> - NewItems = lists:sublist(ItemIds, MaxItems), - OldItems = lists:nthtail(length(NewItems), ItemIds), - del_items(NodeIdx, OldItems), - {result, {NewItems, OldItems}}. - -%% @spec (NodeIdx, Publisher, PublishModel, ItemId) -> -%% {result, {default, broadcast}} | {error, Reason} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Publisher = mod_pubsub:jid() -%% PublishModel = atom() -%% ItemId = mod_pubsub:itemId() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Triggers item deletion.</p> -%% <p>Default plugin: The user performing the deletion must be the node owner -%% or a publisher, or PublishModel being open.</p> --spec(delete_item/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - ItemId :: <<>> | mod_pubsub:itemId()) - -> {result, {default, broadcast}} - %%% - | {error, xmlel()} -). -delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> - SubKey = jlib:jid_tolower(Publisher), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - #pubsub_state{affiliation = Affiliation, items = Items} = GenState, - Allowed = Affiliation == publisher orelse - Affiliation == owner orelse - PublishModel == open orelse - case get_item(NodeIdx, ItemId) of - {result, #pubsub_item{creation = {_, GenKey}}} -> true; - _ -> false - end, - if not Allowed -> {error, ?ERR_FORBIDDEN}; - true -> - case lists:member(ItemId, Items) of - true -> - del_item(NodeIdx, ItemId), - set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}), - {result, {default, broadcast}}; - false -> - case Affiliation of - owner -> - {result, States} = get_states(NodeIdx), - lists:foldl(fun (#pubsub_state{items = PI} = S, Res) -> - case lists:member(ItemId, PI) of - true -> - del_item(NodeIdx, ItemId), - set_state(S#pubsub_state{items - = lists:delete(ItemId, PI)}), - {result, {default, broadcast}}; - false -> Res - end; - (_, Res) -> Res - end, - {error, ?ERR_ITEM_NOT_FOUND}, States); - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end - end - end. - -%% @spec (NodeIdx, Owner) -> {error, Reason} | {result, {default, broadcast}} -%% NodeIdx = mod_pubsub:nodeIdx() -%% Owner = mod_pubsub:jid() -%% Reason = mod_pubsub:stanzaError() --spec(purge_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} - | {error, xmlel()} -). - -purge_node(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - case GenState of - #pubsub_state{affiliation = owner} -> - {result, States} = get_states(NodeIdx), - lists:foreach(fun (#pubsub_state{items = []}) -> ok; - (#pubsub_state{items = Items} = S) -> - del_items(NodeIdx, Items), - set_state(S#pubsub_state{items = []}) - end, - States), - {result, {default, broadcast}}; - _ -> {error, ?ERR_FORBIDDEN} - end. - -%% @spec (Host, Owner) -> {result, Reply} -%% Host = mod_pubsub:hostPubsub() -%% Owner = mod_pubsub:jid() -%% Reply = [] | [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}] -%% @doc <p>Return the current affiliations for the given user</p> -%% <p>The default module reads affiliations in the main Mnesia -%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same -%% table, it should return an empty list, as the affiliation will be read by -%% the default PubSub module. Otherwise, it should return its own affiliation, -%% that will be added to the affiliation stored in the main -%% <tt>pubsub_state</tt> table.</p> --spec(get_entity_affiliations/2 :: -( - Host :: mod_pubsub:host(), - Owner :: jid()) - -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} -). - -get_entity_affiliations(Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}), - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, - Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {Host, _}} = Node -> - [{Node, A} | Acc]; - _ -> Acc - end - end, - [], States), - {result, Reply}. - --spec(get_node_affiliations/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [{ljid(), mod_pubsub:affiliation()}]} -). - -get_node_affiliations(NodeIdx) -> - {result, States} = get_states(NodeIdx), - Tr = fun (#pubsub_state{stateid = {J, _}, - affiliation = A}) -> - {J, A} - end, - {result, lists:map(Tr, States)}. - --spec(get_affiliation/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, mod_pubsub:affiliation()} -). - -get_affiliation(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - #pubsub_state{affiliation = Affiliation} = get_state(NodeIdx, GenKey), - {result, Affiliation}. - --spec(set_affiliation/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid(), - Affiliation :: mod_pubsub:affiliation()) - -> ok -). -set_affiliation(NodeIdx, Owner, Affiliation) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - case {Affiliation, GenState#pubsub_state.subscriptions} of - {none, []} -> del_state(NodeIdx, GenKey); - _ -> set_state(GenState#pubsub_state{affiliation = Affiliation}) - end. - -%% @spec (Host, Owner) -> -%% {'result', [] -%% | [{Node, Subscription, SubId, Entity}] -%% | [{Node, Subscription, Entity}]} -%% Host = mod_pubsub:hostPubsub() -%% Owner = mod_pubsub:jid() -%% Node = mod_pubsub:pubsubNode() -%% Subscription = mod_pubsub:subscription() -%% SubId = mod_pubsub:subId() -%% Entity = mod_pubsub:ljid() -%% @doc <p>Return the current subscriptions for the given user</p> -%% <p>The default module reads subscriptions in the main Mnesia -%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same -%% table, it should return an empty list, as the affiliation will be read by -%% the default PubSub module. Otherwise, it should return its own affiliation, -%% that will be added to the affiliation stored in the main -%% <tt>pubsub_state</tt> table.</p> --spec(get_entity_subscriptions/2 :: -( - Host :: mod_pubsub:host(), - Owner :: jid()) - -> {result, - [{mod_pubsub:pubsubNode(), - mod_pubsub:subscription(), - mod_pubsub:subId(), - ljid()}] - } -). - -get_entity_subscriptions(Host, Owner) -> - {U, D, _} = SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - States = case SubKey of - GenKey -> - mnesia:match_object(#pubsub_state{stateid = - {{U, D, '_'}, '_'}, - _ = '_'}); - _ -> - mnesia:match_object(#pubsub_state{stateid = - {GenKey, '_'}, - _ = '_'}) - ++ - mnesia:match_object(#pubsub_state{stateid = - {SubKey, '_'}, - _ = '_'}) - end, - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, - Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {Host, _}} = Node -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, J} | Acc2] - end, - Acc, Ss); - _ -> Acc - end - end, - [], States), - {result, Reply}. - --spec(get_node_subscriptions/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, - [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] | - [{ljid(), none},...] - } -). -get_node_subscriptions(NodeIdx) -> - {result, States} = get_states(NodeIdx), - Tr = fun (#pubsub_state{stateid = {J, _}, - subscriptions = Subscriptions}) -> - case Subscriptions of - [_ | _] -> - lists:foldl(fun ({S, SubId}, Acc) -> - [{J, S, SubId} | Acc] - end, - [], Subscriptions); - [] -> []; - _ -> [{J, none}] - end - end, - {result, lists:flatmap(Tr, States)}. - --spec(get_subscriptions/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid()) - -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} -). -get_subscriptions(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - SubState = get_state(NodeIdx, SubKey), - {result, SubState#pubsub_state.subscriptions}. - --spec(set_subscriptions/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid(), - Subscription :: mod_pubsub:subscription(), - SubId :: mod_pubsub:subId()) - -> ok - %%% - | {error, xmlel()} -). - -set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> - SubKey = jlib:jid_tolower(Owner), - SubState = get_state(NodeIdx, SubKey), - case {SubId, SubState#pubsub_state.subscriptions} of - {_, []} -> - case Subscription of - none -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), - <<"not-subscribed">>)}; - _ -> - new_subscription(NodeIdx, Owner, Subscription, SubState) - end; - {<<>>, [{_, SID}]} -> - case Subscription of - none -> unsub_with_subid(NodeIdx, SID, SubState); - _ -> replace_subscription({Subscription, SID}, SubState) - end; - {<<>>, [_ | _]} -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), - <<"subid-required">>)}; - _ -> - case Subscription of - none -> unsub_with_subid(NodeIdx, SubId, SubState); - _ -> - replace_subscription({Subscription, SubId}, SubState) - end - end. - -replace_subscription(NewSub, SubState) -> - NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), - set_state(SubState#pubsub_state{subscriptions = NewSubs}). - -replace_subscription(_, [], Acc) -> Acc; -replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) -> - replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]). - -new_subscription(NodeId, Owner, Subscription, SubState) -> - SubId = pubsub_subscription:add_subscription(Owner, NodeId, []), - Subscriptions = SubState#pubsub_state.subscriptions, - set_state(SubState#pubsub_state{subscriptions = - [{Subscription, SubId} | Subscriptions]}), - {Subscription, SubId}. - --spec(unsub_with_subid/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - SubId :: mod_pubsub:subId(), - SubState :: mod_pubsub:pubsubState()) - -> ok -). -unsub_with_subid(NodeIdx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) -> - pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, - NodeIdx, SubId), - NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID - end, - SubState#pubsub_state.subscriptions), - case {NewSubs, SubState#pubsub_state.affiliation} of - {[], none} -> - del_state(NodeIdx, Entity); - _ -> - set_state(SubState#pubsub_state{subscriptions = NewSubs}) - end. - -%% TODO : doc -%% @spec (Host, Owner) -> {result, Reply} | {error, Reason} -%% Host = mod_pubsub:hostPubsub() -%% Owner = mod_pubsub:jid() -%% Reply = [] | [mod_pubsub:nodeId()] -%% @doc <p>Returns a list of Owner's nodes on Host with pending -%% subscriptions.</p> --spec(get_pending_nodes/2 :: -( - Host :: mod_pubsub:host(), - Owner :: jid()) - -> {result, [mod_pubsub:nodeId()]} -). - -get_pending_nodes(Host, Owner) -> - GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)), - States = mnesia:match_object(#pubsub_state{stateid = - {GenKey, '_'}, - affiliation = owner, _ = '_'}), - NodeIDs = [ID - || #pubsub_state{stateid = {_, ID}} <- States], - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, NID}} = S, - Acc) -> - case lists:member(NID, NodeIDs) of - true -> - case get_nodes_helper(NodeTree, S) of - {value, Node} -> [Node | Acc]; - false -> Acc - end; - false -> Acc - end - end, - [], pubsub_state), - {result, Reply}. - --spec(get_nodes_helper/2 :: -( - NodeTree :: module(), - Pubsub_State :: mod_pubsub:pubsubState()) - -> {value, NodeId::mod_pubsub:nodeId()} - | false - -). -get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> - HasPending = fun ({pending, _}) -> true; - (pending) -> true; - (_) -> false - end, - case lists:any(HasPending, Subs) of - true -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {_, Node}} -> {value, Node}; - _ -> false - end; - false -> false - end. - -%% @spec (NodeIdx) -> {result, States} -%% NodeIdx = mod_pubsub:nodeIdx() -%% States = [] | [mod_pubsub:pubsubState()] -%% @doc Returns the list of stored states for a given node. -%% <p>For the default PubSub module, states are stored in Mnesia database.</p> -%% <p>We can consider that the pubsub_state table have been created by the main -%% mod_pubsub module.</p> -%% <p>PubSub plugins can store the states where they wants (for example in a -%% relational database).</p> -%% <p>If a PubSub plugin wants to delegate the states storage to the default node, -%% they can implement this function like this: -%% ```get_states(NodeIdx) -> -%% node_default:get_states(NodeIdx).'''</p> --spec(get_states/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [mod_pubsub:pubsubState()]} -). - -get_states(NodeIdx) -> - States = case catch mnesia:match_object( - #pubsub_state{stateid = {'_', NodeIdx}, _ = '_'}) of - List when is_list(List) -> List; - _ -> [] - end, - {result, States}. - -%% @spec (NodeIdx, JID) -> State -%% NodeIdx = mod_pubsub:nodeIdx() -%% JID = mod_pubsub:jid() -%% State = mod_pubsub:pubsubState() -%% @doc <p>Returns a state (one state list), given its reference.</p> --spec(get_state/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: ljid()) - -> mod_pubsub:pubsubState() -). - -get_state(NodeIdx, JID) -> - StateId = {JID, NodeIdx}, - case catch mnesia:read({pubsub_state, StateId}) of - [State] when is_record(State, pubsub_state) -> State; - _ -> #pubsub_state{stateid=StateId} - end. - -%% @spec (State) -> ok | {error, Reason} -%% State = mod_pubsub:pubsubState() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Write a state into database.</p> --spec(set_state/1 :: -( - State::mod_pubsub:pubsubState()) - -> ok -). -set_state(State) when is_record(State, pubsub_state) -> -%% @spec (NodeIdx, JID) -> ok | {error, Reason} -%% NodeIdx = mod_pubsub:nodeIdx() -%% JID = mod_pubsub:jid() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Delete a state from database.</p> - mnesia:write(State). -%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. - --spec(del_state/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: ljid()) - -> ok -). -del_state(NodeIdx, JID) -> - mnesia:delete({pubsub_state, {JID, NodeIdx}}). - -%% @spec (NodeIdx, From) -> {result, Items} -%% NodeIdx = mod_pubsub:nodeIdx() -%% From = mod_pubsub:jid() -%% Items = [] | [mod_pubsub:pubsubItem()] -%% @doc Returns the list of stored items for a given node. -%% <p>For the default PubSub module, items are stored in Mnesia database.</p> -%% <p>We can consider that the pubsub_item table have been created by the main -%% mod_pubsub module.</p> -%% <p>PubSub plugins can store the items where they wants (for example in a -%% relational database), or they can even decide not to persist any items.</p> -%% <p>If a PubSub plugin wants to delegate the item storage to the default node, -%% they can implement this function like this: -%% ```get_items(NodeIdx, From) -> -%% node_default:get_items(NodeIdx, From).'''</p> --spec(get_items/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - _From :: jid()) - -> {result, [mod_pubsub:pubsubItem()]} -). - -get_items(NodeIdx, _From) -> - Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeIdx}, _ = '_'}), - {result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}. - --spec(get_items/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: jid(), - AccessModel :: mod_pubsub:accessModel(), - Presence_Subscription :: boolean(), - RosterGroup :: boolean(), - _SubId :: mod_pubsub:subId()) - -> {result, [mod_pubsub:pubsubItem()]} - %%% - | {error, xmlel()} -). - -get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> - SubKey = jlib:jid_tolower(JID), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - SubState = get_state(NodeIdx, SubKey), - Affiliation = GenState#pubsub_state.affiliation, - Subscriptions = SubState#pubsub_state.subscriptions, - Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - GenState#pubsub_state.affiliation == outcast -> - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> get_items(NodeIdx, JID) - end. - -%% @spec (NodeIdx, ItemId) -> {result, Item} | {error, 'item-not-found'} -%% NodeIdx = mod_pubsub:nodeIdx() -%% ItemId = mod_pubsub:itemId() -%% Item = mod_pubsub:pubsubItem() -%% @doc <p>Returns an item (one item list), given its reference.</p> --spec(get_item/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemId :: mod_pubsub:itemId()) - -> {result, mod_pubsub:pubsubItem()} - | {error, xmlel()} -). - -get_item(NodeIdx, ItemId) -> - case mnesia:read({pubsub_item, {ItemId, NodeIdx}}) of - [Item] when is_record(Item, pubsub_item) -> {result, Item}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end. - -%% @spec (NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> {result, Item} | {error, Reason} -%% NodeIdx = mod_pubsub:nodeIdx() -%% ItemId = mod_pubsub:itemId() -%% JID = mod_pubsub:jid() -%% AccessModel = mod_pubsub:accessModel() -%% PresenceSubscription = boolean() -%% RosterGroup = boolean() -%% SubId = mod_pubsub:subId() -%% Item = mod_pubsub:pubsubItem() -%% Reason = mod_pubsub:stanzaError() | 'item-not-found' --spec(get_item/7 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemId :: mod_pubsub:itemId(), - JID :: jid(), - AccessModel :: mod_pubsub:accessModel(), - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - SubId :: mod_pubsub:subId()) - -> {result, mod_pubsub:pubsubItem()} - | {error, xmlel()} -). - -get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, - _SubId) -> - SubKey = jlib:jid_tolower(JID), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - Affiliation = GenState#pubsub_state.affiliation, - Subscriptions = GenState#pubsub_state.subscriptions, - Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - GenState#pubsub_state.affiliation == outcast -> - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> get_item(NodeIdx, ItemId) - end. - -%% @spec (Item) -> ok | {error, Reason} -%% Item = mod_pubsub:pubsubItem() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Write an item into database.</p> --spec(set_item/1 :: -( - Item::mod_pubsub:pubsubItem()) - -> ok -). -set_item(Item) when is_record(Item, pubsub_item) -> -%% @spec (NodeIdx, ItemId) -> ok | {error, Reason} -%% NodeIdx = mod_pubsub:nodeIdx() -%% ItemId = mod_pubsub:itemId() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Delete an item from database.</p> - mnesia:write(Item). -%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. - --spec(del_item/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemId :: mod_pubsub:itemId()) - -> ok -). -del_item(NodeIdx, ItemId) -> - mnesia:delete({pubsub_item, {ItemId, NodeIdx}}). - --spec(del_items/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemIds :: [mod_pubsub:pubsubItem(),...]) - -> ok -). - -del_items(NodeIdx, ItemIds) -> - lists:foreach(fun (ItemId) -> del_item(NodeIdx, ItemId) - end, - ItemIds). - -get_item_name(_Host, _Node, Id) -> Id. - -%% @doc <p>Return the name of the node if known: Default is to return -%% node id.</p> --spec(node_to_path/1 :: -( - Node::binary()) - -> [binary()] -). -node_to_path(Node) -> str:tokens((Node), <<"/">>). - --spec(path_to_node/1 :: -( - Path :: [binary()]) - -> binary() -). - -path_to_node([]) -> <<>>; -path_to_node(Path) -> -%% @spec (Affiliation, Subscription) -> true | false -%% Affiliation = owner | member | publisher | outcast | none -%% Subscription = subscribed | none -%% @doc Determines if the combination of Affiliation and Subscribed -%% are allowed to get items from a node. - iolist_to_binary(str:join([<<"">> | Path], <<"/">>)). - -can_fetch_item(owner, _) -> true; -can_fetch_item(member, _) -> true; -can_fetch_item(publisher, _) -> true; -can_fetch_item(outcast, _) -> false; -can_fetch_item(none, Subscriptions) -> - is_subscribed(Subscriptions). -%can_fetch_item(_Affiliation, _Subscription) -> false. - -is_subscribed(Subscriptions) -> - lists:any(fun ({subscribed, _SubId}) -> true; - (_) -> false - end, - Subscriptions). - -%% Returns the first item where Pred() is true in List -first_in_list(_Pred, []) -> false; -first_in_list(Pred, [H | T]) -> - case Pred(H) of - true -> {value, H}; - _ -> first_in_list(Pred, T) - end. diff --git a/src/mod_pubsub/node_hometree_odbc.erl b/src/mod_pubsub/node_hometree_odbc.erl deleted file mode 100644 index a5f8668ec..000000000 --- a/src/mod_pubsub/node_hometree_odbc.erl +++ /dev/null @@ -1,1714 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @todo The item table should be handled by the plugin, but plugin that do -%%% not want to manage it should be able to use the default behaviour. -%%% @todo Plugin modules should be able to register to receive presence update -%%% send to pubsub. - -%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin. -%%% <p>It is used as a default for all unknown PubSub node type. It can serve -%%% as a developer basis and reference to build its own custom pubsub node -%%% types.</p> -%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p> -%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin -%%% development is still a work in progress. However, the system is already -%%% useable and useful as is. Please, send us comments, feedback and -%%% improvements.</p> - --module(node_hometree_odbc). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB, mod_pubsub_odbc). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, - get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, get_subscriptions/2, - set_subscriptions/4, get_pending_nodes/2, get_states/1, - get_state/2, set_state/1, get_items/7, get_items/6, - get_items/3, get_items/2, get_item/7, get_item/2, - set_item/1, get_item_name/3, get_last_items/3, - path_to_node/1, node_to_path/1]). - --export([decode_jid/1, decode_node/1, - decode_affiliation/1, decode_subscriptions/1, - encode_jid/1, encode_affiliation/1, - encode_subscriptions/1]). - -%% ================ -%% API definition -%% ================ - -%% @spec (Host, ServerHost, Opts) -> any() -%% Host = mod_pubsub:host() -%% ServerHost = mod_pubsub:host() -%% Opts = list() -%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must -%% implement this function. It can return anything.</p> -%% <p>This function is mainly used to trigger the setup task necessary for the -%% plugin. It can be used for example by the developer to create the specific -%% module database schema if it does not exists yet.</p> -init(_Host, _ServerHost, _Opts) -> - pubsub_subscription_odbc:init(), ok. - -%% @spec (Host, ServerHost) -> any() -%% Host = mod_pubsub:host() -%% ServerHost = host() -%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must -%% implement this function. It can return anything.</p> -terminate(_Host, _ServerHost) -> ok. - -%% @spec () -> [Option] -%% Option = mod_pubsub:nodeOption() -%% @doc Returns the default pubsub node options. -%% <p>Example of function return value:</p> -%% ``` -%% [{deliver_payloads, true}, -%% {notify_config, false}, -%% {notify_delete, false}, -%% {notify_retract, true}, -%% {persist_items, true}, -%% {max_items, 10}, -%% {subscribe, true}, -%% {access_model, open}, -%% {publish_model, publishers}, -%% {max_payload_size, 100000}, -%% {send_last_published_item, never}, -%% {presence_based_delivery, false}]''' --spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, open}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, false}, {odbc, true}, - {rsm, true}]. - -%% @spec () -> [] -%% @doc Returns the node features --spec(features/0 :: () -> Features::[Feature::binary(),...]). -features() -> -%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool() -%% Host = mod_pubsub:host() -%% ServerHost = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% ParentNode = mod_pubsub:pubsubNode() -%% Owner = mod_pubsub:jid() -%% Access = all | atom() -%% @doc Checks if the current user has the permission to create the requested node -%% <p>In {@link node_default}, the permission is decided by the place in the -%% hierarchy where the user is creating the node. The access parameter is also -%% checked in the default module. This parameter depends on the value of the -%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p> -%% <p>This function also check that node can be created a a children of its -%% parent node</p> -%% <p>PubSub plugins can redefine the PubSub node creation rights as they -%% which. They can simply delegate this check to the {@link node_default} -%% module by implementing this function like this: -%% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> -%% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p> - [<<"create-nodes">>, <<"auto-create">>, - <<"access-authorize">>, <<"delete-nodes">>, - <<"delete-items">>, <<"get-pending">>, - <<"instant-nodes">>, <<"manage-subscriptions">>, - <<"modify-affiliations">>, <<"multi-subscribe">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>, - <<"subscription-options">>, <<"rsm">>]. - --spec(create_node_permission/6 :: -( - Host :: mod_pubsub:host(), - ServerHost :: binary(), - Node :: mod_pubsub:nodeId(), - _ParentNode :: _, - Owner :: jid(), - Access :: atom()) - -> {result, boolean()} -). -create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - {User, Server, _Resource} = LOwner, - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case node_to_path(Node) of - [<<"home">>, Server, User | _] -> true; - _ -> false - end; - _ -> false - end - end, - {result, Allowed}. - -%% @spec (NodeId, Owner) -> -%% {result, Result} | exit -%% NodeId = mod_pubsub:pubsubNodeId() -%% Owner = mod_pubsub:jid() -%% @doc <p></p> --spec(create_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} -). -create_node(NodeIdx, Owner) -> - OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - State = #pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}, - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " - "affiliation, subscriptions) values(">>, - state_to_raw(NodeIdx, State), <<");">>]), - {result, {default, broadcast}}. - -%% @spec (Removed) -> ok -%% Removed = [mod_pubsub:pubsubNode()] -%% @doc <p>purge items of deleted nodes after effective deletion.</p> --spec(delete_node/1 :: -( - Removed :: [mod_pubsub:pubsubNode(),...]) - -> {result, {default, broadcast, _}} -). -delete_node(Removed) -> - Reply = lists:map(fun (#pubsub_node{id = NodeId} = - PubsubNode) -> - Subscriptions = case catch - ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state " - "where nodeid='">>, - NodeId, - <<"';">>]) - of - {selected, - [<<"jid">>, - <<"subscriptions">>], - RItems} -> - lists:map(fun ({SJID, - Subscriptions}) -> - {decode_jid(SJID), - decode_subscriptions(Subscriptions)} - end, - RItems); - _ -> [] - end, - {PubsubNode, Subscriptions} - end, - Removed), - {result, {default, broadcast, Reply}}. - -%% @spec (NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> -%% {error, Reason} | {result, Result} -%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> -%% <p>The mechanism works as follow: -%% <ul> -%% <li>The main PubSub module prepares the subscription and passes the -%% result of the preparation as a record.</li> -%% <li>This function gets the prepared record and several other parameters and -%% can decide to:<ul> -%% <li>reject the subscription;</li> -%% <li>allow it as is, letting the main module perform the database -%% persistance;</li> -%% <li>allow it, modifying the record. The main module will store the -%% modified record;</li> -%% <li>allow it, but perform the needed persistance operations.</li></ul> -%% </li></ul></p> -%% <p>The selected behaviour depends on the return parameter: -%% <ul> -%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No -%% subscription will actually be performed.</li> -%% <li><tt>true</tt>: Subscribe operation is allowed, based on the -%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this -%% parameter contains an error, no subscription will be performed.</li> -%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but -%% the {@link mod_pubsub:pubsubState()} record returned replaces the value -%% passed in parameter <tt>SubscribeResult</tt>.</li> -%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the -%% {@link mod_pubsub:pubsubState()} will be considered as already stored and -%% no further persistance operation will be performed. This case is used, -%% when the plugin module is doing the persistance by itself or when it want -%% to completly disable persistance.</li></ul> -%% </p> -%% <p>In the default plugin module, the record is unchanged.</p> --spec(subscribe_node/8 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - AccessModel :: mod_pubsub:accessModel(), - SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - Options :: mod_pubsub:subOptions()) - -> {result, {default, subscribed, mod_pubsub:subId()}} - | {result, {default, subscribed, mod_pubsub:subId(), send_last}} - | {result, {default, pending, mod_pubsub:subId()}} - %%% - | {error, _} - | {error, _, binary()} -). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - SubKey = jlib:jid_tolower(Subscriber), - GenKey = jlib:jid_remove_resource(SubKey), - Authorized = - jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == - GenKey, - {Affiliation, Subscriptions} = - select_affiliation_subscriptions(NodeId, GenKey, - SubKey), - Whitelisted = lists:member(Affiliation, - [member, publisher, owner]), - PendingSubscription = lists:any(fun ({pending, _}) -> - true; - (_) -> false - end, - Subscriptions), - if not Authorized -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; - Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; - PendingSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"pending-subscription">>)}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - %%ForbiddenAnonymous -> - %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; - true -> - {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options), - NewSub = case AccessModel of - authorize -> pending; - _ -> subscribed - end, - update_subscription(NodeId, SubKey, - [{NewSub, SubId} | Subscriptions]), - case {NewSub, SendLast} of - {subscribed, never} -> - {result, {default, subscribed, SubId}}; - {subscribed, _} -> - {result, {default, subscribed, SubId, send_last}}; - {_, _} -> {result, {default, pending, SubId}} - end - end. - -%% @spec (NodeId, Sender, Subscriber, SubId) -> -%% {error, Reason} | {result, []} -%% NodeId = mod_pubsub:pubsubNodeId() -%% Sender = mod_pubsub:jid() -%% Subscriber = mod_pubsub:jid() -%% SubId = mod_pubsub:subid() -%% Reason = mod_pubsub:stanzaError() -%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> --spec(unsubscribe_node/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: jid(), - SubId :: subId()) - -> {result, default} - % - | {error, _} - | {error, _, binary()} -). -unsubscribe_node(NodeId, Sender, Subscriber, SubId) -> - SubKey = jlib:jid_tolower(Subscriber), - GenKey = jlib:jid_remove_resource(SubKey), - Authorized = - jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == - GenKey, - {Affiliation, Subscriptions} = - select_affiliation_subscriptions(NodeId, SubKey), - SubIdExists = case SubId of - [] -> false; - List when is_binary(List) -> true; - _ -> false - end, - if - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> {error, ?ERR_FORBIDDEN}; - %% Entity did not specify SubId - %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - %% Requesting entity is not a subscriber - Subscriptions == [] -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), - <<"not-subscribed">>)}; - %% Subid supplied, so use that. - SubIdExists -> - Sub = first_in_list(fun (S) -> - case S of - {_Sub, SubId} -> true; - _ -> false - end - end, - Subscriptions), - case Sub of - {value, S} -> - delete_subscription(SubKey, NodeId, S, Affiliation, - Subscriptions), - {result, default}; - false -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), - <<"not-subscribed">>)} - end; - %% Asking to remove all subscriptions to the given node - SubId == all -> - [delete_subscription(SubKey, NodeId, S, Affiliation, - Subscriptions) - || S <- Subscriptions], - {result, default}; - %% No subid supplied, but there's only one matching - %% subscription, so use that. - length(Subscriptions) == 1 -> - delete_subscription(SubKey, NodeId, hd(Subscriptions), - Affiliation, Subscriptions), - {result, default}; - %% No subid and more than one possible subscription match. - true -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} - end. - -%-spec(delete_subscriptions/5 :: -%( -% SubKey :: ljid(), -% NodeIdx :: mod_pubsub:nodeIdx(), -% _ :: {mod_pubsub:subscription(), mod_pubsub:subId()}, -% SubState :: mod_pubsub:pubsubState(), -% Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}]) -% -> ok -%). -delete_subscription(SubKey, NodeIdx, - {Subscription, SubId}, Affiliation, Subscriptions) -> - NewSubs = Subscriptions -- [{Subscription, SubId}], - pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeIdx, SubId), - case {Affiliation, NewSubs} of - {none, []} -> del_state(NodeIdx, SubKey); - _ -> update_subscription(NodeIdx, SubKey, NewSubs) - end. - -%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> -%% {true, PubsubItem} | {result, Reply} -%% NodeId = mod_pubsub:pubsubNodeId() -%% Publisher = mod_pubsub:jid() -%% PublishModel = atom() -%% MaxItems = integer() -%% ItemId = string() -%% Payload = term() -%% @doc <p>Publishes the item passed as parameter.</p> -%% <p>The mechanism works as follow: -%% <ul> -%% <li>The main PubSub module prepares the item to publish and passes the -%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li> -%% <li>This function gets the prepared record and several other parameters and can decide to:<ul> -%% <li>reject the publication;</li> -%% <li>allow the publication as is, letting the main module perform the database persistance;</li> -%% <li>allow the publication, modifying the record. The main module will store the modified record;</li> -%% <li>allow it, but perform the needed persistance operations.</li></ul> -%% </li></ul></p> -%% <p>The selected behaviour depends on the return parameter: -%% <ul> -%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No -%% publication is actually performed.</li> -%% <li><tt>true</tt>: Publication operation is allowed, based on the -%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt> -%% parameter contains an error, no subscription will actually be -%% performed.</li> -%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the -%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed -%% in parameter <tt>Item</tt>. The persistance will be performed by the main -%% module.</li> -%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the -%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and -%% no further persistance operation will be performed. This case is used, -%% when the plugin module is doing the persistance by itself or when it want -%% to completly disable persistance.</li></ul> -%% </p> -%% <p>In the default plugin module, the record is unchanged.</p> - --spec(publish_item/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - Max_Items :: non_neg_integer(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, {default, broadcast, [mod_pubsub:itemId()]}} - %%% - | {error, _} -). -publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> - SubKey = jlib:jid_tolower(Publisher), - GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = - select_affiliation_subscriptions(NodeIdx, GenKey, SubKey), - Subscribed = case PublishModel of - subscribers -> is_subscribed(Subscriptions); - _ -> undefined - end, - if not - ((PublishModel == open) or - (PublishModel == publishers) and - ((Affiliation == owner) or (Affiliation == publisher)) - or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; - true -> - if MaxItems > 0 -> - PubId = {now(), SubKey}, - set_item(#pubsub_item{itemid = {ItemId, NodeIdx}, - creation = {now(), GenKey}, - modification = PubId, - payload = Payload}), - Items = [ItemId | itemids(NodeIdx, GenKey) -- [ItemId]], - {result, {_, OI}} = remove_extra_items(NodeIdx, MaxItems, Items), - {result, {default, broadcast, OI}}; - true -> {result, {default, broadcast, []}} - end - end. - -%% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds} -%% NodeId = mod_pubsub:pubsubNodeId() -%% MaxItems = integer() | unlimited -%% ItemIds = [ItemId::string()] -%% NewItemIds = [ItemId::string()] -%% @doc <p>This function is used to remove extra items, most notably when the -%% maximum number of items has been reached.</p> -%% <p>This function is used internally by the core PubSub module, as no -%% permission check is performed.</p> -%% <p>In the default plugin module, the oldest items are removed, but other -%% rules can be used.</p> -%% <p>If another PubSub plugin wants to delegate the item removal (and if the -%% plugin is using the default pubsub storage), it can implements this function like this: -%% ```remove_extra_items(NodeId, MaxItems, ItemIds) -> -%% node_default:remove_extra_items(NodeId, MaxItems, ItemIds).'''</p> -remove_extra_items(_NodeId, unlimited, ItemIds) -> - {result, {ItemIds, []}}; -remove_extra_items(NodeId, MaxItems, ItemIds) -> - NewItems = lists:sublist(ItemIds, MaxItems), - OldItems = lists:nthtail(length(NewItems), ItemIds), - del_items(NodeId, OldItems), - {result, {NewItems, OldItems}}. - -%% @spec (NodeId, Publisher, PublishModel, ItemId) -> -%% {error, Reason::stanzaError()} | -%% {result, []} -%% NodeId = mod_pubsub:pubsubNodeId() -%% Publisher = mod_pubsub:jid() -%% PublishModel = atom() -%% ItemId = string() -%% @doc <p>Triggers item deletion.</p> -%% <p>Default plugin: The user performing the deletion must be the node owner -%% or a publisher.</p> --spec(delete_item/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - ItemId :: <<>> | mod_pubsub:itemId()) - -> {result, {default, broadcast}} - %%% - | {error, _} -). -delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> - SubKey = jlib:jid_tolower(Publisher), - GenKey = jlib:jid_remove_resource(SubKey), - {result, Affiliation} = get_affiliation(NodeIdx, GenKey), - Allowed = Affiliation == publisher orelse - Affiliation == owner orelse - PublishModel == open orelse - case get_item(NodeIdx, ItemId) of - {result, #pubsub_item{creation = {_, GenKey}}} -> true; - _ -> false - end, - if not Allowed -> {error, ?ERR_FORBIDDEN}; - true -> - case del_item(NodeIdx, ItemId) of - {updated, 1} -> {result, {default, broadcast}}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end - end. - -%% @spec (NodeId, Owner) -> -%% {error, Reason::stanzaError()} | -%% {result, {default, broadcast}} -%% NodeId = mod_pubsub:pubsubNodeId() -%% Owner = mod_pubsub:jid() --spec(purge_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} - | {error, _} -). -purge_node(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeIdx, GenKey), - case GenState of - #pubsub_state{affiliation = owner} -> - {result, States} = get_states(NodeIdx), - lists:foreach(fun (#pubsub_state{items = []}) -> ok; - (#pubsub_state{items = Items}) -> - del_items(NodeIdx, Items) - end, - States), - {result, {default, broadcast}}; - _ -> {error, ?ERR_FORBIDDEN} - end. - -%% @spec (Host, JID) -> [{Node,Affiliation}] -%% Host = host() -%% JID = mod_pubsub:jid() -%% @doc <p>Return the current affiliations for the given user</p> -%% <p>The default module reads affiliations in the main Mnesia -%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same -%% table, it should return an empty list, as the affiliation will be read by -%% the default PubSub module. Otherwise, it should return its own affiliation, -%% that will be added to the affiliation stored in the main -%% <tt>pubsub_state</tt> table.</p> --spec(get_entity_affiliations/2 :: -( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} -). -get_entity_affiliations(Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - H = (?PUBSUB):escape(Host), - J = encode_jid(GenKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation " - "from pubsub_state i, pubsub_node n where " - "i.nodeid = n.nodeid and jid='">>, - J, <<"' and host='">>, H, - <<"';">>]) - of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, - <<"affiliation">>], - RItems} -> - lists:map(fun ({N, T, I, A}) -> - Node = nodetree_tree_odbc:raw_to_node(Host, - {N, - <<"">>, - T, - I}), - {Node, decode_affiliation(A)} - end, - RItems); - _ -> [] - end, - {result, Reply}. - --spec(get_node_affiliations/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [{ljid(), mod_pubsub:affiliation()}]} -). -get_node_affiliations(NodeIdx) -> - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state " - "where nodeid='">>, - NodeIdx, <<"';">>]) - of - {selected, [<<"jid">>, <<"affiliation">>], RItems} -> - lists:map(fun ({J, A}) -> - {decode_jid(J), decode_affiliation(A)} - end, - RItems); - _ -> [] - end, - {result, Reply}. - --spec(get_affiliation/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid()) - -> {result, mod_pubsub:affiliation()} -). - -get_affiliation(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - J = encode_jid(GenKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state " - "where nodeid='">>, - NodeIdx, <<"' and jid='">>, J, - <<"';">>]) - of - {selected, [<<"affiliation">>], [{A}]} -> - decode_affiliation(A); - _ -> none - end, - {result, Reply}. - --spec(set_affiliation/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid(), - Affiliation :: mod_pubsub:affiliation()) - -> ok -). -set_affiliation(NodeIdx, Owner, Affiliation) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - {_, Subscriptions} = select_affiliation_subscriptions(NodeIdx, GenKey), - case {Affiliation, Subscriptions} of - {none, none} -> del_state(NodeIdx, GenKey); - _ -> update_affiliation(NodeIdx, GenKey, Affiliation) - end. - -%% @spec (Host, Owner) -> [{Node,Subscription}] -%% Host = host() -%% Owner = mod_pubsub:jid() -%% @doc <p>Return the current subscriptions for the given user</p> -%% <p>The default module reads subscriptions in the main Mnesia -%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same -%% table, it should return an empty list, as the affiliation will be read by -%% the default PubSub module. Otherwise, it should return its own affiliation, -%% that will be added to the affiliation stored in the main -%% <tt>pubsub_state</tt> table.</p> - --spec(get_entity_subscriptions/2 :: -( - Host :: mod_pubsub:host(), - Owner :: jid()) - -> {result, - [{mod_pubsub:pubsubNode(), - mod_pubsub:subscription(), - mod_pubsub:subId(), - ljid()}] - } -). -get_entity_subscriptions(Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - H = (?PUBSUB):escape(Host), - SJ = encode_jid(SubKey), - GJ = encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptio" - "ns from pubsub_state i, pubsub_node " - "n where i.nodeid = n.nodeid and jid " - "like '">>, - GJ, <<"%' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptio" - "ns from pubsub_state i, pubsub_node " - "n where i.nodeid = n.nodeid and jid " - "in ('">>, - SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, - <<"subscriptions">>], - RItems} -> - lists:foldl(fun ({N, T, I, J, S}, Acc) -> - Node = - nodetree_tree_odbc:raw_to_node(Host, - {N, - <<"">>, - T, - I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, - Acc2) -> - [{Node, Sub, - SubId, Jid} - | Acc2]; - (Sub, Acc2) -> - [{Node, Sub, - Jid} - | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> [] - end, - {result, Reply}. - -%% do the same as get_entity_subscriptions but filter result only to -%% nodes having send_last_published_item=on_sub_and_presence -%% as this call avoid seeking node, it must return node and type as well --spec(get_entity_subscriptions_for_send_last/2 :: -( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, - [{mod_pubsub:pubsubNode(), - mod_pubsub:subscription(), - mod_pubsub:subId(), - ljid()}] - } -). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - H = (?PUBSUB):escape(Host), - SJ = encode_jid(SubKey), - GJ = encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptio" - "ns from pubsub_state i, pubsub_node " - "n, pubsub_node_option o where i.nodeid " - "= n.nodeid and n.nodeid = o.nodeid and " - "name='send_last_published_item' and " - "val='on_sub_and_presence' and jid like " - "'">>, - GJ, <<"%' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptio" - "ns from pubsub_state i, pubsub_node " - "n, pubsub_node_option o where i.nodeid " - "= n.nodeid and n.nodeid = o.nodeid and " - "name='send_last_published_item' and " - "val='on_sub_and_presence' and jid in " - "('">>, - SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, - <<"subscriptions">>], - RItems} -> - lists:foldl(fun ({N, T, I, J, S}, Acc) -> - Node = - nodetree_tree_odbc:raw_to_node(Host, - {N, - <<"">>, - T, - I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, Jid}| Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> [] - end, - {result, Reply}. - --spec(get_node_subscriptions/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}]} -). -get_node_subscriptions(NodeIdx) -> - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state " - "where nodeid='">>, - NodeIdx, <<"';">>]) - of - {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ({J, S}, Acc) -> - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> [{Jid, none} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Jid, Sub, SubId} | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> [] - end, - {result, Reply}. - --spec(get_subscriptions/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid()) - -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} -). -get_subscriptions(NodeIdx, Owner) -> - SubKey = jlib:jid_tolower(Owner), - J = encode_jid(SubKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state " - "where nodeid='">>, - NodeIdx, <<"' and jid='">>, J, - <<"';">>]) - of - {selected, [<<"subscriptions">>], [{S}]} -> - decode_subscriptions(S); - _ -> [] - end, - {result, Reply}. - --spec(set_subscriptions/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid(), - Subscription :: mod_pubsub:subscription(), - SubId :: mod_pubsub:subId()) - -> _ - %%% - | {error, xmlel()} -). -set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> - SubKey = jlib:jid_tolower(Owner), - SubState = get_state_without_itemids(NodeIdx, SubKey), - case {SubId, SubState#pubsub_state.subscriptions} of - {_, []} -> - case Subscription of - none -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), - <<"not-subscribed">>)}; - _ -> - new_subscription(NodeIdx, Owner, Subscription, SubState) - end; - {<<"">>, [{_, SID}]} -> - case Subscription of - none -> unsub_with_subid(NodeIdx, SID, SubState); - _ -> replace_subscription({Subscription, SID}, SubState) - end; - {<<"">>, [_ | _]} -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), - <<"subid-required">>)}; - _ -> - case Subscription of - none -> unsub_with_subid(NodeIdx, SubId, SubState); - _ -> - replace_subscription({Subscription, SubId}, SubState) - end - end. - --spec(replace_subscription/2 :: -( - NewSub :: {mod_pubsub:subscription(), mod_pubsub:subId()}, - SubState :: mod_pubsub:pubsubState()) - -> {result, []} -). -replace_subscription(NewSub, SubState) -> - NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), - set_state(SubState#pubsub_state{subscriptions = NewSubs}). - -replace_subscription(_, [], Acc) -> Acc; -replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) -> - replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]). - --spec(new_subscription/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid(), - Subscription :: mod_pubsub:subscription(), - SubState :: mod_pubsub:pubsubState()) - -> {mod_pubsub:subscription(), mod_pubsub:subId()} - %%% - | {error, xmlel()} -). - -new_subscription(NodeIdx, Owner, Subscription, SubState) -> - case pubsub_subscription_odbc:subscribe_node(Owner, NodeIdx, []) of - {result, SubId} -> - Subscriptions = SubState#pubsub_state.subscriptions, - set_state(SubState#pubsub_state{subscriptions = - [{Subscription, SubId} | Subscriptions]}), - {Subscription, SubId}; - _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} - end. - --spec(unsub_with_subid/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - SubId :: mod_pubsub:subId(), - SubState :: mod_pubsub:pubsubState()) - -> ok -). -unsub_with_subid(NodeIdx, SubId, SubState) -> - pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, - NodeIdx, SubId), - NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID - end, - SubState#pubsub_state.subscriptions), - case {NewSubs, SubState#pubsub_state.affiliation} of - {[], none} -> - del_state(NodeIdx, - element(1, SubState#pubsub_state.stateid)); - _ -> - set_state(SubState#pubsub_state{subscriptions = NewSubs}) - end. - -%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason} -%% Host = host() -%% Owner = jid() -%% Node = pubsubNode() -%% @doc <p>Returns a list of Owner's nodes on Host with pending -%% subscriptions.</p> --spec(get_pending_nodes/2 :: -( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, [mod_pubsub:nodeId()]} -). -get_pending_nodes(Host, Owner) -> - GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)), - States = mnesia:match_object(#pubsub_state{stateid = - {GenKey, '_'}, - affiliation = owner, _ = '_'}), - NodeIDs = [ID - || #pubsub_state{stateid = {_, ID}} <- States], - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(Host, config), - nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree_odbc - end, - Reply = mnesia:foldl(fun (#pubsub_state{stateid = - {_, NID}} = - S, - Acc) -> - case lists:member(NID, NodeIDs) of - true -> - case get_nodes_helper(NodeTree, S) of - {value, Node} -> [Node | Acc]; - false -> Acc - end; - false -> Acc - end - end, - [], pubsub_state), - {result, Reply}. - -get_nodes_helper(NodeTree, - #pubsub_state{stateid = {_, N}, - subscriptions = Subs}) -> - HasPending = fun ({pending, _}) -> true; - (pending) -> true; - (_) -> false - end, - case lists:any(HasPending, Subs) of - true -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {_, Node}} -> {value, Node}; - _ -> false - end; - false -> false - end. - -%% @spec (NodeId) -> [States] | [] -%% NodeId = mod_pubsub:pubsubNodeId() -%% @doc Returns the list of stored states for a given node. -%% <p>For the default PubSub module, states are stored in Mnesia database.</p> -%% <p>We can consider that the pubsub_state table have been created by the main -%% mod_pubsub module.</p> -%% <p>PubSub plugins can store the states where they wants (for example in a -%% relational database).</p> -%% <p>If a PubSub plugin wants to delegate the states storage to the default node, -%% they can implement this function like this: -%% ```get_states(NodeId) -> -%% node_default:get_states(NodeId).'''</p> --spec(get_states/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [mod_pubsub:pubsubState()]} -). -get_states(NodeIdx) -> - case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where nodeid='">>, - NodeIdx, <<"';">>]) - of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], - RItems} -> - {result, - lists:map(fun ({SJID, Affiliation, Subscriptions}) -> - #pubsub_state{stateid = {decode_jid(SJID), NodeIdx}, - items = itemids(NodeIdx, SJID), - affiliation = decode_affiliation(Affiliation), - subscriptions = decode_subscriptions(Subscriptions)} - end, - RItems)}; - _ -> {result, []} - end. - -%% @spec (NodeId, JID) -> [State] | [] -%% NodeId = mod_pubsub:pubsubNodeId() -%% JID = mod_pubsub:jid() -%% State = mod_pubsub:pubsubItems() -%% @doc <p>Returns a state (one state list), given its reference.</p> - --spec(get_state/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: ljid()) - -> mod_pubsub:pubsubState() -). -get_state(NodeIdx, JID) -> - State = get_state_without_itemids(NodeIdx, JID), - {SJID, _} = State#pubsub_state.stateid, - State#pubsub_state{items = itemids(NodeIdx, SJID)}. - --spec(get_state_without_itemids/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: jid()) - -> mod_pubsub:pubsubState() -). -get_state_without_itemids(NodeIdx, JID) -> - J = encode_jid(JID), - case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where jid='">>, - J, <<"' and nodeid='">>, NodeIdx, - <<"';">>]) - of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], - [{SJID, Affiliation, Subscriptions}]} -> - #pubsub_state{stateid = {decode_jid(SJID), NodeIdx}, - affiliation = decode_affiliation(Affiliation), - subscriptions = decode_subscriptions(Subscriptions)}; - _ -> #pubsub_state{stateid = {JID, NodeIdx}} - end. - -%% @spec (State) -> ok | {error, Reason::stanzaError()} -%% State = mod_pubsub:pubsubStates() -%% @doc <p>Write a state into database.</p> - --spec(set_state/1 :: -( - State :: mod_pubsub:pubsubState()) - -> {result, []} -). -set_state(State) -> - {_, NodeIdx} = State#pubsub_state.stateid, - set_state(NodeIdx, State). - --spec(set_state/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - State :: mod_pubsub:pubsubState()) - -> {result, []} -). -set_state(NodeIdx, State) -> - {JID, _} = State#pubsub_state.stateid, - J = encode_jid(JID), - S = encode_subscriptions(State#pubsub_state.subscriptions), - A = encode_affiliation(State#pubsub_state.affiliation), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, - S, <<"', affiliation='">>, A, - <<"' where nodeid='">>, NodeIdx, - <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " - "affiliation, subscriptions) values('">>, - NodeIdx, <<"', '">>, J, <<"', '">>, A, - <<"', '">>, S, <<"');">>]) - end, - {result, []}. - -%% @spec (NodeId, JID) -> ok | {error, Reason::stanzaError()} -%% NodeId = mod_pubsub:pubsubNodeId() -%% JID = mod_pubsub:jid() -%% @doc <p>Delete a state from database.</p> -del_state(NodeId, JID) -> - J = encode_jid(JID), - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>, - J, <<"' and nodeid='">>, NodeId, <<"';">>]), - ok. - -%% @spec (NodeId, From) -> {[Items],RsmOut} | [] -%% NodeId = mod_pubsub:pubsubNodeId() -%% Items = mod_pubsub:pubsubItems() -%% @doc Returns the list of stored items for a given node. -%% <p>For the default PubSub module, items are stored in Mnesia database.</p> -%% <p>We can consider that the pubsub_item table have been created by the main -%% mod_pubsub module.</p> -%% <p>PubSub plugins can store the items where they wants (for example in a -%% relational database), or they can even decide not to persist any items.</p> -%% <p>If a PubSub plugin wants to delegate the item storage to the default node, -%% they can implement this function like this: -%% ```get_items(NodeId, From) -> -%% node_default:get_items(NodeId, From).'''</p> -get_items(NodeId, _From) -> - case catch - ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, - NodeId, - <<"' order by modification desc;">>]) - of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, - <<"modification">>, <<"payload">>], - RItems} -> - {result, - lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end, - RItems)}; - _ -> {result, []} - end. - -get_items(NodeId, From, none) -> - MaxItems = case catch - ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option where " - "nodeid='">>, - NodeId, - <<"' and name='max_items';">>]) - of - {selected, [<<"val">>], [{Value}]} -> - Tokens = element(2, - erl_scan:string(<<Value/binary, ".">>)), - element(2, erl_parse:parse_term(Tokens)); - _ -> ?MAXITEMS - end, - get_items(NodeId, From, #rsm_in{max = MaxItems}); -get_items(NodeId, _From, - #rsm_in{max = M, direction = Direction, id = I, - index = IncIndex}) -> - Max = (?PUBSUB):escape(i2l(M)), - {Way, Order} = case Direction of - aft -> {<<"<">>, <<"desc">>}; - before when I == [] -> {<<"is not">>, <<"asc">>}; - before -> {<<">">>, <<"asc">>}; - _ when IncIndex =/= undefined -> - {<<"<">>, <<"desc">>}; % using index - _ -> {<<"is not">>, <<"desc">>}% Can be better - end, - [AttrName, Id] = case I of - undefined when IncIndex =/= undefined -> - case catch - ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item " - "pi where exists ( select count(*) as " - "count1 from pubsub_item where nodeid='">>, - NodeId, - <<"' and modification > pi.modification " - "having count1 = ">>, - (?PUBSUB):escape(i2l(IncIndex)), - <<" );">>]) - of - {selected, [_], [{O}]} -> - [<<"modification">>, <<"'", O/binary, "'">>]; - _ -> [<<"modification">>, <<"null">>] - end; - undefined -> [<<"modification">>, <<"null">>]; - [] -> [<<"modification">>, <<"null">>]; - I -> - [A, B] = str:tokens((?PUBSUB):escape(i2l(I)), - <<"@">>), - [A, <<"'", B/binary, "'">>] - end, - Count = case catch - ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where " - "nodeid='">>, - NodeId, <<"';">>]) - of - {selected, [_], [{C}]} -> C; - _ -> <<"0">> - end, - case catch - ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, - NodeId, <<"' and ">>, AttrName, <<" ">>, - Way, <<" ">>, Id, <<" order by ">>, - AttrName, <<" ">>, Order, <<" limit ">>, - i2l(Max), <<" ;">>]) - of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, - <<"modification">>, <<"payload">>], - RItems} -> - case str:len(RItems) of - 0 -> {result, {[], #rsm_out{count = Count}}}; - _ -> - {_, _, _, F, _} = hd(RItems), - Index = case catch - ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where " - "nodeid='">>, - NodeId, <<"' and ">>, - AttrName, <<" > '">>, - F, <<"';">>]) - of - %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; - {selected, [_], [{In}]} -> In; - _ -> <<"0">> - end, - {_, _, _, L, _} = lists:last(RItems), - RsmOut = #rsm_out{count = Count, index = Index, - first = <<"modification@", F/binary>>, - last = <<"modification@", (i2l(L))/binary>>}, - {result, - {lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) - end, - RItems), - RsmOut}} - end; - _ -> {result, {[], none}} - end. - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, none). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, _SubId, RSM) -> - SubKey = jlib:jid_tolower(JID), - GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = - select_affiliation_subscriptions(NodeId, GenKey, - SubKey), - Whitelisted = can_fetch_item(Affiliation, - Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> get_items(NodeId, JID, RSM) - end. - -get_last_items(NodeId, _From, Count) -> - case catch - ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, - NodeId, - <<"' order by modification desc limit ">>, - i2l(Count), <<";">>]) - of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, - <<"modification">>, <<"payload">>], - RItems} -> - {result, - lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end, - RItems)}; - _ -> {result, []} - end. - -%% @spec (NodeId, ItemId) -> [Item] | [] -%% NodeId = mod_pubsub:pubsubNodeId() -%% ItemId = string() -%% Item = mod_pubsub:pubsubItems() -%% @doc <p>Returns an item (one item list), given its reference.</p> -get_item(NodeId, ItemId) -> - I = (?PUBSUB):escape(ItemId), - case catch - ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, - NodeId, <<"' and itemid='">>, I, - <<"';">>]) - of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, - <<"modification">>, <<"payload">>], - [RItem]} -> - {result, raw_to_item(NodeId, RItem)}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end. - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, _SubId) -> - SubKey = jlib:jid_tolower(JID), - GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = - select_affiliation_subscriptions(NodeId, GenKey, - SubKey), - Whitelisted = can_fetch_item(Affiliation, - Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and - not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), - <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> get_item(NodeId, ItemId) - end. - -%% @spec (Item) -> ok | {error, Reason::stanzaError()} -%% Item = mod_pubsub:pubsubItems() -%% @doc <p>Write an item into database.</p> -set_item(Item) -> - {ItemId, NodeId} = Item#pubsub_item.itemid, - I = (?PUBSUB):escape(ItemId), - {C, _} = Item#pubsub_item.creation, - {M, JID} = Item#pubsub_item.modification, - P = encode_jid(JID), - Payload = Item#pubsub_item.payload, - XML = (?PUBSUB):escape(lists:flatten(lists:map(fun - (X) -> - xml:element_to_binary(X) - end, - Payload))), - S = fun ({T1, T2, T3}) -> - lists:flatten([i2l(T1, 6), <<":">>, i2l(T2, 6), <<":">>, - i2l(T3, 6)]) - end, - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, - P, <<"', modification='">>, S(M), - <<"', payload='">>, XML, - <<"' where nodeid='">>, NodeId, - <<"' and itemid='">>, I, <<"';">>]) - of - {updated, 1} -> ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, " - "publisher, creation, modification, payload) " - "values('">>, - NodeId, <<"', '">>, I, <<"', '">>, P, - <<"', '">>, S(C), <<"', '">>, S(M), - <<"', '">>, XML, <<"');">>]) - end, - {result, []}. - -%% @spec (NodeId, ItemId) -> ok | {error, Reason::stanzaError()} -%% NodeId = mod_pubsub:pubsubNodeId() -%% ItemId = string() -%% @doc <p>Delete an item from database.</p> -del_item(NodeId, ItemId) -> - I = (?PUBSUB):escape(ItemId), - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>, - I, <<"' and nodeid='">>, NodeId, <<"';">>]). - -del_items(_, []) -> ok; -del_items(NodeId, [ItemId]) -> del_item(NodeId, ItemId); -del_items(NodeId, ItemIds) -> -%% @doc <p>Return the name of the node if known: Default is to return -%% node id.</p> - I = str:join([[<<"'">>, (?PUBSUB):escape(X), <<"'">>] - || X <- ItemIds], - <<",">>), - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid " - "in (">>, - I, <<") and nodeid='">>, NodeId, <<"';">>]). - -get_item_name(_Host, _Node, Id) -> Id. - -node_to_path(Node) -> str:tokens((Node), <<"/">>). - -path_to_node([]) -> <<>>; -path_to_node(Path) -> -%% @spec (Affiliation, Subscription) -> true | false -%% Affiliation = owner | member | publisher | outcast | none -%% Subscription = subscribed | none -%% @doc Determines if the combination of Affiliation and Subscribed -%% are allowed to get items from a node. - iolist_to_binary(str:join([<<"">> | Path], <<"/">>)). - -can_fetch_item(owner, _) -> true; -can_fetch_item(member, _) -> true; -can_fetch_item(publisher, _) -> true; -can_fetch_item(outcast, _) -> false; -can_fetch_item(none, Subscriptions) -> - is_subscribed(Subscriptions); -can_fetch_item(_Affiliation, _Subscription) -> false. - -is_subscribed(Subscriptions) -> - lists:any(fun ({subscribed, _SubId}) -> true; - (_) -> false - end, - Subscriptions). - -%% Returns the first item where Pred() is true in List -first_in_list(_Pred, []) -> false; -first_in_list(Pred, [H | T]) -> - case Pred(H) of - true -> {value, H}; - _ -> first_in_list(Pred, T) - end. - -itemids(NodeId, {U, S, R}) -> - itemids(NodeId, encode_jid({U, S, R})); -itemids(NodeId, SJID) -> - case catch - ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where " - "nodeid='">>, - NodeId, <<"' and publisher like '">>, - SJID, - <<"%' order by modification desc;">>]) - of - {selected, [<<"itemid">>], RItems} -> - lists:map(fun ({ItemId}) -> ItemId end, RItems); - _ -> [] - end. - -select_affiliation_subscriptions(NodeId, JID) -> - J = encode_jid(JID), - case catch - ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from " - "pubsub_state where nodeid='">>, - NodeId, <<"' and jid='">>, J, <<"';">>]) - of - {selected, [<<"affiliation">>, <<"subscriptions">>], - [{A, S}]} -> - {decode_affiliation(A), decode_subscriptions(S)}; - _ -> {none, []} - end. - -select_affiliation_subscriptions(NodeId, JID, JID) -> - select_affiliation_subscriptions(NodeId, JID); -select_affiliation_subscriptions(NodeId, GenKey, - SubKey) -> - {result, Affiliation} = get_affiliation(NodeId, GenKey), - {result, Subscriptions} = get_subscriptions(NodeId, - SubKey), - {Affiliation, Subscriptions}. - -update_affiliation(NodeId, JID, Affiliation) -> - J = encode_jid(JID), - A = encode_affiliation(Affiliation), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>, - A, <<"' where nodeid='">>, NodeId, - <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " - "affiliation, subscriptions) values('">>, - NodeId, <<"', '">>, J, <<"', '">>, A, - <<"', '');">>]) - end. - -update_subscription(NodeId, JID, Subscription) -> - J = encode_jid(JID), - S = encode_subscriptions(Subscription), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, - S, <<"' where nodeid='">>, NodeId, - <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " - "affiliation, subscriptions) values('">>, - NodeId, <<"', '">>, J, <<"', 'n', '">>, - S, <<"');">>]) - end. - -decode_jid(SJID) -> - jlib:jid_tolower(jlib:string_to_jid(SJID)). - -decode_node(N) -> (?PUBSUB):string_to_node(N). - -decode_affiliation(<<"o">>) -> owner; -decode_affiliation(<<"p">>) -> publisher; -decode_affiliation(<<"m">>) -> member; -decode_affiliation(<<"c">>) -> outcast; -decode_affiliation(_) -> none. - -decode_subscription(<<"s">>) -> subscribed; -decode_subscription(<<"p">>) -> pending; -decode_subscription(<<"u">>) -> unconfigured; -decode_subscription(_) -> none. - -decode_subscriptions(Subscriptions) -> - lists:foldl(fun (Subscription, Acc) -> - case str:tokens(Subscription, <<":">>) of - [S, SubId] -> [{decode_subscription(S), SubId} | Acc]; - _ -> Acc - end - end, - [], str:tokens(Subscriptions, <<",">>)). - -%-spec(encode_jid/1 :: -%( -% JID :: jid() | jid()) -% -> binary() -%). -encode_jid(JID) -> - (?PUBSUB):escape(jlib:jid_to_string(JID)). - -encode_affiliation(owner) -> <<"o">>; -encode_affiliation(publisher) -> <<"p">>; -encode_affiliation(member) -> <<"m">>; -encode_affiliation(outcast) -> <<"c">>; -encode_affiliation(_) -> <<"n">>. - -encode_subscription(subscribed) -> <<"s">>; -encode_subscription(pending) -> <<"p">>; -encode_subscription(unconfigured) -> <<"u">>; -encode_subscription(_) -> <<"n">>. - -encode_subscriptions(Subscriptions) -> - str:join(lists:map(fun ({S, SubId}) -> - <<(encode_subscription(S))/binary, ":", - SubId/binary>> - end, - Subscriptions), - <<",">>). - -%%% record getter/setter - -state_to_raw(NodeId, State) -> - {JID, _} = State#pubsub_state.stateid, - J = encode_jid(JID), - A = encode_affiliation(State#pubsub_state.affiliation), - S = - encode_subscriptions(State#pubsub_state.subscriptions), - [<<"'">>, NodeId, <<"', '">>, J, <<"', '">>, A, - <<"', '">>, S, <<"'">>]. - -raw_to_item(NodeId, - {ItemId, SJID, Creation, Modification, XML}) -> - JID = decode_jid(SJID), - ToTime = fun (Str) -> - [T1, T2, T3] = str:tokens(Str, <<":">>), - {l2i(T1), l2i(T2), l2i(T3)} - end, - Payload = case xml_stream:parse_element(XML) of - {error, _Reason} -> []; - El -> [El] - end, - #pubsub_item{itemid = {ItemId, NodeId}, - creation = {ToTime(Creation), JID}, - modification = {ToTime(Modification), JID}, - payload = Payload}. - -l2i(L) when is_binary(L) -> jlib:binary_to_integer(L); -l2i(I) when is_integer(I) -> I. - -i2l(I) when is_integer(I) -> - iolist_to_binary(integer_to_list(I)); -i2l(L) when is_binary(L) -> L. - -i2l(I, N) when is_integer(I) -> i2l(i2l(I), N); -i2l(L, N) when is_binary(L) -> - case str:len(L) of - N -> L; - C when C > N -> L; - _ -> i2l(<<$0, L/binary>>, N) - end. diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl deleted file mode 100644 index b93a57eb5..000000000 --- a/src/mod_pubsub/node_mb.erl +++ /dev/null @@ -1,190 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Eric Cestari <eric@ohmforce.com> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the pep microblog PubSub plugin. -%%% <p> To be used, mod_pubsub must be configured : -%%% {mod_pubsub, [ % requires mod_caps -%%% {access_createnode, pubsub_createnode}, -%%% {plugins, ["default", "pep","mb"]}, -%%% {pep_mapping, [{"urn:xmpp:microblog", "mb"}]} -%%% ]}, -%%% </p> -%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> - --module(node_mb). - --author('eric@ohmforce.com'). - --include("ejabberd.hrl"). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_pep:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_pep:terminate(Host, ServerHost), ok. - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, false}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, presence}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, true}]. - -features() -> - [<<"create-nodes">>, %* - <<"auto-create">>, %* - <<"auto-subscribe">>, %* - <<"delete-nodes">>, %* - <<"delete-items">>, %* - <<"filtered-notifications">>, %* - <<"modify-affiliations">>, <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"publish">>, %* - <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, %* - <<"retrieve-subscriptions">>, <<"subscribe">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_pep:create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_pep:create_node(NodeId, Owner). - -delete_node(Removed) -> node_pep:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_pep:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_pep:unsubscribe_node(NodeId, Sender, Subscriber, - SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_pep:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_pep:remove_extra_items(NodeId, MaxItems, ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_pep:delete_item(NodeId, Publisher, PublishModel, - ItemId). - -purge_node(NodeId, Owner) -> - node_pep:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_pep:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_pep:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_pep:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_pep:set_affiliation(NodeId, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_pep:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_pep:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_pep:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_pep:set_subscriptions(NodeId, Owner, Subscription, - SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_pep:get_states(NodeId). - -get_state(NodeId, JID) -> - node_pep:get_state(NodeId, JID). - -set_state(State) -> node_pep:set_state(State). - -get_items(NodeId, From) -> - node_pep:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_pep:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_pep:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_pep:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_pep:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_pep:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_pep:node_to_path(Node). - -path_to_node(Path) -> node_pep:path_to_node(Path). diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl deleted file mode 100644 index 38a9bcec7..000000000 --- a/src/mod_pubsub/node_pep.erl +++ /dev/null @@ -1,503 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin. -%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> - --module(node_pep). - --author('christophe.romain@process-one.net'). - --include("ejabberd.hrl"). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts), - complain_if_modcaps_disabled(ServerHost), - ok. - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost), ok. - --spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, false}, - {purge_offline, false}, {persist_items, false}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, presence}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, true}]. - --spec(features/0 :: () -> Features::[binary(),...]). -features() -> - [<<"create-nodes">>, %* - <<"auto-create">>, %* - <<"auto-subscribe">>, %* - <<"delete-nodes">>, %* - <<"delete-items">>, %* - <<"filtered-notifications">>, %* - <<"modify-affiliations">>, <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"publish">>, %* - <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, %* - <<"retrieve-subscriptions">>, <<"subscribe">>]. - - --spec(create_node_permission/6 :: -( - Host :: mod_pubsub:hostPEP(), - ServerHost :: binary(), - NodeId :: mod_pubsub:nodeId(), - _ParentNodeId :: mod_pubsub:nodeId(), - Owner :: jid(), - Access :: atom()) - -> {result, boolean()} -). - -create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - {User, Server, _Resource} = LOwner, - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case Host of - {User, Server, _} -> true; - _ -> false - end; - E -> ?DEBUG("Create not allowed : ~p~n", [E]), false - end - end, - {result, Allowed}. - --spec(create_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} -). -create_node(NodeIdx, Owner) -> - node_hometree:create_node(NodeIdx, Owner). - --spec(delete_node/1 :: -( - Nodes :: [mod_pubsub:pubsubNode(),...]) - -> {result, - {[], - [{mod_pubsub:pubsubNode(), - [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...] - } - } -). - -delete_node(Removed) -> - {result, {_, _, Removed}} = node_hometree:delete_node(Removed), - {result, {[], Removed}}. -% case node_hometree:delete_node(Removed) of -% {result, {_, _, Removed}} -> {result, {[], Removed}}; -% Error -> Error -% end. - --spec(subscribe_node/8 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - AccessModel :: mod_pubsub:accessModel(), - SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - Options :: mod_pubsub:subOptions()) - -> {result, {default, subscribed, mod_pubsub:subId()}} - | {result, {default, subscribed, mod_pubsub:subId(), send_last}} - | {result, {default, pending, mod_pubsub:subId()}} - %%% - | {error, xmlel()} -). -subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options). - --spec(unsubscribe_node/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - SubId :: subId()) - -> {result, default} - % - | {error, xmlel()} -). -unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) -> - case node_hometree:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of - {error, Error} -> {error, Error}; - {result, _} -> {result, []} - end. - --spec(publish_item/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - Max_Items :: non_neg_integer(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, {default, broadcast, [mod_pubsub:itemId()]}} - %%% - | {error, xmlel()} -). -publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload). - --spec(remove_extra_items/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Max_Items :: unlimited | non_neg_integer(), - ItemIds :: [mod_pubsub:itemId()]) - -> {result, - {NewItems::[mod_pubsub:itemId()], - OldItems::[mod_pubsub:itemId()]} - } -). -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). - - --spec(delete_item/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - ItemId :: <<>> | mod_pubsub:itemId()) - -> {result, {default, broadcast}} - %%% - | {error, xmlel()} -). -delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeIdx, Publisher, PublishModel, ItemId). - --spec(purge_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} - | {error, xmlel()} -). -purge_node(NodeIdx, Owner) -> - node_hometree:purge_node(NodeIdx, Owner). - --spec(get_entity_affiliations/2 :: -( - Host :: mod_pubsub:hostPEP(), - Owner :: jid()) - -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} -). -get_entity_affiliations(_Host, Owner) -> - {_, D, _} = SubKey = jlib:jid_tolower(Owner), - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - States = mnesia:match_object(#pubsub_state{stateid = - {GenKey, '_'}, - _ = '_'}), - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(D, config), nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, - affiliation = A}, - Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {{_, D, _}, _}} = - Node -> - [{Node, A} | Acc]; - _ -> Acc - end - end, - [], States), - {result, Reply}. - - --spec(get_node_affiliations/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [{ljid(), mod_pubsub:affiliation()}]} -). -get_node_affiliations(NodeIdx) -> - node_hometree:get_node_affiliations(NodeIdx). - --spec(get_affiliation/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, mod_pubsub:affiliation()} -). -get_affiliation(NodeIdx, Owner) -> - node_hometree:get_affiliation(NodeIdx, Owner). - --spec(set_affiliation/3 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid(), - Affiliation :: mod_pubsub:affiliation()) - -> ok -). -set_affiliation(NodeIdx, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeIdx, Owner, Affiliation). - --spec(get_entity_subscriptions/2 :: -( - Host :: mod_pubsub:hostPEP(), - Owner :: jid()) - -> {result, - [{mod_pubsub:pubsubNode(), - mod_pubsub:subscription(), - mod_pubsub:subId(), - ljid()}] - } -). -get_entity_subscriptions(_Host, Owner) -> - {U, D, _} = SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - States = case SubKey of - GenKey -> - mnesia:match_object(#pubsub_state{stateid = - {{U, D, '_'}, '_'}, - _ = '_'}); - _ -> - mnesia:match_object(#pubsub_state{stateid = - {GenKey, '_'}, - _ = '_'}) - ++ - mnesia:match_object(#pubsub_state{stateid = - {SubKey, '_'}, - _ = '_'}) - end, - NodeTree = case catch - ets:lookup(gen_mod:get_module_proc(D, config), nodetree) - of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, - subscriptions = Ss}, - Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> - lists:foldl(fun - ({subscribed, SubID}, Acc2) -> - [{Node, subscribed, SubID, J} | Acc2]; - ({pending, _SubID}, Acc2) -> - [{Node, pending, J} | Acc2]; - (S, Acc2) -> - [{Node, S, J} | Acc2] - end, Acc, Ss); - _ -> Acc - end - end, - [], States), - {result, Reply}. - --spec(get_node_subscriptions/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, - [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] | - [{ljid(), none},...] - } -). -get_node_subscriptions(NodeIdx) -> - node_hometree:get_node_subscriptions(NodeIdx). - --spec(get_subscriptions/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid()) - -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} -). -get_subscriptions(NodeIdx, Owner) -> - node_hometree:get_subscriptions(NodeIdx, Owner). - --spec(set_subscriptions/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid(), - Subscription :: mod_pubsub:subscription(), - SubId :: mod_pubsub:subId()) - -> ok - %%% - | {error, xmlel()} -). -set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeIdx, Owner, Subscription, SubId). - --spec(get_pending_nodes/2 :: -( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, [mod_pubsub:nodeId()]} -). -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - --spec(get_states/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [mod_pubsub:pubsubState()]} -). -get_states(NodeIdx) -> node_hometree:get_states(NodeIdx). - --spec(get_state/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: ljid()) - -> mod_pubsub:pubsubState() -). -get_state(NodeIdx, JID) -> - node_hometree:get_state(NodeIdx, JID). - --spec(set_state/1 :: -( - State::mod_pubsub:pubsubState()) - -> ok -). -set_state(State) -> node_hometree:set_state(State). - --spec(get_items/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - _From :: jid()) - -> {result, [mod_pubsub:pubsubItem()]} -). -get_items(NodeIdx, From) -> - node_hometree:get_items(NodeIdx, From). - --spec(get_items/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - JID :: jid(), - AccessModel :: mod_pubsub:accessModel(), - Presence_Subscription :: boolean(), - RosterGroup :: boolean(), - _SubId :: mod_pubsub:subId()) - -> {result, [mod_pubsub:pubsubItem()]} - %%% - | {error, xmlel()} -). -get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). - --spec(get_item/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemId :: mod_pubsub:itemId()) - -> {result, mod_pubsub:pubsubItem()} - | {error, xmlel()} -). -get_item(NodeIdx, ItemId) -> - node_hometree:get_item(NodeIdx, ItemId). - --spec(get_item/7 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - ItemId :: mod_pubsub:itemId(), - JID :: jid(), - AccessModel :: mod_pubsub:accessModel(), - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - SubId :: mod_pubsub:subId()) - -> {result, mod_pubsub:pubsubItem()} - | {error, xmlel()} -). -get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, - SubId) -> - node_hometree:get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, - RosterGroup, SubId). - --spec(set_item/1 :: -( - Item::mod_pubsub:pubsubItem()) - -> ok -). -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). - -%%% -%%% Internal -%%% - -%% @doc Check mod_caps is enabled, otherwise show warning. -%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host. -%% Check that the mod_caps module is enabled in that Jabber Host -%% If not, show a warning message in the ejabberd log file. -complain_if_modcaps_disabled(ServerHost) -> - Modules = ejabberd_config:get_local_option({modules, ServerHost}, fun(Ms) when is_list(Ms) -> Ms end), - ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules], - case ModCaps of - [] -> - ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " - "of host ~p. This plugin requires mod_caps " - "to be enabled, but it isn't.", - [ServerHost]); - _ -> ok - end. diff --git a/src/mod_pubsub/node_pep_odbc.erl b/src/mod_pubsub/node_pep_odbc.erl deleted file mode 100644 index 8f4f36a45..000000000 --- a/src/mod_pubsub/node_pep_odbc.erl +++ /dev/null @@ -1,448 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin. -%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> - --module(node_pep_odbc). - --author('christophe.romain@process-one.net'). - --include("ejabberd.hrl"). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB, mod_pubsub_odbc). - --behaviour(gen_pubsub_node). - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, - get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, get_subscriptions/2, - set_subscriptions/4, get_pending_nodes/2, get_states/1, - get_state/2, set_state/1, get_items/7, get_items/6, - get_items/3, get_items/2, get_item/7, get_item/2, - set_item/1, get_item_name/3, get_last_items/3, - node_to_path/1, path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree_odbc:init(Host, ServerHost, Opts), - complain_if_modcaps_disabled(ServerHost), - ok. - -terminate(Host, ServerHost) -> - node_hometree_odbc:terminate(Host, ServerHost), ok. - --spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). -options() -> - [{odbc, true}, - {node_type, pep}, - {deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, false}, - {purge_offline, false}, - {persist_items, false}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, true}]. - --spec(features/0 :: () -> Features::[binary(),...]). -features() -> - [<<"create-nodes">>, %* - <<"auto-create">>, %* - <<"auto-subscribe">>, %* - <<"delete-nodes">>, %* - <<"delete-items">>, %* - <<"filtered-notifications">>, %* - <<"modify-affiliations">>, <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"publish">>, %* - <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, %* - <<"retrieve-subscriptions">>, <<"subscribe">>]. - --spec(create_node_permission/6 :: -( - Host :: mod_pubsub:hostPEP(), - ServerHost :: binary(), - NodeId :: mod_pubsub:nodeId(), - _ParentNodeId :: mod_pubsub:nodeId(), - Owner :: jid(), - Access :: atom()) - -> {result, boolean()} -). - -create_node_permission(Host, ServerHost, _NodeId, _ParentNode, Owner, Access) -> - LOwner = jlib:jid_tolower(Owner), - {User, Server, _Resource} = LOwner, - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case Host of - {User, Server, _} -> true; - _ -> false - end; - E -> ?DEBUG("Create not allowed : ~p~n", [E]), false - end - end, - {result, Allowed}. - --spec(create_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, []} -). -create_node(NodeIdx, Owner) -> - case node_hometree_odbc:create_node(NodeIdx, Owner) of - {result, _} -> {result, []}; - Error -> Error - end. - --spec(delete_node/1 :: -( - Removed :: [mod_pubsub:pubsubNode(),...]) - -> {result, - {[], - [{mod_pubsub:pubsubNode(), - [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}] - } - } -). -delete_node(Removed) -> - case node_hometree_odbc:delete_node(Removed) of - {result, {_, _, Removed}} -> {result, {[], Removed}}; - Error -> Error - end. - --spec(subscribe_node/8 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: ljid(), - AccessModel :: mod_pubsub:accessModel(), - SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', - PresenceSubscription :: boolean(), - RosterGroup :: boolean(), - Options :: mod_pubsub:subOptions()) - -> {result, {default, subscribed, mod_pubsub:subId()}} - | {result, {default, subscribed, mod_pubsub:subId(), send_last}} - | {result, {default, pending, mod_pubsub:subId()}} - %%% - | {error, _} - | {error, _, binary()} -). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree_odbc:subscribe_node(NodeId, Sender, - Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, - Options). - - --spec(unsubscribe_node/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Sender :: jid(), - Subscriber :: jid(), - SubId :: subId()) - -> {result, []} - % - | {error, _} - | {error, _, binary()} -). -unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) -> - case node_hometree_odbc:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of - {error, Error} -> {error, Error}; - {result, _} -> {result, []} - end. - --spec(publish_item/6 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - Max_Items :: non_neg_integer(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) - -> {result, {default, broadcast, [mod_pubsub:itemId()]}} - %%% - | {error, _} -). -publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree_odbc:publish_item(NodeIdx, Publisher, - Model, MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree_odbc:remove_extra_items(NodeId, MaxItems, - ItemIds). - --spec(delete_item/4 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Publisher :: jid(), - PublishModel :: mod_pubsub:publishModel(), - ItemId :: <<>> | mod_pubsub:itemId()) - -> {result, {default, broadcast}} - %%% - | {error, _} -). -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree_odbc:delete_item(NodeId, Publisher, - PublishModel, ItemId). - --spec(purge_node/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: jid()) - -> {result, {default, broadcast}} - | {error, _} -). -purge_node(NodeIdx, Owner) -> - node_hometree_odbc:purge_node(NodeIdx, Owner). - --spec(get_entity_affiliations/2 :: -( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} -). -get_entity_affiliations(_Host, Owner) -> - OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner). - --spec(get_node_affiliations/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> {result, [{ljid(), mod_pubsub:affiliation()}]} -). -get_node_affiliations(NodeIdx) -> - node_hometree_odbc:get_node_affiliations(NodeIdx). - --spec(get_affiliation/2 :: -( - NodeIdx :: mod_pubsub:nodeIdx(), - Owner :: ljid()) - -> {result, mod_pubsub:affiliation()} -). -get_affiliation(NodeIdx, Owner) -> - node_hometree_odbc:get_affiliation(NodeIdx, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation). - -get_entity_subscriptions(_Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - Host = (?PUBSUB):escape(element(2, SubKey)), - SJ = node_hometree_odbc:encode_jid(SubKey), - GJ = node_hometree_odbc:encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_nod" - "e n where i.nodeid = n.nodeid and jid " - "like '">>, - GJ, <<"%' and host like '%@">>, Host, <<"';">>]; - _ -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_nod" - "e n where i.nodeid = n.nodeid and jid " - "in ('">>, - SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, - <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, - <<"jid">>, <<"subscriptions">>], - RItems} -> - lists:map(fun ({H, N, T, I, J, S}) -> - O = node_hometree_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, - {N, - <<"">>, - T, - I}), - {Node, - node_hometree_odbc:decode_subscriptions(S), - node_hometree_odbc:decode_jid(J)} - end, - RItems); - _ -> [] - end, - {result, Reply}. - -get_entity_subscriptions_for_send_last(_Host, Owner) -> - SubKey = jlib:jid_tolower(Owner), - GenKey = jlib:jid_remove_resource(SubKey), - Host = (?PUBSUB):escape(element(2, SubKey)), - SJ = node_hometree_odbc:encode_jid(SubKey), - GJ = node_hometree_odbc:encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_nod" - "e n, pubsub_node_option o where i.nodeid " - "= n.nodeid and n.nodeid = o.nodeid and " - "name='send_last_published_item' and " - "val='on_sub_and_presence' and jid like " - "'">>, - GJ, <<"%' and host like '%@">>, Host, <<"';">>]; - _ -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_nod" - "e n, pubsub_node_option o where i.nodeid " - "= n.nodeid and n.nodeid = o.nodeid and " - "name='send_last_published_item' and " - "val='on_sub_and_presence' and jid in " - "('">>, - SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, - <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, - <<"jid">>, <<"subscriptions">>], - RItems} -> - lists:map(fun ({H, N, T, I, J, S}) -> - O = node_hometree_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, - {N, - <<"">>, - T, - I}), - {Node, - node_hometree_odbc:decode_subscriptions(S), - node_hometree_odbc:decode_jid(J)} - end, - RItems); - _ -> [] - end, - {result, Reply}. - -get_node_subscriptions(NodeId) -> - node_hometree_odbc:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree_odbc:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree_odbc:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree_odbc:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> - node_hometree_odbc:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree_odbc:get_state(NodeId, JID). - -set_state(State) -> node_hometree_odbc:set_state(State). - -get_items(NodeId, From) -> - node_hometree_odbc:get_items(NodeId, From). - -get_items(NodeId, From, RSM) -> - node_hometree_odbc:get_items(NodeId, From, RSM). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, none). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree_odbc:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(NodeId, JID, Count) -> - node_hometree_odbc:get_last_items(NodeId, JID, Count). - -get_item(NodeId, ItemId) -> - node_hometree_odbc:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree_odbc:get_item(NodeId, ItemId, JID, - AccessModel, PresenceSubscription, RosterGroup, - SubId). - -set_item(Item) -> node_hometree_odbc:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree_odbc:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat_odbc:node_to_path(Node). - -path_to_node(Path) -> node_flat_odbc:path_to_node(Path). - -%%% -%%% Internal -%%% - -%% @doc Check mod_caps is enabled, otherwise show warning. -%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host. -%% Check that the mod_caps module is enabled in that Jabber Host -%% If not, show a warning message in the ejabberd log file. -complain_if_modcaps_disabled(ServerHost) -> - Modules = ejabberd_config:get_local_option({modules, - ServerHost}, - fun(Ms) when is_list(Ms) -> Ms end), - ModCaps = [mod_caps_enabled - || {mod_caps, _Opts} <- Modules], - case ModCaps of - [] -> - ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " - "of host ~p. This plugin requires mod_caps " - "to be enabled, but it isn't.", - [ServerHost]); - _ -> ok - end. diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl deleted file mode 100644 index 27b7158a1..000000000 --- a/src/mod_pubsub/node_private.erl +++ /dev/null @@ -1,184 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_private). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% Note on function definition -%% included is all defined plugin function -%% it's possible not to define some function at all -%% in that case, warning will be generated at compilation -%% and function call will fail, -%% then mod_pubsub will call function from node_hometree -%% (this makes code cleaner, but execution a little bit longer) - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, whitelist}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, false}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, <<"delete-nodes">>, - <<"delete-items">>, <<"instant-nodes">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, - Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl deleted file mode 100644 index 3c391cfe8..000000000 --- a/src/mod_pubsub/node_public.erl +++ /dev/null @@ -1,186 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(node_public). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_node). - -%% Note on function definition -%% included is all defined plugin function -%% it's possible not to define some function at all -%% in that case, warning will be generated at compilation -%% and function call will fail, -%% then mod_pubsub will call function from node_hometree -%% (this makes code cleaner, but execution a little bit longer) - -%% API definition --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/6, get_items/2, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, {notify_config, false}, - {notify_delete, false}, {notify_retract, true}, - {purge_offline, false}, {persist_items, true}, - {max_items, ?MAXITEMS}, {subscribe, true}, - {access_model, open}, {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, <<"delete-nodes">>, - <<"delete-items">>, <<"instant-nodes">>, - <<"outcast-affiliation">>, <<"persistent-items">>, - <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, - <<"retrieve-affiliations">>, <<"retrieve-items">>, - <<"retrieve-subscriptions">>, <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, - ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, - Node, ParentNode, Owner, Access). - -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, - Subscriber, SubID). - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, - Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, - MaxItems, ItemId, Payload). - -remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, - ItemIds). - -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, - PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, - Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(NodeId) -> - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, - Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(NodeId) -> node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> node_hometree:set_item(Item). - -%% @doc <p>Return the name of the node if known: Default is to return -%% node id.</p> -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> node_flat:node_to_path(Node). - -path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/nodetree_dag.erl b/src/mod_pubsub/nodetree_dag.erl deleted file mode 100644 index 2be3e3522..000000000 --- a/src/mod_pubsub/nodetree_dag.erl +++ /dev/null @@ -1,329 +0,0 @@ -%%% ==================================================================== -%%% ``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. -%%% -%%% @author Brian Cully <bjc@kublai.com> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(nodetree_dag). - --author('bjc@kublai.com'). - -%% API --export([init/3, terminate/2, options/0, set_node/1, - get_node/3, get_node/2, get_node/1, get_nodes/2, - get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, - get_subnodes/3, get_subnodes_tree/3, create_node/6, - delete_node/2]). - --include_lib("stdlib/include/qlc.hrl"). - --include("ejabberd.hrl"). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_nodetree). - --define(DEFAULT_NODETYPE, leaf). - --define(DEFAULT_PARENTS, []). - --define(DEFAULT_CHILDREN, []). - --compile(export_all). - -%%==================================================================== -%% API -%%==================================================================== -init(Host, ServerHost, Opts) -> - nodetree_tree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - nodetree_tree:terminate(Host, ServerHost). - --spec(create_node/6 :: -( - Key :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId(), - Type :: binary(), - Owner :: jid(), - Options :: mod_pubsub:nodeOptions(), - Parents :: [mod_pubsub:nodeId()]) - -> {ok, NodeIdx::mod_pubsub:nodeIdx()} - | {error, xmlel()} -). -create_node(Key, NodeID, Type, Owner, Options, Parents) -> - OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - case find_node(Key, NodeID) of - false -> - NodeIdx = pubsub_index:new(node), - N = #pubsub_node{nodeid = oid(Key, NodeID), id = NodeIdx, - type = Type, parents = Parents, owners = [OwnerJID], - options = Options}, - case set_node(N) of - ok -> {ok, NodeIdx}; - Other -> Other - end; - _ -> {error, ?ERR_CONFLICT} - end. - --spec(set_node/1 :: -( - PubsubNode::mod_pubsub:pubsubNode()) - -> ok - %%% - | {error, xmlel()} -). -set_node(#pubsub_node{nodeid = {Key, _}, owners = Owners, options = Options} = - Node) -> - Parents = find_opt(collection, ?DEFAULT_PARENTS, Options), - case validate_parentage(Key, Owners, Parents) of - true -> - mnesia:write(Node#pubsub_node{parents = Parents}); - Other -> Other - end. - --spec(delete_node/2 :: -( - Key :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode(),...] - %%% - | {error, xmlel()} -). -delete_node(Key, NodeID) -> - case find_node(Key, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - lists:foreach(fun (#pubsub_node{options = Opts} = - Child) -> - NewOpts = remove_config_parent(NodeID, Opts), - Parents = find_opt(collection, ?DEFAULT_PARENTS, - NewOpts), - ok = mnesia:write(pubsub_node, - Child#pubsub_node{parents = - Parents, - options = - NewOpts}, - write) - end, - get_subnodes(Key, NodeID)), - pubsub_index:free(node, Node#pubsub_node.id), - mnesia:delete_object(pubsub_node, Node, write), - [Node] - end. - -options() -> nodetree_tree:options(). - -get_node(Host, NodeID, _From) -> get_node(Host, NodeID). - --spec(get_node/2 :: -( - Host :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId()) - -> mod_pubsub:pubsubNode() - %%% - | {error, xmlel} -). -get_node(Host, NodeID) -> - case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - Node -> Node - end. - --spec(get_node/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> mod_pubsub:pubsubNode() - | {error, xmlel()} -). -get_node(NodeId) -> nodetree_tree:get_node(NodeId). - -get_nodes(Key, From) -> - nodetree_tree:get_nodes(Key, From). - --spec(get_nodes/1 :: -( - Host::mod_pubsub:host()) - -> [mod_pubsub:pubsubNode()] -). -get_nodes(Key) -> nodetree_tree:get_nodes(Key). - --spec(get_parentnodes/3 :: -( - Host :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId(), - _From :: _) - -> [mod_pubsub:pubsubNode()] - %%% - | {error, xmlel()} -). -get_parentnodes(Host, NodeID, _From) -> - case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - #pubsub_node{parents = Parents} -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, NNode}} = N - <- mnesia:table(pubsub_node), - Parent <- Parents, Host == NHost, Parent == NNode]), - qlc:e(Q) - end. - -get_parentnodes_tree(Host, NodeID, _From) -> - Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) -> - NID == NNodeID - end, - Tr = fun (#pubsub_node{parents = Parents}) -> Parents - end, - traversal_helper(Pred, Tr, Host, [NodeID]). - -get_subnodes(Host, NodeID, _From) -> - get_subnodes(Host, NodeID). - --spec(get_subnodes/2 :: -( - Host :: mod_pubsub:hostPubsub(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). -get_subnodes(Host, <<>>) -> - get_subnodes_helper(Host, <<>>); -get_subnodes(Host, NodeID) -> - case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - _ -> get_subnodes_helper(Host, NodeID) - end. - --spec(get_subnodes_helper/2 :: -( - Host :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). -get_subnodes_helper(Host, NodeID) -> - Q = qlc:q([Node - || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = - Node - <- mnesia:table(pubsub_node), - Host == NHost, lists:member(NodeID, Parents)]), - qlc:e(Q). - -get_subnodes_tree(Host, NodeID, From) -> - Pred = fun (NID, #pubsub_node{parents = Parents}) -> - lists:member(NID, Parents) - end, - Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end, - traversal_helper(Pred, Tr, 1, Host, [NodeID], - [{0, [get_node(Host, NodeID, From)]}]). - -%%==================================================================== -%% Internal functions -%%==================================================================== -oid(Key, Name) -> {Key, Name}. - -%% Key = jlib:jid() | host() -%% NodeID = string() --spec(find_node/2 :: -( - Key :: mod_pubsub:hostPubsub(), - NodeID :: mod_pubsub:nodeId()) - -> mod_pubsub:pubsubNode() | false -). -find_node(Key, NodeID) -> - case mnesia:read(pubsub_node, oid(Key, NodeID), read) of - [] -> false; - [Node] -> Node - end. - -%% Key = jlib:jid() | host() -%% Default = term() -%% Options = [{Key = atom(), Value = term()}] -find_opt(Key, Default, Options) -> - case lists:keysearch(Key, 1, Options) of - {value, {Key, Val}} -> Val; - _ -> Default - end. - --spec(traversal_helper/4 :: -( - Pred :: fun(), - Tr :: fun(), - Host :: mod_pubsub:hostPubsub(), - NodeId :: [mod_pubsub:pubsubNode(),...]) - -> [{Depth::non_neg_integer(), Nodes::[mod_pubsub:pubsubNode(),...]}] -). - -traversal_helper(Pred, Tr, Host, NodeIDs) -> - traversal_helper(Pred, Tr, 0, Host, NodeIDs, []). - -traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) -> - Acc; -traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) -> - Q = qlc:q([Node - || #pubsub_node{nodeid = {NHost, _}} = Node - <- mnesia:table(pubsub_node), - NodeID <- NodeIDs, Host == NHost, Pred(NodeID, Node)]), - Nodes = qlc:e(Q), - IDs = lists:flatmap(Tr, Nodes), - traversal_helper(Pred, Tr, Depth + 1, Host, IDs, - [{Depth, Nodes} | Acc]). - -remove_config_parent(NodeID, Options) -> - remove_config_parent(NodeID, Options, []). - -remove_config_parent(_NodeID, [], Acc) -> - lists:reverse(Acc); -remove_config_parent(NodeID, [{collection, Parents} | T], Acc) -> - remove_config_parent(NodeID, T, - [{collection, lists:delete(NodeID, Parents)} | Acc]); -remove_config_parent(NodeID, [H | T], Acc) -> - remove_config_parent(NodeID, T, [H | Acc]). - --spec(validate_parentage/3 :: -( - Key :: mod_pubsub:hostPubsub(), - Owners :: [ljid(),...], - Parent_NodeIds :: [mod_pubsub:nodeId()]) - -> true - %%% - | {error, xmlel()} -). -validate_parentage(_Key, _Owners, []) -> true; -validate_parentage(Key, Owners, [[] | T]) -> - validate_parentage(Key, Owners, T); -validate_parentage(Key, Owners, [<<>> | T]) -> - validate_parentage(Key, Owners, T); -validate_parentage(Key, Owners, [ParentID | T]) -> - case find_node(Key, ParentID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - #pubsub_node{owners = POwners, options = POptions} -> - NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions), - MutualOwners = [O || O <- Owners, PO <- POwners, O == PO], - case {MutualOwners, NodeType} of - {[], _} -> {error, ?ERR_FORBIDDEN}; - {_, collection} -> validate_parentage(Key, Owners, T); - {_, _} -> {error, ?ERR_NOT_ALLOWED} - end - end. - -%% @spec (Host) -> jid() -%% Host = host() -%% @doc <p>Generate pubsub service JID.</p> -service_jid(Host) -> - case Host of - {U, S, _} -> jlib:make_jid(U, S, <<>>); - _ -> jlib:make_jid(<<>>, Host, <<>>) - end. diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl deleted file mode 100644 index 23159d7a2..000000000 --- a/src/mod_pubsub/nodetree_tree.erl +++ /dev/null @@ -1,322 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin. -%%% <p>It is used as a default for all unknown PubSub node type. It can serve -%%% as a developer basis and reference to build its own custom pubsub node tree -%%% types.</p> -%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p> -%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin -%%% development is still a work in progress. However, the system is already -%%% useable and useful as is. Please, send us comments, feedback and -%%% improvements.</p> - --module(nodetree_tree). - --author('christophe.romain@process-one.net'). - --include_lib("stdlib/include/qlc.hrl"). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_nodetree). - --export([init/3, terminate/2, options/0, set_node/1, - get_node/3, get_node/2, get_node/1, get_nodes/2, - get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, - get_subnodes/3, get_subnodes_tree/3, create_node/6, - delete_node/2]). - -%% ================ -%% API definition -%% ================ - -%% @spec (Host, ServerHost, Options) -> ok -%% Host = string() -%% ServerHost = string() -%% Options = [{atom(), term()}] -%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must -%% implement this function. It can return anything.</p> -%% <p>This function is mainly used to trigger the setup task necessary for the -%% plugin. It can be used for example by the developer to create the specific -%% module database schema if it does not exists yet.</p> -init(_Host, _ServerHost, _Options) -> - mnesia:create_table(pubsub_node, - [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_node)}]), - mnesia:add_table_index(pubsub_node, id), - NodesFields = record_info(fields, pubsub_node), - case mnesia:table_info(pubsub_node, attributes) of - NodesFields -> ok; - _ -> ok - end, - %% mnesia:transform_table(pubsub_state, ignore, StatesFields) - ok. -%% @spec (Host, ServerHost) -> ok -%% Host = string() -%% ServerHost = string() - -%% @spec () -> Options -%% Options = [mod_pubsub:nodeOption()] -%% @doc Returns the default pubsub node tree options. -terminate(_Host, _ServerHost) -> ok. - -options() -> [{virtual_tree, false}]. - -%% @spec (Node) -> ok | {error, Reason} -%% Node = mod_pubsub:pubsubNode() -%% Reason = mod_pubsub:stanzaError() --spec(set_node/1 :: -( - Node::mod_pubsub:pubsubNode()) - -> ok -). -set_node(Node) when is_record(Node, pubsub_node) -> - mnesia:write(Node). -%set_node(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. - -get_node(Host, Node, _From) -> get_node(Host, Node). - -%% @spec (Host, NodeId) -> Node | {error, Reason} -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% Node = mod_pubsub:pubsubNode() -%% Reason = mod_pubsub:stanzaError() --spec(get_node/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> mod_pubsub:pubsubNode() - | {error, xmlel()} -). -get_node(Host, NodeId) -> - case catch mnesia:read({pubsub_node, {Host, NodeId}}) of - [Record] when is_record(Record, pubsub_node) -> Record; - [] -> {error, ?ERR_ITEM_NOT_FOUND} -% Error -> Error - end. - --spec(get_node/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> mod_pubsub:pubsubNode() - | {error, xmlel()} -). -get_node(NodeIdx) -> - case catch mnesia:index_read(pubsub_node, NodeIdx, #pubsub_node.id) of - [Record] when is_record(Record, pubsub_node) -> Record; - [] -> {error, ?ERR_ITEM_NOT_FOUND} -% Error -> Error - end. - -get_nodes(Host, _From) -> get_nodes(Host). - -%% @spec (Host) -> Nodes | {error, Reason} -%% Host = mod_pubsub:host() -%% Nodes = [mod_pubsub:pubsubNode()] -%% Reason = {aborted, atom()} --spec(get_nodes/1 :: -( - Host::mod_pubsub:host()) - -> [mod_pubsub:pubsubNode()] -). -get_nodes(Host) -> - mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}). - -%% @spec (Host, Node, From) -> [] -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% From = mod_pubsub:jid() -%% @doc <p>Default node tree does not handle parents, return empty list.</p> -get_parentnodes(_Host, _NodeId, _From) -> []. - -%% @spec (Host, NodeId, From) -> [{Depth, Node}] | [] -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% From = mod_pubsub:jid() -%% Depth = integer() -%% Node = mod_pubsub:pubsubNode() -%% @doc <p>Default node tree does not handle parents, return a list -%% containing just this node.</p> --spec(get_parentnodes_tree/3 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId(), - From :: jid()) - -> [{0, [mod_pubsub:pubsubNode(),...]}] -). -get_parentnodes_tree(Host, NodeId, From) -> - case get_node(Host, NodeId, From) of - Node when is_record(Node, pubsub_node) -> [{0, [Node]}]; - _Error -> [] - end. - -%% @spec (Host, NodeId, From) -> Nodes -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% From = mod_pubsub:jid() -%% Nodes = [mod_pubsub:pubsubNode()] -get_subnodes(Host, NodeId, _From) -> - get_subnodes(Host, NodeId). - --spec(get_subnodes/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). -get_subnodes(Host, <<>>) -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = - N - <- mnesia:table(pubsub_node), - Host == NHost, Parents == []]), - qlc:e(Q); -get_subnodes(Host, Node) -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = - N - <- mnesia:table(pubsub_node), - Host == NHost, lists:member(Node, Parents)]), - qlc:e(Q). - -get_subnodes_tree(Host, Node, _From) -> - get_subnodes_tree(Host, Node). - -%% @spec (Host, NodeId) -> Nodes -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% Nodes = [] | [mod_pubsub:pubsubNode()] --spec(get_subnodes_tree/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). -get_subnodes_tree(Host, NodeId) -> - case get_node(Host, NodeId) of - {error, _} -> []; - Rec -> - BasePlugin = jlib:binary_to_atom(<<"node_", - (Rec#pubsub_node.type)/binary>>), - BasePath = BasePlugin:node_to_path(NodeId), - mnesia:foldl(fun (#pubsub_node{nodeid = {H, N}} = R, - Acc) -> - Plugin = jlib:binary_to_atom(<<"node_", - (R#pubsub_node.type)/binary>>), - Path = Plugin:node_to_path(N), - case lists:prefix(BasePath, Path) and (H == Host) of - true -> [R | Acc]; - false -> Acc - end - end, - [], pubsub_node) - end. - -%% @spec (Host, NodeId, Type, Owner, Options, Parents) -> -%% {ok, NodeIdx} | {error, Reason} -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% Type = mod_pubsub:nodeType() -%% Owner = mod_pubsub:jid() -%% Options = [mod_pubsub:nodeOption()] -%% Parents = [] | [mod_pubsub:nodeId()] -%% NodeIdx = mod_pubsub:nodeIdx() -%% Reason = mod_pubsub:stanzaError() --spec(create_node/6 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId(), - Type :: binary(), - Owner :: jid(), - Options :: mod_pubsub:nodeOptions(), - Parents :: [mod_pubsub:nodeId()]) - -> {ok, NodeIdx::mod_pubsub:nodeIdx()} - %%% - | {error, xmlel()} -). -create_node(Host, NodeId, Type, Owner, Options, Parents) -> - BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - case catch mnesia:read({pubsub_node, {Host, NodeId}}) of - [] -> - ParentExists = case Host of - {_U, _S, _R} -> - %% This is special case for PEP handling - %% PEP does not uses hierarchy - true; - _ -> - case Parents of - [] -> true; - [Parent | _] -> - case catch mnesia:read({pubsub_node, - {Host, Parent}}) - of - [#pubsub_node{owners = - [{[], Host, []}]}] -> - true; - [#pubsub_node{owners = Owners}] -> - lists:member(BJID, Owners); - _ -> false - end; - _ -> false - end - end, - case ParentExists of - true -> - NodeIdx = pubsub_index:new(node), - mnesia:write(#pubsub_node{nodeid = {Host, NodeId}, - id = NodeIdx, parents = Parents, - type = Type, owners = [BJID], - options = Options}), - {ok, NodeIdx}; - false -> {error, ?ERR_FORBIDDEN} - end; - _ -> {error, ?ERR_CONFLICT} - end. - -%% @spec (Host, NodeId) -> Removed -%% Host = mod_pubsub:host() -%% NodeId = mod_pubsub:nodeId() -%% Removed = [mod_pubsub:pubsubNode()] --spec(delete_node/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode(),...] -). -delete_node(Host, NodeId) -> - Removed = get_subnodes_tree(Host, NodeId), - lists:foreach(fun (#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) -> - pubsub_index:free(node, SubNodeIdx), - mnesia:delete({pubsub_node, {Host, SubNodeId}}) - end, - Removed), - Removed. diff --git a/src/mod_pubsub/nodetree_tree_odbc.erl b/src/mod_pubsub/nodetree_tree_odbc.erl deleted file mode 100644 index 9756b897b..000000000 --- a/src/mod_pubsub/nodetree_tree_odbc.erl +++ /dev/null @@ -1,466 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin. -%%% <p>It is used as a default for all unknown PubSub node type. It can serve -%%% as a developer basis and reference to build its own custom pubsub node tree -%%% types.</p> -%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p> -%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin -%%% development is still a work in progress. However, the system is already -%%% useable and useful as is. Please, send us comments, feedback and -%%% improvements.</p> - --module(nodetree_tree_odbc). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB, mod_pubsub_odbc). - --define(PLUGIN_PREFIX, <<"node_">>). - --behaviour(gen_pubsub_nodetree). - --export([init/3, terminate/2, options/0, set_node/1, - get_node/3, get_node/2, get_node/1, get_nodes/2, - get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, - get_subnodes/3, get_subnodes_tree/3, create_node/6, - delete_node/2]). - --export([raw_to_node/2]). - -%% ================ -%% API definition -%% ================ - -%% @spec (Host, ServerHost, Opts) -> any() -%% Host = mod_pubsub:host() -%% ServerHost = host() -%% Opts = list() -%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must -%% implement this function. It can return anything.</p> -%% <p>This function is mainly used to trigger the setup task necessary for the -%% plugin. It can be used for example by the developer to create the specific -%% module database schema if it does not exists yet.</p> -%% @spec () -> [Option] -%% Option = mod_pubsub:nodetreeOption() -%% @doc Returns the default pubsub node tree options. -%% @spec (Host, Node, From) -> pubsubNode() | {error, Reason} -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -init(_Host, _ServerHost, _Opts) -> ok. - -terminate(_Host, _ServerHost) -> ok. - -options() -> [{virtual_tree, false}, {odbc, true}]. - -get_node(Host, Node, _From) -> get_node(Host, Node). - --spec(get_node/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> mod_pubsub:pubsubNode() - | {error, _} -). -get_node(Host, Node) -> - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(Node), - case catch - ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and node='">>, N, <<"';">>]) - of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], - [RItem]} -> - raw_to_node(Host, RItem); - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end. - --spec(get_node/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> mod_pubsub:pubsubNode() - | {error, _} -). -get_node(NodeIdx) -> - case catch - ejabberd_odbc:sql_query_t([<<"select host, node, parent, type from " - "pubsub_node where nodeid='">>, - NodeIdx, <<"';">>]) - of - {selected, - [<<"host">>, <<"node">>, <<"parent">>, <<"type">>], - [{Host, Node, Parent, Type}]} -> - raw_to_node(Host, {Node, Parent, Type, NodeIdx}); - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end. - -%% @spec (Host, From) -> [pubsubNode()] | {error, Reason} -%% Host = mod_pubsub:host() | mod_pubsub:jid() -get_nodes(Host, _From) -> get_nodes(Host). - --spec(get_nodes/1 :: -( - Host::mod_pubsub:host()) - -> [mod_pubsub:pubsubNode()] -). -get_nodes(Host) -> - H = (?PUBSUB):escape(Host), - case catch - ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"';">>]) - of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], - RItems} -> - lists:map(fun (Item) -> raw_to_node(Host, Item) end, - RItems); - _ -> [] - end. - -%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} -%% Host = mod_pubsub:host() | mod_pubsub:jid() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% Depth = integer() -%% Record = pubsubNode() -%% @doc <p>Default node tree does not handle parents, return empty list.</p> -%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} -%% Host = mod_pubsub:host() | mod_pubsub:jid() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% Depth = integer() -%% Record = pubsubNode() -%% @doc <p>Default node tree does not handle parents, return a list -%% containing just this node.</p> -get_parentnodes(_Host, _Node, _From) -> []. - --spec(get_parentnodes_tree/3 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId(), - From :: jid()) - -> [{0, [mod_pubsub:pubsubNode(),...]}] -). - -get_parentnodes_tree(Host, Node, From) -> - case get_node(Host, Node, From) of - N when is_record(N, pubsub_node) -> [{0, [N]}]; - _Error -> [] - end. - -get_subnodes(Host, Node, _From) -> - get_subnodes(Host, Node). - -%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason} -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() --spec(get_subnodes/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). -get_subnodes(Host, Node) -> - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(Node), - case catch - ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and parent='">>, N, <<"';">>]) - of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], - RItems} -> - lists:map(fun (Item) -> raw_to_node(Host, Item) end, - RItems); - _ -> [] - end. - -get_subnodes_tree(Host, Node, _From) -> - get_subnodes_tree(Host, Node). - -%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason} -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() --spec(get_subnodes_tree/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). - -get_subnodes_tree(Host, Node) -> - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(Node), - case catch - ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " - "pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%';">>]) - of - {selected, - [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], - RItems} -> - lists:map(fun (Item) -> raw_to_node(Host, Item) end, - RItems); - _ -> [] - end. - -%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason} -%% Host = mod_pubsub:host() | mod_pubsub:jid() -%% Node = mod_pubsub:pubsubNode() -%% NodeType = mod_pubsub:nodeType() -%% Owner = mod_pubsub:jid() -%% Options = list() -%% Parents = list() - --spec(create_node/6 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId(), - Type :: binary(), - Owner :: jid(), - Options :: mod_pubsub:nodeOptions(), - Parents :: [mod_pubsub:nodeId()]) - -> {ok, NodeIdx::mod_pubsub:nodeIdx()} - %%% - | {error, _} -). - -create_node(Host, Node, Type, Owner, Options, Parents) -> - BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - case nodeid(Host, Node) of - {error, ?ERR_ITEM_NOT_FOUND} -> - ParentExists = case Host of - {_U, _S, _R} -> - %% This is special case for PEP handling - %% PEP does not uses hierarchy - true; - _ -> - case Parents of - [] -> true; - [Parent | _] -> - case nodeid(Host, Parent) of - {result, PNodeId} -> - case nodeowners(PNodeId) of - [{<<>>, Host, <<>>}] -> true; - Owners -> - lists:member(BJID, Owners) - end; - _ -> false - end; - _ -> false - end - end, - case ParentExists of - true -> - case set_node(#pubsub_node{nodeid = {Host, Node}, - parents = Parents, type = Type, - options = Options}) - of - {result, NodeId} -> {ok, NodeId}; - Other -> Other - end; - false -> {error, ?ERR_FORBIDDEN} - end; - {result, _} -> {error, ?ERR_CONFLICT}; - Error -> Error - end. - -%% @spec (Host, Node) -> [mod_pubsub:node()] -%% Host = mod_pubsub:host() | mod_pubsub:jid() -%% Node = mod_pubsub:pubsubNode() --spec(delete_node/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> [mod_pubsub:pubsubNode()] -). - -delete_node(Host, Node) -> - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(Node), - Removed = get_subnodes_tree(Host, Node), - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%';">>]), - Removed. - -%% helpers --spec(raw_to_node/2 :: -( - Host :: mod_pubus:host(), - _ :: {NodeId::mod_pubsub:nodeId(), - Parent::mod_pubsub:nodeId(), - Type::binary(), - NodeIdx::mod_pubsub:nodeIdx()}) - -> mod_pubsub:pubsubNode() -). -raw_to_node(Host, {Node, Parent, Type, NodeIdx}) -> - Options = case catch - ejabberd_odbc:sql_query_t([<<"select name,val from pubsub_node_option " - "where nodeid='">>, - NodeIdx, <<"';">>]) - of - {selected, [<<"name">>, <<"val">>], ROptions} -> - DbOpts = lists:map(fun ({Key, Value}) -> - RKey = - jlib:binary_to_atom(Key), - Tokens = element(2, - erl_scan:string(<<Value/binary, - ".">>)), - RValue = element(2, - erl_parse:parse_term(Tokens)), - {RKey, RValue} - end, - ROptions), - Module = - jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, - Type/binary>>), - StdOpts = Module:options(), - lists:foldl(fun ({Key, Value}, Acc) -> - lists:keyreplace(Key, 1, Acc, - {Key, Value}) - end, - StdOpts, DbOpts); - _ -> [] - end, -%% @spec (NodeRecord) -> ok | {error, Reason} -%% Record = mod_pubsub:pubsub_node() - #pubsub_node{nodeid = - {Host, Node}, - parents = [Parent], - id = NodeIdx, type = Type, options = Options}. - --spec(set_node/1 :: -( - Record::mod_pubsub:pubsubNode()) - -> {result, NodeIdx::mod_pubsub:nodeIdx()} - %%% - | {error, _} -). -set_node(Record) -> - {Host, Node} = Record#pubsub_node.nodeid, - Parent = case Record#pubsub_node.parents of - [] -> <<>>; - [First | _] -> First - end, - Type = Record#pubsub_node.type, - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(Node), - P = (?PUBSUB):escape(Parent), - NodeIdx = case nodeid(Host, Node) of - {result, OldNodeIdx} -> - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where " - "nodeid='">>, - OldNodeIdx, <<"';">>]), - catch - ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>, - H, <<"' node='">>, N, - <<"' parent='">>, P, - <<"' type='">>, Type, - <<"' where nodeid='">>, - OldNodeIdx, <<"';">>]), - OldNodeIdx; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_node(host, node, " - "parent, type) values('">>, - H, <<"', '">>, N, <<"', '">>, P, - <<"', '">>, Type, <<"');">>]), - case nodeid(Host, Node) of - {result, NewNodeIdx} -> NewNodeIdx; - _ -> none % this should not happen - end - end, - case NodeIdx of - none -> {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - lists:foreach(fun ({Key, Value}) -> - SKey = iolist_to_binary(atom_to_list(Key)), - SValue = - (?PUBSUB):escape(lists:flatten(io_lib:fwrite("~p", - [Value]))), - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_node_option(nodeid, " - "name, val) values('">>, - NodeIdx, <<"', '">>, - SKey, <<"', '">>, - SValue, <<"');">>]) - end, - Record#pubsub_node.options), - {result, NodeIdx} - end. - --spec(nodeid/2 :: -( - Host :: mod_pubsub:host(), - NodeId :: mod_pubsub:nodeId()) - -> {result, NodeIdx::mod_pubsub:nodeIdx()} - %%% - | {error, _} -). - -nodeid(Host, NodeId) -> - H = (?PUBSUB):escape(Host), - N = (?PUBSUB):escape(NodeId), - case catch - ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where " - "host='">>, - H, <<"' and node='">>, N, <<"';">>]) - of - {selected, [<<"nodeid">>], [{NodeIdx}]} -> - {result, NodeIdx}; - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end. - --spec(nodeowners/1 :: -( - NodeIdx::mod_pubsub:nodeIdx()) - -> Node_Owners::[ljid()] -). - -nodeowners(NodeIdx) -> - {result, Res} = node_hometree_odbc:get_node_affiliations(NodeIdx), - lists:foldl(fun ({LJID, owner}, Acc) -> [LJID | Acc]; - (_, Acc) -> Acc - end, - [], Res). diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl deleted file mode 100644 index 2aa5d7405..000000000 --- a/src/mod_pubsub/nodetree_virtual.erl +++ /dev/null @@ -1,157 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 ProcessOne -%%% @author Christophe Romain <christophe.romain@process-one.net> -%%% [http://www.process-one.net/] -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - -%%% @doc The module <strong>{@module}</strong> is the PubSub node tree plugin that -%%% allow virtual nodes handling. -%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p> -%%% <p>This plugin development is still a work in progress. Due to optimizations in -%%% mod_pubsub, this plugin can not work anymore without altering functioning. -%%% Please, send us comments, feedback and improvements.</p> - --module(nodetree_virtual). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --behaviour(gen_pubsub_nodetree). - --export([init/3, terminate/2, options/0, set_node/1, - get_node/3, get_node/2, get_node/1, get_nodes/2, - get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, - get_subnodes/3, get_subnodes_tree/3, create_node/6, - delete_node/2]). - -%% ================ -%% API definition -%% ================ - -%% @spec (Host, ServerHost, Opts) -> any() -%% Host = mod_pubsub:host() -%% ServerHost = host() -%% Opts = list() -%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must -%% implement this function. It can return anything.</p> -%% <p>This function is mainly used to trigger the setup task necessary for the -%% plugin. It can be used for example by the developer to create the specific -%% module database schema if it does not exists yet.</p> -%% @spec () -> [Option] -%% Option = mod_pubsub:nodetreeOption() -%% @doc <p>Returns the default pubsub node tree options.</p> -%% @spec (NodeRecord) -> ok | {error, Reason} -%% NodeRecord = mod_pubsub:pubsub_node() -%% @doc <p>No node record is stored on database. Just do nothing.</p> -%% @spec (Host, Node, From) -> pubsubNode() -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle a node database. Any node is considered -%% as existing. Node record contains default values.</p> -init(_Host, _ServerHost, _Opts) -> ok. - -terminate(_Host, _ServerHost) -> ok. - -options() -> [{virtual_tree, true}]. - -set_node(_NodeRecord) -> ok. - -get_node(Host, Node, _From) -> get_node(Host, Node). - -get_node(Host, Node) -> get_node({Host, Node}). - -get_node({Host, _} = NodeId) -> - Record = #pubsub_node{nodeid = NodeId, id = NodeId}, - Module = jlib:binary_to_atom(<<"node_", - (Record#pubsub_node.type)/binary>>), - Options = Module:options(), - Owners = [{<<"">>, Host, <<"">>}], - Record#pubsub_node{owners = Owners, options = Options}. - -%% @spec (Host, From) -> [pubsubNode()] -%% Host = mod_pubsub:host() | mod_pubsub:jid() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle a node database. Any node is considered -%% as existing. Nodes list can not be determined.</p> -%% @spec (Host, Node, From) -> [pubsubNode()] -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> -%% @spec (Host, Node, From) -> [pubsubNode()] -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> -%% @spec (Host, Node, From) -> [pubsubNode()] -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> -get_nodes(Host, _From) -> get_nodes(Host). - -get_nodes(_Host) -> []. - -get_parentnodes(_Host, _Node, _From) -> []. - -get_parentnodes_tree(_Host, _Node, _From) -> []. - -get_subnodes(Host, Node, _From) -> - get_subnodes(Host, Node). -%% @spec (Host, Node, From) -> [pubsubNode()] -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% From = mod_pubsub:jid() -%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> - -get_subnodes(_Host, _Node) -> []. - -get_subnodes_tree(Host, Node, _From) -> - get_subnodes_tree(Host, Node). -%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% Type = mod_pubsub:nodeType() -%% Owner = mod_pubsub:jid() -%% Options = list() -%% @doc <p>No node record is stored on database. Any valid node -%% is considered as already created.</p> -%% <p>default allowed nodes: /home/host/user/any/node/name</p> -%% @spec (Host, Node) -> [mod_pubsub:node()] -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() -%% @doc <p>Virtual node tree does not handle parent/child. -%% node deletion just affects the corresponding node.</p> - -get_subnodes_tree(_Host, _Node) -> []. - -create_node(Host, Node, _Type, _Owner, _Options, - _Parents) -> - {error, {virtual, {Host, Node}}}. - -delete_node(Host, Node) -> [get_node(Host, Node)]. diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl deleted file mode 100644 index 4f2cc9f38..000000000 --- a/src/mod_pubsub/pubsub.hrl +++ /dev/null @@ -1,257 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% copyright 2006-2013 ProcessOne -%%% -%%% This file contains pubsub types definition. -%%% ==================================================================== - -%% ------------------------------- -%% Pubsub constants --define(ERR_EXTENDED(E, C), - mod_pubsub:extended_error(E, C)). - -%% The actual limit can be configured with mod_pubsub's option max_items_node --define(MAXITEMS, 10). - -%% this is currently a hard limit. -%% Would be nice to have it configurable. --define(MAX_PAYLOAD_SIZE, 60000). - -%% ------------------------------- -%% Pubsub types - -%% @type hostPubsub() = string(). --type(hostPubsub() :: binary()). -%% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be -%% <tt>"pubsub.localhost"</tt>.</p> - --type(hostPEP() :: {binary(), binary(), <<>>}). -%% @type hostPEP() = {User, Server, Resource} -%% User = string() -%% Server = string() -%% Resource = []. -%% <p>For example, it can be : -%% ```{"bob", "example.org", []}'''.</p> - --type(host() :: hostPubsub() | hostPEP()). -%% @type host() = hostPubsub() | hostPEP(). - --type(nodeId() :: binary()). -%% @type nodeId() = binary(). -%% <p>A node is defined by a list of its ancestors. The last element is the name -%% of the current node. For example: -%% of the current node. For example: -%% ```<<"/home/localhost/user">>'''</p> - --type(nodeIdx() :: pos_integer()). -%% @type nodeIdx() = integer(). - --type(itemId() :: binary()). -%% @type itemId() = string(). - --type(subId() :: binary()). -%% @type subId() = string(). - - -%% @type payload() = [#xmlelement{} | #xmlcdata{}]. - -%% @type stanzaError() = #xmlelement{}. -%% Example: -%% Example: -%% ```{xmlelement, "error", -%% [{"code", Code}, {"type", Type}], -%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}''' -%% @type pubsubIQResponse() = #xmlelement{}. -%% Example: -%% ```{xmlelement, "pubsub", -%% [{"xmlns", ?NS_PUBSUB_EVENT}], -%% [{xmlelement, "affiliations", [], -%% []}]}''' - --type(nodeOption() :: - {Option::atom(), - Value::binary() | [binary()] | boolean() | non_neg_integer() -}). - --type(nodeOptions() :: [NodeOption::mod_pubsub:nodeOption(),...]). - -%% @type nodeOption() = {Option, Value} -%% Option = atom() -%% Value = term(). -%% Example: -%% ```{deliver_payloads, true}''' - --type(subOption() :: - {Option::atom(), - Value::binary() | [binary()] | boolean() -}). - --type(subOptions() :: [SubOption::mod_pubsub:subOption(),...]). - -%% @type nodeType() = string(). -%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub -%% plugin to use to manage a given node. For example, it can be -%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p> - -%% @type jid() = {jid, User, Server, Resource, LUser, LServer, LResource} -%% User = string() -%% Server = string() -%% Resource = string() -%% LUser = string() -%% LServer = string() -%% LResource = string(). - -%-type(ljid() :: {binary(), binary(), binary()}). -%% @type ljid() = {User, Server, Resource} -%% User = string() -%% Server = string() -%% Resource = string(). - --type(affiliation() :: 'none' - | 'owner' - | 'publisher' - %| 'publish-only' - | 'member' - | 'outcast' -). -%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'. - --type(subscription() :: 'none' - | 'pending' - | 'unconfigured' - | 'subscribed' -). -%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'. - --type(accessModel() :: 'open' - | 'presence' - | 'roster' - | 'authorize' - | 'whitelist' -). -%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'. - -%% @type pubsubIndex() = {pubsub_index, Index, Last, Free} -%% Index = atom() -%% Last = integer() -%% Free = [integer()]. -%% internal pubsub index table --type(publishModel() :: 'publishers' - | 'subscribers' - | 'open' -). - - --record(pubsub_index, -{ - index :: atom(), - last :: mod_pubsub:nodeIdx(), - free :: [mod_pubsub:nodeIdx()] -}). - -%% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options} -%% NodeId = {host() | ljid(), nodeId()} -%% Id = nodeIdx() -%% Parents = [nodeId()] -%% Type = nodeType() -%% Owners = [ljid()] -%% Options = [nodeOption()]. -%% <p>This is the format of the <tt>nodes</tt> table. The type of the table -%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p> -%% <p>The <tt>Parents</tt> and <tt>type</tt> fields are indexed.</p> -%% <tt>id</tt> can be anything you want. --record(pubsub_node, -{ - nodeid ,%:: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()}, - id ,%:: mod_pubsub:nodeIdx(), - parents = [] ,%:: [Parent_NodeId::mod_pubsub:nodeId()], - type = <<"flat">> ,%:: binary(), - owners = [] ,%:: [Owner::ljid(),...], - options = [] %:: mod_pubsub:nodeOptions() -}). - -%% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions} -%% StateId = {ljid(), nodeIdx()} -%% Items = [itemId()] -%% Affiliation = affiliation() -%% Subscriptions = [{subscription(), subId()}]. -%% <p>This is the format of the <tt>affiliations</tt> table. The type of the -%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p> - -%-record(pubsub_state, -% {stateid, items = [], affiliation = none, -% subscriptions = []}). --record(pubsub_state, -{ - stateid ,%:: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()}, - items = [] ,%:: [ItemId::mod_pubsub:itemId()], - affiliation = 'none' ,%:: mod_pubsub:affiliation(), - subscriptions = [] %:: [{mod_pubsub:subscription(), mod_pubsub:subId()}] -}). - -%% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload} -%% ItemId = {itemId(), nodeIdx()} -%% Creation = {now(), ljid()} -%% Modification = {now(), ljid()} -%% Payload = payload(). -%% <p>This is the format of the <tt>published items</tt> table. The type of the -%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p> -%-record(pubsub_item, -% {itemid, creation = {unknown, unknown}, -% modification = {unknown, unknown}, payload = []}). - --record(pubsub_item, -{ - itemid ,%:: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()}, - creation = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()}, - modification = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()}, - payload = [] %:: mod_pubsub:payload() -}). - -%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options} -%% SubId = subId() -%% Options = [nodeOption()]. -%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the -%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p> -%-record(pubsub_subscription, {subid, options}). --record(pubsub_subscription, -{ - subid ,%:: mod_pubsub:subId(), - options %:: [] | mod_pubsub:subOptions() -}). - -%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload} -%% NodeId = nodeIdx() -%% ItemId = itemId() -%% Creation = {now(),ljid()} -%% Payload = payload(). -%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload -%% for every node</p> -%-record(pubsub_last_item, -% {nodeid, itemid, creation, payload}). - --record(pubsub_last_item, -{ - nodeid ,%:: mod_pubsub:nodeIdx(), - itemid ,%:: mod_pubsub:itemId(), - creation ,%:: {erlang:timestamp(), ljid()}, - payload %:: mod_pubsub:payload() -}). diff --git a/src/mod_pubsub/pubsub_db_odbc.erl b/src/mod_pubsub/pubsub_db_odbc.erl deleted file mode 100644 index ca1318865..000000000 --- a/src/mod_pubsub/pubsub_db_odbc.erl +++ /dev/null @@ -1,151 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @author Pablo Polvorin <pablo.polvorin@process-one.net> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== --module(pubsub_db_odbc). - --author("pablo.polvorin@process-one.net"). - --include("pubsub.hrl"). - --export([add_subscription/1, read_subscription/1, - delete_subscription/1, update_subscription/1]). - -%% TODO: Those -spec lines produce errors in old Erlang versions. -%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. -%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound. -read_subscription(SubID) -> - case - ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr" - "iption_opt where subid = '">>, - ejabberd_odbc:escape(SubID), <<"'">>]) - of - {selected, [<<"opt_name">>, <<"opt_value">>], []} -> - notfound; - {selected, [<<"opt_name">>, <<"opt_value">>], - Options} -> - {ok, - #pubsub_subscription{subid = SubID, - options = - lists:map(fun subscription_opt_from_odbc/1, - Options)}} - end. - -%% -spec delete_subscription(SubID :: string()) -> ok. -delete_subscription(SubID) -> -%% -spec update_subscription(#pubsub_subscription{}) -> ok . -%% -spec add_subscription(#pubsub_subscription{}) -> ok. -%% -------------- Internal utilities ----------------------- - ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt " - "where subid = '">>, - ejabberd_odbc:escape(SubID), <<"'">>]), - ok. - -update_subscription(#pubsub_subscription{subid = - SubId} = - Sub) -> - delete_subscription(SubId), add_subscription(Sub). - -add_subscription(#pubsub_subscription{subid = SubId, - options = Opts}) -> - EscapedSubId = ejabberd_odbc:escape(SubId), - lists:foreach(fun (Opt) -> - {OdbcOptName, OdbcOptValue} = - subscription_opt_to_odbc(Opt), - ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, " - "opt_name, opt_value)values ('">>, - EscapedSubId, <<"','">>, - OdbcOptName, <<"','">>, - OdbcOptValue, <<"')">>]) - end, - Opts), - ok. - -subscription_opt_from_odbc({<<"DELIVER">>, Value}) -> - {deliver, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({<<"DIGEST">>, Value}) -> - {digest, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>, - Value}) -> - {digest_frequency, odbc_to_integer(Value)}; -subscription_opt_from_odbc({<<"EXPIRE">>, Value}) -> - {expire, odbc_to_timestamp(Value)}; -subscription_opt_from_odbc({<<"INCLUDE_BODY">>, - Value}) -> - {include_body, odbc_to_boolean(Value)}; -%%TODO: might be > than 1 show_values value??. -%% need to use compact all in only 1 opt. -subscription_opt_from_odbc({<<"SHOW_VALUES">>, - Value}) -> - {show_values, Value}; -subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>, - Value}) -> - {subscription_type, - case Value of - <<"items">> -> items; - <<"nodes">> -> nodes - end}; -subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>, - Value}) -> - {subscription_depth, - case Value of - <<"all">> -> all; - N -> odbc_to_integer(N) - end}. - -subscription_opt_to_odbc({deliver, Bool}) -> - {<<"DELIVER">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({digest, Bool}) -> - {<<"DIGEST">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({digest_frequency, Int}) -> - {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)}; -subscription_opt_to_odbc({expire, Timestamp}) -> - {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)}; -subscription_opt_to_odbc({include_body, Bool}) -> - {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({show_values, Values}) -> - {<<"SHOW_VALUES">>, Values}; -subscription_opt_to_odbc({subscription_type, Type}) -> - {<<"SUBSCRIPTION_TYPE">>, - case Type of - items -> <<"items">>; - nodes -> <<"nodes">> - end}; -subscription_opt_to_odbc({subscription_depth, Depth}) -> - {<<"SUBSCRIPTION_DEPTH">>, - case Depth of - all -> <<"all">>; - N -> integer_to_odbc(N) - end}. - -integer_to_odbc(N) -> - iolist_to_binary(integer_to_list(N)). - -boolean_to_odbc(true) -> <<"1">>; -boolean_to_odbc(false) -> <<"0">>. - -timestamp_to_odbc(T) -> jlib:now_to_utc_string(T). - -odbc_to_integer(N) -> jlib:binary_to_integer(N). - -odbc_to_boolean(B) -> B == <<"1">>. - -odbc_to_timestamp(T) -> - jlib:datetime_string_to_timestamp(T). diff --git a/src/mod_pubsub/pubsub_index.erl b/src/mod_pubsub/pubsub_index.erl deleted file mode 100644 index 1ff5a1e45..000000000 --- a/src/mod_pubsub/pubsub_index.erl +++ /dev/null @@ -1,67 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% -%%% @copyright 2006-2013 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). - --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, new/1, free/2]). - -init(_Host, _ServerHost, _Opts) -> - mnesia:create_table(pubsub_index, - [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_index)}]). - -new(Index) -> - case mnesia:read({pubsub_index, Index}) of - [I] -> - case I#pubsub_index.free of - [] -> - Id = I#pubsub_index.last + 1, - mnesia:write(I#pubsub_index{last = Id}), - Id; - [Id | Free] -> - mnesia:write(I#pubsub_index{free = Free}), Id - end; - _ -> - mnesia:write(#pubsub_index{index = Index, last = 1, - free = []}), - 1 - end. - -free(Index, Id) -> - case mnesia:read({pubsub_index, Index}) of - [I] -> - Free = I#pubsub_index.free, - mnesia:write(I#pubsub_index{free = [Id | Free]}); - _ -> ok - end. diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch deleted file mode 100644 index fdde477c0..000000000 --- a/src/mod_pubsub/pubsub_odbc.patch +++ /dev/null @@ -1,1198 +0,0 @@ ---- mod_pubsub.erl 2013-06-06 11:08:12.333599362 +0200 -+++ mod_pubsub_odbc.erl 2013-06-06 11:31:06.640173557 +0200 -@@ -43,7 +43,7 @@ - %%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see - %%% XEP-0060 section 12.18. - ---module(mod_pubsub). -+-module(mod_pubsub_odbc). - - -author('christophe.romain@process-one.net'). - -@@ -61,11 +61,11 @@ - - -include("pubsub.hrl"). - ---define(STDTREE, <<"tree">>). -+-define(STDTREE, <<"tree_odbc">>). - ---define(STDNODE, <<"flat">>). -+-define(STDNODE, <<"flat_odbc">>). - ---define(PEPNODE, <<"pep">>). -+-define(PEPNODE, <<"pep_odbc">>). - - %% exports for hooks - -export([presence_probe/3, caps_update/3, -@@ -100,7 +100,7 @@ - -export([subscription_to_string/1, affiliation_to_string/1, - string_to_subscription/1, string_to_affiliation/1, - extended_error/2, extended_error/3, -- rename_default_nodeplugin/0]). -+ escape/1]). - - %% API and gen_server callbacks - -export([start_link/2, start/2, stop/1, init/1, -@@ -110,7 +110,7 @@ - %% calls for parallel sending of last items - -export([send_loop/1]). - ---define(PROCNAME, ejabberd_mod_pubsub). -+-define(PROCNAME, ejabberd_mod_pubsub_odbc). - - -define(LOOPNAME, ejabberd_mod_pubsub_loop). - -@@ -349,8 +349,6 @@ - false -> ok - end, - ejabberd_router:register_route(Host), -- update_node_database(Host, ServerHost), -- update_state_database(Host, ServerHost), - put(server_host, ServerHost), - init_nodes(Host, ServerHost, NodeTree, Plugins), - State = #state{host = Host, server_host = ServerHost, -@@ -423,359 +421,14 @@ - ok. - - init_nodes(Host, ServerHost, _NodeTree, Plugins) -> -- case lists:member(<<"hometree">>, Plugins) of -+ case lists:member(<<"hometree_odbc">>, Plugins) of - true -> -- create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>), -+ create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>), - create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), -- <<"hometree">>); -+ <<"hometree_odbc">>); - false -> ok - end. - --update_node_database(Host, ServerHost) -> -- mnesia:del_table_index(pubsub_node, type), -- mnesia:del_table_index(pubsub_node, parentid), -- case catch mnesia:table_info(pubsub_node, attributes) of -- [host_node, host_parent, info] -> -- ?INFO_MSG("upgrade node pubsub tables", []), -- F = fun () -> -- {Result, LastIdx} = lists:foldl(fun ({pubsub_node, -- NodeId, ParentId, -- {nodeinfo, Items, -- Options, -- Entities}}, -- {RecList, -- NodeIdx}) -> -- ItemsList = -- lists:foldl(fun -- ({item, -- IID, -- Publisher, -- Payload}, -- Acc) -> -- C = -- {unknown, -- Publisher}, -- M = -- {now(), -- Publisher}, -- mnesia:write(#pubsub_item{itemid -- = -- {IID, -- NodeIdx}, -- creation -- = -- C, -- modification -- = -- M, -- payload -- = -- Payload}), -- [{Publisher, -- IID} -- | Acc] -- end, -- [], -- Items), -- Owners = -- dict:fold(fun -- (JID, -- {entity, -- Aff, -- Sub}, -- Acc) -> -- UsrItems = -- lists:foldl(fun -- ({P, -- I}, -- IAcc) -> -- case -- P -- of -- JID -> -- [I -- | IAcc]; -- _ -> -- IAcc -- end -- end, -- [], -- ItemsList), -- mnesia:write({pubsub_state, -- {JID, -- NodeIdx}, -- UsrItems, -- Aff, -- Sub}), -- case -- Aff -- of -- owner -> -- [JID -- | Acc]; -- _ -> -- Acc -- end -- end, -- [], -- Entities), -- mnesia:delete({pubsub_node, -- NodeId}), -- {[#pubsub_node{nodeid -- = -- NodeId, -- id -- = -- NodeIdx, -- parents -- = -- [element(2, -- ParentId)], -- owners -- = -- Owners, -- options -- = -- Options} -- | RecList], -- NodeIdx + 1} -- end, -- {[], 1}, -- mnesia:match_object({pubsub_node, -- {Host, -- '_'}, -- '_', -- '_'})), -- mnesia:write(#pubsub_index{index = node, last = LastIdx, -- free = []}), -- Result -- end, -- {atomic, NewRecords} = mnesia:transaction(F), -- {atomic, ok} = mnesia:delete_table(pubsub_node), -- {atomic, ok} = mnesia:create_table(pubsub_node, -- [{disc_copies, [node()]}, -- {attributes, -- record_info(fields, -- pubsub_node)}]), -- FNew = fun () -> -- lists:foreach(fun (Record) -> mnesia:write(Record) end, -- NewRecords) -- end, -- case mnesia:transaction(FNew) of -- {atomic, Result} -> -- ?INFO_MSG("Pubsub node tables updated correctly: ~p", -- [Result]); -- {aborted, Reason} -> -- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", -- [Reason]) -- end; -- [nodeid, parentid, type, owners, options] -> -- F = fun ({pubsub_node, NodeId, {_, Parent}, Type, -- Owners, Options}) -> -- #pubsub_node{nodeid = NodeId, id = 0, -- parents = [Parent], type = Type, -- owners = Owners, options = Options} -- end, -- mnesia:transform_table(pubsub_node, F, -- [nodeid, id, parents, type, owners, options]), -- FNew = fun () -> -- LastIdx = lists:foldl(fun (#pubsub_node{nodeid = -- NodeId} = -- PubsubNode, -- NodeIdx) -> -- mnesia:write(PubsubNode#pubsub_node{id -- = -- NodeIdx}), -- lists:foreach(fun -- (#pubsub_state{stateid -- = -- StateId} = -- State) -> -- {JID, -- _} = -- StateId, -- mnesia:delete({pubsub_state, -- StateId}), -- mnesia:write(State#pubsub_state{stateid -- = -- {JID, -- NodeIdx}}) -- end, -- mnesia:match_object(#pubsub_state{stateid -- = -- {'_', -- NodeId}, -- _ -- = -- '_'})), -- lists:foreach(fun -- (#pubsub_item{itemid -- = -- ItemId} = -- Item) -> -- {IID, -- _} = -- ItemId, -- {M1, -- M2} = -- Item#pubsub_item.modification, -- {C1, -- C2} = -- Item#pubsub_item.creation, -- mnesia:delete({pubsub_item, -- ItemId}), -- mnesia:write(Item#pubsub_item{itemid -- = -- {IID, -- NodeIdx}, -- modification -- = -- {M2, -- M1}, -- creation -- = -- {C2, -- C1}}) -- end, -- mnesia:match_object(#pubsub_item{itemid -- = -- {'_', -- NodeId}, -- _ -- = -- '_'})), -- NodeIdx + 1 -- end, -- 1, -- mnesia:match_object({pubsub_node, -- {Host, '_'}, -- '_', '_', -- '_', '_', -- '_'}) -- ++ -- mnesia:match_object({pubsub_node, -- {{'_', -- ServerHost, -- '_'}, -- '_'}, -- '_', '_', -- '_', '_', -- '_'})), -- mnesia:write(#pubsub_index{index = node, -- last = LastIdx, free = []}) -- end, -- case mnesia:transaction(FNew) of -- {atomic, Result} -> -- rename_default_nodeplugin(), -- ?INFO_MSG("Pubsub node tables updated correctly: ~p", -- [Result]); -- {aborted, Reason} -> -- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", -- [Reason]) -- end; -- [nodeid, id, parent, type, owners, options] -> -- F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners, -- Options}) -> -- #pubsub_node{nodeid = NodeId, id = Id, -- parents = [Parent], type = Type, -- owners = Owners, options = Options} -- end, -- mnesia:transform_table(pubsub_node, F, -- [nodeid, id, parents, type, owners, options]), -- rename_default_nodeplugin(); -- _ -> ok -- end, -- mnesia:transaction(fun () -> -- case catch mnesia:first(pubsub_node) of -- {_, L} when is_binary(L) -> -- lists:foreach(fun ({H, N}) -- when is_binary(N) -> -- [Node] = -- mnesia:read({pubsub_node, -- {H, -- N}}), -- Type = -- Node#pubsub_node.type, -- BN = element(2, -- node_call(Type, -- path_to_node, -- [N])), -- BP = case [element(2, -- node_call(Type, -- path_to_node, -- [P])) -- || P -- <- Node#pubsub_node.parents] -- of -- [<<>>] -> []; -- Parents -> -- Parents -- end, -- mnesia:write(Node#pubsub_node{nodeid -- = -- {H, -- BN}, -- parents -- = -- BP}), -- mnesia:delete({pubsub_node, -- {H, -- N}}); -- (_) -> ok -- end, -- mnesia:all_keys(pubsub_node)); -- _ -> ok -- end -- end). -- --rename_default_nodeplugin() -> -- lists:foreach(fun (Node) -> -- mnesia:dirty_write(Node#pubsub_node{type = -- <<"hometree">>}) -- end, -- mnesia:dirty_match_object(#pubsub_node{type = -- <<"default">>, -- _ = '_'})). -- --update_state_database(_Host, _ServerHost) -> -- case catch mnesia:table_info(pubsub_state, attributes) of -- [stateid, items, affiliation, subscription] -> -- ?INFO_MSG("upgrade state pubsub tables", []), -- F = fun ({pubsub_state, {JID, NodeID}, Items, Aff, Sub}, Acc) -> -- Subs = case Sub of -- none -> -- []; -- _ -> -- {result, SubID} = pubsub_subscription:subscribe_node(JID, NodeID, []), -- [{Sub, SubID}] -- end, -- NewState = #pubsub_state{stateid = {JID, NodeID}, -- items = Items, -- affiliation = Aff, -- subscriptions = Subs}, -- [NewState | Acc] -- end, -- {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3, -- [F, [], pubsub_state]), -- {atomic, ok} = mnesia:delete_table(pubsub_state), -- {atomic, ok} = mnesia:create_table(pubsub_state, -- [{disc_copies, [node()]}, -- {attributes, record_info(fields, pubsub_state)}]), -- FNew = fun () -> -- lists:foreach(fun mnesia:write/1, NewRecs) -- end, -- case mnesia:transaction(FNew) of -- {atomic, Result} -> -- ?INFO_MSG("Pubsub state tables updated correctly: ~p", -- [Result]); -- {aborted, Reason} -> -- ?ERROR_MSG("Problem updating Pubsub state tables:~n~p", -- [Reason]) -- end; -- _ -> -- ok -- end. -- - send_loop(State) -> - receive - {presence, JID, Pid} -> -@@ -784,11 +437,13 @@ - LJID = jlib:jid_tolower(JID), - BJID = jlib:jid_remove_resource(LJID), - lists:foreach(fun (PType) -> -- {result, Subscriptions} = node_action(Host, -+ {result, Subscriptions} = case catch node_action(Host, - PType, -- get_entity_subscriptions, -- [Host, -- JID]), -+ get_entity_subscriptions_for_send_last, -+ [Host, JID]) of -+ {result, S} -> S; -+ _ -> [] -+ end, - lists:foreach(fun ({Node, subscribed, _, - SubJID}) -> - if (SubJID == LJID) or -@@ -800,24 +455,14 @@ - type = - Type, - id = -- NodeId, -- options -- = -- Options} = -+ NodeId} = - Node, -- case -- get_option(Options, -- send_last_published_item) -- of -- on_sub_and_presence -> -- send_items(H, -+ send_items(H, - N, - NodeId, - Type, - LJID, - last); -- _ -> ok -- end; - true -> - % resource not concerned about that subscription - ok -@@ -1008,7 +653,8 @@ - children = []}]; - disco_identity(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, -- options = Options, owners = Owners}) -> -+ options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, -@@ -1060,7 +706,8 @@ - || Feature <- features(<<"pep">>)]]; - disco_features(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, -- options = Options, owners = Owners}) -> -+ options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, -@@ -1105,9 +752,9 @@ - ). - disco_items(Host, <<>>, From) -> - Action = fun (#pubsub_node{nodeid = {_, NodeID}, -- options = Options, type = Type, id = Idx, -- owners = Owners}, -+ options = Options, type = Type, id = Idx}, - Acc) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - [#xmlel{name = <<"item">>, -@@ -1128,13 +775,14 @@ - _ -> Acc - end - end, -- case transaction(Host, Action, sync_dirty) of -+ case transaction_on_nodes(Host, Action, sync_dirty) of - {result, Items} -> Items; - _ -> [] - end; - disco_items(Host, Node, From) -> - Action = fun (#pubsub_node{id = Idx, type = Type, -- options = Options, owners = Owners}) -> -+ options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, - Options, Owners) - of -@@ -1238,9 +886,6 @@ - lists:foreach(fun ({#pubsub_node{options - = - Options, -- owners -- = -- Owners, - id = - NodeId}, - subscribed, _, -@@ -1252,7 +897,7 @@ - presence -> - case - lists:member(BJID, -- Owners) -+ node_owners(Host, PType, NodeId)) - of - true -> - node_action(Host, -@@ -1512,7 +1157,8 @@ - IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = xml:get_attr_s(<<"node">>, QAttrs), -- Res = case iq_disco_items(Host, Node, From) of -+ Rsm = jlib:rsm_decode(IQ), -+ Res = case iq_disco_items(Host, Node, From, Rsm) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = -@@ -1639,7 +1285,7 @@ - % [] -> - % [<<"leaf">>]; %% No sub-nodes: it's a leaf node - % _ -> --% case node_call(Type, get_items, [NodeId, From]) of -+% case node_call(Type, get_items, [NodeId, From, none]) of - % {result, []} -> [<<"collection">>]; - % {result, _} -> [<<"leaf">>, <<"collection">>]; - % _ -> [] -@@ -1661,7 +1307,11 @@ - % [#xmlel{name = <<"feature">>, - % attrs = [{<<"var">>, ?NS_PUBSUB}], - % children = []} --% | lists:map(fun (T) -> -+% | lists:map(fun -+% (<<"rsm">>)-> -+% #xmlel{name = <<"feature">>, -+% attrs = [{<<"var">>, ?NS_RSM}]}; -+% (T) -> - % #xmlel{name = <<"feature">>, - % attrs = - % [{<<"var">>, -@@ -1686,7 +1336,7 @@ - [] -> [<<"leaf">>]; - _ -> - case node_call(Type, get_items, -- [NodeId, From]) -+ [NodeId, From, none]) - of - {result, []} -> - [<<"collection">>]; -@@ -1708,7 +1358,11 @@ - F = [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}], - children = []} -- | lists:map(fun (T) -> -+ | lists:map(fun -+ (<<"rsm">>)-> -+ #xmlel{name = <<"feature">>, -+ attrs = [{<<"var">>, ?NS_RSM}]}; -+ (T) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, -@@ -1752,7 +1406,11 @@ - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}] - ++ -- lists:map(fun (Feature) -> -+ lists:map(fun -+ (<<"rsm">>)-> -+ #xmlel{name = <<"feature">>, -+ attrs = [{<<"var">>, ?NS_RSM}]}; -+ (Feature) -> - #xmlel{name = <<"feature">>, - attrs = - [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}], -@@ -1765,14 +1423,15 @@ - _ -> node_disco_info(Host, Node, From) - end. - ---spec(iq_disco_items/3 :: -+-spec(iq_disco_items/4 :: - ( - Host :: mod_pubsub:host(), - NodeId :: <<>> | mod_pubsub:nodeId(), -- From :: jid()) -+ From :: jid(), -+ Rsm :: any()) - -> {result, [xmlel()]} - ). --iq_disco_items(Host, <<>>, From) -> -+iq_disco_items(Host, <<>>, From, _RSM) -> - {result, - lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, - options = Options}) -> -@@ -1809,7 +1468,7 @@ - % Nodes)}; - % Other -> Other - % end; --iq_disco_items(Host, ?NS_COMMANDS, _From) -> -+iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> - CommandItems = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, -@@ -1817,22 +1476,19 @@ - {<<"name">>, <<"Get Pending">>}], - children = []}], - {result, CommandItems}; --iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> -+iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> - CommandItems = [], {result, CommandItems}; --iq_disco_items(Host, Item, From) -> -+iq_disco_items(Host, Item, From, RSM) -> - case str:tokens(Item, <<"!">>) of - [_Node, _ItemID] -> {result, []}; - [Node] -> - % Node = string_to_node(SNode), - Action = fun (#pubsub_node{id = Idx, type = Type, -- options = Options, owners = Owners}) -> -- NodeItems = case get_allowed_items_call(Host, Idx, -- From, Type, -- Options, -- Owners) -- of -+ options = Options}) -> -+ Owners = node_owners_call(Type, Idx), -+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of - {result, R} -> R; -- _ -> [] -+ _ -> {[], none} - end, - Nodes = lists:map(fun (#pubsub_node{nodeid = - {_, SubNode}, -@@ -1875,7 +1531,7 @@ - children = []} - end, - NodeItems), -- {result, Nodes ++ Items} -+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; -@@ -2026,7 +1682,8 @@ - (_, Acc) -> Acc - end, - [], xml:remove_cdata(Els)), -- get_items(Host, Node, From, SubId, MaxItems, ItemIDs); -+ RSM = jlib:rsm_decode(SubEl), -+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From, Plugins); - {get, <<"affiliations">>} -> -@@ -2061,7 +1718,9 @@ - ). - iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - #xmlel{children = SubEls} = SubEl, -- Action = xml:remove_cdata(SubEls), -+ Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false; -+ (_) -> true -+ end, xml:remove_cdata(SubEls)), - case Action of - [#xmlel{name = Name, attrs = Attrs, children = Els}] -> - Node = xml:get_attr_s(<<"node">>, Attrs), -@@ -2195,7 +1854,8 @@ - _ -> [] - end - end, -- case transaction(fun () -> -+ case transaction(Host, -+ fun () -> - {result, lists:flatmap(Tr, Plugins)} - end, - sync_dirty) -@@ -2240,7 +1900,8 @@ - - %%% authorization handling - --send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, -+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, -+ type = Type, id = NodeId}, - Subscriber) -> - Lang = <<"en">>, - Stanza = #xmlel{name = <<"message">>, attrs = [], -@@ -2318,7 +1979,7 @@ - ejabberd_router:route(service_jid(Host), - jlib:make_jid(Owner), Stanza) - end, -- Owners). -+ node_owners(Host, Type, NodeId)). - - find_authorization_response(Packet) -> - #xmlel{children = Els} = Packet, -@@ -2383,11 +2044,11 @@ - <<"true">> -> true; - _ -> false - end, -- Action = fun (#pubsub_node{type = Type, owners = Owners, -+ Action = fun (#pubsub_node{type = Type, - id = NodeId}) -> - IsApprover = - lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), -- Owners), -+ node_owners_call(Type, NodeId)), - {result, Subscriptions} = node_call(Type, - get_subscriptions, - [NodeId, -@@ -2642,7 +2303,7 @@ - children = [#xmlel{name = <<"create">>, - attrs = nodeAttr(Node), - children = []}]}], -- case transaction(CreateNode, transaction) of -+ case transaction(Host, CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), - ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), -@@ -2779,7 +2440,7 @@ - %%</ul> - subscribe_node(Host, Node, From, JID, Configuration) -> - SubOpts = case -- pubsub_subscription:parse_options_xform(Configuration) -+ pubsub_subscription_odbc:parse_options_xform(Configuration) - of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid -@@ -2793,7 +2454,7 @@ - end - end, - Action = fun (#pubsub_node{options = Options, -- owners = Owners, type = Type, id = NodeId}) -> -+ type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member(<<"subscribe">>, Features), - OptionsFeature = lists:member(<<"subscription-options">>, Features), -@@ -2802,6 +2463,7 @@ - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), -+ Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, Subscriber, - Owners, AccessModel, AllowedGroups), -@@ -2956,12 +2618,9 @@ - Features = features(Type), - PublishFeature = lists:member(<<"publish">>, Features), - PublishModel = get_option(Options, publish_model), -+ MaxItems = max_items(Host, Options), - DeliverPayloads = get_option(Options, deliver_payloads), - PersistItems = get_option(Options, persist_items), -- MaxItems = case PersistItems of -- false -> 0; -- true -> max_items(Host, Options) -- end, - PayloadCount = payload_xmlelements(Payload), - PayloadSize = byte_size(term_to_binary(Payload)) - 2, - PayloadMaxSize = get_option(Options, max_payload_size), -@@ -3017,7 +2676,7 @@ - false -> - ok - end, -- set_cached_item(Host, NodeId, ItemId, Publisher, Payload), -+ set_cached_item(Host, NodeId, ItemId, Publisher, Payload), - case Result of - default -> {result, Reply}; - _ -> {result, Result} -@@ -3210,19 +2869,20 @@ - %% <p>The permission are not checked in this function.</p> - %% @todo We probably need to check that the user doing the query has the right - %% to read the items. ---spec(get_items/6 :: -+-spec(get_items/7 :: - ( - Host :: mod_pubsub:host(), - Node :: mod_pubsub:nodeId(), - From :: jid(), - SubId :: mod_pubsub:subId(), - SMaxItems :: binary(), -- ItemIDs :: [mod_pubsub:itemId()]) -+ ItemIDs :: [mod_pubsub:itemId()], -+ Rsm :: any()) - -> {result, [xmlel(),...]} - %%% - | {error, xmlel()} - ). --get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) -> -+get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) -> - MaxItems = if SMaxItems == <<"">> -> - get_max_items_node(Host); - true -> -@@ -3234,13 +2894,13 @@ - case MaxItems of - {error, Error} -> {error, Error}; - _ -> -- Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId, -- owners = Owners}) -> -+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - RetreiveFeature = lists:member(<<"retrieve-items">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), -+ Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, - AccessModel, AllowedGroups), -@@ -3258,11 +2918,11 @@ - node_call(Type, get_items, - [NodeId, From, AccessModel, - PresenceSubscription, RosterGroup, -- SubId]) -+ SubId, RSM]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of -- {result, {_, Items}} -> -+ {result, {_, {Items, RSMOut}}} -> - SendItems = case ItemIDs of - [] -> Items; - _ -> -@@ -3280,8 +2940,8 @@ - children = - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = -- itemsEls(lists:sublist(SendItems, -- MaxItems))}]}]}; -+ itemsEls(lists:sublist(SendItems, MaxItems))} -+ | jlib:rsm_encode(RSMOut)]}]}; - Error -> Error - end - end. -@@ -3305,13 +2965,18 @@ - end. - - get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> -+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of -+ {result, {I, _}} -> {result, I}; -+ Error -> Error -+ end. -+get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) -> - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - get_presence_and_roster_permissions(Host, From, Owners, AccessModel, - AllowedGroups), - node_call(Type, get_items, -- [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]). -+ [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]). - - %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() - %% Host = pubsubHost() -@@ -3322,35 +2987,32 @@ - %% Number = last | integer() - %% @doc <p>Resend the items of a node to the user.</p> - %% @todo use cache-last-item feature --send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) -> -- case get_cached_item(Host, NodeId) of -- undefined -> -- send_items(Host, Node, NodeId, Type, LJID, 1); -+send_items(Host, Node, NodeId, Type, LJID, last) -> -+ Stanza = case get_cached_item(Host, NodeId) of -+ undefined -> -+ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc -+ case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of -+ {result, [LastItem]} -> -+ {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, -+ event_stanza_with_delay( -+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), -+ children = itemsEls([LastItem])}], ModifNow, ModifUSR); -+ _ -> -+ event_stanza( -+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), -+ children = itemsEls([])}]) -+ end; - LastItem -> - {ModifNow, ModifUSR} = - LastItem#pubsub_item.modification, -- Stanza = event_stanza_with_delay([#xmlel{name = -+ event_stanza_with_delay([#xmlel{name = - <<"items">>, - attrs = nodeAttr(Node), - children = - itemsEls([LastItem])}], -- ModifNow, ModifUSR), -- case is_tuple(Host) of -- false -> -- ejabberd_router:route(service_jid(Host), -- jlib:make_jid(LJID), Stanza); -- true -> -- case ejabberd_sm:get_session_pid(U, S, R) of -- C2SPid when is_pid(C2SPid) -> -- ejabberd_c2s:broadcast(C2SPid, -- {pep_message, -- <<((Node))/binary, "+notify">>}, -- _Sender = service_jid(Host), -- Stanza); -- _ -> ok -- end -- end -- end; -+ ModifNow, ModifUSR) -+ end, -+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); - send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, - Number) -> - ToSend = case node_action(Host, Type, get_items, -@@ -3378,7 +3040,8 @@ - attrs = nodeAttr(Node), - children = itemsEls(ToSend)}]) - end, -- case is_tuple(Host) of -+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza). -+ - %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} - %% Host = host() - %% JID = jid() -@@ -3386,20 +3049,6 @@ - %% Reason = stanzaError() - %% Response = [pubsubIQResponse()] - %% @doc <p>Return the list of affiliations as an XMPP response.</p> -- false -> -- ejabberd_router:route(service_jid(Host), -- jlib:make_jid(LJID), Stanza); -- true -> -- case ejabberd_sm:get_session_pid(U, S, R) of -- C2SPid when is_pid(C2SPid) -> -- ejabberd_c2s:broadcast(C2SPid, -- {pep_message, -- <<((Node))/binary, "+notify">>}, -- _Sender = service_jid(Host), Stanza); -- _ -> ok -- end -- end. -- - -spec(get_affiliations/4 :: - ( - Host :: mod_pubsub:host(), -@@ -3586,9 +3235,10 @@ - case Entities of - error -> {error, ?ERR_BAD_REQUEST}; - _ -> -- Action = fun (#pubsub_node{owners = Owners, type = Type, -+ Action = fun (#pubsub_node{type = Type, - id = NodeId} = - N) -> -+ Owners = node_owners_call(Type, NodeId), - case lists:member(Owner, Owners) of - true -> - OwnerJID = jlib:make_jid(Owner), -@@ -3601,42 +3251,7 @@ - _ -> Entities - end, - lists:foreach(fun ({JID, Affiliation}) -> -- node_call(Type, -- set_affiliation, -- [NodeId, JID, -- Affiliation]), -- case Affiliation of -- owner -> -- NewOwner = -- jlib:jid_tolower(jlib:jid_remove_resource(JID)), -- NewOwners = -- [NewOwner -- | Owners], -- tree_call(Host, -- set_node, -- [N#pubsub_node{owners -- = -- NewOwners}]); -- none -> -- OldOwner = -- jlib:jid_tolower(jlib:jid_remove_resource(JID)), -- case -- lists:member(OldOwner, -- Owners) -- of -- true -> -- NewOwners = -- Owners -- -- [OldOwner], -- tree_call(Host, -- set_node, -- [N#pubsub_node{owners -- = -- NewOwners}]); -- _ -> ok -- end; -- _ -> ok -- end -+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation]) - end, - FilteredEntities), - {result, []}; -@@ -3695,11 +3310,11 @@ - end. - - read_sub(Subscriber, Node, NodeID, SubID, Lang) -> -- case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of -+ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, #pubsub_subscription{options = Options}} -> -- {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options), -+ {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options), - OptionsEl = #xmlel{name = <<"options">>, - attrs = - [{<<"jid">>, jlib:jid_to_string(Subscriber)}, -@@ -3733,7 +3348,7 @@ - end. - - set_options_helper(Configuration, JID, NodeID, SubID, Type) -> -- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of -+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, -@@ -3765,7 +3380,7 @@ - write_sub(_Subscriber, _NodeID, _SubID, invalid) -> - {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; - write_sub(Subscriber, NodeID, SubID, Options) -> -- case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of -+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; - {result, _} -> -@@ -3986,9 +3601,9 @@ - ejabberd_router:route(service_jid(Host), - jlib:make_jid(JID), Stanza) - end, -- Action = fun (#pubsub_node{owners = Owners, type = Type, -+ Action = fun (#pubsub_node{type = Type, - id = NodeId}) -> -- case lists:member(Owner, Owners) of -+ case lists:member(Owner, node_owners_call(Type, NodeId)) of - true -> - Result = lists:foldl(fun ({JID, Subscription, - SubId}, -@@ -4405,7 +4020,7 @@ - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} - end, -- case transaction(Action, sync_dirty) of -+ case transaction(Host, Action, sync_dirty) of - {result, CollSubs} -> CollSubs; - _ -> [] - end. -@@ -4419,9 +4034,9 @@ - - get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> -- case pubsub_subscription:read_subscription(JID, NodeID, SubID) of -+ case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; -- #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc]; -+ {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> -@@ -5121,6 +4736,30 @@ - _ -> features() - end. - -+%% @spec (Host, Type, NodeId) -> [ljid()] -+%% NodeId = pubsubNodeId() -+%% @doc <p>Return list of node owners.</p> -+node_owners(Host, Type, NodeId) -> -+ case node_action(Host, Type, get_node_affiliations, [NodeId]) of -+ {result, Affiliations} -> -+ lists:foldl( -+ fun({LJID, owner}, Acc) -> [LJID|Acc]; -+ (_, Acc) -> Acc -+ end, [], Affiliations); -+ _ -> -+ [] -+ end. -+node_owners_call(Type, NodeId) -> -+ case node_call(Type, get_node_affiliations, [NodeId]) of -+ {result, Affiliations} -> -+ lists:foldl( -+ fun({LJID, owner}, Acc) -> [LJID|Acc]; -+ (_, Acc) -> Acc -+ end, [], Affiliations); -+ _ -> -+ [] -+ end. -+ - %% @doc <p>node tree plugin call.</p> - tree_call({_User, Server, _Resource}, Function, Args) -> - tree_call(Server, Function, Args); -@@ -5140,7 +4779,13 @@ - tree_action(Host, Function, Args) -> - ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]), - Fun = fun () -> tree_call(Host, Function, Args) end, -- catch mnesia:sync_dirty(Fun). -+ case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of -+ {atomic, Result} -> -+ Result; -+ {aborted, Reason} -> -+ ?ERROR_MSG("transaction return internal error: ~p~n",[{aborted, Reason}]), -+ {error, ?ERR_INTERNAL_SERVER_ERROR} -+ end. - - %% @doc <p>node plugin call.</p> - node_call(Type, Function, Args) -> -@@ -5165,13 +4810,12 @@ - node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p", - [Host, Type, Function, Args]), -- transaction(fun () -> node_call(Type, Function, Args) -- end, -+ transaction(Host, fun () -> node_call(Type, Function, Args) end, - sync_dirty). - - %% @doc <p>plugin transaction handling.</p> - transaction(Host, Node, Action, Trans) -> -- transaction(fun () -> -+ transaction(Host, fun () -> - case tree_call(Host, get_node, [Host, Node]) of - N when is_record(N, pubsub_node) -> - case Action(N) of -@@ -5185,16 +4829,22 @@ - end, - Trans). - --transaction(Host, Action, Trans) -> -- transaction(fun () -> -+transaction_on_nodes(Host, Action, Trans) -> -+ transaction(Host, fun () -> - {result, - lists:foldl(Action, [], - tree_call(Host, get_nodes, [Host]))} - end, - Trans). - --transaction(Fun, Trans) -> -- case catch mnesia:Trans(Fun) of -+transaction(Host, Fun, Trans) -> -+ transaction_retry(Host, Fun, Trans, 2). -+transaction_retry(Host, Fun, Trans, Count) -> -+ SqlFun = case Trans of -+ transaction -> sql_transaction; -+ _ -> sql_bloc -+ end, -+ case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {atomic, {result, Result}} -> {result, Result}; -@@ -5203,6 +4853,15 @@ - ?ERROR_MSG("transaction return internal error: ~p~n", - [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; -+ {'EXIT', {timeout, _} = Reason} -> -+ case Count of -+ 0 -> -+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), -+ {error, ?ERR_INTERNAL_SERVER_ERROR}; -+ N -> -+ erlang:yield(), -+ transaction_retry(Host, Fun, Trans, N-1) -+ end; - {'EXIT', Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", - [{'EXIT', Reason}]), -@@ -5213,6 +4872,16 @@ - {error, ?ERR_INTERNAL_SERVER_ERROR} - end. - -+odbc_conn({_U, Host, _R})-> Host; -+odbc_conn(<<$., Host/binary>>) -> Host; -+odbc_conn(<<_, Host/binary>>) -> odbc_conn(Host). -+ -+%% escape value for database storage -+escape({_U, _H, _R}=JID)-> -+ ejabberd_odbc:escape(jlib:jid_to_string(JID)); -+escape(Value)-> -+ ejabberd_odbc:escape(Value). -+ - %%%% helpers - - %% Add pubsub-specific error element diff --git a/src/mod_pubsub/pubsub_subscription.erl b/src/mod_pubsub/pubsub_subscription.erl deleted file mode 100644 index bb09cdd60..000000000 --- a/src/mod_pubsub/pubsub_subscription.erl +++ /dev/null @@ -1,463 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @author Brian Cully <bjc@kublai.com> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(pubsub_subscription). - --author("bjc@kublai.com"). - -%% API --export([init/0, subscribe_node/3, unsubscribe_node/3, - get_subscription/3, set_subscription/4, - get_options_xform/2, parse_options_xform/1]). - -% Internal function also exported for use in transactional bloc from pubsub plugins --export([add_subscription/3, delete_subscription/3, - read_subscription/3, write_subscription/4]). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB_DELIVER, <<"pubsub#deliver">>). - --define(PUBSUB_DIGEST, <<"pubsub#digest">>). - --define(PUBSUB_DIGEST_FREQUENCY, - <<"pubsub#digest_frequency">>). - --define(PUBSUB_EXPIRE, <<"pubsub#expire">>). - --define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). - --define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). - --define(PUBSUB_SUBSCRIPTION_TYPE, - <<"pubsub#subscription_type">>). - --define(PUBSUB_SUBSCRIPTION_DEPTH, - <<"pubsub#subscription_depth">>). - --define(DELIVER_LABEL, - <<"Whether an entity wants to receive or " - "disable notifications">>). - --define(DIGEST_LABEL, - <<"Whether an entity wants to receive digests " - "(aggregations) of notifications or all " - "notifications individually">>). - --define(DIGEST_FREQUENCY_LABEL, - <<"The minimum number of milliseconds between " - "sending any two notification digests">>). - --define(EXPIRE_LABEL, - <<"The DateTime at which a leased subscription " - "will end or has ended">>). - --define(INCLUDE_BODY_LABEL, - <<"Whether an entity wants to receive an " - "XMPP message body in addition to the " - "payload format">>). - --define(SHOW_VALUES_LABEL, - <<"The presence states for which an entity " - "wants to receive notifications">>). - --define(SUBSCRIPTION_TYPE_LABEL, - <<"Type of notification to receive">>). - --define(SUBSCRIPTION_DEPTH_LABEL, - <<"Depth from subscription for which to " - "receive notifications">>). - --define(SHOW_VALUE_AWAY_LABEL, - <<"XMPP Show Value of Away">>). - --define(SHOW_VALUE_CHAT_LABEL, - <<"XMPP Show Value of Chat">>). - --define(SHOW_VALUE_DND_LABEL, - <<"XMPP Show Value of DND (Do Not Disturb)">>). - --define(SHOW_VALUE_ONLINE_LABEL, - <<"Mere Availability in XMPP (No Show Value)">>). - --define(SHOW_VALUE_XA_LABEL, - <<"XMPP Show Value of XA (Extended Away)">>). - --define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, - <<"Receive notification of new items only">>). - --define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, - <<"Receive notification of new nodes only">>). - --define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, - <<"Receive notification from direct child " - "nodes only">>). - --define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, - <<"Receive notification from all descendent " - "nodes">>). - -%%==================================================================== -%% API -%%==================================================================== -init() -> ok = create_table(). - -subscribe_node(JID, NodeID, Options) -> - case catch mnesia:sync_dirty(fun add_subscription/3, - [JID, NodeID, Options]) - of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} - end. - -unsubscribe_node(JID, NodeID, SubID) -> - case catch mnesia:sync_dirty(fun delete_subscription/3, - [JID, NodeID, SubID]) - of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} - end. - -get_subscription(JID, NodeID, SubID) -> - case catch mnesia:sync_dirty(fun read_subscription/3, - [JID, NodeID, SubID]) - of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} - end. - -set_subscription(JID, NodeID, SubID, Options) -> - case catch mnesia:sync_dirty(fun write_subscription/4, - [JID, NodeID, SubID, Options]) - of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} - end. - - -get_options_xform(Lang, Options) -> - Keys = [deliver, show_values, subscription_type, - subscription_depth], - XFields = [get_option_xfield(Lang, Key, Options) - || Key <- Keys], - {result, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] - ++ XFields}}. - -parse_options_xform(XFields) -> - case xml:remove_cdata(XFields) of - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - XData when is_list(XData) -> - Opts = set_xoption(XData, []), - {result, Opts}; - Other -> Other - end; - _ -> {result, []} - end. - -%%==================================================================== -%% Internal functions -%%==================================================================== -create_table() -> - case mnesia:create_table(pubsub_subscription, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, pubsub_subscription)}, - {type, set}]) - of - {atomic, ok} -> ok; - {aborted, {already_exists, _}} -> ok; - Other -> Other - end. - --spec(add_subscription/3 :: -( - _JID :: ljid(), - _NodeID :: mod_pubsub:nodeIdx(), - Options :: [] | mod_pubsub:subOptions()) - -> SubId :: mod_pubsub:subId() -). - -add_subscription(_JID, _NodeID, []) -> make_subid(); -add_subscription(_JID, _NodeID, Options) -> - SubID = make_subid(), - mnesia:write(#pubsub_subscription{subid = SubID, - options = Options}), - SubID. - --spec(delete_subscription/3 :: -( - _JID :: _, - _NodeID :: _, - SubId :: mod_pubsub:subId()) - -> ok -). - -delete_subscription(_JID, _NodeID, SubID) -> - mnesia:delete({pubsub_subscription, SubID}). - --spec(read_subscription/3 :: -( - _JID :: ljid(), - _NodeID :: _, - SubID :: mod_pubsub:subId()) - -> mod_pubsub:pubsubSubscription() - | {error, notfound} -). - -read_subscription(_JID, _NodeID, SubID) -> - case mnesia:read({pubsub_subscription, SubID}) of - [Sub] -> Sub; - _ -> {error, notfound} - end. - --spec(write_subscription/4 :: -( - _JID :: ljid(), - _NodeID :: _, - SubID :: mod_pubsub:subId(), - Options :: mod_pubsub:subOptions()) - -> ok -). - -write_subscription(_JID, _NodeID, SubID, Options) -> - mnesia:write(#pubsub_subscription{subid = SubID, - options = Options}). - --spec(make_subid/0 :: () -> SubId::mod_pubsub:subId()). -make_subid() -> - {T1, T2, T3} = now(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -%% -%% Subscription XForm processing. -%% - -%% Return processed options, with types converted and so forth, using -%% Opts as defaults. -set_xoption([], Opts) -> Opts; -set_xoption([{Var, Value} | T], Opts) -> - NewOpts = case var_xfield(Var) of - {error, _} -> Opts; - Key -> - Val = val_xfield(Key, Value), - lists:keystore(Key, 1, Opts, {Key, Val}) - end, - set_xoption(T, NewOpts). - -%% Return the options list's key for an XForm var. -%% Convert Values for option list's Key. -var_xfield(?PUBSUB_DELIVER) -> deliver; -var_xfield(?PUBSUB_DIGEST) -> digest; -var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> - digest_frequency; -var_xfield(?PUBSUB_EXPIRE) -> expire; -var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; -var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; -var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> - subscription_type; -var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> - subscription_depth; -var_xfield(_) -> {error, badarg}. - -val_xfield(deliver, [Val]) -> xopt_to_bool(Val); -%val_xfield(digest, [Val]) -> xopt_to_bool(Val); -%val_xfield(digest_frequency, [Val]) -> -% jlib:binary_to_integer(Val); -%val_xfield(expire, [Val]) -> -% jlib:datetime_string_to_timestamp(Val); -%val_xfield(include_body, [Val]) -> xopt_to_bool(Val); -val_xfield(show_values, Vals) -> Vals; -val_xfield(subscription_type, [<<"items">>]) -> items; -val_xfield(subscription_type, [<<"nodes">>]) -> nodes; -val_xfield(subscription_depth, [<<"all">>]) -> all; -val_xfield(subscription_depth, [Depth]) -> - case catch jlib:binary_to_integer(Depth) of - N when is_integer(N) -> N; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end. - -%% Convert XForm booleans to Erlang booleans. -xopt_to_bool(<<"0">>) -> false; -xopt_to_bool(<<"1">>) -> true; -xopt_to_bool(<<"false">>) -> false; -xopt_to_bool(<<"true">>) -> true; -xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. - --spec(get_option_xfield/3 :: -( - Lang :: binary(), - Key :: atom(), - Options :: mod_pubsub:subOptions()) - -> xmlel() -). - -%% Return a field for an XForm for Key, with data filled in, if -%% applicable, from Options. -get_option_xfield(Lang, Key, Options) -> - Var = xfield_var(Key), - Label = xfield_label(Key), - {Type, OptEls} = type_and_options(xfield_type(Key), - Lang), - Vals = case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - [tr_xfield_values(Vals) - || Vals <- xfield_val(Key, Val)]; - false -> [] - end, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, Var}, {<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}], - children = OptEls ++ Vals}. - -type_and_options({Type, Options}, Lang) -> - {Type, [tr_xfield_options(O, Lang) || O <- Options]}; -type_and_options(Type, _Lang) -> {Type, []}. - -tr_xfield_options({Value, Label}, Lang) -> - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}]}. - -tr_xfield_values(Value) -> -%% Return the XForm variable name for a subscription option key. -%% Return the XForm variable type for a subscription option key. - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}. - --spec(xfield_var/1 :: -( - Var :: 'deliver' -% | 'digest' -% | 'digest_frequency' -% | 'expire' -% | 'include_body' - | 'show_values' - | 'subscription_type' - | 'subscription_depth') - -> binary() -). - -xfield_var(deliver) -> ?PUBSUB_DELIVER; -%xfield_var(digest) -> ?PUBSUB_DIGEST; -%xfield_var(digest_frequency) -> -% ?PUBSUB_DIGEST_FREQUENCY; -%xfield_var(expire) -> ?PUBSUB_EXPIRE; -%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; -xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; -xfield_var(subscription_type) -> - ?PUBSUB_SUBSCRIPTION_TYPE; -xfield_var(subscription_depth) -> - ?PUBSUB_SUBSCRIPTION_DEPTH. - -xfield_type(deliver) -> <<"boolean">>; -%xfield_type(digest) -> <<"boolean">>; -%xfield_type(digest_frequency) -> <<"text-single">>; -%xfield_type(expire) -> <<"text-single">>; -%xfield_type(include_body) -> <<"boolean">>; -xfield_type(show_values) -> - {<<"list-multi">>, - [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, - {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, - {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, - {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, - {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; -xfield_type(subscription_type) -> - {<<"list-single">>, - [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, - {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; -xfield_type(subscription_depth) -> - {<<"list-single">>, - [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, - {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. - -%% Return the XForm variable label for a subscription option key. -xfield_label(deliver) -> ?DELIVER_LABEL; -%xfield_label(digest) -> ?DIGEST_LABEL; -%xfield_label(digest_frequency) -> -% ?DIGEST_FREQUENCY_LABEL; -%xfield_label(expire) -> ?EXPIRE_LABEL; -%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; -xfield_label(show_values) -> ?SHOW_VALUES_LABEL; -%% Return the XForm value for a subscription option key. -%% Convert erlang booleans to XForms. -xfield_label(subscription_type) -> - ?SUBSCRIPTION_TYPE_LABEL; -xfield_label(subscription_depth) -> - ?SUBSCRIPTION_DEPTH_LABEL. - --spec(xfield_val/2 :: -( - Field :: 'deliver' -% | 'digest' -% | 'digest_frequency' -% | 'expire' -% | 'include_body' - | 'show_values' - | 'subscription_type' - | 'subscription_depth', - Val :: boolean() - | binary() - | integer() - | [binary()]) -% | erlang:timestamp()) - -> [binary()] -). - -xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest_frequency, Val) -> -% [iolist_to_binary(integer_to_list(Val))]; -%xfield_val(expire, Val) -> -% [jlib:now_to_utc_string(Val)]; -%%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; -xfield_val(show_values, Val) -> Val; -xfield_val(subscription_type, items) -> [<<"items">>]; -xfield_val(subscription_type, nodes) -> [<<"nodes">>]; -xfield_val(subscription_depth, all) -> [<<"all">>]; -xfield_val(subscription_depth, N) -> - [iolist_to_binary(integer_to_list(N))]. - - -bool_to_xopt(true) -> <<"true">>; -bool_to_xopt(false) -> <<"false">>. diff --git a/src/mod_pubsub/pubsub_subscription_odbc.erl b/src/mod_pubsub/pubsub_subscription_odbc.erl deleted file mode 100644 index 55b337c80..000000000 --- a/src/mod_pubsub/pubsub_subscription_odbc.erl +++ /dev/null @@ -1,386 +0,0 @@ -%%% ==================================================================== -%%% ``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-2013, ProcessOne -%%% All Rights Reserved.'' -%%% This software is copyright 2006-2013, ProcessOne. -%%% -%%% @author Pablo Polvorin <pablo.polvorin@process-one.net> -%%% @author based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com> -%%% @version {@vsn}, {@date} {@time} -%%% @end -%%% ==================================================================== - --module(pubsub_subscription_odbc). - --author("pablo.polvorin@process-one.net"). - -%% API --export([init/0, subscribe_node/3, unsubscribe_node/3, - get_subscription/3, set_subscription/4, - get_options_xform/2, parse_options_xform/1]). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB_DELIVER, <<"pubsub#deliver">>). - --define(PUBSUB_DIGEST, <<"pubsub#digest">>). - --define(PUBSUB_DIGEST_FREQUENCY, - <<"pubsub#digest_frequency">>). - --define(PUBSUB_EXPIRE, <<"pubsub#expire">>). - --define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). - --define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). - --define(PUBSUB_SUBSCRIPTION_TYPE, - <<"pubsub#subscription_type">>). - --define(PUBSUB_SUBSCRIPTION_DEPTH, - <<"pubsub#subscription_depth">>). - --define(DELIVER_LABEL, - <<"Whether an entity wants to receive or " - "disable notifications">>). - --define(DIGEST_LABEL, - <<"Whether an entity wants to receive digests " - "(aggregations) of notifications or all " - "notifications individually">>). - --define(DIGEST_FREQUENCY_LABEL, - <<"The minimum number of milliseconds between " - "sending any two notification digests">>). - --define(EXPIRE_LABEL, - <<"The DateTime at which a leased subscription " - "will end or has ended">>). - --define(INCLUDE_BODY_LABEL, - <<"Whether an entity wants to receive an " - "XMPP message body in addition to the " - "payload format">>). - --define(SHOW_VALUES_LABEL, - <<"The presence states for which an entity " - "wants to receive notifications">>). - --define(SUBSCRIPTION_TYPE_LABEL, - <<"Type of notification to receive">>). - --define(SUBSCRIPTION_DEPTH_LABEL, - <<"Depth from subscription for which to " - "receive notifications">>). - --define(SHOW_VALUE_AWAY_LABEL, - <<"XMPP Show Value of Away">>). - --define(SHOW_VALUE_CHAT_LABEL, - <<"XMPP Show Value of Chat">>). - --define(SHOW_VALUE_DND_LABEL, - <<"XMPP Show Value of DND (Do Not Disturb)">>). - --define(SHOW_VALUE_ONLINE_LABEL, - <<"Mere Availability in XMPP (No Show Value)">>). - --define(SHOW_VALUE_XA_LABEL, - <<"XMPP Show Value of XA (Extended Away)">>). - --define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, - <<"Receive notification of new items only">>). - --define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, - <<"Receive notification of new nodes only">>). - --define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, - <<"Receive notification from direct child " - "nodes only">>). - --define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, - <<"Receive notification from all descendent " - "nodes">>). - --define(DB_MOD, pubsub_db_odbc). -%%==================================================================== -%% API -%%==================================================================== - -init() -> ok = create_table(). - --spec(subscribe_node/3 :: -( - _JID :: _, - _NodeID :: _, - Options :: mod_pubsub:subOptions()) - -> {result, mod_pubsub:subId()} -). -subscribe_node(_JID, _NodeID, Options) -> - SubID = make_subid(), - (?DB_MOD):add_subscription(#pubsub_subscription{subid = - SubID, - options = Options}), - {result, SubID}. - --spec(unsubscribe_node/3 :: -( - _JID :: _, - _NodeID :: _, - SubID :: mod_pubsub:subId()) - -> {result, mod_pubsub:subscription()} - | {error, notfound} -). -unsubscribe_node(_JID, _NodeID, SubID) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, Sub} -> - (?DB_MOD):delete_subscription(SubID), {result, Sub}; - notfound -> {error, notfound} - end. - --spec(get_subscription/3 :: -( - _JID :: _, - _NodeID :: _, - SubId :: mod_pubsub:subId()) - -> {result, mod_pubsub:subscription()} - | {error, notfound} -). -get_subscription(_JID, _NodeID, SubID) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, Sub} -> {result, Sub}; - notfound -> {error, notfound} - end. - --spec(set_subscription/4 :: -( - _JID :: _, - _NodeID :: _, - SubId :: mod_pubsub:subId(), - Options :: mod_pubsub:subOptions()) - -> {result, ok} -). -set_subscription(_JID, _NodeID, SubID, Options) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, _} -> - (?DB_MOD):update_subscription(#pubsub_subscription{subid - = SubID, - options = - Options}), - {result, ok}; - notfound -> - (?DB_MOD):add_subscription(#pubsub_subscription{subid = - SubID, - options = Options}), - {result, ok} - end. - -get_options_xform(Lang, Options) -> - Keys = [deliver, show_values, subscription_type, subscription_depth], - XFields = [get_option_xfield(Lang, Key, Options) - || Key <- Keys], - {result, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] - ++ XFields}}. - -parse_options_xform(XFields) -> - case xml:remove_cdata(XFields) of - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - XData when is_list(XData) -> - Opts = set_xoption(XData, []), - {result, Opts}; - Other -> Other - end; - _ -> {result, []} - end. - -%%==================================================================== -%% Internal functions -%%==================================================================== -create_table() -> ok. - --spec(make_subid/0 :: () -> mod_pubsub:subId()). -make_subid() -> - {T1, T2, T3} = now(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -%% -%% Subscription XForm processing. -%% - -%% Return processed options, with types converted and so forth, using -%% Opts as defaults. -set_xoption([], Opts) -> Opts; -set_xoption([{Var, Value} | T], Opts) -> - NewOpts = case var_xfield(Var) of - {error, _} -> Opts; - Key -> - Val = val_xfield(Key, Value), - lists:keystore(Key, 1, Opts, {Key, Val}) - end, - set_xoption(T, NewOpts). - -%% Return the options list's key for an XForm var. -%% Convert Values for option list's Key. -var_xfield(?PUBSUB_DELIVER) -> deliver; -%var_xfield(?PUBSUB_DIGEST) -> digest; -%var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> -% digest_frequency; -%var_xfield(?PUBSUB_EXPIRE) -> expire; -%var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; -var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; -var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> - subscription_type; -var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> - subscription_depth; -var_xfield(_) -> {error, badarg}. - -val_xfield(deliver, [Val]) -> xopt_to_bool(Val); -%val_xfield(digest, [Val]) -> xopt_to_bool(Val); -%val_xfield(digest_frequency, [Val]) -> -% jlib:binary_to_integer(Val); -%val_xfield(expire, [Val]) -> -% jlib:datetime_string_to_timestamp(Val); -%val_xfield(include_body, [Val]) -> xopt_to_bool(Val); -val_xfield(show_values, Vals) -> Vals; -val_xfield(subscription_type, [<<"items">>]) -> items; -val_xfield(subscription_type, [<<"nodes">>]) -> nodes; -val_xfield(subscription_depth, [<<"all">>]) -> all; -val_xfield(subscription_depth, [Depth]) -> - case catch jlib:binary_to_integer(Depth) of - N when is_integer(N) -> N; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end. - -%% Convert XForm booleans to Erlang booleans. -xopt_to_bool(<<"0">>) -> false; -xopt_to_bool(<<"1">>) -> true; -xopt_to_bool(<<"false">>) -> false; -xopt_to_bool(<<"true">>) -> true; -xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. - -%% Return a field for an XForm for Key, with data filled in, if -%% applicable, from Options. -get_option_xfield(Lang, Key, Options) -> - Var = xfield_var(Key), - Label = xfield_label(Key), - {Type, OptEls} = type_and_options(xfield_type(Key), - Lang), - Vals = case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - [tr_xfield_values(Vals) - || Vals <- xfield_val(Key, Val)]; - false -> [] - end, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, Var}, {<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}], - children = OptEls ++ Vals}. - -type_and_options({Type, Options}, Lang) -> - {Type, [tr_xfield_options(O, Lang) || O <- Options]}; -type_and_options(Type, _Lang) -> {Type, []}. - -tr_xfield_options({Value, Label}, Lang) -> - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}]}. - -tr_xfield_values(Value) -> -%% Return the XForm variable name for a subscription option key. -%% Return the XForm variable type for a subscription option key. - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}. - -xfield_var(deliver) -> ?PUBSUB_DELIVER; -%xfield_var(digest) -> ?PUBSUB_DIGEST; -%xfield_var(digest_frequency) -> -% ?PUBSUB_DIGEST_FREQUENCY; -%xfield_var(expire) -> ?PUBSUB_EXPIRE; -%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; -xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; -xfield_var(subscription_type) -> - ?PUBSUB_SUBSCRIPTION_TYPE; -xfield_var(subscription_depth) -> - ?PUBSUB_SUBSCRIPTION_DEPTH. - -xfield_type(deliver) -> <<"boolean">>; -%xfield_type(digest) -> <<"boolean">>; -%xfield_type(digest_frequency) -> <<"text-single">>; -%xfield_type(expire) -> <<"text-single">>; -%xfield_type(include_body) -> <<"boolean">>; -xfield_type(show_values) -> - {<<"list-multi">>, - [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, - {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, - {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, - {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, - {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; -xfield_type(subscription_type) -> - {<<"list-single">>, - [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, - {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; -xfield_type(subscription_depth) -> - {<<"list-single">>, - [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, - {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. - -%% Return the XForm variable label for a subscription option key. -xfield_label(deliver) -> ?DELIVER_LABEL; -%xfield_label(digest) -> ?DIGEST_LABEL; -%xfield_label(digest_frequency) -> -% ?DIGEST_FREQUENCY_LABEL; -%xfield_label(expire) -> ?EXPIRE_LABEL; -%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; -xfield_label(show_values) -> ?SHOW_VALUES_LABEL; -%% Return the XForm value for a subscription option key. -%% Convert erlang booleans to XForms. -xfield_label(subscription_type) -> - ?SUBSCRIPTION_TYPE_LABEL; -xfield_label(subscription_depth) -> - ?SUBSCRIPTION_DEPTH_LABEL. - -xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest_frequency, Val) -> -% [iolist_to_binary(integer_to_list(Val))]; -%xfield_val(expire, Val) -> -% [jlib:now_to_utc_string(Val)]; -%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; -xfield_val(show_values, Val) -> Val; -xfield_val(subscription_type, items) -> [<<"items">>]; -xfield_val(subscription_type, nodes) -> [<<"nodes">>]; -xfield_val(subscription_depth, all) -> [<<"all">>]; -xfield_val(subscription_depth, N) -> - [iolist_to_binary(integer_to_list(N))]. - -bool_to_xopt(false) -> <<"false">>; -bool_to_xopt(true) -> <<"true">>. |