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,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