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
|
@@ -24,18 +24,22 @@ module CamaleonCms
|
|
|
24
24
|
# update category assignations for this post
|
|
25
25
|
# remove assignations that no longer exist
|
|
26
26
|
# add new assignations
|
|
27
|
-
# cats: (Array) array of category ids assigned for this post, sample: [1,2,3]
|
|
27
|
+
# cats: (Array) array of category ids assigned for this post, sample: [1, 2, 3]
|
|
28
28
|
def update_categories(cats = [])
|
|
29
29
|
rescue_extra_data
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
# uniq: duplicate ids (e.g. a crafted `categories[]=5&categories[]=5`) would otherwise create
|
|
31
|
+
# duplicate term_relationships rows, which duplicate the post in category/tag listings and
|
|
32
|
+
# inflate will_paginate's total_entries (there is no unique index on the join table)
|
|
33
|
+
cats = cats.to_i.uniq
|
|
34
|
+
# reuse the snapshot rescue_extra_data just took instead of plucking the same set again
|
|
35
|
+
old_categories = manage_categories? ? @cats_before : categories.pluck("#{CamaleonCms::TermTaxonomy.table_name}.id")
|
|
32
36
|
delete_categories = old_categories - cats
|
|
33
37
|
news_categories = cats - old_categories
|
|
34
38
|
term_relationships.where('term_taxonomy_id in (?)', delete_categories).destroy_all if delete_categories.present?
|
|
35
39
|
news_categories.each do |key|
|
|
36
40
|
term_relationships.create(term_taxonomy_id: key)
|
|
37
41
|
end
|
|
38
|
-
|
|
42
|
+
update_counts('categories')
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
# update tags assignations for this post
|
|
@@ -53,7 +57,7 @@ module CamaleonCms
|
|
|
53
57
|
post_tag = post_type.post_tags.where({ name: f }).first_or_create(slug: f.parameterize)
|
|
54
58
|
term_relationships.where({ term_taxonomy_id: post_tag.id }).first_or_create
|
|
55
59
|
end
|
|
56
|
-
|
|
60
|
+
update_counts('tags')
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
# Assign this post for category with id: category_id
|
|
@@ -64,72 +68,62 @@ module CamaleonCms
|
|
|
64
68
|
categories_id.each do |key|
|
|
65
69
|
term_relationships.where(term_taxonomy_id: key).first_or_create!
|
|
66
70
|
end
|
|
67
|
-
|
|
71
|
+
update_counts('categories')
|
|
68
72
|
end
|
|
69
73
|
|
|
70
|
-
#
|
|
71
|
-
# categories_id: (Array) array of category ids
|
|
74
|
+
# Unassign this post from categories with ids: categories_id
|
|
75
|
+
# categories_id: (Array) array of category ids to unassign from this post, sample: [1,2,3]
|
|
72
76
|
def unassign_category(categories_id)
|
|
73
77
|
categories_id = [categories_id] if categories_id.is_a?(Integer)
|
|
74
78
|
rescue_extra_data
|
|
75
79
|
term_relationships.where(term_taxonomy_id: categories_id).destroy_all
|
|
76
|
-
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Assign new tags to this post
|
|
80
|
-
# tags_title: (String) tags name separated by commas, sample: "Tag1,Tag two,tag new"
|
|
81
|
-
def assign_tags(tag_titles)
|
|
82
|
-
update_counters_before
|
|
83
|
-
tags = tag_titles.split(',').strip
|
|
84
|
-
tags.each do |t|
|
|
85
|
-
post_tag = post_type.post_tags.where(name: t).first_or_create!
|
|
86
|
-
term_relationships.where({ term_taxonomy_id: post_tag.id }).first_or_create!
|
|
87
|
-
end
|
|
88
|
-
update_counters('tags')
|
|
80
|
+
update_counts('categories')
|
|
89
81
|
end
|
|
90
82
|
|
|
91
|
-
#
|
|
92
|
-
# tags_title: (String) tags name separated by commas, sample: "Tag1,Tag two,tag new"
|
|
93
|
-
def unassign_tags(tag_titles)
|
|
94
|
-
update_counters_before
|
|
95
|
-
tags = tag_titles.split(',').strip
|
|
96
|
-
term_relationships.where({ term_taxonomy_id: post_type.post_tags.where(name: tags).pluck(:id) }).destroy_all
|
|
97
|
-
update_counters('tags')
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# update quantity of posts assigned for tags and categories assigned to this post
|
|
83
|
+
# Update the quantity of posts assigned for tags and categories assigned to this post
|
|
101
84
|
def update_extra_data
|
|
102
85
|
rescue_extra_data
|
|
103
|
-
|
|
86
|
+
update_counts
|
|
104
87
|
end
|
|
105
88
|
|
|
106
89
|
private
|
|
107
90
|
|
|
108
|
-
@cats_before = []
|
|
109
|
-
@tags_before = []
|
|
110
91
|
# save as a cache previous categories and tags assigned for this post
|
|
111
92
|
def rescue_extra_data
|
|
93
|
+
# Every writer mutates term_relationships directly, which does NOT invalidate an already-loaded
|
|
94
|
+
# categories/post_tags/term_relationships proxy -- and the_post preloads categories/post_tags via
|
|
95
|
+
# with_eager, so the proxy is routinely loaded before a write. Reset here, the single point every
|
|
96
|
+
# writer (and update_extra_data) calls first, so the snapshots below and every later
|
|
97
|
+
# pluck/empty?/count read the database rather than a stale in-memory target.
|
|
98
|
+
reset_term_proxies
|
|
112
99
|
@cats_before = categories.pluck(:id) if manage_categories?
|
|
113
100
|
@tags_before = post_tags.pluck(:id) if manage_tags?
|
|
114
101
|
end
|
|
115
102
|
|
|
116
|
-
#
|
|
103
|
+
# drop any loaded category/tag/term_relationship cache so a write starts from DB truth
|
|
104
|
+
def reset_term_proxies
|
|
105
|
+
categories.reset
|
|
106
|
+
post_tags.reset
|
|
107
|
+
term_relationships.reset
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Update the quantity of posts assigned for tags and categories assigned to this post
|
|
117
111
|
# if kind is empty, then this will update both: cats and tags
|
|
118
112
|
# kind: (string) tags | categories
|
|
119
|
-
def
|
|
113
|
+
def update_counts(kind = '')
|
|
120
114
|
if ['', 'categories'].include?(kind) && manage_categories?
|
|
121
|
-
post_type.full_categories.where(id: (@cats_before + categories.pluck(:id)).uniq).
|
|
122
|
-
c.update_column(
|
|
115
|
+
post_type.full_categories.where(id: (@cats_before + categories.pluck(:id)).uniq).find_each do |c|
|
|
116
|
+
c.update_column(:count, c.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
|
|
123
117
|
end
|
|
124
118
|
end
|
|
125
119
|
return unless ['', 'tags'].include?(kind) && manage_tags?
|
|
126
120
|
|
|
127
|
-
post_type.post_tags.where(id: (@tags_before + post_tags.pluck(:id)).uniq).
|
|
128
|
-
tag.update_column(
|
|
121
|
+
post_type.post_tags.where(id: (@tags_before + post_tags.pluck(:id)).uniq).find_each do |tag|
|
|
122
|
+
tag.update_column(:count, tag.posts.published.size) # rubocop:disable Rails/SkipsModelValidations
|
|
129
123
|
end
|
|
130
124
|
end
|
|
131
125
|
|
|
132
|
-
#
|
|
126
|
+
# Save extra data such as categories and tags assigned to this post
|
|
133
127
|
def save_extra_data
|
|
134
128
|
update_categories(data_categories) if manage_categories? && !data_categories.nil?
|
|
135
129
|
update_tags(data_tags) if manage_tags? && !data_tags.nil?
|
|
@@ -139,7 +133,7 @@ module CamaleonCms
|
|
|
139
133
|
def check_default_category
|
|
140
134
|
return unless manage_categories?
|
|
141
135
|
|
|
142
|
-
assign_category([post_type.default_category.id])
|
|
136
|
+
assign_category([post_type.default_category.id]) if categories.blank?
|
|
143
137
|
end
|
|
144
138
|
end
|
|
145
139
|
end
|
|
@@ -3,22 +3,35 @@
|
|
|
3
3
|
module CamaleonCms
|
|
4
4
|
module CommonRelationships
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
|
+
# rubocop:disable Rails/InverseOf
|
|
6
7
|
|
|
7
8
|
included do
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# Scopes use the demodulized class name (the 2.9.2 contract every install's rows were
|
|
10
|
+
# written under): 'Post', 'Main' for Widget::Main, and 'User' for a host Admin::User.
|
|
11
|
+
# to_s tolerates anonymous subclasses (STI specs build them), whose name is nil.
|
|
12
|
+
object_class_name = name.to_s.demodulize
|
|
13
|
+
has_many :metas, -> { where(object_class: object_class_name) },
|
|
10
14
|
class_name: 'CamaleonCms::Meta', foreign_key: :objectid, dependent: :destroy
|
|
11
15
|
|
|
12
|
-
has_many :custom_field_values, -> { where(object_class:
|
|
16
|
+
has_many :custom_field_values, -> { where(object_class: object_class_name) },
|
|
13
17
|
class_name: 'CamaleonCms::CustomFieldsRelationship', foreign_key: :objectid, dependent: :delete_all
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
# No dependent: on the definition associations (2.9.2 behavior). Teardown is owned by
|
|
20
|
+
# CustomFieldsRead#_destroy_custom_field_groups, which runs first and destroys groups WITH
|
|
21
|
+
# callbacks; a dependent here is inert at best, and on hook-less includers (PostComment) a
|
|
22
|
+
# delete_all removed group rows while orphaning their fields and option metas. A :destroy
|
|
23
|
+
# cascade is no alternative: custom_fields matches group rows too (same table, same scope)
|
|
24
|
+
# but its CustomField typing would skip group teardown.
|
|
25
|
+
# rubocop:disable Rails/HasManyOrHasOneDependent
|
|
26
|
+
has_many :custom_fields, -> { where(object_class: object_class_name) },
|
|
16
27
|
class_name: 'CamaleonCms::CustomField', foreign_key: :objectid
|
|
17
28
|
|
|
18
29
|
# valid only for simple groups and not for complex like: posts, post, ... where the group is for individual or
|
|
19
30
|
# children groups
|
|
20
|
-
has_many :custom_field_groups, -> { where(object_class:
|
|
31
|
+
has_many :custom_field_groups, -> { where(object_class: object_class_name) },
|
|
21
32
|
class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :objectid
|
|
33
|
+
# rubocop:enable Rails/HasManyOrHasOneDependent
|
|
22
34
|
end
|
|
35
|
+
# rubocop:enable Rails/InverseOf
|
|
23
36
|
end
|
|
24
37
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CamaleonCms
|
|
4
|
+
# Save-time authorization gate for authored content that `do_shortcode` later expands (post
|
|
5
|
+
# content, custom-field values, taxonomy/widget descriptions). A registered shortcode makes
|
|
6
|
+
# theme/plugin code emit arbitrary HTML/JS at render, which the content scan cannot judge, so
|
|
7
|
+
# authorship is GATED behind the default-off `content_shortcodes` manager permission rather than
|
|
8
|
+
# filtered. The content is never escaped, stripped or rewritten -- a save carrying a shortcode is
|
|
9
|
+
# refused for an untrusted author, and stored verbatim for a trusted one.
|
|
10
|
+
#
|
|
11
|
+
# Conforms to `security-capability-gating` (fail-closed) and the content-shortcode-gating
|
|
12
|
+
# capability. A model gates an attribute with `gate_content_shortcodes :attr`; a newly added
|
|
13
|
+
# surface that `do_shortcode` expands MUST adopt this gate (asserted by the coverage spec).
|
|
14
|
+
module ContentShortcodeGate
|
|
15
|
+
extend ActiveSupport::Concern
|
|
16
|
+
|
|
17
|
+
class_methods do
|
|
18
|
+
# Gate the named attribute(s) on create/update. Each attribute is scanned in its RAW stored
|
|
19
|
+
# form -- which contains every locale of a translatable value at once, so a shortcode in any
|
|
20
|
+
# locale is caught -- and, if a registered shortcode is present and the actor is a
|
|
21
|
+
# non-administrator lacking `content_shortcodes`, the save is refused with a base error.
|
|
22
|
+
def gate_content_shortcodes(*attributes, on: %i[create update])
|
|
23
|
+
gated = attributes.map(&:to_sym)
|
|
24
|
+
validate(on: on) { cama_reject_unauthorized_shortcodes(gated) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def cama_reject_unauthorized_shortcodes(attributes)
|
|
31
|
+
return unless attributes.any? { |attr| cama_shortcode_attr_gated?(attr) }
|
|
32
|
+
return if cama_trusted_for_shortcodes?
|
|
33
|
+
|
|
34
|
+
errors.add(:base, cama_shortcode_rejection_message)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# An attribute needs the gate only when it (a) changed on this save (or the record is new) -- so
|
|
38
|
+
# a title-only edit never re-gates pre-existing shortcode content, mirroring
|
|
39
|
+
# Post#reject_untrusted_dangerous_content -- and (b) actually contains a registered shortcode.
|
|
40
|
+
def cama_shortcode_attr_gated?(attr)
|
|
41
|
+
return false unless new_record? || cama_gate_attribute_changed?(attr)
|
|
42
|
+
|
|
43
|
+
value = self[attr]
|
|
44
|
+
value.present? && CamaleonCms::ShortcodeRegistry.content_has_shortcode?(value.to_s)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cama_gate_attribute_changed?(attr)
|
|
48
|
+
respond_to?("#{attr}_changed?") ? public_send("#{attr}_changed?") : attribute_changed?(attr.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Fail closed (the gate applies) without a request context or on any evaluation error, per
|
|
52
|
+
# security-capability-gating: a background job, rake task or console is untrusted. Administrators
|
|
53
|
+
# always pass; a non-administrator passes only while holding the `content_shortcodes` manager
|
|
54
|
+
# permission.
|
|
55
|
+
def cama_trusted_for_shortcodes?
|
|
56
|
+
user = CurrentRequest.user
|
|
57
|
+
site = CurrentRequest.site
|
|
58
|
+
return false if user.blank? || site.blank?
|
|
59
|
+
return true if user.admin?
|
|
60
|
+
|
|
61
|
+
CamaleonCms::Ability.new(user, site).can?(:manage, :content_shortcodes)
|
|
62
|
+
rescue StandardError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Only en.yml carries this key while the process locale follows the current admin/site language
|
|
67
|
+
# -- fall back to English rather than emit "translation missing".
|
|
68
|
+
def cama_shortcode_rejection_message
|
|
69
|
+
key = 'camaleon_cms.admin.shortcodes.message.rejected'
|
|
70
|
+
I18n.t(key, default: I18n.t(key, locale: :en))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -35,21 +35,29 @@ module CamaleonCms
|
|
|
35
35
|
when 'Post'
|
|
36
36
|
if term_relationships.empty? && args[:cat_ids].nil?
|
|
37
37
|
CamaleonCms::CustomFieldGroup.where(
|
|
38
|
-
'(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)',
|
|
38
|
+
'(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)',
|
|
39
|
+
id || -1, class_name, post_type.id, "PostType_#{class_name}"
|
|
39
40
|
)
|
|
40
41
|
else
|
|
41
42
|
cat_ids = categories.map(&:id)
|
|
42
43
|
cat_ids += args[:cat_ids] unless args[:cat_ids].nil?
|
|
43
44
|
cat_ids += CamaleonCms::Category.find(cat_ids).map { |category| _category_parents_ids(category) }.flatten.uniq
|
|
44
|
-
CamaleonCms::CustomFieldGroup.where(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
CamaleonCms::CustomFieldGroup.where(
|
|
46
|
+
"(objectid = ? AND object_class = ?) OR
|
|
47
|
+
(objectid = ? AND object_class = ?) OR
|
|
48
|
+
(objectid IN (?) AND object_class = ?)",
|
|
49
|
+
id || -1, class_name, post_type.id, "PostType_#{class_name}", cat_ids, "Category_#{class_name}"
|
|
50
|
+
)
|
|
50
51
|
end
|
|
51
52
|
when 'NavMenuItem'
|
|
52
53
|
main_menu.custom_field_groups
|
|
54
|
+
when 'Site'
|
|
55
|
+
# Site overrides `custom_field_groups` to mean "every group this site owns" (foreign key
|
|
56
|
+
# parent_id), unlike the object_class + objectid association every other model receives from
|
|
57
|
+
# CommonRelationships. Scoping by placement here keeps groups meant for post types, themes or
|
|
58
|
+
# menus off the site settings form, where their required fields blocked the submit and their
|
|
59
|
+
# values were discarded on save anyway.
|
|
60
|
+
custom_field_groups.where(object_class: 'Site', objectid: id)
|
|
53
61
|
when 'PostType'
|
|
54
62
|
case args[:kind]
|
|
55
63
|
when 'all'
|
|
@@ -69,7 +77,9 @@ module CamaleonCms
|
|
|
69
77
|
# return collections CustomFieldGroup
|
|
70
78
|
# site: site object
|
|
71
79
|
def get_user_field_groups(site)
|
|
72
|
-
|
|
80
|
+
# Demodulized to match the association scope and the placement the settings form stores
|
|
81
|
+
# ('User' for a host Admin::User); parseCamaClass still strips a Draper Decorator suffix.
|
|
82
|
+
site.custom_field_groups.where(object_class: self.class.to_s.parseCamaClass.demodulize)
|
|
73
83
|
end
|
|
74
84
|
|
|
75
85
|
# get custom field value
|
|
@@ -82,7 +92,7 @@ module CamaleonCms
|
|
|
82
92
|
rescue StandardError
|
|
83
93
|
_default
|
|
84
94
|
end
|
|
85
|
-
v.
|
|
95
|
+
v.presence || _default
|
|
86
96
|
end
|
|
87
97
|
alias get_field get_field_value
|
|
88
98
|
alias get_field! get_field_value
|
|
@@ -117,12 +127,12 @@ module CamaleonCms
|
|
|
117
127
|
# puts res[0]['my_slug1'].first ==> "val 1"
|
|
118
128
|
def get_fields_grouped(field_keys)
|
|
119
129
|
res = []
|
|
120
|
-
custom_field_values.where(custom_field_slug: field_keys)
|
|
121
|
-
|
|
122
|
-
field_keys.
|
|
123
|
-
_tmp =
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
custom_field_values.where(custom_field_slug: field_keys)
|
|
131
|
+
.order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
|
|
132
|
+
group = field_keys.each_with_object({}) do |field_key, hsh|
|
|
133
|
+
_tmp = group_fields
|
|
134
|
+
.each_with_object([]) { |field, ary| ary << field.value if field_key == field.custom_field_slug }
|
|
135
|
+
hsh[field_key] = _tmp if _tmp.present?
|
|
126
136
|
end
|
|
127
137
|
res << group
|
|
128
138
|
end
|
|
@@ -131,7 +141,8 @@ module CamaleonCms
|
|
|
131
141
|
|
|
132
142
|
# return all values
|
|
133
143
|
# {key1: "single value", key2: [multiple, values], key3: value4} if include_options = false
|
|
134
|
-
# {key1: {values: "single value", options: {a:1, b: 4}}, key2: {values: [multiple, values], options: {a=1, b=2} }}
|
|
144
|
+
# {key1: {values: "single value", options: {a:1, b: 4}}, key2: {values: [multiple, values], options: {a=1, b=2} }}
|
|
145
|
+
# if include_options = true
|
|
135
146
|
def get_field_values_hash(include_options = false)
|
|
136
147
|
fields = {}
|
|
137
148
|
custom_field_values.to_a.uniq.each do |field_value|
|
|
@@ -159,9 +170,10 @@ module CamaleonCms
|
|
|
159
170
|
custom_field = field_value.custom_field
|
|
160
171
|
# if custom_field.options[:show_frontend].to_s.to_bool
|
|
161
172
|
values = custom_field.values.where(objectid: id).pluck(:value)
|
|
162
|
-
fields[field_value.custom_field_slug] =
|
|
163
|
-
|
|
164
|
-
|
|
173
|
+
fields[field_value.custom_field_slug] = custom_field.attributes.merge(
|
|
174
|
+
options: custom_field.cama_options,
|
|
175
|
+
values: custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first
|
|
176
|
+
)
|
|
165
177
|
# end
|
|
166
178
|
end
|
|
167
179
|
fields.to_sym
|
|
@@ -179,10 +191,11 @@ module CamaleonCms
|
|
|
179
191
|
# post_type.add_custom_field_group(values, kind = "Category")
|
|
180
192
|
# Note 2: If you need add fields for only the Post_type, you have to use options or metas
|
|
181
193
|
# return: CustomFieldGroup object
|
|
182
|
-
# kind: argument only for PostType model: (Post | Category | PostTag), default => Post.
|
|
194
|
+
# kind: argument only for PostType model: (Post | Category | PostTag), default => Post.
|
|
195
|
+
# If kind = "" this will add group for all post_types
|
|
183
196
|
def add_custom_field_group(values, kind = 'Post')
|
|
184
197
|
values = values.with_indifferent_access
|
|
185
|
-
group = get_field_groups(kind).
|
|
198
|
+
group = get_field_groups(kind).find_by_slug(values[:slug]) # rubocop:disable Rails/DynamicFindBy
|
|
186
199
|
unless group
|
|
187
200
|
site = _cama_get_field_site
|
|
188
201
|
values[:parent_id] = site.id if site.present?
|
|
@@ -202,7 +215,7 @@ module CamaleonCms
|
|
|
202
215
|
# more details in add_manual_field(item, options) from custom field groups
|
|
203
216
|
# kind: argument only for PostType model: (Post | Category | PostTag), default => Post
|
|
204
217
|
def add_custom_field_to_default_group(item, options, kind = 'Post')
|
|
205
|
-
g = get_field_groups(kind).
|
|
218
|
+
g = get_field_groups(kind).find_by_slug('_default') # rubocop:disable Rails/DynamicFindBy
|
|
206
219
|
g ||= add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind)
|
|
207
220
|
g.add_manual_field(item, options)
|
|
208
221
|
end
|
|
@@ -210,13 +223,8 @@ module CamaleonCms
|
|
|
210
223
|
|
|
211
224
|
# return field object for current model
|
|
212
225
|
def get_field_object(slug)
|
|
213
|
-
CamaleonCms::CustomField.where(
|
|
214
|
-
slug: slug,
|
|
215
|
-
parent_id: get_field_groups.pluck(:id)
|
|
216
|
-
).first || CamaleonCms::CustomField.where(
|
|
217
|
-
slug: slug,
|
|
218
|
-
parent_id: get_field_groups({ include_parent: true })
|
|
219
|
-
).first
|
|
226
|
+
CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups.pluck(:id)).first ||
|
|
227
|
+
CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups({ include_parent: true })).first
|
|
220
228
|
end
|
|
221
229
|
|
|
222
230
|
# save all fields sent from browser (reservated for browser request)
|
|
@@ -226,28 +234,57 @@ module CamaleonCms
|
|
|
226
234
|
# "1"=>{ "untitled-text-box"=>{"id"=>"262", "values"=>{"0"=>"33333"}}}
|
|
227
235
|
# }
|
|
228
236
|
def set_field_values(datas = {})
|
|
229
|
-
return
|
|
237
|
+
return if datas.blank?
|
|
230
238
|
|
|
231
239
|
ActiveRecord::Base.transaction do
|
|
240
|
+
# A value identical to one already stored is not newly authored, so it must not be re-gated on
|
|
241
|
+
# an unrelated edit (audit M8): the admin form round-trips every value and this method
|
|
242
|
+
# delete/recreates them all, so without this skip a single pre-gate dangerous value would fail
|
|
243
|
+
# the whole save and brick every later edit -- the trap Post#reject_untrusted_dangerous_content
|
|
244
|
+
# avoids via content_changed?. Snapshot the stored pairs before clearing; skip the gate for any
|
|
245
|
+
# recreated value that matches one.
|
|
246
|
+
previously_stored = custom_field_values.pluck(:custom_field_slug, :value).to_set
|
|
232
247
|
custom_field_values.delete_all
|
|
233
248
|
datas.each_value do |fields_data|
|
|
234
249
|
fields_data.each do |field_key, values|
|
|
235
|
-
next
|
|
250
|
+
next if values[:values].blank?
|
|
251
|
+
|
|
252
|
+
# Resolve the field id from the trusted slug, not the client-supplied values[:id]: a
|
|
253
|
+
# forged custom_field_id points the row at a different field definition, and the
|
|
254
|
+
# scan-and-reject gate keys off custom_field.options[:field_key] -- so a forged non-gated
|
|
255
|
+
# id would slip markup past the gate for a gated (editor/uri/field_attrs) slug. Fall back
|
|
256
|
+
# to values[:id] only when the slug names no field here (trusted/internal callers that
|
|
257
|
+
# pass slugs outside this object's registered groups; permitted browser payloads never do).
|
|
258
|
+
field_id = get_field_object(field_key)&.id || fallback_field_id_for(field_key) || values[:id]
|
|
259
|
+
group_number = [values[:group_number].to_i, 0].max
|
|
236
260
|
|
|
237
261
|
order_value = -1
|
|
238
|
-
(
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
(
|
|
263
|
+
if values[:values].is_a?(Hash) || values[:values].is_a?(ActionController::Parameters)
|
|
264
|
+
values[:values].values
|
|
265
|
+
else
|
|
266
|
+
values[:values]
|
|
267
|
+
end
|
|
268
|
+
).each do |value|
|
|
269
|
+
row = custom_field_values.new(
|
|
270
|
+
custom_field_id: field_id, custom_field_slug: field_key,
|
|
271
|
+
value: fix_meta_value(value), term_order: order_value += 1, group_number: group_number
|
|
272
|
+
)
|
|
273
|
+
row.unfiltered_value! if previously_stored.include?([field_key.to_s, row.value])
|
|
274
|
+
row.save!
|
|
241
275
|
end
|
|
242
276
|
end
|
|
243
277
|
end
|
|
244
278
|
end
|
|
245
279
|
end
|
|
246
280
|
|
|
247
|
-
#
|
|
281
|
+
# Update the value for the field with slug _key
|
|
248
282
|
# Sample: my_posy.update_field_value('sub_title', 'Test Sub Title')
|
|
249
|
-
def update_field_value(
|
|
250
|
-
|
|
283
|
+
def update_field_value(key, value = nil, group_number = 0)
|
|
284
|
+
# Route through validations so the scan-and-reject gate applies (audit M5): update_column
|
|
285
|
+
# skipped it, letting a dangerous value an untrusted caller supplies reach the frontend verbatim.
|
|
286
|
+
custom_field_values.find_by(custom_field_slug: key, group_number: group_number)
|
|
287
|
+
&.update(value: value)
|
|
251
288
|
rescue StandardError
|
|
252
289
|
nil
|
|
253
290
|
end
|
|
@@ -262,45 +299,65 @@ module CamaleonCms
|
|
|
262
299
|
|
|
263
300
|
# Set custom field values for current model (support for multiple group values)
|
|
264
301
|
# key: (string required) slug of the custom field
|
|
265
|
-
# value: (array | string) array: array of values for multiple values support,
|
|
302
|
+
# value: (array | string) array: array of values for multiple values support,
|
|
303
|
+
# string: uniq value for the custom field
|
|
266
304
|
# args:
|
|
267
305
|
# field_id: (integer optional) identifier of the custom field
|
|
268
306
|
# order: order or position of the field value
|
|
269
307
|
# group_number: number of the group (only for custom field group with is_repeat enabled)
|
|
270
|
-
# clear: (boolean, default true) if true, will remove previous values and set these values,
|
|
308
|
+
# clear: (boolean, default true) if true, will remove previous values and set these values,
|
|
309
|
+
# if not will append values
|
|
271
310
|
# return false if the was not saved because there is not present the field with slug: key
|
|
272
311
|
# sample: my_post.set_field_value('subtitle', 'Sub Title')
|
|
273
|
-
# sample:
|
|
312
|
+
# sample: set values for a field (for fields that support multiple values)
|
|
313
|
+
# my_post.set_field_value('subtitle', ['Sub Title1', 'Sub Title2'])
|
|
274
314
|
# sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1})
|
|
275
|
-
# sample:
|
|
315
|
+
# sample: add field values for fields in group 1
|
|
316
|
+
# my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1, group_number: 1})
|
|
276
317
|
def set_field_value(key, value, args = {})
|
|
277
318
|
args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge!(args)
|
|
278
|
-
|
|
319
|
+
if args[:field_id].blank?
|
|
279
320
|
args[:field_id] = begin
|
|
280
321
|
get_field_object(key).id
|
|
281
322
|
rescue StandardError
|
|
282
323
|
nil
|
|
283
324
|
end
|
|
284
325
|
end
|
|
285
|
-
|
|
326
|
+
args[:field_id] ||= fallback_field_id_for(key)
|
|
286
327
|
|
|
287
|
-
if args[:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
328
|
+
raise ArgumentError, "There is no custom field configured for #{key}" if args[:field_id].blank?
|
|
329
|
+
|
|
330
|
+
v = {
|
|
331
|
+
custom_field_id: args[:field_id], custom_field_slug: key, value: fix_meta_value(value),
|
|
332
|
+
term_order: args[:order], group_number: args[:group_number]
|
|
333
|
+
}
|
|
334
|
+
# Atomic (audit M7): clear the previous value and write the new one in one transaction, so a
|
|
335
|
+
# value the scan-and-reject gate refuses (create! -> RecordInvalid) rolls the delete back and
|
|
336
|
+
# the previously stored value survives instead of being destroyed.
|
|
337
|
+
ActiveRecord::Base.transaction do
|
|
338
|
+
if args[:clear]
|
|
339
|
+
custom_field_values.where({ custom_field_slug: key, group_number: args[:group_number] }).delete_all
|
|
340
|
+
end
|
|
341
|
+
if value.is_a?(Array)
|
|
342
|
+
value.each { |val| custom_field_values.create!(v.merge({ value: fix_meta_value(val) })) }
|
|
343
|
+
else
|
|
344
|
+
custom_field_values.create!(v)
|
|
296
345
|
end
|
|
297
|
-
else
|
|
298
|
-
custom_field_values.create!(v)
|
|
299
346
|
end
|
|
300
347
|
end
|
|
301
348
|
|
|
302
349
|
private
|
|
303
350
|
|
|
351
|
+
def fallback_field_id_for(key)
|
|
352
|
+
return unless self.class.to_s.parseCamaClass == 'Post'
|
|
353
|
+
return unless respond_to?(:post_type_id) && post_type_id.present?
|
|
354
|
+
|
|
355
|
+
group_ids = CamaleonCms::CustomFieldGroup.where(object_class: 'PostType_Post', objectid: post_type_id).pluck(:id)
|
|
356
|
+
return if group_ids.blank?
|
|
357
|
+
|
|
358
|
+
CamaleonCms::CustomField.where(slug: key, parent_id: group_ids).pick(:id)
|
|
359
|
+
end
|
|
360
|
+
|
|
304
361
|
def fix_meta_value(value)
|
|
305
362
|
return value.to_json if value.is_a?(ActionController::Parameters)
|
|
306
363
|
return JSON.generate(value) if value.is_a?(Array) || value.is_a?(Hash)
|
|
@@ -14,7 +14,25 @@ module CamaleonCms
|
|
|
14
14
|
# Add meta with value or Update meta with key: key
|
|
15
15
|
# return true or false
|
|
16
16
|
def set_meta(key, value)
|
|
17
|
-
|
|
17
|
+
fixed_value = fix_meta_value(value)
|
|
18
|
+
|
|
19
|
+
# Check if the parent object has been saved to the database yet
|
|
20
|
+
if persisted?
|
|
21
|
+
# Safe to use database-driven lookups and updates
|
|
22
|
+
meta_record = metas.find_or_create_by(key: key.to_s)
|
|
23
|
+
meta_record.update(value: fixed_value)
|
|
24
|
+
else
|
|
25
|
+
# In-Memory Fallback: Find an existing unsaved item in the array collection,
|
|
26
|
+
# or build a brand new unsaved record on the association.
|
|
27
|
+
meta_record = metas.find { |m| m.key == key.to_s }
|
|
28
|
+
|
|
29
|
+
if meta_record
|
|
30
|
+
meta_record.value = fixed_value
|
|
31
|
+
else
|
|
32
|
+
metas.build(key: key.to_s, value: fixed_value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
18
36
|
cama_set_cache("meta_#{key}", value)
|
|
19
37
|
end
|
|
20
38
|
|
|
@@ -23,7 +41,7 @@ module CamaleonCms
|
|
|
23
41
|
def get_meta(key, default = nil)
|
|
24
42
|
key_str = key.is_a?(Symbol) ? key.to_s : key
|
|
25
43
|
cama_fetch_cache("meta_#{key_str}") do
|
|
26
|
-
option = metas.loaded? ? metas.
|
|
44
|
+
option = metas.loaded? ? metas.find { |m| m.key == key_str } : metas.where(key: key_str).first
|
|
27
45
|
res = ''
|
|
28
46
|
if option.present?
|
|
29
47
|
value = begin
|
|
@@ -73,12 +91,16 @@ module CamaleonCms
|
|
|
73
91
|
# return value for attribute
|
|
74
92
|
def get_option(key = nil, default = nil, meta_key = '_default')
|
|
75
93
|
values = cama_options(meta_key)
|
|
94
|
+
return default if key.nil?
|
|
95
|
+
|
|
76
96
|
key = key.to_sym
|
|
77
97
|
values.key?(key) && values[key] != '' ? values[key] : default
|
|
78
98
|
end
|
|
79
99
|
|
|
80
100
|
# delete attribute from configuration
|
|
81
101
|
def delete_option(key, meta_key = '_default')
|
|
102
|
+
return if key.nil?
|
|
103
|
+
|
|
82
104
|
values = cama_options(meta_key)
|
|
83
105
|
key = key.to_sym
|
|
84
106
|
values.delete(key) if values.key?(key)
|
|
@@ -88,7 +110,7 @@ module CamaleonCms
|
|
|
88
110
|
# set multiple configurations
|
|
89
111
|
# h: {ket1: "sdsds", ff: "fdfdfdfd"}
|
|
90
112
|
def set_options(h = {}, meta_key = '_default')
|
|
91
|
-
return
|
|
113
|
+
return if h.blank?
|
|
92
114
|
|
|
93
115
|
data = cama_options(meta_key)
|
|
94
116
|
PluginRoutes.fixActionParameter(h).to_sym.each do |key, value|
|
|
@@ -116,11 +138,14 @@ module CamaleonCms
|
|
|
116
138
|
save_metas_options # unless self.changed?
|
|
117
139
|
end
|
|
118
140
|
|
|
119
|
-
#
|
|
120
|
-
# sample:
|
|
141
|
+
# Save all settings for this post type - received in data_options and data_metas attribute (options and metas)
|
|
142
|
+
# sample:
|
|
143
|
+
# Site.first.post_types.create(
|
|
144
|
+
# {name: "owen", slug: "my_post_type", data_options: { has_category: true, default_layout: "my_layout" }}
|
|
145
|
+
# )
|
|
121
146
|
def save_metas_options
|
|
122
147
|
set_multiple_options(data_options)
|
|
123
|
-
return
|
|
148
|
+
return if data_metas.blank?
|
|
124
149
|
|
|
125
150
|
data_metas.each do |key, val|
|
|
126
151
|
set_meta(key, val)
|
|
@@ -9,17 +9,11 @@ module CamaleonCms
|
|
|
9
9
|
args.each do |attr|
|
|
10
10
|
next unless new_record? || attribute_changed?(attr)
|
|
11
11
|
|
|
12
|
-
self[attr] =
|
|
13
|
-
__send__(attr)&.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
|
|
14
|
-
)&.gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
|
|
12
|
+
self[attr] = CamaleonRecord.cama_sanitize_translatable(__send__(attr))
|
|
15
13
|
end
|
|
16
14
|
end
|
|
17
15
|
else
|
|
18
|
-
normalizes(*args, with:
|
|
19
|
-
ActionController::Base.helpers.sanitize(
|
|
20
|
-
field.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
|
|
21
|
-
).gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
|
|
22
|
-
})
|
|
16
|
+
normalizes(*args, with: ->(field) { CamaleonRecord.cama_sanitize_translatable(field) })
|
|
23
17
|
end
|
|
24
18
|
end
|
|
25
19
|
end
|