2019-08-06 17:54:17 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-10-06 20:40:14 +00:00
|
|
|
module MetadataTemplateConstants
|
|
|
|
ALLOWED_ATTRIBUTES = %w[style href src alt controls data-align data-multimedia data-multimedia-inner id name].freeze
|
|
|
|
ALLOWED_TAGS = %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote
|
|
|
|
figcaption a sub sup small].freeze
|
|
|
|
end
|
|
|
|
|
2019-08-06 17:54:17 +00:00
|
|
|
# Representa la plantilla de un campo en los metadatos del artículo
|
2019-08-06 23:17:29 +00:00
|
|
|
#
|
|
|
|
# TODO: Validar el tipo de valor pasado a value= según el :type
|
2023-10-06 20:38:40 +00:00
|
|
|
# @todo ¿Es necesario pasar :type, :label, :help, :required si se pueden
|
|
|
|
# obtener del layout a través de :name?
|
2019-08-06 17:54:17 +00:00
|
|
|
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
2019-08-08 18:28:23 +00:00
|
|
|
:value, :help, :required, :errors, :post,
|
2019-08-07 21:35:37 +00:00
|
|
|
:layout, keyword_init: true) do
|
2023-10-06 20:40:14 +00:00
|
|
|
include MetadataTemplateConstants
|
2023-10-06 13:56:16 +00:00
|
|
|
include Metadata::FrontMatterConcern
|
2021-05-06 15:33:28 +00:00
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# @return [String]
|
2021-02-24 20:37:13 +00:00
|
|
|
def inspect
|
|
|
|
"#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>"
|
|
|
|
end
|
|
|
|
|
2020-11-11 20:00:09 +00:00
|
|
|
# Queremos que los artículos nuevos siempre cacheen, si usamos el UUID
|
|
|
|
# siempre vamos a obtener un item nuevo.
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [String]
|
2020-11-11 20:00:09 +00:00
|
|
|
def cache_key
|
2023-10-06 20:41:57 +00:00
|
|
|
@cache_key ||=
|
|
|
|
if post.new?
|
|
|
|
"#{layout.value}/#{name}"
|
|
|
|
else
|
|
|
|
"post/#{post.uuid.value}/#{name}"
|
|
|
|
end
|
2020-11-11 20:00:09 +00:00
|
|
|
end
|
|
|
|
|
2021-04-18 22:00:21 +00:00
|
|
|
# Genera una versión de caché en base a la fecha de modificación del
|
|
|
|
# Post, el valor actual y los valores posibles, de forma que cualquier
|
|
|
|
# cambio permita renovar la caché.
|
|
|
|
#
|
2023-10-06 20:38:40 +00:00
|
|
|
# @todo no será demasiado traer #values acá
|
2021-04-18 22:00:21 +00:00
|
|
|
# @return [String]
|
2020-11-11 20:00:09 +00:00
|
|
|
def cache_version
|
2021-04-18 22:00:21 +00:00
|
|
|
post.cache_version + value.hash.to_s + values.hash.to_s
|
2020-11-11 20:00:09 +00:00
|
|
|
end
|
|
|
|
|
2021-04-18 22:00:21 +00:00
|
|
|
# @return [String]
|
2020-11-11 20:00:09 +00:00
|
|
|
def cache_key_with_version
|
2021-05-06 20:54:49 +00:00
|
|
|
"#{cache_key}-#{cache_version}"
|
2020-11-11 20:00:09 +00:00
|
|
|
end
|
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# @todo: Deberíamos sanitizar durante la asignación?
|
2020-10-04 01:32:52 +00:00
|
|
|
def value=(new_value)
|
|
|
|
@value_was = value
|
|
|
|
self[:value] = new_value
|
|
|
|
end
|
|
|
|
|
2021-02-11 18:27:44 +00:00
|
|
|
# Siempre obtener el valor actual y solo obtenerlo del documento una
|
|
|
|
# vez.
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [any]
|
2021-02-11 18:27:44 +00:00
|
|
|
def value_was
|
|
|
|
return @value_was if instance_variable_defined? '@value_was'
|
|
|
|
|
2021-06-16 14:35:37 +00:00
|
|
|
@value_was = document_value
|
2021-02-11 18:27:44 +00:00
|
|
|
end
|
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# ¿Cambió?
|
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2020-10-04 01:32:52 +00:00
|
|
|
def changed?
|
2021-02-11 18:27:44 +00:00
|
|
|
value_was != value
|
2020-10-04 01:32:52 +00:00
|
|
|
end
|
|
|
|
|
2020-12-24 19:13:29 +00:00
|
|
|
# Obtiene el valor del JekyllDocument
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [any]
|
2020-12-24 19:13:29 +00:00
|
|
|
def document_value
|
|
|
|
document.data[name.to_s]
|
|
|
|
end
|
|
|
|
|
2020-11-11 18:29:12 +00:00
|
|
|
# Trae el idioma actual del sitio o del panel
|
|
|
|
# @return [String]
|
|
|
|
def lang
|
2021-03-03 12:43:15 +00:00
|
|
|
@lang ||= post&.lang&.value || I18n.locale
|
2020-11-11 18:29:12 +00:00
|
|
|
end
|
|
|
|
|
2019-08-06 17:54:17 +00:00
|
|
|
# El valor por defecto
|
|
|
|
def default_value
|
2021-03-03 12:43:15 +00:00
|
|
|
layout.metadata.dig(name, 'default', lang.to_s)
|
2019-08-06 17:54:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Valores posibles, busca todos los valores actuales en otros
|
|
|
|
# artículos del mismo sitio
|
2023-10-06 12:59:42 +00:00
|
|
|
#
|
|
|
|
# @return [Array]
|
2019-08-06 17:54:17 +00:00
|
|
|
def values
|
2023-10-06 12:59:42 +00:00
|
|
|
site.indexed_posts.everything_of(name)
|
2019-08-06 17:54:17 +00:00
|
|
|
end
|
|
|
|
|
2020-07-21 22:19:40 +00:00
|
|
|
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
|
|
|
# usando otros métodos que el de asignación.
|
2019-08-06 17:54:17 +00:00
|
|
|
def value
|
2021-06-16 14:35:37 +00:00
|
|
|
self[:value] ||= if (data = document_value).present?
|
2023-10-06 20:38:40 +00:00
|
|
|
if private?
|
|
|
|
decrypt(data)
|
|
|
|
else
|
|
|
|
data
|
|
|
|
end
|
2020-08-20 23:38:31 +00:00
|
|
|
else
|
2020-11-26 16:17:44 +00:00
|
|
|
default_value
|
2020-08-20 23:38:31 +00:00
|
|
|
end
|
2019-08-06 23:17:29 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Detecta si el valor está vacío
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2019-08-06 23:17:29 +00:00
|
|
|
def empty?
|
|
|
|
value.blank?
|
|
|
|
end
|
|
|
|
|
|
|
|
# Comprueba si el metadato es válido
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2019-08-06 23:17:29 +00:00
|
|
|
def valid?
|
|
|
|
validate
|
|
|
|
end
|
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# Ejecuta validaciones
|
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2019-08-06 23:17:29 +00:00
|
|
|
def validate
|
|
|
|
self.errors = []
|
|
|
|
|
2020-07-02 14:26:00 +00:00
|
|
|
errors << I18n.t('metadata.cant_be_empty') unless can_be_empty?
|
2019-08-06 23:17:29 +00:00
|
|
|
|
|
|
|
errors.empty?
|
|
|
|
end
|
|
|
|
|
2020-07-21 22:34:15 +00:00
|
|
|
# Usa el valor por defecto para generar el formato de StrongParam
|
|
|
|
# necesario.
|
|
|
|
#
|
|
|
|
# @return [Symbol,Hash]
|
2019-08-13 23:33:57 +00:00
|
|
|
def to_param
|
2020-07-21 22:34:15 +00:00
|
|
|
case default_value
|
|
|
|
when Hash then { name => default_value.keys.map(&:to_sym) }
|
|
|
|
when Array then { name => [] }
|
|
|
|
else name
|
|
|
|
end
|
2019-08-13 23:33:57 +00:00
|
|
|
end
|
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# Convierte el valor a String
|
|
|
|
#
|
|
|
|
# @return [String]
|
2020-05-23 18:42:12 +00:00
|
|
|
def to_s
|
|
|
|
value.to_s
|
|
|
|
end
|
|
|
|
|
2019-08-22 01:09:29 +00:00
|
|
|
# En caso de que algún campo necesite realizar acciones antes de ser
|
|
|
|
# guardado
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2019-08-22 01:09:29 +00:00
|
|
|
def save
|
2023-10-06 20:41:57 +00:00
|
|
|
unless changed?
|
2022-11-01 16:08:06 +00:00
|
|
|
self[:value] = document_value if private?
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
2021-02-11 19:45:03 +00:00
|
|
|
|
2020-02-12 21:24:54 +00:00
|
|
|
self[:value] = sanitize value
|
2020-08-20 23:38:31 +00:00
|
|
|
self[:value] = encrypt(value) if private?
|
|
|
|
|
2019-08-22 01:09:29 +00:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# Métodos relacionados
|
|
|
|
#
|
|
|
|
# @return [Array]
|
2020-07-22 23:35:43 +00:00
|
|
|
def related_methods
|
2021-02-11 18:27:44 +00:00
|
|
|
@related_methods ||= [].freeze
|
2020-07-22 23:35:43 +00:00
|
|
|
end
|
|
|
|
|
2020-08-20 23:38:31 +00:00
|
|
|
# Determina si el campo es privado y debería ser cifrado
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2020-08-20 23:38:31 +00:00
|
|
|
def private?
|
2021-02-11 19:45:03 +00:00
|
|
|
layout.metadata.dig(name, 'private').present?
|
2020-08-20 23:38:31 +00:00
|
|
|
end
|
|
|
|
|
2021-03-31 18:04:04 +00:00
|
|
|
# Determina si el campo debería estar deshabilitado
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2021-03-31 18:04:04 +00:00
|
|
|
def disabled?
|
|
|
|
layout.metadata.dig(name, 'disabled') || !writable?
|
|
|
|
end
|
|
|
|
|
|
|
|
# Determina si el campo es de solo lectura
|
|
|
|
#
|
|
|
|
# once => el campo solo se puede modificar si estaba vacío
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2021-03-31 18:04:04 +00:00
|
|
|
def writable?
|
|
|
|
case layout.metadata.dig(name, 'writable')
|
|
|
|
when 'once' then value.blank?
|
|
|
|
else true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-08-06 23:17:29 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
# Si es obligatorio no puede estar vacío
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
2019-08-06 23:17:29 +00:00
|
|
|
def can_be_empty?
|
|
|
|
true unless required && empty?
|
2019-08-06 17:54:17 +00:00
|
|
|
end
|
2020-02-12 21:24:54 +00:00
|
|
|
|
|
|
|
# No usamos sanitize_action_text_content porque espera un ActionText
|
|
|
|
#
|
|
|
|
# Ver ActionText::ContentHelper#sanitize_action_text_content
|
2023-10-06 20:38:40 +00:00
|
|
|
#
|
|
|
|
# @param :string [String]
|
|
|
|
# @return [String,nil]
|
2020-02-12 21:24:54 +00:00
|
|
|
def sanitize(string)
|
2020-06-16 22:20:22 +00:00
|
|
|
return if string.nil?
|
2020-02-12 21:24:54 +00:00
|
|
|
return string unless string.is_a? String
|
|
|
|
|
2022-04-04 16:56:40 +00:00
|
|
|
sanitizer
|
2022-04-28 13:34:57 +00:00
|
|
|
.sanitize(string.tr("\r", '').unicode_normalize,
|
2023-10-06 20:40:14 +00:00
|
|
|
tags: ALLOWED_TAGS,
|
|
|
|
attributes: ALLOWED_ATTRIBUTES)
|
2022-04-04 16:56:40 +00:00
|
|
|
.strip
|
|
|
|
.html_safe
|
2020-11-16 16:09:04 +00:00
|
|
|
end
|
2020-11-14 21:02:30 +00:00
|
|
|
|
2023-10-06 20:38:40 +00:00
|
|
|
# @return [Rails::Html::Sanitizer]
|
2020-11-14 21:02:30 +00:00
|
|
|
def sanitizer
|
|
|
|
@sanitizer ||= Rails::Html::Sanitizer.safe_list_sanitizer.new
|
|
|
|
end
|
|
|
|
|
2020-08-20 23:38:31 +00:00
|
|
|
# Decifra el valor
|
|
|
|
#
|
2023-10-06 20:38:40 +00:00
|
|
|
# @todo Agregar el error de decifrado en self.errors
|
|
|
|
# @todo Mover a su propio Concern
|
|
|
|
# @param :value [String]
|
|
|
|
# @return [String,nil]
|
2020-08-20 23:38:31 +00:00
|
|
|
def decrypt(value)
|
|
|
|
return value if value.blank?
|
|
|
|
|
|
|
|
box.decrypt_str value.to_s
|
|
|
|
rescue Lockbox::DecryptionError => e
|
2021-05-24 13:49:35 +00:00
|
|
|
if value.to_s.include? ' '
|
|
|
|
value
|
|
|
|
else
|
|
|
|
ExceptionNotifier.notify_exception(e, data: { site: site.name, post: post.path.absolute, name: name })
|
2020-08-20 23:38:31 +00:00
|
|
|
|
2021-05-24 13:49:35 +00:00
|
|
|
I18n.t('lockbox.help.decryption_error')
|
|
|
|
end
|
2020-08-20 23:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Cifra el valor.
|
|
|
|
#
|
2023-10-06 20:38:40 +00:00
|
|
|
# @todo serializar como JSON
|
|
|
|
# @param :value [String]
|
|
|
|
# @return [String]
|
2020-08-20 23:38:31 +00:00
|
|
|
def encrypt(value)
|
|
|
|
box.encrypt value.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
# Genera una lockbox a partir de la llave privada del sitio
|
|
|
|
#
|
|
|
|
# @return [Lockbox]
|
|
|
|
def box
|
|
|
|
@box ||= Lockbox.new key: site.private_key, padding: true, encode: true
|
|
|
|
end
|
2019-08-06 17:54:17 +00:00
|
|
|
end
|