camaleon_cms 2.1.1.4 → 2.1.2.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -3
  3. data/app/apps/plugins/contact_form/contact_form_helper.rb +3 -44
  4. data/app/apps/plugins/contact_form/contact_form_html_helper.rb +1 -1
  5. data/app/apps/plugins/contact_form/views/contact_form/{_submission.html.erb → _email_content.html.erb} +0 -0
  6. data/app/apps/themes/camaleon_first/views/index.html.erb +1 -1
  7. data/app/apps/themes/default/views/category.html.erb +1 -1
  8. data/app/apps/themes/default/views/page.html.erb +1 -1
  9. data/app/apps/themes/default/views/partials/_comments.html.erb +5 -5
  10. data/app/apps/themes/default/views/post.html.erb +1 -1
  11. data/app/apps/themes/default/views/post_tag.html.erb +1 -1
  12. data/app/apps/themes/default/views/post_type.html.erb +1 -1
  13. data/app/apps/themes/default/views/search.html.erb +2 -2
  14. data/app/apps/themes/new/views/category.html.erb +1 -1
  15. data/app/apps/themes/new/views/page.html.erb +1 -1
  16. data/app/apps/themes/new/views/post_tag.html.erb +1 -1
  17. data/app/apps/themes/new/views/post_type.html.erb +1 -1
  18. data/app/apps/themes/new/views/search.html.erb +2 -2
  19. data/app/assets/javascripts/camaleon_cms/admin/_actions.js +1 -1
  20. data/app/assets/javascripts/camaleon_cms/admin/{bootstrap-datepicker.js → _bootstrap-datepicker.js} +0 -0
  21. data/app/assets/javascripts/camaleon_cms/admin/_bootstrap-select.js +7 -0
  22. data/app/assets/javascripts/camaleon_cms/admin/_custom_fields.js +9 -0
  23. data/app/assets/javascripts/camaleon_cms/admin/admin-manifest.js +2 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/jquery.validate.js +3 -1
  25. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js.coffee +125 -0
  26. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee +53 -23
  27. data/app/assets/javascripts/camaleon_cms/admin/user_profile.js +23 -29
  28. data/app/assets/stylesheets/camaleon_cms/admin/{bootstrap-datepicker.css.scss → _bootstrap-datepicker.css.scss} +0 -0
  29. data/app/assets/stylesheets/camaleon_cms/admin/_bootstrap-select.css +6 -0
  30. data/app/assets/stylesheets/camaleon_cms/admin/admin-manifest.css +2 -3
  31. data/app/assets/stylesheets/camaleon_cms/admin/colorpicker.css.scss +9 -8
  32. data/app/assets/stylesheets/camaleon_cms/admin/uploader/_uploadfile.css.scss +1 -12
  33. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +102 -81
  34. data/app/controllers/camaleon_cms/admin/media_controller.rb +8 -25
  35. data/app/controllers/camaleon_cms/admin_controller.rb +1 -1
  36. data/app/controllers/camaleon_cms/camaleon_controller.rb +24 -0
  37. data/app/controllers/camaleon_cms/frontend_controller.rb +0 -1
  38. data/app/decorators/camaleon_cms/application_decorator.rb +5 -8
  39. data/app/decorators/camaleon_cms/post_decorator.rb +5 -5
  40. data/app/decorators/camaleon_cms/post_type_decorator.rb +2 -5
  41. data/app/decorators/camaleon_cms/site_decorator.rb +12 -0
  42. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +1 -1
  43. data/app/helpers/camaleon_cms/admin/application_helper.rb +8 -1
  44. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +17 -16
  45. data/app/helpers/camaleon_cms/admin/menus_helper.rb +1 -1
  46. data/app/helpers/camaleon_cms/camaleon_helper.rb +2 -2
  47. data/app/helpers/camaleon_cms/captcha_helper.rb +15 -14
  48. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  49. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +7 -1
  50. data/app/helpers/camaleon_cms/html_helper.rb +1 -1
  51. data/app/helpers/camaleon_cms/site_helper.rb +6 -15
  52. data/app/helpers/camaleon_cms/uploader_helper.rb +31 -187
  53. data/app/mailers/camaleon_cms/html_mailer.rb +4 -8
  54. data/app/models/camaleon_cms/custom_field_group.rb +1 -0
  55. data/app/models/camaleon_cms/nav_menu.rb +1 -20
  56. data/app/models/camaleon_cms/nav_menu_item.rb +13 -0
  57. data/app/models/camaleon_cms/post.rb +1 -1
  58. data/app/models/camaleon_cms/post_type.rb +8 -7
  59. data/app/models/camaleon_cms/site.rb +22 -2
  60. data/app/models/camaleon_cms/user.rb +2 -1
  61. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +21 -29
  62. data/app/models/concerns/camaleon_cms/metas.rb +4 -3
  63. data/app/uploaders/camaleon_cms_aws_uploader.rb +81 -0
  64. data/app/uploaders/camaleon_cms_local_uploader.rb +84 -0
  65. data/app/uploaders/camaleon_cms_uploader.rb +146 -0
  66. data/app/views/camaleon_cms/admin/appearances/nav_menus/_custom_fields.html.erb +3 -5
  67. data/app/views/camaleon_cms/admin/appearances/nav_menus/_external_menu.html.erb +7 -6
  68. data/app/views/camaleon_cms/admin/appearances/nav_menus/_form.html.erb +13 -0
  69. data/app/views/camaleon_cms/admin/appearances/nav_menus/_left_menu_items.html.erb +77 -0
  70. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_items.html.erb +18 -0
  71. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_items_list.html.erb +12 -0
  72. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_options.html.erb +21 -0
  73. data/app/views/camaleon_cms/admin/appearances/nav_menus/index.html.erb +5 -98
  74. data/app/views/camaleon_cms/admin/media/_files_list.html.erb +2 -7
  75. data/app/views/camaleon_cms/admin/media/_render_file_item.html.erb +28 -26
  76. data/app/views/camaleon_cms/admin/media/_render_folder_item.html.erb +14 -12
  77. data/app/views/camaleon_cms/admin/media/index.html.erb +23 -22
  78. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +4 -5
  79. data/app/views/camaleon_cms/admin/posts/form.html.erb +1 -0
  80. data/app/views/camaleon_cms/admin/sessions/forgot.html.erb +6 -6
  81. data/app/views/camaleon_cms/admin/sessions/login.html.erb +7 -7
  82. data/app/views/camaleon_cms/admin/sessions/register.html.erb +8 -8
  83. data/app/views/camaleon_cms/admin/settings/_configuration_settings.html.erb +30 -6
  84. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +26 -23
  85. data/app/views/camaleon_cms/admin/settings/_file_system_settings.html.erb +25 -20
  86. data/app/views/camaleon_cms/admin/settings/_media_settings.html.erb +10 -0
  87. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +2 -3
  88. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +2 -2
  89. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_audio.html.erb +1 -1
  90. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_colorpicker.html.erb +2 -2
  91. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_date.html.erb +1 -1
  92. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_field_attrs.html.erb +2 -2
  93. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_file.html.erb +1 -1
  94. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_image.html.erb +1 -1
  95. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_video.html.erb +1 -1
  96. data/app/views/camaleon_cms/admin/settings/post_types/_form.html.erb +2 -2
  97. data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
  98. data/app/views/camaleon_cms/admin/settings/site.html.erb +11 -13
  99. data/app/views/camaleon_cms/admin/settings/sites/index.html.erb +5 -3
  100. data/app/views/camaleon_cms/admin/users/form.html.erb +14 -22
  101. data/app/views/camaleon_cms/default_theme/category.html.erb +1 -1
  102. data/app/views/camaleon_cms/default_theme/partials/_comments.html.erb +5 -5
  103. data/app/views/camaleon_cms/default_theme/partials/_search_form.html.erb +1 -1
  104. data/app/views/camaleon_cms/default_theme/partials/_sidebar.html.erb +4 -4
  105. data/app/views/camaleon_cms/default_theme/post_tag.html.erb +1 -1
  106. data/app/views/camaleon_cms/default_theme/post_type.html.erb +1 -1
  107. data/app/views/camaleon_cms/default_theme/search.html.erb +1 -1
  108. data/app/views/camaleon_cms/default_theme/single.html.erb +1 -1
  109. data/config/locales/camaleon_cms/admin/en.yml +1 -1
  110. data/config/locales/camaleon_cms/admin/es.yml +36 -0
  111. data/config/locales/camaleon_cms/common.yml +4 -2
  112. data/config/routes/admin.rb +13 -7
  113. data/config/routes/frontend.rb +9 -7
  114. data/config/system.json +1 -0
  115. data/lib/camaleon_cms/engine.rb +1 -1
  116. data/lib/camaleon_cms/version.rb +1 -1
  117. data/lib/ext/string.rb +12 -0
  118. data/lib/generators/camaleon_cms/gem_plugin_generator.rb +1 -1
  119. data/lib/plugin_routes.rb +1 -1
  120. metadata +17 -24
  121. data/app/assets/javascripts/camaleon_cms/admin/bootstrap-select.js +0 -1022
  122. data/app/assets/javascripts/camaleon_cms/admin/nav-menu.js +0 -177
  123. data/app/assets/stylesheets/camaleon_cms/admin/nav-menu.css.scss +0 -33
  124. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_form.html.erb +0 -36
  125. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_list.html.erb +0 -22
@@ -16,6 +16,7 @@ class CamaleonCms::NavMenuItem < CamaleonCms::TermTaxonomy
16
16
  after_create :update_count
17
17
  #before_destroy :update_count
18
18
  alias_attribute :site_id, :term_group
19
+ alias_attribute :label, :name
19
20
 
20
21
  # return the main menu
21
22
  def main_menu
@@ -30,6 +31,12 @@ class CamaleonCms::NavMenuItem < CamaleonCms::TermTaxonomy
30
31
  self.get_option('type')
31
32
  end
32
33
 
34
+ # return the url of the external menu item
35
+ # return the object_id of menus like posttype, post, category, ...
36
+ def url
37
+ get_option('object_id')
38
+ end
39
+
33
40
  # check if this menu have children
34
41
  def have_children?
35
42
  self.children.count != 0
@@ -42,6 +49,12 @@ class CamaleonCms::NavMenuItem < CamaleonCms::TermTaxonomy
42
49
  children.create({name: value[:label], data_options: {type: value[:type], object_id: value[:link]}})
43
50
  end
44
51
 
52
+ # update current menu
53
+ # value: same as append_menu_item (label, link)
54
+ def update_menu_item(value)
55
+ self.update({name: value[:label], data_options: {object_id: value[:link]}})
56
+ end
57
+
45
58
  # skip uniq slug validation
46
59
  def skip_slug_validation?
47
60
  true
@@ -33,7 +33,7 @@ end
33
33
  class CamaleonCms::Post < CamaleonCms::PostDefault
34
34
  include CamaleonCms::CategoriesTagsForPosts
35
35
  default_scope ->{ where(post_class: "Post").order(post_order: :asc, created_at: :desc) }
36
- has_many :metas, ->{ where(object_class: 'Post')}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :destroy
36
+ has_many :metas, ->{ where(object_class: 'Post')}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :delete_all
37
37
 
38
38
  # DEPRECATED
39
39
  has_many :post_relationships, class_name: "CamaleonCms::PostRelationship", foreign_key: :objectid, dependent: :destroy, inverse_of: :posts
@@ -8,7 +8,7 @@
8
8
  =end
9
9
  class CamaleonCms::PostType < CamaleonCms::TermTaxonomy
10
10
  default_scope { where(taxonomy: :post_type) }
11
- has_many :metas, ->{ where(object_class: 'PostType')}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :destroy
11
+ has_many :metas, ->{ where(object_class: 'PostType')}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :delete_all
12
12
  has_many :categories, :class_name => "CamaleonCms::Category", foreign_key: :parent_id, dependent: :destroy
13
13
  has_many :post_tags, :class_name => "CamaleonCms::PostTag", foreign_key: :parent_id, dependent: :destroy
14
14
  has_many :posts, class_name: "CamaleonCms::Post", foreign_key: :taxonomy_id, dependent: :destroy
@@ -169,17 +169,18 @@ class CamaleonCms::PostType < CamaleonCms::TermTaxonomy
169
169
 
170
170
  # destroy all custom field groups assigned to this post type
171
171
  def destroy_field_groups
172
- # TODO: CHANGE TO SUPPORT DESTROY FOR SITE DESTROY
173
- # if self.slug == "post" || self.slug == "page"
174
- # errors.add(:base, "This post type can not be deleted.")
175
- # return false
176
- # end
172
+ unless self.destroyed_by_association.present?
173
+ if self.slug == "post" || self.slug == "page"
174
+ errors.add(:base, "This post type can not be deleted.")
175
+ return false
176
+ end
177
+ end
177
178
  self.get_field_groups.destroy_all
178
179
  end
179
180
 
180
181
  # reload routes to enable this post type url, like: http://localhost/my-slug
181
182
  def refresh_routes
182
- PluginRoutes.reload
183
+ PluginRoutes.reload unless self.destroyed_by_association.present?
183
184
  end
184
185
 
185
186
  # check if slug was changed
@@ -9,7 +9,7 @@
9
9
  class CamaleonCms::Site < CamaleonCms::TermTaxonomy
10
10
  # attrs: [name, description, slug]
11
11
  default_scope { where(taxonomy: :site).reorder(term_group: :desc) }
12
- has_many :metas, -> { where(object_class: 'Site') }, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :destroy
12
+ has_many :metas, -> { where(object_class: 'Site') }, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :delete_all
13
13
  has_many :post_types, :class_name => "CamaleonCms::PostType", foreign_key: :parent_id, dependent: :destroy
14
14
  has_many :nav_menus, :class_name => "CamaleonCms::NavMenu", foreign_key: :parent_id, dependent: :destroy
15
15
  has_many :nav_menu_items, :class_name => "CamaleonCms::NavMenuItem", foreign_key: :term_group
@@ -28,6 +28,7 @@ class CamaleonCms::Site < CamaleonCms::TermTaxonomy
28
28
  after_create :set_default_user_roles
29
29
  after_save :update_routes
30
30
  before_destroy :destroy_site
31
+ after_destroy :reload_routes
31
32
  validates_uniqueness_of :slug, scope: :taxonomy
32
33
 
33
34
  # all user roles for this site
@@ -236,6 +237,21 @@ class CamaleonCms::Site < CamaleonCms::TermTaxonomy
236
237
  end
237
238
  end
238
239
 
240
+ # check if current site is active or not
241
+ def is_active?
242
+ !self.status.present? || self.status == 'active'
243
+ end
244
+
245
+ # check if current site is active or not
246
+ def is_inactive?
247
+ self.status == 'inactive'
248
+ end
249
+
250
+ # check if current site is in maintenance or not
251
+ def is_maintenance?
252
+ self.status == 'maintenance'
253
+ end
254
+
239
255
  private
240
256
  # destroy all things before site destroy
241
257
  def destroy_site
@@ -250,7 +266,7 @@ class CamaleonCms::Site < CamaleonCms::TermTaxonomy
250
266
  def default_settings
251
267
  default_post_type = [
252
268
  {name: 'Post', description: 'Posts', 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, }},
253
- {name: 'Page', description: 'Pages', 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, }}
269
+ {name: 'Page', description: 'Pages', 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}}
254
270
  ]
255
271
  default_post_type.each do |pt|
256
272
  model_pt = self.post_types.create({name: pt[:name], slug: pt[:name].to_s.parameterize, description: pt[:description], data_options: pt[:options]})
@@ -290,6 +306,10 @@ class CamaleonCms::Site < CamaleonCms::TermTaxonomy
290
306
  PluginRoutes.reload if self.slug_changed?
291
307
  end
292
308
 
309
+ def reload_routes
310
+ PluginRoutes.reload
311
+ end
312
+
293
313
  def before_validating
294
314
  slug = self.slug
295
315
  slug = self.name if slug.blank?
@@ -74,8 +74,9 @@ class CamaleonCms::User < ActiveRecord::Base
74
74
  self.role == 'client'
75
75
  end
76
76
 
77
+ # return the UserRole Object of this user in Site
77
78
  def get_role(site)
78
- site.user_roles.where(slug: self.role).first
79
+ @_user_role ||= site.user_roles.where(slug: self.role).first
79
80
  end
80
81
 
81
82
  def set_meta_from_form(metas)
@@ -8,10 +8,13 @@
8
8
  =end
9
9
  module CamaleonCms::CustomFieldsRead extend ActiveSupport::Concern
10
10
  included do
11
- has_many :fields, ->(object){ where(:object_class => object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::",""))} , :class_name => "CamaleonCms::CustomField" ,foreign_key: :objectid
12
- has_many :field_values, ->(object){where(object_class: object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::",""))}, :class_name => "CamaleonCms::CustomFieldsRelationship", foreign_key: :objectid, dependent: :destroy
13
- has_many :custom_field_values, :class_name => "CamaleonCms::CustomFieldsRelationship", foreign_key: :objectid, dependent: :destroy
14
11
  before_destroy :_destroy_custom_field_groups
12
+ has_many :fields, ->(object){ where(:object_class => object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::",""))} , :class_name => "CamaleonCms::CustomField" ,foreign_key: :objectid
13
+ has_many :field_values, ->(object){where(object_class: object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::",""))}, :class_name => "CamaleonCms::CustomFieldsRelationship", foreign_key: :objectid, dependent: :delete_all
14
+ has_many :custom_field_values, :class_name => "CamaleonCms::CustomFieldsRelationship", foreign_key: :objectid, dependent: :delete_all
15
+
16
+ # valid only for simple groups and not for complex like: posts, post, ... where the group is for individual or children groups
17
+ has_many :field_groups, ->(object){where(object_class: object.class.to_s.parseCamaClass)}, :class_name => "CamaleonCms::CustomFieldGroup", foreign_key: :objectid
15
18
  end
16
19
 
17
20
 
@@ -25,34 +28,32 @@ module CamaleonCms::CustomFieldsRead extend ActiveSupport::Concern
25
28
  # args: (String) => is a value for kind attribute
26
29
  def get_field_groups(args = {})
27
30
  args = args.is_a?(String) ? {kind: args, include_parent: false } : {kind: "post", include_parent: false }.merge(args)
28
- class_name = self.class.to_s.gsub("Decorator","").gsub("CamaleonCms::","")
31
+ class_name = self.class.to_s.parseCamaClass
29
32
  case class_name
30
33
  when 'Category','Post','PostTag'
31
34
  if args[:include_parent]
32
- self.post_type.site.custom_field_groups.where("(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)", self.id || -1, class_name, self.post_type.id, "PostType_#{class_name}")
35
+ CamaleonCms::CustomFieldGroup.where("(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)", self.id || -1, class_name, self.post_type.id, "PostType_#{class_name}")
33
36
  else
34
- self.post_type.site.custom_field_groups.where(objectid: self.id || -1, object_class: class_name)
37
+ CamaleonCms::CustomFieldGroup.where(objectid: self.id || -1, object_class: class_name)
35
38
  end
36
39
  when 'Widget::Main'
37
- self.site.custom_field_groups.where(object_class: class_name, objectid: self.id)
40
+ self.field_groups
38
41
  when 'Theme'
39
- self.site.custom_field_groups.where(object_class: class_name, objectid: self.id)
42
+ self.field_groups
40
43
  when 'Site'
41
- self.custom_field_groups.where(object_class: class_name)
44
+ self.field_groups
42
45
  when 'NavMenuItem'
43
- # self.main_menu.custom_field_groups //verify this problem
44
- puts "get_field_groups - NavMenuItem: **************#{self.inspect}***** #{self.main_menu.inspect}"
45
- CamaleonCms::NavMenu.find(self.main_menu.id).get_field_groups
46
+ self.main_menu.field_groups
46
47
  when 'PostType'
47
48
  if args[:kind] == "all"
48
- self.site.custom_field_groups.where(object_class: ["PostType_Post", "PostType_Post", "PostType_PostTag", "PostType"], objectid: self.id )
49
+ CamaleonCms::CustomFieldGroup.where(object_class: ["PostType_Post", "PostType_Post", "PostType_PostTag", "PostType"], objectid: self.id )
49
50
  elsif args[:kind] == "post_type"
50
- self.site.custom_field_groups.where(object_class: class_name)
51
+ self.field_groups
51
52
  else
52
- self.site.custom_field_groups.where(object_class: "PostType_#{args[:kind]}", objectid: self.id )
53
+ CamaleonCms::CustomFieldGroup.where(object_class: "PostType_#{args[:kind]}", objectid: self.id )
53
54
  end
54
55
  else # 'Plugin' or other class
55
- self.site.custom_field_groups.where(object_class: class_name, objectid: self.id) if defined?(self.site)
56
+ self.field_groups
56
57
  end
57
58
  end
58
59
 
@@ -168,26 +169,17 @@ module CamaleonCms::CustomFieldsRead extend ActiveSupport::Concern
168
169
  # :key3 : {id: 4555, values: ['uno','dos']}
169
170
  # }
170
171
  def set_field_values(datas = {})
171
- ids_old = self.field_values.pluck(:id)
172
- ids_saved = []
172
+ self.field_values.delete_all
173
173
  if datas.present?
174
174
  datas.each do |key, values|
175
175
  if values[:values].present?
176
- order_value = 0
177
- values[:values].each do |value|
178
- item = self.field_values.where({custom_field_id: values[:id], custom_field_slug: key, value: fix_meta_value(value)}).first_or_create!()
179
- if defined?(item.id)
180
- item.update_column('term_order', order_value)
181
- ids_saved << item.id
182
- order_value += 1
183
- end
176
+ order_value = -1
177
+ (values[:values].is_a?(Hash) ? values[:values].values : values[:values]).each do |value|
178
+ item = self.field_values.create!({custom_field_id: values[:id], custom_field_slug: key, value: fix_meta_value(value), term_order: order_value += 1})
184
179
  end
185
180
  end
186
181
  end
187
182
  end
188
-
189
- ids_deletes = ids_old - ids_saved
190
- self.field_values.where(id: ids_deletes).destroy_all if ids_deletes.present?
191
183
  end
192
184
 
193
185
  # return field object for current model
@@ -14,7 +14,7 @@ module CamaleonCms::Metas extend ActiveSupport::Concern
14
14
  after_create :save_metas_options, unless: :save_metas_options_skip
15
15
  before_update :fix_save_metas_options_no_changed
16
16
 
17
- has_many :metas, ->(object){where(object_class: object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::", ""))}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :destroy
17
+ has_many :metas, ->(object){where(object_class: object.class.to_s.gsub("Decorator","").gsub("CamaleonCms::", ""))}, :class_name => "CamaleonCms::Meta", foreign_key: :objectid, dependent: :delete_all
18
18
  end
19
19
 
20
20
  # Add meta with value or Update meta with key: key
@@ -30,11 +30,11 @@ module CamaleonCms::Metas extend ActiveSupport::Concern
30
30
  def get_meta(key, default = nil)
31
31
  key_str = key.is_a?(Symbol) ? key.to_s : key
32
32
  cama_fetch_cache("meta_#{key_str}") do
33
- option = metas.select { |m| m.key.eql?(key_str) }.first
33
+ option = metas.where(key: key_str).first
34
34
  res = ''
35
35
  if option.present?
36
36
  value = JSON.parse(option.value) rescue option.value
37
- res = (value.is_a?(Hash) ? value.to_sym : value) rescue option.value
37
+ res = (value.is_a?(Hash) ? value.with_indifferent_access : value) rescue option.value
38
38
  end
39
39
  res == '' ? default : res
40
40
  end
@@ -94,6 +94,7 @@ module CamaleonCms::Metas extend ActiveSupport::Concern
94
94
  set_meta(meta_key, data)
95
95
  end
96
96
  end
97
+ alias_method :set_options, :set_multiple_options
97
98
 
98
99
  # permit to skip save_metas_options in specific models
99
100
  def save_metas_options_skip
@@ -0,0 +1,81 @@
1
+ class CamaleonCmsAwsUploader < CamaleonCmsUploader
2
+ # recover all files from AWS and parse it to save into DB as cache
3
+ def browser_files
4
+ objects = {}
5
+ objects['/'] = {files: {}, folders: {}}
6
+ bucket.objects.each do |file|
7
+ cache_item(file_parse(file), objects)
8
+ end
9
+ @current_site.set_meta('cama_media_cache', objects)
10
+ objects
11
+ end
12
+
13
+ # parse an AWS file into custom file_object
14
+ def file_parse(s3_file)
15
+ key = s3_file.is_a?(String) ? s3_file : s3_file.key
16
+ key = "/#{key}" unless key.starts_with?('/')
17
+ is_dir = File.extname(key) == ''
18
+ res = {
19
+ "name" => File.basename(key),
20
+ "key" => key,
21
+ "url" => is_dir ? '' : (@current_site.get_option("filesystem_s3_cloudfront").present? ? File.join(@current_site.get_option("filesystem_s3_cloudfront"), key) : s3_file.public_url),
22
+ "is_folder" => is_dir,
23
+ "size" => is_dir ? 0 : s3_file.size.round(2),
24
+ "format" => is_dir ? 'folder' : self.class.get_file_format(key),
25
+ "deleteUrl" => '',
26
+ "thumb" => '',
27
+ 'type' => is_dir ? '' : (s3_file.content_type rescue (MIME::Types.type_for(key).first.content_type rescue "")),
28
+ 'created_at' => is_dir ? '' : s3_file.last_modified,
29
+ 'dimension' => ''
30
+ }.with_indifferent_access
31
+ res["thumb"] = version_path(res['url']) if res['format'] == 'image' && File.extname(res['name']).downcase != '.gif'
32
+ if res['format'] == 'image'
33
+ # TODO: Recover image dimension (suggestion: save dimesion as metadata)
34
+ end
35
+ res
36
+ end
37
+
38
+ # add a file object or file path into AWS server
39
+ # :key => (String) key of the file ot save in AWS
40
+ # :args => (HASH) {same_name: false, is_thumb: false}, where:
41
+ # - same_name: false => avoid to overwrite an existent file with same key and search for an available key
42
+ # - is_thumb: true => if this file is a thumbnail of an uploaded file
43
+ def add_file(uploaded_io_or_file_path, key, args = {})
44
+ args, res = {same_name: false, is_thumb: false}.merge(args), nil
45
+ key = search_new_key(key) unless args[:same_name]
46
+ s3_file = bucket.object(key.split('/').clean_empty.join('/'))
47
+ s3_file.upload_file(uploaded_io_or_file_path.is_a?(String) ? uploaded_io_or_file_path : uploaded_io_or_file_path.path, acl: 'public-read')
48
+ res = cache_item(file_parse(s3_file)) unless args[:is_thumb]
49
+ res
50
+ end
51
+
52
+ # add new folder to AWS with :key
53
+ def add_folder(key)
54
+ s3_file = bucket.object(key.split('/').clean_empty.join('/') << '/')
55
+ s3_file.put(body: nil)
56
+ cache_item(file_parse(s3_file))
57
+ s3_file
58
+ end
59
+
60
+ # delete a folder in AWS with :key
61
+ def delete_folder(key)
62
+ bucket.objects(prefix: key.split('/').clean_empty.join('/') << '/').delete
63
+ reload
64
+ end
65
+
66
+ # delete a file in AWS with :key
67
+ def delete_file(key)
68
+ bucket.object(key.split('/').clean_empty.join('/')).delete rescue ''
69
+ reload
70
+ end
71
+
72
+ # initialize a bucket with AWS configurations
73
+ # return: (AWS Bucket object)
74
+ def bucket
75
+ @bucket ||= lambda{
76
+ config = Aws.config.update({ region: @current_site.get_option("filesystem_region", 'us-west-2'), credentials: Aws::Credentials.new(@current_site.get_option("filesystem_s3_access_key"), @current_site.get_option("filesystem_s3_secret_key")) })
77
+ s3 = Aws::S3::Resource.new
78
+ bucket = s3.bucket(@current_site.get_option("filesystem_s3_bucket_name"))
79
+ }.call
80
+ end
81
+ end
@@ -0,0 +1,84 @@
1
+ class CamaleonCmsLocalUploader < CamaleonCmsUploader
2
+ def browser_files(prefix = '/', objects = {})
3
+ objects[prefix] = {files: {}, folders: {}}
4
+ path = File.join(@root_folder, prefix)
5
+ Dir.entries(path).each do |f_name|
6
+ next if f_name == '..' || f_name == '.' || f_name == 'thumb'
7
+ f_path = File.join(path, f_name)
8
+ key = parse_key(f_path)
9
+ obj = get_file(key) || file_parse(key)
10
+ cache_item(obj, objects)
11
+ browser_files(File.join(prefix, obj['name']), objects) if obj['format'] == 'folder'
12
+ end
13
+ @current_site.set_meta('cama_media_cache', objects) if prefix == '/'
14
+ objects
15
+ end
16
+
17
+ def after_initialize
18
+ @root_folder = @args[:root_folder] || @current_site.upload_directory
19
+ FileUtils.mkdir_p(@root_folder) unless Dir.exist?(@root_folder)
20
+ end
21
+
22
+ def file_parse(key)
23
+ file_path = File.join(@root_folder, key)
24
+ url_path, is_dir = file_path.sub(Rails.root.join('public').to_s, ''), File.directory?(file_path)
25
+ res = {
26
+ "name" => File.basename(file_path),
27
+ "key" => parse_key(file_path),
28
+ "url" => is_dir ? '' : File.join(@current_site.the_url(locale: false), url_path),
29
+ "is_folder" => is_dir,
30
+ "size" => is_dir ? 0 : File.size(file_path).round(2),
31
+ "format" => is_dir ? 'folder' : self.class.get_file_format(file_path),
32
+ "deleteUrl" => '',
33
+ "thumb" => '',
34
+ 'type' => (MIME::Types.type_for(file_path).first.content_type rescue ""),
35
+ 'created_at' => File.mtime(file_path),
36
+ 'dimension' => ''
37
+ }.with_indifferent_access
38
+ res["thumb"] = version_path(res['url']) if res['format'] == 'image' && File.extname(file_path).downcase != '.gif'
39
+ if res['format'] == 'image'
40
+ im = MiniMagick::Image.open(file_path)
41
+ res['dimension'] = "#{im[:width]}x#{im[:height]}"
42
+ end
43
+ res
44
+ end
45
+
46
+ #
47
+ def add_file(uploaded_io_or_file_path, key, args = {})
48
+ args, res = {same_name: false, is_thumb: false}.merge(args), nil
49
+ key = search_new_key(key) unless args[:same_name]
50
+ add_folder(File.dirname(key)) if File.dirname(key).present?
51
+ upload_io = uploaded_io_or_file_path.is_a?(String) ? File.open(uploaded_io_or_file_path) : uploaded_io_or_file_path
52
+ File.open(File.join(@root_folder, key), 'wb'){|file| file.write(upload_io.read) }
53
+ res = cache_item(file_parse(key)) unless args[:is_thumb]
54
+ res
55
+ end
56
+
57
+ def add_folder(key)
58
+ d, is_new_folder = File.join(@root_folder, key).to_s, false
59
+ unless Dir.exist?(d)
60
+ FileUtils.mkdir_p(d)
61
+ is_new_folder = true if File.basename(d) != 'thumb'
62
+ end
63
+ f = file_parse(key)
64
+ cache_item(f) if is_new_folder
65
+ f
66
+ end
67
+
68
+ def delete_folder(key)
69
+ FileUtils.rm_rf(File.join(@root_folder, key))
70
+ reload
71
+ end
72
+
73
+ def delete_file(key)
74
+ FileUtils.rm(File.join(@root_folder, key))
75
+ reload
76
+ end
77
+
78
+ # convert a real file path into file key
79
+ def parse_key(file_path)
80
+ r = file_path.sub(@root_folder, '')
81
+ r = "/#{r}" unless r.starts_with?('/')
82
+ r
83
+ end
84
+ end
@@ -0,0 +1,146 @@
1
+ class CamaleonCmsUploader
2
+ attr_accessor :thumb
3
+ # root_folder, current_site, service = 'Local', thumb
4
+ def initialize(args = {})
5
+ @current_site = args[:current_site]
6
+ t_w, t_h = @current_site.get_option('filesystem_thumb_size', '100x100').split('x')
7
+ @thumb = args[:thumb] || {w: t_w, h: t_h}
8
+ @args = args
9
+ after_initialize
10
+ end
11
+
12
+ def after_initialize
13
+
14
+ end
15
+
16
+ # return all files structure, within folder prefix
17
+ # return json like:
18
+ # {files: {'file_name': {'name'=> 'a.jpg', key: '/test/a.jpg', url: '', url: '', size: '', format: '', thumb: 'thumb_url', type: '', created_at: '', dimension: '120x120'}}, folders: {'folder name' => {name: 'folder name', key: '/folder name', ...}}}
19
+ def objects(prefix = '/')
20
+ prefix = "/#{prefix}" unless prefix.starts_with?('/')
21
+ db = @current_site.get_meta('cama_media_cache', nil) || browser_files
22
+ db[prefix.gsub('//', '/')] || {files: {}, folders: {}}
23
+ end
24
+
25
+ # clean cached of files structure saved into DB
26
+ def clear_cache
27
+ @current_site.set_meta('cama_media_cache', nil)
28
+ end
29
+
30
+ # search for folders or files that includes search_text in their names
31
+ def search(search_text)
32
+ res = {files: {}, folders: {}}
33
+ (@current_site.get_meta('cama_media_cache', nil) || browser_files).each do |folder_key, object|
34
+ res[:folders][folder_key] = get_file(folder_key) if !['', '/'].include?(folder_key) && folder_key.split('/').last.include?(search_text)
35
+ object[:files].each do |file_key, obj|
36
+ res[:files][file_key] = obj if file_key.include?(search_text)
37
+ end
38
+ res
39
+ end
40
+ res
41
+ end
42
+
43
+ # reload cache files structure
44
+ def reload
45
+ browser_files
46
+ end
47
+
48
+ # save file_parsed as a cache into DB
49
+ # file_parsed: (HASH) File parsed object
50
+ # objects_db: HASH Object where to add the current object (optional)
51
+ def cache_item(file_parsed, _objects_db = nil)
52
+ objects_db = _objects_db || @current_site.get_meta('cama_media_cache', {}) || {}
53
+ prefix = File.dirname(file_parsed['key'])
54
+
55
+ s = prefix.split('/').clean_empty
56
+ return file_parsed if s.last == 'thumb'
57
+ s.each_with_index{|_s, i| k = "/#{File.join(s.slice(0, i), _s)}"; cache_item(file_parse(k), objects_db) unless objects_db[k].present? } unless ['/', '', '.'].include?(prefix)
58
+
59
+ objects_db[prefix] = {files: {}, folders: {}} if objects_db[prefix].nil?
60
+ if file_parsed['format'] == 'folder'
61
+ objects_db[prefix][:folders][file_parsed['name']] = file_parsed
62
+ else
63
+ objects_db[prefix][:files][file_parsed['name']] = file_parsed
64
+ end
65
+ @current_site.set_meta('cama_media_cache', objects_db) if _objects_db.nil?
66
+ file_parsed
67
+ end
68
+
69
+
70
+ # convert current string path into file version_path, sample:
71
+ # /media/1/screen.png into /media/1/thumb/screen-png.png
72
+ # /media/1/screen.png into /media/1/crop_40x40/screen-png.png
73
+ def version_path(image_path, version_name = 'thumb')
74
+ File.join(File.dirname(image_path), version_name, "#{File.basename(image_path).parameterize}#{File.extname(image_path)}")
75
+ end
76
+
77
+ # return the file format (String) of path (depends of file extension)
78
+ def self.get_file_format(path)
79
+ ext = File.extname(path).sub(".", "").downcase
80
+ format = "unknown"
81
+ format = "image" if get_file_format_extensions('image').split(",").include?(ext)
82
+ format = "video" if get_file_format_extensions('video').split(",").include?(ext)
83
+ format = "audio" if get_file_format_extensions('audio').split(",").include?(ext)
84
+ format = "document" if get_file_format_extensions('document').split(",").include?(ext)
85
+ format = "compress" if get_file_format_extensions('compress').split(",").include?(ext)
86
+ format
87
+ end
88
+
89
+ # return the files extensión for each format
90
+ # support for multiples formats, sample: image,audio
91
+ def self.get_file_format_extensions(format)
92
+ res = []
93
+ format.downcase.gsub(' ', '').split(',').each do |f|
94
+ res << case f
95
+ when 'image', 'images'
96
+ "jpg,jpeg,png,gif,bmp,ico"
97
+ when 'video', 'videos'
98
+ "flv,webm,wmv,avi,swf,mp4,mov,mpg"
99
+ when 'audio'
100
+ "mp3,ogg"
101
+ when 'document', 'documents'
102
+ "pdf,xls,xlsx,doc,docx,ppt,pptx,html,txt,xml,json"
103
+ when 'compress'
104
+ "zip,7z,rar,tar,bz2,gz,rar2"
105
+ else
106
+ ''
107
+ end
108
+ end
109
+ res.join(',')
110
+ end
111
+
112
+ # verify permitted formats (return boolean true | false)
113
+ # true: if format is accepted
114
+ # false: if format is not accepted
115
+ # sample: validate_file_format('/var/www/myfile.xls', 'image,audio,docx,xls') => return true if the file extension is in formats
116
+ def self.validate_file_format(key, valid_formats = "*")
117
+ return true if valid_formats == "*" || !valid_formats.present?
118
+ valid_formats = valid_formats.gsub(' ', '').downcase.split(',') + get_file_format_extensions(valid_formats).split(',')
119
+ valid_formats.include?(File.extname(key).sub(".", "").downcase)
120
+ end
121
+
122
+
123
+ # verify if this file name already exist
124
+ # if the file is already exist, return a new name for this file
125
+ # sample: search_new_key("my_file/file.txt")
126
+ def search_new_key(key)
127
+ _key = key
128
+ if get_file(key).present?
129
+ (1..999).each do |i|
130
+ _key = key.cama_add_postfix_file_name("_#{i}")
131
+ break unless get_file(_key).present?
132
+ end
133
+ end
134
+ _key
135
+ end
136
+
137
+ # check if file with :key exist and return parsed_file, else return nil
138
+ def get_file(key, use_cache = true)
139
+ if use_cache
140
+ db = (@current_site.get_meta('cama_media_cache') || {})[File.dirname(key)] || {}
141
+ else
142
+ db = objects(File.dirname(key)) unless use_cache
143
+ end
144
+ (db[:files][File.basename(key)] || db[:folders][File.basename(key)]) rescue nil
145
+ end
146
+ end