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.
- checksums.yaml +4 -4
- data/README.md +29 -9
- 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 +16 -10
- data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
- data/app/apps/plugins/front_cache/front_cache_helper.rb +57 -24
- 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/category.html.erb +1 -1
- data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
- data/app/apps/themes/default/views/post_tag.html.erb +1 -1
- data/app/apps/themes/default/views/post_type.html.erb +1 -1
- data/app/apps/themes/default/views/search.html.erb +1 -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/category.html.erb +1 -1
- data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
- data/app/apps/themes/new/views/post_tag.html.erb +1 -1
- data/app/apps/themes/new/views/post_type.html.erb +1 -1
- data/app/apps/themes/new/views/search.html.erb +1 -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 +29 -10
- data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
- data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +9 -2
- data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +4 -3
- data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +2 -2
- data/app/controllers/camaleon_cms/admin/categories_controller.rb +27 -8
- 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 +84 -19
- data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
- data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +15 -6
- data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
- data/app/controllers/camaleon_cms/admin/posts_controller.rb +58 -22
- data/app/controllers/camaleon_cms/admin/sessions_controller.rb +238 -45
- data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +103 -18
- data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +17 -5
- data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +33 -7
- data/app/controllers/camaleon_cms/admin/settings_controller.rb +11 -6
- data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +9 -7
- data/app/controllers/camaleon_cms/admin/users_controller.rb +89 -18
- data/app/controllers/camaleon_cms/admin_controller.rb +144 -26
- data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +5 -0
- data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
- data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
- data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +44 -0
- 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 +67 -51
- data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
- data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
- 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 +21 -24
- 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 +51 -31
- 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 +87 -41
- 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/content_select_helper.rb +34 -20
- data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +144 -67
- 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 +73 -38
- data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
- data/app/helpers/camaleon_cms/session_helper.rb +195 -39
- data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
- data/app/helpers/camaleon_cms/site_helper.rb +43 -9
- data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
- data/app/helpers/camaleon_cms/uploader_helper.rb +19 -422
- 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 +65 -105
- data/app/models/camaleon_cms/category.rb +7 -5
- data/app/models/camaleon_cms/custom_field.rb +29 -6
- data/app/models/camaleon_cms/custom_field_group.rb +75 -21
- data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -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 +6 -2
- data/app/models/camaleon_cms/nav_menu_item.rb +18 -8
- data/app/models/camaleon_cms/plugin.rb +4 -3
- data/app/models/camaleon_cms/post.rb +113 -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 -3
- data/app/models/camaleon_cms/post_type.rb +22 -12
- data/app/models/camaleon_cms/site.rb +40 -28
- data/app/models/camaleon_cms/term_relationship.rb +10 -7
- data/app/models/camaleon_cms/term_taxonomy.rb +51 -5
- data/app/models/camaleon_cms/theme.rb +3 -3
- data/app/models/camaleon_cms/user.rb +17 -5
- data/app/models/camaleon_cms/user_role.rb +77 -4
- data/app/models/camaleon_cms/widget/assigned.rb +11 -6
- data/app/models/camaleon_cms/widget/main.rb +13 -7
- data/app/models/camaleon_cms/widget/sidebar.rb +3 -6
- data/app/models/camaleon_record.rb +123 -5
- data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
- data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
- data/app/models/concerns/camaleon_cms/custom_fields_read.rb +115 -58
- data/app/models/concerns/camaleon_cms/metas.rb +32 -7
- 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 +23 -0
- data/app/uploaders/camaleon_cms_aws_uploader.rb +39 -11
- 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 +21 -8
- 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/appearances/nav_menus/_left_menu_items.html.erb +2 -2
- data/app/views/camaleon_cms/admin/appearances/widgets/main/form.html.erb +1 -1
- data/app/views/camaleon_cms/admin/categories/index.html.erb +3 -3
- data/app/views/camaleon_cms/admin/comments/index.html.erb +2 -2
- data/app/views/camaleon_cms/admin/comments/list.html.erb +1 -1
- 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 +3 -3
- data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +2 -2
- data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
- data/app/views/camaleon_cms/admin/posts/index.html.erb +6 -6
- data/app/views/camaleon_cms/admin/search.html.erb +1 -1
- 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 +42 -6
- 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/fields/_select_eval.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +5 -4
- data/app/views/camaleon_cms/admin/settings/custom_fields/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
- 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/settings/sites/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/user_roles/form.html.erb +102 -7
- data/app/views/camaleon_cms/admin/user_roles/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
- data/app/views/camaleon_cms/admin/users/index.html.erb +2 -2
- 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/_flash_messages.html.erb +2 -2
- 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 +3 -3
- data/config/locales/camaleon_cms/admin/ar.yml +7 -2
- data/config/locales/camaleon_cms/admin/de.yml +7 -2
- data/config/locales/camaleon_cms/admin/en.yml +39 -2
- data/config/locales/camaleon_cms/admin/es.yml +7 -2
- data/config/locales/camaleon_cms/admin/fr.yml +7 -2
- data/config/locales/camaleon_cms/admin/it.yml +7 -2
- data/config/locales/camaleon_cms/admin/nl.yml +8 -2
- data/config/locales/camaleon_cms/admin/pt-BR.yml +7 -2
- data/config/locales/camaleon_cms/admin/pt.yml +7 -2
- data/config/locales/camaleon_cms/admin/ru.yml +7 -2
- data/config/locales/camaleon_cms/admin/uk.yml +7 -2
- data/config/locales/camaleon_cms/admin/zh-CH.yml +7 -2
- data/config/routes/admin.rb +33 -15
- data/config/routes/frontend.rb +21 -15
- data/db/migrate/20150611161134_post_table_into_utf8.rb +14 -14
- data/db/migrate/20150926095310_rename_column_posts.rb +3 -3
- data/db/migrate/20151212095328_add_confirm_token_to_users.rb +3 -3
- data/db/migrate/20160504155652_add_feature_to_posts.rb +1 -1
- data/db/migrate/20160504155653_move_first_name_of_users.rb +2 -2
- data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +1 -1
- data/db/migrate/20161215202255_drop_user_relationship_table.rb +1 -1
- data/db/migrate/20180124132318_create_media.rb +1 -1
- data/db/migrate/20180704211100_adjust_field_length.rb +1 -1
- 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 +47 -9
- data/lib/camaleon_cms/media_security_headers.rb +27 -0
- data/lib/camaleon_cms/setup_token.rb +69 -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 +11 -0
- data/lib/ext/hash.rb +28 -2
- data/lib/ext/string.rb +13 -8
- 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 +58 -0
- 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 +185 -7
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class Site < CamaleonCms::TermTaxonomy
|
|
3
|
+
normalize_attrs(:description)
|
|
4
|
+
|
|
3
5
|
include CamaleonCms::SiteDefaultSettings
|
|
4
6
|
|
|
5
7
|
# attrs: [name, description, slug]
|
|
6
8
|
attr_accessor :site_domain
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
# Transient (never persisted): when default_settings auto-provisions the
|
|
11
|
+
# administrator, it stashes the randomly generated plaintext password here so
|
|
12
|
+
# the provisioning controller can surface it once. nil when no admin was minted
|
|
13
|
+
# (e.g. a shared admin already existed). See harden-installer-default-admin.
|
|
14
|
+
attr_accessor :generated_admin_password
|
|
9
15
|
|
|
10
|
-
has_many :post_types, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id, dependent: :destroy
|
|
16
|
+
has_many :post_types, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id, dependent: :destroy,
|
|
17
|
+
inverse_of: :site
|
|
11
18
|
has_many :nav_menus, class_name: 'CamaleonCms::NavMenu', foreign_key: :parent_id, dependent: :destroy,
|
|
12
19
|
inverse_of: :site
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
has_many :
|
|
16
|
-
|
|
20
|
+
# inverse_of: false — this joins by term_group (the site id), while NavMenuItem#parent
|
|
21
|
+
# reads parent_id (the menu id); an inverse here overwrites items' real menu parent.
|
|
22
|
+
has_many :nav_menu_items, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :term_group, inverse_of: false,
|
|
23
|
+
dependent: :destroy
|
|
24
|
+
has_many :widgets, class_name: 'CamaleonCms::Widget::Main', foreign_key: :parent_id, dependent: :destroy,
|
|
25
|
+
inverse_of: :site
|
|
26
|
+
has_many :sidebars, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :parent_id, dependent: :destroy,
|
|
27
|
+
inverse_of: :site
|
|
28
|
+
has_many :user_roles_rel, class_name: 'CamaleonCms::UserRole', foreign_key: :parent_id, dependent: :destroy,
|
|
29
|
+
inverse_of: :site
|
|
17
30
|
has_many :custom_field_groups, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :parent_id,
|
|
18
|
-
dependent: :destroy
|
|
19
|
-
has_many :term_taxonomies, class_name: 'CamaleonCms::TermTaxonomy', foreign_key: :parent_id
|
|
31
|
+
dependent: :destroy, inverse_of: :parent
|
|
32
|
+
has_many :term_taxonomies, class_name: 'CamaleonCms::TermTaxonomy', foreign_key: :parent_id, inverse_of: :parent,
|
|
33
|
+
dependent: :destroy
|
|
20
34
|
|
|
21
35
|
has_many :posts, through: :post_types, source: :posts
|
|
22
|
-
has_many :plugins, class_name: 'CamaleonCms::Plugin', foreign_key: :parent_id, dependent: :destroy
|
|
23
|
-
|
|
24
|
-
has_many :
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
has_many :plugins, class_name: 'CamaleonCms::Plugin', foreign_key: :parent_id, dependent: :destroy,
|
|
37
|
+
inverse_of: :site
|
|
38
|
+
has_many :themes, class_name: 'CamaleonCms::Theme', foreign_key: :parent_id, dependent: :destroy, inverse_of: :site
|
|
39
|
+
has_many :public_media, -> { where(is_public: true) }, class_name: 'CamaleonCms::Media', foreign_key: :site_id,
|
|
40
|
+
dependent: :destroy, inverse_of: :site
|
|
41
|
+
has_many :private_media, -> { where(is_public: false) }, class_name: 'CamaleonCms::Media', foreign_key: :site_id,
|
|
42
|
+
dependent: :destroy, inverse_of: :site
|
|
28
43
|
|
|
29
44
|
after_create :default_settings
|
|
30
45
|
after_create :set_default_user_roles
|
|
@@ -33,7 +48,7 @@ module CamaleonCms
|
|
|
33
48
|
before_destroy :destroy_site
|
|
34
49
|
after_destroy :refresh_routes
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
validates :slug, uniqueness: { scope: :taxonomy }
|
|
37
52
|
|
|
38
53
|
# all user roles for this site
|
|
39
54
|
def user_roles
|
|
@@ -77,7 +92,7 @@ module CamaleonCms
|
|
|
77
92
|
options[:_admin_theme] || 'en'
|
|
78
93
|
end
|
|
79
94
|
|
|
80
|
-
#
|
|
95
|
+
# Set the current admin language for this site
|
|
81
96
|
def set_admin_language(language)
|
|
82
97
|
set_option('_admin_theme', language)
|
|
83
98
|
end
|
|
@@ -147,11 +162,9 @@ module CamaleonCms
|
|
|
147
162
|
|
|
148
163
|
# list all users of current site
|
|
149
164
|
def users
|
|
150
|
-
if PluginRoutes.system_info['users_share_sites']
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
CamaleonCms::User.where(site_id: id)
|
|
154
|
-
end
|
|
165
|
+
return CamaleonCms::User.all if PluginRoutes.system_info['users_share_sites']
|
|
166
|
+
|
|
167
|
+
CamaleonCms::User.where(site_id: id)
|
|
155
168
|
end
|
|
156
169
|
alias users_include_admins users
|
|
157
170
|
|
|
@@ -174,15 +187,13 @@ module CamaleonCms
|
|
|
174
187
|
def get_valid_post_slug(slug, post_id = nil)
|
|
175
188
|
slugs = slug.translations
|
|
176
189
|
if slugs.present?
|
|
177
|
-
slugs.each
|
|
178
|
-
slugs[k] = get_valid_post_slug(v)
|
|
179
|
-
end
|
|
190
|
+
slugs.each { |k, v| slugs[k] = get_valid_post_slug(v) }
|
|
180
191
|
slugs.to_translate
|
|
181
192
|
else
|
|
182
193
|
res = slug
|
|
183
194
|
(1..9999).each do |i|
|
|
184
|
-
p = posts.find_by_slug(res)
|
|
185
|
-
break if
|
|
195
|
+
p = posts.find_by_slug(res) # rubocop:disable Rails/DynamicFindBy
|
|
196
|
+
break if p.blank? || (p.present? && p.id == post_id)
|
|
186
197
|
|
|
187
198
|
res = "#{slug}-#{i}"
|
|
188
199
|
end
|
|
@@ -209,7 +220,7 @@ module CamaleonCms
|
|
|
209
220
|
# if the anonymous user not exist, will create one
|
|
210
221
|
def get_anonymous_user
|
|
211
222
|
user = users.where(username: 'anonymous').first
|
|
212
|
-
|
|
223
|
+
if user.blank?
|
|
213
224
|
pass = "anonymous#{rand(9999)}"
|
|
214
225
|
user = users.create({ email: 'anonymous_user@local.com', username: 'anonymous', password: pass,
|
|
215
226
|
password_confirmation: pass, first_name: 'Anonymous' })
|
|
@@ -219,7 +230,8 @@ module CamaleonCms
|
|
|
219
230
|
|
|
220
231
|
# return the domain for current site
|
|
221
232
|
# sample: mysite.com | sample.mysite.com
|
|
222
|
-
# also, you can define custom domain for this site by: my_site.site_domain = 'my_site.com'
|
|
233
|
+
# also, you can define custom domain for this site by: my_site.site_domain = 'my_site.com'
|
|
234
|
+
# used for sites with different domains to call from console or task
|
|
223
235
|
def get_domain
|
|
224
236
|
@site_domain || (if main_site?
|
|
225
237
|
slug
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class TermRelationship < CamaleonRecord
|
|
3
3
|
self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}term_relationships"
|
|
4
|
+
|
|
4
5
|
default_scope -> { order(term_order: :asc) }
|
|
5
6
|
|
|
6
|
-
belongs_to :term_taxonomy, inverse_of: :term_relationships,
|
|
7
|
-
belongs_to :object,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
belongs_to :term_taxonomy, inverse_of: :term_relationships, optional: true
|
|
8
|
+
belongs_to :object, -> { order("#{CamaleonCms::Post.table_name}.id DESC") },
|
|
9
|
+
class_name: 'CamaleonCms::Post', foreign_key: :objectid, inverse_of: :term_relationships,
|
|
10
|
+
optional: true
|
|
10
11
|
|
|
11
|
-
# callbacks
|
|
12
12
|
after_create :update_count
|
|
13
13
|
before_destroy :update_count
|
|
14
14
|
|
|
15
15
|
private
|
|
16
16
|
|
|
17
|
-
#
|
|
17
|
+
# Update the counter of post-published items
|
|
18
18
|
# TODO verify counter
|
|
19
19
|
def update_count
|
|
20
|
-
|
|
20
|
+
return unless term_taxonomy&.try(:posts)
|
|
21
|
+
|
|
22
|
+
term_taxonomy
|
|
23
|
+
.update_column(:count, term_taxonomy.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
end
|
|
@@ -14,11 +14,56 @@ module CamaleonCms
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}term_taxonomy"
|
|
17
|
-
# attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order,
|
|
17
|
+
# attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order,
|
|
18
|
+
# :user_id
|
|
18
19
|
# attr_accessible :data_options
|
|
19
20
|
# attr_accessible :data_metas
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
self.inheritance_column = :taxonomy
|
|
23
|
+
|
|
24
|
+
def self.sti_name
|
|
25
|
+
name.demodulize.underscore
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.polymorphic_name
|
|
29
|
+
name.demodulize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.find_sti_class(type_name)
|
|
33
|
+
# Handle explicit exceptions where database string doesn't match namespacing layout
|
|
34
|
+
case type_name.to_s
|
|
35
|
+
when 'widget'
|
|
36
|
+
return CamaleonCms::Widget::Main
|
|
37
|
+
when 'sidebar'
|
|
38
|
+
return CamaleonCms::Widget::Sidebar
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Standard conversion for "site" -> "CamaleonCms::Site"
|
|
42
|
+
# or "nav_menu_item" -> "CamaleonCms::NavMenuItem"
|
|
43
|
+
klass = begin
|
|
44
|
+
"CamaleonCms::#{type_name.camelize}".constantize
|
|
45
|
+
rescue NameError
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
# The ancestry guard keeps a value like "meta" from instantiating an unrelated
|
|
49
|
+
# CamaleonCms class against the term_taxonomy table.
|
|
50
|
+
return klass if klass && klass <= base_class
|
|
51
|
+
|
|
52
|
+
# Runtime scan across loaded taxonomy models.
|
|
53
|
+
found = CamaleonCms::TermTaxonomy.descendants.find { |k| k.sti_name == type_name.to_s }
|
|
54
|
+
return found if found
|
|
55
|
+
|
|
56
|
+
# `super` resolves fully-qualified external classes (plugin STI outside the CamaleonCms
|
|
57
|
+
# namespace) and enforces ancestry — the scan above only sees classes already loaded, so
|
|
58
|
+
# without this an unloaded plugin taxonomy silently degraded to the base class. Same
|
|
59
|
+
# fallback PostDefault.find_sti_class keeps. Values it cannot place load as the base class,
|
|
60
|
+
# as every row did before native STI.
|
|
61
|
+
begin
|
|
62
|
+
super
|
|
63
|
+
rescue ActiveRecord::SubclassNotFound
|
|
64
|
+
base_class
|
|
65
|
+
end
|
|
66
|
+
end
|
|
22
67
|
|
|
23
68
|
# callbacks
|
|
24
69
|
before_validation :before_validating
|
|
@@ -29,11 +74,12 @@ module CamaleonCms
|
|
|
29
74
|
validates_with CamaleonCms::UniqValidator
|
|
30
75
|
|
|
31
76
|
# relations
|
|
32
|
-
has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
|
|
77
|
+
has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
|
|
33
78
|
dependent: :destroy
|
|
34
79
|
# has_many :posts, foreign_key: :objectid, through: :term_relationships, :source => :objects
|
|
35
|
-
belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy',
|
|
36
|
-
belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id,
|
|
80
|
+
belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy', optional: true
|
|
81
|
+
belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
|
|
82
|
+
inverse_of: false
|
|
37
83
|
|
|
38
84
|
# return all children taxonomy
|
|
39
85
|
# sample: sub categories of a category
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class Theme < CamaleonCms::TermTaxonomy
|
|
3
|
+
normalize_attrs(:description)
|
|
4
|
+
|
|
3
5
|
# attrs:
|
|
4
6
|
# slug => plugin key
|
|
5
|
-
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id,
|
|
6
|
-
|
|
7
|
-
default_scope { where(taxonomy: :theme) }
|
|
7
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true, inverse_of: :themes
|
|
8
8
|
|
|
9
9
|
before_validation :fix_name
|
|
10
10
|
before_destroy :destroy_custom_fields
|
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
if PluginRoutes.static_system_info['user_model'].blank?
|
|
2
2
|
module CamaleonCms
|
|
3
3
|
class User < CamaleonRecord
|
|
4
4
|
include CamaleonCms::UserMethods
|
|
5
5
|
|
|
6
|
-
self.table_name = PluginRoutes.static_system_info['cama_users_db_table'] ||
|
|
6
|
+
self.table_name = PluginRoutes.static_system_info['cama_users_db_table'] ||
|
|
7
|
+
"#{PluginRoutes.static_system_info['db_prefix']}users"
|
|
7
8
|
|
|
8
9
|
default_scope { order(role: :asc) }
|
|
9
10
|
|
|
11
|
+
# No `dependent:` — a deleted user's widgets keep rendering; only the owner is gone.
|
|
12
|
+
has_many :widgets, class_name: 'CamaleonCms::Widget::Main', # rubocop:disable Rails/HasManyOrHasOneDependent
|
|
13
|
+
inverse_of: :owner
|
|
14
|
+
|
|
10
15
|
validates :username, presence: true
|
|
11
|
-
|
|
16
|
+
# The following might be continued wit: , :unless => Proc.new { |a| a.auth_social.present? }
|
|
17
|
+
validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
|
|
12
18
|
has_secure_password
|
|
19
|
+
# Security (audit 2026-08-11 M15): has_secure_password only validates presence (on create) and the
|
|
20
|
+
# 72-byte bcrypt maximum, so a trivially short password was accepted. Enforce a minimum length
|
|
21
|
+
# whenever a password is actually set (create, reset, change). allow_blank leaves an update that
|
|
22
|
+
# submits no password untouched (a blank field is not a change), and lets has_secure_password's own
|
|
23
|
+
# presence check own the empty-on-create case.
|
|
24
|
+
validates :password, length: { minimum: 8 }, allow_blank: true
|
|
13
25
|
|
|
14
26
|
def self.find_by_email(email)
|
|
15
|
-
|
|
27
|
+
find_by(['lower(email) = ?', email.to_s.downcase])
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
def self.find_by_username(username)
|
|
19
|
-
|
|
31
|
+
find_by(['lower(username) = ?', username.to_s.downcase])
|
|
20
32
|
end
|
|
21
33
|
end
|
|
22
34
|
end
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class UserRole < CamaleonCms::TermTaxonomy
|
|
3
|
-
|
|
3
|
+
normalize_attrs(:description)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
def self.sti_name
|
|
6
|
+
'user_roles'
|
|
7
|
+
end
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
after_destroy :set_users_as_cilent
|
|
10
|
+
|
|
11
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true,
|
|
12
|
+
inverse_of: :user_roles_rel
|
|
8
13
|
|
|
9
14
|
def roles_post_type
|
|
10
15
|
get_meta('_post_type')
|
|
@@ -18,6 +23,23 @@ module CamaleonCms
|
|
|
18
23
|
term_group.nil?
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Users in a role slugged `admin` are administrators: `User#admin?` tests exactly this, and
|
|
27
|
+
# `Ability#initialize` answers `can :manage, :all` for them and returns before this role's
|
|
28
|
+
# `_post_type_`/`_manager_` metas are read. Those metas therefore describe nothing — every
|
|
29
|
+
# permission is held whatever they say.
|
|
30
|
+
def grants_all_permissions?
|
|
31
|
+
slug == 'admin'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Whether the permission checkboxes mean anything. False for the default admin role, which is not
|
|
35
|
+
# editable at all, and false for any other role slugged `admin`, whose permissions cannot be
|
|
36
|
+
# withheld from its users. The editor renders them locked and the controller declines to write
|
|
37
|
+
# them, so the two cannot disagree — and a locked checkbox, which the browser does not submit,
|
|
38
|
+
# can never silently clear what is stored.
|
|
39
|
+
def permissions_editable?
|
|
40
|
+
editable? && !grants_all_permissions?
|
|
41
|
+
end
|
|
42
|
+
|
|
21
43
|
ROLES = {
|
|
22
44
|
post_type: [
|
|
23
45
|
{
|
|
@@ -79,6 +101,14 @@ module CamaleonCms
|
|
|
79
101
|
label: I18n.t('camaleon_cms.admin.users.roles_values.html_manage_tags').to_s,
|
|
80
102
|
color: 'warning',
|
|
81
103
|
description: I18n.t('camaleon_cms.admin.users.tool_tip.manage_tags').to_s
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: 'post_content_unfiltered_html',
|
|
107
|
+
label: I18n.t('camaleon_cms.admin.users.roles_values.post_content_unfiltered_html',
|
|
108
|
+
default: 'Allow unfiltered HTML in post content').to_s,
|
|
109
|
+
color: 'danger',
|
|
110
|
+
description: I18n.t('camaleon_cms.admin.users.tool_tip.post_content_unfiltered_html',
|
|
111
|
+
default: 'Permit users with this role to save raw/unfiltered HTML in post content').to_s
|
|
82
112
|
}
|
|
83
113
|
],
|
|
84
114
|
manager: [
|
|
@@ -122,10 +152,53 @@ module CamaleonCms
|
|
|
122
152
|
label: I18n.t('camaleon_cms.admin.sidebar.settings').to_s,
|
|
123
153
|
description: I18n.t('camaleon_cms.admin.users.tool_tip.settings').to_s
|
|
124
154
|
},
|
|
155
|
+
{
|
|
156
|
+
key: 'custom_fields',
|
|
157
|
+
label: I18n.t('camaleon_cms.admin.sidebar.custom_fields', default: 'Custom Fields').to_s,
|
|
158
|
+
description: I18n.t('camaleon_cms.admin.users.tool_tip.custom_fields',
|
|
159
|
+
default: 'Manage custom field groups and fields, including the select_eval type.').to_s
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: 'select_eval',
|
|
163
|
+
label: I18n.t('camaleon_cms.admin.custom_field.select_eval', default: 'Select Eval').to_s,
|
|
164
|
+
description: I18n.t(
|
|
165
|
+
'camaleon_cms.admin.users.tool_tip.select_eval',
|
|
166
|
+
default: 'Allow toggling and using select_eval custom fields in the admin UI.'
|
|
167
|
+
).to_s
|
|
168
|
+
},
|
|
125
169
|
{
|
|
126
170
|
key: 'theme_settings',
|
|
127
171
|
label: I18n.t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings').to_s,
|
|
128
172
|
description: I18n.t('camaleon_cms.admin.users.tool_tip.themes').to_s
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: 'contact_form_unfiltered_html',
|
|
176
|
+
label: I18n.t('camaleon_cms.admin.users.roles_values.contact_form_unfiltered_html',
|
|
177
|
+
default: 'Allow unfiltered HTML in contact forms').to_s,
|
|
178
|
+
color: 'danger',
|
|
179
|
+
description: I18n.t(
|
|
180
|
+
'camaleon_cms.admin.users.tool_tip.contact_form_unfiltered_html',
|
|
181
|
+
default: 'Permit users with this role to save raw/unfiltered HTML anywhere in a contact form: the ' \
|
|
182
|
+
'markup wrapping a form, field labels, descriptions, templates, CSS classes, default ' \
|
|
183
|
+
'values, custom attributes, option labels and response messages. Without it, a save ' \
|
|
184
|
+
'carrying such content is refused rather than cleaned up. Distinct from the per-post-type ' \
|
|
185
|
+
'"Allow unfiltered HTML in post content" permission.'
|
|
186
|
+
).to_s
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
key: 'media_unfiltered_upload',
|
|
190
|
+
label: I18n.t('camaleon_cms.admin.users.roles_values.media_unfiltered_upload',
|
|
191
|
+
default: 'Allow unscanned media uploads').to_s,
|
|
192
|
+
color: 'danger',
|
|
193
|
+
description: I18n.t(
|
|
194
|
+
'camaleon_cms.admin.users.tool_tip.media_unfiltered_upload',
|
|
195
|
+
default: 'Permit users with this role to upload files without the malicious-content scan. Without ' \
|
|
196
|
+
'it, every upload is scanned whatever its source, and a file carrying scripts, event ' \
|
|
197
|
+
'handlers, blocked elements or blocked URI schemes is refused rather than cleaned up. ' \
|
|
198
|
+
'Uploaded files are served from the site origin, so this grants the ability to publish ' \
|
|
199
|
+
'active content there. Site-wide, unlike the per-post-type "Allow unfiltered HTML in post ' \
|
|
200
|
+
'content" permission.'
|
|
201
|
+
).to_s
|
|
129
202
|
}
|
|
130
203
|
]
|
|
131
204
|
}.freeze
|
|
@@ -133,7 +206,7 @@ module CamaleonCms
|
|
|
133
206
|
private
|
|
134
207
|
|
|
135
208
|
def set_users_as_cilent
|
|
136
|
-
site.users.where(role: slug).update_all(role: 'client')
|
|
209
|
+
site.users.where(role: slug).update_all(role: 'client') # rubocop:disable Rails/SkipsModelValidations
|
|
137
210
|
end
|
|
138
211
|
end
|
|
139
212
|
end
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
module Widget
|
|
3
3
|
class Assigned < CamaleonCms::PostDefault
|
|
4
|
-
default_scope -> {
|
|
4
|
+
default_scope -> { order(:taxonomy_id) }
|
|
5
|
+
|
|
6
|
+
def self.type_condition(table = arel_table)
|
|
7
|
+
super.or(table[inheritance_column].eq(name))
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
# post_parent: sidebar_id
|
|
6
11
|
# visibility: widget_id
|
|
7
12
|
# comment_count: item_order
|
|
@@ -12,11 +17,11 @@ module CamaleonCms
|
|
|
12
17
|
|
|
13
18
|
# attr_accessible :widget_id, :sidebar_id, :item_order
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
belongs_to :sidebar, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :post_parent, inverse_of: :assigned,
|
|
21
|
+
optional: true
|
|
22
|
+
belongs_to :widget, class_name: 'CamaleonCms::Widget::Main', foreign_key: :visibility, inverse_of: :assigned,
|
|
23
|
+
optional: true
|
|
24
|
+
|
|
20
25
|
after_initialize :fix_slug2
|
|
21
26
|
before_create :set_order
|
|
22
27
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
module Widget
|
|
3
3
|
class Main < CamaleonCms::TermTaxonomy
|
|
4
|
-
|
|
4
|
+
def self.sti_name
|
|
5
|
+
'widget'
|
|
6
|
+
end
|
|
7
|
+
|
|
5
8
|
# attr_accessible :excerpt, :renderer
|
|
6
9
|
# name: "title"
|
|
7
10
|
# description: "content for this"
|
|
@@ -10,14 +13,17 @@ module CamaleonCms
|
|
|
10
13
|
# excerpt: string for message
|
|
11
14
|
# renderer: string (path to the template for render this widget)
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id
|
|
16
|
+
# inverse_of: false — custom `user_model` classes get only UserMethods, which
|
|
17
|
+
# defines no `widgets` collection the inverse could point at.
|
|
18
|
+
belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, inverse_of: false,
|
|
19
|
+
optional: true
|
|
20
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, inverse_of: :widgets, optional: true
|
|
21
|
+
|
|
22
|
+
has_many :assigned, class_name: 'CamaleonCms::Widget::Assigned', foreign_key: :visibility, dependent: :destroy,
|
|
23
|
+
inverse_of: :widget
|
|
18
24
|
|
|
19
|
-
has_many :assigned, class_name: 'CamaleonCms::Widget::Assigned', foreign_key: :visibility, dependent: :destroy
|
|
20
25
|
before_save :check_excerpt
|
|
26
|
+
|
|
21
27
|
def is_simple?
|
|
22
28
|
status == 'simple'
|
|
23
29
|
end
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
module Widget
|
|
3
3
|
class Sidebar < CamaleonCms::TermTaxonomy
|
|
4
|
-
|
|
4
|
+
normalize_attrs(:description)
|
|
5
5
|
|
|
6
|
-
has_many :
|
|
7
|
-
|
|
8
|
-
}, class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
|
|
9
|
-
has_many :assigned, foreign_key: :post_parent, dependent: :destroy
|
|
10
|
-
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id
|
|
6
|
+
has_many :assigned, foreign_key: :post_parent, dependent: :destroy, inverse_of: :sidebar
|
|
7
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, inverse_of: :sidebars, optional: true
|
|
11
8
|
|
|
12
9
|
# scopes
|
|
13
10
|
scope :default_sidebar, -> { where(slug: 'default-sidebar') }
|
|
@@ -1,16 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CamaleonRecord < ActiveRecord::Base
|
|
4
|
-
|
|
3
|
+
class CamaleonRecord < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
|
|
4
|
+
# Sentinels used to shield HTML comment delimiters (the translation locale markers such as `<!--:en-->`)
|
|
5
|
+
# from ActionController's sanitize(), which strips HTML comments. Private-Use-Area code points are used so
|
|
6
|
+
# that plain text typed by a user can never be rewritten into a comment delimiter after sanitization: the
|
|
7
|
+
# previous `!--`/`--!` tokens collided with ordinary text (e.g. "Read more !--"), so the restore pass would
|
|
8
|
+
# inject stray `<!--`/`-->` into the output. The hide map also strips any raw sentinel characters supplied
|
|
9
|
+
# in the input, so they cannot be smuggled through to become delimiters on restore.
|
|
10
|
+
TRANSLATION_TAG_HIDE_SENTINELS = { open: "\u{E000}", close: "\u{E001}" }.freeze
|
|
11
|
+
TRANSLATION_TAG_HIDE_MAP = {
|
|
12
|
+
'<!--' => TRANSLATION_TAG_HIDE_SENTINELS[:open],
|
|
13
|
+
'-->' => TRANSLATION_TAG_HIDE_SENTINELS[:close],
|
|
14
|
+
TRANSLATION_TAG_HIDE_SENTINELS[:open] => '',
|
|
15
|
+
TRANSLATION_TAG_HIDE_SENTINELS[:close] => ''
|
|
16
|
+
}.freeze
|
|
5
17
|
TRANSLATION_TAG_HIDE_REGEX = Regexp.new(TRANSLATION_TAG_HIDE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
|
|
6
|
-
TRANSLATION_TAG_RESTORE_MAP = {
|
|
18
|
+
TRANSLATION_TAG_RESTORE_MAP = {
|
|
19
|
+
TRANSLATION_TAG_HIDE_SENTINELS[:open] => '<!--',
|
|
20
|
+
TRANSLATION_TAG_HIDE_SENTINELS[:close] => '-->'
|
|
21
|
+
}.freeze
|
|
7
22
|
TRANSLATION_TAG_RESTORE_REGEX =
|
|
8
23
|
Regexp.new(TRANSLATION_TAG_RESTORE_MAP.keys.map { |x| Regexp.escape(x) }.join('|')).freeze
|
|
9
24
|
|
|
10
|
-
include ActiveRecordExtras::Relation
|
|
11
|
-
|
|
12
25
|
self.abstract_class = true
|
|
13
26
|
|
|
27
|
+
# Sanitize a value with ActionController's sanitize() while preserving translation locale markers
|
|
28
|
+
# (<!--:xx-->). Shared by Post#sanitize_content and the NormalizeAttrs concern so the transform lives in
|
|
29
|
+
# one place. Returns nil unchanged. `tags:`/`attributes:` default to nil, i.e. the sanitizer's own
|
|
30
|
+
# allowlist, so NormalizeAttrs and other callers are unaffected; Post#content passes a widened list.
|
|
31
|
+
def self.cama_sanitize_translatable(value, tags: nil, attributes: nil)
|
|
32
|
+
return value if value.nil?
|
|
33
|
+
|
|
34
|
+
options = {}
|
|
35
|
+
options[:tags] = tags if tags
|
|
36
|
+
options[:attributes] = attributes if attributes
|
|
37
|
+
ActionController::Base.helpers.sanitize(
|
|
38
|
+
value.to_s.gsub(TRANSLATION_TAG_HIDE_REGEX, TRANSLATION_TAG_HIDE_MAP), **options
|
|
39
|
+
).gsub(TRANSLATION_TAG_RESTORE_REGEX, TRANSLATION_TAG_RESTORE_MAP)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.polymorphic_name
|
|
43
|
+
return super unless name.to_s.start_with?('CamaleonCms::')
|
|
44
|
+
|
|
45
|
+
name.demodulize
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.polymorphic_class_for(name)
|
|
49
|
+
super
|
|
50
|
+
rescue NameError
|
|
51
|
+
legacy_class = legacy_camaleon_polymorphic_class(name)
|
|
52
|
+
return legacy_class if legacy_class
|
|
53
|
+
return nil if legacy_polymorphic_marker?(name)
|
|
54
|
+
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
57
|
+
|
|
14
58
|
# save cache value for this key
|
|
15
59
|
def cama_set_cache(key, val)
|
|
16
60
|
@cama_cache_vars ||= {}
|
|
@@ -20,6 +64,8 @@ class CamaleonRecord < ActiveRecord::Base
|
|
|
20
64
|
|
|
21
65
|
# remove cache value for this key
|
|
22
66
|
def cama_remove_cache(key)
|
|
67
|
+
return unless @cama_cache_vars
|
|
68
|
+
|
|
23
69
|
@cama_cache_vars.delete(cama_build_cache_key(key))
|
|
24
70
|
end
|
|
25
71
|
|
|
@@ -49,4 +95,76 @@ class CamaleonRecord < ActiveRecord::Base
|
|
|
49
95
|
def cama_build_cache_key(key)
|
|
50
96
|
_key = "cama_cache_#{self.class.name}_#{id}_#{key}"
|
|
51
97
|
end
|
|
98
|
+
|
|
99
|
+
# Return the current user for this thread/request context.
|
|
100
|
+
# Uses ActiveSupport::CurrentAttributes (CurrentRequest.user)
|
|
101
|
+
def current_user
|
|
102
|
+
return @current_user if defined?(@current_user)
|
|
103
|
+
|
|
104
|
+
@current_user = CurrentRequest.user
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Authorization helpers that delegate to central Ability (CanCan)
|
|
108
|
+
def can?(*args)
|
|
109
|
+
# Return false if no user or site context (e.g., background jobs, console)
|
|
110
|
+
return false if current_user.nil? || current_site.nil?
|
|
111
|
+
|
|
112
|
+
ability.can?(*args)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def ability
|
|
116
|
+
# Memoize Ability per request to avoid repeated DB queries and object instantiation.
|
|
117
|
+
# In tests that modify role meta mid-request, call reset_ability to invalidate cache.
|
|
118
|
+
@ability ||= CamaleonCms::Ability.new(current_user, current_site)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Reset cached ability instance (useful in tests when role meta changes)
|
|
122
|
+
def reset_ability
|
|
123
|
+
@ability = nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# current_site memoized from the CurrentRequest.site
|
|
127
|
+
def current_site
|
|
128
|
+
return @current_site if defined?(@current_site) && @current_site.present?
|
|
129
|
+
|
|
130
|
+
@current_site = CurrentRequest.site
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def self.legacy_camaleon_polymorphic_class(name)
|
|
134
|
+
class_name = name.to_s
|
|
135
|
+
return if class_name.blank?
|
|
136
|
+
|
|
137
|
+
candidates = []
|
|
138
|
+
|
|
139
|
+
unless class_name.include?('::')
|
|
140
|
+
candidates << "CamaleonCms::#{class_name}"
|
|
141
|
+
|
|
142
|
+
if class_name.include?('_')
|
|
143
|
+
base_name = class_name.split('_').first
|
|
144
|
+
candidates << "CamaleonCms::#{base_name}" if base_name.present?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
candidates << CamaManager.get_user_class_name.to_s if class_name == 'User'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
candidates << "CamaleonCms::#{class_name}" if class_name.include?('::') && !class_name.start_with?('CamaleonCms::')
|
|
151
|
+
|
|
152
|
+
candidates.uniq.each do |candidate|
|
|
153
|
+
next unless valid_constant_name?(candidate)
|
|
154
|
+
|
|
155
|
+
klass = candidate.safe_constantize
|
|
156
|
+
return klass if klass
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
private_class_method def self.legacy_polymorphic_marker?(name)
|
|
162
|
+
name.to_s.start_with?('_')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private_class_method def self.valid_constant_name?(name)
|
|
166
|
+
name.to_s.match?(/\A[A-Z]\w*(::[A-Z]\w*)*\z/)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private_class_method :legacy_camaleon_polymorphic_class
|
|
52
170
|
end
|