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
@@ -2,7 +2,7 @@ module CamaleonCms
2
2
  module Frontend
3
3
  module NavMenuHelper
4
4
  # draw nav menu as html list
5
- # key: slug for nav menu
5
+ # key: slug for a nav menu
6
6
  # to register this, go to admin -> appearance -> menus
7
7
  # (DEPRECATED)
8
8
  def get_nav_menu(key = 'main_menu', class_name = 'navigation')
@@ -24,18 +24,27 @@ module CamaleonCms
24
24
  sub_container: 'ul', # type of container for sub items
25
25
  sub_class: 'dropdown-menu', # class for sub container
26
26
  callback_item: ->(args) {},
27
- # callback executed for each item (args = { menu_item, link, level, settings, has_children, link_attrs = "", index}).
27
+ # callback executed for each item:
28
+ # (args = { menu_item, link, level, settings, has_children, link_attrs = "", index}).
28
29
  # menu_item: (Object) Menu object
29
30
  # link: (Hash) link data: {link: '', name: ''}
30
31
  # level: (Integer) current level
31
32
  # has_children: (boolean) if this item contain sub menus
32
33
  # settings: (Hash) menu settings
33
34
  # index: (Integer) Index Position of this menu
34
- # link_attrs: (String) Here you can add your custom attrs for current link, sample: id='my_id' data-title='#{args[:link][:name]}'
35
+ # link_attrs: (String) Here you can add your custom attrs for current link,
36
+ # sample: id='my_id' data-title='#{args[:link][:name]}'
35
37
  # item_container_attrs: (String) Here you can add your custom attrs for link container.
36
- # In settings, you can change the values for this item, like after, before, ..:
37
- # sample: lambda{|args| args[:settings][:after] = "<span class='caret'></span>" if args[:has_children]; args[:link_attrs] = "id='#{menu_item.id}'"; }
38
- # sample: lambda{|args| args[:settings][:before] = "<i class='fa fa-home'></i>" if args[:level] == 0 && args[:index] == 0; }
38
+ # In settings, you can change the values for this item, like after, before,...:
39
+ # sample:
40
+ # lambda do |args|
41
+ # args[:settings][:after] = "<span class='caret'></span>" if args[:has_children]
42
+ # args[:link_attrs] = "id='#{menu_item.id}'"
43
+ # end
44
+ # sample:
45
+ # lambda do |args|
46
+ # args[:settings][:before] = "<i class='fa fa-home'></i>" if args[:level] == 0 && args[:index] == 0
47
+ # end
39
48
  before: '', # content before link text
40
49
  after: '', # content after link text
41
50
  link_current: 'current-link', # class for current menu link
@@ -49,7 +58,7 @@ module CamaleonCms
49
58
  }
50
59
 
51
60
  args = args_def.merge!(args)
52
- nav_menu = current_site.nav_menus.find_by_slug(args[:menu_slug])
61
+ nav_menu = current_site.nav_menus.find_by_slug(args[:menu_slug]) # rubocop:disable Rails/DynamicFindBy
53
62
  nav_menu ||= current_site.nav_menus.first
54
63
  html = "<#{args[:container]} class='#{args[:container_class]}' "\
55
64
  "id='#{args[:container_id]}'>#{args[:container_prepend]}{__}#{args[:container_append]}</#{args[:container]}>"
@@ -61,19 +70,22 @@ module CamaleonCms
61
70
  end
62
71
 
63
72
  # draw menu items
73
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
64
74
  def cama_menu_draw_items(args, nav_menu, level = 0)
65
- html = ''
75
+ items_html = []
66
76
  parent_current = false
67
77
  index = 0
78
+ # rubocop:disable Rails/FindEach -- Rendering must preserve the relation's configured term_order.
68
79
  nav_menu.eager_load(:metas).each do |nav_menu_item|
69
80
  _args = args.dup
70
81
  data_nav_item = cama_parse_menu_item(nav_menu_item)
71
82
  next if data_nav_item == false
72
83
 
73
- _is_current = data_nav_item[:current] || site_current_path == data_nav_item[:link] ||
84
+ _is_current = data_nav_item[:current] ||
85
+ site_current_path == data_nav_item[:link] ||
74
86
  site_current_path == data_nav_item[:link].sub('.html', '')
75
- has_children = nav_menu_item.have_children? && (args[:levels] == -1 ||
76
- (args[:levels] != -1 && level <= args[:levels]))
87
+ has_children = nav_menu_item.have_children? &&
88
+ (args[:levels] == -1 || (args[:levels] != -1 && level <= args[:levels]))
77
89
  r = {
78
90
  menu_item: nav_menu_item.decorate,
79
91
  link: data_nav_item,
@@ -88,44 +100,77 @@ module CamaleonCms
88
100
  _args = r[:settings]
89
101
 
90
102
  if has_children
91
- html_children, current_children = cama_menu_draw_items(args, nav_menu_item.children.reorder(:term_order),
92
- level + 1)
103
+ html_children, current_children = cama_menu_draw_items(
104
+ args, nav_menu_item.children.reorder(:term_order), level + 1
105
+ )
93
106
  else
94
107
  html_children = ''
95
108
  current_children = false
96
109
  end
97
- parent_current = true if _is_current || current_children
110
+ parent_current ||= _is_current || current_children
111
+
112
+ item_classes = [_args[:item_class]]
113
+ item_classes << _args[:item_class_parent] if has_children
114
+ item_classes << _args[:item_current] if _is_current
115
+ item_classes << 'current-menu-ancestor' if current_children
116
+ item_class_str = item_classes.reject(&:blank?).join(' ')
117
+
118
+ item_attrs = []
119
+ item_attrs << r[:item_container_attrs] if r[:item_container_attrs].present?
120
+ item_attrs << "class='#{ERB::Util.html_escape(item_class_str)}'" if item_class_str.present?
121
+ item_attrs_str = item_attrs.reject(&:blank?).join(' ')
122
+
123
+ item_open = "<#{_args[:item_container]}"
124
+ item_open << " #{item_attrs_str}" if item_attrs_str.present?
125
+ item_open << '>'
126
+
127
+ link_attr_parts = []
128
+ link_attr_parts << r[:link_attrs] if r[:link_attrs].present?
129
+ if nav_menu_item.target.present?
130
+ link_attr_parts << "target='#{ERB::Util.html_escape(nav_menu_item.target.to_s)}'"
131
+ end
132
+ link_attr_parts << "href='#{ERB::Util.html_escape(data_nav_item[:link].to_s)}'"
133
+ link_classes = []
134
+ link_classes << args[:link_current] if _is_current
135
+ link_classes << _args[:link_class_parent] if has_children
136
+ link_classes << _args[:link_class]
137
+ link_class_str = link_classes.reject(&:blank?).join(' ')
138
+ link_attr_parts << "class='#{ERB::Util.html_escape(link_class_str)}'" if link_class_str.present?
139
+ link_attr_parts << "data-toggle='dropdown'" if has_children
140
+ link_attrs_str = link_attr_parts.reject(&:blank?).join(' ')
141
+
142
+ # Menu labels intentionally support trusted HTML snippets (icons, buttons, embeds) configured by site admins.
143
+ link_inner = [_args[:before], data_nav_item[:name].to_s, _args[:after]].reject(&:blank?).join('')
144
+ link_tag = "<a #{link_attrs_str}>#{link_inner}</a>"
145
+
146
+ item_inner = [
147
+ _args[:link_before],
148
+ link_tag,
149
+ _args[:link_after],
150
+ html_children
151
+ ].reject(&:blank?).join('')
152
+ item_html = "#{item_open}#{item_inner}</#{_args[:item_container]}>"
153
+ # rubocop:disable Rails/OutputSafety -- User-controlled fields are escaped before this trusted menu markup is marked safe.
154
+ items_html << item_html.html_safe
155
+ # rubocop:enable Rails/OutputSafety
98
156
 
99
- html += "<#{_args[:item_container]} #{r[:item_container_attrs]} class='#{_args[:item_class]} #{if has_children
100
- _args[:item_class_parent]
101
- end} #{if _is_current
102
- _args[:item_current].to_s
103
- end} #{if current_children
104
- 'current-menu-ancestor'
105
- end}'>#{_args[:link_before]}
106
- <a #{r[:link_attrs]} #{if nav_menu_item.target.present?
107
- " target='#{nav_menu_item.target}'"
108
- end} href='#{data_nav_item[:link]}' class='#{if _is_current
109
- args[:link_current]
110
- end} #{if has_children
111
- _args[:link_class_parent]
112
- end} #{_args[:link_class]}' #{if has_children
113
- "data-toggle='dropdown'"
114
- end} >#{_args[:before]}#{data_nav_item[:name]}#{_args[:after]}</a> #{_args[:link_after]}
115
- #{html_children}
116
- </#{_args[:item_container]}>"
117
157
  index += 1
118
158
  end
159
+ # rubocop:enable Rails/FindEach
119
160
 
120
161
  if level == 0
121
- html
162
+ safe_join(items_html)
122
163
  else
123
- html = "<#{args[:sub_container]} class='#{args[:sub_class]} #{if parent_current
124
- "parent-#{args[:item_current]}"
125
- end} level-#{level}'>#{html}</#{args[:sub_container]}>"
126
- [html, parent_current]
164
+ sub_classes = [args[:sub_class]]
165
+ sub_classes << "parent-#{args[:item_current]}" if parent_current
166
+ sub_classes << "level-#{level}"
167
+ sub_container_class = sub_classes.reject(&:blank?).join(' ')
168
+ sub_html = "<#{args[:sub_container]} class='#{sub_container_class}'>" \
169
+ "#{safe_join(items_html)}</#{args[:sub_container]}>"
170
+ [sub_html, parent_current]
127
171
  end
128
172
  end
173
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
129
174
 
130
175
  # filter and parse all menu items visible for current user and adding the flag for current_parent or current_item
131
176
  # max_levels: max levels to iterate
@@ -139,9 +184,8 @@ module CamaleonCms
139
184
  data_nav_item = cama_parse_menu_item(nav_menu_item)
140
185
  next if data_nav_item == false
141
186
 
142
- _is_current = data_nav_item[:current] || site_current_path == data_nav_item[:link] || site_current_path == data_nav_item[:link].sub(
143
- '.html', ''
144
- )
187
+ _is_current = data_nav_item[:current] || site_current_path == data_nav_item[:link] ||
188
+ site_current_path == data_nav_item[:link].sub('.html', '')
145
189
  has_children = nav_menu_item.have_children?
146
190
  has_children = false if max_levels > 0 && max_levels == internal_level
147
191
  data_nav_item[:label] = data_nav_item[:name]
@@ -157,14 +201,15 @@ module CamaleonCms
157
201
  }.merge!(data_nav_item.except(:current, :name, :link))
158
202
 
159
203
  if has_children
160
- r[:children], _is_current_parent, r[:levels] = cama_menu_parse_items(nav_menu_item.children, max_levels,
161
- internal_level + 1)
204
+ r[:children], _is_current_parent, r[:levels] =
205
+ cama_menu_parse_items(nav_menu_item.children, max_levels, internal_level + 1)
162
206
  if _is_current_parent
163
207
  is_current_parent = true
164
208
  r[:current_parent] = true
165
209
  end
166
210
  r[:levels] = r[:levels] + 1
167
211
  end
212
+
168
213
  is_current_parent = true if r[:current_item]
169
214
  levels << r[:levels]
170
215
  res << r
@@ -181,11 +226,12 @@ module CamaleonCms
181
226
  # draw the breadcrumb as html list
182
227
  def breadcrumb_draw
183
228
  res = []
184
- @_front_breadcrumb.each_with_index do |item, index|
185
- res << if @_front_breadcrumb.size == (index + 1) # last menu
229
+ breadcrumb_items = nav_menu_breadcrumb_items
230
+ breadcrumb_items.each_with_index do |item, index|
231
+ res << if breadcrumb_items.size == (index + 1) # last menu
186
232
  "<li class='active'>#{item[0]}</li>"
187
233
  else
188
- "<li><a href='#{item[1]}'>#{item[0]}</a></li>"
234
+ "<li><a href='#{ERB::Util.html_escape(item[1])}'>#{item[0]}</a></li>"
189
235
  end
190
236
  end
191
237
  res.join('')
@@ -195,11 +241,11 @@ module CamaleonCms
195
241
  # label => label of the link
196
242
  # url: url for the link
197
243
  def breadcrumb_add(label, url = '', prepend = false)
198
- @_front_breadcrumb ||= []
244
+ breadcrumb_items = nav_menu_breadcrumb_items
199
245
  if prepend
200
- @_front_breadcrumb.unshift([label, url])
246
+ breadcrumb_items.unshift([label, url])
201
247
  else
202
- @_front_breadcrumb << [label, url]
248
+ breadcrumb_items << [label, url]
203
249
  end
204
250
  end
205
251
 
@@ -213,34 +259,44 @@ module CamaleonCms
213
259
  if is_from_backend || post.can_visit?
214
260
  result = { link: post.the_url(as_path: true), name: post.the_title, type_menu: type_menu,
215
261
  url_edit: post.the_edit_url }
216
- result[:current] = @cama_visited_post.present? && @cama_visited_post.id == post.id unless is_from_backend
262
+ unless is_from_backend
263
+ visited_post = nav_menu_visited_state(:frontend_visited_post)
264
+ result[:current] = visited_post.present? && visited_post.id == post.id
265
+ end
217
266
  end
218
267
  when 'category'
219
268
  category = CamaleonCms::Category.find(nav_menu_item.url).decorate
220
269
  result = { link: category.the_url(as_path: true), name: category.the_title,
221
270
  url_edit: category.the_edit_url }
222
271
  unless is_from_backend
223
- result[:current] =
224
- @cama_visited_category.present? && @cama_visited_category.id == category.id
272
+ visited_category = nav_menu_visited_state(:frontend_visited_category)
273
+ result[:current] = visited_category.present? && visited_category.id == category.id
225
274
  end
226
275
  when 'post_tag'
227
276
  post_tag = CamaleonCms::PostTag.find(nav_menu_item.url).decorate
228
277
  result = { link: post_tag.the_url(as_path: true), name: post_tag.the_title,
229
278
  url_edit: post_tag.the_edit_url }
230
- result[:current] = @cama_visited_tag.present? && @cama_visited_tag.id == post_tag.id unless is_from_backend
279
+ unless is_from_backend
280
+ visited_tag = nav_menu_visited_state(:frontend_visited_tag)
281
+ result[:current] = visited_tag.present? && visited_tag.id == post_tag.id
282
+ end
231
283
  when 'post_type'
232
284
  post_type = CamaleonCms::PostType.find(nav_menu_item.url).decorate
233
285
  result = { link: post_type.the_url(as_path: true), name: post_type.the_title,
234
286
  url_edit: post_type.the_edit_url }
235
287
  unless is_from_backend
236
- result[:current] =
237
- @cama_visited_post_type.present? && @cama_visited_post_type.id == post_type.id
288
+ visited_post_type = nav_menu_visited_state(:frontend_visited_post_type)
289
+ result[:current] = visited_post_type.present? && visited_post_type.id == post_type.id
238
290
  end
239
291
  when 'external'
240
292
  result = { link: nav_menu_item.url.to_s.translate, name: nav_menu_item.name.to_s.translate, current: false }
241
293
  # permit to customize or mark as current menu
242
294
  # _args: (HASH) {menu_item: Model Menu Item, parsed_menu: Parsed Menu }
243
- # Sample parsed_menu: {link: "url of the link", name: "Text of the menu", current: Boolean (true => is current menu, false => not current menu item)}
295
+ # Sample parsed_menu:
296
+ # {
297
+ # link: "url of the link", name: "Text of the menu",
298
+ # current: Boolean (true => is current menu, false => not current menu item)
299
+ # }
244
300
  unless is_from_backend
245
301
  result[:link] = cama_root_path if result[:link] == 'root_url'
246
302
  result[:link] = site_current_path if site_current_path == "#{current_site.the_path}#{result[:link]}"
@@ -251,11 +307,15 @@ module CamaleonCms
251
307
  end
252
308
  else
253
309
  # permit to build custom menu items registered as Custom Menu by hook "nav_menu_custom"
254
- # sample: def my_parse_custom_menu_item_listener(args);
310
+ # sample:
311
+ # def my_parse_custom_menu_item_listener(args)
255
312
  # if args[:menu_item].kind == 'MyModelClass'
256
313
  # my_model = MyModelClass.find(args[:menu_item].url)
257
- # res = {name: my_model.name, url_edit: my_model_edit_url(id: my_model.id), link: my_model_public_url(id: my_model.id)}
258
- # res[:current] = site_current_path == my_model_public_url(id: my_model.id) unless args[:is_from_backend]
314
+ # res = {
315
+ # name: my_model.name, url_edit: my_model_edit_url(id: my_model.id),
316
+ # link: my_model_url(id: my_model.id)
317
+ # }
318
+ # res[:current] = site_current_path == my_model_url(id: my_model.id) unless args[:is_from_backend]
259
319
  # args[:parsed_menu] = res
260
320
  # end
261
321
  # end
@@ -264,17 +324,34 @@ module CamaleonCms
264
324
  result = hook_args[:parsed_menu]
265
325
  end
266
326
  rescue StandardError => e
267
- Rails.logger.error "Camaleon CMS - Menu Item Not Found => Skipped menu for: #{e.message} (#{nav_menu_item.inspect})".cama_log_style(:red)
327
+ Rails.logger.error(
328
+ "Camaleon CMS - Menu Item Not Found => Skipped menu for: #{e.message} (#{nav_menu_item.inspect})"
329
+ .cama_log_style(:red)
330
+ )
268
331
  end
269
332
 
270
- # permit to customize data, like: current, title, ... of parsed menu item or skip menu item by assigning false into :parsed_menu
271
- if is_from_backend
272
- result
273
- else
274
- _args = { menu_item: nav_menu_item, parsed_menu: result }
275
- hooks_run('on_render_front_menu_item', _args)
276
- _args[:parsed_menu]
333
+ # permit customizing data, like: current, title, ... of parsed menu item or
334
+ # skip menu item by assigning false into :parsed_menu
335
+ return result if is_from_backend
336
+
337
+ _args = { menu_item: nav_menu_item, parsed_menu: result }
338
+ hooks_run('on_render_front_menu_item', _args)
339
+ _args[:parsed_menu]
340
+ end
341
+
342
+ private
343
+
344
+ def nav_menu_breadcrumb_items
345
+ CurrentRequest.theme_helper_state ||= {}
346
+ CurrentRequest.theme_helper_state[:front_breadcrumb] ||= []
347
+ end
348
+
349
+ def nav_menu_visited_state(current_request_attr)
350
+ if respond_to?(:camaleon_frontend_visited_state, true)
351
+ return camaleon_frontend_visited_state(current_request_attr)
277
352
  end
353
+
354
+ CurrentRequest.public_send(current_request_attr)
278
355
  end
279
356
  end
280
357
  end
@@ -5,42 +5,49 @@ module CamaleonCms
5
5
  # you can pass custom data to overwrite default data generated by the system
6
6
  # return hash of seo attributes used by the_head(...)
7
7
  def cama_the_seo(data = {})
8
+ visited_post = camaleon_frontend_visited_state(:frontend_visited_post)
9
+ visited_post_type = camaleon_frontend_visited_state(:frontend_visited_post_type)
10
+ visited_tag = camaleon_frontend_visited_state(:frontend_visited_tag)
11
+ visited_category = camaleon_frontend_visited_state(:frontend_visited_category)
12
+ visited_user = CurrentRequest.frontend_user
13
+ visited_user ||= instance_variable_get(:@user) if instance_variable_defined?(:@user) # legacy @user (M20)
14
+
8
15
  data2 = if is_home?
9
16
  {}
10
17
  elsif is_page?
11
- { image: @cama_visited_post.the_thumb_url,
12
- title: "#{current_site.the_title} | #{@cama_visited_post.the_title}",
13
- description: @cama_visited_post.the_excerpt,
14
- keywords: @cama_visited_post.the_keywords,
15
- object: @cama_visited_post }
18
+ { image: visited_post.the_thumb_url,
19
+ title: "#{current_site.the_title} | #{visited_post.the_title}",
20
+ description: visited_post.the_excerpt,
21
+ keywords: visited_post.the_keywords,
22
+ object: visited_post }
16
23
  elsif is_ajax?
17
24
  {}
18
25
  elsif is_search?
19
26
  { title: "#{current_site.the_title} | #{ct('search_title', default: 'Search')}" }
20
27
  elsif is_post_type?
21
- { image: @cama_visited_post_type.the_thumb_url,
22
- title: "#{current_site.the_title} | #{@cama_visited_post_type.the_title}",
23
- description: @cama_visited_post_type.the_excerpt,
24
- keywords: @cama_visited_post_type.the_keywords,
25
- object: @cama_visited_post_type }
28
+ { image: visited_post_type.the_thumb_url,
29
+ title: "#{current_site.the_title} | #{visited_post_type.the_title}",
30
+ description: visited_post_type.the_excerpt,
31
+ keywords: visited_post_type.the_keywords,
32
+ object: visited_post_type }
26
33
  elsif is_post_tag?
27
- { title: "#{current_site.the_title} | #{@cama_visited_tag.the_title}",
28
- description: @cama_visited_tag.the_excerpt,
29
- keywords: @cama_visited_tag.the_keywords,
30
- object: @cama_visited_tag }
34
+ { title: "#{current_site.the_title} | #{visited_tag.the_title}",
35
+ description: visited_tag.the_excerpt,
36
+ keywords: visited_tag.the_keywords,
37
+ object: visited_tag }
31
38
  elsif is_category?
32
- { image: @cama_visited_category.the_thumb_url,
33
- title: "#{current_site.the_title} | #{@cama_visited_category.the_title}",
34
- description: @cama_visited_category.the_excerpt,
35
- keywords: @cama_visited_category.the_keywords,
36
- object: @cama_visited_category }
39
+ { image: visited_category.the_thumb_url,
40
+ title: "#{current_site.the_title} | #{visited_category.the_title}",
41
+ description: visited_category.the_excerpt,
42
+ keywords: visited_category.the_keywords,
43
+ object: visited_category }
37
44
  elsif is_profile?
38
- { image: @user.the_avatar, title: "#{current_site.the_title} | #{@user.the_name}",
39
- description: @user.the_slogan, object: @user }
45
+ { image: visited_user.the_avatar, title: "#{current_site.the_title} | #{visited_user.the_name}",
46
+ description: visited_user.the_slogan, object: visited_user }
40
47
  else
41
48
  {}
42
49
  end
43
- cama_build_seo(data2.merge!(@_cama_seo_setting_values || {}).merge(data))
50
+ cama_build_seo(data2.merge!(camaleon_frontend_seo_settings || {}).merge(data))
44
51
  end
45
52
 
46
53
  # permit to define seo attributes by code without hooks
@@ -51,19 +58,23 @@ module CamaleonCms
51
58
  # )
52
59
  # This values will be shown within the_head(...)
53
60
  def cama_seo_settings(options)
54
- @_cama_seo_setting_values ||= {}
55
- @_cama_seo_setting_values = @_cama_seo_setting_values.merge(options)
61
+ CurrentRequest.frontend_seo_settings ||= {}
62
+ CurrentRequest.frontend_seo_settings = CurrentRequest.frontend_seo_settings.merge(options)
56
63
  end
57
64
 
58
65
  private
59
66
 
67
+ def camaleon_frontend_seo_settings
68
+ CurrentRequest.frontend_seo_settings
69
+ end
70
+
60
71
  # create seo attributes with options + default attributes
61
72
  def cama_build_seo(options = {})
62
- options[:image] = current_site.get_option('screenshot', current_site.the_logo) unless options[:image].present?
63
- options[:title] = current_site.the_title unless options[:title].present?
64
- options[:description] = current_site.the_option('seo_description') unless options[:description].present?
73
+ options[:image] = current_site.get_option('screenshot', current_site.the_logo) if options[:image].blank?
74
+ options[:title] = current_site.the_title if options[:title].blank?
75
+ options[:description] = current_site.the_option('seo_description') if options[:description].blank?
65
76
  options[:url] = request.original_url
66
- options[:keywords] = current_site.the_option('keywords') unless options[:keywords].present?
77
+ options[:keywords] = current_site.the_option('keywords') if options[:keywords].blank?
67
78
 
68
79
  s = {
69
80
  keywords: options[:keywords],
@@ -9,48 +9,48 @@ module CamaleonCms
9
9
  # return current url visited as path
10
10
  # http://localhost:9001/category/cat-post-2 => /category/cat-post-2
11
11
  def site_current_path
12
- @_site_current_path ||= site_current_url.sub(cama_root_url(locale: nil), '/')
12
+ CurrentRequest.frontend_site_current_path ||= site_current_url.sub(cama_root_url(locale: nil), '/')
13
13
  end
14
14
 
15
15
  # **************** section is a? ****************#
16
16
  # check if current section visited is home page
17
17
  def is_home?
18
- @cama_visited_home.present?
18
+ camaleon_frontend_visited_state(:frontend_visited_home).present?
19
19
  end
20
20
 
21
21
  # check if current section visited is for post
22
22
  def is_page?
23
- @cama_visited_post.present?
23
+ camaleon_frontend_visited_state(:frontend_visited_post).present?
24
24
  end
25
25
 
26
26
  # check if current section visited is for ajax
27
27
  def is_ajax?
28
- @cama_visited_ajax.present?
28
+ camaleon_frontend_visited_state(:frontend_visited_ajax).present?
29
29
  end
30
30
 
31
31
  # check if current section visited is for search
32
32
  def is_search?
33
- @cama_visited_search.present?
33
+ camaleon_frontend_visited_state(:frontend_visited_search).present?
34
34
  end
35
35
 
36
36
  # check if current section visited is for post type
37
37
  def is_post_type?
38
- @cama_visited_post_type.present?
38
+ camaleon_frontend_visited_state(:frontend_visited_post_type).present?
39
39
  end
40
40
 
41
41
  # check if current section visited is for post tag
42
42
  def is_post_tag?
43
- @cama_visited_tag.present?
43
+ camaleon_frontend_visited_state(:frontend_visited_tag).present?
44
44
  end
45
45
 
46
46
  # check if current section visited is for category
47
47
  def is_category?
48
- @cama_visited_category.present?
48
+ camaleon_frontend_visited_state(:frontend_visited_category).present?
49
49
  end
50
50
 
51
51
  # check if visited page is user profile (frontend)
52
52
  def is_profile?
53
- @cama_visited_profile == true
53
+ camaleon_frontend_visited_state(:frontend_visited_profile) == true
54
54
  end
55
55
 
56
56
  # **************** end section is a? ****************#
@@ -60,9 +60,29 @@ module CamaleonCms
60
60
  # seo_attrs: Custom attributes for seo in Hash format
61
61
  # show_seo: (Boolean) control to append or not the seo attributes
62
62
  def the_head(seo_attrs = {}, _show_seo = true)
63
- js = "<script>var ROOT_URL = '#{cama_root_url}'; var LANGUAGE = '#{I18n.locale}'; </script>".html_safe
64
- js += cama_draw_pre_asset_contents
65
- "#{csrf_meta_tag || ''}\n#{display_meta_tags(cama_the_seo(seo_attrs))}\n#{js}\n#{cama_draw_custom_assets}"
63
+ js = javascript_tag("var ROOT_URL = #{cama_root_url.to_json}; var LANGUAGE = #{I18n.locale.to_s.to_json};")
64
+ safe_join(
65
+ [
66
+ csrf_meta_tag.presence,
67
+ display_meta_tags(cama_the_seo(seo_attrs)).presence,
68
+ js,
69
+ cama_draw_pre_asset_contents.presence,
70
+ cama_draw_custom_assets.presence
71
+ ].compact,
72
+ "\n"
73
+ )
74
+ end
75
+
76
+ private
77
+
78
+ def camaleon_frontend_visited_state(current_request_attr)
79
+ value = CurrentRequest.public_send(current_request_attr)
80
+ return value unless value.nil?
81
+
82
+ # Fall back to a legacy @cama_visited_* ivar a plugin front controller may have set directly;
83
+ # core writes both stores, so stock flows never reach here. Regression M20.
84
+ legacy_ivar = CamaleonCms::FrontendVisitedStateConcern::LEGACY_VISITED_IVAR_BY_ATTR[current_request_attr]
85
+ instance_variable_get(legacy_ivar) if legacy_ivar && instance_variable_defined?(legacy_ivar)
66
86
  end
67
87
  end
68
88
  end
@@ -16,7 +16,8 @@ module CamaleonCms
16
16
  # hook_key: hook key
17
17
  # params: params for hook
18
18
  def hooks_run(hook_key, params = nil)
19
- PluginRoutes.enabled_apps(current_site).each do |plugin|
19
+ theme_slug = current_theme&.slug.presence || current_site.get_theme_slug
20
+ PluginRoutes.enabled_apps(current_site, theme_slug).each do |plugin|
20
21
  _do_hook(plugin, hook_key, params)
21
22
  end
22
23
 
@@ -28,16 +29,16 @@ module CamaleonCms
28
29
 
29
30
  # skip hook function with name: hook_function_name
30
31
  def hook_skip(hook_function_name)
31
- @_hooks_skip << hook_function_name
32
+ hook_skip_list << hook_function_name
32
33
  end
33
34
 
34
35
  private
35
36
 
36
37
  def _do_hook(plugin, hook_key, params = nil)
37
- return if !plugin.present? || !plugin['hooks'].present? || !plugin['hooks'][hook_key].present?
38
+ return if plugin.blank? || plugin['hooks'].blank? || plugin['hooks'][hook_key].blank?
38
39
 
39
40
  plugin['hooks'][hook_key].each do |hook|
40
- next if @_hooks_skip.present? && @_hooks_skip.include?(hook)
41
+ next if hook_skip_list.include?(hook)
41
42
 
42
43
  begin
43
44
  if params.nil?
@@ -60,5 +61,14 @@ module CamaleonCms
60
61
  end
61
62
  end
62
63
  end
64
+
65
+ def hook_skip_list
66
+ state = camaleon_hooks_state
67
+ state[:hooks_skip] ||= []
68
+ end
69
+
70
+ def camaleon_hooks_state
71
+ CurrentRequest.hooks_helper_state ||= {}
72
+ end
63
73
  end
64
74
  end