alchemy_cms 6.0.0 → 6.0.3

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