camaleon_cms 2.9.1 → 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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -9
  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 +16 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/front_cache_helper.rb +57 -24
  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/category.html.erb +1 -1
  19. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  20. data/app/apps/themes/default/views/post_tag.html.erb +1 -1
  21. data/app/apps/themes/default/views/post_type.html.erb +1 -1
  22. data/app/apps/themes/default/views/search.html.erb +1 -1
  23. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  24. data/app/apps/themes/new/custom_helper.rb +5 -9
  25. data/app/apps/themes/new/views/category.html.erb +1 -1
  26. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  27. data/app/apps/themes/new/views/post_tag.html.erb +1 -1
  28. data/app/apps/themes/new/views/post_type.html.erb +1 -1
  29. data/app/apps/themes/new/views/search.html.erb +1 -1
  30. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  31. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  32. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  33. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  34. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +29 -10
  35. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  36. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +9 -2
  37. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +4 -3
  38. data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +2 -2
  39. data/app/controllers/camaleon_cms/admin/categories_controller.rb +27 -8
  40. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  41. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  42. data/app/controllers/camaleon_cms/admin/media_controller.rb +84 -19
  43. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  44. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +15 -6
  45. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  46. data/app/controllers/camaleon_cms/admin/posts_controller.rb +58 -22
  47. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +238 -45
  48. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +103 -18
  49. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +17 -5
  50. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +33 -7
  51. data/app/controllers/camaleon_cms/admin/settings_controller.rb +11 -6
  52. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +9 -7
  53. data/app/controllers/camaleon_cms/admin/users_controller.rb +89 -18
  54. data/app/controllers/camaleon_cms/admin_controller.rb +144 -26
  55. data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +5 -0
  56. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  57. data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
  58. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +44 -0
  59. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  60. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  61. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  62. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  63. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  64. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  65. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  66. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  67. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  68. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  69. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  70. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  71. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  72. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  73. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  74. data/app/decorators/camaleon_cms/post_decorator.rb +67 -51
  75. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  76. data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
  77. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  78. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  79. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  80. data/app/helpers/camaleon_cms/admin/application_helper.rb +21 -24
  81. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  82. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  83. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  84. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +51 -31
  85. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  86. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  87. data/app/helpers/camaleon_cms/comment_helper.rb +87 -41
  88. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  89. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  90. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  91. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +144 -67
  92. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  93. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  94. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  95. data/app/helpers/camaleon_cms/html_helper.rb +73 -38
  96. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  97. data/app/helpers/camaleon_cms/session_helper.rb +195 -39
  98. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  99. data/app/helpers/camaleon_cms/site_helper.rb +43 -9
  100. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  101. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -422
  102. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  103. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  104. data/app/models/camaleon_cms/ability.rb +65 -105
  105. data/app/models/camaleon_cms/category.rb +7 -5
  106. data/app/models/camaleon_cms/custom_field.rb +29 -6
  107. data/app/models/camaleon_cms/custom_field_group.rb +75 -21
  108. data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
  109. data/app/models/camaleon_cms/media.rb +8 -2
  110. data/app/models/camaleon_cms/meta.rb +2 -2
  111. data/app/models/camaleon_cms/nav_menu.rb +6 -2
  112. data/app/models/camaleon_cms/nav_menu_item.rb +18 -8
  113. data/app/models/camaleon_cms/plugin.rb +4 -3
  114. data/app/models/camaleon_cms/post.rb +113 -27
  115. data/app/models/camaleon_cms/post_comment.rb +10 -6
  116. data/app/models/camaleon_cms/post_default.rb +42 -12
  117. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  118. data/app/models/camaleon_cms/post_tag.rb +4 -3
  119. data/app/models/camaleon_cms/post_type.rb +22 -12
  120. data/app/models/camaleon_cms/site.rb +40 -28
  121. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  122. data/app/models/camaleon_cms/term_taxonomy.rb +51 -5
  123. data/app/models/camaleon_cms/theme.rb +3 -3
  124. data/app/models/camaleon_cms/user.rb +17 -5
  125. data/app/models/camaleon_cms/user_role.rb +77 -4
  126. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  127. data/app/models/camaleon_cms/widget/main.rb +13 -7
  128. data/app/models/camaleon_cms/widget/sidebar.rb +3 -6
  129. data/app/models/camaleon_record.rb +123 -5
  130. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
  131. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  132. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +115 -58
  133. data/app/models/concerns/camaleon_cms/metas.rb +32 -7
  134. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  135. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  136. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  137. data/app/models/current_request.rb +23 -0
  138. data/app/uploaders/camaleon_cms_aws_uploader.rb +39 -11
  139. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  140. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  141. data/app/validators/camaleon_cms/post_uniq_validator.rb +21 -8
  142. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  143. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  144. data/app/views/camaleon_cms/admin/appearances/nav_menus/_left_menu_items.html.erb +2 -2
  145. data/app/views/camaleon_cms/admin/appearances/widgets/main/form.html.erb +1 -1
  146. data/app/views/camaleon_cms/admin/categories/index.html.erb +3 -3
  147. data/app/views/camaleon_cms/admin/comments/index.html.erb +2 -2
  148. data/app/views/camaleon_cms/admin/comments/list.html.erb +1 -1
  149. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  150. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  151. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  152. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +3 -3
  153. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +2 -2
  154. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  155. data/app/views/camaleon_cms/admin/posts/index.html.erb +6 -6
  156. data/app/views/camaleon_cms/admin/search.html.erb +1 -1
  157. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  158. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  159. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  160. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  161. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +42 -6
  162. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  163. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb +1 -1
  164. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +5 -4
  165. data/app/views/camaleon_cms/admin/settings/custom_fields/index.html.erb +1 -1
  166. data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
  167. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  168. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  169. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  170. data/app/views/camaleon_cms/admin/settings/sites/index.html.erb +1 -1
  171. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +102 -7
  172. data/app/views/camaleon_cms/admin/user_roles/index.html.erb +1 -1
  173. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  174. data/app/views/camaleon_cms/admin/users/index.html.erb +2 -2
  175. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  176. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  177. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  178. data/app/views/layouts/camaleon_cms/admin/_flash_messages.html.erb +2 -2
  179. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  180. data/config/initializers/action_view.rb +42 -5
  181. data/config/initializers/active_record_extension.rb +13 -27
  182. data/config/initializers/assets.rb +52 -23
  183. data/config/initializers/custom_initializers.rb +3 -3
  184. data/config/locales/camaleon_cms/admin/ar.yml +7 -2
  185. data/config/locales/camaleon_cms/admin/de.yml +7 -2
  186. data/config/locales/camaleon_cms/admin/en.yml +39 -2
  187. data/config/locales/camaleon_cms/admin/es.yml +7 -2
  188. data/config/locales/camaleon_cms/admin/fr.yml +7 -2
  189. data/config/locales/camaleon_cms/admin/it.yml +7 -2
  190. data/config/locales/camaleon_cms/admin/nl.yml +8 -2
  191. data/config/locales/camaleon_cms/admin/pt-BR.yml +7 -2
  192. data/config/locales/camaleon_cms/admin/pt.yml +7 -2
  193. data/config/locales/camaleon_cms/admin/ru.yml +7 -2
  194. data/config/locales/camaleon_cms/admin/uk.yml +7 -2
  195. data/config/locales/camaleon_cms/admin/zh-CH.yml +7 -2
  196. data/config/routes/admin.rb +33 -15
  197. data/config/routes/frontend.rb +21 -15
  198. data/db/migrate/20150611161134_post_table_into_utf8.rb +14 -14
  199. data/db/migrate/20150926095310_rename_column_posts.rb +3 -3
  200. data/db/migrate/20151212095328_add_confirm_token_to_users.rb +3 -3
  201. data/db/migrate/20160504155652_add_feature_to_posts.rb +1 -1
  202. data/db/migrate/20160504155653_move_first_name_of_users.rb +2 -2
  203. data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +1 -1
  204. data/db/migrate/20161215202255_drop_user_relationship_table.rb +1 -1
  205. data/db/migrate/20180124132318_create_media.rb +1 -1
  206. data/db/migrate/20180704211100_adjust_field_length.rb +1 -1
  207. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  208. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  209. data/lib/camaleon_cms/content_security.rb +158 -0
  210. data/lib/camaleon_cms/engine.rb +47 -9
  211. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  212. data/lib/camaleon_cms/setup_token.rb +69 -0
  213. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  214. data/lib/camaleon_cms/task_reporter.rb +14 -0
  215. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  216. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  217. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  218. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  219. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  220. data/lib/camaleon_cms/uploader_support.rb +86 -0
  221. data/lib/camaleon_cms/version.rb +1 -1
  222. data/lib/camaleon_cms.rb +11 -0
  223. data/lib/ext/hash.rb +28 -2
  224. data/lib/ext/string.rb +13 -8
  225. data/lib/ext/translator.rb +10 -2
  226. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  227. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  228. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  229. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  230. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  231. data/lib/plugin_routes.rb +412 -368
  232. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  233. data/lib/tasks/cross_site_field_groups.rake +74 -0
  234. data/lib/tasks/custom_fields_roles.rake +58 -0
  235. data/lib/tasks/orphaned_comments.rake +42 -0
  236. data/lib/tasks/private_upload_acls.rake +31 -0
  237. data/lib/tasks/site_custom_field_groups.rake +36 -0
  238. data/lib/tasks/unsafe_stored_content.rake +42 -0
  239. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  240. data/lib/tasks/user_field_groups.rake +27 -0
  241. metadata +185 -7
@@ -12,9 +12,16 @@ module CamaleonCms
12
12
  can :read, :all
13
13
  else
14
14
  # conditions:
15
- current_user_role = user.get_role(current_site) || current_site.user_roles.new
16
- @roles_manager ||= current_user_role.get_meta("_manager_#{current_site.id}", {}) || {}
17
- @roles_post_type ||= current_user_role.get_meta("_post_type_#{current_site.id}", {}) || {}
15
+ # Fetch the role record fresh from the database for the current site to
16
+ # ensure up-to-date role meta (avoid stale cached role objects during
17
+ # tests or runtime meta updates).
18
+ current_user_role = if current_site.present?
19
+ current_site.user_roles.where(slug: user.role).first
20
+ else
21
+ user.get_role(current_site)
22
+ end || current_site.user_roles.new
23
+ @roles_manager = current_user_role.get_meta("_manager_#{current_site.id}", {}) || {}
24
+ @roles_post_type = current_user_role.get_meta("_post_type_#{current_site.id}", {}) || {}
18
25
 
19
26
  ids_publish = @roles_post_type[:publish] || []
20
27
  ids_edit = @roles_post_type[:edit] || []
@@ -24,83 +31,49 @@ module CamaleonCms
24
31
  ids_delete_other = @roles_post_type[:delete_other] || []
25
32
  ids_delete_publish = @roles_post_type[:delete_publish] || []
26
33
 
27
- can :posts, CamaleonCms::PostType do |pt|
34
+ safe_can :posts, CamaleonCms::PostType do |pt|
28
35
  (ids_edit + ids_edit_other + ids_edit_publish).to_i.include?(pt.id)
29
- rescue StandardError
30
- false
31
36
  end
32
37
 
33
- can :create_post, CamaleonCms::PostType do |pt|
38
+ safe_can :create_post, CamaleonCms::PostType do |pt|
34
39
  ids_edit.to_i.include?(pt.id)
35
- rescue StandardError
36
- false
37
40
  end
38
- can :publish_post, CamaleonCms::PostType do |pt|
41
+ safe_can :publish_post, CamaleonCms::PostType do |pt|
39
42
  ids_publish.to_i.include?(pt.id)
40
- rescue StandardError
41
- false
42
43
  end
43
- can :edit_other, CamaleonCms::PostType do |pt|
44
+ safe_can :edit_other, CamaleonCms::PostType do |pt|
44
45
  ids_edit_other.to_i.include?(pt.id)
45
- rescue StandardError
46
- false
47
46
  end
48
- can :edit_publish, CamaleonCms::PostType do |pt|
47
+ safe_can :edit_publish, CamaleonCms::PostType do |pt|
49
48
  ids_edit_publish.to_i.include?(pt.id)
50
- rescue StandardError
51
- false
52
49
  end
53
50
 
54
- can :categories, CamaleonCms::PostType do |pt|
51
+ safe_can :categories, CamaleonCms::PostType do |pt|
55
52
  @roles_post_type[:manage_categories].to_i.include?(pt.id)
56
- rescue StandardError
57
- false
58
53
  end
59
- can :post_tags, CamaleonCms::PostType do |pt|
54
+ safe_can :post_tags, CamaleonCms::PostType do |pt|
60
55
  @roles_post_type[:manage_tags].to_i.include?(pt.id)
61
- rescue StandardError
62
- false
56
+ end
57
+ safe_can :post_content_unfiltered_html, CamaleonCms::PostType do |pt|
58
+ ids_unfiltered = @roles_post_type[:post_content_unfiltered_html] || []
59
+ ids_unfiltered.to_i.include?(pt.id)
63
60
  end
64
61
 
65
- can :update, CamaleonCms::Post do |post|
62
+ safe_can :update, CamaleonCms::Post do |post|
66
63
  pt_id = post.post_type.id
67
64
  r = false
68
- r ||= begin
69
- ids_edit.to_i.include?(pt_id) && post.user_id == user.id
70
- rescue StandardError
71
- false
72
- end
73
- r ||= begin
74
- ids_edit_publish.to_i.include?(pt_id) && post.published?
75
- rescue StandardError
76
- false
77
- end
78
- r ||= begin
79
- ids_edit_other.to_i.include?(pt_id) && post.user_id != user.id
80
- rescue StandardError
81
- false
82
- end
65
+ r ||= ids_edit.to_i.include?(pt_id) && post.user_id == user.id
66
+ r ||= ids_edit_publish.to_i.include?(pt_id) && post.published?
67
+ r ||= ids_edit_other.to_i.include?(pt_id) && post.user_id != user.id
83
68
  r
84
69
  end
85
70
 
86
- can :destroy, CamaleonCms::Post do |post|
71
+ safe_can :destroy, CamaleonCms::Post do |post|
87
72
  pt_id = post.post_type.id
88
73
  r = false
89
- r ||= begin
90
- ids_delete.to_i.include?(pt_id) && post.user_id == user.id
91
- rescue StandardError
92
- false
93
- end
94
- r ||= begin
95
- ids_delete_publish.to_i.include?(pt_id) && post.published?
96
- rescue StandardError
97
- false
98
- end
99
- r ||= begin
100
- ids_delete_other.to_i.include?(pt_id) && post.user_id != user.id
101
- rescue StandardError
102
- false
103
- end
74
+ r ||= ids_delete.to_i.include?(pt_id) && post.user_id == user.id
75
+ r ||= ids_delete_publish.to_i.include?(pt_id) && post.published?
76
+ r ||= ids_delete_other.to_i.include?(pt_id) && post.user_id != user.id
104
77
  r
105
78
  end
106
79
 
@@ -109,60 +82,13 @@ module CamaleonCms
109
82
  @roles_post_type.each do |k, v|
110
83
  next if %w[edit edit_other edit_publish publish manage_categories].include?(k.to_s)
111
84
 
112
- can k.to_sym, CamaleonCms::PostType do |pt|
85
+ safe_can k.to_sym, CamaleonCms::PostType do |pt|
113
86
  v.include?(pt.id.to_s)
114
- rescue StandardError
115
- false
116
87
  end
117
88
  end
118
89
 
119
90
  # others
120
- begin
121
- can :manage, :media if @roles_manager[:media]
122
- rescue StandardError
123
- false
124
- end
125
- begin
126
- can :manage, :comments if @roles_manager[:comments]
127
- rescue StandardError
128
- false
129
- end
130
- # can :manage, :forms if @roles_manager[:forms] rescue false
131
- begin
132
- can :manage, :themes if @roles_manager[:themes]
133
- rescue StandardError
134
- false
135
- end
136
- begin
137
- can :manage, :widgets if @roles_manager[:widgets]
138
- rescue StandardError
139
- false
140
- end
141
- begin
142
- can :manage, :nav_menu if @roles_manager[:nav_menu]
143
- rescue StandardError
144
- false
145
- end
146
- begin
147
- can :manage, :plugins if @roles_manager[:plugins]
148
- rescue StandardError
149
- false
150
- end
151
- begin
152
- can :manage, :users if @roles_manager[:users]
153
- rescue StandardError
154
- false
155
- end
156
- begin
157
- can :manage, :settings if @roles_manager[:settings]
158
- rescue StandardError
159
- false
160
- end
161
- @roles_manager.try(:each) do |rol_manage_key, val_role|
162
- can :manage, rol_manage_key.to_sym if val_role.to_s.cama_true?
163
- rescue StandardError
164
- false
165
- end
91
+ define_manage_rules
166
92
  end
167
93
  cannot :impersonate, CamaleonCms::User do |u|
168
94
  u.id == user.id
@@ -182,5 +108,39 @@ module CamaleonCms
182
108
  def cannot?(*args)
183
109
  !can?(*args)
184
110
  end
111
+
112
+ private
113
+
114
+ # Wraps a can rule with block-level exception handling.
115
+ # Blocks are evaluated later during authorization checks (can? calls), so exceptions
116
+ # from accessing post properties, role metadata, etc. must be caught here to fail closed.
117
+ # Non-block calls (e.g., can :manage, :symbol) do not need wrapping; can itself is safe.
118
+ def safe_can(action, subject, &block)
119
+ if block_given?
120
+ can(action, subject) do |resource|
121
+ safely_false { yield(resource) }
122
+ end
123
+ else
124
+ # No block: can(action, subject) does not evaluate user logic; safe to call directly.
125
+ can(action, subject)
126
+ end
127
+ end
128
+
129
+ # Fails closed for permission checks when role/post metadata is malformed.
130
+ # Used by safe_can to guard block evaluation during authorization checks.
131
+ def safely_false
132
+ yield
133
+ rescue StandardError
134
+ false
135
+ end
136
+
137
+ def define_manage_rules
138
+ %i[media comments themes widgets nav_menu plugins users settings custom_fields select_eval].each do |resource|
139
+ safe_can :manage, resource if @roles_manager[resource]
140
+ end
141
+ @roles_manager.each do |rol_manage_key, val_role|
142
+ safe_can :manage, rol_manage_key.to_sym if val_role.to_s.cama_true?
143
+ end
144
+ end
185
145
  end
186
146
  end
@@ -1,19 +1,21 @@
1
1
  module CamaleonCms
2
2
  class Category < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:description)
4
+
3
5
  alias_attribute :site_id, :term_group
4
6
  alias_attribute :post_type_id, :status
5
7
 
6
- default_scope { where(taxonomy: :category) }
7
8
  scope :no_empty, -> { where('count > 0') } # return all categories that contains at least one post
8
9
  scope :empty, -> { where(count: [0, nil]) } # return all categories that does not contain any post
9
10
  # scope :parents, -> { where("term_taxonomy.parent_id IS NULL") }
10
11
 
11
12
  has_many :posts, foreign_key: :objectid, through: :term_relationships, source: :object
12
- has_many :children, class_name: 'CamaleonCms::Category', foreign_key: :parent_id, dependent: :destroy
13
- belongs_to :parent, class_name: 'CamaleonCms::Category', foreign_key: :parent_id, required: false
13
+ has_many :children, class_name: 'CamaleonCms::Category', foreign_key: :parent_id, dependent: :destroy,
14
+ inverse_of: :parent
15
+ belongs_to :parent, class_name: 'CamaleonCms::Category', optional: true
14
16
  belongs_to :post_type_parent, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id,
15
- inverse_of: :categories, required: false
16
- belongs_to :site, required: false
17
+ inverse_of: :categories, optional: true
18
+ belongs_to :site, optional: true
17
19
 
18
20
  before_save :set_site
19
21
  before_destroy :set_posts_in_default_category
@@ -14,17 +14,27 @@ module CamaleonCms
14
14
  # status: nil -> visible on list group fields
15
15
  # attr_accessible :object_class, :objectid, :description, :parent_id, :count, :name, :slug,
16
16
  # :field_order, :status, :is_repeat
17
+
18
+ # Scope metas by object_class. Meta rows are keyed by (objectid, object_class), so a custom
19
+ # field whose numeric id collides with another model's id (e.g. a Post) would otherwise read
20
+ # the wrong "_default" meta and lose its field_key. See regression: TinyMCE editor field
21
+ # rendered as a plain text_box on Theme settings.
22
+ # rubocop:disable Rails/InverseOf
17
23
  has_many :metas, -> { where(object_class: 'CustomField') }, foreign_key: :objectid, dependent: :destroy
18
- has_many :values, class_name: 'CamaleonCms::CustomFieldsRelationship',
19
- foreign_key: :custom_field_id, dependent: :destroy
20
- belongs_to :custom_field_group, required: false
21
- belongs_to :parent, class_name: 'CamaleonCms::CustomField', foreign_key: :parent_id, required: false
24
+ # rubocop:enable Rails/InverseOf
25
+ has_many :values, class_name: 'CamaleonCms::CustomFieldsRelationship', dependent: :destroy
26
+
27
+ belongs_to :custom_field_group, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :parent_id,
28
+ optional: true, inverse_of: :fields
29
+ belongs_to :parent, class_name: 'CamaleonCms::CustomField', optional: true
30
+ belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
22
31
 
23
32
  validates :name, :object_class, presence: true
24
- validates_uniqueness_of :slug, scope: %i[parent_id object_class],
25
- unless: ->(o) { o.is_a?(CamaleonCms::CustomFieldGroup) }
33
+ validates :slug, uniqueness: { scope: %i[parent_id object_class],
34
+ unless: ->(o) { o.is_a?(CamaleonCms::CustomFieldGroup) } }
26
35
 
27
36
  before_validation :before_validating
37
+ before_update :check_select_eval_authorization
28
38
 
29
39
  private
30
40
 
@@ -32,5 +42,18 @@ module CamaleonCms
32
42
  self.slug ||= name
33
43
  self.slug = slug.to_s.parameterize
34
44
  end
45
+
46
+ # Prevent unauthorized modification of field_key to select_eval
47
+ def check_select_eval_authorization
48
+ return unless respond_to?(:options) && options.present?
49
+
50
+ # Check if field_key is being changed to select_eval
51
+ return unless options[:field_key] == 'select_eval'
52
+ # Allow if user has explicit permission
53
+ return if can?(:manage, :select_eval)
54
+
55
+ errors.add(:base, 'Not authorized to create or modify select_eval fields')
56
+ throw :abort
57
+ end
35
58
  end
36
59
  end
@@ -5,16 +5,22 @@ module CamaleonCms
5
5
  alias_attribute :site_id, :parent_id
6
6
 
7
7
  default_scope do
8
- where("object_class != '_fields'")
9
- .reorder("#{CamaleonCms::CustomField.table_name}.field_order ASC")
8
+ where("object_class != '_fields'").reorder("#{CamaleonCms::CustomField.table_name}.field_order ASC")
10
9
  end
11
10
 
11
+ # Scope metas by object_class so a group's numeric id colliding with another model's id does
12
+ # not read the wrong meta rows (meta rows are keyed by both objectid and object_class).
13
+ # rubocop:disable Rails/InverseOf
12
14
  has_many :metas, -> { where(object_class: 'CustomFieldGroup') }, foreign_key: :objectid, dependent: :destroy
15
+ # rubocop:enable Rails/InverseOf
16
+
13
17
  has_many :fields, -> { where(object_class: '_fields') }, class_name: 'CamaleonCms::CustomField',
14
- foreign_key: :parent_id, dependent: :destroy
15
- belongs_to :site, foreign_key: :parent_id, required: false
18
+ foreign_key: :parent_id, dependent: :destroy,
19
+ inverse_of: :custom_field_group
20
+ belongs_to :site, foreign_key: :parent_id, optional: true, inverse_of: :custom_field_groups
21
+ belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
16
22
 
17
- validates_uniqueness_of :slug, scope: %i[object_class objectid parent_id]
23
+ validates :slug, uniqueness: { scope: %i[object_class objectid parent_id] }
18
24
 
19
25
  before_validation :before_validating
20
26
 
@@ -30,7 +36,16 @@ module CamaleonCms
30
36
  # ****** check all options for each case in Admin::CustomFieldsHelper ****
31
37
  # SAMPLE: my_model.add_field({"name"=>"Sub Title", "slug"=>"subtitle"}, {"field_key"=>"text_box",
32
38
  # "translate"=>true, default_value: "Get in Touch"})
39
+ # Adds a manual field to the group (used by admin UI)
40
+ # item: field attributes
41
+ # options: field options
33
42
  def add_manual_field(item, options)
43
+ # Prevent creation of dangerous field types (select_eval) unless the actor is allowed.
44
+ if options[:field_key] == 'select_eval'
45
+ can?(:manage, :select_eval) ||
46
+ raise(CanCan::AccessDenied, 'Not authorized to create select_eval fields')
47
+ end
48
+
34
49
  c = get_field(item[:slug] || item['slug'])
35
50
  return c if c.present?
36
51
 
@@ -43,13 +58,15 @@ module CamaleonCms
43
58
  end
44
59
  alias add_field add_manual_field
45
60
 
46
- # return a field with slug = slug from current group
61
+ # return a field with slug = slug from the current group
47
62
  def get_field(slug)
48
- fields.find_by(slug: slug)
63
+ fields.find_by_slug(slug) # rubocop:disable Rails/DynamicFindBy
49
64
  end
50
65
 
51
66
  # only used by form on admin panel (protected)
52
67
  # return array of failed_fields and full_fields [[failed fields], [all fields]]
68
+ # items: hash of field items
69
+ # item_options: hash of options for each item
53
70
  def add_fields(items, item_options)
54
71
  fields.where.not(id: items.to_h.map { |_k, obj| obj['id'] }.uniq).destroy_all
55
72
  cache_fields = []
@@ -57,12 +74,38 @@ module CamaleonCms
57
74
  errors_saved = []
58
75
  if items.present?
59
76
  items.each do |i, item|
77
+ # allow string or symbol keys for incoming params
78
+ id_val = item['id'] || item[:id]
79
+
60
80
  item[:field_order] = order_index
61
81
  options = item_options[i] || {}
62
- if item[:id].present? && (field_item = fields.find_by(id: item[:id])).present?
82
+ if id_val.present? && (field_item = fields.find_by(id: id_val)).present?
83
+ # If this is an existing select_eval field (or the incoming data would
84
+ # make it a select_eval) ensure the current actor has explicit
85
+ # permission. For updates, we preserve the form-like behaviour by
86
+ # collecting an error-like non-persisted field in errors_saved and
87
+ # skipping the update when unauthorized.
88
+ existing_key = (field_item.options || {})[:field_key].to_s
89
+ # consider field_key coming from the per-item options (options) or the item itself
90
+ incoming_key = (options[:field_key] || item[:field_key]).to_s
91
+ if (existing_key == 'select_eval' || incoming_key == 'select_eval') && !can?(:manage, :select_eval)
92
+ field_item.errors.add(:base, 'Not authorized to modify select_eval field')
93
+ errors_saved << field_item
94
+ next
95
+ end
96
+
63
97
  saved = field_item.update(item)
64
98
  cache_fields << field_item
65
99
  else
100
+ # Check if the incoming options request creation of select_eval
101
+ incoming_key = (options[:field_key] || item[:field_key]).to_s
102
+ if incoming_key == 'select_eval' && !can?(:manage, :select_eval)
103
+ # Add an error-like non-persisted field to errors_saved to preserve behaviour
104
+ field_item = fields.new(item)
105
+ field_item.errors.add(:base, 'Not authorized to create select_eval field')
106
+ errors_saved << field_item
107
+ next
108
+ end
66
109
  field_item = fields.new(item)
67
110
  cache_fields << field_item
68
111
  saved = field_item.save
@@ -79,6 +122,11 @@ module CamaleonCms
79
122
  end
80
123
 
81
124
  # generate the caption for this group
125
+ # The caption is rendered with `raw` by admin/settings/custom_fields/index.html.erb, so every
126
+ # interpolated value is escaped here, at the source: `the_title` already returns an escaped
127
+ # SafeBuffer, and the plain attributes below are escaped explicitly. `object_class` is included
128
+ # because the placement check admits any class name paired with the current site's id, so it is
129
+ # attacker-settable text like the model names are.
82
130
  def get_caption
83
131
  caption = ''
84
132
  begin
@@ -89,16 +137,19 @@ module CamaleonCms
89
137
  caption = "Fields for Categories in <b>#{site.post_types.find(objectid).decorate.the_title}</b>"
90
138
  when 'PostType_PostTag'
91
139
  caption = "Fields for Post tags in <b>#{site.post_types.find(objectid).decorate.the_title}</b>"
92
- when 'Widget::Main'
93
- caption = "Fields for Widget <b>(#{CamaleonCms::Widget::Main.find(objectid).name.translate})</b>"
140
+ when 'Main'
141
+ widget_name = CamaleonCms::Widget::Main.find(objectid).name.translate
142
+ caption = "Fields for Widget <b>(#{ERB::Util.html_escape(widget_name)})</b>"
94
143
  when 'Theme'
95
- caption = "Field settings for Theme <b>(#{begin
144
+ theme_name = begin
96
145
  site.themes.find(objectid).name
97
146
  rescue StandardError
98
147
  objectid
99
- end})</b>"
148
+ end
149
+ caption = "Field settings for Theme <b>(#{ERB::Util.html_escape(theme_name)})</b>"
100
150
  when 'NavMenu'
101
- caption = "Field settings for Menus <b>(#{CamaleonCms::NavMenu.find(objectid).name})</b>"
151
+ menu_name = CamaleonCms::NavMenu.find(objectid).name
152
+ caption = "Field settings for Menus <b>(#{ERB::Util.html_escape(menu_name)})</b>"
102
153
  when 'Site'
103
154
  caption = 'Field settings the site'
104
155
  when 'PostType'
@@ -107,25 +158,28 @@ module CamaleonCms
107
158
  p = CamaleonCms::Post.find(objectid).decorate
108
159
  caption = "Fields for content <b>(#{p.the_title})</b>"
109
160
  else # 'Plugin' or other class
110
- caption = "Fields for <b>#{object_class}</b>"
161
+ caption = "Fields for <b>#{ERB::Util.html_escape(object_class)}</b>"
111
162
  end
112
163
  rescue StandardError => e
113
164
  Rails.logger.debug "Camaleon CMS - Menu Item Error: #{e.message} ==> Attrs: #{attributes}"
114
165
  end
115
- caption
166
+ # rubocop:disable Rails/OutputSafety -- every interpolated value above is escaped at the source
167
+ caption.html_safe
168
+ # rubocop:enable Rails/OutputSafety
116
169
  end
117
170
 
118
171
  private
119
172
 
120
173
  def before_validating
121
- self.slug = "_group-#{name.to_s.parameterize}" unless slug.present?
174
+ self.slug = "_group-#{name.to_s.parameterize}" if slug.blank?
122
175
  end
123
176
 
124
177
  # auto save the default field values
125
178
  def auto_save_default_values(field, options)
126
179
  class_name = object_class.split('_').first
127
- return unless %w[Post Category Plugin
128
- Theme].include?(class_name) && objectid && (options[:default_value].present? || options[:default_values].present?)
180
+ return unless %w[Post Category Plugin Theme].include?(class_name) &&
181
+ objectid &&
182
+ (options[:default_value].present? || options[:default_values].present?)
129
183
 
130
184
  owner = if class_name == 'Theme'
131
185
  "CamaleonCms::#{class_name}".constantize.find(objectid) # owner model
@@ -133,13 +187,13 @@ module CamaleonCms
133
187
  begin
134
188
  "CamaleonCms::#{class_name}".constantize.find(objectid)
135
189
  rescue StandardError
136
- "CamaleonCms::#{class_name}".constantize.find_by(slug: objectid)
190
+ "CamaleonCms::#{class_name}".constantize.find_by_slug(objectid) # rubocop:disable Rails/DynamicFindBy
137
191
  end
138
192
  end
139
193
  (options[:default_values] || [options[:default_value]] || []).each do |value|
140
194
  if owner.present?
141
- owner.custom_field_values.create!(custom_field_id: field.id, custom_field_slug: field.slug,
142
- value: fix_meta_value(value))
195
+ owner.custom_field_values
196
+ .create!(custom_field_id: field.id, custom_field_slug: field.slug, value: fix_meta_value(value))
143
197
  end
144
198
  end
145
199
  end
@@ -7,23 +7,154 @@ 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', optional: true
11
+ belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
11
12
 
12
13
  # validates :objectid, :custom_field_id, presence: true
13
14
  validates :custom_field_id, presence: true # error on clone model
14
15
 
16
+ # Rendered positions that need a save-time gate (audit M17, scan-and-reject policy). An
17
+ # `editor` value is emitted as markup (`raw`); a `field_attrs` value is a JSON pair whose
18
+ # members are emitted as markup; the URI field types are emitted into href/src. Every other
19
+ # field type's value renders through escaping ERB as element content, where markup cannot
20
+ # execute, so it needs no gate.
21
+ MARKUP_FIELD_KEYS = %w[editor].freeze
22
+ JSON_MARKUP_FIELD_KEYS = %w[field_attrs].freeze
23
+ URI_FIELD_KEYS = %w[url image audio video file].freeze
24
+ GATED_FIELD_KEYS = (MARKUP_FIELD_KEYS + JSON_MARKUP_FIELD_KEYS + URI_FIELD_KEYS).freeze
25
+
26
+ validate :reject_untrusted_dangerous_value
27
+
15
28
  after_save :set_parent_slug
16
29
  after_save :update_model_owner # TODO: convert this model into polymorphic
17
30
 
31
+ # Opt-out for trusted server-side pipelines (imports, seeds, plugin code), mirroring
32
+ # Post#unfiltered_content!: a reader plus a bang enabler and NO writer, so mass assignment
33
+ # cannot reach it. Sticky for the lifetime of the instance.
34
+ attr_reader :unfiltered_value
35
+
36
+ def unfiltered_value!
37
+ @unfiltered_value = true
38
+ self
39
+ end
40
+
41
+ class << self
42
+ # Whether this field type's value is emitted into a markup or URL position (so it is gated).
43
+ def gated_field_key?(field_key)
44
+ GATED_FIELD_KEYS.include?(field_key.to_s)
45
+ end
46
+
47
+ # Why the save-time gate would refuse this value for the field type, or nil if it passes:
48
+ # :too_large (over the parse ceiling), :html (disallowed markup), :uri (script-capable URL).
49
+ # Ignores author trust, so the validation (trust-gated) and the scan_content rake task (which
50
+ # reports every stored row) share one dispatch and cannot drift.
51
+ def gate_rejection_reason(field_key, value)
52
+ return if value.blank?
53
+
54
+ key = field_key.to_s
55
+ if MARKUP_FIELD_KEYS.include?(key)
56
+ markup_rejection_reason(value)
57
+ elsif JSON_MARKUP_FIELD_KEYS.include?(key)
58
+ return :too_large if CamaleonCms::UnsafeMarkup.too_large?(value)
59
+
60
+ :html if json_member_values(value).any? { |member| markup_unsafe?(member) }
61
+ elsif URI_FIELD_KEYS.include?(key)
62
+ :uri if CamaleonCms::UnsafeMarkup.dangerous_uri?(value)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def markup_rejection_reason(value)
69
+ return :too_large if CamaleonCms::UnsafeMarkup.too_large?(value)
70
+
71
+ :html if markup_unsafe?(value)
72
+ end
73
+
74
+ def markup_unsafe?(value)
75
+ CamaleonCms::UnsafeMarkup.unsafe_html?(
76
+ value, tags: CamaleonCms::Post::CONTENT_ALLOWED_TAGS,
77
+ attributes: CamaleonCms::Post::CONTENT_ALLOWED_ATTRIBUTES
78
+ )
79
+ end
80
+
81
+ # A field_attrs value stores JSON whose string members render verbatim. Scan every DECODED
82
+ # member (Hash, Array or nested), not the stored bytes: the Rails JSON encoder unicode-escapes
83
+ # angle brackets (storing the escape instead of a literal bracket), so a byte-level scan of the
84
+ # stored string would pass markup that JSON.parse restores at render. Unparseable values are
85
+ # scanned as-is (fail closed on whatever the renderer would fall back to).
86
+ def json_member_values(value)
87
+ flatten_json_strings(JSON.parse(value))
88
+ rescue JSON::ParserError
89
+ [value.to_s]
90
+ end
91
+
92
+ def flatten_json_strings(node)
93
+ case node
94
+ when Hash then node.values.flat_map { |member| flatten_json_strings(member) }
95
+ when Array then node.flat_map { |member| flatten_json_strings(member) }
96
+ else [node.to_s]
97
+ end
98
+ end
99
+ end
100
+
18
101
  private
19
102
 
103
+ # Security (audit M17): the frontend renders an `editor`/`field_attrs` value verbatim and
104
+ # URI-type values into URL positions, so a value an untrusted author may not write is refused --
105
+ # never rewritten. Stored values therefore always equal authored values. The scan/dispatch lives
106
+ # in the class-level gate_rejection_reason so the scan_content rake task reuses it verbatim.
107
+ def reject_untrusted_dangerous_value
108
+ return if value.blank? || unfiltered_value
109
+
110
+ field_key = custom_field&.options&.[](:field_key).to_s
111
+ return unless self.class.gated_field_key?(field_key)
112
+ return if author_trusted_for_unfiltered_value?
113
+
114
+ reason = self.class.gate_rejection_reason(field_key, value)
115
+ return unless reason
116
+
117
+ errors.add(:base, cama_rejection_message(rejection_message_key(reason)))
118
+ end
119
+
120
+ def rejection_message_key(reason)
121
+ case reason
122
+ when :too_large then 'value_too_large'
123
+ when :uri then 'value_rejected_uri'
124
+ else 'value_rejected_html'
125
+ end
126
+ end
127
+
128
+ # The message must never be swallowed by a missing translation: only en.yml carries these keys,
129
+ # while the process locale follows the current admin/site language — fall back to English.
130
+ def cama_rejection_message(key)
131
+ full_key = "camaleon_cms.admin.custom_field.message.#{key}"
132
+ I18n.t(full_key, slug: custom_field_slug,
133
+ default: I18n.t(full_key, slug: custom_field_slug, locale: :en))
134
+ end
135
+
136
+ # Trust follows the post-content model: an admin may write anything; a post's field values may
137
+ # skip the gate when the saver holds post_content_unfiltered_html for the post type. Fails
138
+ # closed (the gate applies) without a request context — benign values still pass the scan.
139
+ def author_trusted_for_unfiltered_value?
140
+ user = CurrentRequest.user
141
+ site = CurrentRequest.site
142
+ return false if user.blank? || site.blank?
143
+ return true if user.admin?
144
+
145
+ parent = owner
146
+ return false unless parent.is_a?(CamaleonCms::Post) && parent.post_type.present?
147
+
148
+ CamaleonCms::Ability.new(user, site).can?(:post_content_unfiltered_html, parent.post_type)
149
+ end
150
+
20
151
  def set_parent_slug
21
152
  # self.update_column('custom_field_slug', self.custom_fields.slug)
22
153
  end
23
154
 
24
155
  # touch owner model
25
156
  def update_model_owner
26
- "CamaleonCms::#{object_class}".constantize.find(objectid).touch
157
+ "CamaleonCms::#{object_class}".constantize.find(objectid).touch # rubocop:disable Rails/SkipsModelValidations
27
158
  rescue StandardError
28
159
  nil
29
160
  end