/src/support/z_datamodel.erl

https://code.google.com/p/zotonic/ · Erlang · 263 lines · 192 code · 38 blank · 33 comment · 5 complexity · aee96d75aeb76ac612a515dd25346e55 MD5 · raw file

  1. %% @author Arjan Scherpenisse <arjan@scherpenisse.net>
  2. %% @copyright 2009 Arjan Scherpenisse
  3. %% Date: 2009-11-08
  4. %% @doc Installing parts of the zotonic datamodel. Installs
  5. %% predicates, categories and default resources.
  6. %% Copyright 2009 Arjan Scherpenisse
  7. %%
  8. %% Licensed under the Apache License, Version 2.0 (the "License");
  9. %% you may not use this file except in compliance with the License.
  10. %% You may obtain a copy of the License at
  11. %%
  12. %% http://www.apache.org/licenses/LICENSE-2.0
  13. %%
  14. %% Unless required by applicable law or agreed to in writing, software
  15. %% distributed under the License is distributed on an "AS IS" BASIS,
  16. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. %% See the License for the specific language governing permissions and
  18. %% limitations under the License.
  19. -module(z_datamodel).
  20. -author("Arjan Scherpenisse <arjan@scherpenisse.net>").
  21. -export([manage/3, reset_deleted/2]).
  22. %% The datamodel manages parts of your datamodel. This includes
  23. %% categories and predicates, but also "default data" like resources.
  24. %%
  25. %% The data model maintains a special property on this installed data,
  26. %% called 'installed_by'. This decides whether it can touch it.
  27. %%
  28. -include_lib("zotonic.hrl").
  29. %% @doc Reset the state of an imported datamodel, causing all deleted resources to be reimported
  30. reset_deleted(Module, Context) ->
  31. m_config:delete(Module, datamodel, Context).
  32. manage(Module, Datamodel, Context) when is_list(Datamodel) ->
  33. %% Backwards compatibility with old datamodel notation.
  34. manage(Module,
  35. #datamodel{categories=proplists:get_value(categories, Datamodel, []),
  36. predicates=proplists:get_value(predicates, Datamodel, []),
  37. resources=proplists:get_value(resources, Datamodel, []),
  38. media=proplists:get_value(media, Datamodel, []),
  39. edges=proplists:get_value(edges, Datamodel, [])
  40. },
  41. Context);
  42. manage(Module, Datamodel, Context) ->
  43. AdminContext = z_acl:sudo(Context),
  44. [manage_category(Module, Cat, AdminContext) || Cat <- Datamodel#datamodel.categories],
  45. [manage_predicate(Module, Pred, AdminContext) || Pred <- Datamodel#datamodel.predicates],
  46. [manage_resource(Module, R, AdminContext) || R <- Datamodel#datamodel.resources],
  47. [manage_medium(Module, Medium, AdminContext) || Medium <- Datamodel#datamodel.media],
  48. [manage_edge(Module, Edge, AdminContext) || Edge <- Datamodel#datamodel.edges],
  49. ok.
  50. manage_medium(Module, {Name, {EmbedService, EmbedCode}, Props}, Context) ->
  51. case manage_resource(Module, {Name, media, Props}, Context) of
  52. {ok} ->
  53. {ok};
  54. {ok, Id} ->
  55. MediaProps = [{mime, "text/html-video-embed"},
  56. {video_embed_service, EmbedService},
  57. {video_embed_code, EmbedCode}
  58. ],
  59. m_media:replace(Id, MediaProps, Context),
  60. {ok, Id}
  61. end;
  62. manage_medium(Module, {Name, Filename, Props}, Context) ->
  63. case manage_resource(Module, {Name, media, Props}, Context) of
  64. {ok} ->
  65. {ok};
  66. {ok, Id} ->
  67. m_media:replace_file(Filename, Id, Context),
  68. {ok, Id}
  69. end.
  70. manage_category(Module, {Name, ParentCategory, Props}, Context) ->
  71. case manage_resource(Module, {Name, category, Props}, Context) of
  72. {ok} ->
  73. {ok};
  74. {ok, Id} ->
  75. case ParentCategory of
  76. undefined ->
  77. {ok, Id};
  78. _ ->
  79. case m_category:name_to_id(ParentCategory, Context) of
  80. {ok, PId} ->
  81. m_category:move_below(Id, PId, Context),
  82. {ok, Id};
  83. _ -> throw({error, {nonexisting_parent_category, ParentCategory}})
  84. end
  85. end
  86. end.
  87. manage_predicate(Module, {Name, Uri, Props, ValidFor}, Context) ->
  88. manage_predicate(Module, {Name, [{uri,Uri}|Props], ValidFor}, Context);
  89. manage_predicate(Module, {Name, Props, ValidFor}, Context) ->
  90. case manage_resource(Module, {Name, predicate, Props}, Context) of
  91. {ok} ->
  92. {ok};
  93. {ok, Id} ->
  94. ok = manage_predicate_validfor(Id, ValidFor, Context),
  95. {ok, Id}
  96. end.
  97. manage_resource(Module, {Name, Category, Props0}, Context) ->
  98. case m_category:name_to_id(Category, Context) of
  99. {ok, CatId} ->
  100. Props = map_props(Props0, Context),
  101. case m_rsc:name_to_id(Name, Context) of
  102. {ok, Id} ->
  103. case m_rsc:p(Id, installed_by, Context) of
  104. Module ->
  105. NewProps = update_new_props(Module, Id, Props, Context),
  106. m_rsc:update(Id, [{managed_props, z_html:escape_props(Props)} | NewProps], Context),
  107. {ok};
  108. _ ->
  109. %% Resource exists but is not installed by us.
  110. ?zInfo(io_lib:format("Resource '~p' (~p) exists but is not managed by ~p.", [Name, Id, Module]), Context),
  111. {ok}
  112. end;
  113. {error, {unknown_rsc, _}} ->
  114. %% new resource, or old resource
  115. Props1 = [{name, Name}, {category_id, CatId},
  116. {is_protected, true},
  117. {installed_by, Module}, {managed_props, z_html:escape_props(Props)}] ++ Props,
  118. Props2 = case proplists:get_value(is_published, Props1) of
  119. undefined -> [{is_published, true} | Props1];
  120. _ -> Props1
  121. end,
  122. Props3 = case proplists:get_value(visible_for, Props2) of
  123. undefined -> [{visible_for, ?ACL_VIS_PUBLIC} | Props2];
  124. _ -> Props2
  125. end,
  126. ?zInfo(io_lib:format("Creating new resource '~p'", [Name]), Context),
  127. {ok, Id} = m_rsc:insert(Props3, Context),
  128. case proplists:get_value(media_url, Props3) of
  129. undefined ->
  130. nop;
  131. Url ->
  132. m_media:replace_url(Url, Id, [], Context)
  133. end,
  134. case proplists:get_value(media_file, Props3) of
  135. undefined ->
  136. nop;
  137. File ->
  138. m_media:replace_file(File, Id, Context)
  139. end,
  140. {ok, Id}
  141. end;
  142. {error, _} ->
  143. Msg = io_lib:format("Resource '~p' could not be handled because the category ~p does not exist.", [Name, Category]),
  144. ?zWarning(Msg, Context),
  145. {ok}
  146. end.
  147. update_new_props(Module, Id, NewProps, Context) ->
  148. case m_rsc:p(Id, managed_props, Context) of
  149. undefined ->
  150. NewProps;
  151. PreviousProps ->
  152. lists:foldl(fun({K, V}, Props) ->
  153. case m_rsc:p(Id, K, Context) of
  154. V ->
  155. %% New value == current value
  156. Props;
  157. DbVal ->
  158. case proplists:get_value(K, PreviousProps) of
  159. DbVal ->
  160. %% New value in NewProps, unchanged in DB
  161. [{K,V} | Props];
  162. _PrevVal when is_binary(DbVal) ->
  163. %% Compare with converted to list value
  164. case z_convert:to_list(DbVal) of
  165. V ->
  166. Props;
  167. _ ->
  168. %% Changed by someone else
  169. ?zInfo(io_lib:format("~p: ~p of ~p changed in database, not updating.", [Module, K, Id]), Context),
  170. Props
  171. end;
  172. _PrevVal2 ->
  173. %% Changed by someone else
  174. ?zInfo(io_lib:format("~p: ~p of ~p changed in database, not updating.", [Module, K, Id]), Context),
  175. Props
  176. end
  177. end
  178. end, [], NewProps)
  179. end.
  180. manage_predicate_validfor(_Id, [], _Context) ->
  181. ok;
  182. manage_predicate_validfor(Id, [{SubjectCat, ObjectCat} | Rest], Context) ->
  183. SubjectId = m_rsc:name_to_id_check(SubjectCat, Context),
  184. ObjectId = m_rsc:name_to_id_check(ObjectCat, Context),
  185. F = fun(_S, _I, undefined) ->
  186. ok;
  187. (S, I, C) ->
  188. case z_db:q("SELECT 1 FROM predicate_category WHERE predicate_id = $1 AND is_subject = $2 AND category_id = $3", [S, I, C], Context) of
  189. [{1}] ->
  190. ok;
  191. _ ->
  192. z_db:q("insert into predicate_category (predicate_id, is_subject, category_id) values ($1, $2, $3)", [S, I, C], Context),
  193. ok
  194. end
  195. end,
  196. F(Id, true, SubjectId),
  197. F(Id, false, ObjectId),
  198. manage_predicate_validfor(Id, Rest, Context).
  199. map_props(Props, Context) ->
  200. map_props(Props, Context, []).
  201. map_props([], _Context, Acc) ->
  202. Acc;
  203. map_props([{Key, Value}|Rest], Context, Acc) ->
  204. Value2 = map_prop(Value, Context),
  205. map_props(Rest, Context, [{Key, Value2}|Acc]).
  206. map_prop({file, Filepath}, _Context) ->
  207. {ok, Txt} = file:read_file(Filepath),
  208. Txt;
  209. map_prop({to_id, Name}, Context) ->
  210. case m_rsc:name_to_id(Name, Context) of
  211. {ok, Id} -> Id;
  212. _ -> undefined
  213. end;
  214. map_prop(Value, _Context) ->
  215. Value.
  216. manage_edge(_Module, {SubjectName, PredicateName, ObjectName}, Context) ->
  217. Subject = m_rsc:name_to_id(SubjectName, Context),
  218. Predicate = m_predicate:name_to_id(PredicateName, Context),
  219. Object = m_rsc:name_to_id(ObjectName, Context),
  220. case {Subject, Predicate, Object} of
  221. {{ok, SubjectId}, {ok, PredicateId}, {ok,ObjectId}} ->
  222. m_edge:insert(SubjectId, PredicateId, ObjectId, Context);
  223. _ ->
  224. skip %% One part of the triple was MIA
  225. end.