module Scrivito module AttributeContent extend ActiveSupport::Concern def respond_to?(method_id, include_private=false) if has_custom_attribute?(method_id) true else super end end def method_missing(method_name, *args) if has_custom_attribute?(method_name) read_attribute(method_name.to_s) else super end end def referenced_widgets data_from_cms.all_custom_attributes. select { |attr| type_of_attribute(attr) == "widget" }. map { |attr| read_attribute(attr) }. flatten end def contained_widgets referenced = referenced_widgets referenced + referenced.map { |w| w.contained_widgets }.flatten end def read_attribute(attribute_name) @attribute_cache.fetch(attribute_name) do (raw_value, attribute_type) = data_from_cms.value_and_type_of(attribute_name) @attribute_cache[attribute_name] = prepare_attribute_value(raw_value, attribute_type, attribute_name) end end def has_custom_attribute?(name) name = name.to_s name != 'blob' && data_from_cms.has_custom_attribute?(name) end alias_method :has_attribute?, :has_custom_attribute? # @return [String] def type_of_attribute(field_name) data_from_cms.type_of(field_name.to_s) end # Returns the value of an internal or external attribute specified by its name. # Passing an invalid key will not raise an error, but return +nil+. # @api public def [](key) key = key.to_s if key == '_obj_class' obj_class else has_attribute?(key) ? read_attribute(key) : nil end end # Hook method to control which widget classes should be available for this page. # Override it to allow only certain classes or none. # Must return either +NilClass+, or +Array+. # # If +nil+ is returned (default), then all widget classes will be available for this page. # # If +Array+ is returned, then it should include desired class names. # Each class name must be either a +String+ or a +Symbol+. # Only this class names will be available for this page. # Order of the class names will be preserved. # # @param [String] field_name Name of the widget field. # @return [nil, Array] # @api public def valid_widget_classes_for(field_name) end def modification_for_attribute(attribute_name, revision=Workspace.current.base_revision) return Modification::UNMODIFIED unless revision if new?(revision) Modification::NEW elsif deleted?(revision) Modification::DELETED else cms_data_in_revision = cms_data_for_revision(revision) if cms_data_in_revision other_value = cms_data_in_revision.value_without_default_of(attribute_name.to_s) if data_from_cms.value_without_default_of(attribute_name.to_s) == other_value Modification::UNMODIFIED else Modification::EDITED end else # I am deleted in both revisions! Modification::UNMODIFIED end end end def update_data(data) self.data_from_cms = data @attribute_cache = {} end # Returns the obj class name of this object. # @api public # @return [String] def obj_class_name data_from_cms.value_of('_obj_class') end # Returns the obj class of this object. # @api public # @return [ObjClass] def obj_class if obj_class_data = CmsBackend.instance.find_obj_class_data_by_name(revision, obj_class_name) ObjClass.new(obj_class_data, revision.workspace) end end private attr_writer :data_from_cms def data_from_cms if @data_from_cms.respond_to?(:call) @data_from_cms = @data_from_cms.call else @data_from_cms end end def prepare_attribute_value(attribute_value, attribute_type, attribute_name) case attribute_type when "html" StringTagging.tag_as_html(attribute_value) when "date" DateAttribute.parse(attribute_value) if attribute_value when "linklist" build_links(attribute_value) when "link" build_link(attribute_value) when "reference" BasicObj.find([attribute_value]).first when "referencelist" BasicObj.find(attribute_value).compact when "widget" build_widgets(attribute_value, attribute_name) else attribute_value end end def build_links(link_definitions) if link_definitions.present? link_definitions = link_definitions.map(&:with_indifferent_access) object_ids = link_definitions.map { |link_data| link_data[:destination] }.compact.uniq objects = object_ids.empty? ? [] : BasicObj.find(object_ids) link_definitions.each_with_object([]) do |link_data, links| obj = objects.detect { |o| o && o.id == link_data[:destination] } link = Link.new(link_data.merge(obj: obj)) links << link if link.resolved? end else [] end end def build_link(attribute_value) return unless attribute_value if attribute_value['destination'] build_internal_link(attribute_value) else build_external_link(attribute_value) end end def build_internal_link(attribute_value) properties = { obj: BasicObj.find(attribute_value['destination']), title: attribute_value['title'], query: attribute_value['query'], fragment: attribute_value['fragment'], target: attribute_value['target'], } Link.new(properties) rescue ResourceNotFound end def build_external_link(attribute_value) properties = { url: attribute_value['url'], title: attribute_value['title'], target: attribute_value['target'], } Link.new(properties) end def build_widgets(widget_data, attribute_name) widget_data.map do |widget_id| widget = widget_from_pool(widget_id) unless widget raise ScrivitoError, "Widget with ID #{widget_id} not found!" end widget.container = self widget.container_field_name = attribute_name widget end end module ClassMethods # Instantiate an Obj or Widget instance from obj_data. # If a subclass of Obj or Widget with the same name as the property +_obj_class+ exists, # the instantiated Obj or Widget will be an instance of that subclass. def instantiate(obj_data) obj_class = obj_data.value_of('_obj_class') instance = type_computer.compute_type(obj_class).allocate instance.update_data(obj_data) instance end def with_default_obj_class(attributes) return attributes if attributes[:_obj_class] || attributes["_obj_class"] return attributes if type_computer.special_class?(self) attributes.merge("_obj_class" => self.to_s) end def descendants type_computer = TypeComputer.new(self, nil) CmsRestApi.get("workspaces/#{Workspace.current.id}/obj_classes")['results'] .map { |obj_class_spec| obj_class_spec['name'] } .sort .map { |obj_class_name| type_computer.compute_type(obj_class_name) } .compact end end end end