alchemy_cms 2.6.3 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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