alchemy_cms 6.0.0 → 6.0.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +24 -1
  3. data/CHANGELOG.md +31 -0
  4. data/Gemfile +15 -1
  5. data/README.md +4 -3
  6. data/app/controllers/alchemy/admin/elements_controller.rb +18 -11
  7. data/app/controllers/alchemy/admin/pages_controller.rb +9 -4
  8. data/app/controllers/alchemy/api/base_controller.rb +4 -3
  9. data/app/controllers/alchemy/api/nodes_controller.rb +1 -0
  10. data/app/controllers/alchemy/base_controller.rb +7 -0
  11. data/app/controllers/alchemy/messages_controller.rb +0 -3
  12. data/app/controllers/alchemy/pages_controller.rb +0 -7
  13. data/app/helpers/alchemy/elements_helper.rb +17 -12
  14. data/app/models/alchemy/content/factory.rb +112 -110
  15. data/app/models/alchemy/element.rb +13 -6
  16. data/app/models/alchemy/ingredient.rb +2 -0
  17. data/app/models/alchemy/ingredient_validator.rb +1 -1
  18. data/app/models/alchemy/language.rb +1 -1
  19. data/app/models/alchemy/page/page_elements.rb +2 -2
  20. data/app/models/alchemy/page/page_naming.rb +1 -1
  21. data/app/models/alchemy/page.rb +1 -1
  22. data/app/models/alchemy/picture/transformations.rb +2 -2
  23. data/app/models/alchemy/picture_variant.rb +3 -1
  24. data/app/models/alchemy/site.rb +1 -1
  25. data/app/views/alchemy/admin/clipboard/insert.js.erb +13 -0
  26. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +27 -0
  27. data/app/views/alchemy/admin/elements/_element.html.erb +1 -23
  28. data/app/views/alchemy/admin/elements/_form.html.erb +5 -1
  29. data/app/views/alchemy/essences/_essence_node_editor.html.erb +1 -1
  30. data/config/initializers/dragonfly.rb +2 -0
  31. data/lib/alchemy/config.rb +5 -1
  32. data/lib/alchemy/controller_actions.rb +2 -1
  33. data/lib/alchemy/dragonfly/processors/thumbnail.rb +27 -0
  34. data/lib/alchemy/element_definition.rb +2 -3
  35. data/lib/alchemy/elements_finder.rb +1 -2
  36. data/lib/alchemy/essence.rb +1 -1
  37. data/lib/alchemy/page_layout.rb +5 -1
  38. data/lib/alchemy/permissions.rb +2 -2
  39. data/lib/alchemy/version.rb +1 -1
  40. data/lib/alchemy.rb +2 -4
  41. data/lib/generators/alchemy/base.rb +7 -3
  42. data/lib/generators/alchemy/install/install_generator.rb +4 -1
  43. data/package.json +1 -1
  44. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1db98328d1be1d96ee705e68f64b757f33d0997e4f622626ca86e4e42511079
4
- data.tar.gz: 563c9f02b864e170ac88a9d3f405c61b46da38fec41d0f4dfba0ff0c179407ba
3
+ metadata.gz: 2114b22cab5252062e853e2b3ea4e2d30ca37f10d1cc49a321644495af52ac3d
4
+ data.tar.gz: 58ece5a7be8ea335944ac21c4004bcd917b88860bef2337fbbe6fa705242651d
5
5
  SHA512:
6
- metadata.gz: 524ab50512e3a7c377f3619f20c6ec812e1c12a933b94fb56d8bbc5b94b843cd9708451b77b96af8be7776bb0c733c90af549da76820109a0163a0338b8599dc
7
- data.tar.gz: 7c8346e5b07d35e2ef471cb90aff4fe8e62e2050ef13d03436cb2492e0cb5a6d9a2003eefa0f4f55d0a61cfdcbda48596788956908964c71333ca0ebdfa1565d
6
+ metadata.gz: 5c604a56a67a92d16c53e10bd4264394a81a7f6c57fb2fec160ff27f4c70711b4231f7347bf977e2556da43761b3e81a32b2ab090cd9c48d7be0624024fd267a
7
+ data.tar.gz: 07b5bde2ada318c77aa0b20d89e96b128da675f07d779c44c28d8dfebc292f7cde24971606958ae3fa3e9d7a9f248358097284efda5f65a6fc5af84107276c00
@@ -16,16 +16,30 @@ jobs:
16
16
  - "2.6"
17
17
  - "2.7"
18
18
  - "3.0"
19
+ - "3.1"
19
20
  database:
20
21
  - mysql
21
22
  - postgresql
23
+ - mariadb
22
24
  exclude:
25
+ - rails: "6.0"
26
+ ruby: "3.1"
27
+ database: mysql
28
+ - rails: "6.0"
29
+ ruby: "3.1"
30
+ database: postgresql
31
+ - rails: "6.0"
32
+ ruby: "3.1"
33
+ database: mariadb
23
34
  - rails: "7.0"
24
35
  ruby: "2.6"
25
36
  database: mysql
26
37
  - rails: "7.0"
27
38
  ruby: "2.6"
28
39
  database: postgresql
40
+ - rails: "7.0"
41
+ ruby: "2.6"
42
+ database: mariadb
29
43
  env:
30
44
  DB: ${{ matrix.database }}
31
45
  DB_USER: alchemy_user
@@ -51,6 +65,15 @@ jobs:
51
65
  MYSQL_DATABASE: alchemy_cms_dummy_test
52
66
  MYSQL_ROOT_PASSWORD: password
53
67
  options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
68
+ mariadb:
69
+ image: mariadb:latest
70
+ ports: ["3307:3306"]
71
+ env:
72
+ MARIADB_USER: alchemy_user
73
+ MARIADB_PASSWORD: password
74
+ MARIADB_DATABASE: alchemy_cms_dummy_test
75
+ MARIADB_ROOT_PASSWORD: password
76
+ options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5
54
77
  steps:
55
78
  - uses: actions/checkout@v2.3.4
56
79
  - name: Set up Ruby
@@ -74,7 +97,7 @@ jobs:
74
97
  sudo apt install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache"
75
98
  sudo chown -R runner /home/runner/apt/cache
76
99
  - name: Install MySQL headers
77
- if: matrix.database == 'mysql'
100
+ if: matrix.database == 'mysql' || matrix.database == 'mariadb'
78
101
  run: |
79
102
  mkdir -p /home/runner/apt/cache
80
103
  sudo apt update -qq
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## 6.0.3 (2022-05-02)
2
+
3
+ - Add Support for MariaDB [#2326](https://github.com/AlchemyCMS/alchemy_cms/pull/2326) ([mamhoff](https://github.com/mamhoff))
4
+ - Fix Alchemy::Content::Factory module definition [#2325](https://github.com/AlchemyCMS/alchemy_cms/pull/2325) ([tvdeyen](https://github.com/tvdeyen))
5
+
6
+ ## 6.0.2 (2022-04-27)
7
+
8
+ - Remove JSON decode from ingredient data store [#2323](https://github.com/AlchemyCMS/alchemy_cms/pull/2323) ([tvdeyen](https://github.com/tvdeyen))
9
+ - Eagerload at the controller or job layer [#2313](https://github.com/AlchemyCMS/alchemy_cms/pull/2313) ([mamhoff](https://github.com/mamhoff))
10
+
11
+ ## 6.0.1 (2022-04-26)
12
+
13
+ - Allow passing a different partial to `render_element` [#2322](https://github.com/AlchemyCMS/alchemy_cms/pull/2322) ([mamhoff](https://github.com/mamhoff))
14
+ - Allow render_elements to take a block [#2321](https://github.com/AlchemyCMS/alchemy_cms/pull/2321) ([mamhoff](https://github.com/mamhoff))
15
+ - Remove old unused root_page ivar [#2320](https://github.com/AlchemyCMS/alchemy_cms/pull/2320) ([tvdeyen](https://github.com/tvdeyen))
16
+ - Raise on non-existing locale [#2319](https://github.com/AlchemyCMS/alchemy_cms/pull/2319) ([mamhoff](https://github.com/mamhoff))
17
+ - chore: Remove unnecessary puts from spec [#2318](https://github.com/AlchemyCMS/alchemy_cms/pull/2318) ([tvdeyen](https://github.com/tvdeyen))
18
+ - Fix gif resizing [#2315](https://github.com/AlchemyCMS/alchemy_cms/pull/2315) ([kulturbande](https://github.com/kulturbande))
19
+ - Refactor: Use page version's element repository rather than a new one [#2312](https://github.com/AlchemyCMS/alchemy_cms/pull/2312) ([mamhoff](https://github.com/mamhoff))
20
+ - Deprecate Alchemy::Element.available [#2309](https://github.com/AlchemyCMS/alchemy_cms/pull/2309) ([mamhoff](https://github.com/mamhoff))
21
+ - Explicitly set "store" for MariaDB [#2308](https://github.com/AlchemyCMS/alchemy_cms/pull/2308) ([mamhoff](https://github.com/mamhoff))
22
+ - Set preview mode earlier [#2307](https://github.com/AlchemyCMS/alchemy_cms/pull/2307) ([mamhoff](https://github.com/mamhoff))
23
+ - Fix updating the public_on date on persisted pages [#2305](https://github.com/AlchemyCMS/alchemy_cms/pull/2305) ([mamhoff](https://github.com/mamhoff))
24
+ - Allow opting out of Turbolinks in non-Alchemy controllers [#2302](https://github.com/AlchemyCMS/alchemy_cms/pull/2302) ([dssjoblom](https://github.com/dssjoblom))
25
+ - Explicitly validate uniqueness without case sensitivity [#2300](https://github.com/AlchemyCMS/alchemy_cms/pull/2300) ([mamhoff](https://github.com/mamhoff))
26
+ - Fix frozen string error when mixing template engines [#2299](https://github.com/AlchemyCMS/alchemy_cms/pull/2299) ([gr8bit](https://github.com/gr8bit))
27
+ - Do not flatten gifs if converted to webp [#2293](https://github.com/AlchemyCMS/alchemy_cms/pull/2293) ([tvdeyen](https://github.com/tvdeyen))
28
+ - Allow pasting into parent element with only one nested element type [#2292](https://github.com/AlchemyCMS/alchemy_cms/pull/2292) ([dbwinger](https://github.com/dbwinger))
29
+ - Restrict Node select to the site/language of the page being edited [#2277](https://github.com/AlchemyCMS/alchemy_cms/pull/2277) ([dbwinger](https://github.com/dbwinger))
30
+ - Add Ruby 3.1 support [#2229](https://github.com/AlchemyCMS/alchemy_cms/pull/2229) ([tvdeyen](https://github.com/tvdeyen))
31
+
1
32
  ## 6.0.0 (2022-04-11)
2
33
 
3
34
  - [ruby - main] Allow ransack version 3.0.1 [#2287](https://github.com/AlchemyCMS/alchemy_cms/pull/2287) ([depfu](https://github.com/apps/depfu))
data/Gemfile CHANGED
@@ -17,7 +17,9 @@ end
17
17
  if ENV["DB"].nil? || ENV["DB"] == "sqlite"
18
18
  gem "sqlite3", "~> 1.4.1"
19
19
  end
20
- gem "mysql2", "~> 0.5.1" if ENV["DB"] == "mysql"
20
+ if ENV["DB"] == "mysql" || ENV["DB"] == "mariadb"
21
+ gem "mysql2", "~> 0.5.1"
22
+ end
21
23
  gem "pg", "~> 1.0" if ENV["DB"] == "postgresql"
22
24
 
23
25
  group :development, :test do
@@ -48,3 +50,15 @@ group :development, :test do
48
50
  gem "brakeman", require: false
49
51
  end
50
52
  end
53
+
54
+ # Ruby 3.1 split out the net-smtp gem
55
+ # Necessary until https://github.com/mikel/mail/pull/1439
56
+ # got merged and released.
57
+ if Gem.ruby_version >= Gem::Version.new("3.1.0")
58
+ if rails_version.to_s.match?(/6.1/)
59
+ # Rails 6.1 needs this as well
60
+ gem "net-pop", "~> 0.1.0", require: false
61
+ gem "net-imap", "~> 0.2.0", require: false
62
+ end
63
+ gem "net-smtp", "~> 0.3.0", require: false
64
+ end
data/README.md CHANGED
@@ -18,7 +18,7 @@ Alchemy is an open source CMS engine written in Ruby on Rails.
18
18
 
19
19
  Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [guidelines](https://guides.alchemy-cms.com).
20
20
 
21
- **CAUTION: This main branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (5.2-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/5.2-stable).**
21
+ **CAUTION: This main branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (6.0-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/6.0-stable).**
22
22
 
23
23
 
24
24
  ## ✅ Features
@@ -27,6 +27,7 @@ Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [gu
27
27
  - A rich RESTful API
28
28
  - Intuitive admin interface with live preview
29
29
  - Multi language and multi domain
30
+ - Page versioning
30
31
  - SEO friendly urls
31
32
  - User Access Control
32
33
  - Build in contact form mailer
@@ -51,9 +52,9 @@ or visit the existing demo at https://alchemy-demo.herokuapp.com
51
52
 
52
53
  ## 🚂 Rails Version
53
54
 
54
- **This version of AlchemyCMS runs with Rails 6.0**
55
+ **This version of AlchemyCMS runs with Rails 7.0, 6.1 and 6.0**
55
56
 
56
- * For a Rails 5.2 compatible version use the [`5.2-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/5.2-stable).
57
+ * For a Rails 5.2 compatible version use the [`5.3-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/5.3-stable).
57
58
  * For a Rails 5.0 or 5.1 compatible version use the [`4.5-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.5-stable).
58
59
  * For a Rails 4.2 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable).
59
60
  * For a Rails 4.0/4.1 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable).
@@ -12,6 +12,7 @@ module Alchemy
12
12
  elements = @page_version.elements.order(:position).includes(*element_includes)
13
13
  @elements = elements.not_nested.unfixed
14
14
  @fixed_elements = elements.not_nested.fixed
15
+ load_clipboard_items
15
16
  end
16
17
 
17
18
  def new
@@ -20,8 +21,7 @@ module Alchemy
20
21
  @parent_element = Element.find_by(id: params[:parent_element_id])
21
22
  @elements = @page.available_elements_within_current_scope(@parent_element)
22
23
  @element = @page_version.elements.build
23
- @clipboard = get_clipboard("elements")
24
- @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
24
+ load_clipboard_items
25
25
  end
26
26
 
27
27
  # Creates a element as discribed in config/alchemy/elements.yml on page via AJAX.
@@ -44,8 +44,7 @@ module Alchemy
44
44
  else
45
45
  @element.page_version = @page_version
46
46
  @elements = @page.available_element_definitions
47
- @clipboard = get_clipboard("elements")
48
- @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
47
+ load_clipboard_items
49
48
  render :new
50
49
  end
51
50
  end
@@ -126,19 +125,27 @@ module Alchemy
126
125
  @element = Element.find(params[:id])
127
126
  end
128
127
 
128
+ def load_clipboard_items
129
+ @clipboard = get_clipboard("elements")
130
+ @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
131
+ end
132
+
129
133
  def element_from_clipboard
130
134
  @element_from_clipboard ||= begin
131
- @clipboard = get_clipboard("elements")
132
- @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
133
- end
135
+ @clipboard = get_clipboard("elements")
136
+ @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
137
+ end
134
138
  end
135
139
 
136
140
  def paste_element_from_clipboard
137
141
  @source_element = Element.find(element_from_clipboard["id"])
138
- element = Element.copy(@source_element, {
139
- parent_element_id: create_element_params[:parent_element_id],
140
- page_version_id: @page_version.id,
141
- })
142
+ element = Element.copy(
143
+ @source_element,
144
+ {
145
+ parent_element_id: create_element_params[:parent_element_id],
146
+ page_version_id: @page_version.id,
147
+ }
148
+ )
142
149
  if element_from_clipboard["action"] == "cut"
143
150
  @cut_element_id = @source_element.id
144
151
  @clipboard.delete_if { |item| item["id"] == @source_element.id.to_s }
@@ -23,9 +23,7 @@ module Alchemy
23
23
  before_action :set_root_page,
24
24
  only: [:index, :show, :order]
25
25
 
26
- before_action :run_on_page_layout_callbacks,
27
- if: :run_on_page_layout_callbacks?,
28
- only: [:show]
26
+ before_action :set_preview_mode, only: [:show]
29
27
 
30
28
  before_action :load_languages_and_layouts,
31
29
  unless: -> { @page_root },
@@ -35,6 +33,10 @@ module Alchemy
35
33
 
36
34
  before_action :set_page_version, only: [:show, :edit]
37
35
 
36
+ before_action :run_on_page_layout_callbacks,
37
+ if: :run_on_page_layout_callbacks?,
38
+ only: [:show]
39
+
38
40
  def index
39
41
  @query = @current_language.pages.contentpages.ransack(search_filter_params[:q])
40
42
 
@@ -64,7 +66,6 @@ module Alchemy
64
66
  # Used by page preview iframe in Page#edit view.
65
67
  #
66
68
  def show
67
- @preview_mode = true
68
69
  Page.current_preview = @page
69
70
  # Setting the locale to pages language, so the page content has it's correct translations.
70
71
  ::I18n.locale = @page.language.locale
@@ -397,6 +398,10 @@ module Alchemy
397
398
  @languages_with_page_tree = Language.on_current_site.with_root_page
398
399
  @page_layouts = PageLayout.layouts_for_select(@language.id)
399
400
  end
401
+
402
+ def set_preview_mode
403
+ @preview_mode = true
404
+ end
400
405
  end
401
406
  end
402
407
  end
@@ -5,17 +5,18 @@ module Alchemy
5
5
  layout false
6
6
  respond_to :json
7
7
 
8
- rescue_from CanCan::AccessDenied, with: :render_not_authorized
8
+ rescue_from CanCan::AccessDenied, with: :render_not_authorized
9
9
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
10
+ rescue_from ActionController::RoutingError, with: :render_not_found
10
11
 
11
12
  private
12
13
 
13
14
  def render_not_authorized
14
- render json: {error: "Not authorized"}, status: 403
15
+ render json: { error: "Not authorized" }, status: 403
15
16
  end
16
17
 
17
18
  def render_not_found
18
- render json: {error: "Record not found"}, status: 404
19
+ render json: { error: "Record not found" }, status: 404
19
20
  end
20
21
  end
21
22
  end
@@ -8,6 +8,7 @@ module Alchemy
8
8
  def index
9
9
  @nodes = Node.all
10
10
  @nodes = @nodes.includes(:parent)
11
+ @nodes = @nodes.where(language_id: params[:language_id]) if params[:language_id]
11
12
  @nodes = @nodes.ransack(params[:filter]).result
12
13
 
13
14
  if params[:page]
@@ -9,6 +9,13 @@ module Alchemy
9
9
  include Alchemy::ControllerActions
10
10
  include Alchemy::Modules
11
11
 
12
+ # Include Turbolinks explicitly in case Alchemy is embedded into a
13
+ # larger application that doesn't work with Turbolinks. The app
14
+ # can then set config.turbolinks.auto_include = false so that
15
+ # Turbolinks is not included in the app controllers.
16
+ include Turbolinks::Controller
17
+ ::ActionDispatch::Assertions.include ::Turbolinks::Assertions
18
+
12
19
  protect_from_forgery
13
20
 
14
21
  before_action :mailer_set_url_options
@@ -62,7 +62,6 @@ module Alchemy
62
62
  end
63
63
 
64
64
  @page = @element.page
65
- @root_page = @page.get_language_root
66
65
  if @message.valid?
67
66
  MessagesMailer.contact_form_mail(@message, mail_to, mail_from, subject).deliver
68
67
  redirect_to_success_page
@@ -122,8 +121,6 @@ module Alchemy
122
121
  if @page.blank?
123
122
  raise "Page for page_layout #{mailer_config["page_layout_name"]} not found"
124
123
  end
125
-
126
- @root_page = @page.get_language_root
127
124
  end
128
125
 
129
126
  def message_params
@@ -32,9 +32,6 @@ module Alchemy
32
32
  if: :locale_prefix_missing?,
33
33
  only: [:index, :show]
34
34
 
35
- # We only need to set the +@root_page+ if we are sure that no more redirects happen.
36
- before_action :set_root_page, only: [:index, :show]
37
-
38
35
  # Page layout callbacks need to run after all other callbacks
39
36
  before_action :run_on_page_layout_callbacks,
40
37
  if: :run_on_page_layout_callbacks?,
@@ -199,10 +196,6 @@ module Alchemy
199
196
  end
200
197
  end
201
198
 
202
- def set_root_page
203
- @root_page ||= Language.current_root_page
204
- end
205
-
206
199
  def signup_required?
207
200
  if Alchemy.user_class.respond_to?(:admins)
208
201
  Alchemy.user_class.admins.empty? && @page.nil?
@@ -70,7 +70,7 @@ module Alchemy
70
70
  # A class instance that will return elements that get rendered.
71
71
  # Use this for your custom element loading logic in views.
72
72
  #
73
- def render_elements(options = {})
73
+ def render_elements(options = {}, &blk)
74
74
  options = {
75
75
  from_page: @page,
76
76
  render_format: "html",
@@ -86,11 +86,12 @@ module Alchemy
86
86
 
87
87
  elements = finder.elements(page_version: page_version)
88
88
 
89
- buff = []
90
- elements.each_with_index do |element, i|
91
- buff << render_element(element, options, i + 1)
92
- end
93
- buff.join(options[:separator]).html_safe
89
+ default_rendering = ->(element, i) { render_element(element, options, i + 1) }
90
+ if block_given?
91
+ elements.map.with_index(&blk)
92
+ else
93
+ elements.map.with_index(&default_rendering)
94
+ end.join(options[:separator]).html_safe
94
95
  end
95
96
 
96
97
  # This helper renders a {Alchemy::Element} view partial.
@@ -125,7 +126,7 @@ module Alchemy
125
126
  #
126
127
  # == Usage
127
128
  #
128
- # <%= render_element(Alchemy::Element.available.named(:headline).first) %>
129
+ # <%= render_element(Alchemy::Element.published.named(:headline).first) %>
129
130
  #
130
131
  # @param [Alchemy::Element] element
131
132
  # The element you want to render the view for
@@ -146,11 +147,15 @@ module Alchemy
146
147
 
147
148
  element.store_page(@page)
148
149
 
149
- render element, {
150
- element: element,
151
- counter: counter,
152
- options: options.except(:locals),
153
- }.merge(options[:locals] || {})
150
+ render(
151
+ partial: options[:partial] || element.to_partial_path,
152
+ object: element,
153
+ locals: {
154
+ element: element,
155
+ counter: counter,
156
+ options: options.except(:locals, :partial),
157
+ }.merge(options[:locals] || {}),
158
+ )
154
159
  rescue ActionView::MissingTemplate => e
155
160
  warning(%(
156
161
  Element view partial not found for #{element.name}.\n
@@ -3,139 +3,141 @@
3
3
  module Alchemy
4
4
  # Holds everything concerning the building and creating of contents and the related essence object.
5
5
  #
6
- module Content::Factory
7
- extend ActiveSupport::Concern
6
+ class Content < BaseRecord
7
+ module Factory
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id element_id id)
12
+
13
+ # Builds a new content as descriped in the elements.yml file.
14
+ #
15
+ # @param [Hash]
16
+ # The content definition used for finding the content in +elements.yml+ file
17
+ #
18
+ def new(attributes = {})
19
+ element = attributes[:element] || Element.find_by(id: attributes[:element_id])
20
+ return super if attributes.empty? || element.nil?
21
+
22
+ definition = element.content_definition_for(attributes[:name])
23
+ if definition.blank? && attributes[:essence_type].nil?
24
+ raise ContentDefinitionError, "No definition found in elements.yml for #{attributes.inspect} and #{element.inspect}"
25
+ end
26
+
27
+ super(
28
+ name: attributes[:name],
29
+ essence_type: attributes[:essence_type] || normalize_essence_type(definition[:type]),
30
+ element: element,
31
+ ).tap(&:build_essence)
32
+ end
8
33
 
9
- module ClassMethods
10
- SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id element_id id)
34
+ # Creates a new content from elements definition in the +elements.yml+ file.
35
+ #
36
+ # 1. It builds the content
37
+ # 2. It creates the essence record (content object gets saved)
38
+ #
39
+ # @return [Alchemy::Content]
40
+ #
41
+ def create(attributes = {})
42
+ new(attributes).tap do |content|
43
+ content.essence.save && content.save
44
+ end
45
+ end
11
46
 
12
- # Builds a new content as descriped in the elements.yml file.
13
- #
14
- # @param [Hash]
15
- # The content definition used for finding the content in +elements.yml+ file
16
- #
17
- def new(attributes = {})
18
- element = attributes[:element] || Element.find_by(id: attributes[:element_id])
19
- return super if attributes.empty? || element.nil?
47
+ # Creates a copy of source and also copies the associated essence.
48
+ #
49
+ # You can pass a differences hash to update the attributes of the copy.
50
+ #
51
+ # === Example
52
+ #
53
+ # @copy = Alchemy::Content.copy(@content, {element_id: 3})
54
+ # @copy.element_id # => 3
55
+ #
56
+ def copy(source, differences = {})
57
+ Content.new(
58
+ source.attributes.with_indifferent_access.
59
+ except(*SKIPPED_ATTRIBUTES_ON_COPY).
60
+ merge(differences.with_indifferent_access)
61
+ ).tap do |new_content|
62
+ new_content.build_essence(
63
+ source.essence.attributes.
64
+ except(*SKIPPED_ATTRIBUTES_ON_COPY)
65
+ )
66
+ new_content.save
67
+ end
68
+ end
20
69
 
21
- definition = element.content_definition_for(attributes[:name])
22
- if definition.blank? && attributes[:essence_type].nil?
23
- raise ContentDefinitionError, "No definition found in elements.yml for #{attributes.inspect} and #{element.inspect}"
70
+ # Returns all content definitions from elements.yml
71
+ #
72
+ def definitions
73
+ definitions = Element.definitions.flat_map { |e| e["contents"] }
74
+ definitions.compact!
75
+ definitions
24
76
  end
25
77
 
26
- super(
27
- name: attributes[:name],
28
- essence_type: attributes[:essence_type] || normalize_essence_type(definition[:type]),
29
- element: element
30
- ).tap(&:build_essence)
31
- end
78
+ # Returns a normalized Essence type
79
+ #
80
+ # Adds Alchemy module name in front of given essence type
81
+ # unless there is a Class with the specified name that is an essence.
82
+ #
83
+ # @param [String]
84
+ # the essence type to normalize
85
+ #
86
+ def normalize_essence_type(essence_type)
87
+ essence_type = essence_type.classify
88
+ return essence_type if is_an_essence?(essence_type)
89
+
90
+ "Alchemy::#{essence_type}"
91
+ end
32
92
 
33
- # Creates a new content from elements definition in the +elements.yml+ file.
34
- #
35
- # 1. It builds the content
36
- # 2. It creates the essence record (content object gets saved)
37
- #
38
- # @return [Alchemy::Content]
39
- #
40
- def create(attributes = {})
41
- new(attributes).tap do |content|
42
- content.essence.save && content.save
93
+ private
94
+
95
+ def is_an_essence?(essence_type)
96
+ klass = Module.const_get(essence_type)
97
+ klass.is_a?(Class) && klass.new.acts_as_essence?
98
+ rescue NameError
99
+ false
43
100
  end
44
101
  end
45
102
 
46
- # Creates a copy of source and also copies the associated essence.
47
- #
48
- # You can pass a differences hash to update the attributes of the copy.
49
- #
50
- # === Example
51
- #
52
- # @copy = Alchemy::Content.copy(@content, {element_id: 3})
53
- # @copy.element_id # => 3
103
+ # Instance Methods
104
+
105
+ # Returns the definition hash from +elements.yml+ file.
54
106
  #
55
- def copy(source, differences = {})
56
- Content.new(
57
- source.attributes.with_indifferent_access.
58
- except(*SKIPPED_ATTRIBUTES_ON_COPY).
59
- merge(differences.with_indifferent_access)
60
- ).tap do |new_content|
61
- new_content.build_essence(
62
- source.essence.attributes.
63
- except(*SKIPPED_ATTRIBUTES_ON_COPY)
64
- )
65
- new_content.save
107
+ def definition
108
+ if element.blank?
109
+ log_warning "Content with id #{id} is missing its Element."
110
+ return {}
66
111
  end
112
+ element.content_definition_for(name) || {}
67
113
  end
68
114
 
69
- # Returns all content definitions from elements.yml
115
+ # Build essence from definition.
70
116
  #
71
- def definitions
72
- definitions = Element.definitions.flat_map { |e| e["contents"] }
73
- definitions.compact!
74
- definitions
117
+ # If an optional type is passed, this type of essence gets created.
118
+ #
119
+ def build_essence(attributes = {})
120
+ self.essence = essence_class.new(
121
+ { content: self, ingredient: default_value }.merge(attributes)
122
+ )
75
123
  end
76
124
 
77
- # Returns a normalized Essence type
78
- #
79
- # Adds Alchemy module name in front of given essence type
80
- # unless there is a Class with the specified name that is an essence.
125
+ # Creates essence from definition.
81
126
  #
82
- # @param [String]
83
- # the essence type to normalize
127
+ # If an optional type is passed, this type of essence gets created.
84
128
  #
85
- def normalize_essence_type(essence_type)
86
- essence_type = essence_type.classify
87
- return essence_type if is_an_essence?(essence_type)
88
-
89
- "Alchemy::#{essence_type}"
129
+ def create_essence!(attrs = {})
130
+ build_essence(attrs).save!
131
+ save!
90
132
  end
91
133
 
92
134
  private
93
135
 
94
- def is_an_essence?(essence_type)
95
- klass = Module.const_get(essence_type)
96
- klass.is_a?(Class) && klass.new.acts_as_essence?
97
- rescue NameError
98
- false
99
- end
100
- end
101
-
102
- # Instance Methods
103
-
104
- # Returns the definition hash from +elements.yml+ file.
105
- #
106
- def definition
107
- if element.blank?
108
- log_warning "Content with id #{id} is missing its Element."
109
- return {}
136
+ # Returns a class constant from definition's type field or the essence_type column
137
+ #
138
+ def essence_class
139
+ (essence_type || Content.normalize_essence_type(definition["type"])).constantize
110
140
  end
111
- element.content_definition_for(name) || {}
112
- end
113
-
114
- # Build essence from definition.
115
- #
116
- # If an optional type is passed, this type of essence gets created.
117
- #
118
- def build_essence(attributes = {})
119
- self.essence = essence_class.new(
120
- { content: self, ingredient: default_value }.merge(attributes)
121
- )
122
- end
123
-
124
- # Creates essence from definition.
125
- #
126
- # If an optional type is passed, this type of essence gets created.
127
- #
128
- def create_essence!(attrs = {})
129
- build_essence(attrs).save!
130
- save!
131
- end
132
-
133
- private
134
-
135
- # Returns a class constant from definition's type field or the essence_type column
136
- #
137
- def essence_class
138
- (essence_type || Content.normalize_essence_type(definition["type"])).constantize
139
141
  end
140
142
  end
141
143
  end
@@ -70,7 +70,7 @@ module Alchemy
70
70
  dependent: :destroy
71
71
 
72
72
  has_many :nested_elements,
73
- -> { order(:position).available },
73
+ -> { order(:position).published },
74
74
  class_name: "Alchemy::Element",
75
75
  foreign_key: :parent_element_id,
76
76
  dependent: :destroy,
@@ -159,7 +159,7 @@ module Alchemy
159
159
  end
160
160
 
161
161
  def all_from_clipboard(clipboard)
162
- return [] if clipboard.nil?
162
+ return none if clipboard.nil?
163
163
 
164
164
  where(id: clipboard.collect { |e| e["id"] })
165
165
  end
@@ -167,12 +167,19 @@ module Alchemy
167
167
  # All elements in clipboard that could be placed on page
168
168
  #
169
169
  def all_from_clipboard_for_page(clipboard, page)
170
- return [] if clipboard.nil? || page.nil?
170
+ return none if clipboard.nil? || page.nil?
171
171
 
172
- all_from_clipboard(clipboard).select { |ce|
173
- page.available_element_names.include?(ce.name)
174
- }
172
+ all_from_clipboard(clipboard).where(name: page.available_element_names)
175
173
  end
174
+
175
+ # All elements in clipboard that could be placed as a child of `parent_element`
176
+ def all_from_clipboard_for_parent_element(clipboard, parent_element)
177
+ return none if clipboard.nil? || parent_element.nil?
178
+
179
+ all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
180
+ end
181
+
182
+ deprecate available: :published, deprecator: Alchemy::Deprecation
176
183
  end
177
184
 
178
185
  # Returns next public element from same page.
@@ -8,6 +8,8 @@ module Alchemy
8
8
 
9
9
  self.table_name = "alchemy_ingredients"
10
10
 
11
+ attribute :data, :json
12
+
11
13
  belongs_to :element, touch: true, class_name: "Alchemy::Element", inverse_of: :ingredients
12
14
  belongs_to :related_object, polymorphic: true, optional: true
13
15
 
@@ -88,7 +88,7 @@ module Alchemy
88
88
 
89
89
  def duplicates
90
90
  ingredient.class
91
- .joins(:element).merge(Alchemy::Element.available)
91
+ .joins(:element).merge(Alchemy::Element.published)
92
92
  .where(Alchemy::Element.table_name => { name: ingredient.element.name })
93
93
  .where(value: ingredient.value)
94
94
  .where.not(id: ingredient.id)
@@ -38,7 +38,7 @@ module Alchemy
38
38
 
39
39
  validates :language_code,
40
40
  presence: true,
41
- uniqueness: { scope: [:site_id, :country_code] },
41
+ uniqueness: { scope: [:site_id, :country_code], case_sensitive: false },
42
42
  format: { with: /\A[a-z]{2}\z/, if: -> { language_code.present? } }
43
43
 
44
44
  validates :country_code,
@@ -15,8 +15,8 @@ module Alchemy
15
15
  source: :elements,
16
16
  ) do
17
17
  has_many :all_elements
18
- has_many :elements, -> { not_nested.unfixed.available }
19
- has_many :fixed_elements, -> { fixed.available }
18
+ has_many :elements, -> { not_nested.unfixed.published }
19
+ has_many :fixed_elements, -> { fixed.published }
20
20
  end
21
21
 
22
22
  has_many :contents, through: :elements
@@ -15,7 +15,7 @@ module Alchemy
15
15
  validates :name,
16
16
  presence: true
17
17
  validates :urlname,
18
- uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
18
+ uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? }, case_sensitive: false },
19
19
  exclusion: { in: RESERVED_URLNAMES },
20
20
  length: { minimum: 3, if: -> { urlname.present? } }
21
21
 
@@ -117,7 +117,7 @@ module Alchemy
117
117
  has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page
118
118
  has_many :versions, class_name: "Alchemy::PageVersion", inverse_of: :page, dependent: :destroy
119
119
  has_one :draft_version, -> { drafts }, class_name: "Alchemy::PageVersion"
120
- has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion"
120
+ has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion", autosave: -> { persisted? }
121
121
 
122
122
  before_validation :set_language,
123
123
  if: -> { language.nil? }
@@ -30,7 +30,7 @@ module Alchemy
30
30
  # Returns the rendered resized image using imagemagick directly.
31
31
  #
32
32
  def resize(size, upsample = false)
33
- image_file.thumb(upsample ? size : "#{size}>")
33
+ image_file.thumbnail(upsample ? size : "#{size}>")
34
34
  end
35
35
 
36
36
  # Returns true if picture's width is greater than it's height
@@ -119,7 +119,7 @@ module Alchemy
119
119
  if is_smaller_than?(dimensions) && upsample == false
120
120
  dimensions = reduce_to_image(dimensions)
121
121
  end
122
- image_file.thumb("#{dimensions_to_string(dimensions)}#")
122
+ image_file.thumbnail("#{dimensions_to_string(dimensions)}#")
123
123
  end
124
124
 
125
125
  # Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing.
@@ -13,6 +13,8 @@ module Alchemy
13
13
  include Alchemy::Logger
14
14
  include Alchemy::Picture::Transformations
15
15
 
16
+ ANIMATED_IMAGE_FORMATS = %w[gif webp]
17
+
16
18
  attr_reader :picture, :render_format
17
19
 
18
20
  def_delegators :@picture,
@@ -86,7 +88,7 @@ module Alchemy
86
88
  end
87
89
 
88
90
  options = {
89
- flatten: render_format != "gif" && picture.image_file_format == "gif",
91
+ flatten: !render_format.in?(ANIMATED_IMAGE_FORMATS) && picture.image_file_format == "gif",
90
92
  }.with_indifferent_access.merge(options)
91
93
 
92
94
  encoding_options = []
@@ -18,7 +18,7 @@ module Alchemy
18
18
  class Site < BaseRecord
19
19
  # validations
20
20
  validates_presence_of :host
21
- validates_uniqueness_of :host
21
+ validates_uniqueness_of :host, case_sensitive: false
22
22
 
23
23
  # associations
24
24
  has_many :languages
@@ -14,3 +14,16 @@
14
14
  Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>')
15
15
  <% end -%>
16
16
  $('#clipboard_button .icon').removeClass('fa-clipboard').addClass('fa-paste');
17
+
18
+ <%# Update add nested element forms for any elements that accept ONLY this as a nested element %>
19
+ <% if @item.class == Alchemy::Element %>
20
+ if (window.location.pathname == "<%= edit_admin_page_path(@item.page.id) %>") {
21
+ <%
22
+ @item.page.draft_version.elements.expanded.select do |element|
23
+ element.definition["nestable_elements"] == [@item.name]
24
+ end.each do |element|
25
+ %>
26
+ $(".add-nested-element[data-element-id='<%= element.id %>']").replaceWith('<%= j render "alchemy/admin/elements/add_nested_element_form", element: element %>')
27
+ <% end %>
28
+ }
29
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <%= content_tag :div, class: 'add-nested-element', data: { element_id: element.id } do %>
2
+ <% if element.expanded? || element.fixed? %>
3
+ <% if element.nestable_elements.length == 1 &&
4
+ (nestable_element = element.nestable_elements.first) &&
5
+ Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
6
+ %>
7
+ <%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
8
+ remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
9
+ <%= f.hidden_field :name %>
10
+ <%= f.hidden_field :page_version_id, value: element.page_version_id %>
11
+ <%= f.hidden_field :parent_element_id, value: element.id %>
12
+ <button class="button add-nestable-element-button" data-alchemy-button>
13
+ <%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) %>
14
+ </button>
15
+ <% end %>
16
+ <% else %>
17
+ <%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) : Alchemy.t("New Element")),
18
+ alchemy.new_admin_element_path(
19
+ parent_element_id: element.id,
20
+ page_version_id: element.page_version_id
21
+ ), {
22
+ size: "320x125",
23
+ title: Alchemy.t("New Element")
24
+ }, class: "button add-nestable-element-button" %>
25
+ <% end %>
26
+ <% end %>
27
+ <% end %>
@@ -55,29 +55,7 @@
55
55
  } %>
56
56
  <% end %>
57
57
 
58
- <% if element.expanded? || element.fixed? %>
59
- <% if element.nestable_elements.length == 1 %>
60
- <% nestable_element = element.nestable_elements.first %>
61
- <%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
62
- remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
63
- <%= f.hidden_field :name %>
64
- <%= f.hidden_field :page_version_id, value: element.page_version_id %>
65
- <%= f.hidden_field :parent_element_id, value: element.id %>
66
- <button class="button add-nestable-element-button" data-alchemy-button>
67
- <%= Alchemy.t(:add_nested_element) % { name: Alchemy.t(nestable_element, scope: 'element_names') } %>
68
- </button>
69
- <% end %>
70
- <% else %>
71
- <%= link_to_dialog Alchemy.t("New Element"),
72
- alchemy.new_admin_element_path(
73
- parent_element_id: element.id,
74
- page_version_id: element.page_version_id
75
- ), {
76
- size: "320x125",
77
- title: Alchemy.t("New Element")
78
- }, class: "button add-nestable-element-button" %>
79
- <% end %>
80
- <% end %>
58
+ <%= render "alchemy/admin/elements/add_nested_element_form", element: element %>
81
59
  </div>
82
60
  <% end %>
83
61
  <% end %>
@@ -9,7 +9,11 @@
9
9
  label: Alchemy.t(:element_of_type),
10
10
  collection: elements_for_select(@elements),
11
11
  prompt: Alchemy.t(:select_element),
12
- input_html: {class: 'alchemy_selectbox', autofocus: true} %>
12
+ selected: (@elements.first if @elements.count == 1),
13
+ input_html: {class: 'alchemy_selectbox', autofocus: true, disabled: @elements.count == 1} %>
14
+ <% if @elements.count == 1 %>
15
+ <%= form.hidden_field :name, value: @elements.first[:name] %>
16
+ <% end %>
13
17
  <%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
14
18
  <%= form.submit Alchemy.t(:add) %>
15
19
  <%- end -%>
@@ -17,7 +17,7 @@
17
17
  }) %>
18
18
  $('#<%= essence_node_editor.form_field_id %>').alchemyNodeSelect({
19
19
  placeholder: "<%= Alchemy.t(:search_node) %>",
20
- url: "<%= alchemy.api_nodes_path %>",
20
+ url: "<%= alchemy.api_nodes_path(language_id: essence_node_editor.page&.language_id) %>",
21
21
  query_params: <%== query_params.to_json %>,
22
22
  <% if essence_node_editor.essence.node %>
23
23
  <% serialized_node = ActiveModelSerializers::SerializableResource.new(essence_node_editor.essence.node, include: :ancestors) %>
@@ -2,6 +2,7 @@
2
2
  require "dragonfly_svg"
3
3
  require "alchemy/dragonfly/processors/crop_resize"
4
4
  require "alchemy/dragonfly/processors/auto_orient"
5
+ require "alchemy/dragonfly/processors/thumbnail"
5
6
 
6
7
  # Logger
7
8
  Dragonfly.logger = Rails.logger
@@ -18,4 +19,5 @@ Dragonfly::ImageMagick::Processors::Encode::WHITELISTED_ARGS << "flatten"
18
19
  Rails.application.config.after_initialize do
19
20
  Dragonfly.app(:alchemy_pictures).add_processor(:crop_resize, Alchemy::Dragonfly::Processors::CropResize.new)
20
21
  Dragonfly.app(:alchemy_pictures).add_processor(:auto_orient, Alchemy::Dragonfly::Processors::AutoOrient.new)
22
+ Dragonfly.app(:alchemy_pictures).add_processor(:thumbnail, Alchemy::Dragonfly::Processors::Thumbnail.new)
21
23
  end
@@ -55,7 +55,11 @@ module Alchemy
55
55
  # If it does not exist, or its empty, it returns an empty Hash.
56
56
  #
57
57
  def read_file(file)
58
- YAML.safe_load(ERB.new(File.read(file)).result, YAML_WHITELIST_CLASSES, [], true) || {}
58
+ YAML.safe_load(
59
+ ERB.new(File.read(file)).result,
60
+ permitted_classes: YAML_PERMITTED_CLASSES,
61
+ aliases: true,
62
+ ) || {}
59
63
  rescue Errno::ENOENT
60
64
  {}
61
65
  end
@@ -75,7 +75,8 @@ module Alchemy
75
75
 
76
76
  def load_alchemy_language_from_params
77
77
  if params[:locale].present?
78
- Language.find_by_code(params[:locale])
78
+ Language.find_by_code(params[:locale]) ||
79
+ raise(ActionController::RoutingError, "Language not found")
79
80
  end
80
81
  end
81
82
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dragonfly/image_magick/processors/thumb"
4
+
5
+ module Alchemy
6
+ module Dragonfly
7
+ module Processors
8
+ class Thumbnail < ::Dragonfly::ImageMagick::Processors::Thumb
9
+ def call(content, geometry, opts = {})
10
+ # store content into an instance variable to use it in args_for_geometry - method
11
+ @content = content
12
+ super
13
+ end
14
+
15
+ ##
16
+ # due to a missing ImageMagick parameter animated GIFs were broken with the default
17
+ # Dragonfly Thumb processor
18
+ def args_for_geometry(geometry)
19
+ # resize all frames in a GIF
20
+ # @link https://imagemagick.org/script/command-line-options.php#coalesce
21
+ # @link https://imagemagick.org/script/command-line-options.php#deconstruct
22
+ @content&.mime_type == "image/gif" ? "-coalesce #{super} -deconstruct" : super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -50,9 +50,8 @@ module Alchemy
50
50
  if File.exist?(definitions_file_path)
51
51
  YAML.safe_load(
52
52
  ERB.new(File.read(definitions_file_path)).result,
53
- YAML_WHITELIST_CLASSES,
54
- [],
55
- true
53
+ permitted_classes: YAML_PERMITTED_CLASSES,
54
+ aliases: true,
56
55
  ) || []
57
56
  else
58
57
  raise LoadError,
@@ -54,8 +54,7 @@ module Alchemy
54
54
  def find_elements(page_version)
55
55
  return Alchemy::ElementsRepository.none unless page_version
56
56
 
57
- elements = Alchemy::ElementsRepository.new(page_version.elements.available)
58
- elements = elements.not_nested
57
+ elements = page_version.element_repository.visible.not_nested
59
58
  elements = options[:fixed] ? elements.fixed : elements.unfixed
60
59
 
61
60
  if options[:only]
@@ -41,7 +41,7 @@ module Alchemy #:nodoc:
41
41
  has_one :element, through: :content, class_name: "Alchemy::Element"
42
42
  has_one :page, through: :element, class_name: "Alchemy::Page"
43
43
 
44
- scope :available, -> { joins(:element).merge(Alchemy::Element.available) }
44
+ scope :available, -> { joins(:element).merge(Alchemy::Element.published) }
45
45
  scope :from_element, ->(name) { joins(:element).where(Element.table_name => { name: name }) }
46
46
 
47
47
  delegate :restricted?, to: :page, allow_nil: true
@@ -151,7 +151,11 @@ module Alchemy
151
151
  #
152
152
  def read_definitions_file
153
153
  if File.exist?(layouts_file_path)
154
- YAML.safe_load(ERB.new(File.read(layouts_file_path)).result, YAML_WHITELIST_CLASSES, [], true) || []
154
+ YAML.safe_load(
155
+ ERB.new(File.read(layouts_file_path)).result,
156
+ permitted_classes: YAML_PERMITTED_CLASSES,
157
+ aliases: true,
158
+ ) || []
155
159
  else
156
160
  raise LoadError, "Could not find page_layouts.yml file! Please run `rails generate alchemy:install`"
157
161
  end
@@ -41,7 +41,7 @@ module Alchemy
41
41
  c.public? && !c.restricted?
42
42
  end
43
43
 
44
- can :read, Alchemy::Element, Alchemy::Element.available.not_restricted do |e|
44
+ can :read, Alchemy::Element, Alchemy::Element.published.not_restricted do |e|
45
45
  e.public? && !e.restricted?
46
46
  end
47
47
 
@@ -68,7 +68,7 @@ module Alchemy
68
68
  c.public?
69
69
  end
70
70
 
71
- can :read, Alchemy::Element, Alchemy::Element.available do |e|
71
+ can :read, Alchemy::Element, Alchemy::Element.published do |e|
72
72
  e.public?
73
73
  end
74
74
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "6.0.0"
4
+ VERSION = "6.0.3"
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/alchemy.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "alchemy/admin/preview_url"
4
4
 
5
5
  module Alchemy
6
- YAML_WHITELIST_CLASSES = %w(Symbol Date Regexp)
6
+ YAML_PERMITTED_CLASSES = %w[Symbol Date Regexp]
7
7
 
8
8
  # Define page preview sources
9
9
  #
@@ -35,9 +35,7 @@ module Alchemy
35
35
  # acme/preview_source: Acme Vorschau
36
36
  #
37
37
  def self.preview_sources
38
- @_preview_sources ||= begin
39
- Set.new << Alchemy::Admin::PreviewUrl
40
- end
38
+ @_preview_sources ||= Set.new << Alchemy::Admin::PreviewUrl
41
39
  end
42
40
 
43
41
  # Define page publish targets
@@ -17,8 +17,8 @@ module Alchemy
17
17
  # source and destination file names to use that engine.
18
18
  if ext != template_engine.to_s
19
19
  say_status :warning, "View uses unexpected template engine '#{ext}'.", :cyan
20
- destination.gsub!(/#{template_engine}$/, ext)
21
- source.gsub!(/#{template_engine}$/, ext)
20
+ destination = destination.gsub(/#{template_engine}$/, ext)
21
+ source = source.gsub(/#{template_engine}$/, ext)
22
22
  end
23
23
  end
24
24
 
@@ -33,7 +33,11 @@ module Alchemy
33
33
  end
34
34
 
35
35
  def load_alchemy_yaml(name)
36
- YAML.safe_load(ERB.new(File.read("#{Rails.root}/config/alchemy/#{name}")).result, YAML_WHITELIST_CLASSES, [], true)
36
+ YAML.safe_load(
37
+ ERB.new(File.read(Rails.root.join("config", "alchemy", name))).result,
38
+ permitted_classes: YAML_PERMITTED_CLASSES,
39
+ aliases: true,
40
+ )
37
41
  rescue Errno::ENOENT
38
42
  puts "\nERROR: Could not read config/alchemy/#{name} file. Please run: `rails generate alchemy:install`"
39
43
  end
@@ -98,7 +98,10 @@ module Alchemy
98
98
  end
99
99
 
100
100
  def copy_alchemy_entry_point
101
- webpack_config = YAML.load_file(app_root.join("config", "webpacker.yml"))[Rails.env]
101
+ webpack_config = YAML.safe_load(
102
+ File.read(app_root.join("config", "webpacker.yml")),
103
+ aliases: true
104
+ )[Rails.env]
102
105
  copy_file "alchemy_admin.js",
103
106
  app_root.join(webpack_config["source_path"], webpack_config["source_entry_path"], "alchemy/admin.js")
104
107
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy_cms/admin",
3
- "version": "6.0.0",
3
+ "version": "6.0.3",
4
4
  "description": "AlchemyCMS",
5
5
  "browser": "package/admin.js",
6
6
  "files": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2022-04-11 00:00:00.000000000 Z
16
+ date: 2022-05-02 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: actionmailer
@@ -1037,6 +1037,7 @@ files:
1037
1037
  - app/views/alchemy/admin/dashboard/help.html.erb
1038
1038
  - app/views/alchemy/admin/dashboard/index.html.erb
1039
1039
  - app/views/alchemy/admin/dashboard/info.html.erb
1040
+ - app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
1040
1041
  - app/views/alchemy/admin/elements/_element.html.erb
1041
1042
  - app/views/alchemy/admin/elements/_footer.html.erb
1042
1043
  - app/views/alchemy/admin/elements/_form.html.erb
@@ -1303,6 +1304,7 @@ files:
1303
1304
  - lib/alchemy/deprecation.rb
1304
1305
  - lib/alchemy/dragonfly/processors/auto_orient.rb
1305
1306
  - lib/alchemy/dragonfly/processors/crop_resize.rb
1307
+ - lib/alchemy/dragonfly/processors/thumbnail.rb
1306
1308
  - lib/alchemy/element_definition.rb
1307
1309
  - lib/alchemy/elements_finder.rb
1308
1310
  - lib/alchemy/engine.rb