alchemy_cms 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +82 -26
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +23 -1
  5. data/README.md +14 -5
  6. data/Rakefile +0 -1
  7. data/alchemy_cms.gemspec +2 -5
  8. data/app/assets/images/alchemy/alchemy-logo.png +0 -0
  9. data/app/assets/javascripts/alchemy/admin.js +1 -1
  10. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +4 -8
  11. data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +2 -2
  12. data/app/assets/javascripts/alchemy/alchemy.datepicker.js.coffee +18 -27
  13. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +3 -8
  15. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +0 -1
  17. data/app/assets/javascripts/alchemy/alchemy.page_sorter.js +22 -46
  18. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +1 -1
  19. data/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee +2 -2
  20. data/app/assets/javascripts/alchemy/alchemy.spinner.js +32 -0
  21. data/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee +1 -1
  22. data/app/assets/javascripts/alchemy/templates/index.js +1 -0
  23. data/app/assets/javascripts/alchemy/templates/spinner.hbs +7 -0
  24. data/app/assets/stylesheets/alchemy/_extends.scss +1 -0
  25. data/app/assets/stylesheets/alchemy/admin.scss +1 -0
  26. data/app/assets/stylesheets/alchemy/base.scss +1 -7
  27. data/app/assets/stylesheets/alchemy/buttons.scss +1 -5
  28. data/app/assets/stylesheets/alchemy/dialogs.scss +0 -4
  29. data/app/assets/stylesheets/alchemy/elements.scss +2 -6
  30. data/app/assets/stylesheets/alchemy/frame.scss +0 -5
  31. data/app/assets/stylesheets/alchemy/image_library.scss +0 -13
  32. data/app/assets/stylesheets/alchemy/sitemap.scss +5 -34
  33. data/app/assets/stylesheets/alchemy/spinner.scss +52 -0
  34. data/app/controllers/alchemy/admin/attachments_controller.rb +12 -11
  35. data/app/controllers/alchemy/admin/base_controller.rb +3 -7
  36. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +2 -1
  37. data/app/controllers/alchemy/admin/languages_controller.rb +3 -8
  38. data/app/controllers/alchemy/admin/pictures_controller.rb +11 -7
  39. data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
  40. data/app/controllers/alchemy/attachments_controller.rb +2 -0
  41. data/app/controllers/alchemy/base_controller.rb +4 -5
  42. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +1 -1
  43. data/app/helpers/alchemy/admin/base_helper.rb +17 -8
  44. data/app/helpers/alchemy/admin/tags_helper.rb +31 -18
  45. data/app/helpers/alchemy/base_helper.rb +1 -1
  46. data/app/helpers/alchemy/pages_helper.rb +4 -7
  47. data/app/models/alchemy/attachment.rb +4 -0
  48. data/app/models/alchemy/cell.rb +1 -1
  49. data/app/models/alchemy/element.rb +6 -12
  50. data/app/models/alchemy/element/definitions.rb +2 -2
  51. data/app/models/alchemy/element/element_contents.rb +1 -1
  52. data/app/models/alchemy/essence_picture_view.rb +14 -2
  53. data/app/models/alchemy/language.rb +4 -4
  54. data/app/models/alchemy/page.rb +25 -28
  55. data/app/models/alchemy/page/page_elements.rb +1 -1
  56. data/app/models/alchemy/page/page_natures.rb +1 -1
  57. data/app/models/alchemy/picture.rb +5 -1
  58. data/app/models/alchemy/site.rb +27 -12
  59. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +14 -13
  60. data/app/views/alchemy/admin/dashboard/info.html.erb +1 -1
  61. data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
  62. data/app/views/alchemy/admin/pages/_create_language_form.html.erb +1 -1
  63. data/app/views/alchemy/admin/pages/_form.html.erb +1 -1
  64. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +1 -1
  65. data/app/views/alchemy/admin/pages/_page.html.erb +1 -1
  66. data/app/views/alchemy/admin/pages/_sitemap.html.erb +2 -2
  67. data/app/views/alchemy/admin/pages/configure_external.html.erb +1 -1
  68. data/app/views/alchemy/admin/pages/index.html.erb +5 -7
  69. data/app/views/alchemy/admin/pages/sort.html.erb +19 -0
  70. data/app/views/alchemy/admin/pages/update.js.erb +1 -1
  71. data/app/views/alchemy/admin/pictures/_archive.html.erb +19 -19
  72. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +3 -4
  73. data/app/views/alchemy/admin/resources/_tag_list.html.erb +3 -4
  74. data/app/views/alchemy/admin/resources/edit.html.erb +1 -1
  75. data/app/views/alchemy/admin/resources/new.html.erb +1 -1
  76. data/app/views/alchemy/pages/_meta_data.html.erb +1 -1
  77. data/app/views/alchemy/pages/show.rss.builder +0 -2
  78. data/app/views/alchemy/welcome.html.erb +1 -1
  79. data/app/views/layouts/alchemy/admin.html.erb +1 -1
  80. data/config/locales/alchemy.de.yml +4 -4
  81. data/config/locales/alchemy.en.yml +4 -4
  82. data/config/locales/alchemy.es.yml +3 -3
  83. data/config/locales/alchemy.fr.yml +4 -4
  84. data/config/locales/alchemy.it.yml +3 -3
  85. data/config/locales/alchemy.nl.yml +4 -4
  86. data/config/locales/alchemy.ru.yml +3 -3
  87. data/lib/alchemy/auth_accessors.rb +6 -6
  88. data/lib/alchemy/cache_digests/template_tracker.rb +5 -5
  89. data/lib/alchemy/controller_actions.rb +1 -6
  90. data/lib/alchemy/engine.rb +0 -53
  91. data/lib/alchemy/errors.rb +12 -3
  92. data/lib/alchemy/i18n.rb +1 -1
  93. data/lib/alchemy/logger.rb +1 -1
  94. data/lib/alchemy/page_layout.rb +5 -5
  95. data/lib/alchemy/seeder.rb +16 -49
  96. data/lib/alchemy/tasks/helpers.rb +1 -1
  97. data/lib/alchemy/test_support/config_stubbing.rb +28 -0
  98. data/lib/alchemy/test_support/essence_shared_examples.rb +6 -6
  99. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  100. data/lib/alchemy/test_support/factories/page_factory.rb +7 -0
  101. data/lib/alchemy/test_support/factories/site_factory.rb +6 -0
  102. data/lib/alchemy/test_support/shared_contexts.rb +14 -0
  103. data/lib/alchemy/test_support/shared_uploader_examples.rb +10 -0
  104. data/lib/alchemy/touching.rb +1 -1
  105. data/lib/alchemy/version.rb +1 -1
  106. data/lib/alchemy_cms.rb +56 -1
  107. data/lib/{alchemy/kaminari → kaminari}/scoped_pagination_url_helper.rb +0 -0
  108. data/lib/rails/generators/alchemy/base.rb +1 -1
  109. data/lib/rails/generators/alchemy/elements/elements_generator.rb +2 -1
  110. data/lib/rails/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -1
  111. data/lib/rails/generators/alchemy/site_layouts/site_layouts_generator.rb +2 -1
  112. data/lib/tasks/alchemy/tidy.rake +91 -89
  113. data/lib/tasks/alchemy/upgrade.rake +15 -15
  114. metadata +29 -14
  115. data/app/assets/javascripts/alchemy/alchemy.spinner.js.coffee +0 -49
  116. data/app/views/alchemy/admin/pages/sort.js.erb +0 -4
  117. data/vendor/assets/javascripts/handlebars.js +0 -4608
  118. data/vendor/assets/javascripts/spin.min.js +0 -1
@@ -44,11 +44,7 @@ module Alchemy
44
44
  else
45
45
  render_errors_or_redirect(
46
46
  @attachment,
47
- admin_attachments_path(
48
- per_page: params[:per_page],
49
- page: params[:page],
50
- q: params[:q]
51
- ),
47
+ admin_attachments_path(search_params),
52
48
  Alchemy.t("File successfully updated")
53
49
  )
54
50
  end
@@ -57,11 +53,7 @@ module Alchemy
57
53
  def destroy
58
54
  name = @attachment.name
59
55
  @attachment.destroy
60
- @url = admin_attachments_url(
61
- per_page: params[:per_page],
62
- page: params[:page],
63
- q: params[:q]
64
- )
56
+ @url = admin_attachments_url(search_params)
65
57
  flash[:notice] = Alchemy.t('File deleted successfully', name: name)
66
58
  end
67
59
 
@@ -75,9 +67,18 @@ module Alchemy
75
67
 
76
68
  private
77
69
 
70
+ def search_params
71
+ params.except(:attachment, :id).permit(
72
+ :file_type,
73
+ :page,
74
+ {q: resource_handler.search_field_name},
75
+ :tagged_with
76
+ )
77
+ end
78
+
78
79
  def handle_uploader_response(status:)
79
80
  if @attachment.valid?
80
- render succesful_uploader_response(file: @attachment, status: status)
81
+ render successful_uploader_response(file: @attachment, status: status)
81
82
  else
82
83
  render failed_uploader_response(file: @attachment)
83
84
  end
@@ -138,17 +138,13 @@ module Alchemy
138
138
  #
139
139
  def options_from_params
140
140
  case params[:options]
141
- when ''
141
+ when '', nil
142
142
  {}
143
143
  when String
144
144
  JSON.parse(params[:options])
145
- when Hash
146
- params[:options]
147
- when Array
148
- params[:options]
149
145
  else
150
- {}
151
- end.symbolize_keys
146
+ params[:options].permit!.to_h
147
+ end.deep_symbolize_keys
152
148
  end
153
149
 
154
150
  # This method decides if we want to raise an exception or not.
@@ -1,6 +1,7 @@
1
1
  module Alchemy
2
2
  module Admin
3
3
  class EssencePicturesController < Alchemy::Admin::BaseController
4
+ FLOAT_REGEX = /\A\d+(\.\d+)?\z/
4
5
  authorize_resource class: Alchemy::EssencePicture
5
6
 
6
7
  before_action :load_essence_picture, only: [:edit, :crop, :update]
@@ -92,7 +93,7 @@ module Alchemy
92
93
  # aspect ratio, don't specify a size or only width or height.
93
94
  #
94
95
  def ratio_from_size_or_params
95
- if @min_size.value?(0) && @options[:fixed_ratio]
96
+ if @min_size.value?(0) && @options[:fixed_ratio].to_s =~ FLOAT_REGEX
96
97
  @options[:fixed_ratio].to_f
97
98
  elsif !@min_size[:width].zero? && !@min_size[:height].zero?
98
99
  @min_size[:width].to_f / @min_size[:height].to_f
@@ -7,14 +7,9 @@ module Alchemy
7
7
  end
8
8
 
9
9
  def new
10
- @language = Language.new
11
- @language.page_layout = configured_page_layout || @language.page_layout
12
- end
13
-
14
- private
15
-
16
- def configured_page_layout
17
- Config.get(:default_language).try('[]', 'page_layout')
10
+ @language = Language.new(
11
+ page_layout: Config.get(:default_language)['page_layout']
12
+ )
18
13
  end
19
14
  end
20
15
  end
@@ -31,7 +31,7 @@ module Alchemy
31
31
  @picture = Picture.new(picture_params)
32
32
  @picture.name = @picture.humanized_name
33
33
  if @picture.save
34
- render succesful_uploader_response(file: @picture)
34
+ render successful_uploader_response(file: @picture)
35
35
  else
36
36
  render failed_uploader_response(file: @picture)
37
37
  end
@@ -136,12 +136,16 @@ module Alchemy
136
136
  end
137
137
 
138
138
  def redirect_to_index
139
- do_redirect_to admin_pictures_path(
140
- filter: params[:filter].presence,
141
- page: params[:page].presence,
142
- q: params[:q].presence,
143
- size: params[:size].presence,
144
- tagged_with: params[:tagged_with].presence
139
+ do_redirect_to admin_pictures_path(search_params)
140
+ end
141
+
142
+ def search_params
143
+ params.except(:id, :picture_ids).permit(
144
+ :filter,
145
+ :page,
146
+ {q: resource_handler.search_field_name},
147
+ :size,
148
+ :tagged_with
145
149
  )
146
150
  end
147
151
 
@@ -97,7 +97,7 @@ module Alchemy
97
97
  when :destroy
98
98
  verb = "removed"
99
99
  end
100
- flash[:notice] = Alchemy.t("#{resource_handler.resource_name.classify} successfully #{verb}", default: Alchemy.t("Succesfully #{verb}"))
100
+ flash[:notice] = Alchemy.t("#{resource_handler.resource_name.classify} successfully #{verb}", default: Alchemy.t("Successfully #{verb}"))
101
101
  end
102
102
 
103
103
  def is_alchemy_module?
@@ -5,6 +5,7 @@ module Alchemy
5
5
 
6
6
  # sends file inline. i.e. for viewing pdfs/movies in browser
7
7
  def show
8
+ response.headers['Content-Length'] = @attachment.file.size.to_s
8
9
  send_file(
9
10
  @attachment.file.path,
10
11
  {
@@ -17,6 +18,7 @@ module Alchemy
17
18
 
18
19
  # sends file as attachment. aka download
19
20
  def download
21
+ response.headers['Content-Length'] = @attachment.file.size.to_s
20
22
  send_file(
21
23
  @attachment.file.path, {
22
24
  filename: @attachment.file_name,
@@ -51,11 +51,10 @@ module Alchemy
51
51
 
52
52
  def permission_denied(exception = nil)
53
53
  if exception
54
- Rails.logger.debug <<-WARN
55
-
56
- /!\\ Failed to permit #{exception.action} on #{exception.subject.inspect} for:
57
- #{current_alchemy_user.inspect}
58
- WARN
54
+ Rails.logger.debug <<-WARN.strip_heredoc
55
+ /!\\ Failed to permit #{exception.action} on #{exception.subject.inspect} for:
56
+ #{current_alchemy_user.inspect}
57
+ WARN
59
58
  end
60
59
  if current_alchemy_user
61
60
  handle_redirect_for_user
@@ -3,7 +3,7 @@ module Alchemy
3
3
  module UploaderResponses
4
4
  extend ActiveSupport::Concern
5
5
 
6
- def succesful_uploader_response(file:, status: :created)
6
+ def successful_uploader_response(file:, status: :created)
7
7
  message = Alchemy.t(:upload_success,
8
8
  scope: [:uploader, file.class.model_name.i18n_key],
9
9
  name: file.name
@@ -312,9 +312,9 @@ module Alchemy
312
312
  }
313
313
  options = defaults.merge(options)
314
314
  content_for(:toolbar) do
315
- content = <<-CONTENT
316
- #{options[:buttons].map { |button_options| toolbar_button(button_options) }.join}
317
- #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]}
315
+ content = <<-CONTENT.strip_heredoc
316
+ #{options[:buttons].map { |button_options| toolbar_button(button_options) }.join}
317
+ #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]}
318
318
  CONTENT
319
319
  content.html_safe
320
320
  end
@@ -332,13 +332,18 @@ module Alchemy
332
332
 
333
333
  # Renders a textfield ready to display a datepicker
334
334
  #
335
- # Uses a HTML5 +<input type="date">+ field.
336
335
  # A Javascript observer converts this into a fancy Datepicker.
337
336
  # If you pass +'datetime'+ as +:type+ the datepicker will also have a Time select.
337
+ # If you pass +'time'+ as +:type+ the datepicker will only have a Time select.
338
338
  #
339
- # The date value gets localized via +I18n.l+. The format on Time and Date is +datepicker+
339
+ # The date value gets localized via +I18n.l+. The format on Time and Date is +datepicker+, +timepicker+
340
340
  # or +datetimepicker+, if you pass another +type+.
341
341
  #
342
+ # This helper always renders "text" as input type because:
343
+ # HTML5 supports input types like 'date' but Browsers are using the users OS settings
344
+ # to validate the input format. Since Alchemy is localized in the backend the date formats
345
+ # should be aligned with the users locale setting in the backend but not the OS settings.
346
+ #
342
347
  # === Date Example
343
348
  #
344
349
  # <%= alchemy_datepicker(@person, :birthday) %>
@@ -347,13 +352,17 @@ module Alchemy
347
352
  #
348
353
  # <%= alchemy_datepicker(@page, :public_on, type: 'datetime') %>
349
354
  #
355
+ # === Time Example
356
+ #
357
+ # <%= alchemy_datepicker(@meeting, :starts_at, type: 'time') %>
358
+ #
350
359
  # @param [ActiveModel::Base] object
351
360
  # An instance of a model
352
361
  # @param [String or Symbol] method
353
362
  # The attribute method to be called for the date value
354
363
  #
355
- # @option html_options [String] :type (date)
356
- # The type of text field
364
+ # @option html_options [String] :data-datepicker-type (type)
365
+ # The value of the data attribute for the type
357
366
  # @option html_options [String] :class (type)
358
367
  # CSS classes of the input field
359
368
  # @option html_options [String] :value (value of method on object)
@@ -366,7 +375,7 @@ module Alchemy
366
375
  value = date ? l(date, format: "#{type}picker".to_sym) : nil
367
376
 
368
377
  text_field object.class.name.demodulize.underscore.to_sym,
369
- method.to_sym, {type: type, class: type, value: value}.merge(html_options)
378
+ method.to_sym, {type: "text", class: type, "data-datepicker-type" => type, value: value}.merge(html_options)
370
379
  end
371
380
 
372
381
  # Merges the params-hash with the given hash
@@ -9,16 +9,16 @@ module Alchemy
9
9
  # @return [String]
10
10
  # A HTML string containing <tt><li></tt> tags
11
11
  #
12
- def render_tag_list(class_name, params)
12
+ def render_tag_list(class_name)
13
13
  raise ArgumentError, 'Please provide a String as class_name' if class_name.nil?
14
14
  li_s = []
15
15
  class_name.constantize.tag_counts.sort { |x, y| x.name.downcase <=> y.name.downcase }.each do |tag|
16
16
  tags = filtered_by_tag?(tag) ? tag_filter(remove: tag) : tag_filter(add: tag)
17
- li_s << content_tag('li', name: tag.name, class: tag_list_tag_active?(tag, params) ? 'active' : nil) do
17
+ li_s << content_tag('li', name: tag.name, class: tag_list_tag_active?(tag) ? 'active' : nil) do
18
18
  link_to(
19
19
  "#{tag.name} (#{tag.count})",
20
20
  url_for(
21
- params.delete_if { |k, _v| k == "page" }.merge(
21
+ tag_list_params.reject { |k, _v| k == "page" }.merge(
22
22
  action: 'index',
23
23
  tagged_with: tags
24
24
  )
@@ -39,14 +39,14 @@ module Alchemy
39
39
  # url params
40
40
  # @return [Boolean]
41
41
  #
42
- def tag_list_tag_active?(tag, params)
43
- params[:tagged_with].to_s.split(',').include?(tag.name)
42
+ def tag_list_tag_active?(tag)
43
+ tag_list_params[:tagged_with].to_s.split(',').include?(tag.name)
44
44
  end
45
45
 
46
46
  # Checks if the tagged_with param contains the given tag
47
47
  def filtered_by_tag?(tag)
48
- if params[:tagged_with].present?
49
- tags = params[:tagged_with].split(',')
48
+ if tag_list_params[:tagged_with].present?
49
+ tags = tag_list_params[:tagged_with].split(',')
50
50
  tags.include?(tag.name)
51
51
  else
52
52
  false
@@ -55,8 +55,8 @@ module Alchemy
55
55
 
56
56
  # Adds the given tag to the tag filter.
57
57
  def add_to_tag_filter(tag)
58
- if params[:tagged_with].present?
59
- tags = params[:tagged_with].split(',')
58
+ if tag_list_params[:tagged_with].present?
59
+ tags = tag_list_params[:tagged_with].split(',')
60
60
  tags << tag.name
61
61
  else
62
62
  [tag.name]
@@ -65,8 +65,8 @@ module Alchemy
65
65
 
66
66
  # Removes the given tag from the tag filter.
67
67
  def remove_from_tag_filter(tag)
68
- if params[:tagged_with].present?
69
- tags = params[:tagged_with].split(',')
68
+ if tag_list_params[:tagged_with].present?
69
+ tags = tag_list_params[:tagged_with].split(',')
70
70
  tags.delete_if { |t| t == tag.name }
71
71
  else
72
72
  []
@@ -84,17 +84,30 @@ module Alchemy
84
84
  # ** :remove (ActsAsTaggableOn::Tag) - The tag that should be removed from the tag-filter
85
85
  #
86
86
  def tag_filter(options = {})
87
- case
88
- when options[:add]
89
- taglist = add_to_tag_filter(options[:add]) if options[:add]
90
- when options[:remove]
91
- taglist = remove_from_tag_filter(options[:remove]) if options[:remove]
92
- else
93
- return params[:tagged_with]
87
+ if options[:add]
88
+ taglist = add_to_tag_filter(options[:add])
89
+ elsif options[:remove]
90
+ taglist = remove_from_tag_filter(options[:remove])
91
+ else
92
+ return tag_list_params[:tagged_with]
94
93
  end
95
94
  return nil if taglist.blank?
96
95
  taglist.uniq.join(',')
97
96
  end
97
+
98
+ def tag_list_params
99
+ params.permit(
100
+ :controller,
101
+ :content_id,
102
+ :element_id,
103
+ :options,
104
+ :swap,
105
+ :use_route,
106
+ :tagged_with,
107
+ :filter,
108
+ q: params.fetch(:q, {}).keys
109
+ )
110
+ end
98
111
  end
99
112
  end
100
113
  end
@@ -14,7 +14,7 @@ module Alchemy
14
14
  # Logs a message in the Rails logger (warn level)
15
15
  # and optionally displays an error message to the user.
16
16
  def warning(message, text = nil)
17
- Logger.warn(message, caller.first)
17
+ Logger.warn(message, caller(0..0))
18
18
  unless text.nil?
19
19
  warning = content_tag('p', class: 'content_editor_error') do
20
20
  render_icon('warning') + text
@@ -166,7 +166,7 @@ module Alchemy
166
166
  pages = page.children.accessible_by(current_ability, :see)
167
167
  pages = pages.restricted if options.delete(:restricted_only)
168
168
  if depth = options[:deepness]
169
- pages = pages.where("#{Page.table_name}.depth <= #{depth}")
169
+ pages = pages.where('depth <= ?', depth)
170
170
  end
171
171
  if options[:reverse]
172
172
  pages.reverse!
@@ -246,15 +246,12 @@ module Alchemy
246
246
  end
247
247
 
248
248
  if options.delete(:reverse)
249
- pages.to_a.reverse!
249
+ pages = pages.reorder('lft DESC')
250
250
  end
251
251
 
252
252
  if options[:without].present?
253
- if options[:without].class == Array
254
- pages = pages.to_a - options[:without]
255
- else
256
- pages.to_a.delete(options[:without])
257
- end
253
+ without = options.delete(:without)
254
+ pages = pages.where.not(id: without.try(:collect, &:id) || without.id)
258
255
  end
259
256
 
260
257
  render 'alchemy/breadcrumb/wrapper', pages: pages, options: options
@@ -36,6 +36,10 @@ module Alchemy
36
36
 
37
37
  # We need to define this method here to have it available in the validations below.
38
38
  class << self
39
+ def searchable_alchemy_resource_attributes
40
+ %w(name file_name)
41
+ end
42
+
39
43
  def allowed_filetypes
40
44
  Config.get(:uploader).fetch('allowed_filetypes', {}).fetch('alchemy/attachments', [])
41
45
  end
@@ -56,7 +56,7 @@ module Alchemy
56
56
  private
57
57
 
58
58
  def read_yml_file
59
- ::YAML.load(ERB.new(File.read(yml_file_path)).result) || []
59
+ ::YAML.safe_load(ERB.new(File.read(yml_file_path)).result, [], [], true) || []
60
60
  end
61
61
 
62
62
  def yml_file_path
@@ -211,7 +211,8 @@ module Alchemy
211
211
  # Pass an element name to get next of this kind.
212
212
  #
213
213
  def next(name = nil)
214
- previous_or_next('>', name)
214
+ elements = page.elements.published.where('position > ?', position)
215
+ select_element(elements, name, :asc)
215
216
  end
216
217
 
217
218
  # Returns previous public element from same page.
@@ -219,7 +220,8 @@ module Alchemy
219
220
  # Pass an element name to get previous of this kind.
220
221
  #
221
222
  def prev(name = nil)
222
- previous_or_next('<', name)
223
+ elements = page.elements.published.where('position < ?', position)
224
+ select_element(elements, name, :desc)
223
225
  end
224
226
 
225
227
  # Stores the page into +touchable_pages+ (Pages that have to be touched after updating the element).
@@ -313,17 +315,9 @@ module Alchemy
313
315
 
314
316
  private
315
317
 
316
- # Returns previous or next public element from same page.
317
- #
318
- # @param [String]
319
- # Pass '>' or '<' to find next or previous public element.
320
- # @param [String]
321
- # Pass an element name to get previous of this kind.
322
- #
323
- def previous_or_next(dir, name = nil)
324
- elements = page.elements.published.where("#{self.class.table_name}.position #{dir} #{position}")
318
+ def select_element(elements, name, order)
325
319
  elements = elements.named(name) if name.present?
326
- elements.reorder("position #{dir == '>' ? 'ASC' : 'DESC'}").limit(1).first
320
+ elements.reorder(position: order).limit(1).first
327
321
  end
328
322
 
329
323
  # Returns all cells from given page this element could be placed in.
@@ -26,7 +26,7 @@ module Alchemy
26
26
  #
27
27
  def read_definitions_file
28
28
  if ::File.exist?(definitions_file_path)
29
- ::YAML.load(ERB.new(File.read(definitions_file_path)).result) || []
29
+ ::YAML.safe_load(ERB.new(File.read(definitions_file_path)).result, [Regexp, Date], [], true) || []
30
30
  else
31
31
  raise LoadError, "Could not find elements.yml file! Please run `rails generate alchemy:scaffold`"
32
32
  end
@@ -46,7 +46,7 @@ module Alchemy
46
46
  definition
47
47
  else
48
48
  log_warning "Could not find element definition for #{name}. Please check your elements.yml file!"
49
- return {}
49
+ {}
50
50
  end
51
51
  end
52
52
  end