Skip to content

Dev053 - Template render controller #678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions modules/mod_ginger_base/support/ginger_environment.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
%% @doc Get current environment
-spec get(#context{}) -> dev|acc|prod.
get(Context) ->
case is_dev(Context) of
true -> dev;
false ->
case is_acc(Context) of
true -> acc;
false -> prod
end
case z_config:get(environment) of
production ->
case is_dev(Context) of
true -> dev;
false ->
case is_acc(Context) of
true -> acc;
false -> prod
end
end;
acceptance ->
acc;
_ ->
dev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change extra or needed for the template render controller?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This smuggled in - for compatibility between the Ginger environment and the Zotonic environment.

end.

is_dev(Context) ->
Expand Down
5 changes: 4 additions & 1 deletion modules/mod_ginger_rdf/models/m_rdf_export.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ to_rdf(Id, Context) ->
%% - media.
-spec to_rdf(m_rsc:resource(), [module()], z:context()) -> m_rdf:rdf_resource().
to_rdf(Id, Ontologies, Context) ->
Properties = m_rsc:get_visible(Id, Context),
Properties = case m_rsc:get_visible(Id, Context) of
undefined -> [];
Props -> Props
end,
Copy link
Contributor

@robvandenbogaard robvandenbogaard Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the record, from our slack conversation: This rdf change was sneaked in, unrelated to template render controller. Maybe we need to apply this in a separate pull request; maybe it has been lying around for too long because it potentially fixes bugs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It potentially fixes bugs.

Ideally it should have been a separate merge request.

Edges = m_edge:get_edges(Id, Context),
Types = types(proplists:get_value(category_id, Properties), Context),
Triples = lists:flatten(
Expand Down
37 changes: 36 additions & 1 deletion modules/mod_ginger_spa/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
mod_ginger_spa
========================
==============

Default template and dispatch rules for a single page app.

_Only routes for which a Ginger resource with the same path exists will be served by the `page.tpl` template.
Other routes should be manually added to the sites dispatch rules._


Template rendering
------------------

The `controller_ginger_spa_template` renders templates and returns the result as plain text.

Pass the template name in the path, example:

https://example.com/render-template/public/test.tpl

This renders the template `"public/test.tpl"`.

Pass arguments via the query string:

https://example.com/render-template/public/test.tpl?id=1

Or in a POST body as `application/x-www-form-urlencoded` or `multipart/form-data`.

Use the argument `catinclude=1` to render the template using a *catinclude*.
Any `id` argument is always added as `id` to the template arguments (after mapping using `m_rsc:rid/2`), in
this way the `id` is always available and usable for the optional catinclude.

Security
........

As in Zotonic 0.x template models are not access checked there is a security using the name of the template.

Templates starting with `public/...` can be rendered by anonymous users.

Templates starting with `member/...` can be rendered by authenticated users.

Administrators are allowed to render all templates.


Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
%% @copyright Driebit BV 2021
%% @doc Controller to render templates. Post arguments via the query arguments.

-module(controller_ginger_spa_template).

-export([init/1, service_available/2, charsets_provided/2, content_types_provided/2]).
-export([allowed_methods/2, resource_exists/2]).
-export([process_post/2, provide_content/2]).

-include_lib("controller_webmachine_helper.hrl").
-include_lib("include/zotonic.hrl").

init(DispatchArgs) -> {ok, DispatchArgs}.

service_available(ReqData, DispatchArgs) when is_list(DispatchArgs) ->
Context1 = z_context:new_request(ReqData, DispatchArgs, ?MODULE),
?WM_REPLY(true, Context1).

allowed_methods(ReqData, Context) ->
{[ 'POST', 'GET', 'HEAD' ], ReqData, Context}.

charsets_provided(ReqData, Context) ->
{[{"utf-8", fun(X) -> X end}], ReqData, Context}.

content_types_provided(ReqData, Context) ->
{[{"text/plain", provide_content}], ReqData, Context}.

resource_exists(ReqData, Context) ->
Context1 = ?WM_REQ(ReqData, Context),
Context2 = z_context:ensure_all(Context1),
?WM_REPLY(true, Context2).

process_post(ReqData, Context) ->
Context1 = ?WM_REQ(ReqData, Context),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this strictly necessary? Would returning Context2 in resource_exists (which is visited before process_post) not already have the ReqData WM_REQ'ed in?

Result = render(Context1),
ReqData1 = wrq:set_resp_body(Result, ReqData),
{{halt, 200}, ReqData1, Context}.

provide_content(ReqData, Context) ->
Context1 = ?WM_REQ(ReqData, Context),
Result = render(Context1),
?WM_REPLY(Result, Context1).

render(Context) ->
case template(Context) of
{ok, Template} ->
Vars = [
{is_spa_render, true},
{id, m_rsc:rid(z_context:get_q("id", Context), Context)}
],
Template1 = case is_catinclude(Context) of
true -> {cat, Template};
false -> Template
end,
{Data, _} = z_template:render_to_iolist(Template1, Vars, Context),
iolist_to_binary(Data);
{error, _} ->
lager:info("[~p] Denied render of template \"~s\"", [ z_context:site(Context), m_req:get(path, Context) ]),
Copy link
Contributor

@robvandenbogaard robvandenbogaard Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is denied the right wording? If the template is in the url, any raised error would be caused by that there is no matching dispatch rule.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be anything, but I think this is good enough.

<<>>
end.

is_catinclude(Context) ->
z_convert:to_bool(z_context:get_q("catinclude", Context)).

template(Context) ->
case z_context:get(template, Context) of
undefined ->
case path(Context) of
"api/render-template/" ++ Template ->
{ok, drop_slash(Template)};
_ ->
{error, enoent}
end;
Template ->
{ok, Template}
Copy link
Contributor

@robvandenbogaard robvandenbogaard Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't do the dispatch check when the template path is in the template context property - is that as intended, or a loophole?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, then it is hard coded in the dispatch rule, so the responsibility of the person programming that dispatch rule.

end.

drop_slash("/" ++ Path) -> drop_slash(Path);
drop_slash(Path) -> Path.

path(Context) ->
DispatchPath = z_context:get_q("zotonic_dispatch_path", Context),
lists:flatten( z_utils:combine($/, DispatchPath) ).
5 changes: 4 additions & 1 deletion modules/mod_ginger_spa/dispatch/dispatch
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[{home, [], controller_template, [ {template, "page.tpl"}, {id, home} ]},
{search, ["search"], controller_template, [ {template, "page.tpl"} ]}
{search, ["search"], controller_template, [ {template, "page.tpl"} ]},

% Render a template the path is the template, pass arguments via the query post or get
{spa_render, ["api", "render-template", "public", '*' ], controller_ginger_spa_template, []}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

She earlier comment on public/test.tpl, maybe we should make the default spa_render rule specific, for discoverability of the default test template.

].
2 changes: 2 additions & 0 deletions modules/mod_ginger_spa/templates/public/test.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% debug %}
{% print q.v %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be less dangerous (-looking) default content, without debug? I Imagine you'll easily forget it is there - wait..maybe if we change the default dispatch rule to explicitly reference test.tpl, you'd be bound to remove or fix it in an actual project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug just echos what is passed, and afaik all data is escaped.