]> granicus.if.org Git - ejabberd/commitdiff
Rewrite mod_vcard_ldap to use XML generator
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 29 Jul 2016 14:39:13 +0000 (17:39 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 29 Jul 2016 14:39:13 +0000 (17:39 +0300)
src/ejabberd_config.erl
src/gen_mod.erl
src/mod_vcard.erl
src/mod_vcard_ldap.erl
src/mod_vcard_mnesia.erl
src/mod_vcard_riak.erl
src/mod_vcard_sql.erl

index 87a9187048c11e9735cd9abb1877c8456c159d10..5a39df043b862fff1dbebe643beba1993b5823e9 100644 (file)
@@ -1010,6 +1010,7 @@ replace_module(mod_private_odbc) -> {mod_private, sql};
 replace_module(mod_roster_odbc) -> {mod_roster, sql};
 replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
 replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
+replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
 replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
 replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
 replace_module(Module) ->
index c4306577c72e0ae027ac7bda8fa9992d66f15f0f..e5b504897c0f3b1269f135ec11b2235a6de624f4 100644 (file)
@@ -48,7 +48,7 @@
          opts = [] :: opts() | '_' | '$2'}).
 
 -type opts() :: [{atom(), any()}].
--type db_type() :: sql | mnesia | riak.
+-type db_type() :: sql | mnesia | riak | ldap.
 
 -callback start(binary(), opts()) -> any().
 -callback stop(binary()) -> any().
@@ -147,7 +147,7 @@ start_module(Host, Module) ->
 -spec start_module(binary(), atom(), opts()) -> any().
 
 start_module(Host, Module, Opts0) ->
-    Opts = validate_opts(Module, Opts0),
+    Opts = validate_opts(Host, Module, Opts0),
     ets:insert(ejabberd_modules,
               #ejabberd_module{module_host = {Module, Host},
                                opts = Opts}),
@@ -308,10 +308,10 @@ get_opt_host(Host, Opts, Default) ->
     Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
     ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
 
-validate_opts(Module, Opts) ->
+validate_opts(Host, Module, Opts) ->
     lists:filtermap(
       fun({Opt, Val}) ->
-             case catch Module:mod_opt_type(Opt) of
+             case catch validate_opt(Host, Module, Opt, Opts) of
                  VFun when is_function(VFun) ->
                      try VFun(Val) of
                          _ ->
@@ -346,6 +346,22 @@ validate_opts(Module, Opts) ->
              false
       end, Opts).
 
+validate_opt(Host, Module, Opt, Opts) ->
+    case Module:mod_opt_type(Opt) of
+       VFun1 when is_function(VFun1) ->
+           VFun1;
+       L1 when is_list(L1) ->
+           DBModule = db_mod(Host, Opts, Module),
+           try DBModule:mod_opt_type(Opt) of
+               VFun2 when is_function(VFun2) ->
+                   VFun2;
+               L2 when is_list(L2) ->
+                   lists:usort(L1 ++ L2)
+           catch _:undef ->
+                   L1
+           end
+    end.
+
 -spec db_type(binary() | global, module()) -> db_type();
             (opts(), module()) -> db_type().
 
index b75b6575b57bd6d9107c362c0bd03c9783669f40..231c42dc02f18c48e501964291c52f411a6b25ab 100644 (file)
 -define(PROCNAME, ejabberd_mod_vcard).
 
 -callback init(binary(), gen_mod:opts()) -> any().
+-callback stop(binary()) -> any().
 -callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
 -callback get_vcard(binary(), binary()) -> [xmlel()] | error.
 -callback set_vcard(binary(), binary(),
                    xmlel(), #vcard_search{}) -> {atomic, any()}.
+-callback search_fields(binary()) -> [{binary(), binary()}].
+-callback search_reported(binary()) -> [{binary(), binary()}].
 -callback search(binary(), [{binary(), [binary()]}], boolean(),
-                infinity | pos_integer()) -> [binary()].
+                infinity | pos_integer()) -> [{binary(), binary()}].
 -callback remove_user(binary(), binary()) -> {atomic, any()}.
 
 start(Host, Opts) ->
@@ -134,6 +137,8 @@ stop(Host) ->
                                     ?NS_VCARD),
     ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
                          get_sm_features, 50),
+    Mod = gen_mod:db_type(Host, ?MODULE),
+    Mod:stop(Host),
     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
     Proc ! stop,
     {wait, Proc}.
@@ -214,7 +219,8 @@ process_vcard(#iq{type = get, lang = Lang} = IQ) ->
 
 -spec process_search(iq()) -> iq().
 process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
-    xmpp:make_iq_result(IQ, mk_search_form(To, Lang));
+    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+    xmpp:make_iq_result(IQ, mk_search_form(To, ServerHost, Lang));
 process_search(#iq{type = set, to = To, lang = Lang,
                   sub_els = [#search{xdata = #xdata{type = submit,
                                                     fields = Fs}}]} = IQ) ->
@@ -366,22 +372,13 @@ mk_tfield(Label, Var, Lang) ->
 mk_field(Var, Val) ->
     #xdata_field{var = Var, values = [Val]}.
 
--spec mk_search_form(jid(), undefined | binary()) -> search().
-mk_search_form(JID, Lang) ->
+-spec mk_search_form(jid(), binary(), undefined | binary()) -> search().
+mk_search_form(JID, ServerHost, Lang) ->
     Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
              (jid:to_string(JID))/binary>>,
-    Fs = [mk_tfield(<<"User">>, <<"user">>, Lang),
-         mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
-         mk_tfield(<<"Name">>, <<"first">>, Lang),
-         mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
-         mk_tfield(<<"Family Name">>, <<"last">>, Lang),
-         mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
-         mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
-         mk_tfield(<<"Country">>, <<"ctry">>, Lang),
-         mk_tfield(<<"City">>, <<"locality">>, Lang),
-         mk_tfield(<<"Email">>, <<"email">>, Lang),
-         mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
-         mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
+    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+    SearchFields = Mod:search_fields(ServerHost),
+    Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields],
     X = #xdata{type = form,
               title = Title,
               instructions =
@@ -398,55 +395,20 @@ mk_search_form(JID, Lang) ->
 
 -spec search_result(undefined | binary(), jid(), binary(), [xdata_field()]) -> xdata().
 search_result(Lang, JID, ServerHost, XFields) ->
+    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+    Reported = [mk_tfield(Label, Var, Lang) ||
+                  {Label, Var} <- Mod:search_reported(ServerHost)],
     #xdata{type = result,
           title = <<(translate:translate(Lang,
                                          <<"Search Results for ">>))/binary,
                     (jid:to_string(JID))/binary>>,
-          reported = [mk_tfield(<<"Jabber ID">>, <<"jid">>, Lang),
-                      mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
-                      mk_tfield(<<"Name">>, <<"first">>, Lang),
-                      mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
-                      mk_tfield(<<"Family Name">>, <<"last">>, Lang),
-                      mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
-                      mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
-                      mk_tfield(<<"Country">>, <<"ctry">>, Lang),
-                      mk_tfield(<<"City">>, <<"locality">>, Lang),
-                      mk_tfield(<<"Email">>, <<"email">>, Lang),
-                      mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
-                      mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
-          items = lists:map(fun (R) -> record_to_item(ServerHost, R) end,
+          reported = Reported,
+          items = lists:map(fun (Item) -> item_to_field(Item) end,
                             search(ServerHost, XFields))}.
 
--spec record_to_item(binary(), [binary()] | #vcard_search{}) -> [xdata_field()].
-record_to_item(LServer,
-              [Username, FN, Family, Given, Middle, Nickname, BDay,
-               CTRY, Locality, EMail, OrgName, OrgUnit]) ->
-    [mk_field(<<"jid">>, <<Username/binary, "@", LServer/binary>>),
-     mk_field(<<"fn">>, FN),
-     mk_field(<<"last">>, Family),
-     mk_field(<<"first">>, Given),
-     mk_field(<<"middle">>, Middle),
-     mk_field(<<"nick">>, Nickname),
-     mk_field(<<"bday">>, BDay),
-     mk_field(<<"ctry">>, CTRY),
-     mk_field(<<"locality">>, Locality),
-     mk_field(<<"email">>, EMail),
-     mk_field(<<"orgname">>, OrgName),
-     mk_field(<<"orgunit">>, OrgUnit)];
-record_to_item(_LServer, #vcard_search{} = R) ->
-    {User, Server} = R#vcard_search.user,
-    [mk_field(<<"jid">>, <<User/binary, "@", Server/binary>>),
-     mk_field(<<"fn">>, (R#vcard_search.fn)),
-     mk_field(<<"last">>, (R#vcard_search.family)),
-     mk_field(<<"first">>, (R#vcard_search.given)),
-     mk_field(<<"middle">>, (R#vcard_search.middle)),
-     mk_field(<<"nick">>, (R#vcard_search.nickname)),
-     mk_field(<<"bday">>, (R#vcard_search.bday)),
-     mk_field(<<"ctry">>, (R#vcard_search.ctry)),
-     mk_field(<<"locality">>, (R#vcard_search.locality)),
-     mk_field(<<"email">>, (R#vcard_search.email)),
-     mk_field(<<"orgname">>, (R#vcard_search.orgname)),
-     mk_field(<<"orgunit">>, (R#vcard_search.orgunit))].
+-spec item_to_field([{binary(), binary()}]) -> [xdata_field()].
+item_to_field(Items) ->
+    [mk_field(Var, Value) || {Var, Value} <- Items].
 
 -spec search(binary(), [xdata_field()]) -> [binary()].
 search(LServer, XFields) ->
index a0ad305a962b4b1df28628376c12e81231aa17b6..74b20d2ffd18505592fe729b9941a55b37a3810a 100644 (file)
@@ -1,53 +1,30 @@
-%%%----------------------------------------------------------------------
-%%% File    : mod_vcard_ldap.erl
-%%% Author  : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : Support for VCards from LDAP storage.
-%%% Created :  2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
 %%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2016   ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%---------------------u-------------------------------------------------
-
+%%% @end
+%%% Created : 29 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
 -module(mod_vcard_ldap).
 
--behaviour(ejabberd_config).
-
--author('alexey@process-one.net').
-
 -behaviour(gen_server).
+-behaviour(mod_vcard).
 
--behaviour(gen_mod).
-
-%% gen_server callbacks.
--export([init/1, handle_info/2, handle_call/3,
-        handle_cast/2, terminate/2, code_change/3]).
+%% API
+-export([start_link/2]).
+-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4,
+        remove_user/2, import/2, search_fields/1, search_reported/1,
+        mod_opt_type/1, opt_type/1]).
 
--export([start/2, start_link/2, stop/1,
-        get_sm_features/5, process_local_iq/3, process_sm_iq/3,
-        remove_user/1, route/4, transform_module_options/1,
-        mod_opt_type/1, opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+        terminate/2, code_change/3]).
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
-
 -include("eldap.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
 
 -define(PROCNAME, ejabberd_mod_vcard_ldap).
 
         deref_aliases = never      :: never | searching | finding | always,
          matches = 0                :: non_neg_integer()}).
 
--define(VCARD_MAP,
-       [{<<"NICKNAME">>, <<"%u">>, []},
-        {<<"FN">>, <<"%s">>, [<<"displayName">>]},
-        {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
-        {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
-        {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
-        {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
-        {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
-        {<<"CTRY">>, <<"%s">>, [<<"c">>]},
-        {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
-        {<<"STREET">>, <<"%s">>, [<<"street">>]},
-        {<<"REGION">>, <<"%s">>, [<<"st">>]},
-        {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
-        {<<"TITLE">>, <<"%s">>, [<<"title">>]},
-        {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
-        {<<"DESC">>, <<"%s">>, [<<"description">>]},
-        {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
-        {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
-        {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
-        {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
-        {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
-
--define(SEARCH_FIELDS,
-       [{<<"User">>, <<"%u">>},
-        {<<"Full Name">>, <<"displayName">>},
-        {<<"Given Name">>, <<"givenName">>},
-        {<<"Middle Name">>, <<"initials">>},
-        {<<"Family Name">>, <<"sn">>},
-        {<<"Nickname">>, <<"%u">>},
-        {<<"Birthday">>, <<"birthDay">>},
-        {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
-        {<<"Email">>, <<"mail">>},
-        {<<"Organization Name">>, <<"o">>},
-        {<<"Organization Unit">>, <<"ou">>}]).
-
--define(SEARCH_REPORTED,
-       [{<<"Full Name">>, <<"FN">>},
-        {<<"Given Name">>, <<"FIRST">>},
-        {<<"Middle Name">>, <<"MIDDLE">>},
-        {<<"Family Name">>, <<"LAST">>},
-        {<<"Nickname">>, <<"NICK">>},
-        {<<"Birthday">>, <<"BDAY">>},
-        {<<"Country">>, <<"CTRY">>},
-        {<<"City">>, <<"LOCALITY">>},
-        {<<"Email">>, <<"EMAIL">>},
-        {<<"Organization Name">>, <<"ORGNAME">>},
-        {<<"Organization Unit">>, <<"ORGUNIT">>}]).
-
-handle_cast(_Request, State) -> {noreply, State}.
-
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-start(Host, Opts) ->
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+init(Host, Opts) ->
     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
     ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
                 transient, 1000, worker, [?MODULE]},
@@ -138,141 +70,127 @@ stop(Host) ->
     supervisor:terminate_child(ejabberd_sup, Proc),
     supervisor:delete_child(ejabberd_sup, Proc).
 
-depends(_Host, _Opts) ->
-    [].
-
-terminate(_Reason, State) ->
-    Host = State#state.serverhost,
-    gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
-                                    ?NS_VCARD),
-    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
-                                    ?NS_VCARD),
-    ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
-                         get_sm_features, 50),
-    case State#state.search of
-      true ->
-         ejabberd_router:unregister_route(State#state.myhost);
-      _ -> ok
+get_vcard(LUser, LServer) ->
+    {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+    VCardMap = State#state.vcard_map,
+    case find_ldap_user(LUser, State) of
+       #eldap_entry{attributes = Attributes} ->
+           ldap_attributes_to_vcard(Attributes, VCardMap,
+                                    {LUser, LServer});
+       _ ->
+           []
     end.
 
-start_link(Host, Opts) ->
-    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
-    gen_server:start_link({local, Proc}, ?MODULE,
-                         [Host, Opts], []).
+set_vcard(_LUser, _LServer, _VCard, _VCardSearch) ->
+    {atomic, not_implemented}.
+
+search_fields(LServer) ->
+    {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+    State#state.search_fields.
+
+search_reported(LServer) ->
+    {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+    State#state.search_reported.
+
+search(LServer, Data, _AllowReturnAll, MaxMatch) ->
+    {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+    Base = State#state.base,
+    SearchFilter = State#state.search_filter,
+    Eldap_ID = State#state.eldap_id,
+    UIDs = State#state.uids,
+    ReportedAttrs = State#state.search_reported_attrs,
+    Filter = eldap:'and'([SearchFilter,
+                         eldap_utils:make_filter(Data, UIDs)]),
+    case eldap_pool:search(Eldap_ID,
+                          [{base, Base}, {filter, Filter}, {limit, MaxMatch},
+                           {deref_aliases, State#state.deref_aliases},
+                           {attributes, ReportedAttrs}])
+       of
+      #eldap_search_result{entries = E} ->
+         search_items(E, State);
+      _ ->
+         []
+    end.
 
+search_items(Entries, State) ->
+    LServer = State#state.serverhost,
+    SearchReported = State#state.search_reported,
+    VCardMap = State#state.vcard_map,
+    UIDs = State#state.uids,
+    Attributes = lists:map(fun (E) ->
+                                  #eldap_entry{attributes = Attrs} = E, Attrs
+                          end,
+                          Entries),
+    lists:flatmap(
+      fun(Attrs) ->
+             case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
+                 {U, UIDAttrFormat} ->
+                     case eldap_utils:get_user_part(U, UIDAttrFormat) of
+                         {ok, Username} ->
+                             case ejabberd_auth:is_user_exists(Username,
+                                                               LServer) of
+                                 true ->
+                                     RFields = lists:map(
+                                                 fun({_, VCardName}) ->
+                                                         {VCardName,
+                                                          map_vcard_attr(VCardName,
+                                                                         Attrs,
+                                                                         VCardMap,
+                                                                         {Username,
+                                                                          ?MYNAME})}
+                                                 end,
+                                                 SearchReported),
+                                     J = <<Username/binary, $@, LServer/binary>>,
+                                     [{<<"jid">>, J} | RFields];
+                                 _ ->
+                                     []
+                             end;
+                         _ ->
+                             []
+                     end;
+                 <<"">> ->
+                     []
+             end
+      end, Attributes).
+
+remove_user(_User, _Server) ->
+    {atomic, not_implemented}.
+
+import(_, _) ->
+    pass.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
 init([Host, Opts]) ->
     State = parse_options(Host, Opts),
-    IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
-                             one_queue),
-    gen_iq_handler:add_iq_handler(ejabberd_local, Host,
-                                 ?NS_VCARD, ?MODULE, process_local_iq, IQDisc),
-    gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
-                                 ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
-    ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
-                      get_sm_features, 50),
     eldap_pool:start_link(State#state.eldap_id,
                          State#state.servers, State#state.backups,
                          State#state.port, State#state.dn,
                          State#state.password, State#state.tls_options),
-    case State#state.search of
-      true ->
-         ejabberd_router:register_route(State#state.myhost, Host);
-      _ -> ok
-    end,
     {ok, State}.
 
-handle_info({route, From, To, Packet}, State) ->
-    case catch do_route(State, From, To, Packet) of
-      Pid when is_pid(Pid) -> ok;
-      _ ->
-         Err = jlib:make_error_reply(Packet,
-                                     ?ERR_INTERNAL_SERVER_ERROR),
-         ejabberd_router:route(To, From, Err)
-    end,
-    {noreply, State};
-handle_info(_Info, State) -> {noreply, State}.
-
-get_sm_features({error, _Error} = Acc, _From, _To,
-               _Node, _Lang) ->
-    Acc;
-get_sm_features(Acc, _From, _To, Node, _Lang) ->
-    case Node of
-      <<"">> ->
-         case Acc of
-           {result, Features} -> {result, [?NS_VCARD | Features]};
-           empty -> {result, [?NS_VCARD]}
-         end;
-      _ -> Acc
-    end.
+handle_call(get_state, _From, State) ->
+    {reply, {ok, State}, State};
+handle_call(_Request, _From, State) ->
+    Reply = ok,
+    {reply, Reply, State}.
 
-process_local_iq(_From, _To,
-                #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
-    case Type of
-      set ->
-         Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-         IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
-      get ->
-         IQ#iq{type = result,
-               sub_el =
-                   [#xmlel{name = <<"vCard">>,
-                           attrs = [{<<"xmlns">>, ?NS_VCARD}],
-                           children =
-                               [#xmlel{name = <<"FN">>, attrs = [],
-                                       children =
-                                           [{xmlcdata, <<"ejabberd">>}]},
-                                #xmlel{name = <<"URL">>, attrs = [],
-                                       children = [{xmlcdata, ?EJABBERD_URI}]},
-                                #xmlel{name = <<"DESC">>, attrs = [],
-                                       children =
-                                           [{xmlcdata,
-                                             <<(translate:translate(Lang,
-                                                                    <<"Erlang Jabber Server">>))/binary,
-                                               "\nCopyright (c) 2002-2016 ProcessOne">>}]},
-                                #xmlel{name = <<"BDAY">>, attrs = [],
-                                       children =
-                                           [{xmlcdata, <<"2002-11-16">>}]}]}]}
-    end.
+handle_cast(_Msg, State) ->
+    {noreply, State}.
 
-process_sm_iq(_From, #jid{lserver = LServer} = To,
-             #iq{sub_el = SubEl} = IQ) ->
-    case catch process_vcard_ldap(To, IQ, LServer) of
-      {'EXIT', _} ->
-         IQ#iq{type = error,
-               sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
-      Other -> Other
-    end.
+handle_info(_Info, State) ->
+    {noreply, State}.
 
-process_vcard_ldap(To, IQ, Server) ->
-    {ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
-    #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ,
-    case Type of
-      set ->
-         Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-         IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
-      get ->
-         #jid{luser = LUser} = To,
-         LServer = State#state.serverhost,
-         case ejabberd_auth:is_user_exists(LUser, LServer) of
-           true ->
-               VCardMap = State#state.vcard_map,
-               case find_ldap_user(LUser, State) of
-                 #eldap_entry{attributes = Attributes} ->
-                     Vcard = ldap_attributes_to_vcard(Attributes, VCardMap,
-                                                      {LUser, LServer}),
-                     IQ#iq{type = result, sub_el = Vcard};
-                 _ -> IQ#iq{type = result, sub_el = []}
-               end;
-           _ -> IQ#iq{type = result, sub_el = []}
-         end
-    end.
+terminate(_Reason, _State) ->
+    ok.
 
-handle_call(get_state, _From, State) ->
-    {reply, {ok, State}, State};
-handle_call(stop, _From, State) ->
-    {stop, normal, ok, State};
-handle_call(_Request, _From, State) ->
-    {reply, bad_request, State}.
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
 
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
 find_ldap_user(User, State) ->
     Base = State#state.base,
     RFC2254_Filter = State#state.user_filter,
@@ -403,309 +321,6 @@ ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
           children = [{xmlcdata, Value}]};
 ldap_attribute_to_vcard(_, _) -> none.
 
--define(TLFIELD(Type, Label, Var),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"type">>, Type},
-                   {<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children = []}).
-
--define(FORM(JID, SearchFields),
-       [#xmlel{name = <<"instructions">>, attrs = [],
-               children =
-                   [{xmlcdata,
-                     translate:translate(Lang,
-                                         <<"You need an x:data capable client to "
-                                           "search">>)}]},
-        #xmlel{name = <<"x">>,
-               attrs =
-                   [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
-               children =
-                   [#xmlel{name = <<"title">>, attrs = [],
-                           children =
-                               [{xmlcdata,
-                                 <<(translate:translate(Lang,
-                                                        <<"Search users in ">>))/binary,
-                                   (jid:to_string(JID))/binary>>}]},
-                    #xmlel{name = <<"instructions">>, attrs = [],
-                           children =
-                               [{xmlcdata,
-                                 translate:translate(Lang,
-                                                     <<"Fill in fields to search for any matching "
-                                                       "Jabber User">>)}]}]
-                     ++
-                     lists:map(fun ({X, Y}) ->
-                                       ?TLFIELD(<<"text-single">>, X, Y)
-                               end,
-                               SearchFields)}]).
-
-do_route(State, From, To, Packet) ->
-    spawn(?MODULE, route, [State, From, To, Packet]).
-
-route(State, From, To, Packet) ->
-    #jid{user = User, resource = Resource} = To,
-    ServerHost = State#state.serverhost,
-    if (User /= <<"">>) or (Resource /= <<"">>) ->
-          Err = jlib:make_error_reply(Packet,
-                                      ?ERR_SERVICE_UNAVAILABLE),
-          ejabberd_router:route(To, From, Err);
-       true ->
-          IQ = jlib:iq_query_info(Packet),
-          case IQ of
-            #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
-                sub_el = SubEl} ->
-                case Type of
-                  set ->
-                      XDataEl = find_xdata_el(SubEl),
-                      case XDataEl of
-                        false ->
-                            Txt = <<"Data form not found">>,
-                            Err = jlib:make_error_reply(
-                                    Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
-                            ejabberd_router:route(To, From, Err);
-                        _ ->
-                            XData = jlib:parse_xdata_submit(XDataEl),
-                            case XData of
-                              invalid ->
-                                  Txt = <<"Incorrect data form">>,
-                                  Err = jlib:make_error_reply(
-                                          Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
-                                  ejabberd_router:route(To, From, Err);
-                              _ ->
-                                  ResIQ = IQ#iq{type = result,
-                                                sub_el =
-                                                    [#xmlel{name = <<"query">>,
-                                                            attrs =
-                                                                [{<<"xmlns">>,
-                                                                  ?NS_SEARCH}],
-                                                            children =
-                                                                [#xmlel{name =
-                                                                            <<"x">>,
-                                                                        attrs =
-                                                                            [{<<"xmlns">>,
-                                                                              ?NS_XDATA},
-                                                                             {<<"type">>,
-                                                                              <<"result">>}],
-                                                                        children
-                                                                            =
-                                                                            search_result(Lang,
-                                                                                          To,
-                                                                                          State,
-                                                                                          XData)}]}]},
-                                  ejabberd_router:route(To, From,
-                                                        jlib:iq_to_xml(ResIQ))
-                            end
-                      end;
-                  get ->
-                      SearchFields = State#state.search_fields,
-                      ResIQ = IQ#iq{type = result,
-                                    sub_el =
-                                        [#xmlel{name = <<"query">>,
-                                                attrs =
-                                                    [{<<"xmlns">>,
-                                                      ?NS_SEARCH}],
-                                                children =
-                                                    ?FORM(To, SearchFields)}]},
-                      ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
-                end;
-            #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
-                case Type of
-                  set ->
-                      Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-                      Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
-                      ejabberd_router:route(To, From, Err);
-                  get ->
-                      Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
-                                                     [],
-                                                     [ServerHost, ?MODULE,
-                                                      <<"">>, <<"">>]),
-                      ResIQ = IQ#iq{type = result,
-                                    sub_el =
-                                        [#xmlel{name = <<"query">>,
-                                                attrs =
-                                                    [{<<"xmlns">>,
-                                                      ?NS_DISCO_INFO}],
-                                                children =
-                                                    [#xmlel{name =
-                                                                <<"identity">>,
-                                                            attrs =
-                                                                [{<<"category">>,
-                                                                  <<"directory">>},
-                                                                 {<<"type">>,
-                                                                  <<"user">>},
-                                                                 {<<"name">>,
-                                                                  translate:translate(Lang,
-                                                                                      <<"vCard User Search">>)}],
-                                                            children = []},
-                                                     #xmlel{name =
-                                                                <<"feature">>,
-                                                            attrs =
-                                                                [{<<"var">>,
-                                                                  ?NS_SEARCH}],
-                                                            children = []},
-                                                     #xmlel{name =
-                                                                <<"feature">>,
-                                                            attrs =
-                                                                [{<<"var">>,
-                                                                  ?NS_VCARD}],
-                                                            children = []}]
-                                                      ++ Info}]},
-                      ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
-                end;
-            #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
-                case Type of
-                  set ->
-                      Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-                      Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
-                      ejabberd_router:route(To, From, Err);
-                  get ->
-                      ResIQ = IQ#iq{type = result,
-                                    sub_el =
-                                        [#xmlel{name = <<"query">>,
-                                                attrs =
-                                                    [{<<"xmlns">>,
-                                                      ?NS_DISCO_ITEMS}],
-                                                children = []}]},
-                      ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
-                end;
-            #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
-                ResIQ = IQ#iq{type = result,
-                              sub_el =
-                                  [#xmlel{name = <<"vCard">>,
-                                          attrs = [{<<"xmlns">>, ?NS_VCARD}],
-                                          children = iq_get_vcard(Lang)}]},
-                ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
-            _ ->
-                Err = jlib:make_error_reply(Packet,
-                                            ?ERR_SERVICE_UNAVAILABLE),
-                ejabberd_router:route(To, From, Err)
-          end
-    end.
-
-iq_get_vcard(Lang) ->
-    [#xmlel{name = <<"FN">>, attrs = [],
-           children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
-     #xmlel{name = <<"URL">>, attrs = [],
-           children = [{xmlcdata, ?EJABBERD_URI}]},
-     #xmlel{name = <<"DESC">>, attrs = [],
-           children =
-               [{xmlcdata,
-                 <<(translate:translate(Lang,
-                                        <<"ejabberd vCard module">>))/binary,
-                   "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
--define(LFIELD(Label, Var),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children = []}).
-
-search_result(Lang, JID, State, Data) ->
-    SearchReported = State#state.search_reported,
-    Header = [#xmlel{name = <<"title">>, attrs = [],
-                    children =
-                        [{xmlcdata,
-                          <<(translate:translate(Lang,
-                                                 <<"Search Results for ">>))/binary,
-                            (jid:to_string(JID))/binary>>}]},
-             #xmlel{name = <<"reported">>, attrs = [],
-                    children =
-                        [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
-                                  <<"jid">>)]
-                          ++
-                          lists:map(fun ({Name, Value}) ->
-                                            ?TLFIELD(<<"text-single">>, Name,
-                                                     Value)
-                                    end,
-                                    SearchReported)}],
-    case search(State, Data) of
-      error -> Header;
-      Result -> Header ++ Result
-    end.
-
--define(FIELD(Var, Val),
-       #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, Val}]}]}).
-
-search(State, Data) ->
-    Base = State#state.base,
-    SearchFilter = State#state.search_filter,
-    Eldap_ID = State#state.eldap_id,
-    UIDs = State#state.uids,
-    Limit = State#state.matches,
-    ReportedAttrs = State#state.search_reported_attrs,
-    Filter = eldap:'and'([SearchFilter,
-                         eldap_utils:make_filter(Data, UIDs)]),
-    case eldap_pool:search(Eldap_ID,
-                          [{base, Base}, {filter, Filter}, {limit, Limit},
-                           {deref_aliases, State#state.deref_aliases},
-                           {attributes, ReportedAttrs}])
-       of
-      #eldap_search_result{entries = E} ->
-         search_items(E, State);
-      _ -> error
-    end.
-
-search_items(Entries, State) ->
-    LServer = State#state.serverhost,
-    SearchReported = State#state.search_reported,
-    VCardMap = State#state.vcard_map,
-    UIDs = State#state.uids,
-    Attributes = lists:map(fun (E) ->
-                                  #eldap_entry{attributes = Attrs} = E, Attrs
-                          end,
-                          Entries),
-    lists:flatmap(fun (Attrs) ->
-                         case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
-                           {U, UIDAttrFormat} ->
-                               case eldap_utils:get_user_part(U, UIDAttrFormat)
-                                   of
-                                 {ok, Username} ->
-                                     case
-                                       ejabberd_auth:is_user_exists(Username,
-                                                                    LServer)
-                                         of
-                                       true ->
-                                           RFields = lists:map(fun ({_,
-                                                                     VCardName}) ->
-                                                                       {VCardName,
-                                                                        map_vcard_attr(VCardName,
-                                                                                       Attrs,
-                                                                                       VCardMap,
-                                                                                       {Username,
-                                                                                        ?MYNAME})}
-                                                               end,
-                                                               SearchReported),
-                                           Result = [?FIELD(<<"jid">>,
-                                                            <<Username/binary,
-                                                              "@",
-                                                              LServer/binary>>)]
-                                                      ++
-                                                      [?FIELD(Name, Value)
-                                                       || {Name, Value}
-                                                              <- RFields],
-                                           [#xmlel{name = <<"item">>,
-                                                   attrs = [],
-                                                   children = Result}];
-                                       _ -> []
-                                     end;
-                                 _ -> []
-                               end;
-                           <<"">> -> []
-                         end
-                 end,
-                 Attributes).
-
-remove_user(_User) -> true.
-
-%%%-----------------------
-%%% Auxiliary functions.
-%%%-----------------------
-
 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
     Res = lists:filter(fun ({Name, _, _}) ->
                               eldap_utils:case_insensitive_match(Name,
@@ -725,19 +340,54 @@ process_pattern(Str, {User, Domain}, AttrValues) ->
                        [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
                          [{<<"%s">>, V, 1} || V <- AttrValues]).
 
-find_xdata_el(#xmlel{children = SubEls}) ->
-    find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
-                      children = SubEls}
-               | Els]) ->
-    case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-      ?NS_XDATA ->
-         #xmlel{name = Name, attrs = Attrs, children = SubEls};
-      _ -> find_xdata_el1(Els)
-    end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
+default_vcard_map() ->
+    [{<<"NICKNAME">>, <<"%u">>, []},
+     {<<"FN">>, <<"%s">>, [<<"displayName">>]},
+     {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
+     {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
+     {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
+     {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
+     {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
+     {<<"CTRY">>, <<"%s">>, [<<"c">>]},
+     {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
+     {<<"STREET">>, <<"%s">>, [<<"street">>]},
+     {<<"REGION">>, <<"%s">>, [<<"st">>]},
+     {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
+     {<<"TITLE">>, <<"%s">>, [<<"title">>]},
+     {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
+     {<<"DESC">>, <<"%s">>, [<<"description">>]},
+     {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
+     {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
+     {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
+     {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
+     {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
+
+default_search_fields() ->
+    [{<<"User">>, <<"%u">>},
+     {<<"Full Name">>, <<"displayName">>},
+     {<<"Given Name">>, <<"givenName">>},
+     {<<"Middle Name">>, <<"initials">>},
+     {<<"Family Name">>, <<"sn">>},
+     {<<"Nickname">>, <<"%u">>},
+     {<<"Birthday">>, <<"birthDay">>},
+     {<<"Country">>, <<"c">>},
+     {<<"City">>, <<"l">>},
+     {<<"Email">>, <<"mail">>},
+     {<<"Organization Name">>, <<"o">>},
+     {<<"Organization Unit">>, <<"ou">>}].
+
+default_search_reported() ->
+    [{<<"Full Name">>, <<"FN">>},
+     {<<"Given Name">>, <<"FIRST">>},
+     {<<"Middle Name">>, <<"MIDDLE">>},
+     {<<"Family Name">>, <<"LAST">>},
+     {<<"Nickname">>, <<"NICK">>},
+     {<<"Birthday">>, <<"BDAY">>},
+     {<<"Country">>, <<"CTRY">>},
+     {<<"City">>, <<"LOCALITY">>},
+     {<<"Email">>, <<"EMAIL">>},
+     {<<"Organization Name">>, <<"ORGNAME">>},
+     {<<"Organization Unit">>, <<"ORGUNIT">>}].
 
 parse_options(Host, Opts) ->
     MyHost = gen_mod:get_opt_host(Host, Opts,
@@ -784,19 +434,19 @@ parse_options(Host, Opts) ->
                                                   [iolist_to_binary(E)
                                                    || E <- L]}
                                          end, Ls)
-                               end, ?VCARD_MAP),
+                               end, default_vcard_map()),
     SearchFields = gen_mod:get_opt(ldap_search_fields, Opts,
                                    fun(Ls) ->
                                            [{iolist_to_binary(S),
                                              iolist_to_binary(P)}
                                             || {S, P} <- Ls]
-                                   end, ?SEARCH_FIELDS),
+                                   end, default_search_fields()),
     SearchReported = gen_mod:get_opt(ldap_search_reported, Opts,
                                      fun(Ls) ->
                                              [{iolist_to_binary(S),
                                                iolist_to_binary(P)}
                                               || {S, P} <- Ls]
-                                     end, ?SEARCH_REPORTED),
+                                     end, default_search_reported()),
     UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
     VCardMapAttrs = lists:usort(lists:append([A
                                              || {_, _, A} <- VCardMap])
@@ -834,27 +484,11 @@ parse_options(Host, Opts) ->
           search_reported_attrs = SearchReportedAttrs,
           matches = Matches}.
 
-transform_module_options(Opts) ->
-    lists:map(
-      fun({ldap_vcard_map, Map}) ->
-              NewMap = lists:map(
-                         fun({Field, Pattern, Attrs}) ->
-                                 {Field, [{Pattern, Attrs}]};
-                            (Opt) ->
-                                 Opt
-                         end, Map),
-              {ldap_vcard_map, NewMap};
-         (Opt) ->
-              Opt
-      end, Opts).
-
 check_filter(F) ->
     NewF = iolist_to_binary(F),
     {ok, _} = eldap_filter:parse(NewF),
     NewF.
 
-mod_opt_type(host) -> fun iolist_to_binary/1;
-mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
 mod_opt_type(ldap_filter) -> fun check_filter/1;
 mod_opt_type(ldap_search_fields) ->
     fun (Ls) ->
@@ -882,12 +516,6 @@ mod_opt_type(ldap_vcard_map) ->
                      end,
                      Ls)
     end;
-mod_opt_type(matches) ->
-    fun (infinity) -> 0;
-       (I) when is_integer(I), I > 0 -> I
-    end;
-mod_opt_type(search) ->
-    fun (B) when is_boolean(B) -> B end;
 mod_opt_type(deref_aliases) ->
     fun (never) -> never;
        (searching) -> searching;
@@ -926,9 +554,9 @@ mod_opt_type(ldap_tls_verify) ->
        (false) -> false
     end;
 mod_opt_type(_) ->
-    [host, iqdisc, ldap_filter, ldap_search_fields,
+    [ldap_filter, ldap_search_fields,
      ldap_search_reported, ldap_uids, ldap_vcard_map,
-     matches, search, deref_aliases, ldap_backups, ldap_base,
+     deref_aliases, ldap_backups, ldap_base,
      ldap_deref_aliases, ldap_encrypt, ldap_password,
      ldap_port, ldap_rootdn, ldap_servers,
      ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
index 67abed09b67d154b549d34069c051b078e016ac0..3b64c29eff03543cb9aa14fc81c35cf7f359f642 100644 (file)
@@ -11,7 +11,8 @@
 -behaviour(mod_vcard).
 
 %% API
--export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]).
+-export([init/2, stop/1, import/2, get_vcard/2, set_vcard/4, search/4,
+        search_fields/1, search_reported/1, remove_user/2]).
 
 -include("ejabberd.hrl").
 -include("xmpp.hrl").
@@ -43,6 +44,9 @@ init(_Host, _Opts) ->
     mnesia:add_table_index(vcard_search, lorgname),
     mnesia:add_table_index(vcard_search, lorgunit).
 
+stop(_Host) ->
+    ok.
+
 get_vcard(LUser, LServer) ->
     US = {LUser, LServer},
     F = fun () -> mnesia:read({vcard, US}) end,
@@ -71,15 +75,44 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
                {'EXIT', Reason} ->
                    ?ERROR_MSG("~p", [Reason]), [];
                Rs ->
+                   Fields = lists:map(fun record_to_item/1, Rs),
                    case MaxMatch of
                        infinity ->
-                           Rs;
+                           Fields;
                        Val ->
-                           lists:sublist(Rs, Val)
+                           lists:sublist(Fields, Val)
                    end
            end
     end.
 
+search_fields(_LServer) ->
+    [{<<"User">>, <<"user">>},
+     {<<"Full Name">>, <<"fn">>},
+     {<<"Name">>, <<"first">>},
+     {<<"Middle Name">>, <<"middle">>},
+     {<<"Family Name">>, <<"last">>},
+     {<<"Nickname">>, <<"nick">>},
+     {<<"Birthday">>, <<"bday">>},
+     {<<"Country">>, <<"ctry">>},
+     {<<"City">>, <<"locality">>},
+     {<<"Email">>, <<"email">>},
+     {<<"Organization Name">>, <<"orgname">>},
+     {<<"Organization Unit">>, <<"orgunit">>}].
+
+search_reported(_LServer) ->
+    [{<<"Jabber ID">>, <<"jid">>},
+     {<<"Full Name">>, <<"fn">>},
+     {<<"Name">>, <<"first">>},
+     {<<"Middle Name">>, <<"middle">>},
+     {<<"Family Name">>, <<"last">>},
+     {<<"Nickname">>, <<"nick">>},
+     {<<"Birthday">>, <<"bday">>},
+     {<<"Country">>, <<"ctry">>},
+     {<<"City">>, <<"locality">>},
+     {<<"Email">>, <<"email">>},
+     {<<"Organization Name">>, <<"orgname">>},
+     {<<"Organization Unit">>, <<"orgunit">>}].
+
 remove_user(LUser, LServer) ->
     US = {LUser, LServer},
     F = fun () ->
@@ -211,3 +244,19 @@ parts_to_string(Parts) ->
     str:strip(list_to_binary(
                 lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
               right, $.).
+
+-spec record_to_item(#vcard_search{}) -> [{binary(), binary()}].
+record_to_item(R) ->
+    {User, Server} = R#vcard_search.user,
+    [{<<"jid">>, <<User/binary, "@", Server/binary>>},
+     {<<"fn">>, (R#vcard_search.fn)},
+     {<<"last">>, (R#vcard_search.family)},
+     {<<"first">>, (R#vcard_search.given)},
+     {<<"middle">>, (R#vcard_search.middle)},
+     {<<"nick">>, (R#vcard_search.nickname)},
+     {<<"bday">>, (R#vcard_search.bday)},
+     {<<"ctry">>, (R#vcard_search.ctry)},
+     {<<"locality">>, (R#vcard_search.locality)},
+     {<<"email">>, (R#vcard_search.email)},
+     {<<"orgname">>, (R#vcard_search.orgname)},
+     {<<"orgunit">>, (R#vcard_search.orgunit)}].
index 397008a798f1471d340935a73417f2e202fd6e8c..23f05f17d9feca4a9fbc2f19306e6600e5db573a 100644 (file)
@@ -12,7 +12,7 @@
 
 %% API
 -export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
-        import/2]).
+        search_fields/1, search_reported/1, import/2, stop/1]).
 
 -include("xmpp.hrl").
 -include("mod_vcard.hrl").
@@ -23,6 +23,9 @@
 init(_Host, _Opts) ->
     ok.
 
+stop(_Host) ->
+    ok.
+
 get_vcard(LUser, LServer) ->
     case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
         {ok, R} ->
@@ -89,6 +92,12 @@ set_vcard(LUser, LServer, VCARD,
 search(_LServer, _Data, _AllowReturnAll, _MaxMatch) ->
     [].
 
+search_fields(_LServer) ->
+    [].
+
+search_reported(_LServer) ->
+    [].
+
 remove_user(LUser, LServer) ->
     {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
 
index f448d07762314b9b3ff1b8e7798f90dc2225d3c1..f8ac6be97ac6fd40c09c3afce55147dbb98fbcbb 100644 (file)
@@ -13,8 +13,8 @@
 -behaviour(mod_vcard).
 
 %% API
--export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
-        import/1, import/2, export/1]).
+-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+        search_fields/1, search_reported/1, import/1, import/2, export/1]).
 
 -include("xmpp.hrl").
 -include("mod_vcard.hrl").
@@ -27,6 +27,9 @@
 init(_Host, _Opts) ->
     ok.
 
+stop(_Host) ->
+    ok.
+
 get_vcard(LUser, LServer) ->
     case catch sql_queries:get_vcard(LServer, LUser) of
        {selected, [{SVCARD}]} ->
@@ -93,12 +96,40 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
                 <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
                 <<"locality">>, <<"email">>, <<"orgname">>,
                 <<"orgunit">>], Rs} when is_list(Rs) ->
-                  Rs;
+                  [row_to_item(LServer, R) || R <- Rs];
               Error ->
                   ?ERROR_MSG("~p", [Error]), []
           end
     end.
 
+search_fields(_LServer) ->
+    [{<<"User">>, <<"user">>},
+     {<<"Full Name">>, <<"fn">>},
+     {<<"Name">>, <<"first">>},
+     {<<"Middle Name">>, <<"middle">>},
+     {<<"Family Name">>, <<"last">>},
+     {<<"Nickname">>, <<"nick">>},
+     {<<"Birthday">>, <<"bday">>},
+     {<<"Country">>, <<"ctry">>},
+     {<<"City">>, <<"locality">>},
+     {<<"Email">>, <<"email">>},
+     {<<"Organization Name">>, <<"orgname">>},
+     {<<"Organization Unit">>, <<"orgunit">>}].
+
+search_reported(_LServer) ->
+    [{<<"Jabber ID">>, <<"jid">>},
+     {<<"Full Name">>, <<"fn">>},
+     {<<"Name">>, <<"first">>},
+     {<<"Middle Name">>, <<"middle">>},
+     {<<"Family Name">>, <<"last">>},
+     {<<"Nickname">>, <<"nick">>},
+     {<<"Birthday">>, <<"bday">>},
+     {<<"Country">>, <<"ctry">>},
+     {<<"City">>, <<"locality">>},
+     {<<"Email">>, <<"email">>},
+     {<<"Organization Name">>, <<"orgname">>},
+     {<<"Organization Unit">>, <<"orgunit">>}].
+
 remove_user(LUser, LServer) ->
     ejabberd_sql:sql_transaction(
       LServer,
@@ -240,3 +271,18 @@ make_val(Match, Field, Val) ->
       <<"">> -> Condition;
       _ -> [Match, <<" and ">>, Condition]
     end.
+
+row_to_item(LServer, [Username, FN, Family, Given, Middle, Nickname, BDay,
+                     CTRY, Locality, EMail, OrgName, OrgUnit]) ->
+    [{<<"jid">>, <<Username/binary, $@, LServer/binary>>},
+     {<<"fn">>, FN},
+     {<<"last">>, Family},
+     {<<"first">>, Given},
+     {<<"middle">>, Middle},
+     {<<"nick">>, Nickname},
+     {<<"bday">>, BDay},
+     {<<"ctry">>, CTRY},
+     {<<"locality">>, Locality},
+     {<<"email">>, EMail},
+     {<<"orgname">>, OrgName},
+     {<<"orgunit">>, OrgUnit}].