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
@@ -4,7 +4,8 @@ module CamaleonCms
4
4
  # visiting sitemap.xml
5
5
  # With hook "on_render_sitemap" you can skip post_types, categories, tags or posts
6
6
  # you can change render file and layout
7
- # you can add custom sitemap elements in the attr "custom", like: https://github.com/owen2345/camaleon-cms/issues/106#issuecomment-146232211
7
+ # you can add custom sitemap elements in the attr "custom",
8
+ # like: https://github.com/owen2345/camaleon-cms/issues/106#issuecomment-146232211
8
9
  # you can customize your content for html or xml format
9
10
  def sitemap
10
11
  r = { layout: (params[:format] == 'html' ? nil : false), render: 'sitemap', custom: {}, format: params[:format],
@@ -31,21 +32,33 @@ module CamaleonCms
31
32
  # save comment from a post
32
33
  def save_comment
33
34
  flash[:comment_submit] = {}
34
- @post = current_site.posts.find_by_id(params[:post_id]).decorate
35
+ # Security (audit Low): this is a public endpoint. A post id naming no post used to reach
36
+ # `.decorate` on nil (500), and the anonymous branch indexed a missing post_comment param.
37
+ # Fail closed with a graceful error instead of a 500 an attacker can trigger at will.
38
+ @post = current_site.posts.find_by(id: params[:post_id])&.decorate
35
39
  user = cama_current_user
36
40
  comment_data = {}
37
- unless @post.can_commented?
38
- flash[:comment_submit][:error] =
39
- t('.comments_not_enabled', default: 'This post can not be commented')
41
+ if @post.nil?
42
+ flash[:comment_submit][:error] = t('.post_not_found', default: 'Post not found')
43
+ elsif !@post.can_commented?
44
+ flash[:comment_submit][:error] = t('.comments_not_enabled', default: 'This post can not be commented')
40
45
  end
46
+
47
+ # Security (audit Low): params[:post_comment] is attacker-controlled and may arrive as a scalar
48
+ # or array, not the expected hash. `|| {}` only covered nil; indexing a String/Array with a
49
+ # symbol key raises TypeError (500). Coerce anything not hash-shaped to an empty hash so every
50
+ # post_comment[...] read below is safe.
51
+ post_comment = params[:post_comment]
52
+ post_comment = {} unless post_comment.is_a?(ActionController::Parameters) || post_comment.is_a?(Hash)
53
+
41
54
  if user.present?
42
55
  comment_data[:author] = user.fullname
43
56
  comment_data[:author_email] = user.email
44
57
  elsif current_site.get_option('permit_anonimos_comment', false)
45
58
  user = current_site.get_anonymous_user
46
59
  comment_data[:is_anonymous] = true
47
- comment_data[:author] = params[:post_comment][:name]
48
- comment_data[:author_email] = params[:post_comment][:email]
60
+ comment_data[:author] = post_comment[:name]
61
+ comment_data[:author_email] = post_comment[:email]
49
62
  if current_site.is_enable_captcha_for_comments? && !cama_captcha_verified?
50
63
  flash[:comment_submit][:error] =
51
64
  t('camaleon_cms.admin.users.message.error_captcha',
@@ -56,24 +69,40 @@ module CamaleonCms
56
69
  unless flash[:comment_submit][:error]
57
70
  if user.present?
58
71
  comment_data[:user_id] = user.id
59
- comment_data[:author_url] = params[:post_comment][:url] || ''
72
+ comment_data[:author_url] = post_comment[:url] || ''
60
73
  comment_data[:author_IP] = request.remote_ip.to_s
61
74
  comment_data[:approved] = current_site.front_comment_status
62
- comment_data[:agent] = request.user_agent.force_encoding('ISO-8859-1').encode('UTF-8')
63
- comment_data[:content] = params[:post_comment][:content]
64
- @comment = params[:post_comment][:parent_id].present? ? @post.comments.find_by_id(params[:post_comment][:parent_id]).children.new(comment_data) : @post.comments.main.new(comment_data)
65
- if @comment.save
66
- flash[:comment_submit][:notice] = t('camaleon_cms.admin.comments.message.created')
75
+ # Browsers always send a User-Agent but API clients/bots may not; record it nil-safely
76
+ # and without mutating the request's header string in place.
77
+ comment_data[:agent] = request.user_agent.to_s.encode('UTF-8', 'ISO-8859-1')
78
+ comment_data[:content] = post_comment[:content]
79
+ # Security (audit Low): a crafted parent_id naming no comment on this post made find_by
80
+ # return nil and the chained `.children` raise (500). Resolve the parent first and fail
81
+ # closed with a graceful error when a supplied parent_id matches nothing.
82
+ parent_id = post_comment[:parent_id]
83
+ parent = @post.comments.find_by(id: parent_id) if parent_id.present?
84
+ if parent_id.present? && parent.nil?
85
+ flash[:comment_submit][:error] = t('.parent_comment_not_found', default: 'Parent comment not found')
67
86
  else
68
- flash[:comment_submit][:error] =
69
- "#{t('camaleon_cms.common.comment_error',
70
- default: 'An error was occurred on save comment')}:<br> #{@comment.errors.full_messages.join(', ')}"
87
+ @comment = parent ? parent.children.new(comment_data) : @post.comments.main.new(comment_data)
88
+ if @comment.save
89
+ flash[:comment_submit][:notice] = t('camaleon_cms.admin.comments.message.created')
90
+ else
91
+ base = t('camaleon_cms.common.comment_error', default: 'An error was occurred on save comment')
92
+ flash[:comment_submit][:error] = "#{base}:<br> #{@comment.errors.full_messages.join(', ')}"
93
+ end
71
94
  end
72
95
  else
73
96
  flash[:comment_submit][:error] = t('camaleon_cms.admin.message.unauthorized')
74
97
  end
75
98
  end
76
- params[:format] == 'json' ? render(json: flash.discard(:comment_submit).to_hash) : redirect_to(request.referer || @post.the_url(as_path: true))
99
+
100
+ return render(json: flash.discard(:comment_submit).to_hash) if params[:format] == 'json'
101
+
102
+ # Security (audit Low): the Referer header is attacker-controlled; redirecting to it raw is an
103
+ # open redirect (Rails < 7) or an UnsafeRedirectError 500 (Rails >= 7). Vet it through the shared
104
+ # same-host/allowlist redirect helper, falling back to the post URL (or root).
105
+ cama_safe_redirect(request.referer, @post&.the_url(as_path: true) || '/')
77
106
  end
78
107
  end
79
108
  end
@@ -0,0 +1,72 @@
1
+ module CamaleonCms
2
+ module FrontendVisitedStateConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ LEGACY_VISITED_IVAR_BY_ATTR = {
6
+ frontend_visited_home: :@cama_visited_home,
7
+ frontend_visited_post: :@cama_visited_post,
8
+ frontend_visited_ajax: :@cama_visited_ajax,
9
+ frontend_visited_search: :@cama_visited_search,
10
+ frontend_visited_post_type: :@cama_visited_post_type,
11
+ frontend_visited_tag: :@cama_visited_tag,
12
+ frontend_visited_category: :@cama_visited_category,
13
+ frontend_visited_profile: :@cama_visited_profile
14
+ }.freeze
15
+
16
+ private
17
+
18
+ def mark_frontend_home_visited
19
+ set_frontend_visited_state(:frontend_visited_home, true)
20
+ end
21
+
22
+ def mark_frontend_post_visited(post)
23
+ set_frontend_visited_state(:frontend_visited_post, post)
24
+ end
25
+
26
+ def mark_frontend_ajax_visited
27
+ set_frontend_visited_state(:frontend_visited_ajax, true)
28
+ end
29
+
30
+ def mark_frontend_search_visited
31
+ set_frontend_visited_state(:frontend_visited_search, true)
32
+ end
33
+
34
+ def mark_frontend_post_type_visited(post_type)
35
+ set_frontend_visited_state(:frontend_visited_post_type, post_type)
36
+ end
37
+
38
+ def mark_frontend_tag_visited(post_tag)
39
+ set_frontend_visited_state(:frontend_visited_tag, post_tag)
40
+ end
41
+
42
+ def mark_frontend_category_visited(category)
43
+ set_frontend_visited_state(:frontend_visited_category, category)
44
+ end
45
+
46
+ def mark_frontend_profile_visited(user = nil)
47
+ set_frontend_visited_state(:frontend_visited_profile, true)
48
+ CurrentRequest.frontend_user = user if user.present?
49
+ end
50
+
51
+ def set_frontend_visited_state(current_request_attr, value)
52
+ CurrentRequest.public_send("#{current_request_attr}=", value)
53
+ legacy_ivar = LEGACY_VISITED_IVAR_BY_ATTR[current_request_attr]
54
+ return if legacy_ivar.blank?
55
+
56
+ instance_variable_set(legacy_ivar, value)
57
+ warn_frontend_legacy_visited_ivar(legacy_ivar, current_request_attr)
58
+ end
59
+
60
+ def warn_frontend_legacy_visited_ivar(legacy_ivar, current_request_attr)
61
+ warned_legacy_ivars = self.class.instance_variable_get(:@_warned_frontend_legacy_visited_ivars) || {}
62
+ return if warned_legacy_ivars[legacy_ivar]
63
+
64
+ ActiveSupport::Deprecation._instance.warn(
65
+ "Controller compatibility ivar #{legacy_ivar} is deprecated and will be removed in a future major release. " \
66
+ "Use CurrentRequest.#{current_request_attr} instead."
67
+ )
68
+ warned_legacy_ivars[legacy_ivar] = true
69
+ self.class.instance_variable_set(:@_warned_frontend_legacy_visited_ivars, warned_legacy_ivars)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,104 @@
1
+ module CamaleonCms
2
+ module HookLifecycleConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ def hook_run(plugin, hook_key, params = nil)
6
+ _do_hook(plugin, hook_key, params)
7
+ end
8
+
9
+ def hooks_run(hook_key, params = nil)
10
+ theme_slug = current_theme&.slug.presence || current_site.get_theme_slug
11
+ PluginRoutes.enabled_apps(current_site, theme_slug).each do |plugin|
12
+ _do_hook(plugin, hook_key, params)
13
+ end
14
+
15
+ PluginRoutes.get_anonymous_hooks(hook_key).each do |_hook|
16
+ _hook.call(params)
17
+ end
18
+ end
19
+
20
+ def hook_skip(hook_function_name)
21
+ hook_skip_list << hook_function_name
22
+ end
23
+
24
+ private
25
+
26
+ def initialize_hook_skip_list
27
+ hooks_skip = []
28
+ CurrentRequest.hooks_helper_state ||= {}
29
+ CurrentRequest.hooks_helper_state[:hooks_skip] = hooks_skip
30
+ @_hooks_skip = hooks_skip
31
+ end
32
+
33
+ def run_hook_lifecycle(hook_name, payload = nil)
34
+ payload.nil? ? hooks_run(hook_name) : hooks_run(hook_name, payload)
35
+ end
36
+
37
+ def run_app_before_load_hooks
38
+ run_hook_lifecycle('app_before_load')
39
+ end
40
+
41
+ def run_app_after_load_hooks
42
+ run_hook_lifecycle('app_after_load')
43
+ end
44
+
45
+ def _do_hook(plugin, hook_key, params = nil)
46
+ return if plugin.blank? || plugin['hooks'].blank? || plugin['hooks'][hook_key].blank?
47
+
48
+ plugin['hooks'][hook_key].each do |hook|
49
+ next if hook_skip_list.include?(hook)
50
+
51
+ begin
52
+ if params.nil?
53
+ send(hook)
54
+ else
55
+ send(hook, params)
56
+ end
57
+ Rails.logger.debug "Camaleon CMS - Hook \"#{hook_key}\" executed from dependency #{begin
58
+ plugin['key']
59
+ rescue StandardError
60
+ ''
61
+ end}".cama_log_style(:light_blue)
62
+ rescue StandardError
63
+ plugin_load_helpers(plugin)
64
+ next unless respond_to?(hook, true)
65
+
66
+ if params.nil?
67
+ send(hook)
68
+ else
69
+ send(hook, params)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def hook_skip_list
76
+ state = camaleon_hooks_state
77
+ return state[:hooks_skip] if state[:hooks_skip]
78
+
79
+ # back-compat: legacy plugins/themes may seed @_hooks_skip directly on the
80
+ # controller. Honored here (controller concern) only as initial input so the
81
+ # shared view helper stays ivar-free.
82
+ existing_hooks_skip = @_hooks_skip
83
+ state[:hooks_skip] = existing_hooks_skip.is_a?(Array) ? existing_hooks_skip : []
84
+ end
85
+
86
+ def camaleon_hooks_state
87
+ CurrentRequest.hooks_helper_state ||= {}
88
+ end
89
+
90
+ def plugin_load_helpers(plugin)
91
+ return if plugin.blank? || plugin['helpers'].blank?
92
+
93
+ plugin['helpers'].each do |h|
94
+ next if self.class.include?(h.constantize)
95
+
96
+ self.class.class_eval { include h.constantize }
97
+ rescue StandardError => e
98
+ Rails.logger.debug do
99
+ "Camaleon CMS - App loading error for #{h}: #{e.message}. Please check the plugins and themes presence"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,113 @@
1
+ module CamaleonCms
2
+ # Wires per-request site/theme resolution and Camaleon's before-action
3
+ # housekeeping into the runtime controller stack. The `current_site` /
4
+ # `current_theme` implementations live in `CamaleonCms::SiteHelper` (single
5
+ # source of truth shared with views); this concern only adds controller-only
6
+ # hooks like view-path setup and site-existence guards.
7
+ module RequestContextConcern
8
+ extend ActiveSupport::Concern
9
+
10
+ include CamaleonCms::SiteHelper
11
+
12
+ # Back-compat: expose the resolved site as the `@current_site` controller
13
+ # ivar for legacy templates/plugins. The helper logic itself stays ivar-free
14
+ # (it reads/writes `CurrentRequest.site`); only this controller concern
15
+ # bridges the value into an instance variable so views/plugins keep working.
16
+ def current_site(site = nil)
17
+ result = super
18
+ @current_site = CurrentRequest.site
19
+ result
20
+ end
21
+
22
+ private
23
+
24
+ def configure_runtime_request_context
25
+ sanitize_forwarded_host_header
26
+ configure_runtime_view_paths
27
+ sync_runtime_defaults
28
+ assign_template_compatibility_state
29
+ end
30
+
31
+ def sanitize_forwarded_host_header
32
+ request.env.except!('HTTP_X_FORWARDED_HOST') if request.env['HTTP_X_FORWARDED_HOST']
33
+ end
34
+
35
+ def configure_runtime_view_paths
36
+ views_dir = 'app/apps/'
37
+ prepend_view_path(CamaleonCms::Engine.root.join(views_dir).to_s)
38
+ prepend_view_path(Rails.root.join(views_dir).to_s)
39
+ end
40
+
41
+ def sync_runtime_defaults
42
+ CamaleonCms::PostDefault.current_user = cama_current_user
43
+ CamaleonCms::PostDefault.current_site = current_site
44
+ end
45
+
46
+ def assign_template_compatibility_state
47
+ @current_site = current_site
48
+ end
49
+
50
+ # check if current site exist, if not, this will be redirected to main domain
51
+ # Also, check current site status
52
+ def cama_site_check_existence
53
+ if current_site.blank?
54
+ redirect_when_site_missing
55
+ elsif (cama_current_user.present? && !cama_current_user.admin?) || cama_current_user.blank?
56
+ handle_inactive_site_access
57
+ handle_maintenance_site_access
58
+ end
59
+ end
60
+
61
+ def redirect_when_site_missing
62
+ if Cama::Site.main_site.present?
63
+ url = Cama::Site.main_site.decorate.the_url
64
+ # TODO: Remove this condition when Rails 6.x won't be supported
65
+ if Rails.gem_version >= Gem::Version.new('7.0.0')
66
+ redirect_to url, allow_other_host: true
67
+ else
68
+ redirect_to url
69
+ end
70
+ else
71
+ redirect_to cama_admin_installers_path
72
+ end
73
+ end
74
+
75
+ def handle_inactive_site_access
76
+ return unless current_site.is_inactive?
77
+
78
+ if request.original_url.to_s.match?(%r{\A#{current_site.the_url}admin(/|\z)})
79
+ if cama_current_user.present?
80
+ cama_logout_user
81
+ flash[:error] = 'Site is Inactive'
82
+ end
83
+ else
84
+ post = current_site.posts.find_by(id: current_site.get_option('page_inactive')).try(:decorate)
85
+ if post
86
+ redirect_to(post.the_url) unless params == {
87
+ 'controller' => 'camaleon_cms/frontend',
88
+ 'action' => 'post',
89
+ 'slug' => post.the_slug
90
+ }
91
+ else
92
+ render html: 'This site was inactivated. Please contact to administrator.'
93
+ end
94
+ end
95
+ end
96
+
97
+ def handle_maintenance_site_access
98
+ return unless current_site.is_maintenance?
99
+ return if current_site.get_option('maintenance_ips', '').split(',').include?(request.remote_ip)
100
+
101
+ post = current_site.posts.find_by(id: current_site.get_option('page_maintenance')).try(:decorate)
102
+ if post
103
+ redirect_to(post.the_url) if params != {
104
+ 'controller' => 'camaleon_cms/frontend',
105
+ 'action' => 'post',
106
+ 'slug' => post.the_slug
107
+ }
108
+ else
109
+ render html: 'This site is in maintenance mode. Please contact to administrator.'
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,237 @@
1
+ # rubocop:disable Metrics/AbcSize
2
+ module CamaleonCms
3
+ module RuntimeAdminMenuConcern
4
+ extend ActiveSupport::Concern
5
+
6
+ def admin_menus_add_commons
7
+ admin_menu_add_menu(
8
+ 'dashboard',
9
+ { icon: 'dashboard', title: t('camaleon_cms.admin.sidebar.dashboard'), url: cama_admin_dashboard_path }
10
+ )
11
+
12
+ items = []
13
+ current_site.post_types.eager_load(:metas).visible_menu.find_each do |pt|
14
+ pt = pt.decorate
15
+ items_i = []
16
+ if can? :posts, pt
17
+ items_i << { icon: 'list', title: t('camaleon_cms.admin.post_type.all').to_s,
18
+ url: cama_admin_post_type_posts_path(pt.id) }
19
+ end
20
+ if can? :create_post, pt
21
+ items_i << { icon: 'plus', title: t('camaleon_cms.admin.post_type.add_new', type_title: pt.the_title).to_s,
22
+ url: new_cama_admin_post_type_post_path(pt.id) }
23
+ end
24
+ if pt.manage_categories? && (can? :categories, pt)
25
+ items_i << { icon: 'folder-open', title: t('camaleon_cms.admin.post_type.categories'),
26
+ url: cama_admin_post_type_categories_path(pt.id) }
27
+ end
28
+ if pt.manage_tags? && (can? :post_tags, pt)
29
+ items_i << { icon: 'tags', title: t('camaleon_cms.admin.post_type.tags'),
30
+ url: cama_admin_post_type_post_tags_path(pt.id) }
31
+ end
32
+ if items_i.present?
33
+ items << { icon: pt.get_option('icon', 'copy'), title: pt.the_title, url: '', items: items_i }
34
+ end
35
+ end
36
+
37
+ if items.present?
38
+ admin_menu_add_menu(
39
+ 'content',
40
+ {
41
+ icon: 'database', title: t('camaleon_cms.admin.sidebar.contents'), url: '', items: items,
42
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.content')}' data-position='right' data-wait='600'"
43
+ }
44
+ )
45
+ end
46
+
47
+ if can? :manage, :media
48
+ admin_menu_add_menu(
49
+ 'media',
50
+ {
51
+ icon: 'picture-o', title: t('camaleon_cms.admin.sidebar.media'), url: cama_admin_media_path,
52
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.media')}' data-position='right'"
53
+ }
54
+ )
55
+ end
56
+ if can? :manage, :comments
57
+ admin_menu_add_menu(
58
+ 'comments',
59
+ {
60
+ icon: 'comments', title: t('camaleon_cms.admin.sidebar.comments'), url: cama_admin_comments_path,
61
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.comments')}' data-position='right'"
62
+ }
63
+ )
64
+ end
65
+
66
+ items = []
67
+ if can? :manage, :themes
68
+ items << {
69
+ icon: 'desktop', title: t('camaleon_cms.admin.sidebar.themes'), url: cama_admin_appearances_themes_path,
70
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.themes')}' data-position='right'"
71
+ }
72
+ end
73
+ if can? :manage, :widgets
74
+ items << {
75
+ icon: 'archive', title: t('camaleon_cms.admin.sidebar.widgets'),
76
+ url: cama_admin_appearances_widgets_main_index_path,
77
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.widgets')}' data-position='right'"
78
+ }
79
+ end
80
+ if can? :manage, :nav_menu
81
+ intro_menus_data =
82
+ t('camaleon_cms.admin.intro.menus', image: helpers.asset_path('camaleon_cms/admin/intro/menus.png'))
83
+ items << {
84
+ icon: 'list', title: t('camaleon_cms.admin.sidebar.menus'), url: cama_admin_appearances_nav_menus_path,
85
+ datas: "data-intro='#{intro_menus_data}' data-position='right'"
86
+ }
87
+ end
88
+ if can? :manage, :shortcodes
89
+ items << {
90
+ icon: 'code', title: t('camaleon_cms.admin.sidebar.shortcodes', default: 'Shortcodes'),
91
+ url: cama_admin_settings_shortcodes_path,
92
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.shortcodes')}' data-position='right'"
93
+ }
94
+ end
95
+ if items.present?
96
+ admin_menu_add_menu(
97
+ 'appearance',
98
+ {
99
+ icon: 'paint-brush', title: t('camaleon_cms.admin.sidebar.appearance'), url: '', items: items,
100
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.appearance')}' data-position='right' data-wait='500'"
101
+ }
102
+ )
103
+ end
104
+
105
+ if can? :manage, :plugins
106
+ plugin_count = PluginRoutes.all_plugins.count do |plugin|
107
+ !(plugin[:domain].present? && !plugin[:domain].split(',').include?(current_site.the_slug))
108
+ end
109
+ admin_menu_add_menu(
110
+ 'plugins',
111
+ {
112
+ icon: 'plug',
113
+ title: helpers.safe_join([
114
+ t('camaleon_cms.admin.sidebar.plugins'),
115
+ ' ',
116
+ helpers.content_tag(:small, plugin_count, class: 'label label-primary')
117
+ ]),
118
+ url: cama_admin_plugins_path,
119
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.plugins')}' data-position='right'"
120
+ }
121
+ )
122
+ end
123
+
124
+ if can? :manage, :users
125
+ items = []
126
+ items << { icon: 'list', title: t('camaleon_cms.admin.users.all_users'), url: cama_admin_users_path }
127
+ items << { icon: 'plus', title: t('camaleon_cms.admin.users.add_user'), url: new_cama_admin_user_path }
128
+ items << { icon: 'group', title: t('camaleon_cms.admin.users.user_roles'), url: cama_admin_user_roles_path }
129
+ admin_menu_add_menu(
130
+ 'users',
131
+ {
132
+ icon: 'users', title: t('camaleon_cms.admin.sidebar.users'), url: '', items: items,
133
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.users')}' data-position='right' data-wait='500'"
134
+ }
135
+ )
136
+ end
137
+
138
+ items = []
139
+ if can? :manage, :settings
140
+ items << {
141
+ icon: 'desktop', title: t('camaleon_cms.admin.sidebar.general_site'),
142
+ url: cama_admin_settings_site_path,
143
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.gral_site')}' data-position='right'"
144
+ }
145
+ if current_site.manage_sites?
146
+ items << {
147
+ icon: 'cog', title: t('camaleon_cms.admin.sidebar.sites'),
148
+ url: cama_admin_settings_sites_path,
149
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.sites')}' data-position='right'"
150
+ }
151
+ end
152
+ items << {
153
+ icon: 'files-o', title: t('camaleon_cms.admin.sidebar.content_groups'),
154
+ url: cama_admin_settings_post_types_path,
155
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.post_type')}' data-position='right'"
156
+ }
157
+ items << {
158
+ icon: 'cog', title: t('camaleon_cms.admin.sidebar.custom_fields'),
159
+ url: cama_admin_settings_custom_fields_path,
160
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.custom_fields')}' data-position='right'"
161
+ }
162
+ items << {
163
+ icon: 'language', title: t('camaleon_cms.admin.sidebar.languages'),
164
+ url: cama_admin_settings_languages_path,
165
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.languages')}' data-position='right'"
166
+ }
167
+ end
168
+
169
+ if can? :manage, :theme_settings
170
+ items << {
171
+ icon: 'windows', title: t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings'),
172
+ url: cama_admin_settings_theme_path
173
+ }
174
+ end
175
+ return if items.blank?
176
+
177
+ admin_menu_add_menu(
178
+ 'settings',
179
+ {
180
+ icon: 'cogs', title: t('camaleon_cms.admin.sidebar.settings'), url: '', items: items,
181
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.settings')}' data-position='right' data-wait='500'"
182
+ }
183
+ )
184
+ end
185
+
186
+ def admin_menu_add_menu(key, menu)
187
+ CurrentRequest.admin_menu_items ||= {}
188
+ CurrentRequest.admin_menu_items[key] = menu
189
+ end
190
+
191
+ def admin_menu_append_menu_item(key, menu)
192
+ CurrentRequest.admin_menu_items ||= {}
193
+ return if CurrentRequest.admin_menu_items[key].blank?
194
+
195
+ CurrentRequest.admin_menu_items[key][:items] = [] unless CurrentRequest.admin_menu_items[key].key?(:items)
196
+ CurrentRequest.admin_menu_items[key][:items] << menu
197
+ end
198
+
199
+ def admin_menu_prepend_menu_item(key, menu)
200
+ CurrentRequest.admin_menu_items ||= {}
201
+ return if CurrentRequest.admin_menu_items[key].blank?
202
+
203
+ CurrentRequest.admin_menu_items[key][:items] = [] unless CurrentRequest.admin_menu_items[key].key?(:items)
204
+ CurrentRequest.admin_menu_items[key][:items] = [menu] + CurrentRequest.admin_menu_items[key][:items]
205
+ end
206
+
207
+ def admin_menu_insert_menu_before(key_target, key_menu, menu)
208
+ CurrentRequest.admin_menu_items ||= {}
209
+ res = CurrentRequest.admin_menu_items.each_with_object({}) do |(key, val), hsh|
210
+ hsh[key_menu] = menu if key == key_target
211
+ hsh[key] = val
212
+ end
213
+ CurrentRequest.admin_menu_items.replace(res) # mutate in place so @_admin_menus stays a live alias (M19)
214
+ end
215
+
216
+ def admin_menu_insert_menu_after(key_target, key_menu, menu)
217
+ CurrentRequest.admin_menu_items ||= {}
218
+ res = CurrentRequest.admin_menu_items.each_with_object({}) do |(key, val), hsh|
219
+ hsh[key] = val
220
+ hsh[key_menu] = menu if key == key_target
221
+ end
222
+ CurrentRequest.admin_menu_items.replace(res) # mutate in place so @_admin_menus stays a live alias (M19)
223
+ end
224
+
225
+ def cama_comments_get_common_data
226
+ comment_data = {}
227
+ comment_data[:user_id] = cama_current_user.id
228
+ comment_data[:author] = cama_current_user.the_name
229
+ comment_data[:author_email] = cama_current_user.email
230
+ comment_data[:author_IP] = request.remote_ip.to_s
231
+ comment_data[:approved] = 'approved'
232
+ comment_data[:agent] = request.user_agent.force_encoding('ISO-8859-1').encode('UTF-8')
233
+ comment_data
234
+ end
235
+ end
236
+ end
237
+ # rubocop:enable Metrics/AbcSize
@@ -0,0 +1,13 @@
1
+ module CamaleonCms
2
+ # Controller-stack entry point for captcha image generation: GET /captcha
3
+ # (CamaleonController#captcha) resolves cama_captcha_build through this concern.
4
+ # The implementation lives in CamaleonCms::CaptchaImageGeneration, shared with
5
+ # CamaleonCms::CaptchaHelper, so a captcha hardening fix cannot land in one entry
6
+ # point and not the other (parity is guarded by
7
+ # spec/lib/camaleon_cms/captcha_implementation_parity_spec.rb).
8
+ module RuntimeCaptchaImageConcern
9
+ extend ActiveSupport::Concern
10
+
11
+ include CamaleonCms::CaptchaImageGeneration
12
+ end
13
+ end