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,52 @@
1
+ module Alchemy
2
+
3
+ # Module concerning element definitions
4
+ #
5
+ module Element::Definitions
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Returns the definitions from elements.yml file.
11
+ #
12
+ # Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
13
+ # your own set of elements
14
+ #
15
+ def definitions
16
+ @definitions ||= read_definitions_file
17
+ end
18
+ alias_method :descriptions, :definitions
19
+
20
+ private
21
+
22
+ # Reads the element definitions file named +elements.yml+ from +config/alchemy/+ folder.
23
+ #
24
+ def read_definitions_file
25
+ if ::File.exists?(definitions_file_path)
26
+ ::YAML.load_file(definitions_file_path) || []
27
+ else
28
+ raise LoadError, "Could not find elements.yml file! Please run `rails generate alchemy:scaffold`"
29
+ end
30
+ end
31
+
32
+ # Returns the +elements.yml+ file path
33
+ #
34
+ def definitions_file_path
35
+ Rails.root.join 'config/alchemy/elements.yml'
36
+ end
37
+ end
38
+
39
+ # The definition of this element.
40
+ #
41
+ def definition
42
+ if definition = self.class.definitions.detect { |d| d['name'] == name }
43
+ definition
44
+ else
45
+ log_warning "Could not find element definition for #{self.name}. Please check your elements.yml file!"
46
+ return {}
47
+ end
48
+ end
49
+ alias_method :description, :definition
50
+
51
+ end
52
+ end
@@ -0,0 +1,87 @@
1
+ module Alchemy
2
+
3
+ # Methods used for presenting an Alchemy Element.
4
+ #
5
+ module Element::Presenters
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Human name for displaying elements in select boxes and element editor views.
11
+ #
12
+ # The name is beeing translated from given name value as described in +config/alchemy/elements.yml+
13
+ #
14
+ # Translate the name in your +config/locales+ language file.
15
+ #
16
+ # == Example:
17
+ #
18
+ # de:
19
+ # alchemy:
20
+ # element_names:
21
+ # contactform: 'Kontakt Formular'
22
+ #
23
+ # If no translation is found a humanized name is used.
24
+ #
25
+ def display_name_for(name)
26
+ I18n.t(name, scope: 'element_names', default: name.to_s.humanize)
27
+ end
28
+ end
29
+
30
+ # Returns the translated name
31
+ #
32
+ # @see Alchemy::Element::Presenters#display_name_for
33
+ #
34
+ def display_name
35
+ self.class.display_name_for(description['name'] || self.name)
36
+ end
37
+
38
+ # Returns a preview text for element.
39
+ #
40
+ # It's taken from the first Content found in the +elements.yml+ description file.
41
+ #
42
+ # You can flag a Content as +take_me_for_preview+ to take this as preview.
43
+ #
44
+ # @param maxlength [Fixnum] (30)
45
+ # Length of characters after the text will be cut off.
46
+ #
47
+ def preview_text(maxlength = 30)
48
+ (contents.detect(&:preview_content?) || contents.first).try(:preview_text, maxlength)
49
+ end
50
+
51
+ # Generates a preview text containing Element#display_name and Element#preview_text.
52
+ #
53
+ # It is displayed inside the head of the Element in the Elements.list overlay window from the Alchemy Admin::Page#edit view.
54
+ #
55
+ # === Example
56
+ #
57
+ # A Element described as:
58
+ #
59
+ # - name: funky_element
60
+ # display_name: Funky Element
61
+ # contents:
62
+ # - name: headline
63
+ # type: EssenceText
64
+ # - name: text
65
+ # type EssenceRichtext
66
+ # take_me_for_preview: true
67
+ #
68
+ # With "I want to tell you a funky story" as stripped_body for the EssenceRichtext Content produces:
69
+ #
70
+ # Funky Element: I want to tell ...
71
+ #
72
+ # @param maxlength [Fixnum] (30)
73
+ # Length of characters after the text will be cut off.
74
+ #
75
+ def display_name_with_preview_text(maxlength = 30)
76
+ "#{display_name}: #{preview_text(maxlength)}"
77
+ end
78
+
79
+ # Returns a dom id used for elements html id tag.
80
+ #
81
+ def dom_id
82
+ "#{name}_#{id}"
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -8,7 +8,7 @@ module Alchemy
8
8
  )
9
9
 
10
10
  # Returns self.date for the Element#preview_text method.
11
- def preview_text(foo=nil)
11
+ def preview_text
12
12
  return "" if date.blank?
13
13
  ::I18n.l(date, :format => :date)
14
14
  end
@@ -1,14 +1,13 @@
1
1
  module Alchemy
2
2
  class EssenceFile < ActiveRecord::Base
3
-
4
3
  attr_accessible :title, :css_class, :attachment_id
5
-
6
- acts_as_essence(
7
- :ingredient_column => :attachment,
8
- :preview_text_method => :name
9
- )
10
-
11
4
  belongs_to :attachment
5
+ acts_as_essence ingredient_column: 'attachment'
6
+
7
+ def preview_text(max=30)
8
+ return "" if attachment.blank?
9
+ attachment.name.to_s[0..max-1]
10
+ end
12
11
 
13
12
  end
14
13
  end
@@ -16,15 +16,30 @@ module Alchemy
16
16
  :picture_id
17
17
  )
18
18
 
19
- acts_as_essence(
20
- :ingredient_column => :picture,
21
- :preview_text_method => :name
22
- )
19
+ acts_as_essence ingredient_column: 'picture'
23
20
 
24
21
  belongs_to :picture
25
22
  before_save :fix_crop_values
26
23
  before_save :replace_newlines
27
24
 
25
+ def preview_text(max=30)
26
+ return "" if picture.nil?
27
+ picture.name.to_s[0..max-1]
28
+ end
29
+
30
+ # Returns a hash suitable for the js image cropper.
31
+ #
32
+ def cropping_mask
33
+ crop_from = self.crop_from.split('x')
34
+ crop_size = self.crop_size.split('x')
35
+ {
36
+ x1: crop_from[0].to_i,
37
+ y1: crop_from[1].to_i,
38
+ x2: crop_from[0].to_i + crop_size[0].to_i,
39
+ y2: crop_from[1].to_i + crop_size[1].to_i
40
+ }
41
+ end
42
+
28
43
  private
29
44
 
30
45
  def fix_crop_values
@@ -17,31 +17,31 @@ module Alchemy
17
17
  include ::ActiveModel::Conversion
18
18
  include ::ActiveModel::MassAssignmentSecurity
19
19
 
20
- def self.attr_accessor(*vars)
21
- @attributes ||= {}
22
- vars.map { |v| @attributes[v] = nil}
23
- super(*vars)
24
- end
20
+ class << self
21
+ def attr_accessor(*vars)
22
+ @attributes ||= {}
23
+ vars.map { |v| @attributes[v] = nil}
24
+ super(*vars)
25
+ end
25
26
 
26
- def self.attributes
27
- @attributes
28
- end
27
+ def attributes
28
+ @attributes
29
+ end
29
30
 
30
- def attributes
31
- self.class.attributes
31
+ def config
32
+ Config.get(:mailer)
33
+ end
32
34
  end
33
35
 
34
- @@config = Config.get(:mailer)
35
-
36
36
  attr_accessor :contact_form_id, :ip
37
37
  attr_accessible :contact_form_id
38
38
 
39
- @@config['fields'].each do |field|
39
+ config['fields'].each do |field|
40
40
  attr_accessor field.to_sym
41
41
  attr_accessible field.to_sym
42
42
  end
43
43
 
44
- @@config['validate_fields'].each do |field|
44
+ config['validate_fields'].each do |field|
45
45
  validates_presence_of field
46
46
 
47
47
  case field.to_sym
@@ -60,6 +60,10 @@ module Alchemy
60
60
  end
61
61
  end
62
62
 
63
+ def attributes
64
+ self.class.attributes
65
+ end
66
+
63
67
  def persisted? #:nodoc:
64
68
  false
65
69
  end
@@ -1,3 +1,7 @@
1
+ require 'acts-as-taggable-on'
2
+ require 'awesome_nested_set'
3
+ require 'userstamp'
4
+
1
5
  module Alchemy
2
6
  class Page < ActiveRecord::Base
3
7
 
@@ -45,6 +49,7 @@ module Alchemy
45
49
  has_many :folded_pages
46
50
  has_many :legacy_urls, :class_name => 'Alchemy::LegacyPageUrl'
47
51
  belongs_to :language
52
+ belongs_to :locker, class_name: 'Alchemy::User', foreign_key: 'locked_by'
48
53
 
49
54
  validates_presence_of :language, :on => :create, :unless => :root
50
55
  validates_presence_of :page_layout, :unless => :systempage?
@@ -54,37 +59,15 @@ module Alchemy
54
59
  attr_accessor :do_not_validate_language
55
60
 
56
61
  before_save :set_language_code, :unless => :systempage?
57
- before_save :inherit_restricted_status, if: -> { parent && parent.restricted? }, unless: :systempage?
58
- after_update :set_restrictions_to_child_pages, unless: :systempage?
62
+ before_save :set_restrictions_to_child_pages, :if => :restricted_changed?, :unless => :systempage?
63
+ before_save :inherit_restricted_status, :if => proc { parent && parent.restricted? }, :unless => :systempage?
59
64
  after_update :create_legacy_url, :if => :urlname_changed?, :unless => :redirects_to_external?
60
65
 
61
- scope :language_roots, where(:language_root => true)
62
- scope :layoutpages, where(:layoutpage => true)
63
- scope :all_locked, where(:locked => true)
64
- scope :all_locked_by, lambda { |user| where(:locked => true, :locked_by => user.id) }
65
- scope :not_locked, where(:locked => false)
66
- scope :visible, where(:visible => true)
67
- scope :published, where(:public => true)
68
- scope :not_restricted, where(:restricted => false)
69
- scope :restricted, where(:restricted => true)
70
- scope :public_language_roots, lambda {
71
- where(:language_root => true, :language_code => Language.all_codes_for_published, :public => true)
72
- }
73
- scope :all_last_edited_from, lambda { |user| where(:updater_id => user.id).order('updated_at DESC').limit(5) }
74
- # Returns all pages that have the given language_id
75
- scope :with_language, lambda { |language_id| where(:language_id => language_id) }
76
- scope :contentpages, where(:layoutpage => [false, nil]).where(Page.arel_table[:parent_id].not_eq(nil))
77
- # Returns all pages that are not locked and public.
78
- # Used for flushing all page caches at once.
79
- scope :flushables, not_locked.published.contentpages
80
- scope :searchables, not_restricted.published.contentpages
81
- # Scope for only the pages from Alchemy::Site.current
82
- scope :from_current_site, lambda { where(:alchemy_languages => {site_id: Site.current || Site.default}).joins(:language) }
83
- # TODO: add this as default_scope
84
- #default_scope { from_current_site }
85
-
86
66
  # Concerns
67
+ include Scopes
68
+ include Natures
87
69
  include Naming
70
+ include Users
88
71
  include Cells
89
72
  include Elements
90
73
 
@@ -101,26 +84,23 @@ module Alchemy
101
84
  self.language_roots.find_by_language_id(language_id)
102
85
  end
103
86
 
104
- # Creates a copy of source
87
+ # Creates a copy of given source.
105
88
  #
106
89
  # Also copies all elements included in source.
107
90
  #
108
91
  # === Note:
92
+ #
109
93
  # It prevents the element auto generator from running.
110
94
  #
111
95
  # @param source [Alchemy::Page]
96
+ # The source page the copy is taken from
112
97
  # @param differences [Hash]
98
+ # A optional hash with attributes that take precedence over the source attributes
113
99
  #
114
100
  # @return [Alchemy::Page]
115
101
  #
116
102
  def copy(source, differences = {})
117
- source.attributes.stringify_keys!
118
- differences.stringify_keys!
119
- attributes = source.attributes.merge(differences)
120
- attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY)
121
- new_name = differences['name'].present? ? differences['name'] : "#{source.name} (#{I18n.t('Copy')})"
122
- attributes.merge!('name' => new_name)
123
- page = self.new(attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY))
103
+ page = Alchemy::Page.new(attributes_from_source_for_copy(source, differences))
124
104
  page.tag_list = source.tag_list
125
105
  if page.save!
126
106
  copy_cells(source, page)
@@ -151,6 +131,19 @@ module Alchemy
151
131
  end
152
132
  end
153
133
 
134
+ def paste_from_clipboard(source, new_parent, new_name)
135
+ page = copy(source, {
136
+ parent_id: new_parent.id,
137
+ language: new_parent.language,
138
+ name: new_name,
139
+ title: new_name
140
+ })
141
+ if source.children.any?
142
+ source.copy_children_to(page)
143
+ end
144
+ return page
145
+ end
146
+
154
147
  def all_from_clipboard(clipboard)
155
148
  return [] if clipboard.blank?
156
149
  self.find_all_by_id(clipboard.collect { |i| i[:id] })
@@ -173,151 +166,101 @@ module Alchemy
173
166
  options
174
167
  end
175
168
 
176
- end
177
-
178
- # Instance methods
179
- #
180
-
181
- # Finds the previous page on the same structure level. Otherwise it returns nil.
182
- # Options:
183
- # => :restricted => boolean (standard: nil) - next restricted page (true), skip restricted pages (false), ignore restriction (nil)
184
- # => :public => boolean (standard: true) - next public page (true), skip public pages (false)
185
- def previous(options = {})
186
- next_or_previous(:previous, {
187
- :restricted => nil,
188
- :public => true
189
- }.merge(options))
190
- end
191
- alias_method :previous_page, :previous
192
-
193
- # Finds the next page on the same structure level. Otherwise it returns nil.
194
- # Options:
195
- # => :restricted => boolean (standard: nil) - next restricted page (true), skip restricted pages (false), ignore restriction (nil)
196
- # => :public => boolean (standard: true) - next public page (true), skip public pages (false)
197
- def next(options = {})
198
- next_or_previous(:next, {
199
- :restricted => nil,
200
- :public => true
201
- }.merge(options))
202
- end
203
- alias_method :next_page, :next
204
-
205
- def lock(user)
206
- self.locked = true
207
- self.locked_by = user.id
208
- self.save(:validate => false)
209
- end
210
-
211
- def unlock
212
- self.locked = false
213
- self.locked_by = nil
214
- self.do_not_sweep = true
215
- self.save
216
- end
217
-
218
- # Returns the name of the creator of this page.
219
- def creator
220
- @page_creator ||= User.find_by_id(creator_id)
221
- return I18n.t('unknown') if @page_creator.nil?
222
- @page_creator.name
223
- end
169
+ private
224
170
 
225
- # Returns the name of the last updater of this page.
226
- def updater
227
- @page_updater = User.find_by_id(updater_id)
228
- return I18n.t('unknown') if @page_updater.nil?
229
- @page_updater.name
230
- end
171
+ # Aggregates the attributes from given source for copy of page.
172
+ #
173
+ # @param [Alchemy::Page]
174
+ # The source page
175
+ # @param [Hash]
176
+ # A optional hash with attributes that take precedence over the source attributes
177
+ #
178
+ def attributes_from_source_for_copy(source, differences = {})
179
+ source.attributes.stringify_keys!
180
+ differences.stringify_keys!
181
+ attributes = source.attributes.merge(differences)
182
+ attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY)
183
+ attributes.merge!('name' => new_name_for_copy(differences['name'], source.name))
184
+ attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY)
185
+ end
231
186
 
232
- # Returns the name of the user currently editing this page.
233
- def current_editor
234
- @current_editor = User.find_by_id(locked_by)
235
- return I18n.t('unknown') if @current_editor.nil?
236
- @current_editor.name
237
- end
187
+ # Returns a new name for copy of page.
188
+ #
189
+ # If the differences hash includes a new name this is taken.
190
+ # Otherwise +source.name+
191
+ #
192
+ # @param [String]
193
+ # The differences hash that contains a new name
194
+ # @param [String]
195
+ # The name of the source
196
+ #
197
+ def new_name_for_copy(custom_name, source_name)
198
+ return custom_name if custom_name.present?
199
+ "#{source_name} (#{I18n.t('Copy')})"
200
+ end
238
201
 
239
- def locker
240
- User.find_by_id(self.locked_by)
241
202
  end
242
203
 
243
- def fold(user_id, status)
244
- folded_page = FoldedPage.find_or_create_by_user_id_and_page_id(user_id, self.id)
245
- folded_page.folded = status
246
- folded_page.save
247
- end
204
+ # Instance methods
205
+ #
248
206
 
249
- def folded?(user_id)
250
- folded_page = FoldedPage.find_by_user_id_and_page_id(user_id, self.id)
251
- return false if folded_page.nil?
252
- folded_page.folded
207
+ # Touches the timestamps and userstamps
208
+ def touch
209
+ Page.where(id: self.id).update_all(updated_at: Time.now, updater_id: User.stamper)
253
210
  end
254
211
 
255
- # Returns a Hash describing the status of the Page.
212
+ # Returns the previous page on the same level or nil.
213
+ #
214
+ # For options @see #next_or_previous
256
215
  #
257
- def status
258
- {
259
- visible: visible?,
260
- public: public?,
261
- locked: locked?,
262
- restricted: restricted?
263
- }
216
+ def previous(options = {})
217
+ next_or_previous('<', options)
264
218
  end
219
+ alias_method :previous_page, :previous
265
220
 
266
- # Returns the translated status for given status type.
221
+ # Returns the next page on the same level or nil.
267
222
  #
268
- # @param [Symbol] status_type
223
+ # For options @see #next_or_previous
269
224
  #
270
- def status_title(status_type)
271
- I18n.t(self.status[status_type].to_s, scope: "page_states.#{status_type}")
272
- end
273
-
274
- def has_controller?
275
- !PageLayout.get(self.page_layout).nil? && !PageLayout.get(self.page_layout)["controller"].blank?
225
+ def next(options = {})
226
+ next_or_previous('>', options)
276
227
  end
228
+ alias_method :next_page, :next
277
229
 
278
- def controller_and_action
279
- if self.has_controller?
280
- controller = self.layout_description["controller"].gsub(/^([^\/])/, "/#{$1}")
281
- {:controller => controller, :action => self.layout_description["action"]}
282
- end
230
+ # Locks the page to given user without updating the timestamps
231
+ #
232
+ def lock!(user)
233
+ # Yes, since +update_columns+ is not available in Rails 3.2,
234
+ # we use this workaround to update straight in the db.
235
+ Page.where(id: self.id).update_all(locked: true, locked_by: user.id)
283
236
  end
284
237
 
285
- # Returns the self#page_layout description from config/alchemy/page_layouts.yml file.
286
- def layout_description
287
- return {} if self.systempage?
288
- description = PageLayout.get(self.page_layout)
289
- if description.nil?
290
- raise PageLayoutDefinitionError, "Description could not be found for page layout named #{self.page_layout}. Please check page_layouts.yml file."
291
- else
292
- description
293
- end
238
+ # Unlocks the page without updating the timestamps
239
+ #
240
+ def unlock!
241
+ # Yes, since +update_columns+ is not available in Rails 3.2,
242
+ # we use this workaround to update straight in the db.
243
+ Page.where(id: self.id).update_all(locked: false, locked_by: nil)
294
244
  end
295
- alias_method :definition, :layout_description
296
245
 
297
- # Returns translated name of the pages page_layout value.
298
- # Page layout names are defined inside the config/alchemy/page_layouts.yml file.
299
- # Translate the name in your config/locales language yml file.
300
- def layout_display_name
301
- I18n.t(self.page_layout, :scope => :page_layout_names)
246
+ def fold!(user_id, status)
247
+ folded_page = folded_pages.find_or_create_by_user_id(user_id)
248
+ folded_page.folded = status
249
+ folded_page.save
302
250
  end
303
251
 
304
252
  def changed_publicity?
305
253
  self.public_was != self.public
306
254
  end
307
255
 
308
- # Sets my restricted value to all child pages
309
- #
310
256
  def set_restrictions_to_child_pages
311
- descendants.update_all(restricted: self.restricted?)
312
- end
313
-
314
- def contains_feed?
315
- definition["feed"]
257
+ descendants.each do |child|
258
+ child.update_attributes(:restricted => self.restricted?)
259
+ end
316
260
  end
317
261
 
318
- # Returns true or false if the pages layout_description for config/alchemy/page_layouts.yml contains redirects_to_external: true
319
- def redirects_to_external?
320
- !!definition["redirects_to_external"]
262
+ def inherit_restricted_status
263
+ self.restricted = parent.restricted?
321
264
  end
322
265
 
323
266
  # Returns the first published child
@@ -342,29 +285,6 @@ module Alchemy
342
285
  end
343
286
  end
344
287
 
345
- def locker_name
346
- return I18n.t('unknown') if self.locker.nil?
347
- self.locker.name
348
- end
349
-
350
- def rootpage?
351
- !self.new_record? && self.parent_id.blank?
352
- end
353
-
354
- def systempage?
355
- return true if Page.root.nil?
356
- rootpage? || (self.parent_id == Page.root.id && !self.language_root?)
357
- end
358
-
359
- # Overwrites the cache_key method.
360
- def cache_key(request = nil)
361
- "alchemy/pages/#{id}"
362
- end
363
-
364
- def taggable?
365
- definition['taggable'] == true
366
- end
367
-
368
288
  # Publishes the page
369
289
  #
370
290
  # Sets public true and saves the object.
@@ -373,43 +293,35 @@ module Alchemy
373
293
  self.save
374
294
  end
375
295
 
376
- # Updates an Alchemy::Page based on a new ordering to be applied to it
377
- #
378
- # Note: Page's urls should not be updated (and a legacy URL created) if nesting is OFF
379
- # or if a page is external or if the URL is the same
380
- #
381
- # @param [TreeNode]
382
- # A tree node with new lft, rgt, depth, url, parent_id and restricted indexes to be updated
383
- #
384
- def update_node!(node)
385
- hash = {lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted}
386
-
387
- if Config.get(:url_nesting) && !self.redirects_to_external? && self.urlname != node.url
388
- LegacyPageUrl.create(page_id: self.id, urlname: self.urlname)
389
- hash.merge!(urlname: node.url)
390
- end
391
-
392
- self.class.update_all(hash, {id: self.id})
296
+ def set_language_from_parent_or_default_language
297
+ self.language = self.parent.language || Language.get_default
298
+ set_language_code
393
299
  end
394
300
 
395
301
  private
396
302
 
397
- def next_or_previous(direction = :next, options = {})
398
- pages = self.class.scoped
399
- if direction == :previous
400
- step_direction = ["#{self.class.table_name}.lft < ?", self.lft]
401
- order_direction = "lft DESC"
402
- else
403
- step_direction = ["#{self.class.table_name}.lft > ?", self.lft]
404
- order_direction = "lft"
405
- end
406
- pages = pages.where(:public => options[:public])
407
- pages = pages.where(:parent_id => self.parent_id)
408
- pages = pages.where(step_direction)
409
- if !options[:restricted].nil?
410
- pages = pages.where(:restricted => options[:restricted])
411
- end
412
- pages.order(order_direction).limit(1).first
303
+ # Returns the next or previous page on the same level or nil.
304
+ #
305
+ # @param [String]
306
+ # Pass '>' for next and '<' for previous page.
307
+ #
308
+ # @option options [Boolean] :restricted (nil)
309
+ # only restricted pages (true), skip restricted pages (false)
310
+ # @option options [Boolean] :public (true)
311
+ # only public pages (true), skip public pages (false)
312
+ #
313
+ def next_or_previous(dir = '>', options = {})
314
+ options = {
315
+ restricted: false,
316
+ public: true
317
+ }.update(options)
318
+
319
+ self_and_siblings
320
+ .where(["#{self.class.table_name}.lft #{dir} ?", lft])
321
+ .where(public: options[:public])
322
+ .where(restricted: options[:restricted])
323
+ .order(dir == '>' ? 'lft' : 'lft DESC')
324
+ .limit(1).first
413
325
  end
414
326
 
415
327
  def set_language_code
@@ -422,11 +334,5 @@ module Alchemy
422
334
  legacy_urls.find_or_create_by_urlname(:urlname => urlname_was)
423
335
  end
424
336
 
425
- # Sets my restricted status to parent's restricted status
426
- #
427
- def inherit_restricted_status
428
- self.restricted = parent.restricted?
429
- end
430
-
431
337
  end
432
338
  end