alchemy_cms 2.6.3 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +14 -0
  3. data/.travis.yml +1 -1
  4. data/Gemfile +7 -6
  5. data/README.md +15 -5
  6. data/alchemy_cms.gemspec +3 -2
  7. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +9 -17
  8. data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +70 -0
  9. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +80 -0
  10. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +43 -19
  11. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
  12. data/app/assets/javascripts/alchemy/alchemy.js +4 -2
  13. data/app/assets/javascripts/alchemy/alchemy.onload.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.spinner.js.coffee +14 -0
  15. data/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee.erb +96 -0
  16. data/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +22 -0
  17. data/app/assets/javascripts/alchemy/alchemy.windows.js.coffee +28 -17
  18. data/app/assets/stylesheets/alchemy/base.scss +6 -0
  19. data/app/assets/stylesheets/alchemy/elements.scss +2 -28
  20. data/app/assets/stylesheets/alchemy/errors.scss +1 -1
  21. data/app/assets/stylesheets/alchemy/menubar.css.scss +2 -0
  22. data/app/assets/stylesheets/alchemy/sitemap.scss +21 -34
  23. data/app/assets/stylesheets/alchemy/tables.scss +13 -3
  24. data/app/controllers/alchemy/admin/attachments_controller.rb +10 -5
  25. data/app/controllers/alchemy/admin/base_controller.rb +19 -0
  26. data/app/controllers/alchemy/admin/contents_controller.rb +1 -4
  27. data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -1
  28. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  29. data/app/controllers/alchemy/admin/essence_files_controller.rb +1 -1
  30. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +70 -56
  31. data/app/controllers/alchemy/admin/pages_controller.rb +37 -114
  32. data/app/controllers/alchemy/admin/pictures_controller.rb +5 -12
  33. data/app/controllers/alchemy/admin/resources_controller.rb +3 -1
  34. data/app/controllers/alchemy/admin/trash_controller.rb +1 -1
  35. data/app/controllers/alchemy/attachments_controller.rb +1 -1
  36. data/app/controllers/alchemy/base_controller.rb +3 -15
  37. data/app/controllers/alchemy/messages_controller.rb +4 -10
  38. data/app/controllers/alchemy/pages_controller.rb +6 -6
  39. data/app/controllers/alchemy/passwords_controller.rb +1 -1
  40. data/app/controllers/alchemy/user_sessions_controller.rb +1 -1
  41. data/app/helpers/alchemy/admin/base_helper.rb +49 -230
  42. data/app/helpers/alchemy/admin/contents_helper.rb +5 -1
  43. data/app/helpers/alchemy/admin/elements_helper.rb +19 -47
  44. data/app/helpers/alchemy/admin/essences_helper.rb +59 -17
  45. data/app/helpers/alchemy/admin/navigation_helper.rb +204 -0
  46. data/app/helpers/alchemy/admin/pages_helper.rb +22 -79
  47. data/app/helpers/alchemy/admin/pictures_helper.rb +1 -1
  48. data/app/helpers/alchemy/admin/tags_helper.rb +42 -0
  49. data/app/helpers/alchemy/base_helper.rb +0 -11
  50. data/app/helpers/alchemy/elements_helper.rb +48 -25
  51. data/app/helpers/alchemy/essences_helper.rb +0 -20
  52. data/app/helpers/alchemy/pages_helper.rb +18 -14
  53. data/app/helpers/alchemy/url_helper.rb +1 -0
  54. data/app/mailers/alchemy/messages.rb +4 -6
  55. data/app/models/alchemy/attachment.rb +3 -0
  56. data/app/models/alchemy/cell.rb +33 -35
  57. data/app/models/alchemy/content.rb +20 -111
  58. data/app/models/alchemy/content/factory.rb +188 -0
  59. data/app/models/alchemy/element.rb +51 -200
  60. data/app/models/alchemy/element/definitions.rb +52 -0
  61. data/app/models/alchemy/element/presenters.rb +87 -0
  62. data/app/models/alchemy/essence_date.rb +1 -1
  63. data/app/models/alchemy/essence_file.rb +6 -7
  64. data/app/models/alchemy/essence_picture.rb +19 -4
  65. data/app/models/alchemy/message.rb +18 -14
  66. data/app/models/alchemy/page.rb +120 -214
  67. data/app/models/alchemy/page/elements.rb +145 -36
  68. data/app/models/alchemy/page/natures.rb +90 -0
  69. data/app/models/alchemy/page/scopes.rb +93 -0
  70. data/app/models/alchemy/page/users.rb +25 -0
  71. data/app/models/alchemy/picture.rb +15 -0
  72. data/app/models/alchemy/site.rb +15 -1
  73. data/app/models/alchemy/site/layout.rb +38 -0
  74. data/app/models/alchemy/user.rb +13 -3
  75. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +7 -7
  76. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +8 -8
  77. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +1 -16
  78. data/app/views/alchemy/admin/attachments/destroy.js.erb +1 -4
  79. data/app/views/alchemy/admin/contents/create.js.erb +1 -1
  80. data/app/views/alchemy/admin/dashboard/index.html.erb +14 -13
  81. data/app/views/alchemy/admin/elements/_element_head.html.erb +7 -7
  82. data/app/views/alchemy/admin/elements/_refresh_editor.js.erb +10 -0
  83. data/app/views/alchemy/admin/elements/create.js.erb +44 -44
  84. data/app/views/alchemy/admin/elements/fold.js.erb +22 -26
  85. data/app/views/alchemy/admin/elements/trash.js.erb +1 -1
  86. data/app/views/alchemy/admin/elements/update.js.erb +22 -25
  87. data/app/views/alchemy/admin/essence_files/assign.js.erb +8 -3
  88. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +14 -12
  89. data/app/views/alchemy/admin/essence_pictures/edit.html.erb +22 -39
  90. data/app/views/alchemy/admin/pages/_page.html.erb +73 -80
  91. data/app/views/alchemy/admin/pages/destroy.js.erb +2 -2
  92. data/app/views/alchemy/admin/pages/edit.html.erb +21 -18
  93. data/app/views/alchemy/admin/pages/fold.js.erb +1 -0
  94. data/app/views/alchemy/admin/pages/info.html.erb +32 -0
  95. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +11 -13
  96. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +20 -20
  97. data/app/views/alchemy/admin/partials/_sub_navigation.html.erb +8 -0
  98. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +25 -0
  99. data/app/views/alchemy/admin/partials/_upload_form.html.erb +15 -15
  100. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +39 -39
  101. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +10 -10
  102. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -16
  103. data/app/views/alchemy/admin/resources/destroy.js.erb +1 -1
  104. data/app/views/alchemy/base/500.html.erb +1 -1
  105. data/app/views/alchemy/base/permission_denied.js.erb +1 -1
  106. data/app/views/alchemy/base/redirect.js.erb +1 -1
  107. data/app/views/alchemy/essences/_essence_link_editor.html.erb +1 -1
  108. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +1 -1
  109. data/app/views/alchemy/essences/_essence_richtext_editor.html.erb +1 -1
  110. data/app/views/alchemy/essences/_essence_text_editor.html.erb +1 -1
  111. data/app/views/alchemy/essences/{_essence_picture_tools.html.erb → shared/_essence_picture_tools.html.erb} +5 -5
  112. data/app/views/alchemy/essences/{_linkable_essence_tools.html.erb → shared/_linkable_essence_tools.html.erb} +0 -0
  113. data/app/views/alchemy/messages/contact_form_mail.de.text.erb +12 -0
  114. data/app/views/alchemy/messages/contact_form_mail.en.text.erb +12 -0
  115. data/app/views/alchemy/notifications/reset_password_instructions.de.text.erb +1 -1
  116. data/app/views/alchemy/notifications/reset_password_instructions.en.text.erb +2 -2
  117. data/app/views/alchemy/pages/sitemap.xml.erb +3 -5
  118. data/app/views/alchemy/user_sessions/leave.html.erb +1 -1
  119. data/app/views/layouts/alchemy/admin.html.erb +4 -2
  120. data/app/views/layouts/alchemy/sitemap.xml.erb +1 -1
  121. data/bin/alchemy +7 -13
  122. data/config/alchemy/config.yml +1 -0
  123. data/config/authorization_rules.rb +2 -3
  124. data/config/initializers/dragonfly.rb +2 -0
  125. data/config/locales/alchemy.de.yml +8 -9
  126. data/config/locales/alchemy.en.yml +7 -4
  127. data/config/routes.rb +3 -0
  128. data/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
  129. data/lib/alchemy/auth/engine.rb +9 -0
  130. data/lib/alchemy/capistrano.rb +37 -12
  131. data/lib/alchemy/config.rb +48 -35
  132. data/lib/alchemy/engine.rb +35 -6
  133. data/lib/alchemy/essence.rb +25 -29
  134. data/lib/alchemy/ferret/search.rb +86 -0
  135. data/lib/alchemy/{scoped_pagination_url_helper.rb → kaminari/scoped_pagination_url_helper.rb} +0 -0
  136. data/lib/alchemy/logger.rb +3 -4
  137. data/lib/alchemy/page_layout.rb +124 -55
  138. data/lib/alchemy/resource.rb +0 -10
  139. data/lib/alchemy/resources_helper.rb +0 -5
  140. data/lib/alchemy/seeder.rb +1 -32
  141. data/lib/alchemy/shell.rb +6 -1
  142. data/lib/alchemy/tinymce.rb +41 -32
  143. data/lib/alchemy/upgrader.rb +3 -1
  144. data/lib/alchemy/upgrader/two_point_five.rb +15 -8
  145. data/lib/alchemy/upgrader/two_point_one.rb +10 -10
  146. data/lib/alchemy/upgrader/two_point_two.rb +96 -51
  147. data/lib/alchemy/version.rb +1 -1
  148. data/lib/alchemy_cms.rb +5 -46
  149. data/lib/rails/generators/alchemy/deploy_script/templates/deploy.rb.tt +1 -1
  150. data/lib/rails/generators/alchemy/devise/devise_generator.rb +9 -4
  151. data/lib/rails/generators/alchemy/essence/essence_generator.rb +7 -6
  152. data/lib/rails/generators/alchemy/essence/templates/editor.html.erb +1 -1
  153. data/lib/rails/generators/alchemy/scaffold/files/_standard.html.erb +1 -0
  154. data/lib/rails/generators/alchemy/scaffold/scaffold_generator.rb +1 -0
  155. data/lib/rails/generators/alchemy/site_layouts/site_layouts_generator.rb +23 -0
  156. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.erb +1 -0
  157. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.haml +1 -0
  158. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.slim +1 -0
  159. data/lib/rails/templates/alchemy.rb +2 -2
  160. data/lib/tasks/alchemy/db.rake +3 -1
  161. data/lib/tasks/alchemy/tidy.rake +82 -0
  162. data/lib/tasks/alchemy/upgrade.rake +2 -1
  163. data/spec/controllers/admin/attachments_controller_spec.rb +124 -0
  164. data/spec/controllers/admin/base_controller_spec.rb +35 -0
  165. data/spec/controllers/admin/clipboard_controller_spec.rb +1 -1
  166. data/spec/controllers/admin/contents_controller_spec.rb +17 -26
  167. data/spec/controllers/admin/dashboard_controller_spec.rb +121 -0
  168. data/spec/controllers/admin/elements_controller_spec.rb +1 -1
  169. data/spec/controllers/admin/essence_files_controller_spec.rb +67 -0
  170. data/spec/controllers/admin/essence_pictures_controller_spec.rb +161 -0
  171. data/spec/controllers/admin/languages_controller_spec.rb +1 -1
  172. data/spec/controllers/admin/layoutpages_controller_spec.rb +28 -0
  173. data/spec/controllers/admin/pages_controller_spec.rb +164 -118
  174. data/spec/controllers/admin/pictures_controller_spec.rb +89 -0
  175. data/spec/controllers/admin/trash_controller_spec.rb +21 -31
  176. data/spec/controllers/admin/users_controller_spec.rb +114 -85
  177. data/spec/controllers/attachments_controller_spec.rb +6 -2
  178. data/spec/controllers/base_controller_spec.rb +22 -0
  179. data/spec/controllers/elements_controller_spec.rb +1 -1
  180. data/spec/controllers/messages_controller_spec.rb +200 -0
  181. data/spec/controllers/pictures_controller_spec.rb +1 -1
  182. data/spec/controllers/user_sessions_controller_spec.rb +7 -6
  183. data/spec/controllers/users_controller_spec.rb +2 -2
  184. data/spec/dummy/config/alchemy/cells.yml +2 -0
  185. data/spec/dummy/config/application.rb +19 -8
  186. data/spec/dummy/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
  187. data/spec/dummy/db/schema.rb +1 -1
  188. data/spec/fast_specs.rb +15 -0
  189. data/spec/helpers/admin/base_helper_spec.rb +53 -34
  190. data/spec/helpers/admin/contents_helper_spec.rb +15 -7
  191. data/spec/helpers/admin/elements_helper_spec.rb +79 -34
  192. data/spec/helpers/admin/essences_helper_spec.rb +45 -31
  193. data/spec/helpers/admin/navigation_helper_spec.rb +204 -0
  194. data/spec/helpers/admin/pages_helper_spec.rb +25 -15
  195. data/spec/helpers/admin/tags_helper_spec.rb +62 -2
  196. data/spec/helpers/elements_helper_spec.rb +202 -138
  197. data/spec/helpers/pages_helper_spec.rb +48 -0
  198. data/spec/helpers/url_helper_spec.rb +7 -0
  199. data/spec/libraries/config_spec.rb +110 -3
  200. data/spec/libraries/essence_spec.rb +29 -9
  201. data/spec/libraries/page_layout_spec.rb +134 -0
  202. data/spec/libraries/resource_spec.rb +3 -16
  203. data/spec/libraries/resources_helper_spec.rb +4 -8
  204. data/spec/libraries/shell_spec.rb +1 -0
  205. data/spec/libraries/tinymce_spec.rb +61 -0
  206. data/spec/mailers/messages_spec.rb +23 -0
  207. data/spec/models/attachment_spec.rb +45 -0
  208. data/spec/models/cell_spec.rb +62 -9
  209. data/spec/models/content_spec.rb +110 -28
  210. data/spec/models/element_spec.rb +275 -253
  211. data/spec/models/essence_date_spec.rb +25 -0
  212. data/spec/models/essence_file_spec.rb +23 -0
  213. data/spec/models/essence_html_spec.rb +13 -0
  214. data/spec/models/essence_picture_spec.rb +16 -0
  215. data/spec/models/essence_text_spec.rb +29 -0
  216. data/spec/models/language_spec.rb +34 -0
  217. data/spec/models/message_spec.rb +43 -0
  218. data/spec/models/page_spec.rb +726 -567
  219. data/spec/models/picture_spec.rb +98 -0
  220. data/spec/models/site_spec.rb +60 -2
  221. data/spec/models/tag_spec.rb +31 -0
  222. data/spec/models/user_spec.rb +4 -4
  223. data/spec/spec_helper.rb +49 -58
  224. data/spec/support/alchemy/controller_helpers.rb +35 -0
  225. data/spec/support/alchemy/{specs_helpers.rb → integration_helpers.rb} +4 -8
  226. data/spec/{factories.rb → support/factories.rb} +11 -1
  227. data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +2 -8
  228. metadata +166 -106
  229. data/Guardfile +0 -16
  230. data/app/assets/javascripts/alchemy/alchemy.dirty.js +0 -93
  231. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js +0 -122
  232. data/app/models/alchemy/tree_node.rb +0 -4
  233. data/app/views/alchemy/admin/pages/_page_infos.html.erb +0 -3
  234. data/app/views/alchemy/admin/partials/_sub_navigation_tab.html.erb +0 -8
  235. data/app/views/alchemy/messages/contact_form_mail.text.erb +0 -12
  236. data/config/initializers/kaminari_config.rb +0 -9
  237. data/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
  238. data/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
  239. data/lib/alchemy/auth_engine.rb +0 -7
  240. data/lib/alchemy/authentication_helpers.rb +0 -9
  241. data/lib/alchemy/ferret_search.rb +0 -84
  242. data/lib/extensions/array.rb +0 -25
  243. data/lib/extensions/hash.rb +0 -34
  244. data/spec/dummy/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
  245. data/spec/dummy/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
  246. data/spec/models/page_layout_spec.rb +0 -60
@@ -0,0 +1,188 @@
1
+ module Alchemy
2
+
3
+ # Holds everything concerning the building and creating of contents and the related essence object.
4
+ #
5
+ module Content::Factory
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id id)
10
+
11
+ # Builds a new content as descriped in the elements.yml file.
12
+ #
13
+ # @param [Alchemy::Element]
14
+ # The element the content is for
15
+ # @param [Hash]
16
+ # The content description used for finding the content in +elements.yml+ file
17
+ #
18
+ def build(element, essence_hash)
19
+ if (description = content_description(element, essence_hash)).blank?
20
+ raise ContentDefinitionError, "No description found in elements.yml for #{essence_hash.inspect} and #{element.inspect}"
21
+ else
22
+ new(name: description['name'], element_id: element.id)
23
+ end
24
+ end
25
+
26
+ # Creates a new content from elements description in the +elements.yml+ file.
27
+ #
28
+ # 1. It builds the content
29
+ # 2. It creates the essence record (content object gets saved)
30
+ #
31
+ # @return [Alchemy::Content]
32
+ #
33
+ def create_from_scratch(element, essence_hash)
34
+ essence_hash.stringify_keys!
35
+ if content = build(element, essence_hash)
36
+ content.create_essence!(essence_hash['essence_type'])
37
+ end
38
+ content
39
+ end
40
+
41
+ # Makes a copy of source and also copies the associated essence.
42
+ #
43
+ # You can pass a differences hash to update the attributes of the copy.
44
+ #
45
+ # === Example
46
+ #
47
+ # @copy = Alchemy::Content.copy(@content, {:element_id => 3})
48
+ # @copy.element_id # => 3
49
+ #
50
+ def copy(source, differences = {})
51
+ attributes = source.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY).merge(differences.stringify_keys)
52
+ content = self.create!(attributes)
53
+ new_essence = content.essence.class.new(content.essence.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY))
54
+ new_essence.save!
55
+ raise "Essence not cloned" if new_essence.id == content.essence_id
56
+ content.update_attributes(essence_id: new_essence.id)
57
+ content
58
+ end
59
+
60
+ # Returns the content description for building a content.
61
+ #
62
+ # 1. It looks in the element's contents description
63
+ # 2. It builds a description hash from essence type, if the the name key is not present
64
+ #
65
+ def content_description(element, essence_hash)
66
+ essence_hash.stringify_keys!
67
+ # No name given. We build the content from essence type.
68
+ if essence_hash['name'].blank? && essence_hash['essence_type'].present?
69
+ content_description_from_essence_type(element, essence_hash['essence_type'])
70
+ else
71
+ content_description_from_element(element, essence_hash['name'])
72
+ end
73
+ end
74
+
75
+ # Returns a hash for building a content from essence type.
76
+ #
77
+ # @param [Alchemy::Element]
78
+ # The element the content is for.
79
+ # @param [String]
80
+ # The essence type the content is from
81
+ #
82
+ def content_description_from_essence_type(element, essence_type)
83
+ {
84
+ 'type' => essence_type,
85
+ 'name' => content_name_from_element_and_essence_type(element, essence_type)
86
+ }
87
+ end
88
+
89
+ # A name for content from its essence type and amount of same essences in element.
90
+ #
91
+ # Example:
92
+ #
93
+ # essence_picture_1
94
+ #
95
+ def content_name_from_element_and_essence_type(element, essence_type)
96
+ essences_of_same_type = element.contents.where(essence_type: normalize_essence_type(essence_type))
97
+ "#{essence_type.classify.demodulize.underscore}_#{essences_of_same_type.count + 1}"
98
+ end
99
+
100
+ # Returns the content description hash from element.
101
+ #
102
+ # It first uses the normal content description described in the +elements.yml+ +contents+ array.
103
+ #
104
+ # If the content description could not be found it tries to load it from +available_contents+ array.
105
+ #
106
+ # @param [Alchemy::Element]
107
+ # The element instance the content is for
108
+ # @param [String]
109
+ # The name of the content
110
+ #
111
+ def content_description_from_element(element, name)
112
+ element.content_description_for(name) ||
113
+ element.available_content_description_for(name)
114
+ end
115
+
116
+ # Returns all content descriptions from elements.yml
117
+ #
118
+ def descriptions
119
+ Element.descriptions.collect { |e| e['contents'] }.flatten.compact
120
+ end
121
+
122
+ # Returns a normalized Essence type
123
+ #
124
+ # Adds Alchemy module name in front of given essence type
125
+ #
126
+ # @param [String]
127
+ # the essence type to normalize
128
+ #
129
+ def normalize_essence_type(essence_type)
130
+ essence_type = essence_type.classify
131
+ if essence_type.match(/^Alchemy::/)
132
+ essence_type
133
+ else
134
+ essence_type.gsub!(/^Essence/, 'Alchemy::Essence')
135
+ end
136
+ end
137
+
138
+ end # end class methods
139
+
140
+ # Instance Methods
141
+
142
+ # Returns the description hash from +elements.yml+ file.
143
+ #
144
+ def description
145
+ if element.blank?
146
+ log_warning "Content with id #{self.id} is missing its Element."
147
+ return {}
148
+ end
149
+ Content.content_description_from_element(element, name) || {}
150
+ end
151
+ alias_method :definition, :description
152
+
153
+ # Creates essence from description.
154
+ #
155
+ # If an optional type is passed, this type of essence gets created.
156
+ #
157
+ def create_essence!(type = nil)
158
+ self.essence = essence_class(type).create!(prepared_attributes_for_essence)
159
+ self.save!
160
+ end
161
+
162
+ private
163
+
164
+ # Returns a class constant from description's type field.
165
+ #
166
+ # If an optional type is passed, this type of essence gets constantized.
167
+ #
168
+ def essence_class(type = nil)
169
+ Content.normalize_essence_type(type || description['type']).constantize
170
+ end
171
+
172
+ # Prepares the attributes for creating the essence.
173
+ #
174
+ # 1. It sets a default text if given in +elements.yml+
175
+ # 2. It sets do_not_index value for EssenceText and EssenceRichtext essences
176
+ #
177
+ def prepared_attributes_for_essence
178
+ attributes = {
179
+ ingredient: default_text(description['default'])
180
+ }
181
+ if description['type'] == "EssenceRichtext" || description['type'] == "EssenceText"
182
+ attributes.merge!(do_not_index: !!description['do_not_index'])
183
+ end
184
+ attributes
185
+ end
186
+
187
+ end
188
+ end
@@ -1,3 +1,7 @@
1
+ require 'acts-as-taggable-on'
2
+ require 'userstamp'
3
+ require 'acts_as_list'
4
+
1
5
  module Alchemy
2
6
  class Element < ActiveRecord::Base
3
7
  include Logger
@@ -24,7 +28,7 @@ module Alchemy
24
28
 
25
29
  has_many :contents, :order => :position, :dependent => :destroy
26
30
  belongs_to :cell
27
- belongs_to :page
31
+ belongs_to :page, touch: true
28
32
  has_and_belongs_to_many :to_be_sweeped_pages, :class_name => 'Alchemy::Page', :uniq => true, :join_table => 'alchemy_elements_alchemy_pages'
29
33
 
30
34
  validates_uniqueness_of :position, :scope => [:page_id, :cell_id], :if => lambda { |e| e.position != nil }
@@ -49,6 +53,10 @@ module Alchemy
49
53
  # TODO: add this as default_scope
50
54
  #default_scope { from_current_site }
51
55
 
56
+ # Concerns
57
+ include Definitions
58
+ include Presenters
59
+
52
60
  # class methods
53
61
  class << self
54
62
 
@@ -56,14 +64,13 @@ module Alchemy
56
64
  def new_from_scratch(attributes)
57
65
  attributes = attributes.dup.symbolize_keys
58
66
  return new if attributes[:name].blank?
59
- return nil if descriptions.blank?
67
+ return nil if definitions.blank?
60
68
  # clean the name from cell name
61
69
  attributes[:name] = attributes[:name].split('#').first
62
- element_scratch = descriptions.detect { |el| el['name'] == attributes[:name] }
63
- if element_scratch
70
+ if element_scratch = definitions.detect { |el| el['name'] == attributes[:name] }
64
71
  new(element_scratch.merge(attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
65
72
  else
66
- raise ElementDefinitionError, "Element description for #{attributes[:name]} not found. Please check your elements.yml"
73
+ raise ElementDefinitionError, "Element definition for #{attributes[:name]} not found. Please check your elements.yml"
67
74
  end
68
75
  end
69
76
 
@@ -74,73 +81,6 @@ module Alchemy
74
81
  return element
75
82
  end
76
83
 
77
- # Returns the descriptions from elements.yml file.
78
- #
79
- # Place a elements.yml file inside your apps config/alchemy folder to define
80
- # your own set of elements
81
- #
82
- def descriptions
83
- if ::File.exists? "#{::Rails.root}/config/alchemy/elements.yml"
84
- ::YAML.load_file("#{::Rails.root}/config/alchemy/elements.yml") || []
85
- else
86
- raise LoadError, "Could not find elements.yml file! Please run: rails generate alchemy:scaffold"
87
- end
88
- rescue TypeError => e
89
- warn "Your elements.yml is empty."
90
- []
91
- end
92
- alias_method :definitions, :descriptions
93
-
94
- # pastes a element from the clipboard in the session to page
95
- def paste_from_clipboard(page_id, element, method, position)
96
- element_copy = copy(element, :page_id => page_id)
97
- element_copy.insert_at(position)
98
- if method == "move" && element_copy.valid?
99
- element.destroy
100
- end
101
- element_copy
102
- end
103
-
104
- # List all element definitions for +self.page#page_layout+
105
- def all_for_page(page)
106
- raise TypeError if page.class.name != "Alchemy::Page"
107
- # if page_layout has cells, collect elements from cells and group them by cellname
108
- page_layout = PageLayout.get(page.page_layout)
109
- if page_layout.blank?
110
- warn "Could not find page_layout description for page: #{page.name}"
111
- return []
112
- end
113
- elements_for_layout = []
114
- elements_for_layout += all_definitions_for(page_layout['elements'])
115
- return [] if elements_for_layout.blank?
116
- # all unique and limited elements from this layout
117
- limited_elements = elements_for_layout.select{ |m| m["unique"] == true || (m["amount"] > 0 unless m["amount"].nil?) }
118
- elements_already_on_the_page = page.elements.not_trashed
119
- # delete all elements from the elements that could be placed that are unique or limited and already and the page
120
- elements_counts = Hash.new(0)
121
- elements_already_on_the_page.each { |e| elements_counts[e.name] += 1 }
122
- limited_elements.each do |limited_element|
123
- next if elements_counts[limited_element["name"]] == 0
124
- if limited_element["unique"]
125
- elements_for_layout.delete(limited_element) if elements_counts[limited_element["name"]] > 0
126
- next
127
- end
128
- unless limited_element["amount"].nil?
129
- elements_for_layout.delete(limited_element) if elements_counts[limited_element["name"]] >= limited_element["amount"]
130
- end
131
- end
132
- elements_for_layout
133
- end
134
-
135
- def all_definitions_for(element_names)
136
- return [] if element_names.blank?
137
- if element_names.to_s == "all"
138
- definitions
139
- else
140
- definitions.select { |e| element_names.include? e['name'] }
141
- end
142
- end
143
-
144
84
  # This methods does a copy of source and all depending contents and all of their depending essences.
145
85
  #
146
86
  # == Options
@@ -165,53 +105,36 @@ module Alchemy
165
105
  element
166
106
  end
167
107
 
168
- # List all elements from page_layout
169
- def elements_for_layout(layout)
170
- elements = []
171
- layout_elements = PageLayout.get(layout)["elements"]
172
- return Element.descriptions if layout_elements == "all"
173
- Element.descriptions.each do |element|
174
- if layout_elements.include?(element["name"])
175
- elements << element
176
- end
177
- end
178
- elements
179
- end
180
-
181
- def get_from_clipboard(clipboard)
182
- return nil if clipboard.blank?
183
- find_by_id(clipboard[:element_id])
184
- end
185
-
186
108
  def all_from_clipboard(clipboard)
187
109
  return [] if clipboard.nil?
188
- find_all_by_id(clipboard.collect { |i| i[:id] })
110
+ find_all_by_id(clipboard.collect { |e| e[:id] })
189
111
  end
190
112
 
113
+ # All elements in clipboard that could be placed on page
114
+ #
191
115
  def all_from_clipboard_for_page(clipboard, page)
192
116
  return [] if clipboard.nil? || page.nil?
193
- allowed_elements = all_for_page(page)
194
- clipboard_elements = all_from_clipboard(clipboard)
195
- allowed_element_names = allowed_elements.collect { |e| e['name'] }
196
- clipboard_elements.select { |ce| allowed_element_names.include?(ce.name) }
117
+ all_from_clipboard(clipboard).select { |ce|
118
+ page.available_element_names.include?(ce.name)
119
+ }
197
120
  end
198
121
 
199
122
  end
200
123
 
201
124
  # Returns next public element from same page.
125
+ #
202
126
  # Pass an element name to get next of this kind.
127
+ #
203
128
  def next(name = nil)
204
- elements = page.elements.published.where(Element.arel_table[:position].gt(position))
205
- elements = elements.named(name) if name.present?
206
- elements.reorder("position ASC").limit(1).first
129
+ previous_or_next('>', name)
207
130
  end
208
131
 
209
132
  # Returns previous public element from same page.
133
+ #
210
134
  # Pass an element name to get previous of this kind.
135
+ #
211
136
  def prev(name = nil)
212
- elements = page.elements.published.where(Element.arel_table[:position].lt(position))
213
- elements = elements.named(name) if name.present?
214
- elements.reorder("position DESC").limit(1).first
137
+ previous_or_next('<', name)
215
138
  end
216
139
 
217
140
  # Stores the page into `to_be_sweeped_pages` (Pages that have to be sweeped after updating element).
@@ -265,9 +188,9 @@ module Alchemy
265
188
  contents.find_by_name(rss_title['name'])
266
189
  end
267
190
 
268
- # Returns the content that is marked as rss description.
191
+ # Returns the content that is marked as rss definition.
269
192
  #
270
- # Mark a content as rss description in your +elements.yml+ file:
193
+ # Mark a content as rss definition in your +elements.yml+ file:
271
194
  #
272
195
  # - name: news
273
196
  # contents:
@@ -282,114 +205,29 @@ module Alchemy
282
205
 
283
206
  # Returns the array with the hashes for all element contents in the elements.yml file
284
207
  def content_descriptions
285
- return nil if description.blank?
286
- description['contents']
287
- end
288
-
289
- # Returns the array with the hashes for all element available_contents in the elements.yml file
290
- def available_content_descriptions
291
- return nil if description.blank?
292
- description['available_contents']
208
+ return nil if definition.blank?
209
+ definition['contents']
293
210
  end
294
211
 
295
- # Returns the description for given content_name
212
+ # Returns the definition for given content_name
296
213
  def content_description_for(content_name)
297
214
  if content_descriptions.blank?
298
- warn "Element #{self.name} is missing the content description for #{content_name}"
215
+ log_warning "Element #{self.name} is missing the content definition for #{content_name}"
299
216
  return nil
300
217
  else
301
218
  content_descriptions.detect { |d| d['name'] == content_name }
302
219
  end
303
220
  end
304
221
 
305
- # Returns the description for given content_name inside the available_contents
222
+ # Returns the definition for given content_name inside the available_contents
306
223
  def available_content_description_for(content_name)
307
- return nil if available_content_descriptions.blank?
308
- available_content_descriptions.detect { |d| d['name'] == content_name }
309
- end
310
-
311
- # returns the description of the element with my name in element.yml
312
- def description
313
- description = self.class.descriptions.detect { |d| d['name'] == self.name }
314
- if description.blank?
315
- warn "Could not find element definition for #{self.name}. Please check your elements.yml!"
316
- return {}
317
- else
318
- return description
319
- end
320
- end
321
- alias_method :definition, :description
322
-
323
- # Human name for displaying in selectboxes and element editor views.
324
- # The name is beeing translated from elements name value as described in config/alchemy/elements.yml
325
- #
326
- # Translate the name in your config/locales language file. Example:
327
- #
328
- # de:
329
- # alchemy:
330
- # element_names:
331
- # contactform: 'Kontakt Formular'
332
- #
333
- # If no translation is found a humanized name is used.
334
- #
335
- def display_name
336
- return name.humanize if description.blank?
337
- I18n.t(description['name'], scope: 'element_names', default: description['name'].to_s.humanize)
338
- end
339
-
340
- # Gets the preview text from the first Content found in the +elements.yml+ Element description file.
341
- # You can flag a Content as +take_me_for_preview+ to take this as preview.
342
- def preview_text(maxlength = 30)
343
- return "" if description.blank?
344
- my_contents = description["contents"]
345
- return "" if my_contents.blank?
346
- content_flagged_as_preview = my_contents.select { |a| a["take_me_for_preview"] }.first
347
- if content_flagged_as_preview.blank?
348
- content_to_take_as_preview = my_contents.first
349
- else
350
- content_to_take_as_preview = content_flagged_as_preview
351
- end
352
- preview_content = self.contents.select { |content| content.name == content_to_take_as_preview["name"] }.first
353
- return "" if preview_content.blank? || preview_content.essence.blank?
354
- text = preview_content.essence.preview_text(maxlength)
355
- text.size > maxlength ? "#{text[0..maxlength]}..." : text
356
- end
357
-
358
- # Generates a preview text containing Element#display_name and Element#preview_text.
359
- # It is displayed inside the head of the Element in the Elements.list overlay window from the Alchemy Admin::Page#edit view.
360
- #
361
- # === Example
362
- #
363
- # A Element described as:
364
- #
365
- # - name: funky_element
366
- # display_name: Funky Element
367
- # contents:
368
- # - name: headline
369
- # type: EssenceText
370
- # - name: text
371
- # type EssenceRichtext
372
- # take_me_for_preview: true
373
- #
374
- # With "I want to tell you a funky story" as stripped_body for the EssenceRichtext Content produces:
375
- #
376
- # Funky Element: I want to tell ...
377
- #
378
- # Options:
379
- #
380
- # maxlength(integer). [Default 30] : Length of characters after the text will be cut off.
381
- #
382
- def display_name_with_preview_text(maxlength = 30)
383
- "#{display_name}: #{preview_text(maxlength)}"
384
- end
385
-
386
- def dom_id
387
- "#{name}_#{id}"
224
+ return nil if available_contents.blank?
225
+ available_contents.detect { |d| d['name'] == content_name }
388
226
  end
389
227
 
390
228
  # returns the collection of available essence_types that can be created for this element depending on its description in elements.yml
391
229
  def available_contents
392
- description['available_contents']
230
+ definition['available_contents']
393
231
  end
394
232
 
395
233
  # Returns the contents ingredient for passed content name.
@@ -537,7 +375,7 @@ module Alchemy
537
375
 
538
376
  # Returns true if the definition of this element has a taggable true value.
539
377
  def taggable?
540
- description['taggable'] == true
378
+ definition['taggable'] == true
541
379
  end
542
380
 
543
381
  def to_partial_path
@@ -589,14 +427,27 @@ module Alchemy
589
427
  # creates the contents for this element as described in the elements.yml
590
428
  def create_contents
591
429
  contents = []
592
- if description["contents"].blank?
593
- warn "Could not find any content descriptions for element: #{self.name}"
430
+ if definition["contents"].blank?
431
+ log_warning "Could not find any content descriptions for element: #{self.name}"
594
432
  else
595
- description["contents"].each do |content_hash|
433
+ definition["contents"].each do |content_hash|
596
434
  contents << Content.create_from_scratch(self, content_hash.symbolize_keys)
597
435
  end
598
436
  end
599
437
  end
600
438
 
439
+ # Returns previous or next public element from same page.
440
+ #
441
+ # @param [String]
442
+ # Pass '>' or '<' to find next or previous public element.
443
+ # @param [String]
444
+ # Pass an element name to get previous of this kind.
445
+ #
446
+ def previous_or_next(dir, name = nil)
447
+ elements = page.elements.published.where("#{self.class.table_name}.position #{dir} #{position}")
448
+ elements = elements.named(name) if name.present?
449
+ elements.reorder("position #{dir == '>' ? 'ASC' : 'DESC'}").limit(1).first
450
+ end
451
+
601
452
  end
602
453
  end