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
@@ -4,10 +4,25 @@ module CamaleonCms
4
4
  flash[:error] = "Error: #{exception.message}"
5
5
  redirect_to cama_admin_dashboard_path
6
6
  end
7
- include CamaleonCms::Admin::ApplicationHelper
7
+ # Security (scan-and-reject policy, audit M17): custom-field values save after their parent
8
+ # through `custom_field_values.create!`, so a value the gate refuses arrives here as
9
+ # RecordInvalid. Surface the refusal as a flash error naming the field instead of a 500; the
10
+ # parent's own attributes were already saved, only the refused value rows are rolled back.
11
+ rescue_from ActiveRecord::RecordInvalid do |exception|
12
+ raise exception unless exception.record.is_a?(CamaleonCms::CustomFieldsRelationship)
13
+
14
+ flash[:error] = exception.record.errors.full_messages.to_sentence
15
+ redirect_back fallback_location: cama_admin_dashboard_path
16
+ end
8
17
  # layout 'camaleon_cms/admin'
18
+ # Admin responses are user-specific and must never be served from a browser or shared cache;
19
+ # without this a stale render (e.g. an editor that failed to initialize on a cold boot) can
20
+ # reappear after a restart until a manual reload. Set first so it applies to auth redirects too.
21
+ before_action :cama_prevent_response_caching
9
22
  before_action :cama_authenticate
23
+ before_action :keep_request_attrs
10
24
  before_action :admin_init_actions
25
+ before_action :enforce_password_change
11
26
  before_action :admin_logged_actions
12
27
  before_action :admin_before_hooks
13
28
  after_action :admin_after_hooks
@@ -15,6 +30,7 @@ module CamaleonCms
15
30
  params[:cama_ajax_request].present? ? 'camaleon_cms/admin/_ajax' : 'camaleon_cms/admin'
16
31
  }
17
32
  add_breadcrumb I18n.t('camaleon_cms.admin.sidebar.dashboard', default: 'Dashboard'), :cama_admin_path
33
+ helper_method :cama_get_i18n_frontend
18
34
 
19
35
  # render admin dashboard
20
36
  def index
@@ -41,55 +57,157 @@ module CamaleonCms
41
57
  # render search results
42
58
  # receive params[:q]
43
59
  # receive params[:kind]: define de type of the results type (content|category|tag) => default content
44
- # if this is receive a param[:ajax], then will render only results view
60
+ # if this is receiving a param[:ajax], then will render only results view
45
61
  def search
46
62
  add_breadcrumb I18n.t('camaleon_cms.admin.button.search')
47
- params[:kind] = 'content' unless params[:kind].present?
48
- params[:q] = (params[:q] || '').downcase
49
- @items = case params[:kind]
50
- when 'post_type'
51
- current_site.post_types.where(
52
- "LOWER(#{Cama::PostType.table_name}.name) LIKE ? OR LOWER(#{Cama::PostType.table_name}.slug) LIKE ? OR LOWER(#{Cama::PostType.table_name}.description) LIKE ?", "%#{params[:q]}%", "%#{params[:q]}%", "%#{params[:q]}%"
53
- )
54
- when 'category'
55
- current_site.full_categories.where(
56
- "LOWER(#{Cama::Category.table_name}.name) LIKE ? OR LOWER(#{Cama::Category.table_name}.slug) LIKE ? OR LOWER(#{Cama::Category.table_name}.description) LIKE ?", "%#{params[:q]}%", "%#{params[:q]}%", "%#{params[:q]}%"
57
- )
58
- when 'tag'
59
- current_site.post_tags.where(
60
- "LOWER(#{Cama::PostTag.table_name}.name) LIKE ? OR LOWER(#{Cama::PostTag.table_name}.slug) LIKE ? OR LOWER(#{Cama::PostTag.table_name}.description) LIKE ?", "%#{params[:q]}%", "%#{params[:q]}%", "%#{params[:q]}%"
61
- )
62
- else
63
- current_site.posts.where(
64
- "LOWER(#{Cama::Post.table_name}.title) LIKE ? OR LOWER(#{Cama::Post.table_name}.slug) LIKE ? OR LOWER(#{Cama::Post.table_name}.content_filtered) LIKE ?", "%#{params[:q]}%", "%#{params[:q]}%", "%#{params[:q]}%"
65
- )
66
- end
63
+ params[:kind] = 'content' if params[:kind].blank?
64
+ # A crafted ?q[]=x / ?q[a]=b arrives as an Array / Parameters, which has no #downcase; treat any
65
+ # non-String q as an empty query instead of 500ing.
66
+ params[:q] = params[:q].is_a?(String) ? params[:q].downcase : ''
67
+ # Security (audit 2026-08-11 M12): the action had no authorization and no status filter, so any
68
+ # admin-area user (e.g. a client with no content rights) could enumerate every post title/slug in
69
+ # every status -- draft, pending, private, trash -- plus post types, categories and tags they
70
+ # cannot access. Scope every kind to the post types the caller may manage: content and post types
71
+ # by :posts, categories and tags by their own :categories / :post_tags abilities (the roles UI
72
+ # grants manage_categories / manage_tags independently of any post edit right).
73
+ table_name = case params[:kind]
74
+ when 'post_type'
75
+ base_query = current_site.post_types.where(id: cama_admin_searchable_post_type_ids)
76
+ Cama::PostType.table_name
77
+ when 'category'
78
+ pt_ids = cama_admin_searchable_post_type_ids(:categories)
79
+ # A category's post type lives in post_type_id (the status column) at every nesting
80
+ # level; parent_id points at the parent *category* for children, so filtering by it
81
+ # would drop every nested category.
82
+ base_query = current_site.full_categories.where(post_type_id: pt_ids)
83
+ Cama::Category.table_name
84
+ when 'tag'
85
+ pt_ids = cama_admin_searchable_post_type_ids(:post_tags)
86
+ base_query = current_site.post_tags.where(parent_id: pt_ids)
87
+ Cama::PostTag.table_name
88
+ else
89
+ base_query = cama_admin_searchable_posts
90
+ Cama::Post.table_name
91
+ end
92
+ @items = base_query.where(
93
+ items_sql_by_name(table_name), "%#{params[:q]}%", "%#{params[:q]}%", "%#{params[:q]}%"
94
+ )
95
+
67
96
  @items = @items.paginate(page: params[:page], per_page: current_site.admin_per_page)
68
97
  end
69
98
 
99
+ # Decorators use this helper while rendering admin pages to keep public URLs in
100
+ # the site's frontend language instead of the admin interface language.
101
+ def cama_get_i18n_frontend
102
+ @cama_i18n_frontend
103
+ end
104
+
70
105
  private
71
106
 
107
+ # Send `Cache-Control: no-store` on every admin response so no admin page is ever cached by the
108
+ # browser or an intermediary. Admin pages are per-user and dynamic; caching them risks a stale
109
+ # render being reused (the cold-boot blank-editor symptom is one such case).
110
+ def cama_prevent_response_caching
111
+ response.headers['Cache-Control'] = 'no-store'
112
+ end
113
+
114
+ # Actions reachable while an administrator still owes a password change: the profile screen, its
115
+ # submit, and the AJAX password-change endpoint. Everything else in the admin panel is redirected
116
+ # to the change-password screen until the marker is cleared. Sign-out lives on SessionsController
117
+ # (not an AdminController subclass), so it is out of this gate's reach and always reachable.
118
+ PASSWORD_CHANGE_EXEMPT = { 'camaleon_cms/admin/users' => %w[profile profile_edit update updated_ajax] }.freeze
119
+
120
+ # Force a newly provisioned administrator (minted with a generated password, see
121
+ # harden-installer-default-admin) to set their own password before using the admin panel.
122
+ def enforce_password_change
123
+ return if cama_current_user.blank?
124
+ return if cama_current_user.get_meta('must_change_password').blank?
125
+ return if PASSWORD_CHANGE_EXEMPT[controller_path]&.include?(action_name)
126
+
127
+ flash[:alert] = t('camaleon_cms.admin.users.message.must_change_password',
128
+ default: 'Please choose a new password before continuing.')
129
+ redirect_to cama_admin_profile_edit_path
130
+ end
131
+
72
132
  # initialize all vars and methods for admin panel
73
133
  def admin_init_actions
74
134
  I18n.locale = current_site.get_admin_language
75
- @_admin_menus = {}
135
+ # Alias the legacy @_admin_menus ivar onto the live menu store so a hook using the WordPress-style
136
+ # `@_admin_menus.delete('comments')` removal idiom mutates the same hash admin_menu_draw reads. The
137
+ # menu-insert methods mutate in place (.replace), so this reference stays valid. Regression M19.
138
+ @_admin_menus = CurrentRequest.admin_menu_items = {}
76
139
  @_admin_breadcrumb = []
77
140
  @_extra_models_for_fields = []
141
+ # Cache the site's frontend language for decorators and plugins that still
142
+ # need to distinguish admin requests from frontend visitor requests.
78
143
  @cama_i18n_frontend = current_site.get_languages.first
79
144
  end
80
145
 
81
146
  # trigger hooks for admin panel before admin load
82
147
  def admin_before_hooks
83
- hooks_run('admin_before_load')
148
+ run_hook_lifecycle('admin_before_load')
149
+ end
150
+
151
+ # Set cama_current_user and current_site in CurrentRequest so models can access the current context.
152
+ # CurrentRequest is an ActiveSupport::CurrentAttributes subclass that auto-resets per request.
153
+ def keep_request_attrs
154
+ CurrentRequest.user = cama_current_user
155
+ CurrentRequest.site = current_site
84
156
  end
85
157
 
86
158
  # trigger hooks for admin panel after admin load
87
159
  def admin_after_hooks
88
- hooks_run('admin_after_load')
160
+ run_hook_lifecycle('admin_after_load')
89
161
  end
90
162
 
91
163
  def admin_logged_actions
92
- admin_menus_add_commons if !request.xhr? || !params[:cama_ajax_request].present? # initialize admin sidebar menus
164
+ admin_menus_add_commons if !request.xhr? || params[:cama_ajax_request].blank? # initialize admin sidebar menus
165
+ end
166
+
167
+ # Post types on which the caller holds `action`: :posts for content and the post-type kind,
168
+ # :categories / :post_tags for the taxonomy kinds. Admins hold every ability on every type, so
169
+ # skip the per-record sweep (and the redundant IN filter it would feed) for them.
170
+ def cama_admin_searchable_post_type_ids(action = :posts)
171
+ return current_site.post_types.ids if can?(:manage, :all)
172
+
173
+ cama_admin_searchable_post_types(action).map(&:id)
174
+ end
175
+
176
+ def cama_admin_searchable_post_types(action)
177
+ current_site.post_types.select { |pt| can?(action, pt) }
178
+ end
179
+
180
+ # Posts the caller may see in admin search: the accessible post types narrowed by the shared
181
+ # admin visibility rule. edit_other implies :posts, so the visibility sweep only rechecks the
182
+ # already-loaded listable subset; admins skip the scoping entirely.
183
+ def cama_admin_searchable_posts
184
+ return current_site.posts if can?(:manage, :all)
185
+
186
+ listable = cama_admin_searchable_post_types(:posts)
187
+ cama_admin_visible_posts(current_site.posts.where(post_type_id: listable.map(&:id)), listable)
188
+ end
189
+
190
+ # Restrict `scope` to the posts the caller may see in admin listings across `post_types`: every
191
+ # post on the types where they hold edit_other, only their own elsewhere. Admins see everything.
192
+ # Single source of the visibility rule for PostsController#index (per post type) and admin search
193
+ # (across types) -- these drifting apart is how the M12 disclosure shipped, so change it here, not
194
+ # at a call site.
195
+ def cama_admin_visible_posts(scope, post_types)
196
+ return scope if can?(:manage, :all)
197
+
198
+ edit_other_ids = post_types.select { |pt| can?(:edit_other, pt) }.map(&:id)
199
+ return scope if edit_other_ids.length == post_types.length
200
+
201
+ scope.where(post_type_id: edit_other_ids).or(scope.where(user_id: cama_current_user.id))
202
+ end
203
+
204
+ def items_sql_by_name(table_name)
205
+ lower_name = "LOWER(#{table_name}"
206
+ if table_name == Cama::Post.table_name
207
+ return "#{lower_name}.title) LIKE ? OR #{lower_name}.slug) LIKE ? OR #{lower_name}.content_filtered) LIKE ?"
208
+ end
209
+
210
+ "#{lower_name}.name) LIKE ? OR #{lower_name}.slug) LIKE ? OR #{lower_name}.description) LIKE ?"
93
211
  end
94
212
  end
95
213
  end
@@ -2,6 +2,7 @@ module CamaleonCms
2
2
  module Apps
3
3
  class PluginsAdminController < CamaleonCms::AdminController
4
4
  before_action :init_plugin
5
+ before_action :authorize_plugin
5
6
 
6
7
  private
7
8
 
@@ -21,6 +22,10 @@ module CamaleonCms
21
22
  lookup_context.prefixes.prepend(params[:controller].sub("plugins/#{plugin_name}",
22
23
  "plugins/#{plugin_name}/views"))
23
24
  end
25
+
26
+ def authorize_plugin
27
+ authorize! :manage, :plugins
28
+ end
24
29
  end
25
30
  end
26
31
  end
@@ -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