camaleon_cms 2.9.2 → 2.9.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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -4
  3. data/Rakefile +3 -16
  4. data/app/apps/plugins/attack/admin_controller.rb +9 -6
  5. data/app/apps/plugins/attack/attack_helper.rb +35 -25
  6. data/app/apps/plugins/authoring_post/authoring_post_helper.rb +23 -16
  7. data/app/apps/plugins/front_cache/admin_controller.rb +15 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/front_cache_helper.rb +33 -9
  10. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  11. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  12. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  13. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  14. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  15. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  16. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  17. data/app/apps/themes/default/default_helper.rb +2 -1
  18. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  19. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  20. data/app/apps/themes/new/custom_helper.rb +5 -9
  21. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  22. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  23. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  24. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  25. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  26. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  27. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  28. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  29. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  30. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  31. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  32. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  33. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  34. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  35. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  36. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  37. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  38. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  39. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +76 -13
  40. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  41. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  42. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  43. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  44. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  45. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  46. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  47. data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
  48. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  49. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  50. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  51. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  52. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  53. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  54. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  59. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  60. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  61. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  62. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  63. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  64. data/app/decorators/camaleon_cms/post_decorator.rb +66 -50
  65. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  66. data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
  67. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  68. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  69. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  70. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  71. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  72. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  73. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  74. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  75. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  76. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  77. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  78. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  79. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  80. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  81. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  82. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  83. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  84. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  85. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  86. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  87. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  88. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  89. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  90. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  91. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  92. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  93. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  94. data/app/models/camaleon_cms/ability.rb +15 -7
  95. data/app/models/camaleon_cms/category.rb +6 -6
  96. data/app/models/camaleon_cms/custom_field.rb +15 -6
  97. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  98. data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
  99. data/app/models/camaleon_cms/media.rb +8 -2
  100. data/app/models/camaleon_cms/meta.rb +2 -2
  101. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  102. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  103. data/app/models/camaleon_cms/plugin.rb +3 -4
  104. data/app/models/camaleon_cms/post.rb +113 -27
  105. data/app/models/camaleon_cms/post_comment.rb +10 -6
  106. data/app/models/camaleon_cms/post_default.rb +42 -12
  107. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  108. data/app/models/camaleon_cms/post_tag.rb +4 -5
  109. data/app/models/camaleon_cms/post_type.rb +21 -13
  110. data/app/models/camaleon_cms/site.rb +39 -29
  111. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  112. data/app/models/camaleon_cms/term_taxonomy.rb +52 -4
  113. data/app/models/camaleon_cms/theme.rb +2 -4
  114. data/app/models/camaleon_cms/user.rb +17 -5
  115. data/app/models/camaleon_cms/user_role.rb +66 -6
  116. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  117. data/app/models/camaleon_cms/widget/main.rb +12 -8
  118. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  119. data/app/models/camaleon_record.rb +89 -5
  120. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
  121. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  122. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  123. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  124. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  125. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  126. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  127. data/app/models/current_request.rb +8 -1
  128. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  129. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  130. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  131. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  132. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  133. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  134. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  135. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  136. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  137. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  138. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  140. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  141. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  142. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  143. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  144. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  145. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  146. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  147. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  148. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  149. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  150. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  151. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  153. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  154. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  155. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  156. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  157. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  158. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  159. data/config/initializers/action_view.rb +42 -5
  160. data/config/initializers/active_record_extension.rb +13 -27
  161. data/config/initializers/assets.rb +52 -23
  162. data/config/initializers/custom_initializers.rb +1 -1
  163. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  164. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  165. data/config/locales/camaleon_cms/admin/en.yml +33 -0
  166. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  167. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  170. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  175. data/config/routes/admin.rb +33 -15
  176. data/config/routes/frontend.rb +21 -15
  177. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  178. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  179. data/lib/camaleon_cms/content_security.rb +158 -0
  180. data/lib/camaleon_cms/engine.rb +47 -9
  181. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  182. data/lib/camaleon_cms/setup_token.rb +69 -0
  183. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  184. data/lib/camaleon_cms/task_reporter.rb +14 -0
  185. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  186. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  187. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  188. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  189. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  190. data/lib/camaleon_cms/uploader_support.rb +86 -0
  191. data/lib/camaleon_cms/version.rb +1 -1
  192. data/lib/camaleon_cms.rb +11 -0
  193. data/lib/ext/hash.rb +28 -2
  194. data/lib/ext/string.rb +10 -5
  195. data/lib/ext/translator.rb +10 -2
  196. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  197. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  198. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  199. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  200. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  201. data/lib/plugin_routes.rb +412 -368
  202. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  203. data/lib/tasks/cross_site_field_groups.rake +74 -0
  204. data/lib/tasks/custom_fields_roles.rake +15 -13
  205. data/lib/tasks/orphaned_comments.rake +42 -0
  206. data/lib/tasks/private_upload_acls.rake +31 -0
  207. data/lib/tasks/site_custom_field_groups.rake +36 -0
  208. data/lib/tasks/unsafe_stored_content.rake +42 -0
  209. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  210. data/lib/tasks/user_field_groups.rake +27 -0
  211. metadata +123 -4
@@ -1,23 +1,26 @@
1
1
  module CamaleonCms
2
2
  class TermRelationship < CamaleonRecord
3
3
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}term_relationships"
4
+
4
5
  default_scope -> { order(term_order: :asc) }
5
6
 
6
- belongs_to :term_taxonomy, inverse_of: :term_relationships, required: false
7
- belongs_to :object, lambda {
8
- order("#{CamaleonCms::Post.table_name}.id DESC")
9
- }, class_name: 'CamaleonCms::Post', foreign_key: :objectid, inverse_of: :term_relationships, required: false
7
+ belongs_to :term_taxonomy, inverse_of: :term_relationships, optional: true
8
+ belongs_to :object, -> { order("#{CamaleonCms::Post.table_name}.id DESC") },
9
+ class_name: 'CamaleonCms::Post', foreign_key: :objectid, inverse_of: :term_relationships,
10
+ optional: true
10
11
 
11
- # callbacks
12
12
  after_create :update_count
13
13
  before_destroy :update_count
14
14
 
15
15
  private
16
16
 
17
- # update counter of post published items
17
+ # Update the counter of post-published items
18
18
  # TODO verify counter
19
19
  def update_count
20
- term_taxonomy.update_column('count', term_taxonomy.posts.published.size) if term_taxonomy&.try(:posts)
20
+ return unless term_taxonomy&.try(:posts)
21
+
22
+ term_taxonomy
23
+ .update_column(:count, term_taxonomy.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
21
24
  end
22
25
  end
23
26
  end
@@ -14,10 +14,57 @@ module CamaleonCms
14
14
  end
15
15
 
16
16
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}term_taxonomy"
17
- # attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order, :user_id
17
+ # attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order,
18
+ # :user_id
18
19
  # attr_accessible :data_options
19
20
  # attr_accessible :data_metas
20
21
 
22
+ self.inheritance_column = :taxonomy
23
+
24
+ def self.sti_name
25
+ name.demodulize.underscore
26
+ end
27
+
28
+ def self.polymorphic_name
29
+ name.demodulize
30
+ end
31
+
32
+ def self.find_sti_class(type_name)
33
+ # Handle explicit exceptions where database string doesn't match namespacing layout
34
+ case type_name.to_s
35
+ when 'widget'
36
+ return CamaleonCms::Widget::Main
37
+ when 'sidebar'
38
+ return CamaleonCms::Widget::Sidebar
39
+ end
40
+
41
+ # Standard conversion for "site" -> "CamaleonCms::Site"
42
+ # or "nav_menu_item" -> "CamaleonCms::NavMenuItem"
43
+ klass = begin
44
+ "CamaleonCms::#{type_name.camelize}".constantize
45
+ rescue NameError
46
+ nil
47
+ end
48
+ # The ancestry guard keeps a value like "meta" from instantiating an unrelated
49
+ # CamaleonCms class against the term_taxonomy table.
50
+ return klass if klass && klass <= base_class
51
+
52
+ # Runtime scan across loaded taxonomy models.
53
+ found = CamaleonCms::TermTaxonomy.descendants.find { |k| k.sti_name == type_name.to_s }
54
+ return found if found
55
+
56
+ # `super` resolves fully-qualified external classes (plugin STI outside the CamaleonCms
57
+ # namespace) and enforces ancestry — the scan above only sees classes already loaded, so
58
+ # without this an unloaded plugin taxonomy silently degraded to the base class. Same
59
+ # fallback PostDefault.find_sti_class keeps. Values it cannot place load as the base class,
60
+ # as every row did before native STI.
61
+ begin
62
+ super
63
+ rescue ActiveRecord::SubclassNotFound
64
+ base_class
65
+ end
66
+ end
67
+
21
68
  # callbacks
22
69
  before_validation :before_validating
23
70
  before_destroy :destroy_dependencies
@@ -27,11 +74,12 @@ module CamaleonCms
27
74
  validates_with CamaleonCms::UniqValidator
28
75
 
29
76
  # relations
30
- has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship', foreign_key: :term_taxonomy_id,
77
+ has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
31
78
  dependent: :destroy
32
79
  # has_many :posts, foreign_key: :objectid, through: :term_relationships, :source => :objects
33
- belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy', foreign_key: :parent_id, required: false
34
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
80
+ belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy', optional: true
81
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
82
+ inverse_of: false
35
83
 
36
84
  # return all children taxonomy
37
85
  # sample: sub categories of a category
@@ -1,12 +1,10 @@
1
1
  module CamaleonCms
2
2
  class Theme < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
3
+ normalize_attrs(:description)
4
4
 
5
5
  # attrs:
6
6
  # slug => plugin key
7
- belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, required: false
8
-
9
- default_scope { where(taxonomy: :theme) }
7
+ belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true, inverse_of: :themes
10
8
 
11
9
  before_validation :fix_name
12
10
  before_destroy :destroy_custom_fields
@@ -1,22 +1,34 @@
1
- unless PluginRoutes.static_system_info['user_model'].present?
1
+ if PluginRoutes.static_system_info['user_model'].blank?
2
2
  module CamaleonCms
3
3
  class User < CamaleonRecord
4
4
  include CamaleonCms::UserMethods
5
5
 
6
- self.table_name = PluginRoutes.static_system_info['cama_users_db_table'] || "#{PluginRoutes.static_system_info['db_prefix']}users"
6
+ self.table_name = PluginRoutes.static_system_info['cama_users_db_table'] ||
7
+ "#{PluginRoutes.static_system_info['db_prefix']}users"
7
8
 
8
9
  default_scope { order(role: :asc) }
9
10
 
11
+ # No `dependent:` — a deleted user's widgets keep rendering; only the owner is gone.
12
+ has_many :widgets, class_name: 'CamaleonCms::Widget::Main', # rubocop:disable Rails/HasManyOrHasOneDependent
13
+ inverse_of: :owner
14
+
10
15
  validates :username, presence: true
11
- validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i } # , :unless => Proc.new { |a| a.auth_social.present? }
16
+ # The following might be continued wit: , :unless => Proc.new { |a| a.auth_social.present? }
17
+ validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
12
18
  has_secure_password
19
+ # Security (audit 2026-08-11 M15): has_secure_password only validates presence (on create) and the
20
+ # 72-byte bcrypt maximum, so a trivially short password was accepted. Enforce a minimum length
21
+ # whenever a password is actually set (create, reset, change). allow_blank leaves an update that
22
+ # submits no password untouched (a blank field is not a change), and lets has_secure_password's own
23
+ # presence check own the empty-on-create case.
24
+ validates :password, length: { minimum: 8 }, allow_blank: true
13
25
 
14
26
  def self.find_by_email(email)
15
- where(['lower(email) = ?', email.to_s.downcase]).take
27
+ find_by(['lower(email) = ?', email.to_s.downcase])
16
28
  end
17
29
 
18
30
  def self.find_by_username(username)
19
- where(['lower(username) = ?', username.to_s.downcase]).take
31
+ find_by(['lower(username) = ?', username.to_s.downcase])
20
32
  end
21
33
  end
22
34
  end
@@ -1,12 +1,15 @@
1
1
  module CamaleonCms
2
2
  class UserRole < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
3
+ normalize_attrs(:description)
4
4
 
5
- after_destroy :set_users_as_cilent
5
+ def self.sti_name
6
+ 'user_roles'
7
+ end
6
8
 
7
- default_scope { where(taxonomy: :user_roles) }
9
+ after_destroy :set_users_as_cilent
8
10
 
9
- belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, required: false
11
+ belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true,
12
+ inverse_of: :user_roles_rel
10
13
 
11
14
  def roles_post_type
12
15
  get_meta('_post_type')
@@ -20,6 +23,23 @@ module CamaleonCms
20
23
  term_group.nil?
21
24
  end
22
25
 
26
+ # Users in a role slugged `admin` are administrators: `User#admin?` tests exactly this, and
27
+ # `Ability#initialize` answers `can :manage, :all` for them and returns before this role's
28
+ # `_post_type_`/`_manager_` metas are read. Those metas therefore describe nothing — every
29
+ # permission is held whatever they say.
30
+ def grants_all_permissions?
31
+ slug == 'admin'
32
+ end
33
+
34
+ # Whether the permission checkboxes mean anything. False for the default admin role, which is not
35
+ # editable at all, and false for any other role slugged `admin`, whose permissions cannot be
36
+ # withheld from its users. The editor renders them locked and the controller declines to write
37
+ # them, so the two cannot disagree — and a locked checkbox, which the browser does not submit,
38
+ # can never silently clear what is stored.
39
+ def permissions_editable?
40
+ editable? && !grants_all_permissions?
41
+ end
42
+
23
43
  ROLES = {
24
44
  post_type: [
25
45
  {
@@ -81,6 +101,14 @@ module CamaleonCms
81
101
  label: I18n.t('camaleon_cms.admin.users.roles_values.html_manage_tags').to_s,
82
102
  color: 'warning',
83
103
  description: I18n.t('camaleon_cms.admin.users.tool_tip.manage_tags').to_s
104
+ },
105
+ {
106
+ key: 'post_content_unfiltered_html',
107
+ label: I18n.t('camaleon_cms.admin.users.roles_values.post_content_unfiltered_html',
108
+ default: 'Allow unfiltered HTML in post content').to_s,
109
+ color: 'danger',
110
+ description: I18n.t('camaleon_cms.admin.users.tool_tip.post_content_unfiltered_html',
111
+ default: 'Permit users with this role to save raw/unfiltered HTML in post content').to_s
84
112
  }
85
113
  ],
86
114
  manager: [
@@ -133,12 +161,44 @@ module CamaleonCms
133
161
  {
134
162
  key: 'select_eval',
135
163
  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
164
+ description: I18n.t(
165
+ 'camaleon_cms.admin.users.tool_tip.select_eval',
166
+ default: 'Allow toggling and using select_eval custom fields in the admin UI.'
167
+ ).to_s
137
168
  },
138
169
  {
139
170
  key: 'theme_settings',
140
171
  label: I18n.t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings').to_s,
141
172
  description: I18n.t('camaleon_cms.admin.users.tool_tip.themes').to_s
173
+ },
174
+ {
175
+ key: 'contact_form_unfiltered_html',
176
+ label: I18n.t('camaleon_cms.admin.users.roles_values.contact_form_unfiltered_html',
177
+ default: 'Allow unfiltered HTML in contact forms').to_s,
178
+ color: 'danger',
179
+ description: I18n.t(
180
+ 'camaleon_cms.admin.users.tool_tip.contact_form_unfiltered_html',
181
+ default: 'Permit users with this role to save raw/unfiltered HTML anywhere in a contact form: the ' \
182
+ 'markup wrapping a form, field labels, descriptions, templates, CSS classes, default ' \
183
+ 'values, custom attributes, option labels and response messages. Without it, a save ' \
184
+ 'carrying such content is refused rather than cleaned up. Distinct from the per-post-type ' \
185
+ '"Allow unfiltered HTML in post content" permission.'
186
+ ).to_s
187
+ },
188
+ {
189
+ key: 'media_unfiltered_upload',
190
+ label: I18n.t('camaleon_cms.admin.users.roles_values.media_unfiltered_upload',
191
+ default: 'Allow unscanned media uploads').to_s,
192
+ color: 'danger',
193
+ description: I18n.t(
194
+ 'camaleon_cms.admin.users.tool_tip.media_unfiltered_upload',
195
+ default: 'Permit users with this role to upload files without the malicious-content scan. Without ' \
196
+ 'it, every upload is scanned whatever its source, and a file carrying scripts, event ' \
197
+ 'handlers, blocked elements or blocked URI schemes is refused rather than cleaned up. ' \
198
+ 'Uploaded files are served from the site origin, so this grants the ability to publish ' \
199
+ 'active content there. Site-wide, unlike the per-post-type "Allow unfiltered HTML in post ' \
200
+ 'content" permission.'
201
+ ).to_s
142
202
  }
143
203
  ]
144
204
  }.freeze
@@ -146,7 +206,7 @@ module CamaleonCms
146
206
  private
147
207
 
148
208
  def set_users_as_cilent
149
- site.users.where(role: slug).update_all(role: 'client')
209
+ site.users.where(role: slug).update_all(role: 'client') # rubocop:disable Rails/SkipsModelValidations
150
210
  end
151
211
  end
152
212
  end
@@ -1,7 +1,12 @@
1
1
  module CamaleonCms
2
2
  module Widget
3
3
  class Assigned < CamaleonCms::PostDefault
4
- default_scope -> { where(post_class: name).order(:taxonomy_id) }
4
+ default_scope -> { order(:taxonomy_id) }
5
+
6
+ def self.type_condition(table = arel_table)
7
+ super.or(table[inheritance_column].eq(name))
8
+ end
9
+
5
10
  # post_parent: sidebar_id
6
11
  # visibility: widget_id
7
12
  # comment_count: item_order
@@ -12,11 +17,11 @@ module CamaleonCms
12
17
 
13
18
  # attr_accessible :widget_id, :sidebar_id, :item_order
14
19
 
15
- has_many :metas, lambda {
16
- where(object_class: 'Widget::Assigned')
17
- }, class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
18
- belongs_to :sidebar, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :post_parent
19
- belongs_to :widget, class_name: 'CamaleonCms::Widget::Main', foreign_key: :visibility
20
+ belongs_to :sidebar, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :post_parent, inverse_of: :assigned,
21
+ optional: true
22
+ belongs_to :widget, class_name: 'CamaleonCms::Widget::Main', foreign_key: :visibility, inverse_of: :assigned,
23
+ optional: true
24
+
20
25
  after_initialize :fix_slug2
21
26
  before_create :set_order
22
27
 
@@ -1,9 +1,10 @@
1
1
  module CamaleonCms
2
2
  module Widget
3
3
  class Main < CamaleonCms::TermTaxonomy
4
- normalize_attrs(:name)
4
+ def self.sti_name
5
+ 'widget'
6
+ end
5
7
 
6
- default_scope { where(taxonomy: :widget) }
7
8
  # attr_accessible :excerpt, :renderer
8
9
  # name: "title"
9
10
  # description: "content for this"
@@ -12,14 +13,17 @@ module CamaleonCms
12
13
  # excerpt: string for message
13
14
  # renderer: string (path to the template for render this widget)
14
15
 
15
- has_many :metas, lambda {
16
- where(object_class: 'Widget::Main')
17
- }, class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
18
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id
19
- belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id
16
+ # inverse_of: false — custom `user_model` classes get only UserMethods, which
17
+ # defines no `widgets` collection the inverse could point at.
18
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, inverse_of: false,
19
+ optional: true
20
+ belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, inverse_of: :widgets, optional: true
21
+
22
+ has_many :assigned, class_name: 'CamaleonCms::Widget::Assigned', foreign_key: :visibility, dependent: :destroy,
23
+ inverse_of: :widget
20
24
 
21
- has_many :assigned, class_name: 'CamaleonCms::Widget::Assigned', foreign_key: :visibility, dependent: :destroy
22
25
  before_save :check_excerpt
26
+
23
27
  def is_simple?
24
28
  status == 'simple'
25
29
  end
@@ -1,15 +1,10 @@
1
1
  module CamaleonCms
2
2
  module Widget
3
3
  class Sidebar < CamaleonCms::TermTaxonomy
4
- normalize_attrs(:name, :description)
4
+ normalize_attrs(:description)
5
5
 
6
- default_scope { where(taxonomy: :sidebar) }
7
-
8
- has_many :metas, lambda {
9
- where(object_class: 'Widget::Sidebar')
10
- }, class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
11
- has_many :assigned, foreign_key: :post_parent, dependent: :destroy
12
- belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id
6
+ has_many :assigned, foreign_key: :post_parent, dependent: :destroy, inverse_of: :sidebar
7
+ belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, inverse_of: :sidebars, optional: true
13
8
 
14
9
  # scopes
15
10
  scope :default_sidebar, -> { where(slug: 'default-sidebar') }
@@ -1,16 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CamaleonRecord < ActiveRecord::Base
4
- TRANSLATION_TAG_HIDE_MAP = { '<!--' => '!--', '-->' => '--!' }.freeze
3
+ class CamaleonRecord < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
4
+ # Sentinels used to shield HTML comment delimiters (the translation locale markers such as `<!--:en-->`)
5
+ # from ActionController's sanitize(), which strips HTML comments. Private-Use-Area code points are used so
6
+ # that plain text typed by a user can never be rewritten into a comment delimiter after sanitization: the
7
+ # previous `!--`/`--!` tokens collided with ordinary text (e.g. "Read more !--"), so the restore pass would
8
+ # inject stray `<!--`/`-->` into the output. The hide map also strips any raw sentinel characters supplied
9
+ # in the input, so they cannot be smuggled through to become delimiters on restore.
10
+ TRANSLATION_TAG_HIDE_SENTINELS = { open: "\u{E000}", close: "\u{E001}" }.freeze
11
+ TRANSLATION_TAG_HIDE_MAP = {
12
+ '<!--' => TRANSLATION_TAG_HIDE_SENTINELS[:open],
13
+ '-->' => TRANSLATION_TAG_HIDE_SENTINELS[:close],
14
+ TRANSLATION_TAG_HIDE_SENTINELS[:open] => '',
15
+ TRANSLATION_TAG_HIDE_SENTINELS[:close] => ''
16
+ }.freeze
5
17
  TRANSLATION_TAG_HIDE_REGEX = Regexp.new(TRANSLATION_TAG_HIDE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
6
- TRANSLATION_TAG_RESTORE_MAP = { '--!' => '-->', '!--' => '<!--' }.freeze
18
+ TRANSLATION_TAG_RESTORE_MAP = {
19
+ TRANSLATION_TAG_HIDE_SENTINELS[:open] => '<!--',
20
+ TRANSLATION_TAG_HIDE_SENTINELS[:close] => '-->'
21
+ }.freeze
7
22
  TRANSLATION_TAG_RESTORE_REGEX =
8
23
  Regexp.new(TRANSLATION_TAG_RESTORE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
9
24
 
10
- include ActiveRecordExtras::Relation
11
-
12
25
  self.abstract_class = true
13
26
 
27
+ # Sanitize a value with ActionController's sanitize() while preserving translation locale markers
28
+ # (<!--:xx-->). Shared by Post#sanitize_content and the NormalizeAttrs concern so the transform lives in
29
+ # one place. Returns nil unchanged. `tags:`/`attributes:` default to nil, i.e. the sanitizer's own
30
+ # allowlist, so NormalizeAttrs and other callers are unaffected; Post#content passes a widened list.
31
+ def self.cama_sanitize_translatable(value, tags: nil, attributes: nil)
32
+ return value if value.nil?
33
+
34
+ options = {}
35
+ options[:tags] = tags if tags
36
+ options[:attributes] = attributes if attributes
37
+ ActionController::Base.helpers.sanitize(
38
+ value.to_s.gsub(TRANSLATION_TAG_HIDE_REGEX, TRANSLATION_TAG_HIDE_MAP), **options
39
+ ).gsub(TRANSLATION_TAG_RESTORE_REGEX, TRANSLATION_TAG_RESTORE_MAP)
40
+ end
41
+
42
+ def self.polymorphic_name
43
+ return super unless name.to_s.start_with?('CamaleonCms::')
44
+
45
+ name.demodulize
46
+ end
47
+
48
+ def self.polymorphic_class_for(name)
49
+ super
50
+ rescue NameError
51
+ legacy_class = legacy_camaleon_polymorphic_class(name)
52
+ return legacy_class if legacy_class
53
+ return nil if legacy_polymorphic_marker?(name)
54
+
55
+ raise
56
+ end
57
+
14
58
  # save cache value for this key
15
59
  def cama_set_cache(key, val)
16
60
  @cama_cache_vars ||= {}
@@ -20,6 +64,8 @@ class CamaleonRecord < ActiveRecord::Base
20
64
 
21
65
  # remove cache value for this key
22
66
  def cama_remove_cache(key)
67
+ return unless @cama_cache_vars
68
+
23
69
  @cama_cache_vars.delete(cama_build_cache_key(key))
24
70
  end
25
71
 
@@ -83,4 +129,42 @@ class CamaleonRecord < ActiveRecord::Base
83
129
 
84
130
  @current_site = CurrentRequest.site
85
131
  end
132
+
133
+ def self.legacy_camaleon_polymorphic_class(name)
134
+ class_name = name.to_s
135
+ return if class_name.blank?
136
+
137
+ candidates = []
138
+
139
+ unless class_name.include?('::')
140
+ candidates << "CamaleonCms::#{class_name}"
141
+
142
+ if class_name.include?('_')
143
+ base_name = class_name.split('_').first
144
+ candidates << "CamaleonCms::#{base_name}" if base_name.present?
145
+ end
146
+
147
+ candidates << CamaManager.get_user_class_name.to_s if class_name == 'User'
148
+ end
149
+
150
+ candidates << "CamaleonCms::#{class_name}" if class_name.include?('::') && !class_name.start_with?('CamaleonCms::')
151
+
152
+ candidates.uniq.each do |candidate|
153
+ next unless valid_constant_name?(candidate)
154
+
155
+ klass = candidate.safe_constantize
156
+ return klass if klass
157
+ end
158
+
159
+ nil
160
+ end
161
+ private_class_method def self.legacy_polymorphic_marker?(name)
162
+ name.to_s.start_with?('_')
163
+ end
164
+
165
+ private_class_method def self.valid_constant_name?(name)
166
+ name.to_s.match?(/\A[A-Z]\w*(::[A-Z]\w*)*\z/)
167
+ end
168
+
169
+ private_class_method :legacy_camaleon_polymorphic_class
86
170
  end
@@ -24,7 +24,7 @@ module CamaleonCms
24
24
  # update category assignations for this post
25
25
  # remove assignations that no longer exist
26
26
  # add new assignations
27
- # cats: (Array) array of category ids assigned for this post, sample: [1,2,3]
27
+ # cats: (Array) array of category ids assigned for this post, sample: [1, 2, 3]
28
28
  def update_categories(cats = [])
29
29
  rescue_extra_data
30
30
  cats = cats.to_i
@@ -35,7 +35,7 @@ module CamaleonCms
35
35
  news_categories.each do |key|
36
36
  term_relationships.create(term_taxonomy_id: key)
37
37
  end
38
- update_counters('categories')
38
+ update_counts('categories')
39
39
  end
40
40
 
41
41
  # update tags assignations for this post
@@ -53,7 +53,7 @@ module CamaleonCms
53
53
  post_tag = post_type.post_tags.where({ name: f }).first_or_create(slug: f.parameterize)
54
54
  term_relationships.where({ term_taxonomy_id: post_tag.id }).first_or_create
55
55
  end
56
- update_counters('tags')
56
+ update_counts('tags')
57
57
  end
58
58
 
59
59
  # Assign this post for category with id: category_id
@@ -64,43 +64,25 @@ module CamaleonCms
64
64
  categories_id.each do |key|
65
65
  term_relationships.where(term_taxonomy_id: key).first_or_create!
66
66
  end
67
- update_counters('categories')
67
+ update_counts('categories')
68
68
  end
69
69
 
70
- # Assign this post for category with id: category_id
71
- # categories_id: (Array) array of category ids assigned for this post, sample: [1,2,3]
70
+ # Unassign this post from categories with ids: categories_id
71
+ # categories_id: (Array) array of category ids to unassign from this post, sample: [1,2,3]
72
72
  def unassign_category(categories_id)
73
73
  categories_id = [categories_id] if categories_id.is_a?(Integer)
74
+ # a loaded (stale) categories association would hide the removed ids from the
75
+ # counter refresh below — check_default_category loads it on every save
76
+ categories.reset
74
77
  rescue_extra_data
75
78
  term_relationships.where(term_taxonomy_id: categories_id).destroy_all
76
- update_counters('categories')
77
- end
78
-
79
- # Assign new tags to this post
80
- # tags_title: (String) tags name separated by commas, sample: "Tag1,Tag two,tag new"
81
- def assign_tags(tag_titles)
82
- update_counters_before
83
- tags = tag_titles.split(',').strip
84
- tags.each do |t|
85
- post_tag = post_type.post_tags.where(name: t).first_or_create!
86
- term_relationships.where({ term_taxonomy_id: post_tag.id }).first_or_create!
87
- end
88
- update_counters('tags')
89
- end
90
-
91
- # Unassign tags from this post
92
- # tags_title: (String) tags name separated by commas, sample: "Tag1,Tag two,tag new"
93
- def unassign_tags(tag_titles)
94
- update_counters_before
95
- tags = tag_titles.split(',').strip
96
- term_relationships.where({ term_taxonomy_id: post_type.post_tags.where(name: tags).pluck(:id) }).destroy_all
97
- update_counters('tags')
79
+ update_counts('categories')
98
80
  end
99
81
 
100
- # update quantity of posts assigned for tags and categories assigned to this post
82
+ # Update the quantity of posts assigned for tags and categories assigned to this post
101
83
  def update_extra_data
102
84
  rescue_extra_data
103
- update_counters
85
+ update_counts
104
86
  end
105
87
 
106
88
  private
@@ -113,23 +95,23 @@ module CamaleonCms
113
95
  @tags_before = post_tags.pluck(:id) if manage_tags?
114
96
  end
115
97
 
116
- # update quantity of posts assigned for tags and categories assigned to this post
98
+ # Update the quantity of posts assigned for tags and categories assigned to this post
117
99
  # if kind is empty, then this will update both: cats and tags
118
100
  # kind: (string) tags | categories
119
- def update_counters(kind = '')
101
+ def update_counts(kind = '')
120
102
  if ['', 'categories'].include?(kind) && manage_categories?
121
- post_type.full_categories.where(id: (@cats_before + categories.pluck(:id)).uniq).each do |c|
122
- c.update_column('count', c.posts.published.size)
103
+ post_type.full_categories.where(id: (@cats_before + categories.pluck(:id)).uniq).find_each do |c|
104
+ c.update_column(:count, c.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
123
105
  end
124
106
  end
125
107
  return unless ['', 'tags'].include?(kind) && manage_tags?
126
108
 
127
- post_type.post_tags.where(id: (@tags_before + post_tags.pluck(:id)).uniq).each do |tag|
128
- tag.update_column('count', tag.posts.published.size)
109
+ post_type.post_tags.where(id: (@tags_before + post_tags.pluck(:id)).uniq).find_each do |tag|
110
+ tag.update_column(:count, tag.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
129
111
  end
130
112
  end
131
113
 
132
- # save extra data such as: categories and tags assigned to this post
114
+ # Save extra data such as categories and tags assigned to this post
133
115
  def save_extra_data
134
116
  update_categories(data_categories) if manage_categories? && !data_categories.nil?
135
117
  update_tags(data_tags) if manage_tags? && !data_tags.nil?
@@ -139,7 +121,7 @@ module CamaleonCms
139
121
  def check_default_category
140
122
  return unless manage_categories?
141
123
 
142
- assign_category([post_type.default_category.id]) unless categories.present?
124
+ assign_category([post_type.default_category.id]) if categories.blank?
143
125
  end
144
126
  end
145
127
  end
@@ -3,22 +3,35 @@
3
3
  module CamaleonCms
4
4
  module CommonRelationships
5
5
  extend ActiveSupport::Concern
6
+ # rubocop:disable Rails/InverseOf
6
7
 
7
8
  included do
8
- class_name = name.demodulize
9
- has_many :metas, -> { where(object_class: class_name) },
9
+ # Scopes use the demodulized class name (the 2.9.2 contract every install's rows were
10
+ # written under): 'Post', 'Main' for Widget::Main, and 'User' for a host Admin::User.
11
+ # to_s tolerates anonymous subclasses (STI specs build them), whose name is nil.
12
+ object_class_name = name.to_s.demodulize
13
+ has_many :metas, -> { where(object_class: object_class_name) },
10
14
  class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
11
15
 
12
- has_many :custom_field_values, -> { where(object_class: class_name) },
16
+ has_many :custom_field_values, -> { where(object_class: object_class_name) },
13
17
  class_name: 'CamaleonCms::CustomFieldsRelationship', foreign_key: :objectid, dependent: :delete_all
14
18
 
15
- has_many :custom_fields, -> { where(object_class: class_name) },
19
+ # No dependent: on the definition associations (2.9.2 behavior). Teardown is owned by
20
+ # CustomFieldsRead#_destroy_custom_field_groups, which runs first and destroys groups WITH
21
+ # callbacks; a dependent here is inert at best, and on hook-less includers (PostComment) a
22
+ # delete_all removed group rows while orphaning their fields and option metas. A :destroy
23
+ # cascade is no alternative: custom_fields matches group rows too (same table, same scope)
24
+ # but its CustomField typing would skip group teardown.
25
+ # rubocop:disable Rails/HasManyOrHasOneDependent
26
+ has_many :custom_fields, -> { where(object_class: object_class_name) },
16
27
  class_name: 'CamaleonCms::CustomField', foreign_key: :objectid
17
28
 
18
29
  # valid only for simple groups and not for complex like: posts, post, ... where the group is for individual or
19
30
  # children groups
20
- has_many :custom_field_groups, -> { where(object_class: class_name) },
31
+ has_many :custom_field_groups, -> { where(object_class: object_class_name) },
21
32
  class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :objectid
33
+ # rubocop:enable Rails/HasManyOrHasOneDependent
22
34
  end
35
+ # rubocop:enable Rails/InverseOf
23
36
  end
24
37
  end