aboutsummaryrefslogtreecommitdiff
path: root/src/mod_pubsub/node_hometree_odbc.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_pubsub/node_hometree_odbc.erl')
-rw-r--r--src/mod_pubsub/node_hometree_odbc.erl1714
1 files changed, 0 insertions, 1714 deletions
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.