/modules/mod_comment/mod_comment.erl

http://github.com/zotonic/zotonic · Erlang · 215 lines · 166 code · 22 blank · 27 comment · 2 complexity · 01de173975342003a5db59a8e054c40c MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2010 Marc Worrell
  3. %% Date: 2010-01-15
  4. %% @doc Simple comment module. Adds comments to any rsc.
  5. %% Copyright 2010 Marc Worrell
  6. %%
  7. %% Licensed under the Apache License, Version 2.0 (the "License");
  8. %% you may not use this file except in compliance with the License.
  9. %% You may obtain a copy of the License at
  10. %%
  11. %% http://www.apache.org/licenses/LICENSE-2.0
  12. %%
  13. %% Unless required by applicable law or agreed to in writing, software
  14. %% distributed under the License is distributed on an "AS IS" BASIS,
  15. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. %% See the License for the specific language governing permissions and
  17. %% limitations under the License.
  18. -module(mod_comment).
  19. -author("Marc Worrell <marc@worrell.nl>").
  20. -mod_title("Comments").
  21. -mod_description("Comments for pages. Implements a simple comment system with comments stored locally.").
  22. -mod_depends([admin, base]).
  23. -mod_provides([comment]).
  24. %% gen_server exports
  25. -export([init/1]).
  26. %% interface functions
  27. -export([
  28. event/2,
  29. observe_search_query/2,
  30. observe_rsc_merge/2,
  31. observe_admin_menu/3
  32. ]).
  33. -include_lib("zotonic.hrl").
  34. -include_lib("modules/mod_admin/include/admin_menu.hrl").
  35. %% @doc Handle the submit event of a new comment
  36. event(#submit{message={newcomment, Args}, form=FormId}, Context) ->
  37. {id, Id} = proplists:lookup(id, Args),
  38. case z_auth:is_auth(Context) of
  39. false ->
  40. Name = z_context:get_q_validated("name", Context),
  41. Email = z_context:get_q_validated("mail", Context);
  42. true ->
  43. Name = "",
  44. Email = ""
  45. end,
  46. Message = z_context:get_q_validated("message", Context),
  47. Is_visible = case m_config:get_value(comments, moderate, Context) of <<"1">> -> false; _Else -> true end,
  48. case m_comment:insert(Id, Name, Email, Message, Is_visible, Context) of
  49. {ok, CommentId} ->
  50. CommentsListElt = proplists:get_value(comments_list, Args, "comments-list"),
  51. CommentTemplate = proplists:get_value(comment_template, Args, "_comments_comment.tpl"),
  52. Comment = m_comment:get(CommentId, Context),
  53. Props = [
  54. {id, Id},
  55. {comment, Comment},
  56. {creator, m_rsc:p(Id, creator_id, Context)},
  57. {hidden, true}
  58. ],
  59. Html = z_template:render(CommentTemplate, Props, Context),
  60. Context1 = z_render:insert_bottom(CommentsListElt, Html, Context),
  61. Context2 = case Is_visible of
  62. true ->
  63. z_render:wire([
  64. {set_value, [{selector, "#"++FormId++" textarea[name=\"message\"]"}, {value, ""}]},
  65. {set_value, [{selector, "#"++FormId++" input[name=\"message\"]"}, {value, ""}]},
  66. {fade_in, [{target, "comment-"++integer_to_list(CommentId)}]}
  67. ], Context1);
  68. false ->
  69. Context1
  70. end,
  71. case z_convert:to_bool(proplists:get_value(do_redirect, Args, true)) of
  72. true -> z_render:wire({redirect, [{location, "#comment-"++integer_to_list(CommentId)}]}, Context2);
  73. false -> Context2
  74. end;
  75. {error, _} ->
  76. Context
  77. end.
  78. %% @doc Return the list of recent comments. Returned values are the complete records.
  79. observe_search_query(#search_query{search={recent_comments, []}, offsetlimit=OffsetLimit}, Context) ->
  80. m_comment:search({recent_comments, []}, OffsetLimit, Context);
  81. observe_search_query(_, _Context) ->
  82. undefined.
  83. %% @doc Move all comments from one resource to another
  84. observe_rsc_merge(#rsc_merge{looser_id=LooserId, winner_id=WinnerId}, Context) ->
  85. m_comment:merge(WinnerId, LooserId, Context).
  86. %% @doc Check the installation of the comment table. A bit more complicated because 0.1 and 0.2 had a table
  87. %% in the default installer, this module installs a different table.
  88. init(Context) ->
  89. ok = z_db:transaction(fun install1/1, Context),
  90. z_depcache:flush(Context),
  91. ok.
  92. install1(Context) ->
  93. ok = remove_old_comment_rsc_fields(Context),
  94. ok = remove_old_rating_table(Context),
  95. ok = install_comment_table(z_db:table_exists(comment, Context), Context),
  96. ok.
  97. remove_old_rating_table(Context) ->
  98. case z_db:table_exists(rating, Context) of
  99. false ->
  100. ok;
  101. true ->
  102. case z_db:column_names(rating, Context) of
  103. [comment_id,created,id,ip_address,rsc_id,visitor_id] ->
  104. z_db:q("drop table rating", Context),
  105. ok;
  106. _ ->
  107. ok
  108. end
  109. end.
  110. install_comment_table(true, Context) ->
  111. % Check for old table
  112. case z_db:column_names(comment, Context) of
  113. [created,creator_id,id,ip_address,notify_id,props,rating,rsc_id] ->
  114. z_db:q("drop table comment", Context),
  115. install_comment_table(false, Context);
  116. [created,id,email,gravatar_code,ip_address,is_visible,keep_informed,
  117. name,props,rsc_id,user_agent,user_id,visitor_id] ->
  118. z_db:q("alter table comment drop column visitor_id cascade, "
  119. "add column persistent_id character varying (32), "
  120. "add constraint fk_comment_persistent_id foreign key (persistent_id) "
  121. " references persistent(id) on delete set null on update cascade", Context),
  122. z_db:q("create index fki_comment_persistent_id on comment(persistent_id)", Context),
  123. ok;
  124. _ ->
  125. % todo: add list of current fields here
  126. ok
  127. end;
  128. install_comment_table(false, Context) ->
  129. z_db:q("
  130. create table comment (
  131. id serial not null,
  132. is_visible boolean not null default true,
  133. rsc_id int not null,
  134. user_id int,
  135. persistent_id character varying(32),
  136. gravatar_code character varying(40) not null default ''::character varying,
  137. email character varying(80) not null default ''::character varying,
  138. name character varying(80) not null default ''::character varying,
  139. user_agent character varying(250) not null default ''::character varying,
  140. ip_address character varying(40) not null default ''::character varying,
  141. keep_informed boolean not null default false,
  142. props bytea,
  143. created timestamp with time zone not null default now(),
  144. constraint comment_pkey primary key (id),
  145. constraint fk_comment_rsc_id foreign key (rsc_id)
  146. references rsc(id)
  147. on delete cascade on update cascade,
  148. constraint fk_comment_user_id foreign key (user_id)
  149. references rsc(id)
  150. on delete set null on update cascade,
  151. constraint fk_comment_persistent_id foreign key (persistent_id)
  152. references persistent(id)
  153. on delete set null on update cascade
  154. )
  155. ", Context),
  156. Indices = [
  157. {"fki_comment_rsc_id", "rsc_id"},
  158. {"fki_comment_user_id", "user_id"},
  159. {"fki_comment_persistent_id", "persistent_id"},
  160. {"fki_comment_ip_address", "ip_address"},
  161. {"comment_rsc_created_key", "rsc_id, created"},
  162. {"comment_created_key", "created"}
  163. ],
  164. [ z_db:q("create index "++Name++" on comment ("++Cols++")", Context) || {Name, Cols} <- Indices ],
  165. ok.
  166. %% @doc In the 0.1.0 and 0.2.0 releases we had some pivot information in the rsc table. Remove this.
  167. remove_old_comment_rsc_fields(Context) ->
  168. Cols = z_db:column_names(rsc, Context),
  169. R = [],
  170. R1 = case lists:member(comment_by, Cols) of true -> ["drop column comment_by"|R]; false -> R end,
  171. R2 = case lists:member(comments, Cols) of true -> ["drop column comments"|R1]; false -> R1 end,
  172. R3 = case lists:member(rating, Cols) of true -> ["drop column rating"|R2]; false -> R2 end,
  173. R4 = case lists:member(rating_count, Cols) of true -> ["drop column rating_count"|R3]; false -> R3 end,
  174. case R4 of
  175. [] ->
  176. ok;
  177. L ->
  178. z_db:q("alter table rsc " ++ string:join(L, ", "), Context),
  179. ok
  180. end.
  181. observe_admin_menu(admin_menu, Acc, Context) ->
  182. [
  183. #menu_item{id=admin_comments,
  184. parent=admin_content,
  185. label=?__("Comments", Context),
  186. url={admin_comments},
  187. visiblecheck={acl, use, ?MODULE}},
  188. #menu_item{id=admin_comments_settings,
  189. parent=admin_modules,
  190. label=?__("Comment settings", Context),
  191. url={admin_comments_settings},
  192. visiblecheck={acl, use, ?MODULE}}
  193. |Acc].