/modules/mod_admin_category/mod_admin_category.erl

http://github.com/zotonic/zotonic · Erlang · 197 lines · 152 code · 25 blank · 20 comment · 1 complexity · 6f859feda3b1b06cbb7fde4a3dcba046 MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2009-2015 Marc Worrell
  3. %% @doc Module for editing and managing categories.
  4. %% Copyright 2009-2015 Marc Worrell
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. -module(mod_admin_category).
  18. -author("Marc Worrell <marc@worrell.nl>").
  19. -mod_title("Admin category support").
  20. -mod_description("Support editing and changing the category hierarchy.").
  21. -mod_prio(600).
  22. -mod_depends([admin, menu]).
  23. -mod_provides([]).
  24. -export([
  25. event/2,
  26. observe_category_hierarchy_save/2,
  27. observe_admin_menu/3,
  28. observe_rsc_delete/2
  29. ]).
  30. -include_lib("zotonic.hrl").
  31. -include_lib("modules/mod_admin/include/admin_menu.hrl").
  32. event(#submit{message={delete_move, Args}}, Context) ->
  33. ToCatId = z_convert:to_integer(z_context:get_q_validated("category_id", Context)),
  34. {id, Id} = proplists:lookup(id, Args),
  35. Ids = [ Id | m_hierarchy:children('$category', Id, Context) ],
  36. case deletable(Ids, Context) andalso z_acl:rsc_editable(ToCatId, Context) of
  37. true ->
  38. Context1 = z_context:prune_for_async(Context),
  39. spawn(fun() ->
  40. cat_move_and_delete(Ids, ToCatId, Context1)
  41. end),
  42. z_render:wire({dialog_close, []}, Context);
  43. false ->
  44. z_render:growl(?__("Sorry, you are not allowed to delete this.", Context), Context)
  45. end;
  46. event(#postback{message={delete_all, Args}}, Context) ->
  47. {id, Id} = proplists:lookup(id, Args),
  48. IfEmpty = proplists:get_value(if_empty, Args, false),
  49. Ids = [ Id | m_hierarchy:children('$category', Id, Context) ],
  50. case not IfEmpty orelse not m_category:is_used(Id, Context) of
  51. true ->
  52. case deletable(Ids, Context) of
  53. true ->
  54. Context1 = z_context:prune_for_async(Context),
  55. spawn(fun() ->
  56. cat_delete(Ids, Context1)
  57. end),
  58. z_render:wire({dialog_close, []}, Context);
  59. false ->
  60. z_render:growl(?__("Sorry, you are not allowed to delete this.", Context), Context)
  61. end;
  62. false ->
  63. z_render:wire({alert, [{message, ?__("Delete is canceled, there are pages in the category.", Context)}]}, Context)
  64. end.
  65. cat_delete(Ids, Context) ->
  66. z_session_page:add_script(z_render:wire({mask, [{message, ?__("Deleting...", Context)}]}, Context)),
  67. RscIds = in_categories(Ids, Context),
  68. case delete_all(RscIds, 0, length(RscIds), Context) of
  69. ok ->
  70. lists:foreach(fun(Id) ->
  71. m_rsc:delete(Id, Context)
  72. end,
  73. Ids),
  74. z_session_page:add_script(z_render:wire({unmask, []}, Context));
  75. {error, _} ->
  76. Context1 = z_render:wire([
  77. {unmask, []},
  78. {alert, [{message, ?__("Not all resources could be deleted.", Context)}]}
  79. ],
  80. Context),
  81. z_session_page:add_script(Context1)
  82. end.
  83. cat_move_and_delete(Ids, ToGroupId, Context) ->
  84. z_session_page:add_script(z_render:wire({mask, [{message, ?__("Deleting...", Context)}]}, Context)),
  85. RscIds = in_categories(Ids, Context),
  86. ok = move_all(RscIds, ToGroupId, 0, length(RscIds), Context),
  87. lists:foreach(fun(Id) ->
  88. m_rsc:delete(Id, Context)
  89. end,
  90. Ids),
  91. z_session_page:add_script(z_render:wire({unmask, []}, Context)),
  92. ok.
  93. in_categories(Ids, Context) when is_list(Ids) ->
  94. RscIds = z_db:q("select id from rsc where category_id in (SELECT(unnest($1::int[])))", [Ids], Context, 60000),
  95. [ RscId || {RscId} <- RscIds ].
  96. delete_all([], _N, _Total, _Context) ->
  97. ok;
  98. delete_all([{Id}|Ids], N, Total, Context) ->
  99. case catch m_rsc:delete(Id, Context) of
  100. ok ->
  101. maybe_progress(N, N+1, Total, Context),
  102. delete_all(Ids, N+1, Total, Context);
  103. Error ->
  104. {error, Error}
  105. end.
  106. move_all([], _ToCatId, _N, _Total, _Context) ->
  107. ok;
  108. move_all([{Id}|Ids], ToCatId, N, Total, Context) ->
  109. m_rsc_update:update(Id, [{category_id, ToCatId}], z_acl:sudo(Context)),
  110. maybe_progress(N, N+1, Total, Context),
  111. move_all(Ids, ToCatId, N+1, Total, Context).
  112. maybe_progress(_N1, _N2, 0, _Context) ->
  113. ok;
  114. maybe_progress(N1, N2, Total, Context) ->
  115. z_pivot_rsc:pivot_delay(Context),
  116. PerStep = Total / 100,
  117. S1 = round(N1 / PerStep),
  118. S2 = round(N2 / PerStep),
  119. case S1 of
  120. S2 -> ok;
  121. _ -> z_session_page:add_script(z_render:wire({mask_progress, [{percent,S2}]}, Context))
  122. end.
  123. deletable(Ids, Context) ->
  124. lists:all(fun(Id) -> z_acl:rsc_deletable(Id, Context) end, Ids).
  125. observe_category_hierarchy_save(#category_hierarchy_save{tree=New}, Context) ->
  126. case z_acl:is_allowed(insert, category, Context) of
  127. true ->
  128. case m_hierarchy:menu('$category', Context) of
  129. New ->
  130. ok;
  131. Old ->
  132. % Check if any ids are added or deleted
  133. NewIds = lists:sort(ids(New, [])),
  134. OldIds = lists:sort(ids(Old, [])),
  135. Deleted = OldIds -- NewIds,
  136. % Inserted = NewIds -- OldIds,
  137. % Delete all ids not in the new category tree
  138. lists:map(fun(Id) ->
  139. ok = m_category:delete(Id, undefined, Context)
  140. end,
  141. Deleted),
  142. _ = m_hierarchy:save_nocheck('$category', New, Context),
  143. m_category:flush(Context),
  144. ok
  145. end;
  146. false ->
  147. undefined
  148. end.
  149. %% @doc Do not allow a category to be removed iff there are resources within that category
  150. observe_rsc_delete(#rsc_delete{id=Id, is_a=IsA}, Context) ->
  151. case lists:member(category, IsA) of
  152. true ->
  153. case m_category:is_used(Id, Context) of
  154. true -> throw({error, is_used});
  155. false -> ok
  156. end;
  157. false ->
  158. ok
  159. end.
  160. observe_admin_menu(admin_menu, Acc, Context) ->
  161. [
  162. #menu_item{id=admin_categories,
  163. parent=admin_structure,
  164. label=?__("Categories", Context),
  165. url={admin_category_sorter},
  166. visiblecheck={acl, insert, category}}
  167. |Acc].
  168. ids([], Acc) ->
  169. Acc;
  170. ids([{Id,Sub}|Rest], Acc) ->
  171. Acc1 = ids(Sub, Acc),
  172. ids(Rest, [Id|Acc1]).