src/models/m_site.erl

%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2009-2021 Marc Worrell
%% @doc Model for the zotonic site configuration

%% Copyright 2009-2021 Marc Worrell
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

-module(m_site).
-author("Marc Worrell <marc@worrell.nl").

-behaviour(zotonic_model).

%% interface functions
-export([
    m_get/3,
    environment/1,
    load_config/1,
    load_config/2,
    all/1,
    get/2,
    get/3,
    put/4
]).

-include_lib("zotonic.hrl").

%% @doc Fetch the value for the key from a model source
-spec m_get( list(), zotonic_model:opt_msg(), z:context() ) -> zotonic_model:return().
m_get([ <<"site">> | Rest ], _Msg, Context) ->
    {ok, {z_context:site(Context), Rest}};
m_get([ <<"environment">> | Rest ], _Msg, Context) ->
    {ok, {environment(Context), Rest}};
m_get([ <<"hostname">> | Rest ], _Msg, Context) ->
    {ok, {z_context:hostname(Context), Rest}};
m_get([ <<"hostname_port">> | Rest ], _Msg, Context) ->
    {ok, {z_context:hostname_port(Context), Rest}};
m_get([ <<"hostname_ssl_port">> | Rest ], _Msg, Context) ->
    {ok, {z_context:hostname_ssl_port(Context), Rest}};
m_get([ <<"hostalias">> | Rest ], _Msg, Context) ->
    {ok, {get(hostalias, Context), Rest}};
m_get([ <<"protocol">> | Rest ], _Msg, Context) ->
    {ok, {z_context:site_protocol(Context), Rest}};
m_get([ <<"is_ssl">> | Rest ], _Msg, Context) ->
    {ok, {z_context:is_ssl_site(Context), Rest}};
m_get([ <<"title">> | Rest ], _Msg, Context) ->
    Title = m_config:get_value(site, title, Context),
    {ok, {Title, Rest}};
m_get([ <<"subtitle">> | Rest ], _Msg, Context) ->
    SubTitle = m_config:get_value(site, subtitle, Context),
    {ok, {SubTitle, Rest}};
m_get([ <<"email_from">> | Rest ], _Msg, Context) ->
    EmailFrom = z_email:get_email_from(Context),
    {ok, {EmailFrom, Rest}};
m_get([ <<"pagelen">> | Rest ], _Msg, Context) ->
    PageLen = case m_config:get_value(site, pagelen, Context) of
        undefined -> ?SEARCH_PAGELEN;
        <<>> -> ?SEARCH_PAGELEN;
        V -> z_convert:to_integer(V)
    end,
    {ok, {PageLen, Rest}};
m_get([ Key | Rest ], _Msg, Context) when is_binary(Key) ->
    try
        KeyAtom = erlang:binary_to_existing_atom(Key, utf8),
        case z_acl:is_admin(Context) of
            true -> {ok, {get(KeyAtom, Context), Rest}};
            false -> {ok, {undefined, []}}
        end
    catch
        error:badarg ->
            {ok, {undefined, []}}
    end;
m_get([], _Msg, Context) ->
    case z_acl:is_admin(Context) of
        true -> {ok, {all(Context), []}};
        false -> {ok, {[], []}}
    end;
m_get(_Vs, _Msg, _Context) ->
    {error, unknown_path}.


%% @doc Return the current DTAP environment
-spec environment( z:context() ) -> z:environment().
environment(Context) ->
    environment_atom( m_site:get(environment, Context) ).

environment_atom(development) -> development;
environment_atom(test) -> test;
environment_atom(acceptance) -> acceptance;
environment_atom(production) -> production;
environment_atom(education) -> education;
environment_atom(backup) -> backup;
environment_atom(undefined) -> z_config:get(environment);
environment_atom(<<>>) -> z_config:get(environment);
environment_atom("") -> z_config:get(environment);
environment_atom(L) when is_list(L) ->
    environment_atom( list_to_existing_atom(L) );
environment_atom(B) when is_binary(B) ->
    environment_atom( binary_to_existing_atom(B, utf8) ).


-spec load_config(atom()|z:context()) -> ok | {error, term()}.
load_config(#context{} = Context) ->
    load_config(z_context:site(Context));
load_config(Site) when is_atom(Site) ->
    case z_sites_manager:get_site_config(Site) of
        {ok, Config} ->
            load_config(Site, Config);
        {error, _} = Error -> Error
    end.

-spec load_config(atom()|z:context(), list()) -> ok.
load_config(#context{} = Context, Config) ->
    load_config(z_context:site(Context), Config);
load_config(Site, Config) when is_atom(Site) ->
    application:load(Site),
    lists:foreach(
        fun({K,V}) ->
            application:set_env(Site, K, V)
        end,
        Config).

%% @doc Return the complete site configuration
-spec all(atom()|z:context()) -> list().
all(#context{} = Context) ->
    all(z_context:site(Context));
all(Site) when is_atom(Site) ->
    application:get_all_env(Site).

%% @doc Fetch a key from the site configuration
-spec get(atom(), z:context() | atom()) -> term() | undefined.
get(Key, Site) when is_atom(Site) ->
    get(Key, z_context:new(Site));
get(Key, Context) when is_atom(Key) ->
    try
        Site = z_context:site(Context),
        case application:get_env(Site, Key) of
            {ok, undefined} ->
                undefined;
            {ok, none} when Key =:= hostname ->
                case z_context:is_request(Context) of
                    true -> cowmachine_req:host(Context);
                    false -> undefined
                end;
            {ok, Value} ->
                Value;
            undefined ->
                undefined
        end
    catch
        error:badarg ->
            % Special case on site setup when the depcache is not yet running
            {ok, Cfg} = z_sites_manager:get_site_config(z_context:site(Context)),
            proplists:get_value(Key, Cfg)
    end.

%% @doc Fetch a nested key from the site configuration
-spec get(atom(), atom(), z:context()) -> term() | undefined.
get(site, Key, Context) when is_atom(Key) ->
    get(Key, Context);
get(Module, Key, Context) when is_atom(Key) ->
    case get(Module, Context) of
        undefined -> undefined;
        L when is_list(L) -> proplists:get_value(Key, L)
    end.

%% @doc Put the value in the site config (temporary, till restart)
-spec put(atom(), atom(), term(), z:context()) -> ok.
put(site, Key, Value, Context) ->
    application:set_env(z_context:site(Context), Key, Value);
put(Module, Key, Value, Context) ->
    L1 = case get(Module, Context) of
        undefined ->
            [ {Key, Value} ];
        L when is_list(L) ->
            [ {Key, Value} | proplists:delete(Key, L) ]
    end,
    application:set_env(z_context:site(Context), Module, L1).