Current section
Files
Jump to
Current section
Files
src/controllers/controller_page.erl
%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2009-2022 Marc Worrell
%% @doc Basic page
%% Copyright 2009-2022 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(controller_page).
-author("Marc Worrell <marc@worrell.nl>").
-export([
service_available/1,
content_types_provided/1,
resource_exists/1,
previously_existed/1,
moved_temporarily/1,
is_authorized/1,
process/4
]).
service_available(Context) ->
Context1 = case z_convert:to_bool(z_context:get(nocache, Context)) of
true -> z_context:set_nocache_headers(Context);
false -> Context
end,
{true, Context1}.
content_types_provided(Context) ->
CTs = case z_context:get(content_type, Context) of
undefined ->
[ {<<"text">>, <<"html">>, []} ];
Mime when is_list(Mime) ->
[ z_convert:to_binary(Mime) ];
Mime ->
[ Mime ]
end,
{CTs, Context}.
%% @doc Check if the id in the request (or dispatch conf) exists.
resource_exists(Context) ->
ContextQs = z_context:ensure_qs(Context),
try
Id = z_controller_helper:get_id(ContextQs),
maybe_redirect(Id, ContextQs)
catch
_:_ ->
{false, ContextQs}
end.
%% @doc Check if the resource used to exist
previously_existed(Context) ->
Id = z_controller_helper:get_id(Context),
{m_rsc_gone:is_gone(Id, Context), Context}.
moved_temporarily(Context) ->
Id = z_controller_helper:get_id(Context),
redirect(m_rsc_gone:get_new_location(Id, Context), Context).
redirect(undefined, Context) ->
{false, Context};
redirect(Location, Context) ->
{{true, Location}, Context}.
%% @doc Check if the current user is allowed to view the resource.
is_authorized(Context) ->
z_context:logger_md(Context),
case z_context:get(anonymous, Context) of
true ->
{true, Context};
_ ->
Id = z_controller_helper:get_id(Context),
z_controller_helper:is_authorized(Id, Context)
end.
%% @doc Show the page. Add a noindex header when requested by the editor.
process(_Method, _AcceptedCT, _ProvidedCT, Context) ->
Id = z_controller_helper:get_id(Context),
CatId = m_rsc:p_no_acl(Id, category_id, Context),
IsSeoNoIndex = z_convert:to_bool(m_rsc:p_no_acl(Id, seo_noindex, Context))
orelse z_convert:to_bool(m_rsc:p_no_acl(CatId, is_seo_noindex_cat, Context)),
Context0 = z_context:set_noindex_header(IsSeoNoIndex, Context),
Context1 = z_context:set_resource_headers(Id, Context0),
%% EXPERIMENTAL:
%%
%% When the 'cache_anonymous_max_age' flag is set then we enable simple page caching.
%% This does not take into account any query args and vary headers.
%% @todo Add the 'vary' headers to the cache key
RenderArgs = [ {id, Id} | z_context:get_all(Context1) ],
RenderArgs1 = z_notifier:foldl(template_vars, RenderArgs, Context),
RenderFunc = fun() ->
Template = z_context:get(template, Context1, {cat, <<"page.tpl">>}),
z_template:render(Template, RenderArgs1, Context1)
end,
MaxAge = z_context:get(cache_anonymous_max_age, Context1),
Html = case not z_auth:is_auth(Context1) of
true when is_integer(MaxAge), MaxAge > 0 ->
QueryArgs = z_context:get_q_all(Context1),
z_depcache:memo(RenderFunc, {page_template_anonymous, RenderArgs, QueryArgs}, MaxAge, [Id], Context1);
true when is_integer(MaxAge), MaxAge == 0 ->
QueryArgs = z_context:get_q_all(Context1),
z_depcache:memo(RenderFunc, {page_template_anonymous, RenderArgs, QueryArgs}, 0, [], Context1);
_ ->
RenderFunc()
end,
%% End experimental.
z_context:output(Html, Context1).
maybe_redirect(Id, Context) ->
maybe_redirect_website(
m_rsc:p_no_acl(Id, website, Context),
m_rsc:p_no_acl(Id, is_website_redirect, Context),
Id, Context).
maybe_redirect_website(undefined, _IsRedirect, Id, Context) ->
maybe_redirect_canonical(Id, Context);
maybe_redirect_website(<<>>, _IsRedirect, Id, Context) ->
maybe_redirect_canonical(Id, Context);
maybe_redirect_website(Website, true, Id, Context) ->
AbsUrl = z_context:abs_url(Website, Context),
CurrAbsUrl = z_context:abs_url(current_path(Context), Context),
case AbsUrl of
CurrAbsUrl -> maybe_redirect_canonical(Id, Context);
_ -> do_temporary_redirect(AbsUrl, Context)
end;
maybe_redirect_website(_Website, _False, Id, Context) ->
maybe_redirect_canonical(Id, Context).
maybe_redirect_canonical(Id, Context) ->
maybe_redirect_page_path(m_rsc:p_no_acl(Id, page_path, Context), Id, Context).
maybe_redirect_page_path(undefined, Id, Context) ->
maybe_exists(Id, Context);
maybe_redirect_page_path(<<>>, Id, Context) ->
maybe_exists(Id, Context);
maybe_redirect_page_path(PagePath, Id, Context) ->
case is_canonical(Id, Context) of
false ->
maybe_exists(Id, Context);
true ->
%% Check if we need to be at a different URL. If the page_path
%% of a resource is set, we need to redirect there if the
%% current request's path is not equal to the resource's path.
case current_path(Context) of
PagePath ->
maybe_exists(Id, Context);
_ ->
AbsUrl = m_rsc:p(Id, page_url_abs, Context),
AbsUrlQs = append_qs(AbsUrl, cowmachine_req:req_qs(Context)),
do_temporary_redirect(AbsUrlQs, Context)
end
end.
do_temporary_redirect(Location, Context) ->
ContextRedirect = z_context:set_resp_header(<<"location">>, Location, Context),
{{halt, 302}, ContextRedirect}.
current_path(Context) ->
case z_context:get_q(<<"zotonic_dispatch_path">>, Context, []) of
[] -> <<"/">>;
DP -> z_convert:to_binary([[ $/, P ] || P <- DP ])
end.
is_canonical(Id, Context) ->
case m_rsc:p_no_acl(Id, is_page_path_multiple, Context) of
true -> false;
_False -> z_context:get(is_canonical, Context, true)
end.
append_qs(AbsUrl, []) ->
AbsUrl;
append_qs(AbsUrl, Qs) ->
iolist_to_binary([AbsUrl, $?, cow_qs:urlencode(Qs)]).
maybe_exists(Id, Context) ->
case {m_rsc:exists(Id, Context), z_context:get(cat, Context)} of
{Exists, undefined} ->
{Exists, Context};
{true, Cat} ->
{m_rsc:is_a(Id, Cat, Context), Context};
{false, _} ->
{false, Context}
end.