camaleon_cms 2.9.1 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -9
  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 +16 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/front_cache_helper.rb +57 -24
  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/category.html.erb +1 -1
  19. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  20. data/app/apps/themes/default/views/post_tag.html.erb +1 -1
  21. data/app/apps/themes/default/views/post_type.html.erb +1 -1
  22. data/app/apps/themes/default/views/search.html.erb +1 -1
  23. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  24. data/app/apps/themes/new/custom_helper.rb +5 -9
  25. data/app/apps/themes/new/views/category.html.erb +1 -1
  26. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  27. data/app/apps/themes/new/views/post_tag.html.erb +1 -1
  28. data/app/apps/themes/new/views/post_type.html.erb +1 -1
  29. data/app/apps/themes/new/views/search.html.erb +1 -1
  30. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  31. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  32. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  33. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  34. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +29 -10
  35. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  36. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +9 -2
  37. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +4 -3
  38. data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +2 -2
  39. data/app/controllers/camaleon_cms/admin/categories_controller.rb +27 -8
  40. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  41. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  42. data/app/controllers/camaleon_cms/admin/media_controller.rb +84 -19
  43. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  44. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +15 -6
  45. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  46. data/app/controllers/camaleon_cms/admin/posts_controller.rb +58 -22
  47. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +238 -45
  48. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +103 -18
  49. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +17 -5
  50. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +33 -7
  51. data/app/controllers/camaleon_cms/admin/settings_controller.rb +11 -6
  52. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +9 -7
  53. data/app/controllers/camaleon_cms/admin/users_controller.rb +89 -18
  54. data/app/controllers/camaleon_cms/admin_controller.rb +144 -26
  55. data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +5 -0
  56. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  57. data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
  58. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +44 -0
  59. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  60. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  61. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  62. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  63. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  64. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  65. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  66. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  67. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  68. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  69. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  70. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  71. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  72. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  73. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  74. data/app/decorators/camaleon_cms/post_decorator.rb +67 -51
  75. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  76. data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
  77. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  78. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  79. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  80. data/app/helpers/camaleon_cms/admin/application_helper.rb +21 -24
  81. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  82. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  83. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  84. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +51 -31
  85. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  86. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  87. data/app/helpers/camaleon_cms/comment_helper.rb +87 -41
  88. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  89. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  90. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  91. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +144 -67
  92. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  93. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  94. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  95. data/app/helpers/camaleon_cms/html_helper.rb +73 -38
  96. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  97. data/app/helpers/camaleon_cms/session_helper.rb +195 -39
  98. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  99. data/app/helpers/camaleon_cms/site_helper.rb +43 -9
  100. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  101. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -422
  102. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  103. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  104. data/app/models/camaleon_cms/ability.rb +65 -105
  105. data/app/models/camaleon_cms/category.rb +7 -5
  106. data/app/models/camaleon_cms/custom_field.rb +29 -6
  107. data/app/models/camaleon_cms/custom_field_group.rb +75 -21
  108. data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
  109. data/app/models/camaleon_cms/media.rb +8 -2
  110. data/app/models/camaleon_cms/meta.rb +2 -2
  111. data/app/models/camaleon_cms/nav_menu.rb +6 -2
  112. data/app/models/camaleon_cms/nav_menu_item.rb +18 -8
  113. data/app/models/camaleon_cms/plugin.rb +4 -3
  114. data/app/models/camaleon_cms/post.rb +113 -27
  115. data/app/models/camaleon_cms/post_comment.rb +10 -6
  116. data/app/models/camaleon_cms/post_default.rb +42 -12
  117. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  118. data/app/models/camaleon_cms/post_tag.rb +4 -3
  119. data/app/models/camaleon_cms/post_type.rb +22 -12
  120. data/app/models/camaleon_cms/site.rb +40 -28
  121. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  122. data/app/models/camaleon_cms/term_taxonomy.rb +51 -5
  123. data/app/models/camaleon_cms/theme.rb +3 -3
  124. data/app/models/camaleon_cms/user.rb +17 -5
  125. data/app/models/camaleon_cms/user_role.rb +77 -4
  126. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  127. data/app/models/camaleon_cms/widget/main.rb +13 -7
  128. data/app/models/camaleon_cms/widget/sidebar.rb +3 -6
  129. data/app/models/camaleon_record.rb +123 -5
  130. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
  131. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  132. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +115 -58
  133. data/app/models/concerns/camaleon_cms/metas.rb +32 -7
  134. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  135. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  136. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  137. data/app/models/current_request.rb +23 -0
  138. data/app/uploaders/camaleon_cms_aws_uploader.rb +39 -11
  139. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  140. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  141. data/app/validators/camaleon_cms/post_uniq_validator.rb +21 -8
  142. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  143. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  144. data/app/views/camaleon_cms/admin/appearances/nav_menus/_left_menu_items.html.erb +2 -2
  145. data/app/views/camaleon_cms/admin/appearances/widgets/main/form.html.erb +1 -1
  146. data/app/views/camaleon_cms/admin/categories/index.html.erb +3 -3
  147. data/app/views/camaleon_cms/admin/comments/index.html.erb +2 -2
  148. data/app/views/camaleon_cms/admin/comments/list.html.erb +1 -1
  149. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  150. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  151. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  152. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +3 -3
  153. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +2 -2
  154. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  155. data/app/views/camaleon_cms/admin/posts/index.html.erb +6 -6
  156. data/app/views/camaleon_cms/admin/search.html.erb +1 -1
  157. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  158. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  159. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  160. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  161. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +42 -6
  162. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  163. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb +1 -1
  164. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +5 -4
  165. data/app/views/camaleon_cms/admin/settings/custom_fields/index.html.erb +1 -1
  166. data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
  167. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  168. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  169. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  170. data/app/views/camaleon_cms/admin/settings/sites/index.html.erb +1 -1
  171. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +102 -7
  172. data/app/views/camaleon_cms/admin/user_roles/index.html.erb +1 -1
  173. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  174. data/app/views/camaleon_cms/admin/users/index.html.erb +2 -2
  175. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  176. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  177. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  178. data/app/views/layouts/camaleon_cms/admin/_flash_messages.html.erb +2 -2
  179. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  180. data/config/initializers/action_view.rb +42 -5
  181. data/config/initializers/active_record_extension.rb +13 -27
  182. data/config/initializers/assets.rb +52 -23
  183. data/config/initializers/custom_initializers.rb +3 -3
  184. data/config/locales/camaleon_cms/admin/ar.yml +7 -2
  185. data/config/locales/camaleon_cms/admin/de.yml +7 -2
  186. data/config/locales/camaleon_cms/admin/en.yml +39 -2
  187. data/config/locales/camaleon_cms/admin/es.yml +7 -2
  188. data/config/locales/camaleon_cms/admin/fr.yml +7 -2
  189. data/config/locales/camaleon_cms/admin/it.yml +7 -2
  190. data/config/locales/camaleon_cms/admin/nl.yml +8 -2
  191. data/config/locales/camaleon_cms/admin/pt-BR.yml +7 -2
  192. data/config/locales/camaleon_cms/admin/pt.yml +7 -2
  193. data/config/locales/camaleon_cms/admin/ru.yml +7 -2
  194. data/config/locales/camaleon_cms/admin/uk.yml +7 -2
  195. data/config/locales/camaleon_cms/admin/zh-CH.yml +7 -2
  196. data/config/routes/admin.rb +33 -15
  197. data/config/routes/frontend.rb +21 -15
  198. data/db/migrate/20150611161134_post_table_into_utf8.rb +14 -14
  199. data/db/migrate/20150926095310_rename_column_posts.rb +3 -3
  200. data/db/migrate/20151212095328_add_confirm_token_to_users.rb +3 -3
  201. data/db/migrate/20160504155652_add_feature_to_posts.rb +1 -1
  202. data/db/migrate/20160504155653_move_first_name_of_users.rb +2 -2
  203. data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +1 -1
  204. data/db/migrate/20161215202255_drop_user_relationship_table.rb +1 -1
  205. data/db/migrate/20180124132318_create_media.rb +1 -1
  206. data/db/migrate/20180704211100_adjust_field_length.rb +1 -1
  207. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  208. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  209. data/lib/camaleon_cms/content_security.rb +158 -0
  210. data/lib/camaleon_cms/engine.rb +47 -9
  211. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  212. data/lib/camaleon_cms/setup_token.rb +69 -0
  213. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  214. data/lib/camaleon_cms/task_reporter.rb +14 -0
  215. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  216. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  217. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  218. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  219. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  220. data/lib/camaleon_cms/uploader_support.rb +86 -0
  221. data/lib/camaleon_cms/version.rb +1 -1
  222. data/lib/camaleon_cms.rb +11 -0
  223. data/lib/ext/hash.rb +28 -2
  224. data/lib/ext/string.rb +13 -8
  225. data/lib/ext/translator.rb +10 -2
  226. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  227. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  228. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  229. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  230. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  231. data/lib/plugin_routes.rb +412 -368
  232. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  233. data/lib/tasks/cross_site_field_groups.rake +74 -0
  234. data/lib/tasks/custom_fields_roles.rake +58 -0
  235. data/lib/tasks/orphaned_comments.rake +42 -0
  236. data/lib/tasks/private_upload_acls.rake +31 -0
  237. data/lib/tasks/site_custom_field_groups.rake +36 -0
  238. data/lib/tasks/unsafe_stored_content.rake +42 -0
  239. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  240. data/lib/tasks/user_field_groups.rake +27 -0
  241. metadata +185 -7
@@ -2,7 +2,7 @@ module CamaleonCms
2
2
  class Media < CamaleonRecord
3
3
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}media"
4
4
 
5
- belongs_to :site, required: false
5
+ belongs_to :site, optional: true
6
6
  validates :name, uniqueness: {
7
7
  scope: %i[site_id is_folder folder_path is_public],
8
8
  message: 'Duplicates not allowed'
@@ -23,7 +23,7 @@ module CamaleonCms
23
23
  end
24
24
 
25
25
  # search file or folder by key
26
- def self.find_by_key(key)
26
+ def self.by_key(key)
27
27
  key = key.cama_fix_media_key
28
28
  if key == '/'
29
29
  where(folder_path: File.dirname(key))
@@ -32,6 +32,12 @@ module CamaleonCms
32
32
  end
33
33
  end
34
34
 
35
+ # legacy public name (pre-rename callers); the media table has no `key` column, so
36
+ # without the alias this falls through to Rails' dynamic finder and raises
37
+ class << self
38
+ alias find_by_key by_key
39
+ end
40
+
35
41
  # return all items of current folder
36
42
  def items
37
43
  coll = is_public ? site.public_media : site.private_media
@@ -3,8 +3,8 @@ module CamaleonCms
3
3
  self.table_name = "#{PluginRoutes.static_system_info['db_prefix']}metas"
4
4
  # attr_accessible :objectid, :key, :value, :object_class
5
5
 
6
- extend CamaleonCms::NormalizeAttrs
6
+ belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
7
7
 
8
- normalize_attrs(:value)
8
+ extend CamaleonCms::NormalizeAttrs
9
9
  end
10
10
  end
@@ -1,11 +1,15 @@
1
1
  module CamaleonCms
2
2
  class NavMenu < CamaleonCms::TermTaxonomy
3
- default_scope { where(taxonomy: :nav_menu).order(id: :asc) }
3
+ normalize_attrs(:description)
4
+
5
+ # deterministic external iteration order (2.9.2 parity); render paths reorder(:term_order)
6
+ default_scope { order(id: :asc) }
7
+
4
8
  alias_attribute :site_id, :parent_id
5
9
 
6
10
  has_many :children, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :parent_id, dependent: :destroy,
7
11
  inverse_of: :parent
8
- belongs_to :site, foreign_key: :parent_id, inverse_of: :nav_menus, required: false
12
+ belongs_to :site, foreign_key: :parent_id, inverse_of: :nav_menus, optional: true
9
13
 
10
14
  # add menu item for current menu
11
15
  # value: (Hash) is a hash object that contains label, type, link
@@ -1,17 +1,25 @@
1
1
  module CamaleonCms
2
2
  class NavMenuItem < CamaleonCms::TermTaxonomy
3
+ # NavMenuItem#name (the menu label) is the one "name" field that is rendered as raw/trusted HTML
4
+ # (see CamaleonCms::Frontend::NavMenuHelper#cama_menu_draw_items), unlike other model names which are
5
+ # ERB-auto-escaped. It therefore keeps save-time sanitization: safe formatting (icons, spans) is preserved
6
+ # while scripts and event handlers are stripped, preventing stored XSS from menu managers.
7
+ normalize_attrs(:name, :description)
8
+
9
+ # deterministic external iteration order (2.9.2 parity); render paths reorder(:term_order)
10
+ default_scope { order(id: :asc) }
11
+
3
12
  alias_attribute :site_id, :term_group
4
13
  alias_attribute :label, :name
5
14
  alias_attribute :url, :description
6
15
  alias_attribute :kind, :slug
7
16
  alias_attribute :target, :status
17
+
8
18
  # attr_accessible :label, :url, :kind
9
- #
10
- default_scope { where(taxonomy: :nav_menu_item).order(id: :asc) }
11
19
 
12
- belongs_to :parent, class_name: 'CamaleonCms::NavMenu', inverse_of: :children, required: false
20
+ belongs_to :parent, class_name: 'CamaleonCms::NavMenu', inverse_of: :children, optional: true
13
21
  belongs_to :parent_item, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :parent_id, inverse_of: :children,
14
- required: false
22
+ optional: true
15
23
  has_many :children, class_name: 'CamaleonCms::NavMenuItem', foreign_key: :parent_id, dependent: :destroy,
16
24
  inverse_of: :parent_item
17
25
 
@@ -54,10 +62,12 @@ module CamaleonCms
54
62
  private
55
63
 
56
64
  def update_count
57
- parent&.update_column('count', parent.children.size)
58
- parent_item&.update_column('count', parent_item.children.size)
59
- update_column(:term_group, main_menu.parent_id)
60
- update_column(:term_order, CamaleonCms::NavMenuItem.where(parent_id: parent_id).count) # update position
65
+ parent&.update_column(:count, parent.children.size) # rubocop:disable Rails/SkipsModelValidations
66
+ parent_item&.update_column(:count, parent_item.children.size) # rubocop:disable Rails/SkipsModelValidations
67
+ # update group and position
68
+ update_columns( # rubocop:disable Rails/SkipsModelValidations
69
+ term_group: main_menu.parent_id, term_order: CamaleonCms::NavMenuItem.where(parent_id: parent_id).count
70
+ )
61
71
  end
62
72
 
63
73
  # fast access from site to menu items
@@ -1,5 +1,7 @@
1
1
  module CamaleonCms
2
2
  class Plugin < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:description)
4
+
3
5
  # attrs:
4
6
  # term_group => status active (1, nil)
5
7
  # slug => plugin key
@@ -7,9 +9,8 @@ module CamaleonCms
7
9
 
8
10
  attr_accessor :error
9
11
 
10
- belongs_to :site, foreign_key: :parent_id, required: false
12
+ belongs_to :site, foreign_key: :parent_id, optional: true, inverse_of: :plugins
11
13
 
12
- default_scope { where(taxonomy: :plugin) }
13
14
  scope :active, -> { where(term_group: 1) }
14
15
 
15
16
  before_validation :set_default
@@ -69,7 +70,7 @@ module CamaleonCms
69
70
  private
70
71
 
71
72
  def set_default
72
- self.name = slug unless name.present?
73
+ self.name = slug if name.blank?
73
74
  end
74
75
 
75
76
  def destroy_custom_fields
@@ -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,9 +1,10 @@
1
1
  module CamaleonCms
2
2
  class PostTag < CamaleonCms::TermTaxonomy
3
- default_scope { where(taxonomy: :post_tag) }
3
+ normalize_attrs(:description)
4
4
 
5
5
  has_many :posts, foreign_key: :objectid, through: :term_relationships, source: :object
6
- belongs_to :post_type, foreign_key: :parent_id, inverse_of: :post_tags, required: false
7
- 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
8
9
  end
9
10
  end
@@ -1,7 +1,8 @@
1
1
  module CamaleonCms
2
2
  class PostType < CamaleonCms::TermTaxonomy
3
+ normalize_attrs(:description)
4
+
3
5
  alias_attribute :site_id, :parent_id
4
- default_scope { where(taxonomy: :post_type) }
5
6
 
6
7
  has_many :categories, foreign_key: :parent_id, dependent: :destroy, inverse_of: :post_type_parent
7
8
  has_many :post_tags, foreign_key: :parent_id, dependent: :destroy, inverse_of: :post_type
@@ -10,12 +11,12 @@ module CamaleonCms
10
11
  has_many :posts_through_categories, foreign_key: :objectid, through: :term_relationships, source: :object
11
12
  has_many :posts_draft, class_name: 'CamaleonCms::Post', foreign_key: :taxonomy_id, dependent: :destroy,
12
13
  inverse_of: :post_type
13
- has_many :field_group_taxonomy, lambda {
14
- where('object_class LIKE ?', 'PostType_%')
15
- }, 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
16
16
 
17
- belongs_to :owner, class_name: CamaManager.get_user_class_name, foreign_key: :user_id, required: false
18
- 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
19
20
 
20
21
  scope :visible_menu, -> { where(term_group: nil) }
21
22
  scope :hidden_menu, -> { where(term_group: -1) }
@@ -90,7 +91,7 @@ module CamaleonCms
90
91
  def default_category
91
92
  return unless manage_categories?
92
93
 
93
- cat = categories.find_by_slug('uncategorized')
94
+ cat = categories.find_by_slug('uncategorized') # rubocop:disable Rails/DynamicFindBy
94
95
  unless cat
95
96
  cat = categories.create({ name: 'Uncategorized', slug: 'uncategorized', parent_id: id })
96
97
  cat.set_option('not_deleted', true)
@@ -108,10 +109,17 @@ module CamaleonCms
108
109
  # summary: String resume (optional) => default (empty)
109
110
  # post_order: Integer to define the order position in the list (optional)
110
111
  # fields: Hash of values for custom fields, sample => fields: {subtitle: 'abc', icon: 'test' } (optional)
111
- # settings: Hash of post settings, sample => settings:
112
- # {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' }
113
114
  # data_metas: {template: "", layout: ""}
114
- # 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
+ # )
115
123
  # More samples here: https://gist.github.com/owen2345/eba9691585ed78ad6f7b52e9591357bf
116
124
  # return created post if it was created, else return errors
117
125
  def add_post(args)
@@ -123,7 +131,7 @@ module CamaleonCms
123
131
  args[:data_tags] = args.delete(:tags)
124
132
  _thumb = args.delete(:thumb)
125
133
  p = posts.new(args)
126
- 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?
127
135
  if p.save!
128
136
  _settings.each { |k, v| p.set_setting(k, v) } if _settings.present?
129
137
  p.set_position(_order_position) if _order_position.present?
@@ -137,6 +145,7 @@ module CamaleonCms
137
145
  end
138
146
 
139
147
  # return all available route formats of this post type for content posts
148
+ # rubocop:disable Layout/LineLength
140
149
  def contents_route_formats
141
150
  {
142
151
  'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample: https://localhost.com/group/17-services/myservice.html)',
@@ -147,6 +156,7 @@ module CamaleonCms
147
156
  'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample: https://localhost.com/item-1/item-1-1/item-111.html)'
148
157
  }
149
158
  end
159
+ # rubocop:enable Layout/LineLength
150
160
 
151
161
  # return the configuration of routes for post contents
152
162
  def contents_route_format
@@ -179,7 +189,7 @@ module CamaleonCms
179
189
 
180
190
  # destroy all custom field groups assigned to this post type
181
191
  def destroy_field_groups
182
- if !destroyed_by_association.present? && %w[post page].include?(slug)
192
+ if destroyed_by_association.blank? && %w[post page].include?(slug)
183
193
  errors.add(:base, 'This post type can not be deleted.')
184
194
  return false
185
195
  end