camaleon_cms 2.9.2 → 2.9.4

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 (216) 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 +14 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/config/locales/translation.yml +0 -7
  10. data/app/apps/plugins/front_cache/front_cache_helper.rb +109 -37
  11. data/app/apps/plugins/front_cache/views/admin/settings.html.erb +0 -5
  12. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  13. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  14. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  15. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  16. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  17. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  18. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  19. data/app/apps/themes/default/default_helper.rb +2 -1
  20. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  21. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  22. data/app/apps/themes/new/custom_helper.rb +5 -9
  23. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  25. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  26. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  27. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  28. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  29. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  30. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  31. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  32. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  33. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  34. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  35. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  36. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  37. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  38. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  39. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  40. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  41. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +80 -13
  42. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  43. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  44. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  45. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  46. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  47. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  48. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  49. data/app/controllers/camaleon_cms/frontend_controller.rb +146 -74
  50. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  51. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  52. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  53. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  54. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  59. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  60. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  61. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  62. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  63. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  64. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  65. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  66. data/app/decorators/camaleon_cms/post_decorator.rb +68 -50
  67. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  68. data/app/decorators/camaleon_cms/site_decorator.rb +33 -25
  69. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  70. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  71. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  72. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  73. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  74. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  75. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  76. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  77. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  78. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  79. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  80. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  81. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  82. data/app/helpers/camaleon_cms/frontend/application_helper.rb +6 -1
  83. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  84. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  85. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  86. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  87. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  88. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  89. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  90. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  91. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  92. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  93. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  94. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  95. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  96. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  97. data/app/models/camaleon_cms/ability.rb +15 -7
  98. data/app/models/camaleon_cms/category.rb +6 -6
  99. data/app/models/camaleon_cms/custom_field.rb +15 -6
  100. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  101. data/app/models/camaleon_cms/custom_fields_relationship.rb +140 -2
  102. data/app/models/camaleon_cms/media.rb +8 -2
  103. data/app/models/camaleon_cms/meta.rb +2 -2
  104. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  105. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  106. data/app/models/camaleon_cms/plugin.rb +3 -4
  107. data/app/models/camaleon_cms/post.rb +137 -27
  108. data/app/models/camaleon_cms/post_comment.rb +10 -6
  109. data/app/models/camaleon_cms/post_default.rb +42 -12
  110. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  111. data/app/models/camaleon_cms/post_tag.rb +4 -5
  112. data/app/models/camaleon_cms/post_type.rb +21 -13
  113. data/app/models/camaleon_cms/site.rb +39 -29
  114. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  115. data/app/models/camaleon_cms/term_taxonomy.rb +58 -4
  116. data/app/models/camaleon_cms/theme.rb +2 -4
  117. data/app/models/camaleon_cms/user.rb +17 -5
  118. data/app/models/camaleon_cms/user_role.rb +81 -6
  119. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  120. data/app/models/camaleon_cms/widget/main.rb +12 -8
  121. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  122. data/app/models/camaleon_record.rb +89 -5
  123. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +36 -42
  124. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  125. data/app/models/concerns/camaleon_cms/content_shortcode_gate.rb +73 -0
  126. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  127. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  128. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  129. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  130. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  131. data/app/models/current_request.rb +8 -1
  132. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  133. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  134. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  135. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  136. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  137. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  138. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  140. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  141. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  142. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  143. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  144. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  145. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  146. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  147. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  148. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  149. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  150. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  151. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  153. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  154. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  155. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  156. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  157. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  158. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  159. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  160. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  161. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  162. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  163. data/config/initializers/action_view.rb +42 -5
  164. data/config/initializers/active_record_extension.rb +13 -27
  165. data/config/initializers/assets.rb +52 -23
  166. data/config/initializers/custom_initializers.rb +1 -1
  167. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/en.yml +36 -0
  170. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  175. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  176. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  177. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  178. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  179. data/config/routes/admin.rb +33 -15
  180. data/config/routes/frontend.rb +21 -15
  181. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  182. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  183. data/lib/camaleon_cms/content_security.rb +158 -0
  184. data/lib/camaleon_cms/engine.rb +45 -21
  185. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  186. data/lib/camaleon_cms/setup_token.rb +69 -0
  187. data/lib/camaleon_cms/shortcode_registry.rb +102 -0
  188. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  189. data/lib/camaleon_cms/task_reporter.rb +14 -0
  190. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  191. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  192. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  193. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  194. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  195. data/lib/camaleon_cms/uploader_support.rb +86 -0
  196. data/lib/camaleon_cms/version.rb +1 -1
  197. data/lib/camaleon_cms.rb +23 -0
  198. data/lib/ext/hash.rb +28 -2
  199. data/lib/ext/string.rb +10 -5
  200. data/lib/ext/translator.rb +10 -2
  201. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  202. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  203. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  204. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  205. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  206. data/lib/plugin_routes.rb +412 -368
  207. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  208. data/lib/tasks/cross_site_field_groups.rake +74 -0
  209. data/lib/tasks/custom_fields_roles.rake +15 -13
  210. data/lib/tasks/orphaned_comments.rake +42 -0
  211. data/lib/tasks/private_upload_acls.rake +31 -0
  212. data/lib/tasks/site_custom_field_groups.rake +36 -0
  213. data/lib/tasks/unsafe_stored_content.rake +42 -0
  214. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  215. data/lib/tasks/user_field_groups.rake +27 -0
  216. metadata +125 -4
@@ -1,32 +1,45 @@
1
1
  module CamaleonCms
2
2
  class Site < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
3
+ normalize_attrs(:description)
4
4
 
5
5
  include CamaleonCms::SiteDefaultSettings
6
6
 
7
7
  # attrs: [name, description, slug]
8
8
  attr_accessor :site_domain
9
9
 
10
- default_scope { where(taxonomy: :site).reorder(term_group: :desc) }
10
+ # Transient (never persisted): when default_settings auto-provisions the
11
+ # administrator, it stashes the randomly generated plaintext password here so
12
+ # the provisioning controller can surface it once. nil when no admin was minted
13
+ # (e.g. a shared admin already existed). See harden-installer-default-admin.
14
+ attr_accessor :generated_admin_password
11
15
 
12
- has_many :post_types, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id, dependent: :destroy
16
+ has_many :post_types, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id, dependent: :destroy,
17
+ inverse_of: :site
13
18
  has_many :nav_menus, class_name: 'CamaleonCms::NavMenu', foreign_key: :parent_id, dependent: :destroy,
14
19
  inverse_of: :site
15
- has_many :nav_menu_items, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :term_group
16
- has_many :widgets, class_name: 'CamaleonCms::Widget::Main', foreign_key: :parent_id, dependent: :destroy
17
- has_many :sidebars, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :parent_id, dependent: :destroy
18
- has_many :user_roles_rel, class_name: 'CamaleonCms::UserRole', foreign_key: :parent_id, dependent: :destroy
20
+ # inverse_of: false — this joins by term_group (the site id), while NavMenuItem#parent
21
+ # reads parent_id (the menu id); an inverse here overwrites items' real menu parent.
22
+ has_many :nav_menu_items, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :term_group, inverse_of: false,
23
+ dependent: :destroy
24
+ has_many :widgets, class_name: 'CamaleonCms::Widget::Main', foreign_key: :parent_id, dependent: :destroy,
25
+ inverse_of: :site
26
+ has_many :sidebars, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :parent_id, dependent: :destroy,
27
+ inverse_of: :site
28
+ has_many :user_roles_rel, class_name: 'CamaleonCms::UserRole', foreign_key: :parent_id, dependent: :destroy,
29
+ inverse_of: :site
19
30
  has_many :custom_field_groups, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :parent_id,
20
- dependent: :destroy
21
- has_many :term_taxonomies, class_name: 'CamaleonCms::TermTaxonomy', foreign_key: :parent_id
31
+ dependent: :destroy, inverse_of: :parent
32
+ has_many :term_taxonomies, class_name: 'CamaleonCms::TermTaxonomy', foreign_key: :parent_id, inverse_of: :parent,
33
+ dependent: :destroy
22
34
 
23
35
  has_many :posts, through: :post_types, source: :posts
24
- has_many :plugins, class_name: 'CamaleonCms::Plugin', foreign_key: :parent_id, dependent: :destroy
25
- has_many :themes, class_name: 'CamaleonCms::Theme', foreign_key: :parent_id, dependent: :destroy
26
- has_many :public_media, -> { where(is_public: true) },
27
- class_name: 'CamaleonCms::Media', foreign_key: :site_id, dependent: :destroy
28
- has_many :private_media, -> { where(is_public: false) },
29
- class_name: 'CamaleonCms::Media', foreign_key: :site_id, dependent: :destroy
36
+ has_many :plugins, class_name: 'CamaleonCms::Plugin', foreign_key: :parent_id, dependent: :destroy,
37
+ inverse_of: :site
38
+ has_many :themes, class_name: 'CamaleonCms::Theme', foreign_key: :parent_id, dependent: :destroy, inverse_of: :site
39
+ has_many :public_media, -> { where(is_public: true) }, class_name: 'CamaleonCms::Media', foreign_key: :site_id,
40
+ dependent: :destroy, inverse_of: :site
41
+ has_many :private_media, -> { where(is_public: false) }, class_name: 'CamaleonCms::Media', foreign_key: :site_id,
42
+ dependent: :destroy, inverse_of: :site
30
43
 
31
44
  after_create :default_settings
32
45
  after_create :set_default_user_roles
@@ -35,7 +48,7 @@ module CamaleonCms
35
48
  before_destroy :destroy_site
36
49
  after_destroy :refresh_routes
37
50
 
38
- validates_uniqueness_of :slug, scope: :taxonomy
51
+ validates :slug, uniqueness: { scope: :taxonomy }
39
52
 
40
53
  # all user roles for this site
41
54
  def user_roles
@@ -79,7 +92,7 @@ module CamaleonCms
79
92
  options[:_admin_theme] || 'en'
80
93
  end
81
94
 
82
- # set current admin language for this site
95
+ # Set the current admin language for this site
83
96
  def set_admin_language(language)
84
97
  set_option('_admin_theme', language)
85
98
  end
@@ -149,11 +162,9 @@ module CamaleonCms
149
162
 
150
163
  # list all users of current site
151
164
  def users
152
- if PluginRoutes.system_info['users_share_sites']
153
- CamaleonCms::User.all
154
- else
155
- CamaleonCms::User.where(site_id: id)
156
- end
165
+ return CamaleonCms::User.all if PluginRoutes.system_info['users_share_sites']
166
+
167
+ CamaleonCms::User.where(site_id: id)
157
168
  end
158
169
  alias users_include_admins users
159
170
 
@@ -176,15 +187,13 @@ module CamaleonCms
176
187
  def get_valid_post_slug(slug, post_id = nil)
177
188
  slugs = slug.translations
178
189
  if slugs.present?
179
- slugs.each do |k, v|
180
- slugs[k] = get_valid_post_slug(v)
181
- end
190
+ slugs.each { |k, v| slugs[k] = get_valid_post_slug(v) }
182
191
  slugs.to_translate
183
192
  else
184
193
  res = slug
185
194
  (1..9999).each do |i|
186
- p = posts.find_by_slug(res)
187
- break if !p.present? || (p.present? && p.id == post_id)
195
+ p = posts.find_by_slug(res) # rubocop:disable Rails/DynamicFindBy
196
+ break if p.blank? || (p.present? && p.id == post_id)
188
197
 
189
198
  res = "#{slug}-#{i}"
190
199
  end
@@ -211,7 +220,7 @@ module CamaleonCms
211
220
  # if the anonymous user not exist, will create one
212
221
  def get_anonymous_user
213
222
  user = users.where(username: 'anonymous').first
214
- unless user.present?
223
+ if user.blank?
215
224
  pass = "anonymous#{rand(9999)}"
216
225
  user = users.create({ email: 'anonymous_user@local.com', username: 'anonymous', password: pass,
217
226
  password_confirmation: pass, first_name: 'Anonymous' })
@@ -221,7 +230,8 @@ module CamaleonCms
221
230
 
222
231
  # return the domain for current site
223
232
  # sample: mysite.com | sample.mysite.com
224
- # also, you can define custom domain for this site by: my_site.site_domain = 'my_site.com' # used for sites with different domains to call from console or task
233
+ # also, you can define custom domain for this site by: my_site.site_domain = 'my_site.com'
234
+ # used for sites with different domains to call from console or task
225
235
  def get_domain
226
236
  @site_domain || (if main_site?
227
237
  slug
@@ -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
@@ -2,6 +2,7 @@ module CamaleonCms
2
2
  class TermTaxonomy < CamaleonRecord
3
3
  include CamaleonCms::Metas
4
4
  include CamaleonCms::CustomFieldsRead
5
+ include CamaleonCms::ContentShortcodeGate
5
6
 
6
7
  extend CamaleonCms::NormalizeAttrs
7
8
 
@@ -14,10 +15,57 @@ module CamaleonCms
14
15
  end
15
16
 
16
17
  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
18
+ # attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order,
19
+ # :user_id
18
20
  # attr_accessible :data_options
19
21
  # attr_accessible :data_metas
20
22
 
23
+ self.inheritance_column = :taxonomy
24
+
25
+ def self.sti_name
26
+ name.demodulize.underscore
27
+ end
28
+
29
+ def self.polymorphic_name
30
+ name.demodulize
31
+ end
32
+
33
+ def self.find_sti_class(type_name)
34
+ # Handle explicit exceptions where database string doesn't match namespacing layout
35
+ case type_name.to_s
36
+ when 'widget'
37
+ return CamaleonCms::Widget::Main
38
+ when 'sidebar'
39
+ return CamaleonCms::Widget::Sidebar
40
+ end
41
+
42
+ # Standard conversion for "site" -> "CamaleonCms::Site"
43
+ # or "nav_menu_item" -> "CamaleonCms::NavMenuItem"
44
+ klass = begin
45
+ "CamaleonCms::#{type_name.camelize}".constantize
46
+ rescue NameError
47
+ nil
48
+ end
49
+ # The ancestry guard keeps a value like "meta" from instantiating an unrelated
50
+ # CamaleonCms class against the term_taxonomy table.
51
+ return klass if klass && klass <= base_class
52
+
53
+ # Runtime scan across loaded taxonomy models.
54
+ found = CamaleonCms::TermTaxonomy.descendants.find { |k| k.sti_name == type_name.to_s }
55
+ return found if found
56
+
57
+ # `super` resolves fully-qualified external classes (plugin STI outside the CamaleonCms
58
+ # namespace) and enforces ancestry — the scan above only sees classes already loaded, so
59
+ # without this an unloaded plugin taxonomy silently degraded to the base class. Same
60
+ # fallback PostDefault.find_sti_class keeps. Values it cannot place load as the base class,
61
+ # as every row did before native STI.
62
+ begin
63
+ super
64
+ rescue ActiveRecord::SubclassNotFound
65
+ base_class
66
+ end
67
+ end
68
+
21
69
  # callbacks
22
70
  before_validation :before_validating
23
71
  before_destroy :destroy_dependencies
@@ -25,13 +73,19 @@ module CamaleonCms
25
73
  # validates
26
74
  validates :name, :taxonomy, presence: true
27
75
  validates_with CamaleonCms::UniqValidator
76
+ # A taxonomy/widget description is expanded by do_shortcode at render
77
+ # (TermTaxonomyDecorator#the_content, shortcode_templates/widget.html.erb); STI-covers every
78
+ # subclass (post types, categories, tags, widgets, sidebars). Gate authorship behind
79
+ # content_shortcodes -- benign descriptions pass without ever building an Ability.
80
+ gate_content_shortcodes :description
28
81
 
29
82
  # relations
30
- has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship', foreign_key: :term_taxonomy_id,
83
+ has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
31
84
  dependent: :destroy
32
85
  # 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
86
+ belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy', optional: true
87
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
88
+ inverse_of: false
35
89
 
36
90
  # return all children taxonomy
37
91
  # 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,59 @@ 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
202
+ },
203
+ {
204
+ key: 'content_shortcodes',
205
+ label: I18n.t('camaleon_cms.admin.users.roles_values.content_shortcodes',
206
+ default: 'Allow shortcodes in content').to_s,
207
+ color: 'danger',
208
+ description: I18n.t(
209
+ 'camaleon_cms.admin.users.tool_tip.content_shortcodes',
210
+ default: 'Permit users with this role to publish shortcodes in content that the CMS expands at ' \
211
+ 'render: post content, custom-field values, taxonomy content and widget descriptions. A ' \
212
+ 'shortcode makes theme/plugin code emit arbitrary HTML/JS on the page, which the ' \
213
+ 'content scan cannot judge. Without it, a save carrying a registered shortcode is refused ' \
214
+ 'rather than cleaned up. Site-wide, and an end-run around the per-post-type "Allow ' \
215
+ 'unfiltered HTML in post content" permission.'
216
+ ).to_s
142
217
  }
143
218
  ]
144
219
  }.freeze
@@ -146,7 +221,7 @@ module CamaleonCms
146
221
  private
147
222
 
148
223
  def set_users_as_cilent
149
- site.users.where(role: slug).update_all(role: 'client')
224
+ site.users.where(role: slug).update_all(role: 'client') # rubocop:disable Rails/SkipsModelValidations
150
225
  end
151
226
  end
152
227
  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