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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6940e13676d23d32f7eddfc3cabe9cc6639914fc79f5d53497040dc2987fbfb
|
|
4
|
+
data.tar.gz: 8079bcd83ef28f77f628ad33fbc0bc89f17e2f6ae130868f3d49f112b4cfd1e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99302745a2adbd3a0bce4c9e85153bd6d216f5fd3d6ed67c038b461e465339de41e386b114d1075dd1471fca07c70c72c8492a5e9fcae28bf49fcc3f91c71389
|
|
7
|
+
data.tar.gz: e5ee5c70326b92c29e9ca02be583930d1e2a1bc9a6308cf0bc90a090a29a2c2e4b9417ce9958065561b8d5844146844544d341464c268f2ff40baec939dc4b6c
|
data/README.md
CHANGED
|
@@ -81,14 +81,13 @@ https://camaleon.website/store/plugins
|
|
|
81
81
|
* Image/iFrame Lazy Loader - https://github.com/brian-kephart/camaleon_lazy_loader
|
|
82
82
|
* Import / Export - https://github.com/owen2345/camaleon_export_import
|
|
83
83
|
* Lightbox - https://github.com/owen2345/CamaImageLightbox
|
|
84
|
-
* Autocomplete - https://github.com/gaelfokou/cama_autocomplete
|
|
85
84
|
* Permissions for External Menus - https://github.com/owen2345/cama_external_menu
|
|
86
85
|
* TinyMCE Template Integration - https://github.com/owen2345/Camaleon-Tinymce-Templates
|
|
87
86
|
* Download Manager - https://github.com/max2320/camaleon-download
|
|
88
87
|
* OAuth - https://github.com/owen2345/camaleon_oauth
|
|
89
|
-
* Visual Editor - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon_editor
|
|
90
|
-
* Spree Commerce Integration - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon-spree
|
|
91
|
-
* Admin AJAX - Paid Plugin ($) - https://camaleon.website/store/plugins/admin_ajax
|
|
88
|
+
* Visual Editor - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon_editor (Github: https://github.com/owen2345/camaleon_editor)
|
|
89
|
+
* Spree Commerce Integration - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon-spree (Github: https://github.com/owen2345/camaleon-spree)
|
|
90
|
+
* Admin AJAX - Paid Plugin ($) - https://camaleon.website/store/plugins/admin_ajax (Github: https://github.com/owen2345/admin-ajax)
|
|
92
91
|
* **See here for a complete Gemfile**: https://github.com/owen2345/camaleon-cms/blob/master/docs/example_gemfile.rb
|
|
93
92
|
|
|
94
93
|
## Camaleon CMS has many useful frontend Themes such as:
|
|
@@ -190,6 +189,13 @@ RAILS_ENV=test bundle exec rake app:db:test:prepare
|
|
|
190
189
|
|
|
191
190
|
See [docs/security/permissions.md](docs/security/permissions.md) for details on manager permissions, custom fields, and security considerations.
|
|
192
191
|
|
|
192
|
+
## Hooks
|
|
193
|
+
|
|
194
|
+
Plugins and themes extend Camaleon by handling named hooks. See [docs/hooks.md](docs/hooks.md) for the
|
|
195
|
+
mechanism (registration via `config.json`, the mutable `args` payload) and a code-derived inventory of the
|
|
196
|
+
hooks core fires, including the session/auth hooks (`user_before_login`, `user_before_register`,
|
|
197
|
+
`after_login`, …).
|
|
198
|
+
|
|
193
199
|
## Contributing
|
|
194
200
|
* Fork it.
|
|
195
201
|
* Create a branch (git checkout -b my_feature_branch)
|
|
@@ -197,6 +203,13 @@ See [docs/security/permissions.md](docs/security/permissions.md) for details on
|
|
|
197
203
|
* Push to the branch (git push origin my_feature_branch)
|
|
198
204
|
* Create a pull request from your branch into master (Please be sure to provide enough detail for us to understand what this change is doing)
|
|
199
205
|
|
|
206
|
+
## Releasing
|
|
207
|
+
|
|
208
|
+
Releases are cut by the manually-started **Release** GitHub Actions workflow, which builds the gem,
|
|
209
|
+
publishes it to RubyGems, tags the commit and creates the GitHub release. See
|
|
210
|
+
[docs/releasing.md](docs/releasing.md) for the full procedure, what the workflow checks before it
|
|
211
|
+
publishes, and the fully manual fallback.
|
|
212
|
+
|
|
200
213
|
## Version History
|
|
201
214
|
|
|
202
215
|
https://camaleon.website/version-history.html
|
data/Rakefile
CHANGED
|
@@ -4,26 +4,13 @@ rescue LoadError
|
|
|
4
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
require 'rdoc/task'
|
|
8
|
-
|
|
9
|
-
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
-
rdoc.title = 'CamaleonCms'
|
|
12
|
-
rdoc.options << '--line-numbers'
|
|
13
|
-
rdoc.rdoc_files.include('README.rdoc')
|
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
-
end
|
|
16
|
-
|
|
17
7
|
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
|
18
8
|
load 'rails/tasks/engine.rake'
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
|
|
10
|
+
# Deliberately no Bundler::GemHelper.install_tasks: its `rake release` would tag `v<version>` and
|
|
11
|
+
# push a gem that none of the release checks had seen. Releases go through the Release workflow —
|
|
12
|
+
# see docs/releasing.md, which also covers building and publishing by hand.
|
|
25
13
|
|
|
26
|
-
require 'rspec/core'
|
|
27
14
|
require 'rspec/core/rake_task'
|
|
28
15
|
|
|
29
16
|
desc 'Run all specs in spec directory (excluding plugin specs)'
|
|
@@ -6,12 +6,15 @@ module Plugins
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def save_settings
|
|
9
|
-
current_site.set_meta(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
current_site.set_meta(
|
|
10
|
+
'attack_config', {
|
|
11
|
+
get: { sec: params[:attack][:get_sec], max: params[:attack][:get_max] },
|
|
12
|
+
post: { sec: params[:attack][:post_sec] || 20, max: params[:attack][:post_max] },
|
|
13
|
+
msg: params[:attack][:msg],
|
|
14
|
+
ban: params[:attack][:ban],
|
|
15
|
+
cleared: Time.zone.now.iso8601
|
|
16
|
+
}
|
|
17
|
+
)
|
|
15
18
|
flash[:notice] = t('plugin.attack.messages.settings_saved').to_s
|
|
16
19
|
redirect_to action: :settings
|
|
17
20
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module Plugins
|
|
2
2
|
module Attack
|
|
3
3
|
module AttackHelper
|
|
4
|
-
#
|
|
4
|
+
# Here all actions on the plugin are destroyed
|
|
5
5
|
# plugin: plugin model
|
|
6
6
|
def attack_on_destroy(_plugin)
|
|
7
7
|
current_site.attack.destroy_all
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
# you can run
|
|
10
|
+
# Here all actions on going to active
|
|
11
|
+
# you can run SQL commands like this:
|
|
12
12
|
# results = ActiveRecord::Base.connection.execute(query);
|
|
13
13
|
# plugin: plugin model
|
|
14
14
|
def attack_on_active(_plugin)
|
|
@@ -16,15 +16,13 @@ module Plugins
|
|
|
16
16
|
post: { sec: 20, max: 5 },
|
|
17
17
|
msg: I18n.t('plugin.attack.form.request_limit_exceeded').to_s,
|
|
18
18
|
ban: 5,
|
|
19
|
-
cleared: Time.now })
|
|
19
|
+
cleared: Time.zone.now.iso8601 })
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
t.datetime 'created_at'
|
|
27
|
-
end
|
|
21
|
+
ActiveRecord::Base.connection.create_table :plugins_attacks, if_not_exists: true do |t|
|
|
22
|
+
t.string :path, index: true
|
|
23
|
+
t.string :browser_key, index: true
|
|
24
|
+
t.belongs_to :site, index: true
|
|
25
|
+
t.datetime 'created_at'
|
|
28
26
|
end
|
|
29
27
|
CamaleonCms::Site.class_eval do
|
|
30
28
|
has_many :attack, class_name: 'Plugins::Attack::Models::Attack'
|
|
@@ -38,9 +36,9 @@ module Plugins
|
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
def attack_app_before_load
|
|
41
|
-
cache_ban = Rails.cache.read(
|
|
39
|
+
cache_ban = Rails.cache.read(attack_ban_cache_key)
|
|
42
40
|
if cache_ban.present? # render banned message if it was banned
|
|
43
|
-
render html: cache_ban.
|
|
41
|
+
render html: cache_ban.to_s, layout: false
|
|
44
42
|
return
|
|
45
43
|
end
|
|
46
44
|
|
|
@@ -50,44 +48,56 @@ module Plugins
|
|
|
50
48
|
|
|
51
49
|
private
|
|
52
50
|
|
|
51
|
+
# Throttle and ban on the client IP, not the session id (audit finding H2). A cookieless /
|
|
52
|
+
# cookie-rotating request presented a fresh session id every time, so the per-key counter never
|
|
53
|
+
# accumulated: the throttle never tripped and its per-request DB insert ran unbounded. Keying on
|
|
54
|
+
# the IP makes the count accumulate, trip the limit, and stop inserting.
|
|
55
|
+
def attack_client_key
|
|
56
|
+
request.remote_ip.to_s
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def attack_ban_cache_key
|
|
60
|
+
"plugins_attack_ban:#{attack_client_key}"
|
|
61
|
+
end
|
|
62
|
+
|
|
53
63
|
def attack_check_request
|
|
54
64
|
return unless current_site
|
|
55
65
|
|
|
56
66
|
config = current_site.get_meta('attack_config')
|
|
57
|
-
|
|
58
|
-
return
|
|
67
|
+
query = current_site.attack.where(browser_key: attack_client_key, path: attack_request_key)
|
|
68
|
+
return if config.blank?
|
|
59
69
|
|
|
60
70
|
# clear past requests
|
|
61
71
|
if begin
|
|
62
|
-
Time.parse(config[:cleared])
|
|
72
|
+
Time.zone.parse(config[:cleared])
|
|
63
73
|
rescue StandardError
|
|
64
74
|
2.hours.ago
|
|
65
75
|
end < 1.hour.ago
|
|
66
76
|
current_site.attack.where('plugins_attacks.created_at < ?', 1.hour.ago).delete_all
|
|
67
|
-
config[:cleared] = Time.now.
|
|
77
|
+
config[:cleared] = Time.zone.now.iso8601 # Use ISO8601 for reliable parsing
|
|
68
78
|
current_site.set_meta('attack_config', config)
|
|
69
79
|
end
|
|
70
80
|
|
|
71
81
|
# post request
|
|
72
82
|
if request.post? || request.patch?
|
|
73
|
-
r =
|
|
83
|
+
r = query.where(created_at: config[:post][:sec].to_i.seconds.ago..Time.zone.now)
|
|
74
84
|
if r.count > config[:post][:max].to_i
|
|
75
|
-
Rails.cache.write(
|
|
76
|
-
#
|
|
77
|
-
render html: config[:msg].
|
|
85
|
+
Rails.cache.write(attack_ban_cache_key, config[:msg], expires_in: config[:ban].to_i.minutes)
|
|
86
|
+
# Email administrator with request info (ip, browser, if logged, then send user info
|
|
87
|
+
render html: config[:msg].to_s
|
|
78
88
|
return
|
|
79
89
|
end
|
|
80
90
|
|
|
81
91
|
# get request
|
|
82
92
|
else
|
|
83
|
-
r =
|
|
93
|
+
r = query.where(created_at: config[:get][:sec].to_i.seconds.ago..Time.zone.now)
|
|
84
94
|
if r.count > config[:get][:max].to_i
|
|
85
|
-
Rails.cache.write(
|
|
86
|
-
render html: config[:msg].
|
|
95
|
+
Rails.cache.write(attack_ban_cache_key, config[:msg], expires_in: config[:ban].to_i.minutes)
|
|
96
|
+
render html: config[:msg].to_s
|
|
87
97
|
return
|
|
88
98
|
end
|
|
89
99
|
end
|
|
90
|
-
|
|
100
|
+
query.create
|
|
91
101
|
end
|
|
92
102
|
|
|
93
103
|
def attack_request_key(method = nil)
|
|
@@ -24,27 +24,34 @@ module Plugins
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
26
|
def plugin_authoring_form_html(post)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
disabled = !(can?(:edit_other, post.post_type) && (can?(:edit_publish, post.post_type) || !post.published?))
|
|
28
|
+
|
|
29
|
+
label = tag.label(t('camaleon_cms.admin.table.author'), class: 'control-label')
|
|
30
|
+
select = content_tag(
|
|
31
|
+
:select,
|
|
32
|
+
safe_join(plugin_authoring_authors_list(post)),
|
|
33
|
+
id: 'post_user_id', name: 'post[user_id]',
|
|
34
|
+
class: 'form-control select valid',
|
|
35
|
+
disabled: disabled,
|
|
36
|
+
'aria-invalid' => 'false'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
tag.div(safe_join([label, select]), class: 'form-group')
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def plugin_authoring_authors_list(post)
|
|
42
43
|
author_id = post.new_record? ? cama_current_user.id : post.author.id
|
|
43
|
-
|
|
44
|
-
current_site.users.where('role <> ?', 'client').order(:username).
|
|
45
|
-
|
|
44
|
+
|
|
45
|
+
current_site.users.where('role <> ?', 'client').order(:username).map do |user|
|
|
46
|
+
selected = user.id == author_id
|
|
47
|
+
titleized_username = user.username.titleize
|
|
48
|
+
display = if user.fullname == titleized_username
|
|
49
|
+
titleized_username
|
|
50
|
+
else
|
|
51
|
+
"#{titleized_username} (#{user.fullname})"
|
|
52
|
+
end
|
|
53
|
+
tag.option(display, value: user.id, selected: selected)
|
|
46
54
|
end
|
|
47
|
-
ret
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
end
|
|
@@ -5,19 +5,22 @@ module Plugins
|
|
|
5
5
|
|
|
6
6
|
def settings
|
|
7
7
|
@caches = current_site.get_meta('front_cache_elements', { paths: [] })
|
|
8
|
-
@caches[:paths] << ''
|
|
8
|
+
@caches[:paths] << '' if @caches[:paths].blank?
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def save_settings
|
|
12
|
-
current_site.set_meta(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
current_site.set_meta(
|
|
13
|
+
'front_cache_elements',
|
|
14
|
+
{
|
|
15
|
+
paths: (params[:cache][:paths] || []).compact_blank || [],
|
|
16
|
+
posts: params[:cache][:posts] || [],
|
|
17
|
+
post_types: params[:cache][:post_type] || [],
|
|
18
|
+
skip_posts: params[:cache][:skip_posts] || [],
|
|
19
|
+
cache_login: params[:cache][:cache_login],
|
|
20
|
+
home: params[:cache][:home],
|
|
21
|
+
preserve_cache_on_restart: params[:cache][:preserve_cache_on_restart]
|
|
22
|
+
}
|
|
23
|
+
)
|
|
21
24
|
flash[:notice] = t('plugin.front_cache.message.settings_saved').to_s
|
|
22
25
|
redirect_to action: :settings
|
|
23
26
|
end
|
|
@@ -25,6 +28,7 @@ module Plugins
|
|
|
25
28
|
def clean_cache
|
|
26
29
|
flash[:notice] = t('plugin.front_cache.message.cache_destroyed').to_s
|
|
27
30
|
front_cache_clean
|
|
31
|
+
front_cache_purge_stored_pages
|
|
28
32
|
redirect_to(request.referer || '/admin/plugins')
|
|
29
33
|
end
|
|
30
34
|
end
|
|
@@ -13,7 +13,6 @@ en:
|
|
|
13
13
|
pages_of: 'Pages of'
|
|
14
14
|
skip_cache_pages: 'Skip Cache Pages'
|
|
15
15
|
preserve_cache_on_restart: 'Preserve cache on restart'
|
|
16
|
-
invalidate_only: 'Invalidate the cache instead of deleting it. (Recommended only if you use custom caching in your views. Not recommended when using FileStore, which is the Rails default cache)'
|
|
17
16
|
message:
|
|
18
17
|
please_checkpost_need_cache: 'Please check all posts or post types you need to cache.'
|
|
19
18
|
enabled_cache_inner_content_logged_users: 'Enable cache for inner content for logged users'
|
|
@@ -34,7 +33,6 @@ es:
|
|
|
34
33
|
pages_of: 'Página de'
|
|
35
34
|
skip_cache_pages: 'Saltar página del Caché'
|
|
36
35
|
preserve_cache_on_restart: 'Preservar caché al reiniciar'
|
|
37
|
-
invalidate_only: 'Invalide el caché en lugar de eliminarlo. (Recomendado sólo si utiliza el almacenamiento en caché personalizado en sus vistas No se recomienda cuando se utiliza FileStore, que es la caché predeterminada de Rails)'
|
|
38
36
|
message:
|
|
39
37
|
please_checkpost_need_cache: 'Por favor, compruebe todos los mensajes o los tipos de correos que necesita para almacenar en caché.'
|
|
40
38
|
enabled_cache_inner_content_logged_users: 'Habilitar caché de contenido interno para usuarios registrados'
|
|
@@ -56,7 +54,6 @@ nl:
|
|
|
56
54
|
pages_of: "Inhoud van"
|
|
57
55
|
skip_cache_pages: 'Cache overslaan'
|
|
58
56
|
preserve_cache_on_restart: 'Bewaar cache bij herstarten'
|
|
59
|
-
invalidate_only: 'De cache ongeldig maken, in plaats van het te verwijderen. (Alleen aanbevolen als u aangepaste caching gebruikt in uw weergaven. Niet aanbevolen bij het gebruik van FileStore, wat is de standaard cache van de Rails)'
|
|
60
57
|
message:
|
|
61
58
|
please_checkpost_need_cache: "Kijk alle berichten of inhoud type's na die je wil cachen."
|
|
62
59
|
enabled_cache_inner_content_logged_users: 'Activeer cache voor gelogde gebruikers'
|
|
@@ -77,7 +74,6 @@ pt-BR:
|
|
|
77
74
|
pages_of: 'Páginas de'
|
|
78
75
|
skip_cache_pages: 'Saltar Cache de Páginas'
|
|
79
76
|
preserve_cache_on_restart: 'Preserve o cache no reinício'
|
|
80
|
-
invalidate_only: 'Invalide o cache em vez de excluí-lo. (Recomendado apenas se você usar cache personalizado em suas visualizações. Não recomendado quando usar FileStore, que é o cache padrão do Rails)'
|
|
81
77
|
message:
|
|
82
78
|
please_checkpost_need_cache: 'Por favor verifique todas as publicações ou o tipo do publicação que precisa colocar em cache.'
|
|
83
79
|
enabled_cache_inner_content_logged_users: 'Permitir cache para conteúdo interno de utilizadores ligados'
|
|
@@ -98,7 +94,6 @@ pt-BR:
|
|
|
98
94
|
pages_of: 'Páginas de'
|
|
99
95
|
skip_cache_pages: 'Pular Cache de Páginas'
|
|
100
96
|
preserve_cache_on_restart: 'Preserve o cache no reinício'
|
|
101
|
-
invalidate_only: 'Invalide o cache em vez de excluí-lo. (Recomendado apenas se você usar cache personalizado em suas visualizações. Não recomendado quando usar FileStore, que é o cache padrão do Rails)'
|
|
102
97
|
message:
|
|
103
98
|
please_checkpost_need_cache: 'Por favor cheque todos os posts ou o tipo do post que você precisa cachear.'
|
|
104
99
|
enabled_cache_inner_content_logged_users: 'Permitir cache para conteúdo interno de usuários logados'
|
|
@@ -119,7 +114,6 @@ it:
|
|
|
119
114
|
pages_of: 'Pagine di'
|
|
120
115
|
skip_cache_pages: 'Pagine da non cachare'
|
|
121
116
|
preserve_cache_on_restart: 'Conserva la cache al riavvio'
|
|
122
|
-
invalidate_only: 'Invalidate la cache invece di cancellarla. (Consigliato solo se si utilizza la cache personalizzata nelle tue viste. Non è consigliata quando si utilizza FileStore, che è la cache predefinita Rails)'
|
|
123
117
|
message:
|
|
124
118
|
please_checkpost_need_cache: 'Per favore, verifica tutti i tipi di messaggi o e-mail che è necessario memorizzare nella cache.'
|
|
125
119
|
enabled_cache_inner_content_logged_users: 'Attiva il contenuto della cache interna per gli utenti registrati'
|
|
@@ -140,7 +134,6 @@ zh-CN:
|
|
|
140
134
|
pages_of: '页'
|
|
141
135
|
skip_cache_pages: '跳过缓存页面'
|
|
142
136
|
preserve_cache_on_restart: '重新启动时保存缓存'
|
|
143
|
-
invalidate_only: '使缓存无效,而不是删除它。 (仅当您在视图中使用自定义缓存时才推荐使用FileStore时,建议不要使用Rails默认缓存)'
|
|
144
137
|
message:
|
|
145
138
|
please_checkpost_need_cache: '请检查您需要缓存位内容.'
|
|
146
139
|
enabled_cache_inner_content_logged_users: '为已登录的用户启用内部内容的缓存'
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
module Plugins
|
|
2
2
|
module FrontCache
|
|
3
3
|
module FrontCacheHelper
|
|
4
|
+
# Upper bound on any stored page's life. Stores whose purge is rescued away (RedisCacheStore
|
|
5
|
+
# and MemCacheStore reject the matcher) would otherwise keep retired or never-revisited
|
|
6
|
+
# entries forever — and Redis's default maxmemory-policy is noeviction, so TTL-less bodies
|
|
7
|
+
# would grow until the shared store refuses writes. An expired entry is an ordinary miss: the
|
|
8
|
+
# page is re-rendered and re-cached.
|
|
9
|
+
FRONT_CACHE_EXPIRATION = 1.week
|
|
4
10
|
# cache all pages configured in this plugin's settings for public users
|
|
5
11
|
def front_cache_front_before_load
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
# invalidate the site's cached pages on the first request after a (re)start, unless
|
|
13
|
+
# preserve_cache_on_restart is checked in the plugin settings
|
|
14
|
+
if current_site.get_option('refresh_cache')
|
|
15
|
+
front_cache_clean unless (current_site.get_meta('front_cache_elements') || {})[:preserve_cache_on_restart]
|
|
8
16
|
current_site.set_option('refresh_cache', false)
|
|
9
17
|
end
|
|
10
18
|
|
|
@@ -12,28 +20,45 @@ module Plugins
|
|
|
12
20
|
return if signin? || Rails.env.development? || Rails.env.test? || !request.get?
|
|
13
21
|
|
|
14
22
|
cache_key = front_cache_plugin_cache_key
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
# Fail closed on a missing settings meta: front_cache runs on every frontend request, so an
|
|
24
|
+
# absent front_cache_elements (never seeded, or hand-deleted) must degrade to "cache nothing"
|
|
25
|
+
# rather than raise NoMethodError and 500 the whole public site.
|
|
26
|
+
@caches = current_site.get_meta('front_cache_elements') || {}
|
|
27
|
+
# Single read: the old exist?-then-get pair issued two store reads, and an entry vanishing
|
|
28
|
+
# between them (a concurrent admin purge, TTL expiry) left .gsub running on nil — a
|
|
29
|
+
# visitor-facing 500.
|
|
30
|
+
cached_body = flash.keys.blank? ? front_cache_get(cache_key) : nil
|
|
31
|
+
if cached_body # recover cache item
|
|
17
32
|
Rails.logger.info "Camaleon CMS - readed cache: #{front_cache_plugin_get_path(cache_key)}"
|
|
18
33
|
response.headers['PLUGIN_FRONT_CACHE'] = 'TRUE'
|
|
19
|
-
args = { data:
|
|
34
|
+
args = { data: cached_body.gsub('{{form_authenticity_token}}', form_authenticity_token) }
|
|
20
35
|
hooks_run('front_cache_reading_cache', args)
|
|
36
|
+
# rubocop:disable Rails/OutputSafety -- This replays a trusted cached page body that was already rendered by Rails.
|
|
21
37
|
render html: args[:data].html_safe
|
|
38
|
+
# rubocop:enable Rails/OutputSafety
|
|
22
39
|
return
|
|
23
40
|
end
|
|
24
41
|
|
|
25
42
|
@_plugin_do_cache = false
|
|
26
|
-
|
|
43
|
+
# cache paths and home page
|
|
44
|
+
paths = @caches[:paths] || []
|
|
45
|
+
if paths.include?(request.original_url) || paths.include?(request.path_info) ||
|
|
46
|
+
front_cache_plugin_match_path_patterns?(request.original_url, request.path_info) ||
|
|
47
|
+
(params[:action] == 'index' && params[:controller] == 'camaleon_cms/frontend' && @caches[:home].present?)
|
|
27
48
|
@_plugin_do_cache = true
|
|
28
|
-
elsif params[:action] == 'post' && params[:controller] == 'camaleon_cms/frontend' &&
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
elsif params[:action] == 'post' && params[:controller] == 'camaleon_cms/frontend' && params[:draft_id].blank?
|
|
50
|
+
# the_post is a single-record lookup (eager: false) -- no listing preloads for one post
|
|
51
|
+
# Never cache non-public posts. A password-protected post is unlocked per session (visibility_post
|
|
52
|
+
# audit M2), but the page cache is keyed on the URL alone, so caching an unlocked render would
|
|
53
|
+
# serve the protected body to visitors who never entered the password. Private posts are already
|
|
54
|
+
# excluded; password posts must be too.
|
|
55
|
+
if (post = current_site.the_post(params[:slug])) && post.can_visit? && !%w[private
|
|
56
|
+
password].include?(post.visibility)
|
|
57
|
+
if (@caches[:skip_posts] || []).include?(post.id.to_s)
|
|
58
|
+
@_plugin_do_cache = false
|
|
59
|
+
elsif (@caches[:post_types] || []).include?(post.post_type_id.to_s) ||
|
|
60
|
+
(@caches[:posts] || []).include?(post.id.to_s)
|
|
61
|
+
@_plugin_do_cache = true
|
|
37
62
|
end
|
|
38
63
|
end
|
|
39
64
|
end
|
|
@@ -43,7 +68,7 @@ module Plugins
|
|
|
43
68
|
|
|
44
69
|
def front_cache_front_after_load
|
|
45
70
|
cache_key = front_cache_plugin_cache_key
|
|
46
|
-
return unless @_plugin_do_cache &&
|
|
71
|
+
return unless @_plugin_do_cache && flash.keys.blank?
|
|
47
72
|
|
|
48
73
|
body =
|
|
49
74
|
response
|
|
@@ -69,8 +94,7 @@ module Plugins
|
|
|
69
94
|
post_types: [current_site.post_types.where(slug: 'page').first.id],
|
|
70
95
|
skip_posts: [],
|
|
71
96
|
home: true,
|
|
72
|
-
cache_login: true
|
|
73
|
-
cache_counter: 0
|
|
97
|
+
cache_login: true
|
|
74
98
|
}
|
|
75
99
|
)
|
|
76
100
|
end
|
|
@@ -91,56 +115,104 @@ module Plugins
|
|
|
91
115
|
arg[:links] << link_to(t('plugin.front_cache.clean_cache'), admin_plugins_front_cache_clean_path)
|
|
92
116
|
end
|
|
93
117
|
|
|
94
|
-
#
|
|
118
|
+
# invalidate the page cache on any content-changing request. PUT and DELETE count: a permanent
|
|
119
|
+
# post deletion rides DELETE (Rack::MethodOverride makes request.post? false for
|
|
120
|
+
# _method=delete forms), and without a bump the deleted page kept being served from cache.
|
|
95
121
|
def front_cache_post_requests
|
|
96
|
-
return unless request.post? || request.patch?
|
|
122
|
+
return unless request.post? || request.put? || request.patch? || request.delete?
|
|
123
|
+
# A draft autosave (posts/drafts) writes a private draft_child buffer, never published
|
|
124
|
+
# output, so it must not retire the public page cache — otherwise an open editor's
|
|
125
|
+
# per-minute autosave keeps the whole site's cache from ever warming.
|
|
126
|
+
return if params[:controller].to_s.end_with?('posts/drafts')
|
|
97
127
|
|
|
98
128
|
front_cache_clean
|
|
99
129
|
end
|
|
100
130
|
|
|
101
|
-
#
|
|
131
|
+
# invalidate all cached pages of the current site
|
|
102
132
|
def front_cache_clean
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
# Security: never Rails.cache.clear — the store is shared, and clearing it on every POST also
|
|
134
|
+
# destroyed unrelated entries such as the per-IP login brute-force counter (CaptchaHelper),
|
|
135
|
+
# silently defeating it. Bumping the version compared on every page-cache read (ActiveSupport
|
|
136
|
+
# `version:`) retires all of this site's pages on any store, with no store enumeration.
|
|
137
|
+
#
|
|
138
|
+
# The version lives in its own meta key, apart from the front_cache_elements settings hash:
|
|
139
|
+
# save_settings rewrites that hash wholesale from an earlier read, so a version stored inside
|
|
140
|
+
# it could be reverted by a settings save racing a concurrent bump — and a reverted version
|
|
141
|
+
# resurrects a retired generation as servable.
|
|
142
|
+
current_site.set_meta('front_cache_counter', front_cache_version + 1)
|
|
111
143
|
end
|
|
112
144
|
|
|
113
145
|
private
|
|
114
146
|
|
|
147
|
+
# Current page-cache version of the site. `.to_i` covers a site that has never invalidated
|
|
148
|
+
# (missing meta) as version 0 — it must never be nil: an entry written with `version: nil`
|
|
149
|
+
# would match ANY requested version and become immune to invalidation.
|
|
150
|
+
def front_cache_version
|
|
151
|
+
current_site.get_meta('front_cache_counter').to_i
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Physically remove every stored page entry of the current site. Only the explicit admin
|
|
155
|
+
# "Clean cache" action calls this — per-request invalidation is the version bump alone, so the
|
|
156
|
+
# request path never pays a store walk. The pattern is ^-anchored on purpose: with a store
|
|
157
|
+
# :namespace, ActiveSupport's key_matcher recognizes only a leading ^ (composing /^ns:<rest>/);
|
|
158
|
+
# any other source becomes /^ns:.*<source>/, where \A can never match, silently deleting
|
|
159
|
+
# nothing. Keys are single-line, so ^ cannot over-match. Best-effort: delete_matched is
|
|
160
|
+
# optional store API (MemCacheStore raises NotImplementedError, RedisCacheStore accepts only
|
|
161
|
+
# glob Strings, third-party stores raise their own classes) and the version bump has already
|
|
162
|
+
# retired the pages, so any store error is logged and swallowed.
|
|
163
|
+
def front_cache_purge_stored_pages
|
|
164
|
+
Rails.cache.delete_matched(%r{^cama_front_cache/#{current_site.id}/})
|
|
165
|
+
rescue StandardError, NotImplementedError => e
|
|
166
|
+
Rails.logger.warn "Camaleon CMS - front_cache purge skipped: #{e.class}: #{e.message}"
|
|
167
|
+
end
|
|
168
|
+
|
|
115
169
|
def front_cache_exist?(key)
|
|
116
|
-
!
|
|
170
|
+
!front_cache_get(key).nil?
|
|
117
171
|
end
|
|
118
172
|
|
|
119
173
|
def front_cache_get(key)
|
|
120
|
-
Rails.cache.read(front_cache_plugin_get_path(key))
|
|
174
|
+
Rails.cache.read(front_cache_plugin_get_path(key), version: front_cache_version)
|
|
121
175
|
end
|
|
122
176
|
|
|
177
|
+
# One entry per URL: the version is stored inside the entry, so re-caching a page after an
|
|
178
|
+
# invalidation overwrites the retired body in place on every store — storage is bounded at one
|
|
179
|
+
# entry per cached URL instead of one per URL per invalidation.
|
|
123
180
|
def front_cache_plugin_cache_create(key, content)
|
|
124
|
-
Rails.cache.write(front_cache_plugin_get_path(key), content
|
|
181
|
+
Rails.cache.write(front_cache_plugin_get_path(key), content,
|
|
182
|
+
version: front_cache_version, expires_in: FRONT_CACHE_EXPIRATION)
|
|
125
183
|
end
|
|
126
184
|
|
|
127
|
-
# return the
|
|
185
|
+
# return the cache key of a stored page
|
|
128
186
|
# key: (string, optional) the key of the cached page
|
|
129
187
|
def front_cache_plugin_get_path(key = nil)
|
|
130
188
|
if key.nil?
|
|
131
|
-
"
|
|
189
|
+
"cama_front_cache/#{current_site.id}"
|
|
132
190
|
else
|
|
133
|
-
"
|
|
191
|
+
"cama_front_cache/#{current_site.id}/#{key}"
|
|
134
192
|
end
|
|
135
193
|
end
|
|
136
194
|
|
|
137
195
|
def front_cache_plugin_match_path_patterns?(key, key2)
|
|
138
|
-
|
|
196
|
+
front_cache_compiled_path_patterns.any? { |pattern| key =~ pattern || key2 =~ pattern }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Security (audit Low): compile the admin-configured patterns once per request and skip any that
|
|
200
|
+
# are malformed -- Regexp.new was rebuilt on every request and raised (500) on an invalid pattern
|
|
201
|
+
# (RegexpError) or a non-String pattern reachable via crafted settings params (TypeError).
|
|
202
|
+
def front_cache_compiled_path_patterns
|
|
203
|
+
@front_cache_compiled_path_patterns ||= (@caches[:paths] || []).filter_map do |pattern|
|
|
204
|
+
Regexp.new(pattern)
|
|
205
|
+
rescue RegexpError, TypeError
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
139
208
|
end
|
|
140
209
|
|
|
141
210
|
def front_cache_plugin_cache_key
|
|
142
211
|
uri = [request.protocol + request.host_with_port, request.fullpath].join('/')
|
|
143
|
-
|
|
212
|
+
# Security (audit Low): a lossless key. parameterize lowercased and collapsed every run of
|
|
213
|
+
# non-alphanumerics to '-', so distinct URLs shared one cache entry -- a visitor to one
|
|
214
|
+
# cacheable URL could be served the cached body of another.
|
|
215
|
+
Digest::SHA256.hexdigest(uri)
|
|
144
216
|
end
|
|
145
217
|
end
|
|
146
218
|
end
|
|
@@ -61,11 +61,6 @@
|
|
|
61
61
|
<%= check_box_tag("cache[preserve_cache_on_restart]", 1, @caches[:preserve_cache_on_restart]) %>
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
|
-
<div class="form-group">
|
|
65
|
-
<label><%= t('plugin.front_cache.invalidate_only') %></label><br>
|
|
66
|
-
<%= check_box_tag("cache[invalidate_only]", 1, @caches[:invalidate_only]) %>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
64
|
<!--<div class="form-group">-->
|
|
70
65
|
<!--<label><%#= t('plugin.front_cache.message.enabled_cache_inner_content_logged_users') %></label><br>-->
|
|
71
66
|
<!--<%#= check_box_tag("cache[cache_login]", 1, @caches[:cache_login]) %>-->
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"created_post":["plugin_visibility_create_post"],
|
|
18
18
|
"updated_post":["plugin_visibility_create_post"],
|
|
19
19
|
"post_the_content": ["plugin_visibility_post_the_content"],
|
|
20
|
+
"post_the_excerpt": ["plugin_visibility_post_the_excerpt"],
|
|
20
21
|
"filter_post": ["plugin_visibility_filter_post"],
|
|
21
22
|
"post_can_visit": ["plugin_visibility_can_visit"],
|
|
22
23
|
"list_post_extra_columns": ["plugin_visibility_extra_columns"]
|