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
@@ -5,21 +5,24 @@ module CamaleonCms
5
5
  add_flash_types :notice
6
6
  add_flash_types :info
7
7
 
8
+ # Controller-context helper surface (ct, cama_t, cama_cache_fetch, ...): plugin hook
9
+ # functions run on the controller and actions like FrontendController#search call these.
8
10
  include CamaleonCms::CamaleonHelper
9
- include CamaleonCms::SessionHelper
10
- include CamaleonCms::SiteHelper
11
- include CamaleonCms::HtmlHelper
12
- include CamaleonCms::UserRolesHelper
13
- include CamaleonCms::ShortCodeHelper
14
- include CamaleonCms::PluginsHelper
15
- include CamaleonCms::ThemeHelper
16
- include CamaleonCms::HooksHelper
17
- include CamaleonCms::ContentHelper
18
- include CamaleonCms::CaptchaHelper
19
- include CamaleonCms::UploaderHelper
20
- include CamaleonCms::EmailHelper
11
+ include CamaleonCms::SessionRuntimeConcern
12
+ include CamaleonCms::RequestContextConcern
13
+ include CamaleonCms::HookLifecycleConcern
14
+ include CamaleonCms::RuntimeShortcodeThemeConcern
15
+ include CamaleonCms::RuntimeHtmlContentConcern
16
+ include CamaleonCms::RuntimeAdminMenuConcern
17
+ include CamaleonCms::RuntimeCaptchaImageConcern
18
+ include CamaleonCms::RuntimeUploaderConcern
21
19
  include Mobu::DetectMobile
20
+ delegate :tag, :content_tag, :safe_join, :image_tag, :link_to, :sanitize, to: :helpers
22
21
 
22
+ # Preserve backwards-compatible plugin helper inclusion: any helper module a
23
+ # plugin declares in its config.json is mixed in here so plugins can call
24
+ # those methods from controllers (not only from views). Documented in
25
+ # `docs/ai/plans/helper-concern-architecture-refactor.md`.
23
26
  PluginRoutes.all_helpers.each { |h| include h.constantize }
24
27
 
25
28
  before_action :cama_site_check_existence, except: [:captcha]
@@ -35,13 +38,24 @@ module CamaleonCms
35
38
 
36
39
  # show page error
37
40
  def render_error(status = 404, exception = nil, message = '')
38
- Rails.logger.debug "Camaleon CMS - 404 url: #{begin
39
- request.original_url
40
- rescue StandardError
41
- nil
42
- end} ==> message: #{exception.message if exception.present?} ==> #{params[:error_msg]} ==> #{caller.inspect}"
43
- @message = "#{message} #{params[:error_msg] || (exception.present? ? "#{exception.message}<br><br>#{caller.inspect}" : '')}"
44
- @message = '' if Rails.env == 'production'
41
+ error_msg = params[:error_msg]
42
+ Rails.logger.debug do
43
+ original_url = begin
44
+ request.original_url
45
+ rescue StandardError
46
+ nil
47
+ end
48
+ 'Camaleon CMS - 404 url: ' \
49
+ "#{original_url} ==> message: #{exception&.message} ==> #{error_msg} ==> #{caller.inspect}"
50
+ end
51
+
52
+ @message = if Rails.env.production?
53
+ ''
54
+ else
55
+ "#{message} " \
56
+ "#{error_msg || (exception.present? ? "#{exception.message}<br><br>#{caller.inspect}" : '')}"
57
+ end
58
+
45
59
  respond_to do |format|
46
60
  format.html { render "camaleon_cms/#{status}", status: status }
47
61
  format.any { head status }
@@ -69,18 +83,9 @@ module CamaleonCms
69
83
  # initializing before and after contents
70
84
  cama_content_init
71
85
 
72
- @_hooks_skip = []
73
- # trigger all hooks before_load_app
74
- hooks_run('app_before_load')
75
-
76
- request.env.except!('HTTP_X_FORWARDED_HOST') if request.env['HTTP_X_FORWARDED_HOST'] # just drop the variable
77
-
78
- views_dir = 'app/apps/'
79
- prepend_view_path(File.join($camaleon_engine_dir, views_dir).to_s)
80
- prepend_view_path(Rails.root.join(views_dir).to_s)
81
-
82
- CamaleonCms::PostDefault.current_user = cama_current_user
83
- CamaleonCms::PostDefault.current_site = current_site
86
+ initialize_hook_skip_list
87
+ run_app_before_load_hooks
88
+ configure_runtime_request_context
84
89
  end
85
90
 
86
91
  # initialize ability for current user
@@ -89,67 +94,7 @@ module CamaleonCms
89
94
  end
90
95
 
91
96
  def cama_after_actions
92
- # trigger all actions app after load
93
- hooks_run('app_after_load')
94
- end
95
-
96
- # redirect to sessions login form when the session was expired.
97
- def auth_session_error
98
- redirect_to cama_root_path
99
- end
100
-
101
- # check if current site exist, if not, this will be redirected to main domain
102
- # Also, check current site status
103
- def cama_site_check_existence
104
- if !current_site.present?
105
- if Cama::Site.main_site.present?
106
- url = Cama::Site.main_site.decorate.the_url
107
- # TODO: Remove this condition when Rails 6.x won't be supported
108
- if Rails.gem_version >= Gem::Version.new('7.0.0')
109
- redirect_to url, allow_other_host: true
110
- else
111
- redirect_to url
112
- end
113
- else
114
- redirect_to cama_admin_installers_path
115
- end
116
- elsif (cama_current_user.present? && !cama_current_user.admin?) || !cama_current_user.present?
117
- # inactive page control
118
- if current_site.is_inactive?
119
- if request.original_url.to_s.match(%r{\A#{current_site.the_url}admin(/|\z)})
120
- if cama_current_user.present?
121
- cama_logout_user
122
- flash[:error] = 'Site is Inactive'
123
- end
124
- else
125
- p = current_site.posts.find_by_id(current_site.get_option('page_inactive')).try(:decorate)
126
- if p
127
- redirect_to(p.the_url) unless params == { 'controller' => 'camaleon_cms/frontend', 'action' => 'post',
128
- 'slug' => p.the_slug }
129
- else
130
- render html: 'This site was inactivated. Please contact to administrator.'
131
- end
132
- end
133
- end
134
-
135
- # maintenance page and IP's control
136
- if current_site.is_maintenance? && !current_site.get_option('maintenance_ips',
137
- '').split(',').include?(request.remote_ip)
138
- p = current_site.posts.find_by_id(current_site.get_option('page_maintenance')).try(:decorate)
139
- if p
140
- redirect_to(p.the_url) if params != { 'controller' => 'camaleon_cms/frontend', 'action' => 'post',
141
- 'slug' => p.the_slug }
142
- else
143
- render html: 'This site is in maintenance mode. Please contact to administrator.'
144
- end
145
- end
146
- end
147
- end
148
-
149
- unless ApplicationController.method_defined?(:current_user)
150
- def current_user
151
- cama_current_user
152
- end
97
+ run_app_after_load_hooks
153
98
  end
154
99
  end
155
100
  end
@@ -1,22 +1,22 @@
1
1
  module CamaleonCms
2
2
  class FrontendController < CamaleonCms::CamaleonController
3
3
  before_action :init_frontent
4
+ include CamaleonCms::FrontendVisitedStateConcern
4
5
  include CamaleonCms::FrontendConcern
5
6
  include CamaleonCms::Frontend::ApplicationHelper
6
7
  layout proc { |controller|
7
- args = {
8
- layout: (params[:cama_ajax_request].present? ? 'cama_ajax' : PluginRoutes.static_system_info['default_layout']), controller: controller
9
- }
10
- hooks_run('front_default_layout', args)
11
- args[:layout]
12
- }
8
+ args = {
9
+ layout: (params[:cama_ajax_request].present? ? 'cama_ajax' : PluginRoutes.static_system_info['default_layout']),
10
+ controller: controller
11
+ }
12
+ hooks_run('front_default_layout', args)
13
+ args[:layout]
14
+ }
13
15
  before_action :before_hooks
14
16
  after_action :after_hooks
15
- # rescue_from ActiveRecord::RecordNotFound, with: :page_not_found
16
-
17
17
  # home page for frontend
18
18
  def index
19
- @cama_visited_home = true
19
+ mark_frontend_home_visited
20
20
  if @_site_options[:home_page].present?
21
21
  render_post(@_site_options[:home_page].to_i)
22
22
  else
@@ -29,28 +29,38 @@ module CamaleonCms
29
29
  # render category list
30
30
  def category
31
31
  begin
32
- @category ||= current_site.the_full_categories.find_by_slug(params[:category_slug]).decorate if params[:category_slug].present?
32
+ if params[:category_slug].present?
33
+ @category ||= current_site.the_full_categories.find_by_slug(params[:category_slug]).decorate # rubocop:disable Rails/DynamicFindBy
34
+ end
33
35
  @category ||= current_site.the_full_categories.find(params[:category_id]).decorate
34
36
  @post_type = @category.the_post_type
35
37
  rescue StandardError
36
38
  return page_not_found
37
39
  end
38
- @cama_visited_category = @category
40
+ mark_frontend_category_visited(@category)
39
41
  @children = @category.children.no_empty.decorate
40
42
  @posts = @category.the_posts.paginate(page: params[:page],
41
43
  per_page: current_site.front_per_page).eager_load(:metas)
42
- r_file = lookup_context.template_exists?("category_#{@category.the_slug}") ? "category_#{@category.the_slug}" : nil # specific template category with specific slug within a posttype
43
- unless r_file.present?
44
- r_file = lookup_context.template_exists?("post_types/#{@post_type.the_slug}/category") ? "post_types/#{@post_type.the_slug}/category" : nil
44
+ category_slug = @category.the_slug
45
+
46
+ # specific template category with specific slug within a post-type
47
+ r_file = lookup_context.template_exists?("category_#{category_slug}") ? "category_#{category_slug}" : nil
48
+ post_type_slug = "post_types/#{@post_type.the_slug}"
49
+ if r_file.blank?
50
+ r_file = lookup_context.template_exists?("#{post_type_slug}/category") ? "#{post_type_slug}/category" : nil
45
51
  end
46
- unless r_file.present?
47
- r_file = lookup_context.template_exists?("categories/#{@category.the_slug}") ? "categories/#{@category.the_slug}" : 'category'
52
+ if r_file.blank?
53
+ r_file = if lookup_context.template_exists?("categories/#{category_slug}")
54
+ "categories/#{category_slug}"
55
+ else
56
+ 'category'
57
+ end
48
58
  end
49
59
 
50
- layout_ = if lookup_context.template_exists?("layouts/post_types/#{@post_type.the_slug}/category")
51
- "post_types/#{@post_type.the_slug}/category"
52
- elsif lookup_context.template_exists?("layouts/categories/#{@category.the_slug}")
53
- "categories/#{@category.the_slug}"
60
+ layout_ = if lookup_context.template_exists?("layouts/#{post_type_slug}/category")
61
+ "#{post_type_slug}/category"
62
+ elsif lookup_context.template_exists?("layouts/categories/#{category_slug}")
63
+ "categories/#{category_slug}"
54
64
  end
55
65
  r = { category: @category, layout: layout_, render: r_file }
56
66
  hooks_run('on_render_category', r)
@@ -60,18 +70,20 @@ module CamaleonCms
60
70
  # render contents from post type
61
71
  def post_type
62
72
  begin
63
- @post_type = current_site.post_types.find_by_slug(params[:post_type_slug]).decorate
73
+ @post_type = current_site.post_types.find_by_slug(params[:post_type_slug]).decorate # rubocop:disable Rails/DynamicFindBy
64
74
  rescue StandardError
65
75
  return page_not_found
66
76
  end
67
77
  @object = @post_type
68
- @cama_visited_post_type = @post_type
78
+ CurrentRequest.frontend_object = @object
79
+ mark_frontend_post_type_visited(@post_type)
69
80
  @posts = @post_type.the_posts.paginate(page: params[:page],
70
81
  per_page: current_site.front_per_page).eager_load(:metas)
71
82
  @categories = @post_type.categories.no_empty.eager_load(:metas).decorate
72
83
  @post_tags = @post_type.post_tags.eager_load(:metas)
73
- r_file = lookup_context.template_exists?("post_types/#{@post_type.the_slug}") ? "post_types/#{@post_type.the_slug}" : 'post_type'
74
- layout_ = lookup_context.template_exists?("layouts/post_types/#{@post_type.the_slug}") ? "post_types/#{@post_type.the_slug}" : nil
84
+ post_type_slug = "post_types/#{@post_type.the_slug}"
85
+ r_file = lookup_context.template_exists?(post_type_slug) ? post_type_slug : 'post_type'
86
+ layout_ = lookup_context.template_exists?("layouts/#{post_type_slug}") ? post_type_slug : nil
75
87
  r = { post_type: @post_type, layout: layout_, render: r_file }
76
88
  hooks_run('on_render_post_type', r)
77
89
  render r[:render], (!r[:layout].nil? ? { layout: r[:layout] } : {})
@@ -81,7 +93,7 @@ module CamaleonCms
81
93
  def post_tag
82
94
  begin
83
95
  @post_tag = if params[:post_tag_slug].present?
84
- current_site.post_tags.find_by_slug(params[:post_tag_slug]).decorate
96
+ current_site.post_tags.find_by_slug(params[:post_tag_slug]).decorate # rubocop:disable Rails/DynamicFindBy
85
97
  else
86
98
  current_site.post_tags.find(params[:post_tag_id]).decorate
87
99
  end
@@ -90,10 +102,12 @@ module CamaleonCms
90
102
  return page_not_found
91
103
  end
92
104
  @object = @post_tag
93
- @cama_visited_tag = @post_tag
105
+ CurrentRequest.frontend_object = @object
106
+ mark_frontend_tag_visited(@post_tag)
94
107
  @posts = @post_tag.the_posts.paginate(page: params[:page],
95
108
  per_page: current_site.front_per_page).eager_load(:metas)
96
- r_file = lookup_context.template_exists?("post_types/#{@post_type.the_slug}/post_tag") ? "post_types/#{@post_type.the_slug}/post_tag" : 'post_tag'
109
+ slug_post_tag = "post_types/#{@post_type.the_slug}/post_tag"
110
+ r_file = lookup_context.template_exists?(slug_post_tag) ? slug_post_tag : 'post_tag'
97
111
  layout_ = lookup_context.template_exists?('layouts/post_tag') ? 'post_tag' : nil
98
112
  r = { post_tag: @post_tag, layout: layout_, render: r_file }
99
113
  hooks_run('on_render_post_tag', r)
@@ -103,19 +117,17 @@ module CamaleonCms
103
117
  # search contents
104
118
  def search
105
119
  breadcrumb_add(ct('search'))
106
- items = params[:post_type_slugs].present? ? current_site.the_posts(params[:post_type_slugs].split(',')) : current_site.the_posts
107
- @cama_visited_search = true
120
+ post_type_slugs = params[:post_type_slugs]
121
+ items = post_type_slugs.present? ? current_site.the_posts(post_type_slugs.split(',')) : current_site.the_posts
122
+ mark_frontend_search_visited
108
123
  @param_search = params[:q]
109
124
  layout_ = lookup_context.template_exists?('layouts/search') ? 'search' : nil
110
125
  r = { layout: layout_, render: 'search', posts: nil }
111
126
  hooks_run('on_render_search', r)
112
- params[:q] = (params[:q] || '').downcase
113
- @posts = if !r[:posts].nil?
114
- r[:posts]
115
- else
116
- items.where('LOWER(title) LIKE ? OR LOWER(content_filtered) LIKE ?',
117
- "%#{params[:q]}%", "%#{params[:q]}%")
118
- end
127
+ q_params = params[:q].to_s.downcase
128
+ params[:q] = q_params
129
+ @posts = r[:posts] ||
130
+ items.where('LOWER(title) LIKE ? OR LOWER(content_filtered) LIKE ?', "%#{q_params}%", "%#{q_params}%")
119
131
  @posts_size = @posts.size
120
132
  @posts = @posts.paginate(page: params[:page], per_page: current_site.front_per_page)
121
133
  render r[:render], (!r[:layout].nil? ? { layout: r[:layout] } : {})
@@ -124,7 +136,7 @@ module CamaleonCms
124
136
  # ajax requests
125
137
  def ajax
126
138
  r = { render_file: nil, render_text: '', layout: nil }
127
- @cama_visited_ajax = true
139
+ mark_frontend_ajax_visited
128
140
  hooks_run('on_ajax', r)
129
141
  if r[:render_file]
130
142
  render r[:render_file], (!r[:layout].nil? ? { layout: r[:layout] } : {})
@@ -150,7 +162,8 @@ module CamaleonCms
150
162
  return page_not_found
151
163
  end
152
164
  @object = @user
153
- @cama_visited_profile = true
165
+ CurrentRequest.frontend_object = @object
166
+ mark_frontend_profile_visited(@user)
154
167
  layout_ = lookup_context.template_exists?('layouts/profile') ? 'profile' : nil
155
168
  r = { user: @user, layout: layout_, render: 'profile' }
156
169
  hooks_run('on_render_profile', r)
@@ -168,6 +181,7 @@ module CamaleonCms
168
181
  def draft_render
169
182
  post_draft = current_site.posts.drafts.find(params[:draft_id])
170
183
  @object = post_draft
184
+ CurrentRequest.frontend_object = @object
171
185
 
172
186
  # let a hook override the ability for certain roles see drafts
173
187
  args = { permitted: false }
@@ -186,7 +200,9 @@ module CamaleonCms
186
200
  def render_post(post_or_slug_or_id, from_url = false, status = nil, force_visit = false)
187
201
  @post = case post_or_slug_or_id
188
202
  when String # slug
189
- current_site.the_posts.find_by_slug(post_or_slug_or_id)
203
+ # find_by_slug is multi-language aware (matches localized slugs like
204
+ # "<!--:en-->sample-post<!--:-->..."), unlike find_by(slug:)
205
+ current_site.the_posts.find_by_slug(post_or_slug_or_id) # rubocop:disable Rails/DynamicFindBy
190
206
  when Integer # id
191
207
  current_site.the_posts.where(id: post_or_slug_or_id).first
192
208
  else # model
@@ -194,15 +210,16 @@ module CamaleonCms
194
210
  end
195
211
 
196
212
  @post = @post.try(:decorate)
197
- if !@post.present? || !(force_visit || @post.can_visit?)
198
- if params[:format] == 'html' || !params[:format].present?
213
+ if @post.blank? || !(force_visit || @post.can_visit?)
214
+ if params[:format] == 'html' || params[:format].blank?
199
215
  page_not_found
200
216
  else
201
- head 404
217
+ head :not_found
202
218
  end
203
219
  else
204
220
  @object = @post
205
- @cama_visited_post = @post
221
+ CurrentRequest.frontend_object = @object
222
+ mark_frontend_post_visited(@post)
206
223
  @post_type = @post.the_post_type
207
224
  @comments = @post.the_comments
208
225
  @categories = @post.the_categories
@@ -213,10 +230,11 @@ module CamaleonCms
213
230
  rescue StandardError
214
231
  nil
215
232
  end
233
+ post_template = @post.get_template(@post_type)
216
234
  r_file = if lookup_context.template_exists?("page_#{@post.id}")
217
235
  "page_#{@post.id}"
218
- elsif @post.get_template(@post_type).present? && lookup_context.template_exists?(@post.get_template(@post_type))
219
- @post.get_template(@post_type)
236
+ elsif post_template.present? && lookup_context.template_exists?(post_template)
237
+ post_template
220
238
  elsif home_page.present? && @post.id.to_s == home_page
221
239
  'index'
222
240
  elsif lookup_context.template_exists?("post_types/#{@post_type.the_slug}/single")
@@ -261,17 +279,54 @@ module CamaleonCms
261
279
  # if url hasn't a locale, then it will use default locale set on application.rb
262
280
  def init_frontent
263
281
  # preview theme initializing
264
- @_current_theme = current_site.themes.where(slug: params[:ccc_theme_preview]).first_or_create!.decorate if cama_sign_in? && params[:ccc_theme_preview].present? && can?(
265
- :manage, :themes
266
- )
282
+ previewing_theme = cama_sign_in? && params[:ccc_theme_preview].present? && can?(:manage, :themes)
283
+ if previewing_theme
284
+ preview_theme = current_site.themes.where(slug: params[:ccc_theme_preview]).first_or_create!.decorate
285
+ @_current_theme = preview_theme
286
+ CurrentRequest.frontend_current_theme = preview_theme
287
+ ensure_preview_site_defaults
288
+ end
267
289
 
268
290
  @_site_options = current_site.options
269
- session[:cama_current_language] = params[:cama_set_language].to_sym if params[:cama_set_language].present?
291
+ # Only a scalar language param participates in locale resolution — a non-scalar
292
+ # (?locale[]= / ?cama_set_language[]=) would crash to_sym and 500 the request
293
+ set_language = params[:cama_set_language]
294
+ session[:cama_current_language] = set_language.to_sym if set_language.is_a?(String) && set_language.present?
270
295
  session[:cama_current_language] = nil if current_site.get_languages.exclude?(session[:cama_current_language])
271
- I18n.locale = params[:locale] || session[:cama_current_language] || current_site.get_languages.first
272
- return page_not_found unless current_site.get_languages.include?(I18n.locale.to_sym)
296
+ requested_locale = params[:locale] if params[:locale].is_a?(String)
297
+ I18n.locale = requested_locale || session[:cama_current_language] || current_site.get_languages.first
298
+ # The availability gate renders the site's 404 (directly or via a custom error_404
299
+ # post), so the theme lookup prefixes must already be registered when it fires
300
+ configure_frontend_lookup_prefixes
301
+ theme_init
302
+ return if current_site.get_languages.include?(I18n.locale.to_sym)
303
+
304
+ # The unoffered locale must not style the 404 page or leak into its links
305
+ I18n.locale = current_site.get_languages.first
306
+ page_not_found
307
+ end
308
+
309
+ # initialize hooks before to execute action
310
+ def before_hooks
311
+ hooks_run('front_before_load')
312
+ end
313
+
314
+ # initialize hooks after executed action
315
+ def after_hooks
316
+ hooks_run('front_after_load')
317
+ end
318
+
319
+ # define default options for url helpers
320
+ # control for default locale
321
+ def default_url_options(options = {})
322
+ return options if current_site.get_languages.first.to_s == I18n.locale.to_s
273
323
 
274
- # define render paths
324
+ { locale: I18n.locale }.merge!(options)
325
+ rescue StandardError
326
+ options
327
+ end
328
+
329
+ def configure_frontend_lookup_prefixes
275
330
  lookup_context.prefixes.delete('frontend')
276
331
  lookup_context.prefixes.delete('application')
277
332
  lookup_context.prefixes.delete('camaleon_cms/frontend')
@@ -282,36 +337,51 @@ module CamaleonCms
282
337
  t =~ %r{themes/(.*)/views}i || t == 'camaleon_cms/default_theme' || t == "themes/#{current_site.id}/views"
283
338
  end
284
339
 
285
- lookup_context.prefixes.append("themes/#{current_site.id}/views") if Dir.exist?(Rails.root.join('app', 'apps',
286
- 'themes', current_site.id.to_s).to_s)
340
+ # Per-site theme override: views placed under app/apps/themes/<site_id>/views
341
+ # take precedence over the active theme's views (it is appended first).
342
+ if Dir.exist?(Rails.root.join('app', 'apps', 'themes', current_site.id.to_s).to_s)
343
+ lookup_context.prefixes.append("themes/#{current_site.id}/views")
344
+ end
287
345
  lookup_context.prefixes.append("themes/#{current_theme.slug}/views")
288
346
  lookup_context.prefixes.append('camaleon_cms/default_theme')
289
347
 
290
348
  lookup_context.prefixes = lookup_context.prefixes.uniq
291
349
  lookup_context.use_camaleon_partial_prefixes = true
292
- theme_init
293
350
  end
294
351
 
295
- # initialize hooks before to execute action
296
- def before_hooks
297
- hooks_run('front_before_load')
352
+ def ensure_preview_site_defaults
353
+ preview_required_menu_slugs.each do |slug|
354
+ current_site.nav_menus.where(slug: slug).first_or_create! do |menu|
355
+ menu.name = slug.to_s.humanize
356
+ end
357
+ end
298
358
  end
299
359
 
300
- # initialize hooks after executed action
301
- def after_hooks
302
- hooks_run('front_after_load')
360
+ def preview_required_menu_slugs
361
+ slugs = ['main_menu']
362
+ slugs.concat(extract_theme_menu_slugs(current_theme.settings['path']))
363
+ slugs.uniq
303
364
  end
304
365
 
305
- # define default options for url helpers
306
- # control for default locale
307
- def default_url_options(options = {})
308
- if current_site.get_languages.first.to_s == I18n.locale.to_s
309
- options
310
- else
311
- { locale: I18n.locale }.merge options
366
+ def extract_theme_menu_slugs(theme_path)
367
+ return [] if theme_path.blank?
368
+
369
+ views_path = File.join(theme_path, 'views')
370
+ return [] unless Dir.exist?(views_path)
371
+
372
+ Dir.glob(File.join(views_path, '**', '*.erb')).flat_map do |file_path|
373
+ extract_menu_slugs_from_template(File.read(file_path))
374
+ end.uniq
375
+ end
376
+
377
+ def extract_menu_slugs_from_template(content)
378
+ slugs = []
379
+ slugs.concat(content.scan(/nav_menus\.where\(\s*slug:\s*'([^']+)'\s*\)/).flatten)
380
+ slugs.concat(content.scan(/nav_menus\.where\(\s*slug:\s*"([^"]+)"\s*\)/).flatten)
381
+ content.scan(/nav_menus\.where\(\s*slug:\s*\[([^\]]+)\]\s*\)/).flatten.each do |array_content|
382
+ slugs.concat(array_content.scan(/['"]([^'"]+)['"]/).flatten)
312
383
  end
313
- rescue StandardError
314
- options
384
+ slugs
315
385
  end
316
386
  end
317
387
  end
@@ -5,19 +5,34 @@ module CamaleonCms
5
5
 
6
6
  private
7
7
 
8
- # Only permit field_options that match registered custom field slugs
9
- def cama_permitted_field_options(object_class)
10
- return {} unless params[:field_options].present?
8
+ # Only permit field values whose slug is registered under object_class. param_key selects
9
+ # which request param carries the payload, so sibling params (the theme form's
10
+ # theme_fields) get the same allowed-slugs permit as the default field_options.
11
+ def cama_permitted_field_options(object_class, param_key: :field_options)
12
+ field_options = params[param_key]
13
+ # A non-hash payload (a scalar `field_options=foo`, or an array) carries no groups to permit,
14
+ # so treat it as empty instead of calling #keys/#permit on it. Guarding here rather than
15
+ # `params.require` avoids a NoMethodError -> 500 an authenticated caller could trigger with a
16
+ # malformed param (the same crash every set_field_values caller shared).
17
+ return {} unless field_options.is_a?(ActionController::Parameters)
11
18
 
12
19
  allowed_keys = cama_custom_field_allowed_slugs(object_class)
13
20
  return {} if allowed_keys.blank?
14
21
 
15
- field_options = params.require(:field_options)
16
- field_options.permit(field_options.keys.select { |k| k.to_s =~ /\A\d+\z/ }.index_with do
22
+ permitted = field_options.permit(field_options.keys.select { |k| k.to_s =~ /\A\d+\z/ }.index_with do
17
23
  allowed_keys.index_with { [:id, :group_number, { values: {} }] }
18
24
  end).to_h
25
+ # Drop groups left empty after filtering. set_field_values deletes every existing value
26
+ # before writing, so handing it a non-blank-but-empty payload (a group whose submitted
27
+ # slugs were all unregistered) would wipe the object's stored values and write nothing.
28
+ permitted.reject { |_group, fields| fields.blank? }
19
29
  end
20
30
 
31
+ # The allow-list covers fields registered directly under object_class. For a post
32
+ # ('PostType_Post') this intentionally spans only the post type's own groups -- not per-post
33
+ # ('Post') or category-inherited ('Category_Post') groups the edit form also renders. It
34
+ # matches PostsController#save_post_with_fields, so the main post save and drafts confine field
35
+ # writes identically; values for those sibling scopes are deliberately not written this way.
21
36
  def cama_custom_field_allowed_slugs(object_class)
22
37
  CamaleonCms::CustomField.where(
23
38
  parent_id: CamaleonCms::CustomField.where(object_class: object_class).select(:id),