camaleon_cms 2.9.2 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -4
  3. data/Rakefile +3 -16
  4. data/app/apps/plugins/attack/admin_controller.rb +9 -6
  5. data/app/apps/plugins/attack/attack_helper.rb +35 -25
  6. data/app/apps/plugins/authoring_post/authoring_post_helper.rb +23 -16
  7. data/app/apps/plugins/front_cache/admin_controller.rb +15 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/front_cache_helper.rb +33 -9
  10. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  11. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  12. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  13. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  14. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  15. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  16. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  17. data/app/apps/themes/default/default_helper.rb +2 -1
  18. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  19. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  20. data/app/apps/themes/new/custom_helper.rb +5 -9
  21. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  22. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  23. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  24. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  25. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  26. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  27. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  28. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  29. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  30. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  31. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  32. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  33. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  34. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  35. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  36. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  37. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  38. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  39. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +76 -13
  40. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  41. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  42. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  43. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  44. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  45. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  46. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  47. data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
  48. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  49. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  50. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  51. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  52. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  53. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  54. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  59. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  60. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  61. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  62. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  63. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  64. data/app/decorators/camaleon_cms/post_decorator.rb +66 -50
  65. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  66. data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
  67. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  68. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  69. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  70. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  71. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  72. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  73. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  74. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  75. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  76. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  77. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  78. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  79. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  80. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  81. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  82. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  83. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  84. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  85. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  86. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  87. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  88. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  89. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  90. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  91. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  92. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  93. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  94. data/app/models/camaleon_cms/ability.rb +15 -7
  95. data/app/models/camaleon_cms/category.rb +6 -6
  96. data/app/models/camaleon_cms/custom_field.rb +15 -6
  97. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  98. data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
  99. data/app/models/camaleon_cms/media.rb +8 -2
  100. data/app/models/camaleon_cms/meta.rb +2 -2
  101. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  102. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  103. data/app/models/camaleon_cms/plugin.rb +3 -4
  104. data/app/models/camaleon_cms/post.rb +113 -27
  105. data/app/models/camaleon_cms/post_comment.rb +10 -6
  106. data/app/models/camaleon_cms/post_default.rb +42 -12
  107. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  108. data/app/models/camaleon_cms/post_tag.rb +4 -5
  109. data/app/models/camaleon_cms/post_type.rb +21 -13
  110. data/app/models/camaleon_cms/site.rb +39 -29
  111. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  112. data/app/models/camaleon_cms/term_taxonomy.rb +52 -4
  113. data/app/models/camaleon_cms/theme.rb +2 -4
  114. data/app/models/camaleon_cms/user.rb +17 -5
  115. data/app/models/camaleon_cms/user_role.rb +66 -6
  116. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  117. data/app/models/camaleon_cms/widget/main.rb +12 -8
  118. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  119. data/app/models/camaleon_record.rb +89 -5
  120. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
  121. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  122. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  123. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  124. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  125. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  126. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  127. data/app/models/current_request.rb +8 -1
  128. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  129. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  130. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  131. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  132. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  133. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  134. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  135. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  136. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  137. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  138. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  140. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  141. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  142. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  143. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  144. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  145. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  146. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  147. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  148. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  149. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  150. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  151. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  153. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  154. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  155. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  156. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  157. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  158. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  159. data/config/initializers/action_view.rb +42 -5
  160. data/config/initializers/active_record_extension.rb +13 -27
  161. data/config/initializers/assets.rb +52 -23
  162. data/config/initializers/custom_initializers.rb +1 -1
  163. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  164. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  165. data/config/locales/camaleon_cms/admin/en.yml +33 -0
  166. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  167. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  170. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  175. data/config/routes/admin.rb +33 -15
  176. data/config/routes/frontend.rb +21 -15
  177. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  178. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  179. data/lib/camaleon_cms/content_security.rb +158 -0
  180. data/lib/camaleon_cms/engine.rb +47 -9
  181. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  182. data/lib/camaleon_cms/setup_token.rb +69 -0
  183. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  184. data/lib/camaleon_cms/task_reporter.rb +14 -0
  185. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  186. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  187. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  188. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  189. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  190. data/lib/camaleon_cms/uploader_support.rb +86 -0
  191. data/lib/camaleon_cms/version.rb +1 -1
  192. data/lib/camaleon_cms.rb +11 -0
  193. data/lib/ext/hash.rb +28 -2
  194. data/lib/ext/string.rb +10 -5
  195. data/lib/ext/translator.rb +10 -2
  196. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  197. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  198. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  199. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  200. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  201. data/lib/plugin_routes.rb +412 -368
  202. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  203. data/lib/tasks/cross_site_field_groups.rake +74 -0
  204. data/lib/tasks/custom_fields_roles.rake +15 -13
  205. data/lib/tasks/orphaned_comments.rake +42 -0
  206. data/lib/tasks/private_upload_acls.rake +31 -0
  207. data/lib/tasks/site_custom_field_groups.rake +36 -0
  208. data/lib/tasks/unsafe_stored_content.rake +42 -0
  209. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  210. data/lib/tasks/user_field_groups.rake +27 -0
  211. metadata +123 -4
@@ -2,9 +2,44 @@ module CamaleonCms
2
2
  class Post < CamaleonCms::PostDefault
3
3
  include CamaleonCms::CategoriesTagsForPosts
4
4
 
5
+ # Structural, non-executable markup that long-form post content legitimately uses but the
6
+ # sanitizer default drops. Superset of the default so upstream security additions are inherited.
7
+ SANITIZE_EXTRA_TAGS = %w[table thead tbody tfoot tr td th caption col colgroup
8
+ figure figcaption u s hr].freeze
9
+ # These attributes are allowed knowing what they cost, none of which is script execution:
10
+ # `style` is CSS-scrubbed by the gate's scrubber (expression(), url(javascript:) and friends
11
+ # register as removals) but permits absolutely-positioned overlays; `id` widens DOM clobbering,
12
+ # which the scrubber's own default `name` already opened; and `target` without a forced
13
+ # `rel=noopener` is reverse tabnabbing on browsers predating the implicit default. The trade
14
+ # is deliberate: an untrusted author whose content needs more than this list has the save
15
+ # refused, and the role that wants more grants post_content_unfiltered_html.
16
+ SANITIZE_EXTRA_ATTRIBUTES = %w[id style target rel colspan rowspan].freeze
17
+ CONTENT_ALLOWED_TAGS = (ActionController::Base.helpers.sanitizer_vendor.safe_list_sanitizer
18
+ .allowed_tags.to_a + SANITIZE_EXTRA_TAGS).uniq.freeze
19
+ CONTENT_ALLOWED_ATTRIBUTES = (ActionController::Base.helpers.sanitizer_vendor.safe_list_sanitizer
20
+ .allowed_attributes.to_a + SANITIZE_EXTRA_ATTRIBUTES).uniq.freeze
21
+
22
+ # Opt-out for trusted server-side pipelines (imports, seeds, plugin code) that would otherwise
23
+ # be sanitized by the fail-closed default. Exposed as a reader plus a bang enabler and NO
24
+ # `unfiltered_content=` writer, so `assign_attributes`/mass assignment cannot reach it — only
25
+ # explicit server-side code calling `post.unfiltered_content!` can.
26
+ #
27
+ # It is sticky for the lifetime of the object, not for one save: an instance that has been
28
+ # opted out stays opted out for every subsequent save of that instance. That is what a
29
+ # pipeline staging a record over several steps wants; code that needs the opt-out to apply
30
+ # once should not reuse the instance afterwards.
31
+ attr_reader :unfiltered_content
32
+
33
+ def unfiltered_content!
34
+ @unfiltered_content = true
35
+ self
36
+ end
37
+
5
38
  alias_attribute :post_type_id, :taxonomy_id
6
39
  default_scope -> { where(post_class: 'Post').order(post_order: :asc, created_at: :desc) }
7
40
 
41
+ validate :reject_untrusted_dangerous_content, on: %i[create update]
42
+
8
43
  # DEPRECATED
9
44
  has_many :post_relationships, class_name: 'CamaleonCms::PostRelationship', foreign_key: :objectid,
10
45
  dependent: :destroy, inverse_of: :post
@@ -14,16 +49,17 @@ module CamaleonCms
14
49
  has_many :term_relationships, foreign_key: :objectid, dependent: :destroy, inverse_of: :object
15
50
  has_many :categories, class_name: 'CamaleonCms::Category', through: :term_relationships, source: :term_taxonomy
16
51
  has_many :post_tags, class_name: 'CamaleonCms::PostTag', through: :term_relationships, source: :term_taxonomy
17
- has_many :comments, class_name: 'CamaleonCms::PostComment', foreign_key: :post_id, dependent: :destroy
18
- has_many :drafts, lambda {
19
- where(status: 'draft_child')
20
- }, class_name: 'CamaleonCms::Post', foreign_key: :post_parent, dependent: :destroy
52
+ has_many :comments, class_name: 'CamaleonCms::PostComment', dependent: :destroy
53
+ has_many :drafts, -> { where(status: 'draft_child') }, class_name: 'CamaleonCms::Post', inverse_of: :parent,
54
+ foreign_key: :post_parent, dependent: :destroy
21
55
  has_many :children, class_name: 'CamaleonCms::Post', foreign_key: :post_parent, dependent: :destroy,
22
- primary_key: :id
56
+ primary_key: :id, inverse_of: :parent
23
57
 
24
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
25
- belongs_to :parent, class_name: 'CamaleonCms::Post', foreign_key: :post_parent, required: false
26
- belongs_to :post_type, foreign_key: :taxonomy_id, inverse_of: :posts, required: false
58
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
59
+ inverse_of: :all_posts
60
+ belongs_to :parent, class_name: 'CamaleonCms::Post', foreign_key: :post_parent, optional: true,
61
+ inverse_of: :children
62
+ belongs_to :post_type, foreign_key: :taxonomy_id, inverse_of: :posts, optional: true
27
63
 
28
64
  scope :visible_frontend, -> { where(status: 'published') }
29
65
  scope :public_posts, lambda {
@@ -42,7 +78,7 @@ module CamaleonCms
42
78
  validates_with CamaleonCms::PostUniqValidator
43
79
  attr_accessor :show_title_with_parent
44
80
 
45
- before_create :fix_post_order, if: ->(p) { !p.post_order.present? || p.post_order == 0 }
81
+ before_create :fix_post_order, if: ->(p) { p.post_order.blank? || p.post_order == 0 }
46
82
 
47
83
  # return all parents for current page hierarchy ordered bottom to top
48
84
  def parents
@@ -66,22 +102,22 @@ module CamaleonCms
66
102
  end
67
103
  end
68
104
 
69
- # return the post type of this post (DEPRECATED)
105
+ # Return the post type of this post (DEPRECATED), though used yet in migration, so let it stay
70
106
  def get_post_type_depre
71
107
  post_types.reorder(nil).first
72
108
  end
73
109
 
74
- # check if this post was published
110
+ # Check if this post was published
75
111
  def published?
76
112
  status == 'published'
77
113
  end
78
114
 
79
- # check if this is in pending status
115
+ # Check if this post is in the pending status
80
116
  def pending?
81
117
  status == 'pending'
82
118
  end
83
119
 
84
- # check if this is in draft status
120
+ # Check if this post is in the draft status
85
121
  def draft?
86
122
  %w[draft draft_child].include?(status)
87
123
  end
@@ -90,12 +126,12 @@ module CamaleonCms
90
126
  status == 'draft_child'
91
127
  end
92
128
 
93
- # check if this is in trash status
129
+ # Check if this post is in the trash status
94
130
  def trash?
95
131
  status == 'trash'
96
132
  end
97
133
 
98
- # check if current post can manage content
134
+ # Check if the current post can manage content
99
135
  # return boolean
100
136
  def manage_content?(posttype = nil)
101
137
  get_option('has_content', (posttype || post_type).get_option('has_content', true))
@@ -106,34 +142,35 @@ module CamaleonCms
106
142
  get_option('has_layout', (posttype || post_type).get_option('has_layout', false))
107
143
  end
108
144
 
109
- # check if current post can manage template
145
+ # Check if current post can manage template
110
146
  # return boolean
111
147
  def manage_template?(posttype = nil)
112
148
  get_option('has_template', (posttype || post_type).get_option('has_template', true))
113
149
  end
114
150
 
115
- # check if current post can manage summary
151
+ # Check if the current post can manage summary
116
152
  # return boolean
117
153
  def manage_summary?(posttype = nil)
118
154
  get_option('has_summary', (posttype || post_type).get_option('has_summary', true))
119
155
  end
120
156
 
121
- # check if current post can manage picture
157
+ # Check if the current post can manage picture
122
158
  # return boolean
123
159
  def manage_picture?(posttype = nil)
124
160
  get_option('has_picture', (posttype || post_type).get_option('has_picture', true))
125
161
  end
126
162
 
127
- # check if current post can manage comments
163
+ # Check if the current post can manage comments
128
164
  # return boolean
129
165
  def manage_comments?(posttype = nil)
130
166
  get_option('has_comments', (posttype || post_type).get_option('has_comments', false))
131
167
  end
132
168
 
133
- # check if the post can be commented
169
+ # Check if the post can be commented
134
170
  # sample: @post.can_commented?
135
- # return Boolean (true/false)
136
- # to enable comments for current post, use this: post.set_meta('has_comments', '1'). Note: Parent PostType should be enabled for comments too: post_type.set_option('has_comments', true)
171
+ # to enable comments for current post, use this: post.set_meta('has_comments', '1').
172
+ # Note: Parent PostType should be enabled for comments too: post_type.set_option('has_comments', true)
173
+ # @return [TrueClass, FalseClass]
137
174
  def can_commented?
138
175
  manage_comments? && get_meta('has_comments').to_s == '1'
139
176
  end
@@ -151,10 +188,15 @@ module CamaleonCms
151
188
  # has_picture, boolean (default true)
152
189
  # has_template, boolean (default false)
153
190
  # has_comments, boolean (default false)
154
- # default_layout: (string) (default layout) # this is still used if post type was inactivated layout and overwritten by dropdown in post view
155
- # default_template: (string) (default template) # this is still used if post type was inactivated template and overwritten by dropdown in post view
191
+ #
192
+ # the following is still used if post type was inactivated layout and overwritten by dropdown in post view
193
+ # default_layout: (string) (default layout)
194
+ #
195
+ # the following is still used if post type was inactivated template and overwritten by dropdown in post view
196
+ # default_template: (string) (default template)
156
197
  # has_layout: (boolean) (default false)
157
- # skip_fields: (array) (default empty) array of custom field keys to avoid for this post, sample: ["subtitle", "icon"]
198
+ # skip_fields: (array) (default empty) array of custom field keys to avoid for this post,
199
+ # sample: ["subtitle", "icon"]
158
200
  # val: value for the setting
159
201
  def set_setting(key, val)
160
202
  set_option(key, val)
@@ -171,7 +213,7 @@ module CamaleonCms
171
213
  # new_order_position: (Integer) position number
172
214
  # return nil
173
215
  def set_position(new_order_position)
174
- update_column('post_order', new_order_position)
216
+ update_column(:post_order, new_order_position) # rubocop:disable Rails/SkipsModelValidations
175
217
  end
176
218
 
177
219
  # save the summary for current post
@@ -247,10 +289,54 @@ module CamaleonCms
247
289
 
248
290
  private
249
291
 
292
+ def trusted_for_unfiltered_html?
293
+ user = CurrentRequest.user
294
+ site = CurrentRequest.site
295
+ # Fail closed (sanitize) when either user or site context is missing - e.g. background jobs, rake tasks
296
+ # or the console. Guarding the site too avoids a NoMethodError from Ability#initialize (which dereferences
297
+ # the site for non-admin users) that would otherwise abort the whole save.
298
+ return false if user.blank? || site.blank?
299
+
300
+ CamaleonCms::Ability.new(user, site).can?(:post_content_unfiltered_html, post_type)
301
+ end
302
+
303
+ # Security (scan-and-reject policy): content an untrusted author is not permitted to write is
304
+ # refused with a validation error -- never sanitized or rewritten -- so stored content always
305
+ # equals authored content and the frontend may render it verbatim. The guard on
306
+ # `content_changed?` deliberately leaves pre-gate stored content editable (a title fix on an
307
+ # old post must not brick it); `rake camaleon_cms:security:scan_content` lists such content.
308
+ def reject_untrusted_dangerous_content
309
+ return unless new_record? || content_changed?
310
+ return if content.blank?
311
+ # The explicit pipeline opt-out short-circuits before the Ability lookup below.
312
+ return if unfiltered_content
313
+ # Check trust only once we know there is content to gate, so unchanged/blank updates never pay for
314
+ # building an Ability (a role-meta DB lookup) on every save.
315
+ return if trusted_for_unfiltered_html?
316
+
317
+ # Distinguish an over-size refusal from a disallowed-markup refusal (audit M16): an over-size
318
+ # value can be perfectly clean, so the markup message (scripts/handlers/embeds) would misdescribe
319
+ # it and send the author hunting for markup that is not there.
320
+ if CamaleonCms::UnsafeMarkup.too_large?(content)
321
+ errors.add(:content, cama_content_rejection_message('content_too_large'))
322
+ elsif CamaleonCms::UnsafeMarkup.unsafe_html?(
323
+ content, tags: CONTENT_ALLOWED_TAGS, attributes: CONTENT_ALLOWED_ATTRIBUTES
324
+ )
325
+ errors.add(:content, cama_content_rejection_message('content_rejected'))
326
+ end
327
+ end
328
+
329
+ # Only en.yml carries these keys while the process locale follows the current admin/site
330
+ # language — fall back to English rather than emit "translation missing".
331
+ def cama_content_rejection_message(key)
332
+ full_key = "camaleon_cms.admin.post.message.#{key}"
333
+ I18n.t(full_key, default: I18n.t(full_key, locale: :en))
334
+ end
335
+
250
336
  # calculate a post order when it is empty
251
337
  def fix_post_order
252
338
  last_post = post_type.posts.where.not(id: nil).last
253
- self.post_order = last_post.present? ? last_post.post_order + 1 : 1
339
+ self.post_order = last_post.present? ? (last_post.post_order || 0) + 1 : 1
254
340
  end
255
341
  end
256
342
  end
@@ -6,16 +6,20 @@ module CamaleonCms
6
6
  extend CamaleonCms::NormalizeAttrs
7
7
 
8
8
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}comments"
9
- # attr_accessible :user_id, :post_id, :content, :author, :author_email, :author_url, :author_IP, :approved, :agent, :agent, :typee, :comment_parent, :is_anonymous
9
+ # attr_accessible :user_id, :post_id, :content, :author, :author_email, :author_url, :author_IP, :approved, :agent,
10
+ # :agent, :typee, :comment_parent, :is_anonymous
10
11
  attr_accessor :is_anonymous
11
12
 
12
13
  # default_scope order('comments.created_at ASC')
13
14
  # approved: approved | pending | spam
14
15
 
15
- has_many :children, class_name: 'CamaleonCms::PostComment', foreign_key: :comment_parent, dependent: :destroy
16
- belongs_to :post, required: false
17
- belongs_to :parent, class_name: 'CamaleonCms::PostComment', foreign_key: :comment_parent, required: false
18
- belongs_to :user, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
16
+ has_many :children, class_name: 'CamaleonCms::PostComment', foreign_key: :comment_parent, dependent: :destroy,
17
+ inverse_of: :parent
18
+
19
+ belongs_to :post, optional: true
20
+ belongs_to :parent, class_name: 'CamaleonCms::PostComment', foreign_key: :comment_parent, optional: true,
21
+ inverse_of: :children
22
+ belongs_to :user, class_name: CamaManager.get_user_class_name.to_s, optional: true
19
23
 
20
24
  default_scope { order("#{CamaleonCms::PostComment.table_name}.created_at DESC") }
21
25
 
@@ -26,7 +30,7 @@ module CamaleonCms
26
30
  normalize_attrs(:content)
27
31
 
28
32
  validates :content, presence: true
29
- validates_presence_of :author, :author_email, if: proc { |c| c.is_anonymous.present? }
33
+ validates :author, :author_email, presence: { if: proc { |c| c.is_anonymous.present? } }
30
34
  after_create :update_counter
31
35
  after_destroy :update_counter
32
36
 
@@ -11,9 +11,42 @@ module CamaleonCms
11
11
  end
12
12
  end
13
13
 
14
+ # Point Rails to use the post_class column for Native STI
15
+ self.inheritance_column = :post_class
16
+
17
+ # Controls Writing: Strip 'CamaleonCms::' when saving to DB
18
+ # "CamaleonCms::Widget::Assigned" -> "Widget::Assigned"
19
+ # "CamaleonCms::Post" -> "Post"
20
+ def self.sti_name
21
+ name.sub(/^CamaleonCms::/, '')
22
+ end
23
+
24
+ # Controls Reading: Prepend 'CamaleonCms::' when pulling from DB
25
+ # "Widget::Assigned" -> "CamaleonCms::Widget::Assigned"
26
+ # "Post" -> "CamaleonCms::Post"
27
+ def self.find_sti_class(type_name)
28
+ full_class_name = type_name.start_with?('CamaleonCms::') ? type_name : "CamaleonCms::#{type_name}"
29
+ klass = begin
30
+ full_class_name.constantize
31
+ rescue NameError
32
+ nil
33
+ end
34
+ return klass if klass && klass <= base_class
35
+
36
+ # `super` resolves fully-qualified external classes (plugin STI outside the
37
+ # CamaleonCms namespace) and enforces ancestry; values it cannot place load as
38
+ # the base class, as every row did before native STI.
39
+ begin
40
+ super
41
+ rescue ActiveRecord::SubclassNotFound
42
+ base_class
43
+ end
44
+ end
45
+
14
46
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}posts"
15
47
 
16
- # attr_accessible :user_id, :title, :slug, :content, :content_filtered, :status, :visibility, :visibility_value, :post_order, :post_type_key, :taxonomy_id, :published_at, :post_parent, :post_order, :is_feature
48
+ # attr_accessible :user_id, :title, :slug, :content, :content_filtered, :status, :visibility, :visibility_value,
49
+ # :post_order, :post_type_key, :taxonomy_id, :published_at, :post_parent, :post_order, :is_feature
17
50
  attr_accessor :draft_id
18
51
 
19
52
  # attr_accessible :data_options
@@ -21,10 +54,12 @@ module CamaleonCms
21
54
  cattr_accessor :current_user
22
55
  cattr_accessor :current_site
23
56
 
24
- has_many :term_relationships, foreign_key: :objectid, dependent: :destroy, primary_key: :id
25
- has_many :children, lambda {
26
- where(post_class: 'PostDefault')
27
- }, class_name: 'CamaleonCms::PostDefault', foreign_key: :post_parent, dependent: :destroy, primary_key: :id
57
+ has_many :children, -> { where(post_class: 'PostDefault') },
58
+ class_name: 'CamaleonCms::PostDefault', foreign_key: :post_parent, dependent: :destroy, inverse_of: :parent
59
+
60
+ belongs_to :parent, class_name: 'CamaleonCms::PostDefault', foreign_key: :post_parent, optional: true,
61
+ inverse_of: :children
62
+
28
63
  scope :featured, -> { where(is_feature: true) }
29
64
 
30
65
  validates :title, :slug, presence: true
@@ -45,11 +80,6 @@ module CamaleonCms
45
80
  res.take
46
81
  end
47
82
 
48
- # return the parent of a post (support for sub contents or tree of posts)
49
- def parent
50
- CamaleonCms::Post.where(id: post_parent).first
51
- end
52
-
53
83
  # return the author of this Content
54
84
  def author
55
85
  CamaleonCms::User.find(user_id)
@@ -62,7 +92,7 @@ module CamaleonCms
62
92
  CamaleonCms::NavMenuItem.where(url: id, kind: 'post')
63
93
  end
64
94
 
65
- # Set the meta, field values and the post keywords here
95
+ # Set the meta-field values and the post keywords here
66
96
  def set_params(meta, custom_fields, options)
67
97
  set_metas(meta)
68
98
  set_field_values(custom_fields)
@@ -78,7 +108,7 @@ module CamaleonCms
78
108
 
79
109
  # do all before actions to save the content
80
110
  def before_saved
81
- self.title = 'Untitled' unless title.present?
111
+ self.title = 'Untitled' if title.blank?
82
112
  self.content_filtered = if content.to_s.include?('<!--:-->')
83
113
  content.translations.transform_values do |value|
84
114
  value.squish.strip_tags
@@ -5,11 +5,11 @@ module CamaleonCms
5
5
  # attr_accessible :objectid, :term_taxonomy_id, :term_order
6
6
  default_scope -> { order(term_order: :asc) }
7
7
 
8
- belongs_to :post_type, class_name: 'CamaleonCms::PostType', foreign_key: :term_taxonomy_id,
9
- inverse_of: :post_relationships, required: false
8
+ belongs_to :post_type, class_name: 'CamaleonCms::PostType', foreign_key: :term_taxonomy_id, inverse_of: false,
9
+ optional: true
10
10
  belongs_to :post, lambda {
11
11
  order("#{CamaleonCms::Post.table_name}.id DESC")
12
- }, foreign_key: :objectid, inverse_of: :post_relationships, dependent: :destroy, required: false
12
+ }, foreign_key: :objectid, inverse_of: :post_relationships, optional: true
13
13
 
14
14
  # callbacks
15
15
  after_create :update_count
@@ -18,7 +18,9 @@ module CamaleonCms
18
18
  private
19
19
 
20
20
  def update_count
21
- post_type.update_column('count', post_type.posts.size) if post_type.present?
21
+ return if post_type.blank?
22
+
23
+ post_type.update_column(:count, post_type.posts.size) # rubocop:disable Rails/SkipsModelValidations
22
24
  end
23
25
  end
24
26
  end
@@ -1,11 +1,10 @@
1
1
  module CamaleonCms
2
2
  class PostTag < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
4
-
5
- default_scope { where(taxonomy: :post_tag) }
3
+ normalize_attrs(:description)
6
4
 
7
5
  has_many :posts, foreign_key: :objectid, through: :term_relationships, source: :object
8
- belongs_to :post_type, foreign_key: :parent_id, inverse_of: :post_tags, required: false
9
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
6
+ belongs_to :post_type, foreign_key: :parent_id, inverse_of: :post_tags, optional: true
7
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
8
+ inverse_of: false
10
9
  end
11
10
  end
@@ -1,9 +1,8 @@
1
1
  module CamaleonCms
2
2
  class PostType < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
3
+ normalize_attrs(:description)
4
4
 
5
5
  alias_attribute :site_id, :parent_id
6
- default_scope { where(taxonomy: :post_type) }
7
6
 
8
7
  has_many :categories, foreign_key: :parent_id, dependent: :destroy, inverse_of: :post_type_parent
9
8
  has_many :post_tags, foreign_key: :parent_id, dependent: :destroy, inverse_of: :post_type
@@ -12,12 +11,12 @@ module CamaleonCms
12
11
  has_many :posts_through_categories, foreign_key: :objectid, through: :term_relationships, source: :object
13
12
  has_many :posts_draft, class_name: 'CamaleonCms::Post', foreign_key: :taxonomy_id, dependent: :destroy,
14
13
  inverse_of: :post_type
15
- has_many :field_group_taxonomy, lambda {
16
- where('object_class LIKE ?', 'PostType_%')
17
- }, class_name: 'CamaleonCms::CustomField', foreign_key: :objectid, dependent: :destroy
14
+ has_many :field_group_taxonomy, -> { where('object_class LIKE ?', 'PostType_%') },
15
+ class_name: 'CamaleonCms::CustomField', foreign_key: :objectid, dependent: :destroy, inverse_of: :owner
18
16
 
19
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
20
- belongs_to :site, foreign_key: :parent_id, required: false
17
+ belongs_to :owner, class_name: CamaManager.get_user_class_name.to_s, foreign_key: :user_id, optional: true,
18
+ inverse_of: false
19
+ belongs_to :site, foreign_key: :parent_id, optional: true, inverse_of: :post_types
21
20
 
22
21
  scope :visible_menu, -> { where(term_group: nil) }
23
22
  scope :hidden_menu, -> { where(term_group: -1) }
@@ -92,7 +91,7 @@ module CamaleonCms
92
91
  def default_category
93
92
  return unless manage_categories?
94
93
 
95
- cat = categories.find_by_slug('uncategorized')
94
+ cat = categories.find_by_slug('uncategorized') # rubocop:disable Rails/DynamicFindBy
96
95
  unless cat
97
96
  cat = categories.create({ name: 'Uncategorized', slug: 'uncategorized', parent_id: id })
98
97
  cat.set_option('not_deleted', true)
@@ -110,10 +109,17 @@ module CamaleonCms
110
109
  # summary: String resume (optional) => default (empty)
111
110
  # post_order: Integer to define the order position in the list (optional)
112
111
  # fields: Hash of values for custom fields, sample => fields: {subtitle: 'abc', icon: 'test' } (optional)
113
- # settings: Hash of post settings, sample => settings:
114
- # {has_content: false, has_summary: true, default_layout: 'my_layout', default_template: 'my_template' } (optional, see more in post.set_setting(...))
112
+ # settings: Hash of post settings, sample => settings: (optional, see more in post.set_setting(...))
113
+ # {has_content: false, has_summary: true, default_layout: 'my_layout', default_template: 'my_template' }
115
114
  # data_metas: {template: "", layout: ""}
116
- # sample: my_posttype.add_post(title: "My Title", post_order: 5, content: 'lorem_ipsum', settings: {default_template: "home/counters", has_content: false, has_seo: false, skip_fields: ["sub_tite", 'banner']}, fields: {pattern: true, bg: 'https://www.reallusion.com/de/images/3dx5/whatsnew/3dx5_features_banner_bg_02.jpg'})
115
+ # sample:
116
+ # my_posttype.add_post(
117
+ # title: "My Title", post_order: 5, content: 'lorem_ipsum',
118
+ # settings: {
119
+ # default_template: "home/counters", has_content: false, has_seo: false, skip_fields: ["sub_tite", 'banner']
120
+ # },
121
+ # fields: { pattern: true, bg: 'https://www.reallusion.com/de/images/3dx5/whatsnew/3dx5_features_banner_bg_02.jpg' }
122
+ # )
117
123
  # More samples here: https://gist.github.com/owen2345/eba9691585ed78ad6f7b52e9591357bf
118
124
  # return created post if it was created, else return errors
119
125
  def add_post(args)
@@ -125,7 +131,7 @@ module CamaleonCms
125
131
  args[:data_tags] = args.delete(:tags)
126
132
  _thumb = args.delete(:thumb)
127
133
  p = posts.new(args)
128
- p.slug = site.get_valid_post_slug(p.title.parameterize) unless p.slug.present?
134
+ p.slug = site.get_valid_post_slug(p.title.parameterize) if p.slug.blank?
129
135
  if p.save!
130
136
  _settings.each { |k, v| p.set_setting(k, v) } if _settings.present?
131
137
  p.set_position(_order_position) if _order_position.present?
@@ -139,6 +145,7 @@ module CamaleonCms
139
145
  end
140
146
 
141
147
  # return all available route formats of this post type for content posts
148
+ # rubocop:disable Layout/LineLength
142
149
  def contents_route_formats
143
150
  {
144
151
  'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample: https://localhost.com/group/17-services/myservice.html)',
@@ -149,6 +156,7 @@ module CamaleonCms
149
156
  'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample: https://localhost.com/item-1/item-1-1/item-111.html)'
150
157
  }
151
158
  end
159
+ # rubocop:enable Layout/LineLength
152
160
 
153
161
  # return the configuration of routes for post contents
154
162
  def contents_route_format
@@ -181,7 +189,7 @@ module CamaleonCms
181
189
 
182
190
  # destroy all custom field groups assigned to this post type
183
191
  def destroy_field_groups
184
- if !destroyed_by_association.present? && %w[post page].include?(slug)
192
+ if destroyed_by_association.blank? && %w[post page].include?(slug)
185
193
  errors.add(:base, 'This post type can not be deleted.')
186
194
  return false
187
195
  end
@@ -1,32 +1,45 @@
1
1
  module CamaleonCms
2
2
  class Site < CamaleonCms::TermTaxonomy
3
- normalize_attrs(:name, :description)
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
- default_scope { where(taxonomy: :site).reorder(term_group: :desc) }
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
- has_many :nav_menu_items, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :term_group
16
- has_many :widgets, class_name: 'CamaleonCms::Widget::Main', foreign_key: :parent_id, dependent: :destroy
17
- has_many :sidebars, class_name: 'CamaleonCms::Widget::Sidebar', foreign_key: :parent_id, dependent: :destroy
18
- has_many :user_roles_rel, class_name: 'CamaleonCms::UserRole', foreign_key: :parent_id, dependent: :destroy
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
- has_many :themes, class_name: 'CamaleonCms::Theme', foreign_key: :parent_id, dependent: :destroy
26
- has_many :public_media, -> { where(is_public: true) },
27
- class_name: 'CamaleonCms::Media', foreign_key: :site_id, dependent: :destroy
28
- has_many :private_media, -> { where(is_public: false) },
29
- class_name: 'CamaleonCms::Media', foreign_key: :site_id, dependent: :destroy
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
- validates_uniqueness_of :slug, scope: :taxonomy
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
- # set current admin language for this site
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
- CamaleonCms::User.all
154
- else
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 do |k, v|
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 !p.present? || (p.present? && p.id == post_id)
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
- unless user.present?
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' # used for sites with different domains to call from console or task
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