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
@@ -2,31 +2,19 @@ module CamaleonCms
2
2
  module Admin
3
3
  module CustomFieldsHelper
4
4
  def cama_custom_field_elements
5
- return @_cama_custom_field_elements if @_cama_custom_field_elements.present?
5
+ return CurrentRequest.custom_field_elements if CurrentRequest.custom_field_elements.present?
6
6
 
7
7
  items = {}
8
8
  items[:text_box] = {
9
9
  key: 'text_box',
10
10
  label: t('camaleon_cms.admin.custom_field.fields.text_box'),
11
- options: {
12
- required: true,
13
- multiple: true,
14
- translate: true,
15
- default_value: '',
16
- show_frontend: true
17
- }
11
+ options: { required: true, multiple: true, translate: true, default_value: '', show_frontend: true }
18
12
  }
19
13
 
20
14
  items[:text_area] = {
21
15
  key: 'text_area',
22
16
  label: t('camaleon_cms.admin.custom_field.fields.text_area'),
23
- options: {
24
- required: true,
25
- multiple: true,
26
- translate: true,
27
- default_value: '',
28
- show_frontend: true
29
- }
17
+ options: { required: true, multiple: true, translate: true, default_value: '', show_frontend: true }
30
18
  }
31
19
  items[:select] = {
32
20
  key: 'select',
@@ -35,10 +23,7 @@ module CamaleonCms
35
23
  required: true,
36
24
  multiple: true,
37
25
  translate: true,
38
- multiple_options: {
39
- label: t('camaleon_cms.admin.settings.options_select'),
40
- default: 'radio'
41
- },
26
+ multiple_options: { label: t('camaleon_cms.admin.settings.options_select'), default: 'radio' },
42
27
  show_frontend: true
43
28
  }
44
29
  }
@@ -298,7 +283,8 @@ module CamaleonCms
298
283
  }
299
284
 
300
285
  # evaluate the content of command value on listing
301
- # sample command: options_from_collection_for_select(current_site.the_posts("commerce").decorate, :id, :the_title)
286
+ # sample command:
287
+ # options_from_collection_for_select(current_site.the_posts("commerce").decorate, :id, :the_title)
302
288
  items[:select_eval] = {
303
289
  key: 'select_eval',
304
290
  label: t('camaleon_cms.admin.custom_field.fields.select_eval'),
@@ -344,17 +330,32 @@ module CamaleonCms
344
330
  }
345
331
  r = { fields: items }
346
332
  hooks_run('extra_custom_fields', r)
347
- @_cama_custom_field_elements = r[:fields]
333
+ CurrentRequest.custom_field_elements = r[:fields]
348
334
  end
349
335
 
350
336
  # add your model class into custom fields editor
351
337
  # Note: to use custom fields on your model, you need the following:
352
- # - add: belongs_to :site (in your model) //don't forget multi site support, i.e.: you need site_id attribute in your table
338
+ # - add: belongs_to :site (in your model)
339
+ # //don't forget multi site support, i.e.: you need site_id attribute in your table
353
340
  # - add: include CamaleonCms::CustomFieldsRead (in your model)
354
341
  # ==> With this, you can manage your model like a plugin. Check api -> custom fields section into docs)
355
342
  # model_class: class name (Product)
356
343
  def cf_add_model(model_class)
357
- @_extra_models_for_fields << model_class
344
+ CurrentRequest.extra_models_for_fields ||= []
345
+ CurrentRequest.extra_models_for_fields << model_class
346
+ end
347
+
348
+ # Models offered in the field-group placement dropdown: those registered via cf_add_model plus any
349
+ # a custom_field_custom_models hook appends. Seeded from the legacy @_extra_models_for_fields ivar
350
+ # so a controller still assigning it keeps working. The dropdown used to read that ivar while
351
+ # cf_add_model wrote CurrentRequest, so registered models never appeared. Regression M18.
352
+ # The seed and the hook payload are copies: hook appends must reach only this read, not
353
+ # accumulate in the request store or leak into the controller ivar.
354
+ def cf_extra_models_for_fields
355
+ CurrentRequest.extra_models_for_fields ||= (instance_variable_get(:@_extra_models_for_fields) || []).dup
356
+ f_args = { models: CurrentRequest.extra_models_for_fields.dup }
357
+ hooks_run('custom_field_custom_models', f_args)
358
+ f_args[:models]
358
359
  end
359
360
  end
360
361
  end
@@ -1,34 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CamaleonCms
2
4
  module Admin
3
5
  module MenusHelper
4
6
  include CamaleonCms::Admin::BreadcrumbHelper
7
+ include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::OutputSafetyHelper
5
9
 
6
10
  def admin_menus_add_commons
7
- admin_menu_add_menu('dashboard',
8
- { icon: 'dashboard', title: t('camaleon_cms.admin.sidebar.dashboard'),
9
- url: cama_admin_dashboard_path })
10
- items = []
11
+ admin_menu_add_menu(
12
+ 'dashboard',
13
+ { icon: 'dashboard', title: t('camaleon_cms.admin.sidebar.dashboard'), url: cama_admin_dashboard_path }
14
+ )
11
15
 
12
- current_site.post_types.eager_load(:metas).visible_menu.all.each do |pt|
16
+ items = []
17
+ current_site.post_types.eager_load(:metas).visible_menu.find_each do |pt|
13
18
  pt = pt.decorate
14
19
  items_i = []
15
- if can? :posts,
16
- pt
20
+ if can? :posts, pt
17
21
  items_i << { icon: 'list', title: t('camaleon_cms.admin.post_type.all').to_s,
18
22
  url: cama_admin_post_type_posts_path(pt.id) }
19
23
  end
20
- if can? :create_post,
21
- pt
24
+ if can? :create_post, pt
22
25
  items_i << { icon: 'plus', title: t('camaleon_cms.admin.post_type.add_new', type_title: pt.the_title).to_s,
23
26
  url: new_cama_admin_post_type_post_path(pt.id) }
24
27
  end
25
- if pt.manage_categories? && (can? :categories,
26
- pt)
28
+ if pt.manage_categories? && (can? :categories, pt)
27
29
  items_i << { icon: 'folder-open', title: t('camaleon_cms.admin.post_type.categories'),
28
30
  url: cama_admin_post_type_categories_path(pt.id) }
29
31
  end
30
- if pt.manage_tags? && (can? :post_tags,
31
- pt)
32
+ if pt.manage_tags? && (can? :post_tags, pt)
32
33
  items_i << { icon: 'tags', title: t('camaleon_cms.admin.post_type.tags'),
33
34
  url: cama_admin_post_type_post_tags_path(pt.id) }
34
35
  end
@@ -36,58 +37,93 @@ module CamaleonCms
36
37
  items << { icon: pt.get_option('icon', 'copy'), title: pt.the_title, url: '', items: items_i }
37
38
  end
38
39
  end
40
+
39
41
  if items.present?
40
- admin_menu_add_menu('content',
41
- { icon: 'database', title: t('camaleon_cms.admin.sidebar.contents'), url: '', items: items,
42
- datas: "data-intro='#{t('camaleon_cms.admin.intro.content')}' data-position='right' data-wait='600'" })
42
+ admin_menu_add_menu(
43
+ 'content',
44
+ {
45
+ icon: 'database', title: t('camaleon_cms.admin.sidebar.contents'), url: '', items: items,
46
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.content')}' data-position='right' data-wait='600'"
47
+ }
48
+ )
43
49
  end
44
50
  # end
45
51
 
46
- if can? :manage,
47
- :media
48
- admin_menu_add_menu('media',
49
- { icon: 'picture-o', title: t('camaleon_cms.admin.sidebar.media'), url: cama_admin_media_path,
50
- datas: "data-intro='#{t('camaleon_cms.admin.intro.media')}' data-position='right'" })
52
+ if can? :manage, :media
53
+ admin_menu_add_menu(
54
+ 'media',
55
+ {
56
+ icon: 'picture-o', title: t('camaleon_cms.admin.sidebar.media'), url: cama_admin_media_path,
57
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.media')}' data-position='right'"
58
+ }
59
+ )
51
60
  end
52
- if can? :manage,
53
- :comments
54
- admin_menu_add_menu('comments',
55
- { icon: 'comments', title: t('camaleon_cms.admin.sidebar.comments'), url: cama_admin_comments_path,
56
- datas: "data-intro='#{t('camaleon_cms.admin.intro.comments')}' data-position='right'" })
61
+ if can? :manage, :comments
62
+ admin_menu_add_menu(
63
+ 'comments',
64
+ {
65
+ icon: 'comments', title: t('camaleon_cms.admin.sidebar.comments'), url: cama_admin_comments_path,
66
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.comments')}' data-position='right'"
67
+ }
68
+ )
57
69
  end
58
70
 
59
71
  items = []
60
- if can? :manage,
61
- :themes
62
- items << { icon: 'desktop', title: t('camaleon_cms.admin.sidebar.themes'), url: cama_admin_appearances_themes_path,
63
- datas: "data-intro='#{t('camaleon_cms.admin.intro.themes')}' data-position='right'" }
72
+ if can? :manage, :themes
73
+ items << {
74
+ icon: 'desktop', title: t('camaleon_cms.admin.sidebar.themes'), url: cama_admin_appearances_themes_path,
75
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.themes')}' data-position='right'"
76
+ }
64
77
  end
65
- if can? :manage,
66
- :widgets
67
- items << { icon: 'archive', title: t('camaleon_cms.admin.sidebar.widgets'),
68
- url: cama_admin_appearances_widgets_main_index_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.widgets')}' data-position='right'" }
78
+ if can? :manage, :widgets
79
+ items << {
80
+ icon: 'archive', title: t('camaleon_cms.admin.sidebar.widgets'),
81
+ url: cama_admin_appearances_widgets_main_index_path,
82
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.widgets')}' data-position='right'"
83
+ }
69
84
  end
70
- if can? :manage,
71
- :nav_menu
72
- items << { icon: 'list', title: t('camaleon_cms.admin.sidebar.menus'), url: cama_admin_appearances_nav_menus_path,
73
- datas: "data-intro='#{t('camaleon_cms.admin.intro.menus', image: view_context.asset_path('camaleon_cms/admin/intro/menus.png'))}' data-position='right'" }
85
+ if can? :manage, :nav_menu
86
+ intro_menus_data =
87
+ t('camaleon_cms.admin.intro.menus', image: view_context.asset_path('camaleon_cms/admin/intro/menus.png'))
88
+ items << {
89
+ icon: 'list', title: t('camaleon_cms.admin.sidebar.menus'), url: cama_admin_appearances_nav_menus_path,
90
+ datas: "data-intro='#{intro_menus_data}' data-position='right'"
91
+ }
74
92
  end
75
- if can? :manage,
76
- :shortcodes
77
- items << { icon: 'code', title: t('camaleon_cms.admin.sidebar.shortcodes', default: 'Shortcodes'),
78
- url: cama_admin_settings_shortcodes_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.shortcodes')}' data-position='right'" }
93
+ if can? :manage, :shortcodes
94
+ items << {
95
+ icon: 'code', title: t('camaleon_cms.admin.sidebar.shortcodes', default: 'Shortcodes'),
96
+ url: cama_admin_settings_shortcodes_path,
97
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.shortcodes')}' data-position='right'"
98
+ }
79
99
  end
80
100
  if items.present?
81
- admin_menu_add_menu('appearance',
82
- { icon: 'paint-brush', title: t('camaleon_cms.admin.sidebar.appearance'), url: '', items: items,
83
- datas: "data-intro='#{t('camaleon_cms.admin.intro.appearance')}' data-position='right' data-wait='500'" })
101
+ admin_menu_add_menu(
102
+ 'appearance',
103
+ {
104
+ icon: 'paint-brush', title: t('camaleon_cms.admin.sidebar.appearance'), url: '', items: items,
105
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.appearance')}' data-position='right' data-wait='500'"
106
+ }
107
+ )
84
108
  end
85
109
 
86
- if can? :manage,
87
- :plugins
88
- admin_menu_add_menu('plugins', { icon: 'plug', title: "#{t('camaleon_cms.admin.sidebar.plugins')} <small class='label label-primary'>#{PluginRoutes.all_plugins.clone.delete_if do |plugin|
89
- plugin[:domain].present? && !plugin[:domain].split(',').include?(current_site.the_slug)
90
- end.size}</small>", url: cama_admin_plugins_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.plugins')}' data-position='right'" })
110
+ if can? :manage, :plugins
111
+ plugin_count = PluginRoutes.all_plugins.count do |plugin|
112
+ !(plugin[:domain].present? && !plugin[:domain].split(',').include?(current_site.the_slug))
113
+ end
114
+ admin_menu_add_menu(
115
+ 'plugins',
116
+ {
117
+ icon: 'plug',
118
+ title: safe_join([
119
+ t('camaleon_cms.admin.sidebar.plugins'),
120
+ ' ',
121
+ content_tag(:small, plugin_count, class: 'label label-primary')
122
+ ]),
123
+ url: cama_admin_plugins_path,
124
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.plugins')}' data-position='right'"
125
+ }
126
+ )
91
127
  end
92
128
 
93
129
  if can? :manage, :users
@@ -95,117 +131,154 @@ module CamaleonCms
95
131
  items << { icon: 'list', title: t('camaleon_cms.admin.users.all_users'), url: cama_admin_users_path }
96
132
  items << { icon: 'plus', title: t('camaleon_cms.admin.users.add_user'), url: new_cama_admin_user_path }
97
133
  items << { icon: 'group', title: t('camaleon_cms.admin.users.user_roles'), url: cama_admin_user_roles_path }
98
- admin_menu_add_menu('users',
99
- { icon: 'users', title: t('camaleon_cms.admin.sidebar.users'), url: '', items: items,
100
- datas: "data-intro='#{t('camaleon_cms.admin.intro.users')}' data-position='right' data-wait='500'" })
134
+ admin_menu_add_menu(
135
+ 'users',
136
+ {
137
+ icon: 'users', title: t('camaleon_cms.admin.sidebar.users'), url: '', items: items,
138
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.users')}' data-position='right' data-wait='500'"
139
+ }
140
+ )
101
141
  end
102
142
 
103
143
  items = []
104
144
  if can? :manage, :settings
105
- items << { icon: 'desktop', title: t('camaleon_cms.admin.sidebar.general_site'),
106
- url: cama_admin_settings_site_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.gral_site')}' data-position='right'" }
145
+ items << {
146
+ icon: 'desktop', title: t('camaleon_cms.admin.sidebar.general_site'),
147
+ url: cama_admin_settings_site_path,
148
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.gral_site')}' data-position='right'"
149
+ }
107
150
  if current_site.manage_sites?
108
- items << { icon: 'cog', title: t('camaleon_cms.admin.sidebar.sites'), url: cama_admin_settings_sites_path,
109
- datas: "data-intro='#{t('camaleon_cms.admin.intro.sites')}' data-position='right'" }
151
+ items << {
152
+ icon: 'cog', title: t('camaleon_cms.admin.sidebar.sites'),
153
+ url: cama_admin_settings_sites_path,
154
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.sites')}' data-position='right'"
155
+ }
110
156
  end
111
- items << { icon: 'files-o', title: t('camaleon_cms.admin.sidebar.content_groups'),
112
- url: cama_admin_settings_post_types_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.post_type')}' data-position='right'" }
113
- items << { icon: 'cog', title: t('camaleon_cms.admin.sidebar.custom_fields'),
114
- url: cama_admin_settings_custom_fields_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.custom_fields')}' data-position='right'" }
115
- items << { icon: 'language', title: t('camaleon_cms.admin.sidebar.languages'),
116
- url: cama_admin_settings_languages_path, datas: "data-intro='#{t('camaleon_cms.admin.intro.languages')}' data-position='right'" }
157
+ items << {
158
+ icon: 'files-o', title: t('camaleon_cms.admin.sidebar.content_groups'),
159
+ url: cama_admin_settings_post_types_path,
160
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.post_type')}' data-position='right'"
161
+ }
162
+ items << {
163
+ icon: 'cog', title: t('camaleon_cms.admin.sidebar.custom_fields'),
164
+ url: cama_admin_settings_custom_fields_path,
165
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.custom_fields')}' data-position='right'"
166
+ }
167
+ items << {
168
+ icon: 'language', title: t('camaleon_cms.admin.sidebar.languages'),
169
+ url: cama_admin_settings_languages_path,
170
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.languages')}' data-position='right'"
171
+ }
117
172
  end
118
173
 
119
- if can? :manage,
120
- :theme_settings
121
- items << { icon: 'windows', title: t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings'),
122
- url: cama_admin_settings_theme_path }
174
+ if can? :manage, :theme_settings
175
+ items << {
176
+ icon: 'windows', title: t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings'),
177
+ url: cama_admin_settings_theme_path
178
+ }
123
179
  end
124
- return unless items.present?
180
+ return if items.blank?
125
181
 
126
- admin_menu_add_menu('settings',
127
- { icon: 'cogs', title: t('camaleon_cms.admin.sidebar.settings'), url: '', items: items,
128
- datas: "data-intro='#{t('camaleon_cms.admin.intro.settings')}' data-position='right' data-wait='500'" })
182
+ admin_menu_add_menu(
183
+ 'settings',
184
+ {
185
+ icon: 'cogs', title: t('camaleon_cms.admin.sidebar.settings'), url: '', items: items,
186
+ datas: "data-intro='#{t('camaleon_cms.admin.intro.settings')}' data-position='right' data-wait='500'"
187
+ }
188
+ )
129
189
  end
130
190
 
131
- # add menu item to admin menu at the the end
132
- # key: key for menu
133
- # menu: is hash like this: {icon: "dashboard", title: "My title", url: my_path, items: [sub menus]}
191
+ # add a menu item to the menu at the end
192
+ # key: key for the menu
193
+ # menu: is hash like this: { icon: "dashboard", title: "My title", url: my_path, items: [sub menus] }
134
194
  # - icon: font-awesome icon (it is already included "fa fa-")
135
195
  # - title: title for the menu
136
196
  # - url: url for the menu
137
- # - items: is an recursive array of the menus without a key
138
- # - datas: html data text for this menu item
197
+ # - items: is a recursive array of the menus without a key
198
+ # - datas: HTML data text for this menu item
139
199
  def admin_menu_add_menu(key, menu)
140
- @_admin_menus[key] = menu
200
+ CurrentRequest.admin_menu_items ||= {}
201
+ CurrentRequest.admin_menu_items[key] = menu
141
202
  end
142
203
 
143
- # append sub menu to menu with key = key
204
+ # append sub menu to menu with a key = key
144
205
  # menu: is hash like this: {icon: "dashboard", title: "My title", url: my_path, items: [sub menus]}
145
206
  def admin_menu_append_menu_item(key, menu)
146
- return unless @_admin_menus[key].present?
207
+ CurrentRequest.admin_menu_items ||= {}
208
+ return if CurrentRequest.admin_menu_items[key].blank?
147
209
 
148
- @_admin_menus[key][:items] = [] unless @_admin_menus[key].key?(:items)
149
- @_admin_menus[key][:items] << menu
210
+ CurrentRequest.admin_menu_items[key][:items] = [] unless CurrentRequest.admin_menu_items[key].key?(:items)
211
+ CurrentRequest.admin_menu_items[key][:items] << menu
150
212
  end
151
213
 
152
- # prepend sub menu to menu with key = key
153
- # menu: is hash like this: {icon: "dashboard", title: "My title", url: my_path, items: [sub menus]}
214
+ # prepend submenu to menu with key = key
215
+ # menu: is hash like this: { icon: "dashboard", title: "My title", url: my_path, items: [sub menus] }
154
216
  def admin_menu_prepend_menu_item(key, menu)
155
- return unless @_admin_menus[key].present?
217
+ CurrentRequest.admin_menu_items ||= {}
218
+ return if CurrentRequest.admin_menu_items[key].blank?
156
219
 
157
- @_admin_menus[key][:items] = [] unless @_admin_menus[key].key?(:items)
158
- @_admin_menus[key][:items] = [menu] + @_admin_menus[key][:items]
220
+ CurrentRequest.admin_menu_items[key][:items] = [] unless CurrentRequest.admin_menu_items[key].key?(:items)
221
+ CurrentRequest.admin_menu_items[key][:items] = [menu] + CurrentRequest.admin_menu_items[key][:items]
159
222
  end
160
223
 
161
- # add menu before menu with key = key_target
224
+ # add the menu before the menu with key = key_target
162
225
  # key_menu: key for menu
163
- # menu: is hash like this: {icon: "dashboard", title: "My title", url: my_path, items: [sub menus]}
226
+ # menu: is hash like this: { icon: "dashboard", title: "My title", url: my_path, items: [sub menus] }
164
227
  def admin_menu_insert_menu_before(key_target, key_menu, menu)
165
- res = {}
166
- @_admin_menus.each do |key, val|
167
- res[key_menu] = menu if key == key_target
168
- res[key] = val
228
+ CurrentRequest.admin_menu_items ||= {}
229
+ res = CurrentRequest.admin_menu_items.each_with_object({}) do |(key, val), hsh|
230
+ hsh[key_menu] = menu if key == key_target
231
+ hsh[key] = val
169
232
  end
170
- @_admin_menus = res
233
+ CurrentRequest.admin_menu_items.replace(res) # mutate in place so @_admin_menus stays a live alias (M19)
171
234
  end
172
235
 
173
236
  # add menu after menu with key = key_target
174
237
  # key_menu: key for menu
175
238
  # menu: is hash like this: {icon: "dashboard", title: "My title", url: my_path, items: [sub menus]}
176
239
  def admin_menu_insert_menu_after(key_target, key_menu, menu)
177
- res = {}
178
- @_admin_menus.each do |key, val|
179
- res[key] = val
180
- res[key_menu] = menu if key == key_target
240
+ CurrentRequest.admin_menu_items ||= {}
241
+ res = CurrentRequest.admin_menu_items.each_with_object({}) do |(key, val), hsh|
242
+ hsh[key] = val
243
+ hsh[key_menu] = menu if key == key_target
181
244
  end
182
- @_admin_menus = res
245
+ CurrentRequest.admin_menu_items.replace(res) # mutate in place so @_admin_menus stays a live alias (M19)
183
246
  end
184
247
 
185
248
  # draw admin menu as html
186
249
  def admin_menu_draw
187
- res = []
188
- @_tmp_menu_parents = []
189
- menus = _get_url_current
190
- menus.each do |menu|
191
- res << "<li data-key='#{menu[:key]}' class='#{if menu.key?(:items)
192
- 'treeview'
193
- end} #{if is_active_menu(menu[:key])
194
- 'active'
195
- end}' #{menu[:datas]}>
196
- <a href='#{menu[:url]}'><i class='fa fa-#{menu[:icon]}'></i> <span class=''>#{menu[:title]}</span> #{if menu.key?(:items)
197
- '<i class="fa fa-angle-left pull-right"></i>'
198
- end}</a>
199
- #{_admin_menu_draw(menu[:items]) if menu.key?(:items)}
200
- </li>"
201
- end
202
- res.join
250
+ CurrentRequest.admin_menu_items ||= {}
251
+ menu_parents = []
252
+ menus = _get_url_current(CurrentRequest.admin_menu_items, menu_parents)
253
+ safe_join(menus.map do |menu|
254
+ css_class = +''
255
+ css_class << 'treeview ' if menu.key?(:items)
256
+ css_class << 'active' if is_active_menu(menu[:key], menu_parents)
257
+ css_class.strip!
258
+ data_attrs = parse_datas(menu[:datas])
259
+ content_tag(
260
+ :li, ''.html_safe,
261
+ class: css_class.presence, data: { key: menu[:key] }.merge!(data_attrs.presence || {})
262
+ ) do
263
+ safe_join([
264
+ content_tag(:a, href: menu[:url]) do
265
+ safe_join([
266
+ content_tag(:i, nil, class: "fa fa-#{menu[:icon]}"),
267
+ ' ',
268
+ content_tag(:span, cama_admin_menu_title(menu[:title])),
269
+ (content_tag(:i, nil, class: 'fa fa-angle-left pull-right') if menu.key?(:items))
270
+ ].compact)
271
+ end,
272
+ (_admin_menu_draw(menu[:items], menu_parents) if menu.key?(:items))
273
+ ].compact)
274
+ end
275
+ end)
203
276
  end
204
277
 
205
278
  private
206
279
 
207
- def _get_url_current
208
- menus = @_admin_menus.map do |key, menu|
280
+ def _get_url_current(menus, menu_parents)
281
+ menus = menus.map do |key, menu|
209
282
  menu[:key] = key
210
283
  menu
211
284
  end
@@ -213,7 +286,7 @@ module CamaleonCms
213
286
  current_path_array = current_path.split('/')
214
287
  a_size = current_path_array.size
215
288
  (0..a_size).each do |i|
216
- resp = _search_in_menus(menus, current_path_array[0..a_size - i].join('/'))
289
+ resp = _search_in_menus(menus, current_path_array[0..a_size - i].join('/'), 0, menu_parents)
217
290
  bool = resp[:bool]
218
291
  menus = resp[:menus]
219
292
  break if bool
@@ -221,11 +294,11 @@ module CamaleonCms
221
294
  menus
222
295
  end
223
296
 
224
- def is_active_menu(key)
225
- @_tmp_menu_parents.map { |item| item[:key] == key }.include?(true)
297
+ def is_active_menu(key, menu_parents)
298
+ menu_parents.map { |item| item[:key] == key }.include?(true)
226
299
  end
227
300
 
228
- def _search_in_menus(menus, _url, parent_index = 0)
301
+ def _search_in_menus(menus, _url, parent_index = 0, menu_parents = [])
229
302
  bool = false
230
303
  menus.each_with_index do |menu, _index_menu|
231
304
  menu[:key] = "#{parent_index}__#{rand(999...99_999)}" if menu[:key].nil?
@@ -235,38 +308,75 @@ module CamaleonCms
235
308
  bool = url_path.to_s == _url.to_s && url_path.present?
236
309
  # params compare
237
310
  if url_query.present?
238
- bool &&= Rack::Utils.parse_nested_query(url_query.to_s) == Rack::Utils.parse_nested_query(URI(site_current_url).query.to_s)
311
+ menu_params = Rack::Utils.parse_nested_query(url_query.to_s)
312
+ current_params = Rack::Utils.parse_nested_query(URI(site_current_url).query.to_s)
313
+ bool &&= menu_params == current_params
239
314
  end
240
315
  if menu.key?(:items)
241
- resp = _search_in_menus(menu[:items], _url, parent_index + 1)
316
+ resp = _search_in_menus(menu[:items], _url, parent_index + 1, menu_parents)
242
317
  bool ||= resp[:bool]
243
318
  menu[:items] = resp[:menus]
244
319
  end
245
320
  if bool
246
- @_tmp_menu_parents[parent_index] = { url: menu[:url], title: menu[:title], key: menu[:key] }
321
+ menu_parents[parent_index] = { url: menu[:url], title: menu[:title], key: menu[:key] }
247
322
  break
248
323
  end
249
324
  end
250
325
  { menus: menus, bool: bool }
251
326
  end
252
327
 
253
- def _admin_menu_draw(items)
254
- res = []
255
- res << "<ul class='treeview-menu'>"
256
- items.each_with_index do |item, index|
257
- res << "<li class='#{if item.key?(:items)
258
- 'xn-openable'
259
- end} item_#{index + 1} #{if is_active_menu(item[:key])
260
- 'active'
261
- end}' #{item[:datas]}>
262
- <a href='#{item[:url]}'><i class='fa fa-#{item[:icon]}'></i> #{item[:title]} #{if item.key?(:items)
263
- '<i class="fa fa-angle-left pull-right"></i>'
264
- end}</a>
265
- #{_admin_menu_draw(item[:items]) if item.key?(:items)}
266
- </li>"
328
+ def _admin_menu_draw(items, menu_parents)
329
+ return ''.html_safe if items.blank?
330
+
331
+ content_tag(:ul, class: 'treeview-menu') do
332
+ safe_join(items.each_with_index.map do |item, index|
333
+ css_class = +"item_#{index + 1} "
334
+ css_class << 'xn-openable ' if item.key?(:items)
335
+ css_class << 'active ' if is_active_menu(item[:key], menu_parents)
336
+ css_class.strip!
337
+ data_attrs = parse_datas(item[:datas])
338
+ content_tag(
339
+ :li, ''.html_safe,
340
+ class: css_class.presence,
341
+ data: { key: item[:key] }.merge!(data_attrs.presence || {})
342
+ ) do
343
+ safe_join([
344
+ content_tag(:a, href: item[:url]) do
345
+ safe_join([
346
+ content_tag(:i, nil, class: "fa fa-#{item[:icon]}"),
347
+ ' ',
348
+ cama_admin_menu_title(item[:title]),
349
+ (content_tag(:i, nil, class: 'fa fa-angle-left pull-right') if item.key?(:items))
350
+ ].compact)
351
+ end,
352
+ (item.key?(:items) ? _admin_menu_draw(item[:items], menu_parents) : nil)
353
+ ].compact)
354
+ end
355
+ end)
356
+ end
357
+ end
358
+
359
+ # Menu titles may carry inline formatting (e.g. camaleon-ecommerce's count badge). Core builds its
360
+ # titles as SafeBuffers via safe_join/content_tag and those pass through untouched; a plain-String
361
+ # title from a plugin is sanitized to a safe inline subset — the badge renders, scripts and event
362
+ # handlers are stripped — rather than escaped whole. Regression M22.
363
+ def cama_admin_menu_title(title)
364
+ return title if title.is_a?(ActiveSupport::SafeBuffer)
365
+
366
+ sanitize(title.to_s, tags: %w[span small i b strong em], attributes: %w[class])
367
+ end
368
+
369
+ def parse_datas(datas_string)
370
+ return {} if datas_string.blank?
371
+
372
+ result = {}
373
+ # Match single- or double-quoted values so a value may contain the other quote character. The
374
+ # old /['"]([^'"]*)['"]/ stopped at the first quote of either kind, truncating data-intro
375
+ # strings that carry HTML (e.g. the Menus tooltip's <img ... style="...">). Regression M22.
376
+ datas_string.scan(/data-([\w-]+)=(?:'([^']*)'|"([^"]*)")/).each do |key, single, double|
377
+ result[key.to_sym] = single || double
267
378
  end
268
- res << '</ul>'
269
- res.join
379
+ result
270
380
  end
271
381
  end
272
382
  end