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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# rubocop:disable Rails/OutputSafety
|
|
2
|
+
module CamaleonCms
|
|
3
|
+
module RuntimeHtmlContentConcern
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Single source of truth for the cama_content_* surface (prepend/append/draw).
|
|
7
|
+
# Including it here keeps those methods reachable from controllers (e.g. plugin
|
|
8
|
+
# hooks running in controller context call cama_content_append) while the shared
|
|
9
|
+
# view helper stays ivar-free.
|
|
10
|
+
include CamaleonCms::ContentHelper
|
|
11
|
+
|
|
12
|
+
def cama_html_helpers_init
|
|
13
|
+
state = camaleon_html_helper_state
|
|
14
|
+
state[:pre_assets_content] = []
|
|
15
|
+
state[:assets_libraries] = {}
|
|
16
|
+
state[:assets_content] = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cama_load_libraries(*keys)
|
|
20
|
+
state = camaleon_html_helper_state
|
|
21
|
+
keys.each do |key|
|
|
22
|
+
library = cama_assets_libraries[key.to_sym]
|
|
23
|
+
state[:assets_libraries][key.to_sym] = library if library.present?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
alias add_asset_library cama_load_libraries
|
|
27
|
+
|
|
28
|
+
def append_asset_libraries(libraries)
|
|
29
|
+
state = camaleon_html_helper_state
|
|
30
|
+
libraries.each do |key, library|
|
|
31
|
+
state[:assets_libraries][key.to_sym] = if state[:assets_libraries].include?(key)
|
|
32
|
+
state[:assets_libraries][key.to_sym].merge(library)
|
|
33
|
+
else
|
|
34
|
+
library
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
alias cama_load_custom_assets append_asset_libraries
|
|
39
|
+
|
|
40
|
+
def append_asset_content(content)
|
|
41
|
+
camaleon_html_helper_state[:assets_content] << content
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def append_pre_asset_content(content)
|
|
45
|
+
camaleon_html_helper_state[:pre_assets_content] << content
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cama_draw_pre_asset_contents
|
|
49
|
+
camaleon_html_helper_state[:pre_assets_content].join('').html_safe
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cama_draw_custom_assets
|
|
53
|
+
state = camaleon_html_helper_state
|
|
54
|
+
cama_html_helpers_init if state[:assets_libraries].blank?
|
|
55
|
+
libs = []
|
|
56
|
+
state[:assets_libraries].each_value do |assets|
|
|
57
|
+
libs += assets[:css] if assets[:css].present?
|
|
58
|
+
end
|
|
59
|
+
stylesheets = libs.uniq
|
|
60
|
+
css = helpers.stylesheet_link_tag(*stylesheets, media: 'all')
|
|
61
|
+
|
|
62
|
+
libs = []
|
|
63
|
+
state[:assets_libraries].each_value do |assets|
|
|
64
|
+
libs += assets[:js] if assets[:js].present?
|
|
65
|
+
end
|
|
66
|
+
javascripts = libs.uniq
|
|
67
|
+
js = helpers.javascript_include_tag(*javascripts)
|
|
68
|
+
|
|
69
|
+
args = { stylesheets: stylesheets, javascripts: javascripts, js_html: js, css_html: css }
|
|
70
|
+
hooks_run('draw_custom_assets', args)
|
|
71
|
+
trusted_fragments = [args[:css_html], args[:js_html], *state[:assets_content]].filter_map do |fragment|
|
|
72
|
+
next if fragment.blank?
|
|
73
|
+
|
|
74
|
+
fragment.is_a?(ActiveSupport::SafeBuffer) ? fragment : fragment.to_s.html_safe
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
helpers.safe_join(trusted_fragments, "\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def theme_init
|
|
81
|
+
breadcrumb_items = []
|
|
82
|
+
camaleon_theme_state[:front_breadcrumb] = breadcrumb_items
|
|
83
|
+
# back-compat ivar: themes/plugins may read @_front_breadcrumb directly.
|
|
84
|
+
# Owned here (controller concern) so the shared view helper stays ivar-free.
|
|
85
|
+
@_front_breadcrumb = breadcrumb_items
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def breadcrumb_add(label, url = '', prepend = false)
|
|
89
|
+
breadcrumb_items = nav_menu_breadcrumb_items
|
|
90
|
+
if prepend
|
|
91
|
+
breadcrumb_items.unshift([label, url])
|
|
92
|
+
else
|
|
93
|
+
breadcrumb_items << [label, url]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def camaleon_html_helper_state
|
|
100
|
+
state = CurrentRequest.html_helper_state ||= {}
|
|
101
|
+
state[:pre_assets_content] ||= []
|
|
102
|
+
state[:assets_libraries] ||= {}
|
|
103
|
+
state[:assets_content] ||= []
|
|
104
|
+
state
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def cama_assets_libraries
|
|
108
|
+
state = camaleon_html_helper_state
|
|
109
|
+
return state[:cama_assets_libraries] if state[:cama_assets_libraries].present?
|
|
110
|
+
|
|
111
|
+
libs = {}
|
|
112
|
+
libs[:colorpicker] =
|
|
113
|
+
{ js: ['camaleon_cms/admin/bootstrap-colorpicker'], css: ['camaleon_cms/admin/colorpicker'] }
|
|
114
|
+
libs[:datepicker] = { js: [] }
|
|
115
|
+
libs[:datetimepicker] = { js: [], css: [] }
|
|
116
|
+
libs[:tinymce] =
|
|
117
|
+
{ js: %w[camaleon_cms/admin/tinymce/tinymce.min camaleon_cms/admin/tinymce/plugins/filemanager/plugin.min],
|
|
118
|
+
css: ['camaleon_cms/admin/tinymce/skins/lightgray/content.min'] }
|
|
119
|
+
libs[:form_ajax] = { js: ['camaleon_cms/admin/form/jquery.form'] }
|
|
120
|
+
libs[:cropper] = {}
|
|
121
|
+
libs[:post] =
|
|
122
|
+
{ js: %w[camaleon_cms/admin/jquery.tagsinput.min camaleon_cms/admin/post],
|
|
123
|
+
css: ['camaleon_cms/admin/jquery.tagsinput'] }
|
|
124
|
+
libs[:multiselect] = { js: ['camaleon_cms/admin/bootstrap-select.js'] }
|
|
125
|
+
libs[:validate] = { js: ['camaleon_cms/admin/jquery.validate'] }
|
|
126
|
+
libs[:nav_menu] =
|
|
127
|
+
{ css: ['camaleon_cms/admin/nestable/jquery.nestable'],
|
|
128
|
+
js: %w[camaleon_cms/admin/jquery.nestable camaleon_cms/admin/nav_menu] }
|
|
129
|
+
libs[:admin_intro] =
|
|
130
|
+
{ js: ['camaleon_cms/admin/introjs/intro.min'], css: ['camaleon_cms/admin/introjs/introjs.min'] }
|
|
131
|
+
state[:cama_assets_libraries] = libs
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def camaleon_theme_state
|
|
135
|
+
CurrentRequest.theme_helper_state ||= {}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def nav_menu_breadcrumb_items
|
|
139
|
+
camaleon_theme_state[:front_breadcrumb] ||= []
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
# rubocop:enable Rails/OutputSafety
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# rubocop:disable Layout/LineLength
|
|
2
|
+
module CamaleonCms
|
|
3
|
+
module RuntimeShortcodeThemeConcern
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Theme asset/view helpers are owned by CamaleonCms::ThemeHelper (single
|
|
7
|
+
# source of truth shared with views). Including it here exposes them on the
|
|
8
|
+
# controller runtime stack so plugin/theme hooks executed in controller
|
|
9
|
+
# context (e.g. `on_install_theme`) can call `theme_asset`, `theme_view`,
|
|
10
|
+
# `theme_asset_path`, etc. ThemeHelper is ivar-free, so it is safe to mix in.
|
|
11
|
+
include CamaleonCms::ThemeHelper
|
|
12
|
+
|
|
13
|
+
def shortcodes_init
|
|
14
|
+
CurrentRequest.shortcodes = []
|
|
15
|
+
CurrentRequest.shortcodes_template = {}
|
|
16
|
+
CurrentRequest.shortcodes_descr = {}
|
|
17
|
+
|
|
18
|
+
shortcode_add('widget', nil, "Renderize the widget content in this place.
|
|
19
|
+
Don't forget to create and copy the shortcode of your widgets in appearance -> widgets
|
|
20
|
+
Sample: [widget widget_key]")
|
|
21
|
+
|
|
22
|
+
shortcode_add(
|
|
23
|
+
'load_libraries',
|
|
24
|
+
lambda do |attrs, args|
|
|
25
|
+
return args[:shortcode] if attrs.blank?
|
|
26
|
+
|
|
27
|
+
cama_load_libraries(*attrs['data'].to_s.split(','))
|
|
28
|
+
''
|
|
29
|
+
end,
|
|
30
|
+
"Permit to load libraries on demand, sample: [load_libraries data='datepicker,tinymce']"
|
|
31
|
+
)
|
|
32
|
+
shortcode_add(
|
|
33
|
+
'asset',
|
|
34
|
+
lambda do |attrs, args|
|
|
35
|
+
return args[:shortcode] if attrs.blank?
|
|
36
|
+
|
|
37
|
+
url = shortcode_asset_reference(attrs['file'], as_path: attrs['as_path'].present?)
|
|
38
|
+
if attrs['image'].present?
|
|
39
|
+
ActionController::Base.helpers.image_tag(url, class: attrs['class'], style: attrs['style'])
|
|
40
|
+
else
|
|
41
|
+
url
|
|
42
|
+
end
|
|
43
|
+
end,
|
|
44
|
+
"Permit to generate an asset url (
|
|
45
|
+
add file='' asset file path,
|
|
46
|
+
add as_path='true' to generate only the path and not the full url,
|
|
47
|
+
add class='my_class' to setup image class,
|
|
48
|
+
add style='height: 100px; width: 200px;...' to setup image style,
|
|
49
|
+
add image='true' to generate the image tag with this url),
|
|
50
|
+
sample: <img src=\"[asset as_path='true' file='themes/my_theme/assets/img/signature.png']\" /> or [asset image='true' file='themes/my_theme/assets/img/signature.png' style='height: 50px;']"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
shortcode_add(
|
|
54
|
+
'data',
|
|
55
|
+
->(attrs, args) { attrs.present? ? cama_shortcode_data(attrs, args) : args[:shortcode] },
|
|
56
|
+
"Permit to generate specific data of a post.
|
|
57
|
+
Attributes:
|
|
58
|
+
object: (String, defaut post) Model name: post | posttype | category | posttag | site | user |theme | navmenu
|
|
59
|
+
id: (Integer) Post id
|
|
60
|
+
key: (String) Post slug
|
|
61
|
+
field: (String) Custom field key, you can add render_field='true' to render field as html element, also you can add index=2 to indicate the value in position 2 for multitple values
|
|
62
|
+
attrs: (String) attribute name
|
|
63
|
+
post: title | created_at | excerpt | url | link | thumb | updated_at | author_name | author_url | content
|
|
64
|
+
posttype: title | created_at | excerpt | url | link | thumb | updated_at
|
|
65
|
+
category: title | created_at | excerpt | url | link | thumb | updated_at
|
|
66
|
+
posttag: title | created_at | excerpt | url | link | thumb | updated_at
|
|
67
|
+
site: title | created_at | excerpt | url | link | thumb | updated_at
|
|
68
|
+
user: title | created_at | excerpt | url | link | thumb | updated_at
|
|
69
|
+
theme: title | created_at | excerpt | url | link | thumb | updated_at
|
|
70
|
+
navmenu: title | created_at | excerpt | url | link | thumb | updated_at
|
|
71
|
+
Note: If id and key is not present, then will be rendered for current model
|
|
72
|
+
Sample:
|
|
73
|
+
[data id='122' attr='title'] ==> return the title of post with id = 122
|
|
74
|
+
[data key='contact' attr='url'] ==> return the url of post with slug = contact
|
|
75
|
+
[data key='contact' attr='link'] ==> return the link with title as text of the link of post with slug = contact
|
|
76
|
+
[data object='site' attr='url'] ==> return the url of currrent site
|
|
77
|
+
[data key='page' object='posttype' attr='url'] ==> return the url of post_type with slug = page
|
|
78
|
+
[data field=icon index=2] ==> return the second value (multiple values) for this custom field with key=icon of current object
|
|
79
|
+
[data key='contact' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for post.slug = contact
|
|
80
|
+
[data object='site' field='sub_title'] ==> return the value of the custom_field with key=sub_title registered for current_site"
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def shortcode_add(key, template = nil, descr = '')
|
|
85
|
+
shortcode_keys << key
|
|
86
|
+
CurrentRequest.shortcodes_template = shortcode_templates.merge({ key.to_s => template }) if template.present?
|
|
87
|
+
shortcode_descriptions[key] = descr if descr.present?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def shortcode_keys
|
|
91
|
+
CurrentRequest.shortcodes ||= []
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def shortcode_templates
|
|
95
|
+
CurrentRequest.shortcodes_template ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def shortcode_descriptions
|
|
99
|
+
CurrentRequest.shortcodes_descr ||= {}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def shortcode_asset_reference(file, as_path: false)
|
|
103
|
+
return if file.blank?
|
|
104
|
+
|
|
105
|
+
# Defensive ordering: assign the locals the rescue branch uses before everything else, so it
|
|
106
|
+
# can never see them nil. The only call that raises AssetNotFound is the asset helper below —
|
|
107
|
+
# resolve_shortcode_theme_asset never does (regex, string interpolation and File.exist? only).
|
|
108
|
+
helper = ActionController::Base.helpers
|
|
109
|
+
method_name = as_path ? :asset_path : :asset_url
|
|
110
|
+
file = resolve_shortcode_theme_asset(file)
|
|
111
|
+
helper.public_send(method_name, file)
|
|
112
|
+
rescue Sprockets::Rails::Helper::AssetNotFound
|
|
113
|
+
helper.public_send(method_name, file, skip_pipeline: true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def resolve_shortcode_theme_asset(file)
|
|
117
|
+
# Tolerate both `themes/<slug>/assets/...` and `/themes/<slug>/assets/...` — the latter is
|
|
118
|
+
# what Rails' asset helpers typically emit.
|
|
119
|
+
match = file.to_s.match(%r{\A/?themes/[^/]+/assets/(?<asset>.+)\z})
|
|
120
|
+
return file unless match
|
|
121
|
+
|
|
122
|
+
can_resolve_theme_assets = respond_to?(:current_theme) &&
|
|
123
|
+
respond_to?(:theme_asset_path) &&
|
|
124
|
+
respond_to?(:theme_asset_file_path)
|
|
125
|
+
return file unless can_resolve_theme_assets
|
|
126
|
+
return file if current_theme.blank?
|
|
127
|
+
|
|
128
|
+
asset = match[:asset]
|
|
129
|
+
remapped_file = theme_asset_path(asset)
|
|
130
|
+
return file unless remapped_file.present? && remapped_file != file
|
|
131
|
+
return file unless File.exist?(theme_asset_file_path(asset))
|
|
132
|
+
|
|
133
|
+
remapped_file
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def cama_shortcode_data(attrs, args)
|
|
137
|
+
res = args[:shortcode]
|
|
138
|
+
object = (attrs['object'].presence || 'post').downcase
|
|
139
|
+
attr = attrs['attr'] || 'title'
|
|
140
|
+
model = if attrs['id'].present? || attrs['key'].present?
|
|
141
|
+
cama_shortcode_model_parser(object, attrs)
|
|
142
|
+
else
|
|
143
|
+
args[:owner]
|
|
144
|
+
end
|
|
145
|
+
return res if model.blank?
|
|
146
|
+
|
|
147
|
+
if attrs['field'].present?
|
|
148
|
+
field = model.get_field_object(attrs['field'])
|
|
149
|
+
if attrs['render_field'].present?
|
|
150
|
+
return render_to_string(template: "custom_fields/#{field.options['field_key']}", layout: false,
|
|
151
|
+
locals: { object: model, field: field, field_key: attrs['field'], attibutes: attrs })
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
res = if attrs['index']
|
|
155
|
+
begin
|
|
156
|
+
model.the_fields(attrs['field'])[attrs['index'].to_i - 1]
|
|
157
|
+
rescue StandardError
|
|
158
|
+
''
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
model.the_field(attrs['field'])
|
|
162
|
+
end
|
|
163
|
+
return res
|
|
164
|
+
else
|
|
165
|
+
case attr
|
|
166
|
+
when 'title'
|
|
167
|
+
res = model.the_title
|
|
168
|
+
when 'created_at'
|
|
169
|
+
res = model.the_created_at
|
|
170
|
+
when 'updated_at'
|
|
171
|
+
res = model.the_updated_at
|
|
172
|
+
when 'excerpt'
|
|
173
|
+
res = begin
|
|
174
|
+
model.the_excerpt
|
|
175
|
+
rescue StandardError
|
|
176
|
+
''
|
|
177
|
+
end
|
|
178
|
+
when 'url'
|
|
179
|
+
res = begin
|
|
180
|
+
model.the_url
|
|
181
|
+
rescue StandardError
|
|
182
|
+
''
|
|
183
|
+
end
|
|
184
|
+
when 'link'
|
|
185
|
+
res = begin
|
|
186
|
+
model.the_link
|
|
187
|
+
rescue StandardError
|
|
188
|
+
''
|
|
189
|
+
end
|
|
190
|
+
when 'thumb'
|
|
191
|
+
res = case object
|
|
192
|
+
when 'site'
|
|
193
|
+
model.the_logo
|
|
194
|
+
when 'user'
|
|
195
|
+
model.the_avatar
|
|
196
|
+
else
|
|
197
|
+
model.the_thumb_url
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
case object
|
|
201
|
+
when 'post'
|
|
202
|
+
case attr
|
|
203
|
+
when 'content'
|
|
204
|
+
res = model.try(:the_content)
|
|
205
|
+
when 'author_name'
|
|
206
|
+
res = model.try(:the_author).try(:the_name)
|
|
207
|
+
when 'author_url'
|
|
208
|
+
res = model.try(:the_author).try(:the_name)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
res
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def cama_shortcode_model_parser(object, attrs)
|
|
217
|
+
model = nil
|
|
218
|
+
case object.downcase
|
|
219
|
+
when 'post'
|
|
220
|
+
model = current_site.the_post(attrs['id'].to_i) if attrs['id'].present?
|
|
221
|
+
model = current_site.the_post(attrs['key'].to_s) if attrs['key'].present?
|
|
222
|
+
when 'posttype'
|
|
223
|
+
model = current_site.the_post_type(attrs['id'].to_i) if attrs['id'].present?
|
|
224
|
+
model = current_site.the_post_type(attrs['key'].to_s) if attrs['key'].present?
|
|
225
|
+
when 'category'
|
|
226
|
+
model = current_site.the_category(attrs['id'].to_i) if attrs['id'].present?
|
|
227
|
+
model = current_site.the_category(attrs['key'].to_s) if attrs['key'].present?
|
|
228
|
+
when 'posttag'
|
|
229
|
+
model = current_site.the_tag(attrs['id'].to_i) if attrs['id'].present?
|
|
230
|
+
model = current_site.the_tag(attrs['key'].to_s) if attrs['key'].present?
|
|
231
|
+
when 'site'
|
|
232
|
+
model = current_site
|
|
233
|
+
when 'theme'
|
|
234
|
+
model = current_theme
|
|
235
|
+
when 'navmenu'
|
|
236
|
+
model = current_site.nav_menu_items.find(attrs['id']) if attrs['id'].present?
|
|
237
|
+
model = current_site.nav_menu_items.find_by_slug(attrs['key']) if attrs['key'].present? # rubocop:disable Rails/DynamicFindBy
|
|
238
|
+
when 'user'
|
|
239
|
+
model = current_site.the_user(attrs['id'].to_i) if attrs['id'].present?
|
|
240
|
+
model = current_site.the_user(attrs['key'].to_s) if attrs['key'].present?
|
|
241
|
+
end
|
|
242
|
+
model
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
# rubocop:enable Layout/LineLength
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module CamaleonCms
|
|
2
|
+
module RuntimeStateConcern
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
include CamaleonCms::RuntimeShortcodeThemeConcern
|
|
6
|
+
include CamaleonCms::RuntimeHtmlContentConcern
|
|
7
|
+
include CamaleonCms::RuntimeAdminMenuConcern
|
|
8
|
+
include CamaleonCms::RuntimeCaptchaImageConcern
|
|
9
|
+
include CamaleonCms::RuntimeUploaderConcern
|
|
10
|
+
|
|
11
|
+
delegate :tag, :content_tag, :safe_join, :image_tag, :link_to, :sanitize, to: :helpers
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module CamaleonCms
|
|
2
|
+
# Uploader entry point for controllers (mixed into CamaleonCms::CamaleonController and
|
|
3
|
+
# re-exported through RuntimeStateConcern). The implementation lives in the shared
|
|
4
|
+
# modules below, which CamaleonCms::UploaderHelper includes as well, so the two entry
|
|
5
|
+
# points cannot drift. Only cama_upload_url_error is controller-specific.
|
|
6
|
+
module RuntimeUploaderConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
include UploaderContentSecurity
|
|
10
|
+
include UploaderPathSecurity
|
|
11
|
+
include UploaderPipeline
|
|
12
|
+
include UploaderImageProcessing
|
|
13
|
+
include UploaderSupport
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Route upload messages through `ct` when the host provides it — the media controllers do,
|
|
18
|
+
# since #1223 restored CamaleonHelper (and its `ct`) to the controller chain — so the
|
|
19
|
+
# on_translation hook that lets plugins override upload error text runs on the controller
|
|
20
|
+
# path too. Non-controller includers (ActiveJobs, standalone objects) have no `ct` and keep
|
|
21
|
+
# the pipeline's I18n default via super. The default (no hook) text is identical either way:
|
|
22
|
+
# `ct`'s default arm is I18n.translate("camaleon_cms.common.#{key}", **args).
|
|
23
|
+
def cama_uploader_ct(key, args = {})
|
|
24
|
+
return ct(key, args) if respond_to?(:ct, true)
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Validates a user-supplied upload URL and returns nil when it is acceptable,
|
|
30
|
+
# or an error String to render. Only http(s) URLs are validated here; other
|
|
31
|
+
# inputs (local paths, data: URIs) are handled by the sink guards in
|
|
32
|
+
# cama_tmp_upload/upload_file.
|
|
33
|
+
#
|
|
34
|
+
# Same-site URLs are mapped to a local file and read directly (no network
|
|
35
|
+
# request), so they only need the static path-traversal check — not DNS
|
|
36
|
+
# resolution, SSRF/private-IP blocking, or HTML-tag sanitizing. Applying those
|
|
37
|
+
# to a same-site URL wrongly rejects legitimate values: hosts that resolve to a
|
|
38
|
+
# loopback/private IP (localhost, Docker, intranet), non-resolvable dev hosts,
|
|
39
|
+
# and multi-parameter query strings (the "&" is escaped by sanitize). Remote
|
|
40
|
+
# URLs, which are actually fetched, still get the full SSRF validation.
|
|
41
|
+
def cama_upload_url_error(url)
|
|
42
|
+
return unless url.to_s.start_with?('http://', 'https://')
|
|
43
|
+
|
|
44
|
+
result = if same_site_url?(url, current_site)
|
|
45
|
+
UserUrlValidator.validate(url, reject_path_traversal: true, resolve: false,
|
|
46
|
+
enforce_sanitizing: false, enforce_user: false,
|
|
47
|
+
allow_localhost: true, allow_local_network: true)
|
|
48
|
+
else
|
|
49
|
+
UserUrlValidator.validate(url, reject_path_traversal: true)
|
|
50
|
+
end
|
|
51
|
+
result.join(', ') if result.is_a?(Array)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module CamaleonCms
|
|
2
|
+
# Wires the captcha helper into the runtime controller stack. Tag building,
|
|
3
|
+
# verification and the under-attack session counters live only in
|
|
4
|
+
# CamaleonCms::CaptchaHelper (shared with views); challenge/image generation
|
|
5
|
+
# lives in CamaleonCms::CaptchaImageGeneration, which both the helper and the
|
|
6
|
+
# controller-stack RuntimeCaptchaImageConcern include (parity-guarded), so the
|
|
7
|
+
# controller and view contexts cannot drift apart.
|
|
8
|
+
#
|
|
9
|
+
# CaptchaHelper is ivar-free (it only touches session/params/current_site and
|
|
10
|
+
# ActionController::Base.helpers), so it is safe to mix into the controller.
|
|
11
|
+
# Including it here restores `cama_captcha_tag`/`cama_captcha_build` on the
|
|
12
|
+
# controller runtime stack so shortcodes/forms rendered in controller context
|
|
13
|
+
# (e.g. the contact-form plugin) can call them.
|
|
14
|
+
module SessionCaptchaRuntimeConcern
|
|
15
|
+
extend ActiveSupport::Concern
|
|
16
|
+
|
|
17
|
+
include CamaleonCms::CaptchaHelper
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module CamaleonCms
|
|
2
|
+
# Wires the session/auth and email helpers into the runtime controller stack.
|
|
3
|
+
# Behaviour lives in the helper modules (single source of truth) so view and
|
|
4
|
+
# controller contexts cannot drift apart.
|
|
5
|
+
module SessionRuntimeConcern
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
include CamaleonCms::SessionHelper
|
|
9
|
+
include CamaleonCms::EmailHelper
|
|
10
|
+
include CamaleonCms::SessionCaptchaRuntimeConcern
|
|
11
|
+
|
|
12
|
+
# When the host application does not define `current_user`, fall back to
|
|
13
|
+
# Camaleon's own user lookup so `helper_method :current_user` keeps working.
|
|
14
|
+
included do
|
|
15
|
+
define_method(:current_user) { cama_current_user } unless ApplicationController.method_defined?(:current_user)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Back-compat: expose the affected user record as the `@user` controller ivar
|
|
19
|
+
# for legacy templates/plugins. The helper methods themselves stay ivar-free;
|
|
20
|
+
# these controller-concern overrides own the instance-variable bridging so the
|
|
21
|
+
# shared view helpers never pollute the view context.
|
|
22
|
+
def login_user_with_password(username, password)
|
|
23
|
+
result = super
|
|
24
|
+
@user = current_site.users.find_by(username: username)
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cama_register_user(user_data, meta)
|
|
29
|
+
result = super
|
|
30
|
+
@user = result[:user] if result.is_a?(Hash) && result.key?(:user)
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Redirect to login when the session has expired. Lives here because it is a
|
|
35
|
+
# controller-only redirect concern (not a view helper).
|
|
36
|
+
def auth_session_error
|
|
37
|
+
redirect_to cama_root_path
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -15,7 +15,7 @@ module CamaleonCms
|
|
|
15
15
|
# return the keywords for this model
|
|
16
16
|
def the_keywords
|
|
17
17
|
k = object.get_option('keywords', '')
|
|
18
|
-
k = h.current_site.the_keywords if object.class.name != 'CamaleonCms::Site' &&
|
|
18
|
+
k = h.current_site.the_keywords if object.class.name != 'CamaleonCms::Site' && k.blank?
|
|
19
19
|
k.to_s.translate(get_locale)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -50,22 +50,25 @@ module CamaleonCms
|
|
|
50
50
|
@_deco_locale = locale.to_sym
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
#
|
|
53
|
+
# Admin requests can render frontend URLs from the dashboard, so prefer the
|
|
54
|
+
# cached frontend locale before falling back to the current I18n locale.
|
|
54
55
|
def get_locale(locale = nil)
|
|
55
|
-
|
|
56
|
+
frontend_locale = begin
|
|
56
57
|
h.cama_get_i18n_frontend
|
|
57
|
-
rescue
|
|
58
|
+
rescue NoMethodError
|
|
58
59
|
nil
|
|
59
|
-
end
|
|
60
|
+
end
|
|
61
|
+
locale || @_deco_locale || frontend_locale || I18n.locale
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
# return the current locale prefixed to add in frontend routes
|
|
63
65
|
def _calc_locale(_l)
|
|
64
|
-
|
|
66
|
+
frontend_locale = begin
|
|
65
67
|
h.cama_get_i18n_frontend
|
|
66
|
-
rescue
|
|
68
|
+
rescue NoMethodError
|
|
67
69
|
nil
|
|
68
|
-
end
|
|
70
|
+
end
|
|
71
|
+
_l = (_l || @_deco_locale || frontend_locale || I18n.locale).to_s
|
|
69
72
|
"_#{_l}"
|
|
70
73
|
end
|
|
71
74
|
end
|
|
@@ -19,7 +19,8 @@ module CamaleonCms
|
|
|
19
19
|
h.edit_cama_admin_post_type_category_url(object.post_type.id, object)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# return all children categories for the current
|
|
22
|
+
# return all children categories for the current
|
|
23
|
+
# category (active_record) filtered by permissions + hidden posts + roles + etc...
|
|
23
24
|
# in return object, you can add custom where's or pagination like here:
|
|
24
25
|
# https://edgeguides.rubyonrails.org/active_record_querying.html
|
|
25
26
|
def the_categories
|
|
@@ -30,7 +31,7 @@ module CamaleonCms
|
|
|
30
31
|
def the_category(slug_or_id)
|
|
31
32
|
return object.categories.where(id: slug_or_id).first if slug_or_id.is_a?(Integer)
|
|
32
33
|
|
|
33
|
-
object.categories.find_by_slug(slug_or_id) if slug_or_id.is_a?(String)
|
|
34
|
+
object.categories.find_by_slug(slug_or_id) if slug_or_id.is_a?(String) # rubocop:disable Rails/DynamicFindBy
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# ---------------------
|
|
@@ -7,9 +7,9 @@ module CamaleonCms
|
|
|
7
7
|
h.l(object.created_at, format: format.to_sym)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
# return owner of this comment
|
|
10
|
+
# return owner of this comment, or nil for rows whose user is missing
|
|
11
11
|
def the_user
|
|
12
|
-
object.user
|
|
12
|
+
object.user&.decorate
|
|
13
13
|
end
|
|
14
14
|
alias the_author the_user
|
|
15
15
|
|
|
@@ -26,15 +26,23 @@ module CamaleonCms
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def the_author_name
|
|
29
|
-
|
|
29
|
+
# `fullname` is the model method; `full_name` never existed, so this fallback
|
|
30
|
+
# crashed for any comment stored without an author string.
|
|
31
|
+
object.author.presence || object.user&.fullname
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def the_author_email
|
|
33
|
-
object.author_email.presence || object.user
|
|
35
|
+
object.author_email.presence || object.user&.email
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# A missing user reads the same as the anonymous one: there is no profile to link to.
|
|
39
|
+
# Its three siblings above were made nil-safe for orphaned rows; this one was missed, so it
|
|
40
|
+
# still raised NoMethodError on exactly the rows they were fixed to tolerate.
|
|
36
41
|
def the_author_url
|
|
37
|
-
object.author_url
|
|
42
|
+
return object.author_url if object.author_url.present?
|
|
43
|
+
return '' if object.user.blank? || object.user.username == 'anonymous'
|
|
44
|
+
|
|
45
|
+
object.user.decorate.the_url
|
|
38
46
|
end
|
|
39
47
|
end
|
|
40
48
|
end
|