Module: CamaleonCms::CustomFieldsRead

Extended by:
ActiveSupport::Concern
Included in:
PostDefault, TermTaxonomy
Defined in:
app/models/concerns/camaleon_cms/custom_fields_read.rb

Instance Method Summary collapse

Instance Method Details

#add_custom_field_group(values, kind = 'Post') ⇒ Object Also known as: add_field_group

add a custom field group for current model values: name: name for the group slug: key for group (if slug = _default => this will never show title and description) description: description for the group (optional) is_repeat: (boolean, optional -> default false) indicate if group support multiple format (repeated values) Model supported: PostType, Category, Post, Posttag, Widget, Plugin, Theme, User and Custom models pre configured Note 1: If you need add fields for all post's or all categories, then you need to add the fields into the post_type.add_custom_field_group(values, kind = "Post") post_type.add_custom_field_group(values, kind = "Category") Note 2: If you need add fields for only the Post_type, you have to use options or metas return: CustomFieldGroup object kind: argument only for PostType model: (Post | Category | PostTag), default => Post. If kind = "" this will add group for all post_types



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 196

def add_custom_field_group(values, kind = 'Post')
  values = values.with_indifferent_access
  group = get_field_groups(kind).find_by_slug(values[:slug]) # rubocop:disable Rails/DynamicFindBy
  unless group
    site = _cama_get_field_site
    values[:parent_id] = site.id if site.present?
    group = if is_a?(CamaleonCms::Post) # harcoded for post to support custom field groups
              CamaleonCms::CustomFieldGroup.where(object_class: 'Post', objectid: id).create!(values)
            else
              get_field_groups(kind).create!(values)
            end
  end

  group
end

#add_custom_field_to_default_group(item, options, kind = 'Post') ⇒ Object Also known as: add_field

Add custom fields for a default group: This will create a new group with slug=_default if it doesn't exist yet more details in add_manual_field(item, options) from custom field groups kind: argument only for PostType model: (Post | Category | PostTag), default => Post



217
218
219
220
221
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 217

def add_custom_field_to_default_group(item, options, kind = 'Post')
  g = get_field_groups(kind).find_by_slug('_default') # rubocop:disable Rails/DynamicFindBy
  g ||= add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind)
  g.add_manual_field(item, options)
end

#get_field_groups(args = {}) ⇒ Object

get custom field groups for current object only: Post_type, Post, Category, PostTag, Widget, Site and a Custom model pre configured return collections CustomFieldGroup args: (Hash, used only for PostType Objects) kind: (Post (Default) | Category | PostTag | PostType). If kind = "Post" this will return all groups for all posts from current post type If kind = "Category" this will return all groups for all categories from current post type If kind = "PostTag" this will return all groups for all posttags from current post type If kind = "all" this will return all groups from current post type If kind = "post_type" this will return groups for all post_types Sample: mypost.get_field_groups() ==> return fields for posts from parent posttype Sample: mycat.get_field_groups() ==> return fields for categories from parent posttype Sample: myposttag.get_field_groups() ==> return fields for posttags from parent posttype Sample: mypost_type.get_field_groups('Post') => return custom fields for posts Sample: mypost_type.get_field_groups('Category') => return custom fields for posts Sample: mypost_type.get_field_groups('PostTag') => return custom fields for posts



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 24

def get_field_groups(args = {})
  args = if args.is_a?(String)
           { kind: args,
             include_parent: false }
         else
           { kind: 'Post', include_parent: false }.merge!(args)
         end
  class_name = self.class.to_s.parseCamaClass
  case class_name
  when 'Category', 'PostTag'
    post_type.get_field_groups(class_name)
  when 'Post'
    if term_relationships.empty? && args[:cat_ids].nil?
      CamaleonCms::CustomFieldGroup.where(
        '(objectid = ? AND object_class = ?) OR (objectid = ? AND object_class = ?)',
        id || -1, class_name, post_type.id, "PostType_#{class_name}"
      )
    else
      cat_ids = categories.map(&:id)
      cat_ids += args[:cat_ids] unless args[:cat_ids].nil?
      cat_ids += CamaleonCms::Category.find(cat_ids).map { |category| _category_parents_ids(category) }.flatten.uniq
      CamaleonCms::CustomFieldGroup.where(
        "(objectid = ? AND object_class = ?) OR
         (objectid = ? AND object_class = ?) OR
         (objectid IN (?) AND object_class = ?)",
        id || -1, class_name, post_type.id, "PostType_#{class_name}", cat_ids, "Category_#{class_name}"
      )
    end
  when 'NavMenuItem'
    main_menu.custom_field_groups
  when 'Site'
    # Site overrides `custom_field_groups` to mean "every group this site owns" (foreign key
    # parent_id), unlike the object_class + objectid association every other model receives from
    # CommonRelationships. Scoping by placement here keeps groups meant for post types, themes or
    # menus off the site settings form, where their required fields blocked the submit and their
    # values were discarded on save anyway.
    custom_field_groups.where(object_class: 'Site', objectid: id)
  when 'PostType'
    case args[:kind]
    when 'all'
      CamaleonCms::CustomFieldGroup.where(object_class: %w[PostType_Post PostType_Post PostType_PostTag PostType],
                                          objectid: id)
    when 'post_type'
      custom_field_groups
    else
      CamaleonCms::CustomFieldGroup.where(object_class: "PostType_#{args[:kind]}", objectid: id)
    end
  else # 'Plugin' or other classes
    custom_field_groups
  end
end

#get_field_object(slug) ⇒ Object

return field object for current model



225
226
227
228
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 225

def get_field_object(slug)
  CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups.pluck(:id)).first ||
    CamaleonCms::CustomField.where(slug: slug, parent_id: get_field_groups({ include_parent: true })).first
end

#get_field_value(_key, _default = nil, group_number = 0) ⇒ Object Also known as: get_field, get_field!

get custom field value _key: custom field key if value is not present, then return default return default only if the field was not registered



89
90
91
92
93
94
95
96
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 89

def get_field_value(_key, _default = nil, group_number = 0)
  v = begin
    get_field_values(_key, group_number).first
  rescue StandardError
    _default
  end
  v.presence || _default
end

#get_field_values(_key, group_number = 0) ⇒ Object Also known as: get_fields

get custom field values _key: custom field key



102
103
104
105
106
107
108
109
110
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 102

def get_field_values(_key, group_number = 0)
  if custom_field_values.loaded?
    custom_field_values.select do |f|
      f.custom_field_slug == _key && f.group_number == group_number
    end.map(&:value)
  else
    custom_field_values.where(custom_field_slug: _key, group_number: group_number).pluck(:value)
  end
end

#get_field_values_hash(include_options = false) ⇒ Object

return all values "single value", key2: [multiple, values], key3: value4 if include_options = false {values: "single value", options: {a:1, b: 4}, key2: [multiple, values], options: {a=1, b=2 }} if include_options = true



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 146

def get_field_values_hash(include_options = false)
  fields = {}
  custom_field_values.to_a.uniq.each do |field_value|
    custom_field = field_value.custom_field
    values = custom_field.values.where(objectid: id).pluck(:value)
    unless include_options
      fields[field_value.custom_field_slug] =
        custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first
    end
    next unless include_options

    fields[field_value.custom_field_slug] =
      { values: custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first,
        options: custom_field.cama_options, id: custom_field.id }
  end
  fields.to_sym
end

#get_fields_grouped(field_keys) ⇒ Object

return the values of custom fields grouped by group_number field_keys: (array of keys) samples: my_object.get_fields_grouped(['my_slug1', 'my_slug2']) return: [ { 'my_slug1' => ["val 1"], 'my_slug2' => ['val 2']}, { 'my_slug1' => ["val2 for slug1"], 'my_slug2' => ['val 2 for slug2']} ] ==> 2 groups

return: [
        { 'my_slug1' => ["val 1", 'val 2 for fields multiple support'], 'my_slug2' => ['val 2']},
        { 'my_slug1' => ["val2 for slug1", 'val 2'], 'my_slug2' => ['val 2 for slug2']}
        { 'my_slug1' => ["val3 for slug1", 'val 3'], 'my_slug2' => ['val 3 for slug2']}
] ==> 3 groups

puts res[0]['my_slug1'].first ==> "val 1"


128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 128

def get_fields_grouped(field_keys)
  res = []
  custom_field_values.where(custom_field_slug: field_keys)
                     .order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
    group = field_keys.each_with_object({}) do |field_key, hsh|
      _tmp = group_fields
             .each_with_object([]) { |field, ary| ary << field.value if field_key == field.custom_field_slug }
      hsh[field_key] = _tmp if _tmp.present?
    end
    res << group
  end
  res
end

#get_fields_object(_f = true) ⇒ Object

return all custom fields for current element {options: {, values: [], name: '', ...} } deprecated f attribute



167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 167

def get_fields_object(_f = true)
  fields = {}
  custom_field_values.eager_load(:custom_field).to_a.uniq.each do |field_value|
    custom_field = field_value.custom_field
    # if custom_field.options[:show_frontend].to_s.to_bool
    values = custom_field.values.where(objectid: id).pluck(:value)
    fields[field_value.custom_field_slug] = custom_field.attributes.merge(
      options: custom_field.cama_options,
      values: custom_field.cama_options[:multiple].to_s.to_bool ? values : values.first
    )
    # end
  end
  fields.to_sym
end

#get_user_field_groups(site) ⇒ Object

get custom field groups for current user return collections CustomFieldGroup site: site object



79
80
81
82
83
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 79

def get_user_field_groups(site)
  # Demodulized to match the association scope and the placement the settings form stores
  # ('User' for a host Admin::User); parseCamaClass still strips a Draper Decorator suffix.
  site.custom_field_groups.where(object_class: self.class.to_s.parseCamaClass.demodulize)
end

#save_field_value(key, value, order = 0, clear = true) ⇒ Object

Set custom field values for current model key: slug of the custom field value: array of values for multiple values support value: string value



296
297
298
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 296

def save_field_value(key, value, order = 0, clear = true)
  set_field_value(key, value, { clear: clear, order: order })
end

#set_field_value(key, value, args = {}) ⇒ Object

Set custom field values for current model (support for multiple group values) key: (string required) slug of the custom field value: (array | string) array: array of values for multiple values support, string: uniq value for the custom field args:

field_id: (integer optional) identifier of the custom field
order: order or position of the field value
group_number: number of the group (only for custom field group with is_repeat enabled)
clear: (boolean, default true) if true, will remove previous values and set these values,
                             if not will append values

return false if the was not saved because there is not present the field with slug: key sample: my_post.set_field_value('subtitle', 'Sub Title') sample: set values for a field (for fields that support multiple values) my_post.set_field_value('subtitle', ['Sub Title1', 'Sub Title2']) sample: my_post.set_field_value('subtitle', 'Sub Title', 1) sample: add field values for fields in group 1 my_post.set_field_value('subtitle', 'Sub Title', 1, group_number: 1)

Raises:

  • (ArgumentError)


317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 317

def set_field_value(key, value, args = {})
  args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge!(args)
  if args[:field_id].blank?
    args[:field_id] = begin
      get_field_object(key).id
    rescue StandardError
      nil
    end
  end
  args[:field_id] ||= fallback_field_id_for(key)

  raise ArgumentError, "There is no custom field configured for #{key}" if args[:field_id].blank?

  v = {
    custom_field_id: args[:field_id], custom_field_slug: key, value: fix_meta_value(value),
    term_order: args[:order], group_number: args[:group_number]
  }
  # Atomic (audit M7): clear the previous value and write the new one in one transaction, so a
  # value the scan-and-reject gate refuses (create! -> RecordInvalid) rolls the delete back and
  # the previously stored value survives instead of being destroyed.
  ActiveRecord::Base.transaction do
    if args[:clear]
      custom_field_values.where({ custom_field_slug: key, group_number: args[:group_number] }).delete_all
    end
    if value.is_a?(Array)
      value.each { |val| custom_field_values.create!(v.merge({ value: fix_meta_value(val) })) }
    else
      custom_field_values.create!(v)
    end
  end
end

#set_field_values(datas = {}) ⇒ Object

save all fields sent from browser (reservated for browser request) sample: { "0"=>{ "untitled-text-box"=>"values"=>{"0"=>"33333"}}, "1"=>{ "untitled-text-box"=>"values"=>{"0"=>"33333"}} }



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 236

def set_field_values(datas = {})
  return if datas.blank?

  ActiveRecord::Base.transaction do
    # A value identical to one already stored is not newly authored, so it must not be re-gated on
    # an unrelated edit (audit M8): the admin form round-trips every value and this method
    # delete/recreates them all, so without this skip a single pre-gate dangerous value would fail
    # the whole save and brick every later edit -- the trap Post#reject_untrusted_dangerous_content
    # avoids via content_changed?. Snapshot the stored pairs before clearing; skip the gate for any
    # recreated value that matches one.
    previously_stored = custom_field_values.pluck(:custom_field_slug, :value).to_set
    custom_field_values.delete_all
    datas.each_value do |fields_data|
      fields_data.each do |field_key, values|
        next if values[:values].blank?

        # Resolve the field id from the trusted slug, not the client-supplied values[:id]: a
        # forged custom_field_id points the row at a different field definition, and the
        # scan-and-reject gate keys off custom_field.options[:field_key] -- so a forged non-gated
        # id would slip markup past the gate for a gated (editor/uri/field_attrs) slug. Fall back
        # to values[:id] only when the slug names no field here (trusted/internal callers that
        # pass slugs outside this object's registered groups; permitted browser payloads never do).
        field_id = get_field_object(field_key)&.id || fallback_field_id_for(field_key) || values[:id]
        group_number = [values[:group_number].to_i, 0].max

        order_value = -1
        (
          if values[:values].is_a?(Hash) || values[:values].is_a?(ActionController::Parameters)
            values[:values].values
          else
            values[:values]
          end
        ).each do |value|
          row = custom_field_values.new(
            custom_field_id: field_id, custom_field_slug: field_key,
            value: fix_meta_value(value), term_order: order_value += 1, group_number: group_number
          )
          row.unfiltered_value! if previously_stored.include?([field_key.to_s, row.value])
          row.save!
        end
      end
    end
  end
end

#update_field_value(key, value = nil, group_number = 0) ⇒ Object

Update the value for the field with slug _key Sample: my_posy.update_field_value('sub_title', 'Test Sub Title')



283
284
285
286
287
288
289
290
# File 'app/models/concerns/camaleon_cms/custom_fields_read.rb', line 283

def update_field_value(key, value = nil, group_number = 0)
  # Route through validations so the scan-and-reject gate applies (audit M5): update_column
  # skipped it, letting a dangerous value an untrusted caller supplies reach the frontend verbatim.
  custom_field_values.find_by(custom_field_slug: key, group_number: group_number)
                     &.update(value: value)
rescue StandardError
  nil
end