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.
- checksums.yaml +4 -4
- data/README.md +17 -4
- data/Rakefile +3 -16
- data/app/apps/plugins/attack/admin_controller.rb +9 -6
- data/app/apps/plugins/attack/attack_helper.rb +35 -25
- data/app/apps/plugins/authoring_post/authoring_post_helper.rb +23 -16
- data/app/apps/plugins/front_cache/admin_controller.rb +14 -10
- data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
- data/app/apps/plugins/front_cache/config/locales/translation.yml +0 -7
- data/app/apps/plugins/front_cache/front_cache_helper.rb +109 -37
- data/app/apps/plugins/front_cache/views/admin/settings.html.erb +0 -5
- data/app/apps/plugins/visibility_post/config/config.json +1 -0
- data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
- data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
- data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
- data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
- data/app/apps/themes/default/assets/css/style.css.scss +24 -0
- data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
- data/app/apps/themes/default/default_helper.rb +2 -1
- data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
- data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
- data/app/apps/themes/new/custom_helper.rb +5 -9
- data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
- data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
- data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
- data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
- data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
- data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
- data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
- data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
- data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
- data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
- data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
- data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
- data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
- data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
- data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
- data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
- data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
- data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
- data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +80 -13
- data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
- data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
- data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
- data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
- data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
- data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
- data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
- data/app/controllers/camaleon_cms/frontend_controller.rb +146 -74
- data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
- data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
- data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
- data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
- data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
- data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
- data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
- data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
- data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
- data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
- data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
- data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
- data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
- data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
- data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
- data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
- data/app/decorators/camaleon_cms/post_decorator.rb +68 -50
- data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
- data/app/decorators/camaleon_cms/site_decorator.rb +33 -25
- data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
- data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
- data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
- data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
- data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
- data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
- data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
- data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
- data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
- data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
- data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
- data/app/helpers/camaleon_cms/content_helper.rb +13 -6
- data/app/helpers/camaleon_cms/email_helper.rb +7 -3
- data/app/helpers/camaleon_cms/frontend/application_helper.rb +6 -1
- data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
- data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
- data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
- data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
- data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
- data/app/helpers/camaleon_cms/html_helper.rb +58 -37
- data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
- data/app/helpers/camaleon_cms/session_helper.rb +188 -44
- data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
- data/app/helpers/camaleon_cms/site_helper.rb +34 -13
- data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
- data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
- data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
- data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
- data/app/models/camaleon_cms/ability.rb +15 -7
- data/app/models/camaleon_cms/category.rb +6 -6
- data/app/models/camaleon_cms/custom_field.rb +15 -6
- data/app/models/camaleon_cms/custom_field_group.rb +39 -22
- data/app/models/camaleon_cms/custom_fields_relationship.rb +140 -2
- data/app/models/camaleon_cms/media.rb +8 -2
- data/app/models/camaleon_cms/meta.rb +2 -2
- data/app/models/camaleon_cms/nav_menu.rb +5 -3
- data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
- data/app/models/camaleon_cms/plugin.rb +3 -4
- data/app/models/camaleon_cms/post.rb +137 -27
- data/app/models/camaleon_cms/post_comment.rb +10 -6
- data/app/models/camaleon_cms/post_default.rb +42 -12
- data/app/models/camaleon_cms/post_relationship.rb +6 -4
- data/app/models/camaleon_cms/post_tag.rb +4 -5
- data/app/models/camaleon_cms/post_type.rb +21 -13
- data/app/models/camaleon_cms/site.rb +39 -29
- data/app/models/camaleon_cms/term_relationship.rb +10 -7
- data/app/models/camaleon_cms/term_taxonomy.rb +58 -4
- data/app/models/camaleon_cms/theme.rb +2 -4
- data/app/models/camaleon_cms/user.rb +17 -5
- data/app/models/camaleon_cms/user_role.rb +81 -6
- data/app/models/camaleon_cms/widget/assigned.rb +11 -6
- data/app/models/camaleon_cms/widget/main.rb +12 -8
- data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
- data/app/models/camaleon_record.rb +89 -5
- data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +36 -42
- data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
- data/app/models/concerns/camaleon_cms/content_shortcode_gate.rb +73 -0
- data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
- data/app/models/concerns/camaleon_cms/metas.rb +31 -6
- data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
- data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
- data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
- data/app/models/current_request.rb +8 -1
- data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
- data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
- data/app/uploaders/camaleon_cms_uploader.rb +23 -10
- data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
- data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
- data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
- data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
- data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
- data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
- data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
- data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
- data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
- data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
- data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
- data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
- data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
- data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
- data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
- data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
- data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
- data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
- data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
- data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
- data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
- data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
- data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
- data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
- data/config/initializers/action_view.rb +42 -5
- data/config/initializers/active_record_extension.rb +13 -27
- data/config/initializers/assets.rb +52 -23
- data/config/initializers/custom_initializers.rb +1 -1
- data/config/locales/camaleon_cms/admin/ar.yml +1 -0
- data/config/locales/camaleon_cms/admin/de.yml +1 -0
- data/config/locales/camaleon_cms/admin/en.yml +36 -0
- data/config/locales/camaleon_cms/admin/es.yml +1 -0
- data/config/locales/camaleon_cms/admin/fr.yml +1 -0
- data/config/locales/camaleon_cms/admin/it.yml +1 -0
- data/config/locales/camaleon_cms/admin/nl.yml +1 -0
- data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
- data/config/locales/camaleon_cms/admin/pt.yml +1 -0
- data/config/locales/camaleon_cms/admin/ru.yml +1 -0
- data/config/locales/camaleon_cms/admin/uk.yml +1 -0
- data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
- data/config/routes/admin.rb +33 -15
- data/config/routes/frontend.rb +21 -15
- data/lib/camaleon_cms/assets_precompile.rb +86 -0
- data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
- data/lib/camaleon_cms/content_security.rb +158 -0
- data/lib/camaleon_cms/engine.rb +45 -21
- data/lib/camaleon_cms/media_security_headers.rb +27 -0
- data/lib/camaleon_cms/setup_token.rb +69 -0
- data/lib/camaleon_cms/shortcode_registry.rb +102 -0
- data/lib/camaleon_cms/svg_content_checker.rb +123 -0
- data/lib/camaleon_cms/task_reporter.rb +14 -0
- data/lib/camaleon_cms/unsafe_markup.rb +207 -0
- data/lib/camaleon_cms/uploader_content_security.rb +248 -0
- data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
- data/lib/camaleon_cms/uploader_path_security.rb +146 -0
- data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
- data/lib/camaleon_cms/uploader_support.rb +86 -0
- data/lib/camaleon_cms/version.rb +1 -1
- data/lib/camaleon_cms.rb +23 -0
- data/lib/ext/hash.rb +28 -2
- data/lib/ext/string.rb +10 -5
- data/lib/ext/translator.rb +10 -2
- data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
- data/lib/generators/camaleon_cms/install_generator.rb +7 -4
- data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
- data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
- data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
- data/lib/plugin_routes.rb +412 -368
- data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
- data/lib/tasks/cross_site_field_groups.rake +74 -0
- data/lib/tasks/custom_fields_roles.rake +15 -13
- data/lib/tasks/orphaned_comments.rake +42 -0
- data/lib/tasks/private_upload_acls.rake +31 -0
- data/lib/tasks/site_custom_field_groups.rake +36 -0
- data/lib/tasks/unsafe_stored_content.rake +42 -0
- data/lib/tasks/unsafe_stored_uploads.rake +70 -0
- data/lib/tasks/user_field_groups.rake +27 -0
- metadata +125 -4
|
@@ -4,10 +4,20 @@ module CamaleonCms
|
|
|
4
4
|
# default structure for each new site
|
|
5
5
|
def default_settings
|
|
6
6
|
default_post_type = [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
{
|
|
8
|
+
name: 'Post', description: 'Posts',
|
|
9
|
+
options: {
|
|
10
|
+
has_category: true, has_tags: true, not_deleted: true, has_summary: true, has_content: true,
|
|
11
|
+
has_comments: true, has_picture: true, has_template: true
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'Page', description: 'Pages',
|
|
16
|
+
options: {
|
|
17
|
+
has_category: false, has_tags: false, not_deleted: true, has_summary: false, has_content: true,
|
|
18
|
+
has_comments: false, has_picture: true, has_template: true, has_layout: true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
]
|
|
12
22
|
default_post_type.each do |pt|
|
|
13
23
|
post_types.create({ name: pt[:name], slug: pt[:name].to_s.parameterize, description: pt[:description],
|
|
@@ -17,20 +27,101 @@ module CamaleonCms
|
|
|
17
27
|
# nav menus
|
|
18
28
|
@nav_menu = nav_menus.new({ name: 'Main Menu', slug: 'main_menu' })
|
|
19
29
|
if @nav_menu.save
|
|
20
|
-
post_types.
|
|
30
|
+
post_types.find_each do |pt|
|
|
21
31
|
if pt.slug == 'post'
|
|
22
32
|
title = 'Sample Post'
|
|
23
33
|
slug = 'sample-post'
|
|
24
|
-
|
|
34
|
+
content_parts = []
|
|
35
|
+
content_parts << helpers.content_tag(
|
|
36
|
+
:p, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pharetra ut augue in posuere. ' \
|
|
37
|
+
'Nulla non malesuada dui. Sed egestas tortor ut purus tempor sodales. ' \
|
|
38
|
+
'Duis non sollicitudin nulla, quis mollis neque. Integer sit amet augue ac neque varius auctor. ' \
|
|
39
|
+
'Vestibulum malesuada leo leo, at semper libero efficitur nec. ' \
|
|
40
|
+
'Etiam semper nisi ac nisi ullamcorper, sed tincidunt purus elementum. ' \
|
|
41
|
+
'Mauris ac congue nibh. Quisque pretium eget leo nec suscipit.'
|
|
42
|
+
)
|
|
43
|
+
content_parts << helpers.content_tag(
|
|
44
|
+
:p, 'Vestibulum ultrices orci ut congue interdum. ' \
|
|
45
|
+
'Morbi dolor nunc, imperdiet vel risus semper, tempor dapibus urna. ' \
|
|
46
|
+
'Phasellus luctus pharetra enim quis volutpat. Integer tristique urna nec malesuada ullamcorper. ' \
|
|
47
|
+
'Curabitur dictum, lectus id ultrices rhoncus, ante neque auctor erat, ' \
|
|
48
|
+
'ut sodales nisi odio sit amet lorem. In hac habitasse platea dictumst. ' \
|
|
49
|
+
'Quisque orci orci, hendrerit at luctus tristique, lobortis in diam. ' \
|
|
50
|
+
'Curabitur ligula enim, rhoncus ut vestibulum a, consequat sit amet nisi. ' \
|
|
51
|
+
'Aliquam bibendum fringilla ultrices. Aliquam erat volutpat. ' \
|
|
52
|
+
'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; ' \
|
|
53
|
+
'In justo mi, congue in rhoncus lobortis, facilisis in est. Nam et rhoncus purus.'
|
|
54
|
+
)
|
|
55
|
+
content_parts << helpers.content_tag(
|
|
56
|
+
:p, 'Sed sagittis auctor lectus at rutrum. ' \
|
|
57
|
+
'Morbi ultricies felis mi, ut scelerisque augue facilisis eu. In molestie quam ex. ' \
|
|
58
|
+
'Quisque ut sapien sed odio tempus imperdiet. In id accumsan massa. ' \
|
|
59
|
+
'Morbi quis nunc ullamcorper, interdum enim eu, finibus purus. ' \
|
|
60
|
+
'Vestibulum ac fermentum augue, at tempus ante. ' \
|
|
61
|
+
'Aliquam ultrices, purus ut porttitor gravida, dui augue dignissim massa, ' \
|
|
62
|
+
'ac tempor ante dolor at arcu. ' \
|
|
63
|
+
'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' \
|
|
64
|
+
'Suspendisse placerat risus est, eget varius mi ultricies in. ' \
|
|
65
|
+
'Duis non odio ut felis dapibus eleifend. In fringilla enim lobortis placerat efficitur.'
|
|
66
|
+
)
|
|
67
|
+
content_parts << helpers.content_tag(
|
|
68
|
+
:p, 'Nulla sodales faucibus urna, quis viverra dolor facilisis sollicitudin. Aenean ac egestas nibh. ' \
|
|
69
|
+
'Nam non tortor eget nibh scelerisque fermentum. ' \
|
|
70
|
+
'Etiam ornare, nunc ut luctus mollis, ante dolor consectetur augue, ' \
|
|
71
|
+
'non scelerisque odio est a nulla. Nullam cursus egestas nulla, nec commodo nibh suscipit ut. ' \
|
|
72
|
+
'Mauris ut felis sem. Aenean at mi at nisi dictum blandit sit amet at erat. ' \
|
|
73
|
+
'Etiam eget lobortis tellus. Curabitur in commodo arcu, at vehicula tortor.'
|
|
74
|
+
)
|
|
75
|
+
content = helpers.safe_join(content_parts)
|
|
25
76
|
else
|
|
26
77
|
title = 'Welcome'
|
|
27
78
|
slug = 'welcome'
|
|
28
|
-
|
|
79
|
+
welcome_parts = []
|
|
80
|
+
logo_img = helpers
|
|
81
|
+
.image_tag('https://camaleon.website/media/132/logo2.png', width: 155, height: 155, alt: 'logo')
|
|
82
|
+
welcome_parts << helpers.content_tag(:p, logo_img, style: 'text-align: center;')
|
|
83
|
+
cms_strong = helpers.content_tag(:strong, 'Camaleon CMS')
|
|
84
|
+
rails_link = helpers.link_to('Ruby on Rails', 'https://rubyonrails.org')
|
|
85
|
+
part2_text = helpers.safe_join(
|
|
86
|
+
[
|
|
87
|
+
cms_strong,
|
|
88
|
+
' is a free and open-source tool and a fexible content management system (CMS) based on ',
|
|
89
|
+
rails_link
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
welcome_parts << helpers.content_tag(:p, part2_text)
|
|
93
|
+
welcome_parts << helpers.content_tag(:p, 'With Camaleon you can do the following:')
|
|
94
|
+
li_texts = [
|
|
95
|
+
'Create instantly a lot of sites in the same installation',
|
|
96
|
+
'Manage your content information in several languages',
|
|
97
|
+
'Extend current functionality by plugins (MVC structure and no more echo or prints anywhere)',
|
|
98
|
+
'Create or install different themes for each site',
|
|
99
|
+
'Create your own structure without coding anything (adapt Camaleon as you want and not you for Camaleon)',
|
|
100
|
+
'Create your store and start to sell your products using our plugins',
|
|
101
|
+
'Avoid web attacks',
|
|
102
|
+
'Compare the speed and enjoy the speed of your new Camaleon site',
|
|
103
|
+
'Customize or create your themes for mobile support',
|
|
104
|
+
'Support more visitors at the same time',
|
|
105
|
+
'Manage your information with a panel like wordpress ',
|
|
106
|
+
'All urls are oriented for SEO',
|
|
107
|
+
'Multiples roles of users'
|
|
108
|
+
]
|
|
109
|
+
li_tags = li_texts.map { |text| helpers.content_tag(:li, text) }
|
|
110
|
+
welcome_parts << helpers.content_tag(:ul, helpers.safe_join(li_tags))
|
|
111
|
+
content = helpers.safe_join(welcome_parts)
|
|
29
112
|
end
|
|
30
113
|
user = users.admin_scope.first
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
114
|
+
if user.blank?
|
|
115
|
+
# Never mint a known default password. Generate a random one, force a change on first
|
|
116
|
+
# login, and stash the plaintext transiently so the provisioning controller can surface
|
|
117
|
+
# it once. See harden-installer-default-admin.
|
|
118
|
+
generated_password = SecureRandom.alphanumeric(16)
|
|
119
|
+
user = users.admin_scope.create({ email: 'admin@local.com', username: 'admin',
|
|
120
|
+
password: generated_password,
|
|
121
|
+
password_confirmation: generated_password,
|
|
122
|
+
first_name: 'Administrator' })
|
|
123
|
+
user.set_meta('must_change_password', true)
|
|
124
|
+
self.generated_admin_password = generated_password
|
|
34
125
|
end
|
|
35
126
|
post = pt.add_post({ title: title, slug: slug, content: content, user_id: user.id, status: 'published' })
|
|
36
127
|
@nav_menu.append_menu_item({ label: title, type: 'post', link: post.id })
|
|
@@ -60,18 +151,26 @@ module CamaleonCms
|
|
|
60
151
|
if post_type.present?
|
|
61
152
|
d = user_role.get_meta("_post_type_#{id}", {})
|
|
62
153
|
CamaleonCms::UserRole::ROLES[:post_type].each do |value|
|
|
154
|
+
# post_content_unfiltered_html lets a role store unsanitized HTML in post content; it must not be
|
|
155
|
+
# granted to the default Editor role (only admins, via `can :manage, :all`, are trusted for it).
|
|
156
|
+
next if value[:key].to_s == 'post_content_unfiltered_html'
|
|
157
|
+
|
|
63
158
|
value_old = d[value[:key].to_sym] || []
|
|
64
159
|
d[value[:key].to_sym] = value_old + [post_type.id]
|
|
65
160
|
end
|
|
66
161
|
else
|
|
67
162
|
pts = post_types.all.pluck(:id)
|
|
68
|
-
CamaleonCms::UserRole::ROLES[:post_type].each
|
|
163
|
+
CamaleonCms::UserRole::ROLES[:post_type].each do |value|
|
|
164
|
+
next if value[:key].to_s == 'post_content_unfiltered_html'
|
|
165
|
+
|
|
166
|
+
d[value[:key]] = pts
|
|
167
|
+
end
|
|
69
168
|
end
|
|
70
169
|
user_role.set_meta("_post_type_#{id}", d || {})
|
|
71
170
|
end
|
|
72
171
|
|
|
73
|
-
user_role = user_roles.where({ slug: 'contributor' })
|
|
74
|
-
|
|
172
|
+
user_role = user_roles.where({ slug: 'contributor' })
|
|
173
|
+
.first_or_create({ name: 'Contributor', description: 'Contributor Role' })
|
|
75
174
|
if user_role.valid?
|
|
76
175
|
d = {}
|
|
77
176
|
if post_type.present?
|
|
@@ -89,13 +188,18 @@ module CamaleonCms
|
|
|
89
188
|
|
|
90
189
|
return if post_type.present?
|
|
91
190
|
|
|
92
|
-
user_role = user_roles.where({ slug: 'client',
|
|
93
|
-
|
|
94
|
-
description: 'Default roles client' })
|
|
191
|
+
user_role = user_roles.where({ slug: 'client', term_group: -1 })
|
|
192
|
+
.first_or_create({ name: 'Client', description: 'Default roles client' })
|
|
95
193
|
return unless user_role.valid?
|
|
96
194
|
|
|
97
195
|
user_role.set_meta("_post_type_#{id}", {})
|
|
98
196
|
user_role.set_meta("_manager_#{id}", {})
|
|
99
197
|
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
def helpers
|
|
202
|
+
ActionController::Base.helpers
|
|
203
|
+
end
|
|
100
204
|
end
|
|
101
205
|
end
|
|
@@ -8,12 +8,20 @@ module CamaleonCms
|
|
|
8
8
|
|
|
9
9
|
extend CamaleonCms::NormalizeAttrs
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
validates(
|
|
12
|
+
:username, uniqueness: { scope: [:site_id], case_sensitive: false,
|
|
13
|
+
message: I18n.t(
|
|
14
|
+
'camaleon_cms.admin.users.message.requires_different_username',
|
|
15
|
+
default: 'Requires different username'
|
|
16
|
+
) }
|
|
17
|
+
)
|
|
18
|
+
validates(
|
|
19
|
+
:email, uniqueness: { scope: [:site_id], case_sensitive: false,
|
|
20
|
+
message: I18n.t(
|
|
21
|
+
'camaleon_cms.admin.users.message.requires_different_email',
|
|
22
|
+
default: 'Requires different email'
|
|
23
|
+
) }
|
|
24
|
+
)
|
|
17
25
|
|
|
18
26
|
# callbacks
|
|
19
27
|
before_validation :cama_before_validation
|
|
@@ -24,8 +32,12 @@ module CamaleonCms
|
|
|
24
32
|
before_update { generate_token :auth_token if will_save_change_to_password_digest? }
|
|
25
33
|
|
|
26
34
|
# relations
|
|
27
|
-
has_many :all_posts, class_name: 'CamaleonCms::Post', foreign_key: :user_id
|
|
28
|
-
|
|
35
|
+
has_many :all_posts, class_name: 'CamaleonCms::Post', foreign_key: :user_id, inverse_of: :owner,
|
|
36
|
+
dependent: :nullify
|
|
37
|
+
# No `dependent:` — the rows must still carry this user_id when the
|
|
38
|
+
# `after_destroy :reassign_comments` callback moves them to the anonymous user.
|
|
39
|
+
has_many :all_comments, class_name: 'CamaleonCms::PostComment' # rubocop:disable Rails/HasManyOrHasOneDependent
|
|
40
|
+
|
|
29
41
|
belongs_to :site, class_name: 'CamaleonCms::Site', optional: true
|
|
30
42
|
|
|
31
43
|
# scopes
|
|
@@ -64,9 +76,9 @@ module CamaleonCms
|
|
|
64
76
|
@_user_role ||= site.user_roles.where(slug: role).first
|
|
65
77
|
end
|
|
66
78
|
|
|
67
|
-
#
|
|
79
|
+
# Assign a new site for the current user
|
|
68
80
|
def assign_site(site)
|
|
69
|
-
update_column(:site_id, site.id)
|
|
81
|
+
update_column(:site_id, site.id) # rubocop:disable Rails/SkipsModelValidations
|
|
70
82
|
end
|
|
71
83
|
|
|
72
84
|
def sites
|
|
@@ -93,6 +105,14 @@ module CamaleonCms
|
|
|
93
105
|
end
|
|
94
106
|
end
|
|
95
107
|
|
|
108
|
+
# Rotate the auth token and persist it (audit 2026-08-11 M3): called on logout so a cookie copied
|
|
109
|
+
# before logout can no longer authenticate. Skips validations/callbacks -- it only replaces the
|
|
110
|
+
# token column and must work even for a record that would not otherwise pass validation.
|
|
111
|
+
def cama_reset_auth_token!
|
|
112
|
+
generate_token(:auth_token)
|
|
113
|
+
update_column(:auth_token, auth_token) # rubocop:disable Rails/SkipsModelValidations
|
|
114
|
+
end
|
|
115
|
+
|
|
96
116
|
def send_password_reset
|
|
97
117
|
generate_token(:password_reset_token)
|
|
98
118
|
self.password_reset_sent_at = Time.zone.now
|
|
@@ -126,20 +146,25 @@ module CamaleonCms
|
|
|
126
146
|
all_posts.each do |p|
|
|
127
147
|
s = p.post_type.site
|
|
128
148
|
u = s.users.admin_scope.where.not(id: id).first
|
|
129
|
-
next
|
|
149
|
+
next if u.blank?
|
|
130
150
|
|
|
131
|
-
p.update_column(:user_id, u.id)
|
|
132
|
-
p.comments.where(user_id: id).
|
|
133
|
-
c.update_column(:user_id, u.id)
|
|
134
|
-
end
|
|
151
|
+
p.update_column(:user_id, u.id) # rubocop:disable Rails/SkipsModelValidations
|
|
152
|
+
p.comments.where(user_id: id).update_all(user_id: u.id) # rubocop:disable Rails/SkipsModelValidations
|
|
135
153
|
end
|
|
136
154
|
end
|
|
137
155
|
|
|
156
|
+
# Until the dependent: :nullify on all_comments was removed this loop never ran — the
|
|
157
|
+
# association was already empty by the time after_destroy fired — so the unguarded chain
|
|
158
|
+
# below was never exercised. It runs now, and a comment whose post, post type or site cannot
|
|
159
|
+
# be resolved would raise inside after_destroy and roll the whole user deletion back. Such a
|
|
160
|
+
# comment is skipped instead; `rake camaleon_cms:reassign_orphaned_comments` reports the same
|
|
161
|
+
# rows, and it already guarded the identical chain.
|
|
138
162
|
def reassign_comments
|
|
139
|
-
all_comments.includes(post: { post_type: :site }).
|
|
140
|
-
site = comment.post
|
|
141
|
-
|
|
142
|
-
|
|
163
|
+
all_comments.includes(post: { post_type: :site }).find_each do |comment|
|
|
164
|
+
site = comment.post&.post_type&.site
|
|
165
|
+
next if site.nil?
|
|
166
|
+
|
|
167
|
+
comment.update_column(:user_id, site.get_anonymous_user.id) # rubocop:disable Rails/SkipsModelValidations
|
|
143
168
|
end
|
|
144
169
|
end
|
|
145
170
|
end
|
|
@@ -12,5 +12,12 @@
|
|
|
12
12
|
# Note: Rails automatically resets CurrentAttributes between requests, ensuring no leakage.
|
|
13
13
|
# In specs, call CurrentRequest.reset in before/after hooks to avoid cross-test pollution.
|
|
14
14
|
class CurrentRequest < ActiveSupport::CurrentAttributes
|
|
15
|
-
attribute :user, :site
|
|
15
|
+
attribute :user, :user_resolved, :site, :content_helper_state, :hooks_helper_state, :html_helper_state,
|
|
16
|
+
:theme_helper_state,
|
|
17
|
+
:frontend_object, :frontend_seo_settings, :frontend_current_theme, :frontend_site_current_path,
|
|
18
|
+
:frontend_visited_home, :frontend_visited_post, :frontend_visited_ajax, :frontend_visited_search,
|
|
19
|
+
:frontend_visited_post_type, :frontend_visited_tag, :frontend_visited_category,
|
|
20
|
+
:frontend_visited_profile, :frontend_user,
|
|
21
|
+
:admin_menu_items, :custom_field_elements, :extra_models_for_fields,
|
|
22
|
+
:shortcodes, :shortcodes_template, :shortcodes_descr
|
|
16
23
|
end
|
|
@@ -72,9 +72,7 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
|
|
|
72
72
|
'dimension' => ''
|
|
73
73
|
}.with_indifferent_access
|
|
74
74
|
if res['file_type'] == 'image' && File.extname(res['name']).downcase != '.gif'
|
|
75
|
-
res['thumb'] =
|
|
76
|
-
version_path(res['url']).sub('.svg',
|
|
77
|
-
'.jpg')
|
|
75
|
+
res['thumb'] = version_path(res['url']).sub(SVG_EXT_PATTERN, '.jpg')
|
|
78
76
|
end
|
|
79
77
|
res['key'] = File.join(res['folder_path'], res['name'])
|
|
80
78
|
@aws_settings[:aws_file_read_settings].call(res, s3_file)
|
|
@@ -100,14 +98,20 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
|
|
|
100
98
|
end
|
|
101
99
|
|
|
102
100
|
s3_file = bucket.object(key.slice(1..-1))
|
|
101
|
+
# Security (audit 2026-08-11 M5): a private-mode upload must be owner-only. Storing it 'public-read'
|
|
102
|
+
# (like a public file) left it world-readable at a guessable s3://bucket/private/<name> URL,
|
|
103
|
+
# bypassing the download_private_file gate. That gate fetches via the authenticated S3 API, so a
|
|
104
|
+
# 'private' ACL does not affect legitimate serving.
|
|
105
|
+
acl = is_private_uploader? ? 'private' : 'public-read'
|
|
103
106
|
s3_file.upload_file(
|
|
104
|
-
uploaded_io_or_file_path.is_a?(String) ? uploaded_io_or_file_path : uploaded_io_or_file_path.path,
|
|
107
|
+
uploaded_io_or_file_path.is_a?(String) ? uploaded_io_or_file_path : uploaded_io_or_file_path.path,
|
|
108
|
+
@aws_settings[:aws_file_upload_settings].call({ acl: acl })
|
|
105
109
|
)
|
|
106
110
|
res = cache_item(file_parse(s3_file)) unless args[:is_thumb]
|
|
107
111
|
res
|
|
108
112
|
end
|
|
109
113
|
|
|
110
|
-
#
|
|
114
|
+
# Add a new folder to AWS with :key
|
|
111
115
|
def add_folder(key)
|
|
112
116
|
return { error: 'Invalid folder path' } unless valid_folder_path?(key)
|
|
113
117
|
|
|
@@ -118,17 +122,17 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
|
|
|
118
122
|
cache_item(file_parse(s3_file))
|
|
119
123
|
end
|
|
120
124
|
|
|
121
|
-
#
|
|
125
|
+
# Delete a folder in AWS with :key
|
|
122
126
|
def delete_folder(key)
|
|
123
127
|
return { error: 'Invalid folder path' } unless valid_folder_path?(key)
|
|
124
128
|
|
|
125
129
|
key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present?
|
|
126
130
|
key = key.cama_fix_media_key
|
|
127
131
|
bucket.objects(prefix: key.slice(1..-1) << '/').delete
|
|
128
|
-
get_media_collection.
|
|
132
|
+
get_media_collection.by_key(key).take.destroy
|
|
129
133
|
end
|
|
130
134
|
|
|
131
|
-
#
|
|
135
|
+
# Delete the `:key` file in AWS
|
|
132
136
|
def delete_file(key)
|
|
133
137
|
return { error: 'Invalid file path' } unless valid_folder_path?(key)
|
|
134
138
|
|
|
@@ -140,10 +144,27 @@ class CamaleonCmsAwsUploader < CamaleonCmsUploader
|
|
|
140
144
|
''
|
|
141
145
|
end
|
|
142
146
|
@instance.hooks_run('after_delete', key)
|
|
143
|
-
get_media_collection.
|
|
147
|
+
get_media_collection.by_key(key).take.destroy
|
|
144
148
|
end
|
|
145
149
|
|
|
146
|
-
#
|
|
150
|
+
# Security (audit 2026-08-11 M5 follow-up): uploads stored before the private-ACL fix kept a
|
|
151
|
+
# world-readable ACL under the private prefix. Sweep them back to owner-only and return the count.
|
|
152
|
+
# The prefix mirrors setup_private_folder -- <inner_folder>/private/ -- normalized exactly like
|
|
153
|
+
# add_file keys (leading slash added by cama_fix_media_key, then stripped for S3), with a trailing
|
|
154
|
+
# slash so a sibling folder such as private_x is not swept.
|
|
155
|
+
def repair_private_acls!
|
|
156
|
+
inner_folder = @aws_settings['inner_folder'].to_s
|
|
157
|
+
inner_folder = "#{inner_folder}/#{PRIVATE_DIRECTORY}" unless is_private_uploader?
|
|
158
|
+
prefix = "#{inner_folder.cama_fix_media_key.slice(1..-1)}/"
|
|
159
|
+
repaired = 0
|
|
160
|
+
bucket.objects(prefix: prefix).each do |object|
|
|
161
|
+
object.acl.put(acl: 'private')
|
|
162
|
+
repaired += 1
|
|
163
|
+
end
|
|
164
|
+
repaired
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Initialize a bucket with AWS configurations
|
|
147
168
|
# return: (AWS Bucket object)
|
|
148
169
|
def bucket
|
|
149
170
|
@bucket ||= lambda {
|
|
@@ -2,13 +2,13 @@ class CamaleonCmsLocalUploader < CamaleonCmsUploader
|
|
|
2
2
|
def after_initialize
|
|
3
3
|
@root_folder = @current_site.upload_directory
|
|
4
4
|
|
|
5
|
-
FileUtils.mkdir_p(@root_folder)
|
|
5
|
+
FileUtils.mkdir_p(@root_folder)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def setup_private_folder
|
|
9
9
|
@root_folder = Rails.root.join(self.class::PRIVATE_DIRECTORY).to_s
|
|
10
10
|
|
|
11
|
-
FileUtils.mkdir_p(@root_folder)
|
|
11
|
+
FileUtils.mkdir_p(@root_folder)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def browser_files(prefix = '/', _objects = {})
|
|
@@ -32,6 +32,17 @@ class CamaleonCmsLocalUploader < CamaleonCmsUploader
|
|
|
32
32
|
{ error: 'File not found' }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# Repairs legacy thumbnail URLs (see #cama_compat_legacy_thumb) for one rendered page of
|
|
36
|
+
# media items. The cache (media DB records) may still hold thumb URLs computed from the
|
|
37
|
+
# source extension (sample: ".jpg") while the on-disk thumbnail is a legacy ".png"; without
|
|
38
|
+
# this the admin media browser would render 404 thumbnails for such files. Applied by the
|
|
39
|
+
# media controller to the paginated page only — #objects stays a lazy relation so the browser
|
|
40
|
+
# paginates at the database instead of materializing (and stat-ing) the whole folder.
|
|
41
|
+
def cama_prepare_browser_page(items)
|
|
42
|
+
items.each { |item| cama_fix_legacy_thumb_item(item) }
|
|
43
|
+
items
|
|
44
|
+
end
|
|
45
|
+
|
|
35
46
|
def file_parse(key)
|
|
36
47
|
file_path = File.join(@root_folder, key)
|
|
37
48
|
url_path = file_path.sub(Rails.root.join('public').to_s, '')
|
|
@@ -41,16 +52,13 @@ class CamaleonCmsLocalUploader < CamaleonCmsUploader
|
|
|
41
52
|
'folder_path' => File.dirname(key),
|
|
42
53
|
'url' => if is_dir
|
|
43
54
|
''
|
|
55
|
+
elsif is_private_uploader?
|
|
56
|
+
url_path.sub("#{@root_folder}/", '')
|
|
44
57
|
else
|
|
45
|
-
(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
File.join(
|
|
50
|
-
@current_site.decorate.the_url(as_path: true, locale: false,
|
|
51
|
-
skip_relative_url_root: true), url_path
|
|
52
|
-
)
|
|
53
|
-
end)
|
|
58
|
+
File.join(
|
|
59
|
+
@current_site.decorate.the_url(as_path: true, locale: false,
|
|
60
|
+
skip_relative_url_root: true), url_path
|
|
61
|
+
)
|
|
54
62
|
end,
|
|
55
63
|
'is_folder' => is_dir,
|
|
56
64
|
'file_size' => is_dir ? 0 : File.size(file_path).round(2),
|
|
@@ -60,11 +68,16 @@ class CamaleonCmsLocalUploader < CamaleonCmsUploader
|
|
|
60
68
|
}.with_indifferent_access
|
|
61
69
|
res['key'] = File.join(res['folder_path'], res['name'])
|
|
62
70
|
if res['file_type'] == 'image' && File.extname(file_path).downcase != '.gif'
|
|
63
|
-
res['thumb'] =
|
|
64
|
-
|
|
71
|
+
res['thumb'] = if is_private_uploader?
|
|
72
|
+
"/admin/media/download_private_file?file=#{version_path(key).slice(1..-1)}"
|
|
73
|
+
else
|
|
74
|
+
version_path(res['url'])
|
|
75
|
+
end
|
|
65
76
|
end
|
|
66
77
|
if res['file_type'] == 'image'
|
|
67
|
-
res['thumb'].sub!
|
|
78
|
+
res['thumb'].sub!(SVG_EXT_PATTERN, '.jpg')
|
|
79
|
+
res['thumb'] = cama_compat_legacy_thumb(res['thumb'], version_path(key).sub(SVG_EXT_PATTERN, '.jpg'),
|
|
80
|
+
res['url'])
|
|
68
81
|
im = MiniMagick::Image.open(file_path)
|
|
69
82
|
res['dimension'] = begin
|
|
70
83
|
"#{im[:width]}x#{im[:height]}"
|
|
@@ -109,27 +122,71 @@ class CamaleonCmsLocalUploader < CamaleonCmsUploader
|
|
|
109
122
|
f
|
|
110
123
|
end
|
|
111
124
|
|
|
112
|
-
#
|
|
125
|
+
# Remove an existent folder
|
|
113
126
|
def delete_folder(key)
|
|
114
127
|
return { error: 'Invalid folder path' } if key.include?('..')
|
|
115
128
|
|
|
116
129
|
folder = File.join(@root_folder, key)
|
|
117
130
|
FileUtils.rm_rf(folder) if Dir.exist? folder
|
|
118
|
-
get_media_collection.
|
|
131
|
+
get_media_collection.by_key(key).take.destroy
|
|
119
132
|
end
|
|
120
133
|
|
|
121
|
-
#
|
|
134
|
+
# Remove an existent file
|
|
122
135
|
def delete_file(key)
|
|
123
136
|
return { error: 'Invalid file path' } if key.include?('..')
|
|
124
137
|
|
|
125
138
|
file = File.join(@root_folder, key)
|
|
126
139
|
FileUtils.rm(file) if File.exist? file
|
|
127
140
|
@instance.hooks_run('after_delete', key)
|
|
128
|
-
get_media_collection.
|
|
141
|
+
get_media_collection.by_key(key).take.destroy
|
|
129
142
|
end
|
|
130
143
|
|
|
131
|
-
#
|
|
144
|
+
# Convert a real file path into a file key
|
|
132
145
|
def parse_key(file_path)
|
|
133
146
|
file_path.sub(@root_folder, '').cama_fix_media_key
|
|
134
147
|
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
# Applies the legacy-thumbnail fallback (see #cama_compat_legacy_thumb) to a
|
|
152
|
+
# single cached media item in place, so the admin media browser renders the
|
|
153
|
+
# on-disk ".png" thumbnail instead of a 404ing ".jpg" one. No-op for folders,
|
|
154
|
+
# non-image files and items without a thumb.
|
|
155
|
+
# item: (CamaleonCms::Media | Hash) cached media object
|
|
156
|
+
def cama_fix_legacy_thumb_item(item)
|
|
157
|
+
return if item['is_folder'] || item['file_type'] != 'image' || item['thumb'].blank?
|
|
158
|
+
|
|
159
|
+
thumb_key = version_path(File.join(item['folder_path'].to_s, item['name'].to_s))
|
|
160
|
+
item['thumb'] = cama_compat_legacy_thumb(item['thumb'], thumb_key, item['url'])
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Backwards-compatibility for legacy thumbnails.
|
|
164
|
+
# Older Camaleon releases stored raster thumbnails as PNG regardless of the
|
|
165
|
+
# source extension (sample: "photo.jpg" => "thumb/photo-jpg.png"). The thumb
|
|
166
|
+
# URL is normally derived from the source extension, so for such legacy files
|
|
167
|
+
# the computed (sample: ".jpg") thumb URL would 404. Resolution order when the
|
|
168
|
+
# computed thumb is missing on disk:
|
|
169
|
+
# 1. a ".png" sibling exists => point the thumb at the PNG;
|
|
170
|
+
# 2. no thumbnail exists at all (sample: ".ico" favicons are never
|
|
171
|
+
# thumbnailed) => fall back to the original file url so the browser does
|
|
172
|
+
# not request a 404 thumb (the original lives one level above /thumb).
|
|
173
|
+
# Otherwise the computed thumb url is returned unchanged (new uploads, PNG
|
|
174
|
+
# sources and S3 are unaffected).
|
|
175
|
+
# thumb_url: (String) computed public/private thumb url
|
|
176
|
+
# thumb_key: (String) computed thumb media key (relative to @root_folder)
|
|
177
|
+
# original_url: (String) url of the original (non-thumb) file
|
|
178
|
+
def cama_compat_legacy_thumb(thumb_url, thumb_key, original_url = nil)
|
|
179
|
+
return thumb_url if thumb_url.blank?
|
|
180
|
+
|
|
181
|
+
ext = File.extname(thumb_key)
|
|
182
|
+
return thumb_url if ext.blank?
|
|
183
|
+
return thumb_url if file_exists?(File.join(@root_folder, thumb_key))
|
|
184
|
+
|
|
185
|
+
unless ext.casecmp?('.png')
|
|
186
|
+
png_key = thumb_key.sub(/#{Regexp.escape(ext)}\z/, '.png')
|
|
187
|
+
return thumb_url.sub(/#{Regexp.escape(ext)}\z/, '.png') if file_exists?(File.join(@root_folder, png_key))
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
original_url.presence || thumb_url
|
|
191
|
+
end
|
|
135
192
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
class CamaleonCmsUploader
|
|
2
2
|
PRIVATE_DIRECTORY = 'private'.freeze
|
|
3
|
+
# Case-insensitive terminal-`.svg` matcher, borrowed from the thumb/crop rename convention it
|
|
4
|
+
# must stay in lockstep with (defined beside that convention in UploaderImageProcessing).
|
|
5
|
+
SVG_EXT_PATTERN = CamaleonCms::UploaderImageProcessing::SVG_EXT_PATTERN
|
|
3
6
|
|
|
4
7
|
attr_accessor :thumb
|
|
5
8
|
|
|
@@ -25,10 +28,11 @@ class CamaleonCmsUploader
|
|
|
25
28
|
''].include?(prefix)
|
|
26
29
|
get_media_collection.where(folder_path: '/')
|
|
27
30
|
else
|
|
28
|
-
get_media_collection.
|
|
31
|
+
get_media_collection.by_key(prefix).take.try(:items)
|
|
29
32
|
end
|
|
30
|
-
# Private hook to recover custom files to include
|
|
31
|
-
#
|
|
33
|
+
# Private hook to recover custom files to include into the current list where
|
|
34
|
+
# data can be modified to add custom{files, folders}.
|
|
35
|
+
# Note: these hooks don't have access to public vars like params. requests, ...
|
|
32
36
|
if @instance
|
|
33
37
|
args = { data: res, prefix: prefix }
|
|
34
38
|
@instance.hooks_run('uploader_list_objects', args)
|
|
@@ -37,6 +41,15 @@ class CamaleonCmsUploader
|
|
|
37
41
|
res
|
|
38
42
|
end
|
|
39
43
|
|
|
44
|
+
# Post-process one *rendered page* of media items for display. Base and S3 need nothing;
|
|
45
|
+
# the local uploader overrides this to repair legacy thumbnail URLs (see its override), so
|
|
46
|
+
# that per-image filesystem check runs over the paginated page rather than the whole folder.
|
|
47
|
+
# Kept out of #objects on purpose: #objects must stay a lazy relation so the media browser
|
|
48
|
+
# paginates at the database.
|
|
49
|
+
def cama_prepare_browser_page(items)
|
|
50
|
+
items
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
# clean cached of files structure saved into DB
|
|
41
54
|
def clear_cache
|
|
42
55
|
get_media_collection.destroy_all
|
|
@@ -94,7 +107,7 @@ class CamaleonCmsUploader
|
|
|
94
107
|
# support for multiples formats, sample: image,audio
|
|
95
108
|
def self.get_file_format_extensions(format)
|
|
96
109
|
res = []
|
|
97
|
-
format.downcase.
|
|
110
|
+
format.downcase.delete(' ').split(',').each do |f|
|
|
98
111
|
res << case f
|
|
99
112
|
when 'image', 'images'
|
|
100
113
|
'jpg,jpeg,png,gif,bmp,ico,svg'
|
|
@@ -116,12 +129,12 @@ class CamaleonCmsUploader
|
|
|
116
129
|
# verify permitted formats (return boolean true | false)
|
|
117
130
|
# true: if format is accepted
|
|
118
131
|
# false: if format is not accepted
|
|
119
|
-
# sample: validate_file_format('/var/www/myfile.xls', 'image,audio,docx,xls') => return true if the file extension
|
|
132
|
+
# sample: validate_file_format('/var/www/myfile.xls', 'image,audio,docx,xls') => return true if the file extension
|
|
133
|
+
# is in formats
|
|
120
134
|
def self.validate_file_format(key, valid_formats = '*')
|
|
121
|
-
return true if valid_formats == '*' ||
|
|
135
|
+
return true if valid_formats == '*' || valid_formats.blank?
|
|
122
136
|
|
|
123
|
-
valid_formats = valid_formats.
|
|
124
|
-
'').downcase.split(',') + get_file_format_extensions(valid_formats).split(',')
|
|
137
|
+
valid_formats = valid_formats.delete(' ').downcase.split(',') + get_file_format_extensions(valid_formats).split(',')
|
|
125
138
|
valid_formats.include?(File.extname(key).sub('.', '').split('?').first.try(:downcase))
|
|
126
139
|
end
|
|
127
140
|
|
|
@@ -136,10 +149,10 @@ class CamaleonCmsUploader
|
|
|
136
149
|
# sample: search_new_key("my_file/file.txt")
|
|
137
150
|
def search_new_key(key)
|
|
138
151
|
_key = key
|
|
139
|
-
if get_media_collection.
|
|
152
|
+
if get_media_collection.by_key(key).any?
|
|
140
153
|
(1..999).each do |i|
|
|
141
154
|
_key = key.cama_add_postfix_file_name("_#{i}")
|
|
142
|
-
break unless get_media_collection.
|
|
155
|
+
break unless get_media_collection.by_key(_key).any?
|
|
143
156
|
end
|
|
144
157
|
end
|
|
145
158
|
_key
|