camaleon_cms 2.9.0 → 2.9.2

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -5
  3. data/app/apps/plugins/front_cache/admin_controller.rb +1 -0
  4. data/app/apps/plugins/front_cache/front_cache_helper.rb +23 -14
  5. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +1 -1
  6. data/app/apps/themes/default/views/category.html.erb +1 -1
  7. data/app/apps/themes/default/views/post_tag.html.erb +1 -1
  8. data/app/apps/themes/default/views/post_type.html.erb +1 -1
  9. data/app/apps/themes/default/views/search.html.erb +1 -1
  10. data/app/apps/themes/new/views/category.html.erb +1 -1
  11. data/app/apps/themes/new/views/post_tag.html.erb +1 -1
  12. data/app/apps/themes/new/views/post_type.html.erb +1 -1
  13. data/app/apps/themes/new/views/search.html.erb +1 -1
  14. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +22 -5
  15. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +4 -2
  16. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +3 -3
  17. data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +2 -2
  18. data/app/controllers/camaleon_cms/admin/categories_controller.rb +9 -5
  19. data/app/controllers/camaleon_cms/admin/media_controller.rb +18 -5
  20. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +7 -4
  21. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +1 -1
  22. data/app/controllers/camaleon_cms/admin/posts_controller.rb +7 -4
  23. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +2 -2
  24. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +33 -11
  25. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +13 -4
  26. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +7 -4
  27. data/app/controllers/camaleon_cms/admin/settings_controller.rb +7 -4
  28. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +2 -2
  29. data/app/controllers/camaleon_cms/admin/users_controller.rb +23 -14
  30. data/app/controllers/camaleon_cms/admin_controller.rb +8 -0
  31. data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +5 -0
  32. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +29 -0
  33. data/app/decorators/camaleon_cms/post_decorator.rb +1 -1
  34. data/app/decorators/camaleon_cms/user_decorator.rb +1 -1
  35. data/app/helpers/camaleon_cms/admin/application_helper.rb +17 -17
  36. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +25 -22
  37. data/app/helpers/camaleon_cms/comment_helper.rb +74 -40
  38. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +1 -1
  39. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +7 -7
  40. data/app/helpers/camaleon_cms/html_helper.rb +15 -1
  41. data/app/helpers/camaleon_cms/session_helper.rb +13 -1
  42. data/app/helpers/camaleon_cms/site_helper.rb +16 -3
  43. data/app/helpers/camaleon_cms/uploader_helper.rb +102 -51
  44. data/app/models/camaleon_cms/ability.rb +54 -102
  45. data/app/models/camaleon_cms/category.rb +2 -0
  46. data/app/models/camaleon_cms/custom_field.rb +14 -0
  47. data/app/models/camaleon_cms/custom_field_group.rb +38 -1
  48. data/app/models/camaleon_cms/custom_fields_relationship.rb +1 -1
  49. data/app/models/camaleon_cms/meta.rb +4 -0
  50. data/app/models/camaleon_cms/nav_menu.rb +2 -0
  51. data/app/models/camaleon_cms/nav_menu_item.rb +2 -0
  52. data/app/models/camaleon_cms/plugin.rb +2 -0
  53. data/app/models/camaleon_cms/post.rb +1 -1
  54. data/app/models/camaleon_cms/post_comment.rb +4 -0
  55. data/app/models/camaleon_cms/post_tag.rb +2 -0
  56. data/app/models/camaleon_cms/post_type.rb +3 -1
  57. data/app/models/camaleon_cms/site.rb +2 -0
  58. data/app/models/camaleon_cms/term_taxonomy.rb +1 -23
  59. data/app/models/camaleon_cms/theme.rb +2 -0
  60. data/app/models/camaleon_cms/user_role.rb +13 -0
  61. data/app/models/camaleon_cms/widget/main.rb +2 -0
  62. data/app/models/camaleon_cms/widget/sidebar.rb +2 -0
  63. data/app/models/camaleon_record.rb +40 -0
  64. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +7 -7
  65. data/app/models/concerns/camaleon_cms/metas.rb +10 -6
  66. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +26 -0
  67. data/app/models/concerns/camaleon_cms/user_methods.rb +6 -2
  68. data/app/models/current_request.rb +16 -0
  69. data/app/uploaders/camaleon_cms_aws_uploader.rb +8 -1
  70. data/app/validators/camaleon_cms/post_uniq_validator.rb +21 -8
  71. data/app/views/camaleon_cms/admin/appearances/nav_menus/_left_menu_items.html.erb +2 -2
  72. data/app/views/camaleon_cms/admin/appearances/widgets/main/form.html.erb +1 -1
  73. data/app/views/camaleon_cms/admin/categories/index.html.erb +1 -1
  74. data/app/views/camaleon_cms/admin/comments/index.html.erb +2 -2
  75. data/app/views/camaleon_cms/admin/comments/list.html.erb +1 -1
  76. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +1 -1
  77. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  78. data/app/views/camaleon_cms/admin/posts/index.html.erb +3 -3
  79. data/app/views/camaleon_cms/admin/search.html.erb +1 -1
  80. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +23 -2
  81. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb +1 -1
  82. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +6 -5
  83. data/app/views/camaleon_cms/admin/settings/custom_fields/index.html.erb +1 -1
  84. data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
  85. data/app/views/camaleon_cms/admin/settings/sites/index.html.erb +1 -1
  86. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +79 -5
  87. data/app/views/camaleon_cms/admin/user_roles/index.html.erb +1 -1
  88. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  89. data/app/views/layouts/camaleon_cms/admin/_flash_messages.html.erb +2 -2
  90. data/config/initializers/custom_initializers.rb +2 -2
  91. data/config/locales/camaleon_cms/admin/ar.yml +6 -2
  92. data/config/locales/camaleon_cms/admin/de.yml +6 -2
  93. data/config/locales/camaleon_cms/admin/en.yml +6 -2
  94. data/config/locales/camaleon_cms/admin/es.yml +6 -2
  95. data/config/locales/camaleon_cms/admin/fr.yml +6 -2
  96. data/config/locales/camaleon_cms/admin/it.yml +6 -2
  97. data/config/locales/camaleon_cms/admin/nl.yml +7 -2
  98. data/config/locales/camaleon_cms/admin/pt-BR.yml +6 -2
  99. data/config/locales/camaleon_cms/admin/pt.yml +6 -2
  100. data/config/locales/camaleon_cms/admin/ru.yml +6 -2
  101. data/config/locales/camaleon_cms/admin/uk.yml +6 -2
  102. data/config/locales/camaleon_cms/admin/zh-CH.yml +6 -2
  103. data/db/migrate/20150611161134_post_table_into_utf8.rb +14 -14
  104. data/db/migrate/20150926095310_rename_column_posts.rb +3 -3
  105. data/db/migrate/20151212095328_add_confirm_token_to_users.rb +3 -3
  106. data/db/migrate/20160504155652_add_feature_to_posts.rb +1 -1
  107. data/db/migrate/20160504155653_move_first_name_of_users.rb +2 -2
  108. data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +1 -1
  109. data/db/migrate/20161215202255_drop_user_relationship_table.rb +1 -1
  110. data/db/migrate/20180124132318_create_media.rb +1 -1
  111. data/db/migrate/20180704211100_adjust_field_length.rb +1 -1
  112. data/lib/camaleon_cms/version.rb +1 -1
  113. data/lib/ext/string.rb +3 -3
  114. data/lib/plugin_routes.rb +6 -6
  115. data/lib/tasks/custom_fields_roles.rake +56 -0
  116. metadata +65 -8
@@ -30,7 +30,16 @@ module CamaleonCms
30
30
  # ****** check all options for each case in Admin::CustomFieldsHelper ****
31
31
  # SAMPLE: my_model.add_field({"name"=>"Sub Title", "slug"=>"subtitle"}, {"field_key"=>"text_box",
32
32
  # "translate"=>true, default_value: "Get in Touch"})
33
+ # Adds a manual field to the group (used by admin UI)
34
+ # item: field attributes
35
+ # options: field options
33
36
  def add_manual_field(item, options)
37
+ # Prevent creation of dangerous field types (select_eval) unless the actor is allowed.
38
+ if options[:field_key] == 'select_eval'
39
+ can?(:manage, :select_eval) ||
40
+ raise(CanCan::AccessDenied, 'Not authorized to create select_eval fields')
41
+ end
42
+
34
43
  c = get_field(item[:slug] || item['slug'])
35
44
  return c if c.present?
36
45
 
@@ -50,6 +59,8 @@ module CamaleonCms
50
59
 
51
60
  # only used by form on admin panel (protected)
52
61
  # return array of failed_fields and full_fields [[failed fields], [all fields]]
62
+ # items: hash of field items
63
+ # item_options: hash of options for each item
53
64
  def add_fields(items, item_options)
54
65
  fields.where.not(id: items.to_h.map { |_k, obj| obj['id'] }.uniq).destroy_all
55
66
  cache_fields = []
@@ -57,12 +68,38 @@ module CamaleonCms
57
68
  errors_saved = []
58
69
  if items.present?
59
70
  items.each do |i, item|
71
+ # allow string or symbol keys for incoming params
72
+ id_val = item['id'] || item[:id]
73
+
60
74
  item[:field_order] = order_index
61
75
  options = item_options[i] || {}
62
- if item[:id].present? && (field_item = fields.find_by(id: item[:id])).present?
76
+ if id_val.present? && (field_item = fields.find_by(id: id_val)).present?
77
+ # If this is an existing select_eval field (or the incoming data would
78
+ # make it a select_eval) ensure the current actor has explicit
79
+ # permission. For updates we preserve the form-like behavior by
80
+ # collecting an error-like non-persisted field in errors_saved and
81
+ # skipping the update when unauthorized.
82
+ existing_key = (field_item.options || {})[:field_key].to_s
83
+ # consider field_key coming from the per-item options (options) or the item itself
84
+ incoming_key = (options[:field_key] || item[:field_key]).to_s
85
+ if (existing_key == 'select_eval' || incoming_key == 'select_eval') && !can?(:manage, :select_eval)
86
+ field_item.errors.add(:base, 'Not authorized to modify select_eval field')
87
+ errors_saved << field_item
88
+ next
89
+ end
90
+
63
91
  saved = field_item.update(item)
64
92
  cache_fields << field_item
65
93
  else
94
+ # Check if the incoming options request creation of select_eval
95
+ incoming_key = (options[:field_key] || item[:field_key]).to_s
96
+ if incoming_key == 'select_eval' && !can?(:manage, :select_eval)
97
+ # Add an error-like non-persisted field to errors_saved to preserve behavior
98
+ field_item = fields.new(item)
99
+ field_item.errors.add(:base, 'Not authorized to create select_eval field')
100
+ errors_saved << field_item
101
+ next
102
+ end
66
103
  field_item = fields.new(item)
67
104
  cache_fields << field_item
68
105
  saved = field_item.save
@@ -7,7 +7,7 @@ module CamaleonCms
7
7
  default_scope { order("#{CamaleonCms::CustomFieldsRelationship.table_name}.term_order ASC") }
8
8
 
9
9
  # relations
10
- belongs_to :custom_field, required: false
10
+ belongs_to :custom_field, class_name: 'CamaleonCms::CustomField', required: false
11
11
 
12
12
  # validates :objectid, :custom_field_id, presence: true
13
13
  validates :custom_field_id, presence: true # error on clone model
@@ -2,5 +2,9 @@ module CamaleonCms
2
2
  class Meta < CamaleonRecord
3
3
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}metas"
4
4
  # attr_accessible :objectid, :key, :value, :object_class
5
+
6
+ extend CamaleonCms::NormalizeAttrs
7
+
8
+ normalize_attrs(:value)
5
9
  end
6
10
  end
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class NavMenu < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  default_scope { where(taxonomy: :nav_menu).order(id: :asc) }
4
6
  alias_attribute :site_id, :parent_id
5
7
 
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class NavMenuItem < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  alias_attribute :site_id, :term_group
4
6
  alias_attribute :label, :name
5
7
  alias_attribute :url, :description
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class Plugin < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  # attrs:
4
6
  # term_group => status active (1, nil)
5
7
  # slug => plugin key
@@ -83,7 +83,7 @@ module CamaleonCms
83
83
 
84
84
  # check if this is in draft status
85
85
  def draft?
86
- status == 'draft' || status == 'draft_child'
86
+ %w[draft draft_child].include?(status)
87
87
  end
88
88
 
89
89
  def draft_child?
@@ -3,6 +3,8 @@ module CamaleonCms
3
3
  include CamaleonCms::Metas
4
4
  include CamaleonCms::CommonRelationships
5
5
 
6
+ extend CamaleonCms::NormalizeAttrs
7
+
6
8
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}comments"
7
9
  # attr_accessible :user_id, :post_id, :content, :author, :author_email, :author_url, :author_IP, :approved, :agent, :agent, :typee, :comment_parent, :is_anonymous
8
10
  attr_accessor :is_anonymous
@@ -21,6 +23,8 @@ module CamaleonCms
21
23
  scope :comment_parent, -> { where(comment_parent: 'is not null') }
22
24
  scope :approveds, -> { where(approved: 'approved') }
23
25
 
26
+ normalize_attrs(:content)
27
+
24
28
  validates :content, presence: true
25
29
  validates_presence_of :author, :author_email, if: proc { |c| c.is_anonymous.present? }
26
30
  after_create :update_counter
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class PostTag < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  default_scope { where(taxonomy: :post_tag) }
4
6
 
5
7
  has_many :posts, foreign_key: :objectid, through: :term_relationships, source: :object
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class PostType < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  alias_attribute :site_id, :parent_id
4
6
  default_scope { where(taxonomy: :post_type) }
5
7
 
@@ -179,7 +181,7 @@ module CamaleonCms
179
181
 
180
182
  # destroy all custom field groups assigned to this post type
181
183
  def destroy_field_groups
182
- if !destroyed_by_association.present? && (slug == 'post' || slug == 'page')
184
+ if !destroyed_by_association.present? && %w[post page].include?(slug)
183
185
  errors.add(:base, 'This post type can not be deleted.')
184
186
  return false
185
187
  end
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class Site < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  include CamaleonCms::SiteDefaultSettings
4
6
 
5
7
  # attrs: [name, description, slug]
@@ -3,11 +3,7 @@ module CamaleonCms
3
3
  include CamaleonCms::Metas
4
4
  include CamaleonCms::CustomFieldsRead
5
5
 
6
- TRANSLATION_TAG_HIDE_MAP = { '<!--' => '!--', '-->' => '--!' }.freeze
7
- TRANSLATION_TAG_HIDE_REGEX = Regexp.new(TRANSLATION_TAG_HIDE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
8
- TRANSLATION_TAG_RESTORE_MAP = { '--!' => '-->', '!--' => '<!--' }.freeze
9
- TRANSLATION_TAG_RESTORE_REGEX =
10
- Regexp.new(TRANSLATION_TAG_RESTORE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
6
+ extend CamaleonCms::NormalizeAttrs
11
7
 
12
8
  def self.inherited(subclass)
13
9
  super
@@ -22,24 +18,6 @@ module CamaleonCms
22
18
  # attr_accessible :data_options
23
19
  # attr_accessible :data_metas
24
20
 
25
- # TODO: Remove the 1st branch when support will be dropped of Rails < 7.1
26
- if ::Rails::VERSION::STRING < '7.1.0'
27
- before_validation(on: %i[create update]) do
28
- %i[name description].each do |attr|
29
- next unless new_record? || attribute_changed?(attr)
30
-
31
- self[attr] = ActionController::Base.helpers.sanitize(
32
- __send__(attr)&.gsub(TRANSLATION_TAG_HIDE_REGEX, TRANSLATION_TAG_HIDE_MAP)
33
- )&.gsub(TRANSLATION_TAG_RESTORE_REGEX, TRANSLATION_TAG_RESTORE_MAP)
34
- end
35
- end
36
- else
37
- normalizes :name, :description, with: lambda { |field|
38
- ActionController::Base.helpers.sanitize(field.gsub(TRANSLATION_TAG_HIDE_REGEX, TRANSLATION_TAG_HIDE_MAP))
39
- .gsub(TRANSLATION_TAG_RESTORE_REGEX, TRANSLATION_TAG_RESTORE_MAP)
40
- }
41
- end
42
-
43
21
  # callbacks
44
22
  before_validation :before_validating
45
23
  before_destroy :destroy_dependencies
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class Theme < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  # attrs:
4
6
  # slug => plugin key
5
7
  belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, required: false
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class UserRole < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:name, :description)
4
+
3
5
  after_destroy :set_users_as_cilent
4
6
 
5
7
  default_scope { where(taxonomy: :user_roles) }
@@ -122,6 +124,17 @@ module CamaleonCms
122
124
  label: I18n.t('camaleon_cms.admin.sidebar.settings').to_s,
123
125
  description: I18n.t('camaleon_cms.admin.users.tool_tip.settings').to_s
124
126
  },
127
+ {
128
+ key: 'custom_fields',
129
+ label: I18n.t('camaleon_cms.admin.sidebar.custom_fields', default: 'Custom Fields').to_s,
130
+ description: I18n.t('camaleon_cms.admin.users.tool_tip.custom_fields',
131
+ default: 'Manage custom field groups and fields, including the select_eval type.').to_s
132
+ },
133
+ {
134
+ key: 'select_eval',
135
+ label: I18n.t('camaleon_cms.admin.custom_field.select_eval', default: 'Select Eval').to_s,
136
+ description: I18n.t('camaleon_cms.admin.users.tool_tip.select_eval', default: 'Allow toggling and using select_eval custom fields in the admin UI.').to_s
137
+ },
125
138
  {
126
139
  key: 'theme_settings',
127
140
  label: I18n.t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings').to_s,
@@ -1,6 +1,8 @@
1
1
  module CamaleonCms
2
2
  module Widget
3
3
  class Main < CamaleonCms::TermTaxonomy
4
+ normalize_attrs(:name)
5
+
4
6
  default_scope { where(taxonomy: :widget) }
5
7
  # attr_accessible :excerpt, :renderer
6
8
  # name: "title"
@@ -1,6 +1,8 @@
1
1
  module CamaleonCms
2
2
  module Widget
3
3
  class Sidebar < CamaleonCms::TermTaxonomy
4
+ normalize_attrs(:name, :description)
5
+
4
6
  default_scope { where(taxonomy: :sidebar) }
5
7
 
6
8
  has_many :metas, lambda {
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class CamaleonRecord < ActiveRecord::Base
4
+ TRANSLATION_TAG_HIDE_MAP = { '<!--' => '!--', '-->' => '--!' }.freeze
5
+ TRANSLATION_TAG_HIDE_REGEX = Regexp.new(TRANSLATION_TAG_HIDE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
6
+ TRANSLATION_TAG_RESTORE_MAP = { '--!' => '-->', '!--' => '<!--' }.freeze
7
+ TRANSLATION_TAG_RESTORE_REGEX =
8
+ Regexp.new(TRANSLATION_TAG_RESTORE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
9
+
4
10
  include ActiveRecordExtras::Relation
5
11
 
6
12
  self.abstract_class = true
@@ -43,4 +49,38 @@ class CamaleonRecord < ActiveRecord::Base
43
49
  def cama_build_cache_key(key)
44
50
  _key = "cama_cache_#{self.class.name}_#{id}_#{key}"
45
51
  end
52
+
53
+ # Return the current user for this thread/request context.
54
+ # Uses ActiveSupport::CurrentAttributes (CurrentRequest.user)
55
+ def current_user
56
+ return @current_user if defined?(@current_user)
57
+
58
+ @current_user = CurrentRequest.user
59
+ end
60
+
61
+ # Authorization helpers that delegate to central Ability (CanCan)
62
+ def can?(*args)
63
+ # Return false if no user or site context (e.g., background jobs, console)
64
+ return false if current_user.nil? || current_site.nil?
65
+
66
+ ability.can?(*args)
67
+ end
68
+
69
+ def ability
70
+ # Memoize Ability per request to avoid repeated DB queries and object instantiation.
71
+ # In tests that modify role meta mid-request, call reset_ability to invalidate cache.
72
+ @ability ||= CamaleonCms::Ability.new(current_user, current_site)
73
+ end
74
+
75
+ # Reset cached ability instance (useful in tests when role meta changes)
76
+ def reset_ability
77
+ @ability = nil
78
+ end
79
+
80
+ # current_site memoized from the CurrentRequest.site
81
+ def current_site
82
+ return @current_site if defined?(@current_site) && @current_site.present?
83
+
84
+ @current_site = CurrentRequest.site
85
+ end
46
86
  end
@@ -95,9 +95,7 @@ module CamaleonCms
95
95
  f.custom_field_slug == _key && f.group_number == group_number
96
96
  end.map(&:value)
97
97
  else
98
- custom_field_values.where(
99
- custom_field_slug: _key, group_number: group_number
100
- ).pluck(:value)
98
+ custom_field_values.where(custom_field_slug: _key, group_number: group_number).pluck(:value)
101
99
  end
102
100
  end
103
101
  alias get_fields get_field_values
@@ -137,7 +135,7 @@ module CamaleonCms
137
135
  def get_field_values_hash(include_options = false)
138
136
  fields = {}
139
137
  custom_field_values.to_a.uniq.each do |field_value|
140
- custom_field = field_value.custom_fields
138
+ custom_field = field_value.custom_field
141
139
  values = custom_field.values.where(objectid: id).pluck(:value)
142
140
  unless include_options
143
141
  fields[field_value.custom_field_slug] =
@@ -157,8 +155,8 @@ module CamaleonCms
157
155
  # deprecated f attribute
158
156
  def get_fields_object(_f = true)
159
157
  fields = {}
160
- custom_field_values.eager_load(:custom_fields).to_a.uniq.each do |field_value|
161
- custom_field = field_value.custom_fields
158
+ custom_field_values.eager_load(:custom_field).to_a.uniq.each do |field_value|
159
+ custom_field = field_value.custom_field
162
160
  # if custom_field.options[:show_frontend].to_s.to_bool
163
161
  values = custom_field.values.where(objectid: id).pluck(:value)
164
162
  fields[field_value.custom_field_slug] =
@@ -304,7 +302,9 @@ module CamaleonCms
304
302
  private
305
303
 
306
304
  def fix_meta_value(value)
307
- value = value.to_json if value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
305
+ return value.to_json if value.is_a?(ActionController::Parameters)
306
+ return JSON.generate(value) if value.is_a?(Array) || value.is_a?(Hash)
307
+
308
308
  value
309
309
  end
310
310
 
@@ -19,8 +19,7 @@ module CamaleonCms
19
19
  end
20
20
 
21
21
  # return value of meta with key: key,
22
- # if meta not exist, return default
23
- # return default if meta value == ""
22
+ # if meta not exist, or its value == "", return default
24
23
  def get_meta(key, default = nil)
25
24
  key_str = key.is_a?(Symbol) ? key.to_s : key
26
25
  cama_fetch_cache("meta_#{key_str}") do
@@ -70,8 +69,7 @@ module CamaleonCms
70
69
 
71
70
  # return configuration for current object
72
71
  # key: attribute name
73
- # default: if attribute not exist, return default
74
- # return default if option value == ""
72
+ # default: if the attribute doesn't exist, or its value == "", return default
75
73
  # return value for attribute
76
74
  def get_option(key = nil, default = nil, meta_key = '_default')
77
75
  values = cama_options(meta_key)
@@ -133,8 +131,14 @@ module CamaleonCms
133
131
 
134
132
  # fix to parse value
135
133
  def fix_meta_value(value)
136
- value = value.to_json if value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
137
- fix_meta_var(value)
134
+ changed_value = if value.is_a?(ActionController::Parameters)
135
+ value.to_json
136
+ elsif value.is_a?(Array) || value.is_a?(Hash)
137
+ JSON.generate(value)
138
+ else
139
+ value
140
+ end
141
+ fix_meta_var(changed_value)
138
142
  end
139
143
 
140
144
  # fix to detect type of the variable
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CamaleonCms
4
+ module NormalizeAttrs
5
+ def normalize_attrs(*args)
6
+ # TODO: Remove the 1st branch when support will be dropped of Rails < 7.1
7
+ if ::Rails::VERSION::STRING < '7.1.0'
8
+ before_validation(on: %i[create update]) do
9
+ args.each do |attr|
10
+ next unless new_record? || attribute_changed?(attr)
11
+
12
+ self[attr] = ActionController::Base.helpers.sanitize(
13
+ __send__(attr)&.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
14
+ )&.gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
15
+ end
16
+ end
17
+ else
18
+ normalizes(*args, with: lambda { |field|
19
+ ActionController::Base.helpers.sanitize(
20
+ field.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
21
+ ).gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
22
+ })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -6,17 +6,21 @@ module CamaleonCms
6
6
  include CamaleonCms::CustomFieldsRead
7
7
  include CamaleonCms::CommonRelationships
8
8
 
9
+ extend CamaleonCms::NormalizeAttrs
10
+
9
11
  validates_uniqueness_of :username, scope: [:site_id], case_sensitive: false,
10
12
  message: I18n.t('camaleon_cms.admin.users.message.requires_different_username', default: 'Requires different username')
11
13
  validates_uniqueness_of :email, scope: [:site_id], case_sensitive: false,
12
14
  message: I18n.t('camaleon_cms.admin.users.message.requires_different_email', default: 'Requires different email')
13
15
 
16
+ normalize_attrs(:first_name, :last_name, :username)
17
+
14
18
  # callbacks
15
19
  before_validation :cama_before_validation
16
20
  before_destroy :reassign_posts
17
21
  after_destroy :reassign_comments
18
22
  before_create { generate_token(:auth_token) }
19
- # invaliidate sessions when changing password
23
+ # invalidate sessions when changing password
20
24
  before_update { generate_token :auth_token if will_save_change_to_password_digest? }
21
25
 
22
26
  # relations
@@ -117,7 +121,7 @@ module CamaleonCms
117
121
 
118
122
  # reassign all posts of this user to first admin
119
123
  # reassign all comments of this user to first admin
120
- # if doesn't exist any other administrator, this will cancel the user destroy
124
+ # if it doesn't exist any other administrator, this will cancel the user destroy
121
125
  def reassign_posts
122
126
  all_posts.each do |p|
123
127
  s = p.post_type.site
@@ -0,0 +1,16 @@
1
+ # CurrentRequest holds per-request state (user, site) accessible to models
2
+ # via ActiveSupport::CurrentAttributes (thread-safe, resets after each request).
3
+ #
4
+ # Usage in controllers:
5
+ # CurrentRequest.user = cama_current_user
6
+ # CurrentRequest.site = current_site
7
+ #
8
+ # Usage in models:
9
+ # current_user # delegates to CurrentRequest.user via CamaleonRecord
10
+ # current_site # delegates to CurrentRequest.site via CamaleonRecord
11
+ #
12
+ # Note: Rails automatically resets CurrentAttributes between requests, ensuring no leakage.
13
+ # In specs, call CurrentRequest.reset in before/after hooks to avoid cross-test pollution.
14
+ class CurrentRequest < ActiveSupport::CurrentAttributes
15
+ attribute :user, :site
16
+ end
@@ -37,6 +37,8 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
37
37
  end
38
38
 
39
39
  def fetch_file(file_name)
40
+ return { error: 'Invalid file path' } unless valid_folder_path?(file_name)
41
+
40
42
  return file_name if file_exists?(file_name)
41
43
 
42
44
  return file_name if bucket.object(file_name).download_file(file_name) && file_exists?(file_name)
@@ -84,8 +86,9 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
84
86
  # - same_name: false => avoid to overwrite an existent file with same key and search for an available key
85
87
  # - is_thumb: true => if this file is a thumbnail of an uploaded file
86
88
  def add_file(uploaded_io_or_file_path, key, args = {})
89
+ return { error: 'Invalid file path' } unless valid_folder_path?(key)
90
+
87
91
  args = { same_name: false, is_thumb: false }.merge(args)
88
- res = nil
89
92
  key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present? && !args[:is_thumb]
90
93
  key = key.cama_fix_media_key
91
94
  key = search_new_key(key) unless args[:same_name]
@@ -117,6 +120,8 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
117
120
 
118
121
  # delete a folder in AWS with :key
119
122
  def delete_folder(key)
123
+ return { error: 'Invalid folder path' } unless valid_folder_path?(key)
124
+
120
125
  key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present?
121
126
  key = key.cama_fix_media_key
122
127
  bucket.objects(prefix: key.slice(1..-1) << '/').delete
@@ -125,6 +130,8 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
125
130
 
126
131
  # delete a file in AWS with :key
127
132
  def delete_file(key)
133
+ return { error: 'Invalid file path' } unless valid_folder_path?(key)
134
+
128
135
  key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present?
129
136
  key = key.cama_fix_media_key
130
137
  begin
@@ -7,11 +7,23 @@ module CamaleonCms
7
7
  ptype = record.post_type
8
8
  return unless ptype.present? # only for posts that belongs to a post type model
9
9
 
10
+ post_table = CamaleonCms::Post.table_name
11
+
12
+ conditions = []
13
+ params = []
14
+
15
+ slug_array.each do |s|
16
+ conditions << "#{post_table}.slug LIKE ?"
17
+ params << "%-->#{s}<!--%"
18
+ end
19
+
20
+ conditions << "#{post_table}.slug = ?"
21
+ params << record.slug
22
+
23
+ where_clause = "(#{conditions.join(' OR ')})"
24
+
10
25
  posts = ptype.site.posts
11
- .where(
12
- "(#{slug_array.map { |s| "#{CamaleonCms::Post.table_name}.slug LIKE '%-->#{s}<!--%'" }
13
- .join(' OR ')} ) OR #{CamaleonCms::Post.table_name}.slug = ?", record.slug
14
- )
26
+ .where(where_clause, *params)
15
27
  .where.not(id: record.id)
16
28
  .where.not(status: %i[draft draft_child trash])
17
29
  unless posts.empty?
@@ -28,10 +40,11 @@ module CamaleonCms
28
40
  end
29
41
 
30
42
  # avoid recursive page parent
31
- if record.post_parent.present? && ptype.manage_hierarchy? && record.parents.cama_pluck(:id).include?(record.id)
32
- record.errors[:base] << I18n.t('camaleon_cms.admin.post.message.recursive_hierarchy',
33
- default: 'Parent Post Recursive')
34
- end
43
+ return unless record.post_parent.present? && ptype.manage_hierarchy? &&
44
+ record.parents.cama_pluck(:id).include?(record.id)
45
+
46
+ record.errors[:base] << I18n.t('camaleon_cms.admin.post.message.recursive_hierarchy',
47
+ default: 'Parent Post Recursive')
35
48
  end
36
49
  end
37
50
  end
@@ -46,12 +46,12 @@
46
46
  </div>
47
47
  <% if pt.manage_categories? %>
48
48
  <div class="tab-pane class_type" id="tab-<%= pt.slug %>-categories" data-type="category" data-post_type="<%= pt.slug %>">
49
- <%= raw post_type_html_inputs(pt, "categories", "categories", "checkbox", [], "categorychecklist") %>
49
+ <%= post_type_html_inputs(pt, "categories", "categories", "checkbox", [], "categorychecklist") %>
50
50
  </div>
51
51
  <% end %>
52
52
  <% if pt.manage_tags? %>
53
53
  <div class="tab-pane class_type" id="tab-<%= pt.slug %>-tags" data-type="post_tag" data-post_type="<%= pt.slug %>">
54
- <%= raw post_type_html_inputs(pt, "post_tags", "post_tags", "checkbox", [], "categorychecklist") %>
54
+ <%= post_type_html_inputs(pt, "post_tags", "post_tags", "checkbox", [], "categorychecklist") %>
55
55
  </div>
56
56
  <% end %>
57
57
  </div>
@@ -1,3 +1,4 @@
1
+ <div class="alert alert-warning"><%= t('camaleon_cms.admin.widgets.warning') %></div>
1
2
  <%= form_for @widget, as: "widget_main", url: { action: (@widget.new_record? ? "create" : "update"), controller: "camaleon_cms/admin/appearances/widgets/main", id: @widget }, html: { role: 'form', class: "validate cama_ajax_request", id: "widget_form" } do |f| %>
2
3
  <%= render partial: 'layouts/camaleon_cms/admin/form_error', locals: {data: @widget} %>
3
4
  <div class="form-group">
@@ -24,4 +25,3 @@
24
25
  jQuery(function(){ init_form_validations($("#widget_form")); });
25
26
  </script>
26
27
  <% end %>
27
-
@@ -58,7 +58,7 @@
58
58
  </tbody>
59
59
  </table>
60
60
  <%= content_tag("div", raw(t('camaleon_cms.admin.message.data_found_list')), class: "alert alert-warning") if @categories.empty? %>
61
- <%= raw cama_do_pagination(@categories) %>
61
+ <%= cama_do_pagination(@categories) %>
62
62
  </div>
63
63
  </div>
64
64
  <!-- END BASIC TABLE SAMPLE -->
@@ -15,7 +15,7 @@
15
15
  <div class="panel-body">
16
16
  <div class="messages messages-img">
17
17
  <%= raw "<div class='alert alert-warning'>#{t('camaleon_cms.admin.comments.message.there_no_comments')}</div>" if @comments.size == 0 %>
18
- <%= raw cama_comments_render_html(@comments) %>
18
+ <%= cama_comments_render_html(@comments) %>
19
19
  </div>
20
20
  </div>
21
- </div>
21
+ </div>
@@ -31,6 +31,6 @@
31
31
  </tbody>
32
32
  </table>
33
33
  <%= content_tag("div", raw(t('camaleon_cms.admin.message.data_found_list')), class: "alert alert-warning") if @posts.empty? %>
34
- <%= raw cama_do_pagination(@posts) %>
34
+ <%= cama_do_pagination(@posts) %>
35
35
  </div>
36
36
  </div>
@@ -46,7 +46,7 @@
46
46
  </tbody>
47
47
  </table>
48
48
  <%= content_tag("div", raw(t('camaleon_cms.admin.message.data_found_list')), class: "alert alert-warning") if @post_tags.empty? %>
49
- <%= raw cama_do_pagination(@post_tags) %>
49
+ <%= cama_do_pagination(@post_tags) %>
50
50
  </div>
51
51
  </div>
52
52
  <!-- END BASIC TABLE SAMPLE -->
@@ -101,7 +101,7 @@
101
101
  </div>
102
102
  <div class="panel-body ">
103
103
  <div class="form-group list-categories">
104
- <%= raw post_type_html_inputs(@post_type, "categories", "categories" , @post_type.get_option('has_single_category', false) ? 'radio' : "checkbox" ,params[:categories] || (@post.new_record? ? [] : @post.categories.pluck("#{CamaleonCms::TermTaxonomy.table_name}.id")), "categorychecklist", true )%>
104
+ <%= post_type_html_inputs(@post_type, "categories", "categories" , @post_type.get_option('has_single_category', false) ? 'radio' : "checkbox" ,params[:categories] || (@post.new_record? ? [] : @post.categories.pluck("#{CamaleonCms::TermTaxonomy.table_name}.id")), "categorychecklist", true )%>
105
105
  </div>
106
106
  </div>
107
107
  </div>