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
@@ -35,21 +35,29 @@ module CamaleonCms
35
35
  when 'Post'
36
36
  if term_relationships.empty? && args[:cat_ids].nil?
37
37
  CamaleonCms::CustomFieldGroup.where(
38
- '(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)', id || -1, class_name, post_type.id, "PostType_#{class_name}"
38
+ '(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)',
39
+ id || -1, class_name, post_type.id, "PostType_#{class_name}"
39
40
  )
40
41
  else
41
42
  cat_ids = categories.map(&:id)
42
43
  cat_ids += args[:cat_ids] unless args[:cat_ids].nil?
43
44
  cat_ids += CamaleonCms::Category.find(cat_ids).map { |category| _category_parents_ids(category) }.flatten.uniq
44
- CamaleonCms::CustomFieldGroup.where("(objectid = ? AND object_class = ?) OR
45
- (objectid = ? AND object_class = ?) OR
46
- (objectid IN (?) AND object_class = ?)",
47
- id || -1, class_name,
48
- post_type.id, "PostType_#{class_name}",
49
- cat_ids, "Category_#{class_name}")
45
+ CamaleonCms::CustomFieldGroup.where(
46
+ "(objectid = ? AND object_class = ?) OR
47
+ (objectid = ? AND object_class = ?) OR
48
+ (objectid IN (?) AND object_class = ?)",
49
+ id || -1, class_name, post_type.id, "PostType_#{class_name}", cat_ids, "Category_#{class_name}"
50
+ )
50
51
  end
51
52
  when 'NavMenuItem'
52
53
  main_menu.custom_field_groups
54
+ when 'Site'
55
+ # Site overrides `custom_field_groups` to mean "every group this site owns" (foreign key
56
+ # parent_id), unlike the object_class + objectid association every other model receives from
57
+ # CommonRelationships. Scoping by placement here keeps groups meant for post types, themes or
58
+ # menus off the site settings form, where their required fields blocked the submit and their
59
+ # values were discarded on save anyway.
60
+ custom_field_groups.where(object_class: 'Site', objectid: id)
53
61
  when 'PostType'
54
62
  case args[:kind]
55
63
  when 'all'
@@ -69,7 +77,9 @@ module CamaleonCms
69
77
  # return collections CustomFieldGroup
70
78
  # site: site object
71
79
  def get_user_field_groups(site)
72
- site.custom_field_groups.where(object_class: self.class.to_s.parseCamaClass)
80
+ # Demodulized to match the association scope and the placement the settings form stores
81
+ # ('User' for a host Admin::User); parseCamaClass still strips a Draper Decorator suffix.
82
+ site.custom_field_groups.where(object_class: self.class.to_s.parseCamaClass.demodulize)
73
83
  end
74
84
 
75
85
  # get custom field value
@@ -82,7 +92,7 @@ module CamaleonCms
82
92
  rescue StandardError
83
93
  _default
84
94
  end
85
- v.present? ? v : _default
95
+ v.presence || _default
86
96
  end
87
97
  alias get_field get_field_value
88
98
  alias get_field! get_field_value
@@ -117,12 +127,12 @@ module CamaleonCms
117
127
  # puts res[0]['my_slug1'].first ==> "val 1"
118
128
  def get_fields_grouped(field_keys)
119
129
  res = []
120
- custom_field_values.where(custom_field_slug: field_keys).order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
121
- group = {}
122
- field_keys.each do |field_key|
123
- _tmp = []
124
- group_fields.each { |field| _tmp << field.value if field_key == field.custom_field_slug }
125
- group[field_key] = _tmp if _tmp.present?
130
+ custom_field_values.where(custom_field_slug: field_keys)
131
+ .order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
132
+ group = field_keys.each_with_object({}) do |field_key, hsh|
133
+ _tmp = group_fields
134
+ .each_with_object([]) { |field, ary| ary << field.value if field_key == field.custom_field_slug }
135
+ hsh[field_key] = _tmp if _tmp.present?
126
136
  end
127
137
  res << group
128
138
  end
@@ -131,7 +141,8 @@ module CamaleonCms
131
141
 
132
142
  # return all values
133
143
  # {key1: "single value", key2: [multiple, values], key3: value4} if include_options = false
134
- # {key1: {values: "single value", options: {a:1, b: 4}}, key2: {values: [multiple, values], options: {a=1, b=2} }} if include_options = true
144
+ # {key1: {values: "single value", options: {a:1, b: 4}}, key2: {values: [multiple, values], options: {a=1, b=2} }}
145
+ # if include_options = true
135
146
  def get_field_values_hash(include_options = false)
136
147
  fields = {}
137
148
  custom_field_values.to_a.uniq.each do |field_value|
@@ -159,9 +170,10 @@ module CamaleonCms
159
170
  custom_field = field_value.custom_field
160
171
  # if custom_field.options[:show_frontend].to_s.to_bool
161
172
  values = custom_field.values.where(objectid: id).pluck(:value)
162
- fields[field_value.custom_field_slug] =
163
- custom_field.attributes.merge(options: custom_field.cama_options,
164
- values: custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first)
173
+ fields[field_value.custom_field_slug] = custom_field.attributes.merge(
174
+ options: custom_field.cama_options,
175
+ values: custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first
176
+ )
165
177
  # end
166
178
  end
167
179
  fields.to_sym
@@ -179,10 +191,11 @@ module CamaleonCms
179
191
  # post_type.add_custom_field_group(values, kind = "Category")
180
192
  # Note 2: If you need add fields for only the Post_type, you have to use options or metas
181
193
  # return: CustomFieldGroup object
182
- # kind: argument only for PostType model: (Post | Category | PostTag), default => Post. If kind = "" this will add group for all post_types
194
+ # kind: argument only for PostType model: (Post | Category | PostTag), default => Post.
195
+ # If kind = "" this will add group for all post_types
183
196
  def add_custom_field_group(values, kind = 'Post')
184
197
  values = values.with_indifferent_access
185
- group = get_field_groups(kind).find_by(slug: values[:slug])
198
+ group = get_field_groups(kind).find_by_slug(values[:slug]) # rubocop:disable Rails/DynamicFindBy
186
199
  unless group
187
200
  site = _cama_get_field_site
188
201
  values[:parent_id] = site.id if site.present?
@@ -202,7 +215,7 @@ module CamaleonCms
202
215
  # more details in add_manual_field(item, options) from custom field groups
203
216
  # kind: argument only for PostType model: (Post | Category | PostTag), default => Post
204
217
  def add_custom_field_to_default_group(item, options, kind = 'Post')
205
- g = get_field_groups(kind).find_by(slug: '_default')
218
+ g = get_field_groups(kind).find_by_slug('_default') # rubocop:disable Rails/DynamicFindBy
206
219
  g ||= add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind)
207
220
  g.add_manual_field(item, options)
208
221
  end
@@ -210,13 +223,8 @@ module CamaleonCms
210
223
 
211
224
  # return field object for current model
212
225
  def get_field_object(slug)
213
- CamaleonCms::CustomField.where(
214
- slug: slug,
215
- parent_id: get_field_groups.pluck(:id)
216
- ).first || CamaleonCms::CustomField.where(
217
- slug: slug,
218
- parent_id: get_field_groups({ include_parent: true })
219
- ).first
226
+ CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups.pluck(:id)).first ||
227
+ CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups({ include_parent: true })).first
220
228
  end
221
229
 
222
230
  # save all fields sent from browser (reservated for browser request)
@@ -226,28 +234,57 @@ module CamaleonCms
226
234
  # "1"=>{ "untitled-text-box"=>{"id"=>"262", "values"=>{"0"=>"33333"}}}
227
235
  # }
228
236
  def set_field_values(datas = {})
229
- return unless datas.present?
237
+ return if datas.blank?
230
238
 
231
239
  ActiveRecord::Base.transaction do
240
+ # A value identical to one already stored is not newly authored, so it must not be re-gated on
241
+ # an unrelated edit (audit M8): the admin form round-trips every value and this method
242
+ # delete/recreates them all, so without this skip a single pre-gate dangerous value would fail
243
+ # the whole save and brick every later edit -- the trap Post#reject_untrusted_dangerous_content
244
+ # avoids via content_changed?. Snapshot the stored pairs before clearing; skip the gate for any
245
+ # recreated value that matches one.
246
+ previously_stored = custom_field_values.pluck(:custom_field_slug, :value).to_set
232
247
  custom_field_values.delete_all
233
248
  datas.each_value do |fields_data|
234
249
  fields_data.each do |field_key, values|
235
- next unless values[:values].present?
250
+ next if values[:values].blank?
251
+
252
+ # Resolve the field id from the trusted slug, not the client-supplied values[:id]: a
253
+ # forged custom_field_id points the row at a different field definition, and the
254
+ # scan-and-reject gate keys off custom_field.options[:field_key] -- so a forged non-gated
255
+ # id would slip markup past the gate for a gated (editor/uri/field_attrs) slug. Fall back
256
+ # to values[:id] only when the slug names no field here (trusted/internal callers that
257
+ # pass slugs outside this object's registered groups; permitted browser payloads never do).
258
+ field_id = get_field_object(field_key)&.id || fallback_field_id_for(field_key) || values[:id]
259
+ group_number = [values[:group_number].to_i, 0].max
236
260
 
237
261
  order_value = -1
238
- (values[:values].is_a?(Hash) || values[:values].is_a?(ActionController::Parameters) ? values[:values].values : values[:values]).each do |value|
239
- custom_field_values.create!({ custom_field_id: values[:id], custom_field_slug: field_key,
240
- value: fix_meta_value(value), term_order: order_value += 1, group_number: values[:group_number] || 0 })
262
+ (
263
+ if values[:values].is_a?(Hash) || values[:values].is_a?(ActionController::Parameters)
264
+ values[:values].values
265
+ else
266
+ values[:values]
267
+ end
268
+ ).each do |value|
269
+ row = custom_field_values.new(
270
+ custom_field_id: field_id, custom_field_slug: field_key,
271
+ value: fix_meta_value(value), term_order: order_value += 1, group_number: group_number
272
+ )
273
+ row.unfiltered_value! if previously_stored.include?([field_key.to_s, row.value])
274
+ row.save!
241
275
  end
242
276
  end
243
277
  end
244
278
  end
245
279
  end
246
280
 
247
- # update new value for field with slug _key
281
+ # Update the value for the field with slug _key
248
282
  # Sample: my_posy.update_field_value('sub_title', 'Test Sub Title')
249
- def update_field_value(_key, value = nil, group_number = 0)
250
- custom_field_values.find_by(custom_field_slug: _key, group_number: group_number)&.update_column('value', value)
283
+ def update_field_value(key, value = nil, group_number = 0)
284
+ # Route through validations so the scan-and-reject gate applies (audit M5): update_column
285
+ # skipped it, letting a dangerous value an untrusted caller supplies reach the frontend verbatim.
286
+ custom_field_values.find_by(custom_field_slug: key, group_number: group_number)
287
+ &.update(value: value)
251
288
  rescue StandardError
252
289
  nil
253
290
  end
@@ -262,45 +299,65 @@ module CamaleonCms
262
299
 
263
300
  # Set custom field values for current model (support for multiple group values)
264
301
  # key: (string required) slug of the custom field
265
- # value: (array | string) array: array of values for multiple values support, string: uniq value for the custom field
302
+ # value: (array | string) array: array of values for multiple values support,
303
+ # string: uniq value for the custom field
266
304
  # args:
267
305
  # field_id: (integer optional) identifier of the custom field
268
306
  # order: order or position of the field value
269
307
  # group_number: number of the group (only for custom field group with is_repeat enabled)
270
- # clear: (boolean, default true) if true, will remove previous values and set these values, if not will append values
308
+ # clear: (boolean, default true) if true, will remove previous values and set these values,
309
+ # if not will append values
271
310
  # return false if the was not saved because there is not present the field with slug: key
272
311
  # sample: my_post.set_field_value('subtitle', 'Sub Title')
273
- # sample: my_post.set_field_value('subtitle', ['Sub Title1', 'Sub Title2']) # set values for a field (for fields that support multiple values)
312
+ # sample: set values for a field (for fields that support multiple values)
313
+ # my_post.set_field_value('subtitle', ['Sub Title1', 'Sub Title2'])
274
314
  # sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1})
275
- # sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1, group_number: 1}) # add field values for fields in group 1
315
+ # sample: add field values for fields in group 1
316
+ # my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1, group_number: 1})
276
317
  def set_field_value(key, value, args = {})
277
318
  args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge!(args)
278
- unless args[:field_id].present?
319
+ if args[:field_id].blank?
279
320
  args[:field_id] = begin
280
321
  get_field_object(key).id
281
322
  rescue StandardError
282
323
  nil
283
324
  end
284
325
  end
285
- raise ArgumentError, "There is no custom field configured for #{key}" unless args[:field_id].present?
326
+ args[:field_id] ||= fallback_field_id_for(key)
286
327
 
287
- if args[:clear]
288
- custom_field_values.where({ custom_field_slug: key,
289
- group_number: args[:group_number] }).delete_all
290
- end
291
- v = { custom_field_id: args[:field_id], custom_field_slug: key, value: fix_meta_value(value),
292
- term_order: args[:order], group_number: args[:group_number] }
293
- if value.is_a?(Array)
294
- value.each do |val|
295
- custom_field_values.create!(v.merge({ value: fix_meta_value(val) }))
328
+ raise ArgumentError, "There is no custom field configured for #{key}" if args[:field_id].blank?
329
+
330
+ v = {
331
+ custom_field_id: args[:field_id], custom_field_slug: key, value: fix_meta_value(value),
332
+ term_order: args[:order], group_number: args[:group_number]
333
+ }
334
+ # Atomic (audit M7): clear the previous value and write the new one in one transaction, so a
335
+ # value the scan-and-reject gate refuses (create! -> RecordInvalid) rolls the delete back and
336
+ # the previously stored value survives instead of being destroyed.
337
+ ActiveRecord::Base.transaction do
338
+ if args[:clear]
339
+ custom_field_values.where({ custom_field_slug: key, group_number: args[:group_number] }).delete_all
340
+ end
341
+ if value.is_a?(Array)
342
+ value.each { |val| custom_field_values.create!(v.merge({ value: fix_meta_value(val) })) }
343
+ else
344
+ custom_field_values.create!(v)
296
345
  end
297
- else
298
- custom_field_values.create!(v)
299
346
  end
300
347
  end
301
348
 
302
349
  private
303
350
 
351
+ def fallback_field_id_for(key)
352
+ return unless self.class.to_s.parseCamaClass == 'Post'
353
+ return unless respond_to?(:post_type_id) && post_type_id.present?
354
+
355
+ group_ids = CamaleonCms::CustomFieldGroup.where(object_class: 'PostType_Post', objectid: post_type_id).pluck(:id)
356
+ return if group_ids.blank?
357
+
358
+ CamaleonCms::CustomField.where(slug: key, parent_id: group_ids).pick(:id)
359
+ end
360
+
304
361
  def fix_meta_value(value)
305
362
  return value.to_json if value.is_a?(ActionController::Parameters)
306
363
  return JSON.generate(value) if value.is_a?(Array) || value.is_a?(Hash)
@@ -14,7 +14,25 @@ module CamaleonCms
14
14
  # Add meta with value or Update meta with key: key
15
15
  # return true or false
16
16
  def set_meta(key, value)
17
- metas.where(key: key).update_or_create({ value: fix_meta_value(value) })
17
+ fixed_value = fix_meta_value(value)
18
+
19
+ # Check if the parent object has been saved to the database yet
20
+ if persisted?
21
+ # Safe to use database-driven lookups and updates
22
+ meta_record = metas.find_or_create_by(key: key.to_s)
23
+ meta_record.update(value: fixed_value)
24
+ else
25
+ # In-Memory Fallback: Find an existing unsaved item in the array collection,
26
+ # or build a brand new unsaved record on the association.
27
+ meta_record = metas.find { |m| m.key == key.to_s }
28
+
29
+ if meta_record
30
+ meta_record.value = fixed_value
31
+ else
32
+ metas.build(key: key.to_s, value: fixed_value)
33
+ end
34
+ end
35
+
18
36
  cama_set_cache("meta_#{key}", value)
19
37
  end
20
38
 
@@ -23,7 +41,7 @@ module CamaleonCms
23
41
  def get_meta(key, default = nil)
24
42
  key_str = key.is_a?(Symbol) ? key.to_s : key
25
43
  cama_fetch_cache("meta_#{key_str}") do
26
- option = metas.loaded? ? metas.select { |m| m.key == key }.first : metas.where(key: key_str).first
44
+ option = metas.loaded? ? metas.find { |m| m.key == key_str } : metas.where(key: key_str).first
27
45
  res = ''
28
46
  if option.present?
29
47
  value = begin
@@ -73,12 +91,16 @@ module CamaleonCms
73
91
  # return value for attribute
74
92
  def get_option(key = nil, default = nil, meta_key = '_default')
75
93
  values = cama_options(meta_key)
94
+ return default if key.nil?
95
+
76
96
  key = key.to_sym
77
97
  values.key?(key) && values[key] != '' ? values[key] : default
78
98
  end
79
99
 
80
100
  # delete attribute from configuration
81
101
  def delete_option(key, meta_key = '_default')
102
+ return if key.nil?
103
+
82
104
  values = cama_options(meta_key)
83
105
  key = key.to_sym
84
106
  values.delete(key) if values.key?(key)
@@ -88,7 +110,7 @@ module CamaleonCms
88
110
  # set multiple configurations
89
111
  # h: {ket1: "sdsds", ff: "fdfdfdfd"}
90
112
  def set_options(h = {}, meta_key = '_default')
91
- return unless h.present?
113
+ return if h.blank?
92
114
 
93
115
  data = cama_options(meta_key)
94
116
  PluginRoutes.fixActionParameter(h).to_sym.each do |key, value|
@@ -116,11 +138,14 @@ module CamaleonCms
116
138
  save_metas_options # unless self.changed?
117
139
  end
118
140
 
119
- # save all settings for this post type received in data_options and data_metas attribute (options and metas)
120
- # sample: Site.first.post_types.create({name: "owen", slug: "my_post_type", data_options: { has_category: true, default_layout: "my_layout" }})
141
+ # Save all settings for this post type - received in data_options and data_metas attribute (options and metas)
142
+ # sample:
143
+ # Site.first.post_types.create(
144
+ # {name: "owen", slug: "my_post_type", data_options: { has_category: true, default_layout: "my_layout" }}
145
+ # )
121
146
  def save_metas_options
122
147
  set_multiple_options(data_options)
123
- return unless data_metas.present?
148
+ return if data_metas.blank?
124
149
 
125
150
  data_metas.each do |key, val|
126
151
  set_meta(key, val)
@@ -9,17 +9,11 @@ module CamaleonCms
9
9
  args.each do |attr|
10
10
  next unless new_record? || attribute_changed?(attr)
11
11
 
12
- self[attr] = ActionController::Base.helpers.sanitize(
13
- __send__(attr)&.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
14
- )&.gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
12
+ self[attr] = CamaleonRecord.cama_sanitize_translatable(__send__(attr))
15
13
  end
16
14
  end
17
15
  else
18
- normalizes(*args, with: lambda { |field|
19
- ActionController::Base.helpers.sanitize(
20
- field.gsub(CamaleonRecord::TRANSLATION_TAG_HIDE_REGEX, CamaleonRecord::TRANSLATION_TAG_HIDE_MAP)
21
- ).gsub(CamaleonRecord::TRANSLATION_TAG_RESTORE_REGEX, CamaleonRecord::TRANSLATION_TAG_RESTORE_MAP)
22
- })
16
+ normalizes(*args, with: ->(field) { CamaleonRecord.cama_sanitize_translatable(field) })
23
17
  end
24
18
  end
25
19
  end
@@ -4,10 +4,20 @@ module CamaleonCms
4
4
  # default structure for each new site
5
5
  def default_settings
6
6
  default_post_type = [
7
- { name: 'Post', description: 'Posts',
8
- options: { has_category: true, has_tags: true, not_deleted: true, has_summary: true, has_content: true, has_comments: true, has_picture: true, has_template: true } },
9
- { name: 'Page', description: 'Pages',
10
- options: { has_category: false, has_tags: false, not_deleted: true, has_summary: false, has_content: true, has_comments: false, has_picture: true, has_template: true, has_layout: true } }
7
+ {
8
+ name: 'Post', description: 'Posts',
9
+ options: {
10
+ has_category: true, has_tags: true, not_deleted: true, has_summary: true, has_content: true,
11
+ has_comments: true, has_picture: true, has_template: true
12
+ }
13
+ },
14
+ {
15
+ name: 'Page', description: 'Pages',
16
+ options: {
17
+ has_category: false, has_tags: false, not_deleted: true, has_summary: false, has_content: true,
18
+ has_comments: false, has_picture: true, has_template: true, has_layout: true
19
+ }
20
+ }
11
21
  ]
12
22
  default_post_type.each do |pt|
13
23
  post_types.create({ name: pt[:name], slug: pt[:name].to_s.parameterize, description: pt[:description],
@@ -17,20 +27,101 @@ module CamaleonCms
17
27
  # nav menus
18
28
  @nav_menu = nav_menus.new({ name: 'Main Menu', slug: 'main_menu' })
19
29
  if @nav_menu.save
20
- post_types.all.each do |pt|
30
+ post_types.find_each do |pt|
21
31
  if pt.slug == 'post'
22
32
  title = 'Sample Post'
23
33
  slug = 'sample-post'
24
- content = '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pharetra ut augue in posuere. Nulla non malesuada dui. Sed egestas tortor ut purus tempor sodales. Duis non sollicitudin nulla, quis mollis neque. Integer sit amet augue ac neque varius auctor. Vestibulum malesuada leo leo, at semper libero efficitur nec. Etiam semper nisi ac nisi ullamcorper, sed tincidunt purus elementum. Mauris ac congue nibh. Quisque pretium eget leo nec suscipit. </p> <p> Vestibulum ultrices orci ut congue interdum. Morbi dolor nunc, imperdiet vel risus semper, tempor dapibus urna. Phasellus luctus pharetra enim quis volutpat. Integer tristique urna nec malesuada ullamcorper. Curabitur dictum, lectus id ultrices rhoncus, ante neque auctor erat, ut sodales nisi odio sit amet lorem. In hac habitasse platea dictumst. Quisque orci orci, hendrerit at luctus tristique, lobortis in diam. Curabitur ligula enim, rhoncus ut vestibulum a, consequat sit amet nisi. Aliquam bibendum fringilla ultrices. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In justo mi, congue in rhoncus lobortis, facilisis in est. Nam et rhoncus purus. </p> <p> Sed sagittis auctor lectus at rutrum. Morbi ultricies felis mi, ut scelerisque augue facilisis eu. In molestie quam ex. Quisque ut sapien sed odio tempus imperdiet. In id accumsan massa. Morbi quis nunc ullamcorper, interdum enim eu, finibus purus. Vestibulum ac fermentum augue, at tempus ante. Aliquam ultrices, purus ut porttitor gravida, dui augue dignissim massa, ac tempor ante dolor at arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse placerat risus est, eget varius mi ultricies in. Duis non odio ut felis dapibus eleifend. In fringilla enim lobortis placerat efficitur. </p> <p> Nulla sodales faucibus urna, quis viverra dolor facilisis sollicitudin. Aenean ac egestas nibh. Nam non tortor eget nibh scelerisque fermentum. Etiam ornare, nunc ut luctus mollis, ante dolor consectetur augue, non scelerisque odio est a nulla. Nullam cursus egestas nulla, nec commodo nibh suscipit ut. Mauris ut felis sem. Aenean at mi at nisi dictum blandit sit amet at erat. Etiam eget lobortis tellus. Curabitur in commodo arcu, at vehicula tortor. </p>'
34
+ content_parts = []
35
+ content_parts << helpers.content_tag(
36
+ :p, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pharetra ut augue in posuere. ' \
37
+ 'Nulla non malesuada dui. Sed egestas tortor ut purus tempor sodales. ' \
38
+ 'Duis non sollicitudin nulla, quis mollis neque. Integer sit amet augue ac neque varius auctor. ' \
39
+ 'Vestibulum malesuada leo leo, at semper libero efficitur nec. ' \
40
+ 'Etiam semper nisi ac nisi ullamcorper, sed tincidunt purus elementum. ' \
41
+ 'Mauris ac congue nibh. Quisque pretium eget leo nec suscipit.'
42
+ )
43
+ content_parts << helpers.content_tag(
44
+ :p, 'Vestibulum ultrices orci ut congue interdum. ' \
45
+ 'Morbi dolor nunc, imperdiet vel risus semper, tempor dapibus urna. ' \
46
+ 'Phasellus luctus pharetra enim quis volutpat. Integer tristique urna nec malesuada ullamcorper. ' \
47
+ 'Curabitur dictum, lectus id ultrices rhoncus, ante neque auctor erat, ' \
48
+ 'ut sodales nisi odio sit amet lorem. In hac habitasse platea dictumst. ' \
49
+ 'Quisque orci orci, hendrerit at luctus tristique, lobortis in diam. ' \
50
+ 'Curabitur ligula enim, rhoncus ut vestibulum a, consequat sit amet nisi. ' \
51
+ 'Aliquam bibendum fringilla ultrices. Aliquam erat volutpat. ' \
52
+ 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; ' \
53
+ 'In justo mi, congue in rhoncus lobortis, facilisis in est. Nam et rhoncus purus.'
54
+ )
55
+ content_parts << helpers.content_tag(
56
+ :p, 'Sed sagittis auctor lectus at rutrum. ' \
57
+ 'Morbi ultricies felis mi, ut scelerisque augue facilisis eu. In molestie quam ex. ' \
58
+ 'Quisque ut sapien sed odio tempus imperdiet. In id accumsan massa. ' \
59
+ 'Morbi quis nunc ullamcorper, interdum enim eu, finibus purus. ' \
60
+ 'Vestibulum ac fermentum augue, at tempus ante. ' \
61
+ 'Aliquam ultrices, purus ut porttitor gravida, dui augue dignissim massa, ' \
62
+ 'ac tempor ante dolor at arcu. ' \
63
+ 'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' \
64
+ 'Suspendisse placerat risus est, eget varius mi ultricies in. ' \
65
+ 'Duis non odio ut felis dapibus eleifend. In fringilla enim lobortis placerat efficitur.'
66
+ )
67
+ content_parts << helpers.content_tag(
68
+ :p, 'Nulla sodales faucibus urna, quis viverra dolor facilisis sollicitudin. Aenean ac egestas nibh. ' \
69
+ 'Nam non tortor eget nibh scelerisque fermentum. ' \
70
+ 'Etiam ornare, nunc ut luctus mollis, ante dolor consectetur augue, ' \
71
+ 'non scelerisque odio est a nulla. Nullam cursus egestas nulla, nec commodo nibh suscipit ut. ' \
72
+ 'Mauris ut felis sem. Aenean at mi at nisi dictum blandit sit amet at erat. ' \
73
+ 'Etiam eget lobortis tellus. Curabitur in commodo arcu, at vehicula tortor.'
74
+ )
75
+ content = helpers.safe_join(content_parts)
25
76
  else
26
77
  title = 'Welcome'
27
78
  slug = 'welcome'
28
- content = "<p style='text-align: center;'><img width='155' height='155' src='https://camaleon.website/media/132/logo2.png' alt='logo' /></p><p><strong>Camaleon CMS</strong>&nbsp;is a free and open-source tool and a fexible content management system (CMS) based on <a href='https://rubyonrails.org'>Ruby on Rails</a>.</p> <p>With Camaleon you can do the following:</p> <ul> <li>Create instantly a lot of sites&nbsp;in the same installation</li> <li>Manage your content information in several languages</li> <li>Extend current functionality by&nbsp;plugins (MVC structure and no more echo or prints anywhere)</li> <li>Create or install different themes for each site</li> <li>Create your own structure without coding anything (adapt Camaleon as you want&nbsp;and not you for Camaleon)</li> <li>Create your store and start to sell your products using our plugins</li> <li>Avoid web attacks</li> <li>Compare the speed and enjoy the speed of your new Camaleon site</li> <li>Customize or create your themes for mobile support</li> <li>Support&nbsp;more visitors at the same time</li> <li>Manage your information with a panel like wordpress&nbsp;</li> <li>All urls are oriented for SEO</li> <li>Multiples roles of users</li> </ul>"
79
+ welcome_parts = []
80
+ logo_img = helpers
81
+ .image_tag('https://camaleon.website/media/132/logo2.png', width: 155, height: 155, alt: 'logo')
82
+ welcome_parts << helpers.content_tag(:p, logo_img, style: 'text-align: center;')
83
+ cms_strong = helpers.content_tag(:strong, 'Camaleon CMS')
84
+ rails_link = helpers.link_to('Ruby on Rails', 'https://rubyonrails.org')
85
+ part2_text = helpers.safe_join(
86
+ [
87
+ cms_strong,
88
+ ' is a free and open-source tool and a fexible content management system (CMS) based on ',
89
+ rails_link
90
+ ]
91
+ )
92
+ welcome_parts << helpers.content_tag(:p, part2_text)
93
+ welcome_parts << helpers.content_tag(:p, 'With Camaleon you can do the following:')
94
+ li_texts = [
95
+ 'Create instantly a lot of sites in the same installation',
96
+ 'Manage your content information in several languages',
97
+ 'Extend current functionality by plugins (MVC structure and no more echo or prints anywhere)',
98
+ 'Create or install different themes for each site',
99
+ 'Create your own structure without coding anything (adapt Camaleon as you want and not you for Camaleon)',
100
+ 'Create your store and start to sell your products using our plugins',
101
+ 'Avoid web attacks',
102
+ 'Compare the speed and enjoy the speed of your new Camaleon site',
103
+ 'Customize or create your themes for mobile support',
104
+ 'Support more visitors at the same time',
105
+ 'Manage your information with a panel like wordpress ',
106
+ 'All urls are oriented for SEO',
107
+ 'Multiples roles of users'
108
+ ]
109
+ li_tags = li_texts.map { |text| helpers.content_tag(:li, text) }
110
+ welcome_parts << helpers.content_tag(:ul, helpers.safe_join(li_tags))
111
+ content = helpers.safe_join(welcome_parts)
29
112
  end
30
113
  user = users.admin_scope.first
31
- unless user.present?
32
- user = users.admin_scope.create({ email: 'admin@local.com', username: 'admin', password: 'admin123',
33
- password_confirmation: 'admin123', first_name: 'Administrator' })
114
+ if user.blank?
115
+ # Never mint a known default password. Generate a random one, force a change on first
116
+ # login, and stash the plaintext transiently so the provisioning controller can surface
117
+ # it once. See harden-installer-default-admin.
118
+ generated_password = SecureRandom.alphanumeric(16)
119
+ user = users.admin_scope.create({ email: 'admin@local.com', username: 'admin',
120
+ password: generated_password,
121
+ password_confirmation: generated_password,
122
+ first_name: 'Administrator' })
123
+ user.set_meta('must_change_password', true)
124
+ self.generated_admin_password = generated_password
34
125
  end
35
126
  post = pt.add_post({ title: title, slug: slug, content: content, user_id: user.id, status: 'published' })
36
127
  @nav_menu.append_menu_item({ label: title, type: 'post', link: post.id })
@@ -60,18 +151,26 @@ module CamaleonCms
60
151
  if post_type.present?
61
152
  d = user_role.get_meta("_post_type_#{id}", {})
62
153
  CamaleonCms::UserRole::ROLES[:post_type].each do |value|
154
+ # post_content_unfiltered_html lets a role store unsanitized HTML in post content; it must not be
155
+ # granted to the default Editor role (only admins, via `can :manage, :all`, are trusted for it).
156
+ next if value[:key].to_s == 'post_content_unfiltered_html'
157
+
63
158
  value_old = d[value[:key].to_sym] || []
64
159
  d[value[:key].to_sym] = value_old + [post_type.id]
65
160
  end
66
161
  else
67
162
  pts = post_types.all.pluck(:id)
68
- CamaleonCms::UserRole::ROLES[:post_type].each { |value| d[value[:key]] = pts }
163
+ CamaleonCms::UserRole::ROLES[:post_type].each do |value|
164
+ next if value[:key].to_s == 'post_content_unfiltered_html'
165
+
166
+ d[value[:key]] = pts
167
+ end
69
168
  end
70
169
  user_role.set_meta("_post_type_#{id}", d || {})
71
170
  end
72
171
 
73
- user_role = user_roles.where({ slug: 'contributor' }).first_or_create({ name: 'Contributor',
74
- description: 'Contributor Role' })
172
+ user_role = user_roles.where({ slug: 'contributor' })
173
+ .first_or_create({ name: 'Contributor', description: 'Contributor Role' })
75
174
  if user_role.valid?
76
175
  d = {}
77
176
  if post_type.present?
@@ -89,13 +188,18 @@ module CamaleonCms
89
188
 
90
189
  return if post_type.present?
91
190
 
92
- user_role = user_roles.where({ slug: 'client',
93
- term_group: -1 }).first_or_create({ name: 'Client',
94
- description: 'Default roles client' })
191
+ user_role = user_roles.where({ slug: 'client', term_group: -1 })
192
+ .first_or_create({ name: 'Client', description: 'Default roles client' })
95
193
  return unless user_role.valid?
96
194
 
97
195
  user_role.set_meta("_post_type_#{id}", {})
98
196
  user_role.set_meta("_manager_#{id}", {})
99
197
  end
198
+
199
+ private
200
+
201
+ def helpers
202
+ ActionController::Base.helpers
203
+ end
100
204
  end
101
205
  end