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
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class Site < CamaleonCms::TermTaxonomy
|
|
3
|
-
normalize_attrs(:
|
|
3
|
+
normalize_attrs(:description)
|
|
4
4
|
|
|
5
5
|
include CamaleonCms::SiteDefaultSettings
|
|
6
6
|
|
|
7
7
|
# attrs: [name, description, slug]
|
|
8
8
|
attr_accessor :site_domain
|
|
9
9
|
|
|
10
|
-
|
|
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
|
|
11
15
|
|
|
12
|
-
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
|
|
13
18
|
has_many :nav_menus, class_name: 'CamaleonCms::NavMenu', foreign_key: :parent_id, dependent: :destroy,
|
|
14
19
|
inverse_of: :site
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
has_many :
|
|
18
|
-
|
|
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
|
|
19
30
|
has_many :custom_field_groups, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :parent_id,
|
|
20
|
-
dependent: :destroy
|
|
21
|
-
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
|
|
22
34
|
|
|
23
35
|
has_many :posts, through: :post_types, source: :posts
|
|
24
|
-
has_many :plugins, class_name: 'CamaleonCms::Plugin', foreign_key: :parent_id, dependent: :destroy
|
|
25
|
-
|
|
26
|
-
has_many :
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
30
43
|
|
|
31
44
|
after_create :default_settings
|
|
32
45
|
after_create :set_default_user_roles
|
|
@@ -35,7 +48,7 @@ module CamaleonCms
|
|
|
35
48
|
before_destroy :destroy_site
|
|
36
49
|
after_destroy :refresh_routes
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
validates :slug, uniqueness: { scope: :taxonomy }
|
|
39
52
|
|
|
40
53
|
# all user roles for this site
|
|
41
54
|
def user_roles
|
|
@@ -79,7 +92,7 @@ module CamaleonCms
|
|
|
79
92
|
options[:_admin_theme] || 'en'
|
|
80
93
|
end
|
|
81
94
|
|
|
82
|
-
#
|
|
95
|
+
# Set the current admin language for this site
|
|
83
96
|
def set_admin_language(language)
|
|
84
97
|
set_option('_admin_theme', language)
|
|
85
98
|
end
|
|
@@ -149,11 +162,9 @@ module CamaleonCms
|
|
|
149
162
|
|
|
150
163
|
# list all users of current site
|
|
151
164
|
def users
|
|
152
|
-
if PluginRoutes.system_info['users_share_sites']
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
CamaleonCms::User.where(site_id: id)
|
|
156
|
-
end
|
|
165
|
+
return CamaleonCms::User.all if PluginRoutes.system_info['users_share_sites']
|
|
166
|
+
|
|
167
|
+
CamaleonCms::User.where(site_id: id)
|
|
157
168
|
end
|
|
158
169
|
alias users_include_admins users
|
|
159
170
|
|
|
@@ -176,15 +187,13 @@ module CamaleonCms
|
|
|
176
187
|
def get_valid_post_slug(slug, post_id = nil)
|
|
177
188
|
slugs = slug.translations
|
|
178
189
|
if slugs.present?
|
|
179
|
-
slugs.each
|
|
180
|
-
slugs[k] = get_valid_post_slug(v)
|
|
181
|
-
end
|
|
190
|
+
slugs.each { |k, v| slugs[k] = get_valid_post_slug(v) }
|
|
182
191
|
slugs.to_translate
|
|
183
192
|
else
|
|
184
193
|
res = slug
|
|
185
194
|
(1..9999).each do |i|
|
|
186
|
-
p = posts.find_by_slug(res)
|
|
187
|
-
break if
|
|
195
|
+
p = posts.find_by_slug(res) # rubocop:disable Rails/DynamicFindBy
|
|
196
|
+
break if p.blank? || (p.present? && p.id == post_id)
|
|
188
197
|
|
|
189
198
|
res = "#{slug}-#{i}"
|
|
190
199
|
end
|
|
@@ -211,7 +220,7 @@ module CamaleonCms
|
|
|
211
220
|
# if the anonymous user not exist, will create one
|
|
212
221
|
def get_anonymous_user
|
|
213
222
|
user = users.where(username: 'anonymous').first
|
|
214
|
-
|
|
223
|
+
if user.blank?
|
|
215
224
|
pass = "anonymous#{rand(9999)}"
|
|
216
225
|
user = users.create({ email: 'anonymous_user@local.com', username: 'anonymous', password: pass,
|
|
217
226
|
password_confirmation: pass, first_name: 'Anonymous' })
|
|
@@ -221,7 +230,8 @@ module CamaleonCms
|
|
|
221
230
|
|
|
222
231
|
# return the domain for current site
|
|
223
232
|
# sample: mysite.com | sample.mysite.com
|
|
224
|
-
# 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
|
|
225
235
|
def get_domain
|
|
226
236
|
@site_domain || (if main_site?
|
|
227
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
|
|
@@ -2,6 +2,7 @@ module CamaleonCms
|
|
|
2
2
|
class TermTaxonomy < CamaleonRecord
|
|
3
3
|
include CamaleonCms::Metas
|
|
4
4
|
include CamaleonCms::CustomFieldsRead
|
|
5
|
+
include CamaleonCms::ContentShortcodeGate
|
|
5
6
|
|
|
6
7
|
extend CamaleonCms::NormalizeAttrs
|
|
7
8
|
|
|
@@ -14,10 +15,57 @@ module CamaleonCms
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
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,
|
|
18
|
+
# attr_accessible :taxonomy, :description, :parent_id, :count, :name, :slug, :term_group, :status, :term_order,
|
|
19
|
+
# :user_id
|
|
18
20
|
# attr_accessible :data_options
|
|
19
21
|
# attr_accessible :data_metas
|
|
20
22
|
|
|
23
|
+
self.inheritance_column = :taxonomy
|
|
24
|
+
|
|
25
|
+
def self.sti_name
|
|
26
|
+
name.demodulize.underscore
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.polymorphic_name
|
|
30
|
+
name.demodulize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.find_sti_class(type_name)
|
|
34
|
+
# Handle explicit exceptions where database string doesn't match namespacing layout
|
|
35
|
+
case type_name.to_s
|
|
36
|
+
when 'widget'
|
|
37
|
+
return CamaleonCms::Widget::Main
|
|
38
|
+
when 'sidebar'
|
|
39
|
+
return CamaleonCms::Widget::Sidebar
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Standard conversion for "site" -> "CamaleonCms::Site"
|
|
43
|
+
# or "nav_menu_item" -> "CamaleonCms::NavMenuItem"
|
|
44
|
+
klass = begin
|
|
45
|
+
"CamaleonCms::#{type_name.camelize}".constantize
|
|
46
|
+
rescue NameError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
# The ancestry guard keeps a value like "meta" from instantiating an unrelated
|
|
50
|
+
# CamaleonCms class against the term_taxonomy table.
|
|
51
|
+
return klass if klass && klass <= base_class
|
|
52
|
+
|
|
53
|
+
# Runtime scan across loaded taxonomy models.
|
|
54
|
+
found = CamaleonCms::TermTaxonomy.descendants.find { |k| k.sti_name == type_name.to_s }
|
|
55
|
+
return found if found
|
|
56
|
+
|
|
57
|
+
# `super` resolves fully-qualified external classes (plugin STI outside the CamaleonCms
|
|
58
|
+
# namespace) and enforces ancestry — the scan above only sees classes already loaded, so
|
|
59
|
+
# without this an unloaded plugin taxonomy silently degraded to the base class. Same
|
|
60
|
+
# fallback PostDefault.find_sti_class keeps. Values it cannot place load as the base class,
|
|
61
|
+
# as every row did before native STI.
|
|
62
|
+
begin
|
|
63
|
+
super
|
|
64
|
+
rescue ActiveRecord::SubclassNotFound
|
|
65
|
+
base_class
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
21
69
|
# callbacks
|
|
22
70
|
before_validation :before_validating
|
|
23
71
|
before_destroy :destroy_dependencies
|
|
@@ -25,13 +73,19 @@ module CamaleonCms
|
|
|
25
73
|
# validates
|
|
26
74
|
validates :name, :taxonomy, presence: true
|
|
27
75
|
validates_with CamaleonCms::UniqValidator
|
|
76
|
+
# A taxonomy/widget description is expanded by do_shortcode at render
|
|
77
|
+
# (TermTaxonomyDecorator#the_content, shortcode_templates/widget.html.erb); STI-covers every
|
|
78
|
+
# subclass (post types, categories, tags, widgets, sidebars). Gate authorship behind
|
|
79
|
+
# content_shortcodes -- benign descriptions pass without ever building an Ability.
|
|
80
|
+
gate_content_shortcodes :description
|
|
28
81
|
|
|
29
82
|
# relations
|
|
30
|
-
has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
|
|
83
|
+
has_many :term_relationships, class_name: 'CamaleonCms::TermRelationship',
|
|
31
84
|
dependent: :destroy
|
|
32
85
|
# has_many :posts, foreign_key: :objectid, through: :term_relationships, :source => :objects
|
|
33
|
-
belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy',
|
|
34
|
-
belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id,
|
|
86
|
+
belongs_to :parent, class_name: 'CamaleonCms::TermTaxonomy', optional: true
|
|
87
|
+
belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
|
|
88
|
+
inverse_of: false
|
|
35
89
|
|
|
36
90
|
# return all children taxonomy
|
|
37
91
|
# sample: sub categories of a category
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class Theme < CamaleonCms::TermTaxonomy
|
|
3
|
-
normalize_attrs(:
|
|
3
|
+
normalize_attrs(:description)
|
|
4
4
|
|
|
5
5
|
# attrs:
|
|
6
6
|
# slug => plugin key
|
|
7
|
-
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id,
|
|
8
|
-
|
|
9
|
-
default_scope { where(taxonomy: :theme) }
|
|
7
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true, inverse_of: :themes
|
|
10
8
|
|
|
11
9
|
before_validation :fix_name
|
|
12
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,12 +1,15 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class UserRole < CamaleonCms::TermTaxonomy
|
|
3
|
-
normalize_attrs(:
|
|
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
|
|
8
10
|
|
|
9
|
-
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id,
|
|
11
|
+
belongs_to :site, class_name: 'CamaleonCms::Site', foreign_key: :parent_id, optional: true,
|
|
12
|
+
inverse_of: :user_roles_rel
|
|
10
13
|
|
|
11
14
|
def roles_post_type
|
|
12
15
|
get_meta('_post_type')
|
|
@@ -20,6 +23,23 @@ module CamaleonCms
|
|
|
20
23
|
term_group.nil?
|
|
21
24
|
end
|
|
22
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
|
+
|
|
23
43
|
ROLES = {
|
|
24
44
|
post_type: [
|
|
25
45
|
{
|
|
@@ -81,6 +101,14 @@ module CamaleonCms
|
|
|
81
101
|
label: I18n.t('camaleon_cms.admin.users.roles_values.html_manage_tags').to_s,
|
|
82
102
|
color: 'warning',
|
|
83
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
|
|
84
112
|
}
|
|
85
113
|
],
|
|
86
114
|
manager: [
|
|
@@ -133,12 +161,59 @@ module CamaleonCms
|
|
|
133
161
|
{
|
|
134
162
|
key: 'select_eval',
|
|
135
163
|
label: I18n.t('camaleon_cms.admin.custom_field.select_eval', default: 'Select Eval').to_s,
|
|
136
|
-
description: I18n.t(
|
|
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
|
|
137
168
|
},
|
|
138
169
|
{
|
|
139
170
|
key: 'theme_settings',
|
|
140
171
|
label: I18n.t('camaleon_cms.admin.settings.theme_setting', default: 'Theme Settings').to_s,
|
|
141
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
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
key: 'content_shortcodes',
|
|
205
|
+
label: I18n.t('camaleon_cms.admin.users.roles_values.content_shortcodes',
|
|
206
|
+
default: 'Allow shortcodes in content').to_s,
|
|
207
|
+
color: 'danger',
|
|
208
|
+
description: I18n.t(
|
|
209
|
+
'camaleon_cms.admin.users.tool_tip.content_shortcodes',
|
|
210
|
+
default: 'Permit users with this role to publish shortcodes in content that the CMS expands at ' \
|
|
211
|
+
'render: post content, custom-field values, taxonomy content and widget descriptions. A ' \
|
|
212
|
+
'shortcode makes theme/plugin code emit arbitrary HTML/JS on the page, which the ' \
|
|
213
|
+
'content scan cannot judge. Without it, a save carrying a registered shortcode is refused ' \
|
|
214
|
+
'rather than cleaned up. Site-wide, and an end-run around the per-post-type "Allow ' \
|
|
215
|
+
'unfiltered HTML in post content" permission.'
|
|
216
|
+
).to_s
|
|
142
217
|
}
|
|
143
218
|
]
|
|
144
219
|
}.freeze
|
|
@@ -146,7 +221,7 @@ module CamaleonCms
|
|
|
146
221
|
private
|
|
147
222
|
|
|
148
223
|
def set_users_as_cilent
|
|
149
|
-
site.users.where(role: slug).update_all(role: 'client')
|
|
224
|
+
site.users.where(role: slug).update_all(role: 'client') # rubocop:disable Rails/SkipsModelValidations
|
|
150
225
|
end
|
|
151
226
|
end
|
|
152
227
|
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,9 +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
|
|
5
7
|
|
|
6
|
-
default_scope { where(taxonomy: :widget) }
|
|
7
8
|
# attr_accessible :excerpt, :renderer
|
|
8
9
|
# name: "title"
|
|
9
10
|
# description: "content for this"
|
|
@@ -12,14 +13,17 @@ module CamaleonCms
|
|
|
12
13
|
# excerpt: string for message
|
|
13
14
|
# renderer: string (path to the template for render this widget)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
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
|
|
20
24
|
|
|
21
|
-
has_many :assigned, class_name: 'CamaleonCms::Widget::Assigned', foreign_key: :visibility, dependent: :destroy
|
|
22
25
|
before_save :check_excerpt
|
|
26
|
+
|
|
23
27
|
def is_simple?
|
|
24
28
|
status == 'simple'
|
|
25
29
|
end
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
module Widget
|
|
3
3
|
class Sidebar < CamaleonCms::TermTaxonomy
|
|
4
|
-
normalize_attrs(:
|
|
4
|
+
normalize_attrs(:description)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
has_many :metas, lambda {
|
|
9
|
-
where(object_class: 'Widget::Sidebar')
|
|
10
|
-
}, class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
|
|
11
|
-
has_many :assigned, foreign_key: :post_parent, dependent: :destroy
|
|
12
|
-
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
|
|
13
8
|
|
|
14
9
|
# scopes
|
|
15
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
|
|
|
@@ -83,4 +129,42 @@ class CamaleonRecord < ActiveRecord::Base
|
|
|
83
129
|
|
|
84
130
|
@current_site = CurrentRequest.site
|
|
85
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
|
|
86
170
|
end
|