alchemy_cms 8.1.12 → 8.1.14

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.
@@ -67,8 +67,8 @@ module Alchemy
67
67
  def tree
68
68
  @root_page = Alchemy::PageTreePreloader.new(
69
69
  page: @current_language.root_page,
70
- user: current_alchemy_user,
71
- admin_includes: true
70
+ ability: current_ability,
71
+ user: current_alchemy_user
72
72
  ).call
73
73
  end
74
74
 
@@ -180,8 +180,8 @@ module Alchemy
180
180
  if was_folded
181
181
  @page = PageTreePreloader.new(
182
182
  page: @page,
183
- user: current_alchemy_user,
184
- admin_includes: true
183
+ ability: current_ability,
184
+ user: current_alchemy_user
185
185
  ).call
186
186
  else
187
187
  head 200
@@ -28,8 +28,14 @@ module Alchemy
28
28
  def nested
29
29
  @page = Page.find_by(id: params[:page_id]) || Language.current_root_page
30
30
 
31
- # Preload the full tree from this page
32
- preloaded_page = PageTreePreloader.new(page: @page, user: current_alchemy_user).call
31
+ authorize! :show, @page
32
+
33
+ # Preload the full tree from this page, scoped to what the user may see
34
+ preloaded_page = PageTreePreloader.new(
35
+ page: @page,
36
+ user: current_alchemy_user,
37
+ ability: current_ability
38
+ ).call
33
39
 
34
40
  render json: PageTreeSerializer.new(
35
41
  preloaded_page,
@@ -250,7 +250,10 @@ export class ElementEditor extends HTMLElement {
250
250
  */
251
251
  setTitle(title) {
252
252
  const quote = this.querySelector(".element-header .preview_text_quote")
253
- quote.textContent = title
253
+ // Fixed elements have no header, so there is no quote to update.
254
+ if (quote) {
255
+ quote.textContent = title
256
+ }
254
257
  }
255
258
 
256
259
  /**
@@ -14,6 +14,12 @@ export class RemoteSelect extends AlchemyHTMLElement {
14
14
  url: { default: "" }
15
15
  }
16
16
 
17
+ // Select2 manages its own DOM after initialization, so attribute changes
18
+ // must not trigger the default re-render which would destroy the widget.
19
+ static get observedAttributes() {
20
+ return []
21
+ }
22
+
17
23
  async connected() {
18
24
  await setupSelectLocale()
19
25
 
@@ -34,6 +40,11 @@ export class RemoteSelect extends AlchemyHTMLElement {
34
40
  * @param {Event} event
35
41
  */
36
42
  onChange(event) {
43
+ // Update selection attribute so re-attaching the select2 component to
44
+ // the same input (e.g. after dragndrop) does not reset the selection.
45
+ if (event.added) {
46
+ this.setAttribute("selection", JSON.stringify(event.added))
47
+ }
37
48
  this.dispatchCustomEvent("RemoteSelect.Change", {
38
49
  removed: event.removed,
39
50
  added: event.added
@@ -63,6 +63,12 @@ module Alchemy
63
63
  end
64
64
 
65
65
  def page_elements(page)
66
+ # Pages usually arrive wrapped in a PageTreePage delegator. Unwrap it so
67
+ # the ability matches the Alchemy::Page rules instead of the delegator
68
+ # class, while still supporting a plain Alchemy::Page being passed in.
69
+ authorized_page = page.try(:__getobj__) || page
70
+ return Alchemy::Element.none unless opts[:ability].can?(:read, authorized_page)
71
+
66
72
  elements = page.public_version&.elements || Alchemy::Element.none
67
73
  if opts[:elements] == "true"
68
74
  elements
@@ -7,24 +7,24 @@ module Alchemy
7
7
  # It handles folded pages and preloads all necessary associations.
8
8
  #
9
9
  # @example Preload subtree from a specific page
10
- # preloader = Alchemy::PageTreePreloader.new(page: page, user: current_user)
10
+ # preloader = Alchemy::PageTreePreloader.new(page: page, ability: current_ability, user: current_user)
11
11
  # page_with_descendants = preloader.call
12
12
  #
13
13
  class PageTreePreloader
14
14
  # @param page [Page] Starting page for loading descendants
15
+ # @param ability [CanCan::Ability] Ability used to scope descendants to readable pages
15
16
  # @param user [User, nil] User for folding support
16
- # @param admin_includes [Boolean] Whether to include admin-only associations like :locker
17
- def initialize(page:, user: nil, admin_includes: false)
17
+ def initialize(page:, ability:, user: nil)
18
18
  @page = page
19
+ @ability = ability
19
20
  @user = user
20
- @admin_includes = admin_includes
21
21
  end
22
22
 
23
23
  # Preloads and returns the page tree
24
24
  #
25
25
  # @return [Array<Page>] Pages with preloaded children, or array with single page when using from:
26
26
  def call
27
- pages = page.self_and_descendants
27
+ pages = page.self_and_descendants.accessible_by(ability, :read)
28
28
  folded_page_ids = load_folded_page_ids
29
29
  if folded_page_ids.any?
30
30
  pages = pages.where(
@@ -45,7 +45,7 @@ module Alchemy
45
45
 
46
46
  private
47
47
 
48
- attr_reader :page, :user, :admin_includes
48
+ attr_reader :page, :user, :ability
49
49
 
50
50
  # Load folded page IDs for the user
51
51
  def load_folded_page_ids
@@ -93,7 +93,9 @@ module Alchemy
93
93
  },
94
94
  :public_version
95
95
  ]
96
- associations.push(:locker) if admin_includes
96
+ # The admin sitemap (and the serializer's admin fields) render lock info,
97
+ # so only eager-load the locker when the user may administer pages.
98
+ associations.push(:locker) if ability.can?(:index, :alchemy_admin_pages)
97
99
  associations
98
100
  end
99
101
  end
@@ -8,7 +8,7 @@
8
8
  <%= render_icon "file-edit", size: "xl" %>
9
9
  </sl-tooltip>
10
10
  <% else %>
11
- <%= render_icon "file-edit", size: "xl" %>
11
+ <%= render_icon "file", size: "xl" %>
12
12
  <% end %>
13
13
  </div>
14
14
  <div class="sitemap_sitename without-status">
@@ -1,11 +1,18 @@
1
1
  class ConvertSelectValueForMultiple < ActiveRecord::Migration[7.1]
2
2
  def up
3
3
  say_with_time "Converting Alchemy::Ingredients::Select values to multiple" do
4
- update <<-SQL.squish
5
- UPDATE alchemy_ingredients
6
- SET value = '["' || value || '"]'
7
- WHERE type = 'Alchemy::Ingredients::Select' AND value NOT LIKE '["%"]';
8
- SQL
4
+ Alchemy::Ingredients::Select
5
+ .where.not("value LIKE ?", '["%')
6
+ .update_all(
7
+ Arel.sql(
8
+ case ActiveRecord::Base.connection.adapter_name
9
+ when /mysql|mariadb/i
10
+ "value = CONCAT('[\"', value, '\"]')"
11
+ else
12
+ "value = '[\"' || value || '\"]'"
13
+ end
14
+ )
15
+ )
9
16
  end
10
17
  end
11
18
  end
@@ -18,8 +18,6 @@ module Alchemy
18
18
  when /^alchemy\/page_layouts\/_(\w+)/
19
19
  page_definition = PageDefinition.get($1)
20
20
  page_definition.elements.map { "alchemy/elements/_#{_1}" }
21
- when /^alchemy\/elements\/_(\w+)/
22
- ingredient_types($1).map { "alchemy/ingredients/_#{_1.underscore}_view" }.tap(&:uniq!)
23
21
  else
24
22
  ActionView::DependencyTracker::ERBTracker.call(@name, @template)
25
23
  end
@@ -66,7 +66,7 @@ module Alchemy
66
66
  end
67
67
 
68
68
  def get_item_class(item_type)
69
- "Alchemy::Configuration::#{item_type.to_s.classify}Option".constantize
69
+ "Alchemy::Configuration::#{item_type.to_s.camelcase}Option".constantize
70
70
  end
71
71
  end
72
72
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "8.1.12"
4
+ VERSION = "8.1.14"
5
5
 
6
6
  def self.version
7
7
  VERSION
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: 8.1.12
4
+ version: 8.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -1458,7 +1458,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1458
1458
  version: '0'
1459
1459
  requirements:
1460
1460
  - ImageMagick (libmagick), v6.6 or greater.
1461
- rubygems_version: 4.0.6
1461
+ rubygems_version: 4.0.10
1462
1462
  specification_version: 4
1463
1463
  summary: A powerful, userfriendly and flexible CMS for Rails
1464
1464
  test_files: []