camaleon_cms 2.9.2 → 2.9.4

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 (216) 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 +14 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/config/locales/translation.yml +0 -7
  10. data/app/apps/plugins/front_cache/front_cache_helper.rb +109 -37
  11. data/app/apps/plugins/front_cache/views/admin/settings.html.erb +0 -5
  12. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  13. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  14. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  15. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  16. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  17. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  18. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  19. data/app/apps/themes/default/default_helper.rb +2 -1
  20. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  21. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  22. data/app/apps/themes/new/custom_helper.rb +5 -9
  23. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  25. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  26. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  27. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  28. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  29. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  30. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  31. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  32. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  33. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  34. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  35. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  36. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  37. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  38. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  39. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  40. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  41. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +80 -13
  42. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  43. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  44. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  45. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  46. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  47. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  48. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  49. data/app/controllers/camaleon_cms/frontend_controller.rb +146 -74
  50. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  51. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  52. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  53. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  54. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  59. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  60. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  61. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  62. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  63. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  64. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  65. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  66. data/app/decorators/camaleon_cms/post_decorator.rb +68 -50
  67. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  68. data/app/decorators/camaleon_cms/site_decorator.rb +33 -25
  69. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  70. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  71. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  72. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  73. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  74. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  75. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  76. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  77. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  78. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  79. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  80. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  81. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  82. data/app/helpers/camaleon_cms/frontend/application_helper.rb +6 -1
  83. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  84. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  85. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  86. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  87. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  88. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  89. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  90. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  91. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  92. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  93. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  94. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  95. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  96. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  97. data/app/models/camaleon_cms/ability.rb +15 -7
  98. data/app/models/camaleon_cms/category.rb +6 -6
  99. data/app/models/camaleon_cms/custom_field.rb +15 -6
  100. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  101. data/app/models/camaleon_cms/custom_fields_relationship.rb +140 -2
  102. data/app/models/camaleon_cms/media.rb +8 -2
  103. data/app/models/camaleon_cms/meta.rb +2 -2
  104. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  105. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  106. data/app/models/camaleon_cms/plugin.rb +3 -4
  107. data/app/models/camaleon_cms/post.rb +137 -27
  108. data/app/models/camaleon_cms/post_comment.rb +10 -6
  109. data/app/models/camaleon_cms/post_default.rb +42 -12
  110. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  111. data/app/models/camaleon_cms/post_tag.rb +4 -5
  112. data/app/models/camaleon_cms/post_type.rb +21 -13
  113. data/app/models/camaleon_cms/site.rb +39 -29
  114. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  115. data/app/models/camaleon_cms/term_taxonomy.rb +58 -4
  116. data/app/models/camaleon_cms/theme.rb +2 -4
  117. data/app/models/camaleon_cms/user.rb +17 -5
  118. data/app/models/camaleon_cms/user_role.rb +81 -6
  119. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  120. data/app/models/camaleon_cms/widget/main.rb +12 -8
  121. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  122. data/app/models/camaleon_record.rb +89 -5
  123. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +36 -42
  124. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  125. data/app/models/concerns/camaleon_cms/content_shortcode_gate.rb +73 -0
  126. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  127. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  128. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  129. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  130. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  131. data/app/models/current_request.rb +8 -1
  132. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  133. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  134. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  135. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  136. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  137. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  138. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  140. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  141. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  142. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  143. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  144. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  145. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  146. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  147. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  148. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  149. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  150. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  151. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  153. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  154. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  155. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  156. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  157. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  158. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  159. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  160. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  161. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  162. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  163. data/config/initializers/action_view.rb +42 -5
  164. data/config/initializers/active_record_extension.rb +13 -27
  165. data/config/initializers/assets.rb +52 -23
  166. data/config/initializers/custom_initializers.rb +1 -1
  167. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/en.yml +36 -0
  170. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  175. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  176. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  177. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  178. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  179. data/config/routes/admin.rb +33 -15
  180. data/config/routes/frontend.rb +21 -15
  181. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  182. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  183. data/lib/camaleon_cms/content_security.rb +158 -0
  184. data/lib/camaleon_cms/engine.rb +45 -21
  185. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  186. data/lib/camaleon_cms/setup_token.rb +69 -0
  187. data/lib/camaleon_cms/shortcode_registry.rb +102 -0
  188. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  189. data/lib/camaleon_cms/task_reporter.rb +14 -0
  190. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  191. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  192. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  193. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  194. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  195. data/lib/camaleon_cms/uploader_support.rb +86 -0
  196. data/lib/camaleon_cms/version.rb +1 -1
  197. data/lib/camaleon_cms.rb +23 -0
  198. data/lib/ext/hash.rb +28 -2
  199. data/lib/ext/string.rb +10 -5
  200. data/lib/ext/translator.rb +10 -2
  201. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  202. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  203. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  204. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  205. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  206. data/lib/plugin_routes.rb +412 -368
  207. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  208. data/lib/tasks/cross_site_field_groups.rake +74 -0
  209. data/lib/tasks/custom_fields_roles.rake +15 -13
  210. data/lib/tasks/orphaned_comments.rake +42 -0
  211. data/lib/tasks/private_upload_acls.rake +31 -0
  212. data/lib/tasks/site_custom_field_groups.rake +36 -0
  213. data/lib/tasks/unsafe_stored_content.rake +42 -0
  214. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  215. data/lib/tasks/user_field_groups.rake +27 -0
  216. metadata +125 -4
@@ -2,96 +2,100 @@ module CamaleonCms
2
2
  module ShortCodeHelper
3
3
  # Internal method
4
4
  def shortcodes_init
5
- @_shortcodes = []
6
- @_shortcodes_template = {}
7
- @_shortcodes_descr = {}
5
+ CurrentRequest.shortcodes = []
6
+ CurrentRequest.shortcodes_template = {}
7
+ CurrentRequest.shortcodes_descr = {}
8
8
 
9
9
  shortcode_add('widget', nil, "Renderize the widget content in this place.
10
10
  Don't forget to create and copy the shortcode of your widgets in appearance -> widgets
11
11
  Sample: [widget widget_key]")
12
12
 
13
- shortcode_add('load_libraries',
14
- lambda { |attrs, args|
15
- return args[:shortcode] unless attrs.present?
16
-
17
- cama_load_libraries(*attrs['data'].to_s.split(','))
18
- ''
19
- },
20
- "Permit to load libraries on demand, sample: [load_libraries data='datepicker,tinymce']")
21
-
22
- shortcode_add('asset',
23
- lambda { |attrs, args|
24
- return args[:shortcode] unless attrs.present?
25
-
26
- url = ActionController::Base.helpers.asset_url(attrs['file'])
27
- if attrs['image'].present?
28
- ActionController::Base.helpers.image_tag(attrs['file'], class: attrs['class'],
29
- style: attrs['style'])
30
- else
31
- url
32
- end
33
- },
34
- "Permit to generate an asset url (
35
- add file='' asset file path,
36
- add as_path='true' to generate only the path and not the full url,
37
- add class='my_class' to setup image class,
38
- add style='height: 100px; width: 200px;...' to setup image style,
39
- add image='true' to generate the image tag with this url),
40
- sample: <img src=\"[asset as_path='true' file='themes/my_theme/assets/img/signature.png']\" /> or [asset image='true' file='themes/my_theme/assets/img/signature.png' style='height: 50px;']")
41
-
42
- shortcode_add('data',
43
- lambda { |attrs, args|
44
- attrs.present? ? cama_shortcode_data(attrs, args) : args[:shortcode]
45
- },
46
- "Permit to generate specific data of a post.
47
- Attributes:
48
- object: (String, defaut post) Model name: post | posttype | category | posttag | site | user |theme | navmenu
49
- id: (Integer) Post id
50
- key: (String) Post slug
51
- field: (String) Custom field key, you can add render_field='true' to render field as html element, also you can add index=2 to indicate the value in position 2 for multitple values
52
- attrs: (String) attribute name
53
- post: title | created_at | excerpt | url | link | thumb | updated_at | author_name | author_url | content
54
- posttype: title | created_at | excerpt | url | link | thumb | updated_at
55
- category: title | created_at | excerpt | url | link | thumb | updated_at
56
- posttag: title | created_at | excerpt | url | link | thumb | updated_at
57
- site: title | created_at | excerpt | url | link | thumb | updated_at
58
- user: title | created_at | excerpt | url | link | thumb | updated_at
59
- theme: title | created_at | excerpt | url | link | thumb | updated_at
60
- navmenu: title | created_at | excerpt | url | link | thumb | updated_at
61
- Note: If id and key is not present, then will be rendered for current model
62
- Sample:
63
- [data id='122' attr='title'] ==> return the title of post with id = 122
64
- [data key='contact' attr='url'] ==> return the url of post with slug = contact
65
- [data key='contact' attr='link'] ==> return the link with title as text of the link of post with slug = contact
66
- [data object='site' attr='url'] ==> return the url of currrent site
67
- [data key='page' object='posttype' attr='url'] ==> return the url of post_type with slug = page
68
- [data field=icon index=2] ==> return the second value (multiple values) for this custom field with key=icon of current object
69
- [data key='contact' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for post.slug = contact
70
- [data object='site' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for current_site")
13
+ shortcode_add(
14
+ 'load_libraries',
15
+ lambda do |attrs, args|
16
+ return args[:shortcode] if attrs.blank?
17
+
18
+ cama_load_libraries(*attrs['data'].to_s.split(','))
19
+ ''
20
+ end,
21
+ "Permit to load libraries on demand, sample: [load_libraries data='datepicker,tinymce']"
22
+ )
23
+ # rubocop:disable Layout/LineLength
24
+ shortcode_add(
25
+ 'asset',
26
+ lambda do |attrs, args|
27
+ return args[:shortcode] if attrs.blank?
28
+
29
+ url = shortcode_asset_reference(attrs['file'], as_path: attrs['as_path'].present?)
30
+ if attrs['image'].present?
31
+ ActionController::Base.helpers.image_tag(url, class: attrs['class'], style: attrs['style'])
32
+ else
33
+ url
34
+ end
35
+ end,
36
+ "Permit to generate an asset url (
37
+ add file='' asset file path,
38
+ add as_path='true' to generate only the path and not the full url,
39
+ add class='my_class' to setup image class,
40
+ add style='height: 100px; width: 200px;...' to setup image style,
41
+ add image='true' to generate the image tag with this url),
42
+ sample: <img src=\"[asset as_path='true' file='themes/my_theme/assets/img/signature.png']\" /> or [asset image='true' file='themes/my_theme/assets/img/signature.png' style='height: 50px;']"
43
+ )
44
+
45
+ shortcode_add(
46
+ 'data',
47
+ ->(attrs, args) { attrs.present? ? cama_shortcode_data(attrs, args) : args[:shortcode] },
48
+ "Permit to generate specific data of a post.
49
+ Attributes:
50
+ object: (String, defaut post) Model name: post | posttype | category | posttag | site | user |theme | navmenu
51
+ id: (Integer) Post id
52
+ key: (String) Post slug
53
+ field: (String) Custom field key, you can add render_field='true' to render field as html element, also you can add index=2 to indicate the value in position 2 for multitple values
54
+ attrs: (String) attribute name
55
+ post: title | created_at | excerpt | url | link | thumb | updated_at | author_name | author_url | content
56
+ posttype: title | created_at | excerpt | url | link | thumb | updated_at
57
+ category: title | created_at | excerpt | url | link | thumb | updated_at
58
+ posttag: title | created_at | excerpt | url | link | thumb | updated_at
59
+ site: title | created_at | excerpt | url | link | thumb | updated_at
60
+ user: title | created_at | excerpt | url | link | thumb | updated_at
61
+ theme: title | created_at | excerpt | url | link | thumb | updated_at
62
+ navmenu: title | created_at | excerpt | url | link | thumb | updated_at
63
+ Note: If id and key is not present, then will be rendered for current model
64
+ Sample:
65
+ [data id='122' attr='title'] ==> return the title of post with id = 122
66
+ [data key='contact' attr='url'] ==> return the url of post with slug = contact
67
+ [data key='contact' attr='link'] ==> return the link with title as text of the link of post with slug = contact
68
+ [data object='site' attr='url'] ==> return the url of currrent site
69
+ [data key='page' object='posttype' attr='url'] ==> return the url of post_type with slug = page
70
+ [data field=icon index=2] ==> return the second value (multiple values) for this custom field with key=icon of current object
71
+ [data key='contact' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for post.slug = contact
72
+ [data object='site' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for current_site"
73
+ )
71
74
  end
75
+ # rubocop:enable Layout/LineLength
72
76
 
73
77
  # add shortcode
74
78
  # key: shortcode key
75
- # template: template to render, if nil will render "shortcode_templates/<key>"
76
- # Also can be a function to execute that instead a render, sample: lambda{|attrs, args| return "my custom content"; }
79
+ # template: template to render, if nil renders "shortcode_templates/<key>"
80
+ # Also can be a function to execute that instead a render, sample: lambda{|attrs, args| return "my custom content" }
77
81
  # descr: description for shortcode
78
82
  def shortcode_add(key, template = nil, descr = '')
79
- @_shortcodes << key
80
- @_shortcodes_template = @_shortcodes_template.merge({ key.to_s => template }) if template.present?
81
- @_shortcodes_descr[key] = descr if descr.present?
83
+ shortcode_keys << key
84
+ CurrentRequest.shortcodes_template = shortcode_templates.merge({ key.to_s => template }) if template.present?
85
+ shortcode_descriptions[key] = descr if descr.present?
82
86
  end
83
87
 
84
88
  # add or update shortcode template
85
89
  # key: chortcode key to add or update
86
90
  # template: template to render, if nil will render "shortcode_templates/<key>"
87
91
  def shortcode_change_template(key, template = nil)
88
- @_shortcodes_template[key] = template
92
+ shortcode_templates[key] = template
89
93
  end
90
94
 
91
- # delete shortcode
95
+ # Delete the shortcode
92
96
  # key: chortcode key to delete
93
97
  def shortcode_delete(key)
94
- @_shortcodes.delete(key)
98
+ shortcode_keys.delete(key)
95
99
  end
96
100
 
97
101
  # run all shortcodes in the content
@@ -122,8 +126,11 @@ module CamaleonCms
122
126
  # text: text that contain the shortcode
123
127
  # key: shortcode key
124
128
  # template: template to render, if nil this will render default render file
125
- # Also can be a function to execute that instead a render, sample: lambda{|attrs, args| return "my custom content"; }
126
- # render_shortcode("asda dasdasdas[owen a='1'] [bbb] sdasdas dasd as das[owen a=213]", "owen", lambda{|attrs, args| puts attrs; return "my test"; })
129
+ # Also can be a function to execute that instead a render, sample: lambda{|attrs, args| return "my custom content" }
130
+ # render_shortcode(
131
+ # "asda dasdasdas[owen a='1'] [bbb] sdasdas dasd as das[owen a=213]", "owen",
132
+ # lambda { |attrs, args| puts attrs; return "my test"; }
133
+ # )
127
134
  def render_shortcode(text, key, template = nil)
128
135
  text.scan(/#{cama_reg_shortcode(key)}/).each do |item|
129
136
  text = _cama_replace_shortcode(text, item, {}, template)
@@ -131,6 +138,14 @@ module CamaleonCms
131
138
  text
132
139
  end
133
140
 
141
+ def shortcodes_list
142
+ shortcode_keys
143
+ end
144
+
145
+ def shortcodes_descriptions
146
+ shortcode_descriptions
147
+ end
148
+
134
149
  private
135
150
 
136
151
  # helper to replace shortcodes adding support for closed shortcodes, sample: [title]my title[/title]
@@ -154,32 +169,113 @@ module CamaleonCms
154
169
  # if empty, codes will be replaced with all registered shortcodes
155
170
  # Return: (String) reg expression string
156
171
  def cama_reg_shortcode(codes = nil)
157
- # "(\\[(#{codes || (@_shortcodes || []).join("|")})(\s|\\]){0}(.*?)\\])" # doesn't support for similar names, like: [media] and [media_gallery]
158
- "(\\[(#{codes || (@_shortcodes || []).join('|')})((\s)((?!\\]).)*|)\\])"
172
+ # doesn't support for similar names, like: [media] and [media_gallery]
173
+ # "(\\[(#{codes || (@_shortcodes || []).join("|")})(\s|\\]){0}(.*?)\\])"
174
+ "(\\[(#{codes || shortcode_keys.join('|')})((\s)((?!\\]).)*|)\\])"
159
175
  end
160
176
 
161
177
  # determine the content to replace instead the shortcode
162
178
  # return string
163
179
  def _eval_shortcode(code, attrs, args = {}, template = nil)
164
- template ||= (@_shortcodes_template[code].present? ? @_shortcodes_template[code] : "camaleon_cms/shortcode_templates/#{code}")
165
- if @_shortcodes_template[code].instance_of?(::Proc)
166
- @_shortcodes_template[code].call(_shortcode_parse_attr(attrs), args)
180
+ templates = shortcode_templates
181
+ template ||= templates[code].presence || "camaleon_cms/shortcode_templates/#{code}"
182
+ if templates[code].instance_of?(::Proc)
183
+ templates[code].call(_shortcode_parse_attr(attrs), args)
167
184
  else
168
185
  render template: template, locals: { attributes: _shortcode_parse_attr(attrs), args: args }, formats: [:html]
169
186
  end
170
187
  end
171
188
 
189
+ def shortcode_keys
190
+ CurrentRequest.shortcodes ||= []
191
+ end
192
+
193
+ def shortcode_templates
194
+ CurrentRequest.shortcodes_template ||= {}
195
+ end
196
+
197
+ def shortcode_descriptions
198
+ CurrentRequest.shortcodes_descr ||= {}
199
+ end
200
+
201
+ def shortcode_asset_reference(file, as_path: false)
202
+ return if file.blank?
203
+
204
+ # Defensive ordering: assign the locals the rescue branch uses before
205
+ # everything else, so it can never see them nil. The only call that raises
206
+ # AssetNotFound is the asset helper below — resolve_shortcode_theme_asset
207
+ # never does (regex, string interpolation and File.exist? only).
208
+ helper = ActionController::Base.helpers
209
+ method_name = as_path ? :asset_path : :asset_url
210
+ file = resolve_shortcode_theme_asset(file)
211
+ helper.public_send(method_name, file)
212
+ rescue Sprockets::Rails::Helper::AssetNotFound
213
+ helper.public_send(method_name, file, skip_pipeline: true)
214
+ end
215
+
216
+ def resolve_shortcode_theme_asset(file)
217
+ # Tolerate both `themes/<slug>/assets/...` and `/themes/<slug>/assets/...`
218
+ # — the latter is what Rails' asset helpers typically emit.
219
+ match = file.to_s.match(%r{\A/?themes/[^/]+/assets/(?<asset>.+)\z})
220
+ return file unless match
221
+
222
+ can_resolve_theme_assets = respond_to?(:current_theme) &&
223
+ respond_to?(:theme_asset_path) &&
224
+ respond_to?(:theme_asset_file_path)
225
+ return file unless can_resolve_theme_assets
226
+
227
+ return file if current_theme.blank?
228
+
229
+ asset = match[:asset]
230
+ remapped_file = theme_asset_path(asset)
231
+ return file unless remapped_file.present? && remapped_file != file
232
+ return file unless File.exist?(theme_asset_file_path(asset))
233
+
234
+ remapped_file
235
+ end
236
+
172
237
  # parse the attributes of a shortcode
173
238
  def _shortcode_parse_attr(text)
174
239
  res = {}
175
- return res unless text.present?
240
+ return res if text.blank?
176
241
 
177
- text.scan(/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*'([^']*)'(?:\s|$)|(\w+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/).each do |item|
178
- item.each_with_index do |c, index|
179
- if c.present?
180
- res[c] = item[index + 1]
181
- break
182
- end
242
+ # StringScanner tokenizes the text by moving through it sequentially,
243
+ # matching patterns and advancing the scan pointer with each successful scan.
244
+ scanner = StringScanner.new(text)
245
+
246
+ # Continue parsing until we reach the end of the string.
247
+ until scanner.eos?
248
+ # Skip any leading whitespace before attempting to match a token.
249
+ scanner.skip(/\s+/)
250
+ break if scanner.eos?
251
+
252
+ # Attempt to match a key=value attribute (e.g., width="100", class='foo', height=200).
253
+ # Capture group 1 extracts the attribute name (word characters before =).
254
+ if scanner.scan(/(\w+)\s*=\s*/)
255
+ key = scanner[1]
256
+
257
+ # Determine the attribute value based on its quoting style:
258
+ # 1. Double-quoted string: "value" → extract content between quotes
259
+ # 2. Single-quoted string: 'value' → extract content between quotes
260
+ # 3. Unquoted value: any characters except whitespace or quotes
261
+ val = if scanner.scan(/"([^"]*)"/)
262
+ scanner[1]
263
+ elsif scanner.scan(/'([^']*)'/)
264
+ scanner[1]
265
+ elsif scanner.scan(/([^\s'"]+)/)
266
+ scanner[1]
267
+ end
268
+
269
+ # Store the parsed key-value pair in the result hash.
270
+ res[key] = val
271
+
272
+ # If no key=value pattern matched, this is a standalone value (no assignment).
273
+ # Match either a double-quoted string or a bare unquoted token.
274
+ elsif scanner.scan(/"([^"]*)"|(\S+)/)
275
+ # Use the appropriate capture group: group 1 for quoted, group 2 for unquoted.
276
+ val = scanner[1] || scanner[2]
277
+ # Standalone values are stored with a nil key reference.
278
+ res[val] = nil
183
279
  end
184
280
  end
185
281
  res
@@ -195,7 +291,7 @@ module CamaleonCms
195
291
  else
196
292
  args[:owner]
197
293
  end
198
- return res unless model.present?
294
+ return res if model.blank?
199
295
 
200
296
  if attrs['field'].present? # model custom fields
201
297
  field = model.get_field_object(attrs['field'])
@@ -295,7 +391,7 @@ module CamaleonCms
295
391
 
296
392
  when 'navmenu'
297
393
  model = current_site.nav_menu_items.find(attrs['id']) if attrs['id'].present?
298
- model = current_site.nav_menu_items.find_by_slug(attrs['key']) if attrs['key'].present?
394
+ model = current_site.nav_menu_items.find_by_slug(attrs['key']) if attrs['key'].present? # rubocop:disable Rails/DynamicFindBy
299
395
 
300
396
  when 'user'
301
397
  model = current_site.the_user(attrs['id'].to_i) if attrs['id'].present?
@@ -3,9 +3,9 @@ module CamaleonCms
3
3
  # return current site or assign a site as a current site
4
4
  def current_site(site = nil)
5
5
  if site.present?
6
- @current_site = site.decorate
7
- CurrentRequest.site = @current_site
8
- return @current_site
6
+ CurrentRequest.site = site.decorate
7
+ CurrentRequest.frontend_current_theme = nil
8
+ return CurrentRequest.site
9
9
  end
10
10
 
11
11
  if defined?($current_site)
@@ -13,11 +13,19 @@ module CamaleonCms
13
13
  return $current_site
14
14
  end
15
15
 
16
- if defined?(@current_site) && @current_site.present?
17
- CurrentRequest.site = @current_site
18
- return @current_site
16
+ # Honor a caller-set @current_site (HtmlMailer and other background senders assign it) before the
17
+ # memoized CurrentRequest.site, so a delivery for another site resolves against that site rather
18
+ # than the request's. Restores 2.9.2; without it, multisite/background mail resolves the wrong site
19
+ # or raises NameError on `request` in a job. See regression audit M21. Read via
20
+ # instance_variable_get to match #current_theme and avoid Rails/HelperInstanceVariable.
21
+ caller_site = (instance_variable_get(:@current_site) if instance_variable_defined?(:@current_site))
22
+ if caller_site.present?
23
+ CurrentRequest.site = caller_site
24
+ return caller_site
19
25
  end
20
26
 
27
+ return CurrentRequest.site if CurrentRequest.site.present?
28
+
21
29
  if PluginRoutes.get_sites.size == 1
22
30
  site = begin
23
31
  CamaleonCms::Site.first.decorate
@@ -43,16 +51,28 @@ module CamaleonCms
43
51
  rescue StandardError
44
52
  nil
45
53
  end
46
- unless r[:site].present?
47
- Rails.logger.error 'Camaleon CMS - Please define your current site: $current_site = CamaleonCms::Site.first.decorate or map your domains: https://camaleon.website/documentation/category/139779-examples/how.html'.cama_log_style(:red)
54
+ if r[:site].blank?
55
+ Rails.logger.error(
56
+ 'Camaleon CMS - Please define your current site: $current_site = CamaleonCms::Site.first.decorate or ' \
57
+ 'map your domains: https://camaleon.website/documentation/category/139779-examples/how.html'
58
+ .cama_log_style(:red)
59
+ )
48
60
  end
49
- @current_site = r[:site]
50
- CurrentRequest.site = @current_site
61
+ CurrentRequest.site = r[:site]
62
+ CurrentRequest.frontend_current_theme = nil
63
+ CurrentRequest.site
51
64
  end
52
65
 
53
66
  # return current theme model for current site
54
67
  def current_theme
55
- @_current_theme ||= current_site.get_theme.decorate
68
+ preview_theme = (instance_variable_get(:@_current_theme) if instance_variable_defined?(:@_current_theme))
69
+ return CurrentRequest.frontend_current_theme = preview_theme if preview_theme.present?
70
+
71
+ theme = CurrentRequest.frontend_current_theme
72
+ return theme if theme.present?
73
+
74
+ theme = current_site.get_theme.decorate
75
+ CurrentRequest.frontend_current_theme = theme
56
76
  end
57
77
 
58
78
  # get list templates files of current theme
@@ -105,7 +125,7 @@ module CamaleonCms
105
125
  # new theme
106
126
  current_site.set_option('_theme', key)
107
127
  theme = PluginRoutes.theme_info(key)
108
- current_site.themes.update_all(status: 'inactive')
128
+ current_site.themes.update_all(status: 'inactive') # rubocop:disable Rails/SkipsModelValidations
109
129
  theme_model = current_site.themes.where(slug: key).first_or_create! { |t| t.name = theme[:name] }
110
130
  theme_model.update(status: nil)
111
131
  hook_run(theme, 'on_active', theme_model)
@@ -121,7 +141,8 @@ module CamaleonCms
121
141
  # theme_model.destroy
122
142
  end
123
143
 
124
- # add host + port to args of the current site visited (only if the request is coming from console or tasks i.e. not web browser)
144
+ # add host and port to args of the current site visited
145
+ # (only if the request is coming from console or tasks i.e. not web browser)
125
146
  # args: Hash
126
147
  # sample: {} will return {host: 'localhost', port: 3000}
127
148
  def cama_current_site_host_port(args)
@@ -1,13 +1,14 @@
1
1
  module CamaleonCms
2
2
  module ThemeHelper
3
3
  def theme_init
4
- @_front_breadcrumb = []
4
+ camaleon_theme_state[:front_breadcrumb] = []
5
5
  end
6
6
 
7
7
  # return theme full asset path
8
8
  # theme_name: theme name, if nil, then will use current theme
9
- # asset: asset file name, if asset is present return full path to this asset
10
- # sample: <script src="<%= theme_asset_path("js/admin.js") %>"></script> => return: /assets/themes/my_theme/assets/js/admin-54505620f.js
9
+ # asset: asset file name, if asset is present return the full path to this asset
10
+ # sample: <script src="<%= theme_asset_path("js/admin.js") %>"></script> =>
11
+ # return: /assets/themes/my_theme/assets/js/admin-54505620f.js
11
12
  def theme_asset_path(asset = nil, theme_name = nil)
12
13
  return theme_asset_url(theme_name, current_theme.slug) if theme_name.present? && theme_name.include?('/')
13
14
 
@@ -22,11 +23,12 @@ module CamaleonCms
22
23
  alias theme_asset theme_asset_path
23
24
  alias theme_gem_asset theme_asset_path
24
25
 
25
- # return the full url for asset of current theme:
26
+ # return the full url for asset of the current theme:
26
27
  # asset: (String) asset name
27
28
  # theme_name: (optional) theme name, default (current theme caller to this function)
28
29
  # sample:
29
- # theme_asset_url("css/main.css") => return: https://myhost.com/assets/themes/my_theme/assets/css/main-54505620f.css
30
+ # theme_asset_url("css/main.css") =>
31
+ # return: https://myhost.com/assets/themes/my_theme/assets/css/main-54505620f.css
30
32
  def theme_asset_url(asset, theme_name = nil)
31
33
  p = theme_asset_path(asset, theme_name)
32
34
  begin
@@ -36,11 +38,17 @@ module CamaleonCms
36
38
  end
37
39
  end
38
40
 
39
- # return theme view path including the path of current theme
41
+ # return the theme view path including the path of current theme
40
42
  # view_name: name of the view or template
41
43
  # sample: theme_view("index") => "themes/my_theme/index"
42
44
  def theme_view(view_name, deprecated_attr = '')
43
- view_name = deprecated_attr if deprecated_attr.present?
45
+ if deprecated_attr.present?
46
+ ActiveSupport::Deprecation._instance.warn(
47
+ 'Passing theme view name as the second argument to #theme_view is deprecated. ' \
48
+ 'Pass the view name as the first argument instead.'
49
+ )
50
+ view_name = deprecated_attr
51
+ end
44
52
  if current_theme.settings['gem_mode']
45
53
  "themes/#{current_theme.slug}/#{view_name}"
46
54
  else
@@ -49,7 +57,7 @@ module CamaleonCms
49
57
  end
50
58
 
51
59
  # assign the layout for this request
52
- # asset: asset file name, if asset is present return full path to this asset
60
+ # asset: asset file name, if asset is present return the full path to this asset
53
61
  # layout_name: layout name
54
62
  def theme_layout(layout_name, _theme_name = nil)
55
63
  if current_theme.settings['gem_mode']
@@ -70,15 +78,19 @@ module CamaleonCms
70
78
  f.split(k).last.split('/').first
71
79
  end
72
80
 
73
- # returns file system path to theme asset
74
- # theme_name: theme name, if nil, then will use current theme
75
- # asset: asset file name, if asset is present return full path to this asset
76
- # sample: theme_asset_file_path('images/foo.jpg') => return: /home/camaleon/my-site/app/apps/themes/default/assets/images/foo.jpg
81
+ # returns the filesystem path to theme asset
82
+ # theme_name: theme name, if nil, then will use the current theme
83
+ # asset: asset file name, if asset is present, return the full path to this asset
84
+ # sample:
85
+ # theme_asset_file_path('images/foo.jpg') =>
86
+ # return: /home/camaleon/my-site/app/apps/themes/default/assets/images/foo.jpg
77
87
  def theme_asset_file_path(asset = nil, theme_name = nil)
78
- theme_path = current_theme.settings['path']
79
- if theme_name && (theme = Theme.where(name: theme_name).first)
80
- theme_path = theme.settings['path']
81
- end
88
+ theme_path = if theme_name && (theme = Theme.where(name: theme_name).first)
89
+ theme.settings['path']
90
+ else
91
+ current_theme.settings['path']
92
+ end
93
+
82
94
  "#{theme_path}/assets/#{asset}"
83
95
  end
84
96
 
@@ -86,9 +98,13 @@ module CamaleonCms
86
98
  def theme_home_page
87
99
  current_site.the_post(current_theme.get_field('home_page')) ||
88
100
  current_site.the_posts('page').first ||
89
- CamaleonCms::Post.new(title: 'Hello World!',
90
- content: 'Please add a page.',
91
- status: 'published')
101
+ CamaleonCms::Post.new(title: 'Hello World!', content: 'Please add a page.', status: 'published')
102
+ end
103
+
104
+ private
105
+
106
+ def camaleon_theme_state
107
+ CurrentRequest.theme_helper_state ||= {}
92
108
  end
93
109
  end
94
110
  end