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
@@ -1,436 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CamaleonCms
4
+ # Uploader entry point for views, ActiveJobs, and any standalone object that includes
5
+ # it (see config/initializers/custom_initializers.rb). The implementation lives in the
6
+ # shared modules below, which CamaleonCms::RuntimeUploaderConcern includes as well, so
7
+ # the two entry points cannot drift.
4
8
  module UploaderHelper
5
- UNSAFE_EVENT_PATTERNS = %w[
6
- onabort onafter onbefore onblur oncanplay onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick
7
- ondrag ondrop ondurationchange onended onerror onfocus onhashchange oninvalid oninput onkey onload onmessage
8
- onmouse ononline onoffline onpagehide onpageshow onpage onpaste onpause onplay onpopstate onprogress
9
- onpropertychange onratechange onreadystatechange onreset onresize onscroll onsearch onseek onselect onshow
10
- onstalled onstorage onsuspend ontimeupdate ontoggle onunloadonsubmit onvolumechange onwaiting onwheel
11
- ].map { |pattern| /#{pattern}\w*\s*=/i }.freeze
12
-
13
- SUSPICIOUS_PATTERNS = (UNSAFE_EVENT_PATTERNS + [
14
- /<script[\s>]/i, # Script tags
15
- /javascript:/i, # JavaScript in href/src attributes
16
- /<iframe[\s>]/i, # Iframes
17
- /<object[\s>]/i, # Object tags
18
- /<embed[\s>]/i, # Embed tags
19
- /<base[\s>]/i, # Base tags (can be used to manipulate URLs)
20
- /data:/i # data: URLs (which can include scripts)
21
- ]).freeze
22
-
9
+ include UploaderContentSecurity
10
+ include UploaderPathSecurity
11
+ include UploaderPipeline
12
+ include UploaderImageProcessing
13
+ include UploaderSupport
23
14
  include ActionView::Helpers::NumberHelper
24
15
  include CamaleonCms::CamaleonHelper
25
16
 
26
- # upload a file into server
27
- # settings:
28
- # folder: Directory where the file will be saved (default: "")
29
- # sample: temporal => will save in /rails_path/public/temporal
30
- # generate_thumb: true, # generate thumb image if this is image format (default true)
31
- # maximum: maximum bytes permitted to upload (default: 1000MG)
32
- # dimension: dimension for the image (sample: 30x30 | x30 | 30x | 300x300?)
33
- # formats: extensions permitted, sample: jpg,png,... or generic: images | videos | audios | documents (default *)
34
- # remove_source: Boolean (delete source file after saved if this is true, default false)
35
- # same_name: Boolean (save the file with the same name if defined true, else search for a non used name)
36
- # versions: (String) Create additional multiple versions of the image uploaded,
37
- # sample: '300x300,505x350' ==> Will create two extra images with these dimensions
38
- # sample "test.png", versions: '200x200,450x450' will generate: thumb/test-png_200x200.png, test-png_450x450.png
39
- # thumb_size: String (redefine the dimensions of the thumbnail, sample: '100x100' ==> only for images)
40
- # temporal_time: if great than 0 seconds, then this file will expire (removed) in that time (default: 0)
41
- # To manage jobs, please check https://edgeguides.rubyonrails.org/active_job_basics.html
42
- # Note: if you are using temporal_time, you will need to copy the file to another directory later
43
- # sample: upload_file(params[:my_file], {formats: "images", folder: "temporal"})
44
- # sample: upload_file(params[:my_file], {formats: "jpg,png,gif,mp3,mp4", temporal_time: 10.minutes, maximum: 10.megabytes})
45
- def upload_file(uploaded_io, settings = {})
46
- cached_name = uploaded_io.is_a?(ActionDispatch::Http::UploadedFile) ? uploaded_io.original_filename : nil
47
- return { error: 'File is empty', file: nil, size: nil } unless uploaded_io.present?
48
-
49
- if uploaded_io.is_a?(String) && uploaded_io.match(%r{^https?://}).present? # download url file
50
- tmp = cama_tmp_upload(uploaded_io)
51
- return tmp if tmp[:error].present?
52
-
53
- settings[:remove_source] = true
54
- uploaded_io = tmp[:file_path]
55
- end
56
- uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
57
- if settings[:dimension].present?
58
- uploaded_io = File.open(cama_resize_upload(uploaded_io.path, settings[:dimension]))
59
- end
60
-
61
- return { error: 'Potentially malicious content found!' } if file_content_unsafe?(uploaded_io)
62
-
63
- settings = settings.to_sym
64
- settings[:uploaded_io] = uploaded_io
65
- settings = {
66
- folder: '',
67
- maximum: current_site.get_option('filesystem_max_size', 100).to_f.megabytes,
68
- formats: '*',
69
- generate_thumb: true,
70
- temporal_time: 0,
71
- filename: begin
72
- cached_name || uploaded_io.original_filename
73
- rescue StandardError
74
- uploaded_io.path.split('/').last
75
- end.cama_fix_filename,
76
- file_size: File.size(uploaded_io.to_io),
77
- remove_source: false,
78
- same_name: false,
79
- versions: '',
80
- thumb_size: nil
81
- }.merge!(settings)
82
- hooks_run('before_upload', settings)
83
- res = { error: nil }
84
-
85
- # guard against path traversal
86
- return { error: 'Invalid file path' } unless cama_uploader.valid_folder_path?(settings[:folder])
87
-
88
- # formats validations
89
- return { error: "#{ct('file_format_error')} (#{settings[:formats]})" } unless cama_uploader.class.validate_file_format(
90
- uploaded_io.path, settings[:formats]
91
- )
92
-
93
- # file size validations
94
- if settings[:maximum] < settings[:file_size]
95
- res[:error] =
96
- "#{ct('file_size_exceeded', default: 'File size exceeded')} (#{number_to_human_size(settings[:maximum])})"
97
- return res
98
- end
99
- # save file
100
- key = File.join(settings[:folder], settings[:filename]).to_s.cama_fix_slash
101
- res = cama_uploader.add_file(settings[:uploaded_io], key, { same_name: settings[:same_name] })
102
-
103
- # generate image versions
104
- if res['file_type'] == 'image'
105
- settings[:versions].to_s.gsub(' ', '').split(',').each do |v|
106
- version_path = cama_resize_upload(settings[:uploaded_io].path, v, { replace: false })
107
- cama_uploader.add_file(version_path, cama_uploader.version_path(res['key'], v), is_thumb: true,
108
- same_name: true)
109
- FileUtils.rm_f(version_path)
110
- end
111
- end
112
-
113
- # generate thumb
114
- if settings[:generate_thumb] && res['thumb'].present?
115
- cama_uploader_generate_thumbnail(uploaded_io.path, res['key'], settings[:thumb_size],
116
- settings[:remove_source])
117
- end
118
- FileUtils.rm_f(uploaded_io.path) if settings[:remove_source] && File.exist?(uploaded_io.path)
119
-
120
- hooks_run('after_upload', settings)
121
-
122
- # temporal file upload (always put as local for temporal files)
123
- if settings[:temporal_time] > 0
124
- CamaleonCmsUploader.delete_block.call(settings, cama_uploader, key)
125
- end
126
-
127
- res
128
- end
129
-
130
- # generate thumbnail of a existent image
131
- # key: key of the current file
132
- # the thumbnail will be saved in my_images/my_img.png => my_images/thumb/my_img.png
133
- def cama_uploader_generate_thumbnail(uploaded_io, key, thumb_size = nil, remove_source = false)
134
- w = cama_uploader.thumb[:w]
135
- h = cama_uploader.thumb[:h]
136
- w, h = thumb_size.split('x') if thumb_size.present?
137
- uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
138
- path_thumb = cama_resize_and_crop(uploaded_io.path, w, h)
139
- thumb = cama_uploader.add_file(path_thumb, cama_uploader.version_path(key).sub('.svg', '.jpg'), is_thumb: true,
140
- same_name: true)
141
- FileUtils.rm_f(path_thumb) if remove_source
142
- thumb
143
- end
144
-
145
- # helper to find an available filename for file_path in that directory
146
- # sample: uploader_verify_name("/var/www/my_image.jpg")
147
- # return "/var/www/my_image_1.jpg" => if "/var/www/my_image.jpg" exist
148
- # return "/var/www/my_image.jpg" => if "/var/www/my_image.jpg" doesn't exist
149
- def uploader_verify_name(file_path)
150
- dir = File.dirname(file_path)
151
- filename = File.basename(file_path).to_s.cama_fix_filename
152
- files = Dir.entries(dir)
153
- if files.include?(filename)
154
- i = 1
155
- _filename = filename
156
- while files.include?(_filename)
157
- _filename = "#{File.basename(filename, File.extname(filename))}_#{i}#{File.extname(filename)}"
158
- i += 1
159
- end
160
- filename = _filename
161
- end
162
- "#{File.dirname(file_path)}/#{filename}"
163
- end
164
-
165
- # convert downloaded file path into public url
166
- def cama_file_path_to_url(file_path)
167
- file_path.sub(Rails.public_path.to_s, begin
168
- root_url
169
- rescue StandardError
170
- cama_root_url
171
- end)
172
- end
173
-
174
- # convert public url to file path
175
- def cama_url_to_file_path(url)
176
- File.join(Rails.public_path, URI(url.to_s).path)
177
- end
178
-
179
- # crop and image and saved as imagename_crop.ext
180
- # file: file path
181
- # w: new width
182
- # h: new height
183
- # w_offset: left offset
184
- # w_offset: top offset
185
- # resize: true/false
186
- # (true => resize the image to this dimension)
187
- # (false => crop the image with this dimension)
188
- # replace: Boolean (replace current image or create another file)
189
- def cama_crop_image(file_path, w = nil, h = nil, w_offset = 0, h_offset = 0, resize = false, replace = true)
190
- force = ''
191
- force = '!' if w.present? && h.present? && !w.include?('?') && !h.include?('?')
192
- img = MiniMagick::Image.open(file_path)
193
- w = img[:width].to_f > w.sub('?', '').to_i ? w.sub('?', '') : img[:width] if w.present? && w.to_s.include?('?')
194
- h = img[:height].to_f > h.sub('?', '').to_i ? h.sub('?', '') : img[:height] if h.present? && h.to_s.include?('?')
195
- data = { img: img, w: w, h: h, w_offset: w_offset, h_offset: h_offset, resize: resize, replace: replace }
196
- hooks_run('before_crop_image', data)
197
- data[:img].combine_options do |i|
198
- i.resize("#{w if w.present?}x#{h if h.present?}#{force}") if data[:resize]
199
- i.crop "#{w if w.present?}x#{h if h.present?}+#{w_offset}+#{h_offset}#{force}" unless data[:resize]
200
- end
201
-
202
- res = file_path
203
- unless data[:replace]
204
- ext = File.extname(file_path)
205
- res = file_path.gsub(ext, "_crop#{ext}")
206
- end
207
- data[:img].write res
208
- res
209
- end
210
-
211
- # resize and crop a file
212
- # SVGs are converted to JPEGs for editing
213
- # Params:
214
- # file: (String) File path
215
- # w: (Integer) width
216
- # h: (Integer) height
217
- # settings:
218
- # gravity: (Sym, default :north_east) Crop position: :north_west, :north, :north_east, :east, :south_east, :south, :south_west, :west, :center
219
- # overwrite: (Boolean, default true) true for overwrite current image with resized resolutions, false: create other file called with prefix "crop_"
220
- # output_name: (String, default prefixd name with crop_), permit to define the output name of the thumbnail if overwrite = true
221
- # Return: (String) file path where saved this cropped
222
- # sample: cama_resize_and_crop(my_file, 200, 200, {gravity: :north_east, overwrite: false})
223
- def cama_resize_and_crop(file, w, h, settings = {})
224
- settings = { gravity: :north_east, overwrite: true, output_name: +'' }.merge!(settings)
225
- img = MiniMagick::Image.open(file)
226
- if file.end_with? '.svg'
227
- img.format 'jpg'
228
- file.sub! '.svg', '.jpg'
229
- settings[:output_name]&.sub!('.svg', '.jpg')
230
- end
231
- w = img[:width].to_f > w.sub('?', '').to_i ? w.sub('?', '') : img[:width] if w.present? && w.to_s.include?('?')
232
- h = img[:height].to_f > h.sub('?', '').to_i ? h.sub('?', '') : img[:height] if h.present? && h.to_s.include?('?')
233
- w_original = img[:width].to_f
234
- h_original = img[:height].to_f
235
- w = w.to_i if w.present?
236
- h = h.to_i if h.present?
237
-
238
- # check proportions
239
- if w_original * h < h_original * w
240
- op_resize = "#{w.to_i}x"
241
- w_result = w
242
- h_result = (h_original * w / w_original)
243
- else
244
- op_resize = "x#{h.to_i}"
245
- w_result = (w_original * h / h_original)
246
- h_result = h
247
- end
248
-
249
- w_offset, h_offset = cama_crop_offsets_by_gravity(settings[:gravity], [w_result, h_result], [w, h])
250
- data = { img: img, w: w, h: h, w_offset: w_offset, h_offset: h_offset, op_resize: op_resize, settings: settings }
251
- hooks_run('before_resize_crop', data)
252
- data[:img].combine_options do |i|
253
- i.resize(data[:op_resize])
254
- i.gravity(settings[:gravity])
255
- i.crop "#{data[:w].to_i}x#{data[:h].to_i}+#{data[:w_offset]}+#{data[:h_offset]}!"
256
- end
257
-
258
- if settings[:overwrite]
259
- data[:img].write(file.sub('.svg', '.jpg'))
260
- elsif settings[:output_name].present?
261
- data[:img].write(file = File.join(File.dirname(file), settings[:output_name]).to_s)
262
- else
263
- data[:img].write(file = uploader_verify_name(File.join(File.dirname(file),
264
- "crop_#{File.basename(file.sub('.svg', '.jpg'))}")))
265
- end
266
- file
267
- end
268
-
269
- # upload tmp file
270
- # support for url and local path
271
- # sample:
272
- # cama_tmp_upload('https://camaleon.website/media/132/logo2.png') ==> /var/rails/my_project/public/tmp/1/logo2.png
273
- # cama_tmp_upload('/var/www/media/132/logo 2.png') ==> /var/rails/my_project/public/tmp/1/logo-2.png
274
- # accept args:
275
- # name: to indicate the name to use, sample: cama_tmp_upload('/var/www/media/132/logo 2.png', {name: 'owen.png', formats: 'images'})
276
- # formats: extensions permitted, sample: jpg,png,... or generic: images | videos | audios | documents (default *)
277
- # dimension: 20x30
278
- # return: {file_path, error}
279
- def cama_tmp_upload(uploaded_io, args = {})
280
- tmp_path = args[:path] || File.join(Rails.public_path, 'tmp', current_site.id.to_s).to_s
281
- FileUtils.mkdir_p(tmp_path) unless Dir.exist?(tmp_path)
282
- saved = false
283
- if uploaded_io.is_a?(String) && uploaded_io.start_with?('data:') # create tmp file using base64 format
284
- _tmp_name = args[:name]
285
- return { error: cama_t('camaleon_cms.admin.media.name_required').to_s } unless params[:name].present?
286
- return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
287
- _tmp_name, args[:formats]
288
- )
289
-
290
- path = uploader_verify_name(File.join(tmp_path, _tmp_name))
291
- File.open(path, 'wb') { |f| f.write(Base64.decode64(uploaded_io.split(';base64,').last)) }
292
- uploaded_io = File.open(path)
293
- saved = true
294
- elsif uploaded_io.is_a?(String) && (uploaded_io.start_with?('http://') || uploaded_io.start_with?('https://'))
295
- return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
296
- uploaded_io, args[:formats]
297
- )
298
-
299
- if uploaded_io.include?(current_site.the_url(locale: nil))
300
- uploaded_io = File.join(Rails.public_path, uploaded_io.sub(current_site.the_url(locale: nil), '')).to_s
301
- end
302
- _tmp_name = uploaded_io.split('/').last.split('?').first
303
- args[:name] = args[:name] || _tmp_name
304
- uploaded_io = URI(uploaded_io).open
305
- end
306
- uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
307
- return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
308
- _tmp_name || uploaded_io.path, args[:formats]
309
- )
310
-
311
- if args[:maximum].present? && args[:maximum] < begin
312
- uploaded_io.size
313
- rescue StandardError
314
- File.size(uploaded_io)
315
- end
316
- return { error: "#{ct('file_size_exceeded',
317
- default: 'File size exceeded')} (#{number_to_human_size(args[:maximum])})" }
318
- end
319
-
320
- name = args[:name] || uploaded_io&.original_filename || uploaded_io.path.split('/').last
321
- name = "#{File.basename(name, File.extname(name)).parameterize}#{File.extname(name)}"
322
- path ||= uploader_verify_name(File.join(tmp_path, name))
323
- File.open(path, 'wb') { |f| f.write(uploaded_io.read) } unless saved
324
- path = cama_resize_upload(path, args[:dimension]) if args[:dimension].present?
325
- { file_path: path, error: nil }
326
- end
327
-
328
- # resize image if the format is correct
329
- # return resized file path
330
- def cama_resize_upload(image_path, dimension, args = {})
331
- if cama_uploader.class.validate_file_format(image_path, 'image') && dimension.present?
332
- r = { file: image_path, w: dimension.split('x')[0], h: dimension.split('x')[1], w_offset: 0, h_offset: 0,
333
- resize: !dimension.split('x')[2] || dimension.split('x')[2] == 'resize',
334
- replace: true, gravity: :north_east }.merge!(args)
335
- hooks_run('on_uploader_resize', r)
336
- image_path = if r[:w].present? && r[:h].present?
337
- cama_resize_and_crop(r[:file], r[:w], r[:h], { overwrite: r[:replace], gravity: r[:gravity] })
338
- else
339
- cama_crop_image(r[:file], r[:w], r[:h], r[:w_offset], r[:h_offset], r[:resize], r[:replace])
340
- end
341
- end
342
- image_path
343
- end
344
-
345
- # return the current uploader
346
- def cama_uploader
347
- @cama_uploader ||= lambda {
348
- thumb = current_site.get_option('filesystem_thumb_size', '100x100').split('x')
349
- args = {
350
- server: current_site.get_option('filesystem_type', 'local').downcase,
351
- thumb: { w: thumb[0], h: thumb[1] },
352
- aws_settings: {
353
- region: current_site.get_option('filesystem_region', 'us-west-2'),
354
- access_key: current_site.get_option('filesystem_s3_access_key'),
355
- secret_key: current_site.get_option('filesystem_s3_secret_key'),
356
- bucket: current_site.get_option('filesystem_s3_bucket_name'),
357
- cloud_front: current_site.get_option('filesystem_s3_cloudfront'),
358
- aws_file_upload_settings: lambda { |settings|
359
- settings
360
- }, # permit to add your custom attributes for file_upload https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#upload_file-instance_method
361
- aws_file_read_settings: lambda { |data, _s3_file|
362
- data
363
- } # permit to read custom attributes from aws file and add to file parsed object
364
- },
365
- custom_uploader: nil # possibility to use custom file uploader
366
- }
367
- hooks_run('on_uploader', args)
368
- return args[:custom_uploader] if args[:custom_uploader].present?
369
-
370
- case args[:server]
371
- when 's3', 'aws'
372
- CamaleonCmsAwsUploader.new(
373
- { current_site: current_site, thumb: args[:thumb], aws_settings: args[:aws_settings] }, self
374
- )
375
- else
376
- CamaleonCmsLocalUploader.new({ current_site: current_site, thumb: args[:thumb] }, self)
377
- end
378
- }.call
17
+ # Message seam overrides (see CamaleonCms::UploaderPipeline). This entry point runs
18
+ # where `ct` and `cama_t` exist, so upload errors keep going through them: `ct` fires
19
+ # the `on_translation` hook, letting plugins override the text. The pipeline defaults
20
+ # go straight to I18n, which is all a controller can do.
21
+ def cama_uploader_ct(key, args = {})
22
+ ct(key, args)
379
23
  end
380
24
 
381
- def slugify(val)
382
- val.to_s.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
383
- end
384
-
385
- def slugify_folder(val)
386
- split_folder = val.split('/')
387
- split_folder[-1] = slugify(split_folder.last)
388
- split_folder.join('/')
389
- end
390
-
391
- private
392
-
393
- def file_content_unsafe?(uploaded_io)
394
- file = uploaded_io.is_a?(ActionDispatch::Http::UploadedFile) ? uploaded_io.tempfile : uploaded_io
395
- file_content_unsafe = nil
396
-
397
- file.set_encoding(Encoding::BINARY) if file.respond_to?(:binmode) && file.respond_to?(:set_encoding)
398
-
399
- file_content = file.read
400
- SUSPICIOUS_PATTERNS.each do |pattern|
401
- if file_content =~ pattern
402
- Rails.logger.info { "Potentially malicious content found: #{pattern.inspect}" }
403
- break file_content_unsafe = pattern.inspect
404
- end
405
- end
406
-
407
- file_content_unsafe
408
- end
409
-
410
- # helper for resize and crop method
411
- def cama_crop_offsets_by_gravity(gravity, original_dimensions, cropped_dimensions)
412
- original_width, original_height = original_dimensions
413
- cropped_width, cropped_height = cropped_dimensions
414
-
415
- vertical_offset = case gravity
416
- when :north_west, :north, :north_east then 0
417
- when :center, :east, :west then [((original_height - cropped_height) / 2.0).to_i, 0].max
418
- when :south_west, :south, :south_east then (original_height - cropped_height).to_i
419
- end
420
-
421
- horizontal_offset = case gravity
422
- when :north_west, :west, :south_west then 0
423
- when :center, :north, :south then [((original_width - cropped_width) / 2.0).to_i, 0].max
424
- when :north_east, :east, :south_east then (original_width - cropped_width).to_i
425
- end
426
-
427
- [horizontal_offset, vertical_offset]
25
+ def cama_uploader_t(key, args = {})
26
+ cama_t(key, args)
428
27
  end
429
28
 
430
- # convert file path into thumb path format
431
- # return the image name into thumb format: owewen.png into thumb/owen-png.png
432
- def cama_parse_for_thumb_name(file_path)
433
- "#{@fog_connection_hook_res[:thumb_folder_name]}/#{File.basename(file_path).parameterize}#{File.extname(file_path)}"
29
+ def cama_uploader_human_size(bytes)
30
+ number_to_human_size(bytes)
434
31
  end
435
32
  end
436
33
  end
@@ -1,15 +1,16 @@
1
1
  module CamaleonCms
2
2
  module UserRolesHelper
3
3
  def cama_get_roles_values
4
- @_cache_cama_get_roles_values ||= lambda {
4
+ @_cache_cama_get_roles_values ||= lambda do
5
5
  roles_list = CamaleonCms::UserRole::ROLES
6
- # permit to add custom roles to be listed in editing roles form
7
- # sample: args[:roles_list][:manager] << { key: 'my_role_key', label: "my_custom_permission", description: "lorem ipsum"}
6
+ # permit adding custom roles to be listed in editing roles form
7
+ # sample:
8
+ # args[:roles_list][:manager] << {key: 'my_role_key', label: "my_custom_permission", description: "lorem ipsum"}
8
9
  # authorize! :manage, :my_role_key
9
10
  args = { roles_list: roles_list }
10
11
  hooks_run('available_user_roles_list', args)
11
12
  args[:roles_list]
12
- }.call
13
+ end.call
13
14
  end
14
15
  end
15
16
  end
@@ -1,5 +1,5 @@
1
1
  module CamaleonCms
2
- class HtmlMailer < ActionMailer::Base
2
+ class HtmlMailer < ActionMailer::Base # rubocop:disable Rails/ApplicationMailer
3
3
  include CamaleonCms::SiteHelper
4
4
  include CamaleonCms::HooksHelper
5
5
  include CamaleonCms::PluginsHelper
@@ -23,7 +23,7 @@ module CamaleonCms
23
23
  layout_name: 'camaleon_cms/mailer',
24
24
  format: 'html'
25
25
  }.merge(data)
26
- data[:cc_to] = [data[:cc_to]] if data[:cc_to].is_a?(String) || !data[:cc_to].present?
26
+ data[:cc_to] = [data[:cc_to]] if data[:cc_to].is_a?(String) || data[:cc_to].blank?
27
27
 
28
28
  mail_data = { to: email, subject: subject }
29
29
  if @current_site.get_option('mailer_enabled') == 1
@@ -72,7 +72,7 @@ module CamaleonCms
72
72
  end
73
73
  end
74
74
 
75
- layout = data[:layout_name].present? ? data[:layout_name] : false
75
+ layout = data[:layout_name].presence || false
76
76
  if data[:template_name].present? # render email with template
77
77
  if data[:format] == 'html'
78
78
  mail(mail_data) do |format|
@@ -92,7 +92,7 @@ module CamaleonCms
92
92
  mail(mail_data) { |format| format.html { render inline: @html, layout: layout } } if data[:format] == 'html'
93
93
  mail(mail_data) { |format| format.text { render inline: @html, layout: layout } } if data[:format] == 'txt'
94
94
  end
95
- mail(mail_data) unless data[:format].present?
95
+ mail(mail_data) if data[:format].blank?
96
96
  end
97
97
 
98
98
  private