%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2009-2016 Marc Worrell
%% @doc Template handling, compiles and renders django compatible templates using the template_compiler
%% Copyright 2009-2016 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(z_template).
-author("Marc Worrell <marc@worrell.nl>").
-export([start_link/1]).
%% External exports
-export([
reset/1,
module_reindexed/2,
render/2,
render/3,
render_to_iolist/3,
render_block/3,
render_block/4,
render_block_to_iolist/4,
is_template_module/1,
blocks/3
]).
-include_lib("template_compiler/include/template_compiler.hrl").
-include("../../include/zotonic.hrl").
start_link(Site) ->
z_notifier:observe(module_reindexed, {?MODULE, module_reindexed}, z_context:new(Site)),
ignore.
%% @doc Force a reset of all templates, used after a module has been activated or deactivated.
-spec reset(atom()|#context{}) -> ok.
reset(Site) when is_atom(Site) ->
z_file_mtime:flush_site(Site),
template_compiler:flush_context_name(Site);
reset(Context) ->
reset(z_context:site(Context)).
%% @doc Observer, triggered when there are new module files indexed
-spec module_reindexed(module_reindexed, #context{}) -> ok.
module_reindexed(module_reindexed, Context) ->
reset(z_context:site(Context)).
-spec render(#render{}, #context{}) -> template_compiler:render_result().
render(#render{template=Template, vars=Vars}, Context) ->
render_block(undefined, Template, Vars, Context).
-spec render(template_compiler:template()|#module_index{}, list()|map(), #context{}) -> template_compiler:render_result().
render(Template, Vars, Context) ->
render_block(undefined, Template, Vars, Context).
-spec render_block(atom(), #render{}, #context{}) -> template_compiler:render_result().
render_block(Block, #render{template=Template, vars=Vars}, Context) ->
render_block(Block, Template, Vars, Context).
-spec render_block(atom(), template_compiler:template()|#module_index{}, list()|map(), #context{}) -> template_compiler:render_result().
render_block(OptBlock, Template, Vars, Context) when is_list(Vars) ->
render_block(OptBlock, Template, props_to_map(Vars, #{}), Context);
render_block(OptBlock, #module_index{filepath=Filename, key=Key}, Vars, Context) ->
Template = #template_file{
filename=Filename,
template=Key#module_index_key.name
},
render_block(OptBlock, Template, Vars, Context);
render_block(OptBlock, Template, Vars, Context) when is_map(Vars) ->
OldCaching = z_depcache:in_process(true),
Opts = [
{runtime, z_template_compiler_runtime},
{context_name, z_context:site(Context)},
{context_vars, [
<<"sudo">>,
<<"anondo">>,
<<"z_language">>,
<<"extra_args">>
]}
],
Result = case OptBlock of
undefined ->
template_compiler:render(Template, Vars, Opts, Context);
Block when is_atom(Block) ->
template_compiler:render_block(Block, Template, Vars, Opts, Context)
end,
z_depcache:in_process(OldCaching),
case Result of
{ok, Output} ->
Output;
{error, Reason} when is_list(Reason); is_binary(Reason) ->
try
Reason1 = iolist_to_binary(Reason),
?LOG_ERROR(#{
text => <<"Error rendering template">>,
in => zotonic_core,
template => Template,
result => error,
reason => Reason1
})
catch
_:_ ->
?LOG_ERROR(#{
text => <<"Error rendering template">>,
in => zotonic_core,
template => Template,
result => error,
reason => Reason
})
end,
<<>>;
{error, Reason} when is_map(Reason) ->
?LOG_ERROR(Reason),
<<>>;
{error, Reason} ->
?LOG_ERROR(#{
text => <<"Error rendering template">>,
in => zotonic_core,
template => Template,
result => error,
reason => Reason
}),
<<>>
end.
props_to_map(L, Map) ->
lists:foldr(
fun
({K, V}, Acc) ->
Acc#{ K => V };
(K, Acc) ->
Acc#{ K => true }
end,
Map,
L).
%% @todo Remove these functions, templates should not have any javascript etc. (call z_render for old style templates)
%% @doc Render a template to an iolist(). This removes all scomp state etc from the rendered html and appends the
%% information in the scomp states to the context for later rendering.
-spec render_to_iolist(template_compiler:template() | #module_index{},
list() | map(), z:context()) -> {iolist(), z:context()}.
render_to_iolist(File, Vars, Context) ->
Html = render(File, Vars, Context),
z_render:render_to_iolist(Html, Context).
%% @doc Render a block template to an iolist().
-spec render_block_to_iolist(atom(), template_compiler:template(), list()|map(), z:context()) ->
{iolist(), z:context()}.
render_block_to_iolist(Block, File, Vars, Context) ->
Html = render_block(Block, File, Vars, Context),
z_render:render_to_iolist(Html, Context).
%% @doc Check if the modulename looks like a module generated by the template compiler.
-spec is_template_module(binary()|string()|atom()) -> boolean().
is_template_module(Module) ->
template_compiler:is_template_module(Module).
%% @doc Return a list of all block names in a template.
-spec blocks(template_compiler:template()|#module_index{}, map(), z:context() ) -> {ok, [ atom() ]} | {error, term()}.
blocks(#module_index{filepath=Filename, key=Key}, Vars, Context) ->
Template = #template_file{
filename=Filename,
template=Key#module_index_key.name
},
blocks(Template, Vars, Context);
blocks(#template_file{ filename = Filename }, Vars, Context) when is_map(Vars) ->
Opts = [
{runtime, z_template_compiler_runtime},
{context_name, z_context:site(Context)},
{context_vars, [
<<"sudo">>,
<<"anondo">>,
<<"z_language">>,
<<"extra_args">>
]}
],
case template_compiler:lookup(Filename, Opts, Context) of
{ok, Module} ->
{ok, Module:blocks()};
{error, _} = Error ->
Error
end;
blocks(Template, Vars, Context) ->
case z_template_compiler_runtime:map_template(Template, Vars, Context) of
{ok, MappedTemplate} ->
blocks(MappedTemplate, Vars, Context);
{error, _} = Error ->
Error
end.