camaleon_cms 2.7.4 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of camaleon_cms might be problematic. Click here for more details.

Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -20
  3. data/app/apps/plugins/attack/config/config.json +2 -2
  4. data/app/apps/plugins/front_cache/admin_controller.rb +4 -6
  5. data/app/apps/plugins/front_cache/config/config.json +1 -1
  6. data/app/apps/plugins/front_cache/config/locales/translation.yml +1 -1
  7. data/app/apps/plugins/front_cache/front_cache_helper.rb +3 -3
  8. data/app/apps/plugins/visibility_post/config/config.json +2 -2
  9. data/app/apps/themes/camaleon_first/assets/js/main.js +1 -1
  10. data/app/apps/themes/camaleon_first/views/index.html.erb +1 -1
  11. data/app/apps/themes/default/assets/js/main.js +1 -1
  12. data/app/apps/themes/new/assets/js/main.js +1 -1
  13. data/app/apps/themes/new/views/index.html.erb +4 -4
  14. data/app/apps/themes/new/views/layouts/_footer.html.erb +2 -2
  15. data/app/assets/javascripts/camaleon_cms/admin/_posttype.js +7 -6
  16. data/app/assets/javascripts/camaleon_cms/admin/admin-basic-manifest.js +2 -2
  17. data/app/assets/javascripts/camaleon_cms/admin/admin-manifest.js +1 -1
  18. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +20 -29
  19. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/de.js +2 -2
  20. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/en.js +2 -2
  21. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/fr.js +2 -2
  22. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/it.js +2 -2
  23. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/nl.js +1 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/pt-BR.js +2 -2
  25. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/ru.js +2 -2
  26. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/uk.js +2 -2
  27. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/zh-CN.js +1 -1
  28. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +2 -2
  29. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +15 -10
  30. data/app/controllers/camaleon_cms/admin/installers_controller.rb +1 -1
  31. data/app/controllers/camaleon_cms/admin/media_controller.rb +6 -19
  32. data/app/controllers/camaleon_cms/admin/settings_controller.rb +3 -2
  33. data/app/controllers/camaleon_cms/camaleon_controller.rb +8 -2
  34. data/app/decorators/camaleon_cms/application_decorator.rb +8 -0
  35. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  36. data/app/decorators/camaleon_cms/post_decorator.rb +5 -5
  37. data/app/decorators/camaleon_cms/post_type_decorator.rb +5 -4
  38. data/app/decorators/camaleon_cms/site_decorator.rb +2 -1
  39. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +1 -1
  40. data/app/helpers/camaleon_cms/admin/category_helper.rb +1 -1
  41. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +12 -11
  42. data/app/helpers/camaleon_cms/html_helper.rb +6 -6
  43. data/app/helpers/camaleon_cms/plugins_helper.rb +1 -1
  44. data/app/helpers/camaleon_cms/session_helper.rb +1 -1
  45. data/app/helpers/camaleon_cms/short_code_helper.rb +1 -1
  46. data/app/helpers/camaleon_cms/site_helper.rb +1 -1
  47. data/app/helpers/camaleon_cms/theme_helper.rb +1 -1
  48. data/app/helpers/camaleon_cms/uploader_helper.rb +10 -5
  49. data/app/mailers/camaleon_cms/html_mailer.rb +5 -2
  50. data/app/models/camaleon_cms/ability.rb +1 -1
  51. data/app/models/camaleon_cms/custom_field_group.rb +2 -2
  52. data/app/models/camaleon_cms/nav_menu.rb +1 -1
  53. data/app/models/camaleon_cms/post.rb +1 -1
  54. data/app/models/camaleon_cms/post_default.rb +1 -1
  55. data/app/models/camaleon_cms/post_type.rb +8 -8
  56. data/app/models/camaleon_cms/site.rb +1 -1
  57. data/app/models/camaleon_cms/term_taxonomy.rb +13 -0
  58. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +11 -22
  59. data/app/models/concerns/camaleon_cms/site_default_settings.rb +1 -1
  60. data/app/uploaders/camaleon_cms_uploader.rb +5 -0
  61. data/app/validators/camaleon_cms/post_uniq_validator.rb +15 -15
  62. data/app/validators/camaleon_cms/uniq_validator.rb +9 -3
  63. data/app/validators/camaleon_cms/user_url_validator.rb +207 -0
  64. data/app/views/camaleon_cms/admin/media/index.html.erb +1 -1
  65. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +2 -2
  66. data/app/views/camaleon_cms/default_theme/index.html.erb +4 -4
  67. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +3 -3
  68. data/app/views/layouts/camaleon_cms/admin/_footer.html.erb +1 -1
  69. data/config/initializers/custom_initializers.rb +14 -0
  70. data/config/locales/camaleon_cms/admin/en.yml +11 -1
  71. data/lib/camaleon_cms/engine.rb +1 -1
  72. data/lib/camaleon_cms/version.rb +1 -1
  73. data/lib/ext/hash.rb +1 -1
  74. data/lib/ext/string.rb +2 -2
  75. data/lib/generators/camaleon_cms/theme_template/assets/js/main.js +1 -1
  76. metadata +50 -7
@@ -101,7 +101,7 @@ module CamaleonCms
101
101
  # add a post for current model
102
102
  # title: title for post, => required
103
103
  # content: html text content, => required
104
- # thumb: image url, => default (empty). check http://camaleon.tuzitio.com/api-methods.html#section_fileuploads
104
+ # thumb: image url, => default (empty). check https://camaleon.website/api-methods.html#section_fileuploads
105
105
  # categories: [1,3,4,5], => default (empty)
106
106
  # tags: String comma separated, => default (empty)
107
107
  # slug: string key for post, => default (empty)
@@ -111,7 +111,7 @@ module CamaleonCms
111
111
  # settings: Hash of post settings, sample => settings:
112
112
  # {has_content: false, has_summary: true, default_layout: 'my_layout', default_template: 'my_template' } (optional, see more in post.set_setting(...))
113
113
  # 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: 'http://www.reallusion.com/de/images/3dx5/whatsnew/3dx5_features_banner_bg_02.jpg'})
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
115
  # More samples here: https://gist.github.com/owen2345/eba9691585ed78ad6f7b52e9591357bf
116
116
  # return created post if it was created, else return errors
117
117
  def add_post(args)
@@ -139,12 +139,12 @@ module CamaleonCms
139
139
  # return all available route formats of this post type for content posts
140
140
  def contents_route_formats
141
141
  {
142
- 'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample: http://localhost.com/group/17-services/myservice.html)',
143
- 'post_of_category' => '<code>/category/:category_id-:title/:slug</code><br> (Sample: http://localhost.com/category/17-services/myservice.html)',
144
- 'post_of_category_post_type' => '<code>/:post_type_title/category/:category_id-:title/:slug</code><br> (Sample: http://localhost.com/services/category/17-services/myservice.html)',
145
- 'post_of_posttype' => '<code>/:post_type_title/:slug</code><br> (Sample: http://localhost.com/services/myservice.html)',
146
- 'post' => '<code>/:slug</code><br> (Sample: http://localhost.com/myservice.html)',
147
- 'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample: http://localhost.com/item-1/item-1-1/item-111.html)'
142
+ 'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample: https://localhost.com/group/17-services/myservice.html)',
143
+ 'post_of_category' => '<code>/category/:category_id-:title/:slug</code><br> (Sample: https://localhost.com/category/17-services/myservice.html)',
144
+ 'post_of_category_post_type' => '<code>/:post_type_title/category/:category_id-:title/:slug</code><br> (Sample: https://localhost.com/services/category/17-services/myservice.html)',
145
+ 'post_of_posttype' => '<code>/:post_type_title/:slug</code><br> (Sample: https://localhost.com/services/myservice.html)',
146
+ 'post' => '<code>/:slug</code><br> (Sample: https://localhost.com/myservice.html)',
147
+ 'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample: https://localhost.com/item-1/item-1-1/item-111.html)'
148
148
  }
149
149
  end
150
150
 
@@ -91,7 +91,7 @@ module CamaleonCms
91
91
  # return theme model with slug theme_slug for this site
92
92
  # theme_slug: (optional) if it is null, this will return current theme for this site
93
93
  def get_theme(theme_slug = nil)
94
- themes.where(slug: (theme_slug || get_theme_slug), status: nil).first_or_create!
94
+ themes.where(slug: theme_slug || get_theme_slug, status: nil).first_or_create!
95
95
  end
96
96
 
97
97
  # return plugin model with slug plugin_slug
@@ -16,6 +16,19 @@ module CamaleonCms
16
16
  # attr_accessible :data_options
17
17
  # attr_accessible :data_metas
18
18
 
19
+ # TODO: Remove the 1st branch when support will be dropped of Rails < 7.1
20
+ if ::Rails::VERSION::STRING < '7.1.0'
21
+ before_validation(on: %i[create update]) do
22
+ %i[name description].each do |attr|
23
+ next unless new_record? || attribute_changed?(attr)
24
+
25
+ self[attr] = ActionController::Base.helpers.sanitize(__send__(attr))
26
+ end
27
+ end
28
+ else
29
+ normalizes :name, :description, with: ->(field) { ActionController::Base.helpers.sanitize(field) }
30
+ end
31
+
19
32
  # callbacks
20
33
  before_validation :before_validating
21
34
  before_destroy :destroy_dependencies
@@ -3,18 +3,6 @@ module CamaleonCms
3
3
  extend ActiveSupport::Concern
4
4
  included do
5
5
  before_destroy :_destroy_custom_field_groups
6
- # DEPRECATED, INSTEAD USE: custom_fields
7
- has_many :fields, lambda { |object|
8
- where(object_class: object.class.to_s.gsub('Decorator', '').gsub('CamaleonCms::', ''))
9
- }, class_name: 'CamaleonCms::CustomField', foreign_key: :objectid
10
- # DEPRECATED, INSTEAD USE: custom_field_values
11
- has_many :field_values, lambda { |object|
12
- where(object_class: object.class.to_s.gsub('Decorator', '').gsub('CamaleonCms::', ''))
13
- }, class_name: 'CamaleonCms::CustomFieldsRelationship', foreign_key: :objectid, dependent: :delete_all
14
- # DEPRECATED, INSTEAD USE: custom_field_groups
15
- has_many :field_groups, lambda { |object|
16
- where(object_class: object.class.to_s.parseCamaClass)
17
- }, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :objectid
18
6
  end
19
7
 
20
8
  # get custom field groups for current object
@@ -38,7 +26,7 @@ module CamaleonCms
38
26
  { kind: args,
39
27
  include_parent: false }
40
28
  else
41
- { kind: 'Post', include_parent: false }.merge(args)
29
+ { kind: 'Post', include_parent: false }.merge!(args)
42
30
  end
43
31
  class_name = self.class.to_s.parseCamaClass
44
32
  case class_name
@@ -73,7 +61,7 @@ module CamaleonCms
73
61
  CamaleonCms::CustomFieldGroup.where(object_class: "PostType_#{args[:kind]}", objectid: id)
74
62
  end
75
63
  else # 'Plugin' or other classes
76
- field_groups
64
+ custom_field_groups
77
65
  end
78
66
  end
79
67
 
@@ -131,7 +119,7 @@ module CamaleonCms
131
119
  # puts res[0]['my_slug1'].first ==> "val 1"
132
120
  def get_fields_grouped(field_keys)
133
121
  res = []
134
- custom_field_values.where(custom_field_slug: field_keys).order(group_number: :asc).group_by(&:group_number).each do |_group_number, group_fields|
122
+ custom_field_values.where(custom_field_slug: field_keys).order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
135
123
  group = {}
136
124
  field_keys.each do |field_key|
137
125
  _tmp = []
@@ -196,8 +184,8 @@ module CamaleonCms
196
184
  # kind: argument only for PostType model: (Post | Category | PostTag), default => Post. If kind = "" this will add group for all post_types
197
185
  def add_custom_field_group(values, kind = 'Post')
198
186
  values = values.with_indifferent_access
199
- group = get_field_groups(kind).where(slug: values[:slug]).first
200
- unless group.present?
187
+ group = get_field_groups(kind).find_by(slug: values[:slug])
188
+ unless group
201
189
  site = _cama_get_field_site
202
190
  values[:parent_id] = site.id if site.present?
203
191
  group = if is_a?(CamaleonCms::Post) # harcoded for post to support custom field groups
@@ -206,6 +194,7 @@ module CamaleonCms
206
194
  get_field_groups(kind).create!(values)
207
195
  end
208
196
  end
197
+
209
198
  group
210
199
  end
211
200
  alias add_field_group add_custom_field_group
@@ -215,8 +204,8 @@ module CamaleonCms
215
204
  # more details in add_manual_field(item, options) from custom field groups
216
205
  # kind: argument only for PostType model: (Post | Category | PostTag), default => Post
217
206
  def add_custom_field_to_default_group(item, options, kind = 'Post')
218
- g = get_field_groups(kind).where(slug: '_default').first
219
- g = add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind) unless g.present?
207
+ g = get_field_groups(kind).find_by(slug: '_default')
208
+ g ||= add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind)
220
209
  g.add_manual_field(item, options)
221
210
  end
222
211
  alias add_field add_custom_field_to_default_group
@@ -243,7 +232,7 @@ module CamaleonCms
243
232
 
244
233
  ActiveRecord::Base.transaction do
245
234
  custom_field_values.delete_all
246
- datas.each do |_index, fields_data|
235
+ datas.each_value do |fields_data|
247
236
  fields_data.each do |field_key, values|
248
237
  next unless values[:values].present?
249
238
 
@@ -260,7 +249,7 @@ module CamaleonCms
260
249
  # update new value for field with slug _key
261
250
  # Sample: my_posy.update_field_value('sub_title', 'Test Sub Title')
262
251
  def update_field_value(_key, value = nil, group_number = 0)
263
- custom_field_values.where(custom_field_slug: _key, group_number: group_number).first.update_column('value', value)
252
+ custom_field_values.find_by(custom_field_slug: _key, group_number: group_number)&.update_column('value', value)
264
253
  rescue StandardError
265
254
  nil
266
255
  end
@@ -287,7 +276,7 @@ module CamaleonCms
287
276
  # sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1})
288
277
  # sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1, group_number: 1}) # add field values for fields in group 1
289
278
  def set_field_value(key, value, args = {})
290
- args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge(args)
279
+ args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge!(args)
291
280
  unless args[:field_id].present?
292
281
  args[:field_id] = begin
293
282
  get_field_object(key).id
@@ -25,7 +25,7 @@ module CamaleonCms
25
25
  else
26
26
  title = 'Welcome'
27
27
  slug = 'welcome'
28
- content = "<p style='text-align: center;'><img width='155' height='155' src='http://camaleon.tuzitio.com/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='http://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>"
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>"
29
29
  end
30
30
  user = users.admin_scope.first
31
31
  unless user.present?
@@ -158,6 +158,11 @@ class CamaleonCmsUploader
158
158
  File.exist?(file_name)
159
159
  end
160
160
 
161
+ def self.delete_block(&block)
162
+ @delete_block = block if block
163
+ @delete_block
164
+ end
165
+
161
166
  private
162
167
 
163
168
  def cache_key
@@ -8,23 +8,23 @@ module CamaleonCms
8
8
  return unless ptype.present? # only for posts that belongs to a post type model
9
9
 
10
10
  posts = ptype.site.posts
11
- .where("(#{slug_array.map do |s|
12
- "#{CamaleonCms::Post.table_name}.slug LIKE '%-->#{s}<!--%'"
13
- end.join(' OR ')} ) OR #{CamaleonCms::Post.table_name}.slug = ?", record.slug)
11
+ .where(
12
+ "(#{slug_array.map { |s| "#{CamaleonCms::Post.table_name}.slug LIKE '%-->#{s}<!--%'" }
13
+ .join(' OR ')} ) OR #{CamaleonCms::Post.table_name}.slug = ?", record.slug
14
+ )
14
15
  .where.not(id: record.id)
15
16
  .where.not(status: %i[draft draft_child trash])
16
- if posts.size.positive?
17
- record.errors[:base] << if slug_array.size > 1
18
- "#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{posts.pluck(:slug).map do |slug|
19
- record.slug.to_s.translations.map do |lng, r_slug|
20
- if slug.translations_array.include?(r_slug)
21
- "#{r_slug} (#{lng})"
22
- end
23
- end.join(',')
24
- end.join(',').split(',').uniq.clean_empty.join(', ')} "
25
- else
26
- "#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{record.slug} "
27
- end
17
+ unless posts.empty?
18
+ record.errors[:base] <<
19
+ if slug_array.size > 1
20
+ "#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{posts.pluck(:slug).map do |slug|
21
+ record.slug.to_s.translations.map do |lng, r_slug|
22
+ "#{r_slug} (#{lng})" if slug.translations_array.include?(r_slug)
23
+ end.join(',')
24
+ end.join(',').split(',').uniq.clean_empty.join(', ')} "
25
+ else
26
+ "#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{record.slug} "
27
+ end
28
28
  end
29
29
 
30
30
  # avoid recursive page parent
@@ -3,9 +3,15 @@ module CamaleonCms
3
3
  def validate(record)
4
4
  return if record.skip_slug_validation?
5
5
 
6
- if CamaleonCms::TermTaxonomy.where(slug: record.slug).where.not(id: record.id).where("#{CamaleonCms::TermTaxonomy.table_name}.taxonomy" => record.taxonomy).where("#{CamaleonCms::TermTaxonomy.table_name}.parent_id" => record.parent_id).size.positive?
7
- record.errors[:base] << I18n.t('camaleon_cms.admin.post.message.requires_different_slug').to_s
8
- end
6
+ taxonomy_table = CamaleonCms::TermTaxonomy.table_name
7
+ slug_exists = CamaleonCms::TermTaxonomy.where(slug: record.slug)
8
+ .where.not(id: record.id)
9
+ .where("#{taxonomy_table}.taxonomy" => record.taxonomy)
10
+ .where("#{taxonomy_table}.parent_id" => record.parent_id).exists?
11
+
12
+ return unless slug_exists
13
+
14
+ record.errors[:base] << I18n.t('camaleon_cms.admin.post.message.requires_different_slug').to_s
9
15
  end
10
16
  end
11
17
  end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2011-present GitLab B.V.
4
+ #
5
+ # See https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/url_blocker.rb
6
+ #
7
+ # Portions of this software are licensed under the "MIT Expat" license as defined below.
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in all
17
+ # copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+
27
+ # require 'resolv'
28
+ require 'ipaddress'
29
+ require 'addressable/uri'
30
+
31
+ module CamaleonCms
32
+ class UserUrlValidator
33
+ LOCAL_IPS = %w[0.0.0.0 ::].freeze
34
+
35
+ def self.validate(...)
36
+ new.validate(...)
37
+ end
38
+
39
+ def initialize
40
+ @errors = []
41
+ end
42
+
43
+ # Validates the given url according to the constraints specified by the received arguments.
44
+ #
45
+ # allow_localhost - Registers error if URL resolves to a localhost IP address and argument is false.
46
+ # allow_local_network - Registers error if URL resolves to a link-local address and argument is false.
47
+ # enforce_user - Registers error if URL user doesn't start with alphanumeric characters and argument is true.
48
+ # enforce_sanitizing - Registers error if URL includes any HTML/CSS/JS tags and argument is true.
49
+ #
50
+ # Returns an array with [<uri>, <original-hostname>].
51
+ def validate(url, allow_localhost: false, allow_local_network: false, enforce_user: true, enforce_sanitizing: true)
52
+ return invalid_url unless url.present?
53
+
54
+ # Param url can be a string, URI or Addressable::URI
55
+ return invalid_url unless (uri = parse_url(url))
56
+
57
+ validate_uri(uri: uri, enforce_sanitizing: enforce_sanitizing, enforce_user: enforce_user)
58
+ return @errors if @errors.any?
59
+
60
+ address_info = get_address_info(uri)
61
+ return @errors if @errors.any?
62
+
63
+ validate_local_request(
64
+ address_info: address_info,
65
+ allow_localhost: allow_localhost,
66
+ allow_local_network: allow_local_network
67
+ )
68
+
69
+ @errors.empty? || @errors
70
+ end
71
+
72
+ private
73
+
74
+ def validate_uri(uri:, enforce_sanitizing:, enforce_user:)
75
+ validate_html_tags(uri) if enforce_sanitizing
76
+
77
+ validate_user(uri.user) if enforce_user
78
+ validate_hostname(uri.hostname)
79
+ end
80
+
81
+ # @param uri [Addressable::URI]
82
+ # @return [Array<Addrinfo>] addrinfo object for the URI
83
+ def get_address_info(uri)
84
+ Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM).map do |addr|
85
+ addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
86
+ end
87
+ rescue ArgumentError => e
88
+ # Addrinfo.getaddrinfo errors if the domain exceeds 1024 characters.
89
+ @errors << I18n.t('camaleon_cms.admin.validate.hostname_long') if e.message.include?('hostname too long')
90
+
91
+ @errors << "#{e.message}: #{I18n.t('camaleon_cms.admin.validate.url')}" if @errors.blank?
92
+ rescue SocketError
93
+ @errors << I18n.t('camaleon_cms.admin.validate.host_invalid')
94
+ end
95
+
96
+ def validate_local_request(address_info:, allow_localhost:, allow_local_network:)
97
+ return if allow_local_network && allow_localhost
98
+
99
+ unless allow_localhost
100
+ validate_localhost(address_info)
101
+ validate_loopback(address_info)
102
+ end
103
+
104
+ return if allow_local_network
105
+
106
+ validate_local_network(address_info)
107
+ validate_link_local(address_info)
108
+ validate_shared_address(address_info)
109
+ validate_limited_broadcast_address(address_info)
110
+ end
111
+
112
+ def get_port(uri)
113
+ uri.port || uri.default_port
114
+ end
115
+
116
+ def validate_html_tags(uri)
117
+ uri_str = uri.to_s
118
+ sanitized_uri = ActionController::Base.helpers.sanitize(uri_str, tags: [])
119
+ @errors << I18n.t('camaleon_cms.admin.validate.html_tags') unless sanitized_uri == uri_str
120
+ end
121
+
122
+ # @param [String, Addressable::URI, #to_str] url The URL string to parse
123
+ # @return [Addressable::URI, nil] URI object based on the parsed string, or `nil` if the `url` is invalid
124
+ def parse_url(url)
125
+ invalid = nil
126
+ uri = Addressable::URI.parse(url).tap do |parsed_url|
127
+ invalid = true if multiline_blocked?(parsed_url)
128
+ end
129
+ return if invalid
130
+
131
+ uri
132
+ rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
133
+ nil
134
+ end
135
+
136
+ def multiline_blocked?(parsed_url)
137
+ url = parsed_url.to_s
138
+
139
+ return true if url =~ /[\n\r]/
140
+ # Google Cloud Storage uses a multi-line, encoded Signature query string
141
+ return false if %w[http https].include?(parsed_url.scheme&.downcase)
142
+
143
+ CGI.unescape(url) =~ /[\n\r]/
144
+ end
145
+
146
+ def validate_user(value)
147
+ return if value.blank?
148
+ return if value =~ /\A\p{Alnum}/
149
+
150
+ @errors << I18n.t('camaleon_cms.admin.validate.username_alphanumeric')
151
+ end
152
+
153
+ def validate_hostname(value)
154
+ return if value.blank?
155
+ return if IPAddress.valid?(value)
156
+ return if value =~ /\A\p{Alnum}/
157
+
158
+ @errors << I18n.t('camaleon_cms.admin.validate.host_or_ip_invalid')
159
+ end
160
+
161
+ def validate_localhost(addrs_info)
162
+ return if (Socket.ip_address_list.map(&:ip_address).concat(LOCAL_IPS) & addrs_info.map(&:ip_address)).empty?
163
+
164
+ @errors << I18n.t('camaleon_cms.admin.validate.no_localhost_requests')
165
+ end
166
+
167
+ def validate_loopback(addrs_info)
168
+ return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
169
+
170
+ @errors << I18n.t('camaleon_cms.admin.validate.no_loopback_requests')
171
+ end
172
+
173
+ def validate_local_network(addrs_info)
174
+ return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
175
+
176
+ @errors << I18n.t('camaleon_cms.admin.validate.no_local_net_requests')
177
+ end
178
+
179
+ def validate_link_local(addrs_info)
180
+ netmask = IPAddr.new('169.254.0.0/16')
181
+ return unless addrs_info.any? { |addr| addr.ipv6_linklocal? || netmask.include?(addr.ip_address) }
182
+
183
+ @errors << I18n.t('camaleon_cms.admin.validate.no_link_local_net_requests')
184
+ end
185
+
186
+ def validate_shared_address(addrs_info)
187
+ netmask = IPAddr.new('100.64.0.0/10')
188
+ return unless addrs_info.any? { |addr| netmask.include?(addr.ip_address) }
189
+
190
+ @errors << I18n.t('camaleon_cms.admin.validate.no_shared_address_requests')
191
+ end
192
+
193
+ # Registers an error if any IP in `addrs_info` is the limited broadcast address.
194
+ # https://datatracker.ietf.org/doc/html/rfc919#section-7
195
+ def validate_limited_broadcast_address(addrs_info)
196
+ blocked_ips = ['255.255.255.255']
197
+
198
+ return if (blocked_ips & addrs_info.map(&:ip_address)).empty?
199
+
200
+ @errors << I18n.t('camaleon_cms.admin.validate.no_limited_broadcast_address_requests')
201
+ end
202
+
203
+ def invalid_url
204
+ @errors << I18n.t('camaleon_cms.admin.validate.url')
205
+ end
206
+ end
207
+ end
@@ -48,7 +48,7 @@
48
48
  <fieldset>
49
49
  <legend><%= t("camaleon_cms.admin.media.external", default: 'From URL') %></legend>
50
50
  <div class="form-group">
51
- <input type="text" name="remote_file" class="form-control" placeholder="http://..." class="required">
51
+ <input type="text" name="remote_file" class="form-control" placeholder="https://..." class="required">
52
52
  </div>
53
53
  <div class="form-group">
54
54
  <button type="submit" class="btn btn-primary"><%= t("camaleon_cms.admin.button.submit") %></button>
@@ -10,7 +10,7 @@
10
10
  <hr>
11
11
  <div class="alert alert-info">Gmail Need Permissions:
12
12
  <div class="pull-left">
13
- <a href="http://know.mailsbestfriend.com/smtp_error_password_command_failed_5345714-1194946499.shtml" target="_blank">Check
13
+ <a href="https://know.mailsbestfriend.com/smtp_error_password_command_failed_5345714-1194946499.shtml" target="_blank">Check
14
14
  here.</a>
15
15
  </div>
16
16
  <div class="pull-right">
@@ -55,4 +55,4 @@
55
55
  return false;
56
56
  });
57
57
  });
58
- </script>
58
+ </script>
@@ -12,19 +12,19 @@
12
12
  <!-- Wrapper for slides -->
13
13
  <div class="carousel-inner">
14
14
  <div class="item active">
15
- <img src="http://placehold.it/800x400" alt="...">
15
+ <img src="https://placehold.it/800x400" alt="...">
16
16
  <div class="carousel-caption">
17
17
  <h2>Heading</h2>
18
18
  </div>
19
19
  </div>
20
20
  <div class="item">
21
- <img src="http://placehold.it/800x400" alt="...">
21
+ <img src="https://placehold.it/800x400" alt="...">
22
22
  <div class="carousel-caption">
23
23
  <h2>Heading</h2>
24
24
  </div>
25
25
  </div>
26
26
  <div class="item">
27
- <img src="http://placehold.it/800x400" alt="...">
27
+ <img src="https://placehold.it/800x400" alt="...">
28
28
  <div class="carousel-caption">
29
29
  <h2>Heading</h2>
30
30
  </div>
@@ -67,4 +67,4 @@
67
67
  </div>
68
68
 
69
69
  </div>
70
- </section>
70
+ </section>
@@ -1,7 +1,7 @@
1
1
  xml.instruct! :xml, version: '1.0'
2
- xml.urlset 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9' do
2
+ xml.urlset 'xmlns' => 'https://www.sitemaps.org/schemas/sitemap/0.9' do
3
3
  current_site.get_languages.each_with_index do |lang, index|
4
- lang = (index.zero? ? nil : lang)
4
+ lang = (index == 0 ? nil : lang)
5
5
  xml.url do
6
6
  xml.loc current_site.the_url(locale: lang)
7
7
  xml.lastmod current_site.updated_at.to_date
@@ -54,7 +54,7 @@ xml.urlset 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9' do
54
54
  end
55
55
  end
56
56
 
57
- @r[:custom].each do |_key, item|
57
+ @r[:custom].each_value do |item|
58
58
  xml.url do
59
59
  xml.loc item[:url]
60
60
  xml.lastmod item[:lastmod] || Date.today.to_s
@@ -1,6 +1,6 @@
1
1
  <footer class="main-footer" id="main-footer">
2
2
  <div class="row">
3
- <div class="col-md-6"><strong>Copyright &copy; 2015 - <%= Time.now.year %> <a href="http://camaleon.tuzitio.com">Camaleon CMS.</a> </strong></div>
3
+ <div class="col-md-6"><strong>Copyright &copy; 2015 - <%= Time.now.year %> <a href="https://camaleon.website">Camaleon CMS.</a> </strong></div>
4
4
  <div class="col-md-6 hidden-xs">
5
5
  <div class="pull-left">
6
6
  <a id="link_see_intro" href="#" onclick="init_intro(); return false;"><i class="fa fa-tv"></i> <%= I18n.t('camaleon_cms.see_intro') %>.</a>
@@ -9,4 +9,18 @@ Rails.application.config.to_prepare do |_config|
9
9
  f = File.join(ap['path'], 'config', 'custom_models.rb')
10
10
  eval(File.read(f)) if File.exist?(f)
11
11
  end
12
+
13
+ # This block can be overridden in the app initializer to wrap the sleep and delete_file in an async job,
14
+ # something like this:
15
+ #
16
+ # CamaleonDeleteFileJob.set(wait: temporal_time).perform_later(file_key)
17
+ # # put this in app/jobs/camaleon_delete_file_job.rb:
18
+ # include CamaleonCms::UploaderHelper
19
+ # cama_uploader.delete_file(file_key)
20
+ CamaleonCmsUploader.delete_block do |settings, cama_uploader, file_key|
21
+ next unless Rails.env.test?
22
+
23
+ sleep(settings[:temporal_time])
24
+ cama_uploader.delete_file(file_key)
25
+ end
12
26
  end
@@ -212,7 +212,6 @@ en:
212
212
  reload: 'Reload'
213
213
  clear_cache: 'Clear Cache'
214
214
  name_required: 'File name is required'
215
- local_upload_denied: 'Cannot upload from localhost'
216
215
  menus:
217
216
  menus: Menus
218
217
  link_url: 'Link URL'
@@ -684,6 +683,16 @@ en:
684
683
  required: 'This field is required.'
685
684
  remote: 'Please fix this field.'
686
685
  email: 'Please enter a valid email address.'
686
+ host_invalid: 'Host cannot be resolved or invalid'
687
+ host_or_ip_invalid: 'Hostname or IP address invalid'
688
+ hostname_long: 'Host is too long (maximum is 1024 characters)'
689
+ html_tags: 'HTML/CSS/JS tags are not allowed'
690
+ no_localhost_requests: 'Requests to localhost are not allowed'
691
+ no_loopback_requests: 'Requests to loopback addresses are not allowed'
692
+ no_local_net_requests: 'Requests to the local network are not allowed'
693
+ no_link_local_net_requests: 'Requests to the link local network are not allowed'
694
+ no_shared_address_requests: 'Requests to the shared address space are not allowed'
695
+ no_limited_broadcast_address_requests: 'Requests to the limited broadcast address are not allowed'
687
696
  url: 'Please enter a valid URL.'
688
697
  date: 'Please enter a valid date.'
689
698
  dateiso: 'Please enter a valid date ( ISO ).'
@@ -697,6 +706,7 @@ en:
697
706
  range: 'Please enter a value between {0} and {1}.'
698
707
  max: 'Please enter a value less than or equal to {0}.'
699
708
  min: 'Please enter a value greater than or equal to {0}.'
709
+ username_alphanumeric: 'Username needs to start with an alphanumeric character'
700
710
  widgets:
701
711
  create_widget: 'Create Widget'
702
712
  create_sidebar: 'Create Sidebar'
@@ -59,7 +59,7 @@ module CamaleonCms
59
59
  app.config.assets.paths << File.join($camaleon_engine_dir, 'app', 'assets', 'fonts')
60
60
  app.config.encoding = 'utf-8'
61
61
 
62
- # add prefix url, like: http://localhost.com/blog/
62
+ # add prefix url, like: https://localhost.com/blog/
63
63
  # config.action_controller.relative_url_root = PluginRoutes.system_info["relative_url_root"] if PluginRoutes.system_info["relative_url_root"].present?
64
64
 
65
65
  # multiple route files
@@ -1,3 +1,3 @@
1
1
  module CamaleonCms
2
- VERSION = '2.7.4'.freeze
2
+ VERSION = '2.8.0'.freeze
3
3
  end
data/lib/ext/hash.rb CHANGED
@@ -19,7 +19,7 @@ class Hash
19
19
 
20
20
  # used for hash of objects
21
21
  def find_by(val, attr = 'id')
22
- each do |_key, p|
22
+ each_value do |p|
23
23
  return p if p[attr].to_s == val.to_s
24
24
  end
25
25
  nil
data/lib/ext/string.rb CHANGED
@@ -66,11 +66,11 @@ class String
66
66
  end
67
67
 
68
68
  # parse string into domain
69
- # http://owem.tuzitio.com into owem.tuzitio.com
69
+ # https://owen.camaleon.website into owen.camaleon.website
70
70
  def parse_domain
71
71
  url = self
72
72
  uri = URI.parse(url)
73
- uri = URI.parse("http://#{url}") if uri.scheme.nil?
73
+ uri = URI.parse("https://#{url}") if uri.scheme.nil?
74
74
  host = (uri.host || self).downcase
75
75
  h = host.start_with?('www.') ? host[4..] : host
76
76
  "#{h}#{":#{uri.port}" unless [80, 443].include?(uri.port)}"