module RailsConnector module AttributeContent extend ActiveSupport::Concern included do private attr_accessor :data_from_cms end def respond_to?(method_id, include_private=false) if has_attribute?(method_id) true else super end end def method_missing(method_name, *args) if has_attribute?(method_name) read_attribute(method_name.to_s) else super end end def widgets(attribute_name) if widget_data = read_widget_attribute(attribute_name) (widget_data['list'] || []).map do |list_item| widget_from_pool(list_item['widget']) end else [] end end def read_attribute(attribute_name, skip_widgets = true) if skip_widgets && (type_of_attribute(attribute_name) == 'widget') raise "Field #{attribute_name} not (yet) available, since it's a widget" end @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) end end def has_attribute?(name) data_from_cms.has_custom_attribute?(name.to_s) end # @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 has_attribute?(key) ? read_attribute(key) : nil end # Hook method to control which widget classes should be avaiable 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 text_links read_attribute('_text_links') end private def read_widget_attribute(attribute_name) assert_widget_attribute(attribute_name) widget_data = read_attribute(attribute_name.to_s, false) if widget_data && widget_data['layout'] # Old style widget fields have a 'layout' key. Deprecation.warn("Key 'layout' found in attribute '#{attribute_name}' of type 'widget'." \ ' Please migrate the content so that is uses embedded widgets.') end widget_data end def assert_widget_attribute(attribute_name) unless type_of_attribute(attribute_name) == 'widget' raise "Not a widget field: #{attribute_name}." end end def update_data(data) self.data_from_cms = data @attribute_cache = {} end def prepare_attribute_value(attribute_value, attribute_type) case attribute_type when "html" StringTagging.tag_as_html(convert_links(attribute_value), self) when "date" DateAttribute.parse(attribute_value) if attribute_value when "linklist" build_links(attribute_value) when "reference" BasicObj.find([attribute_value]).first when "referencelist" BasicObj.find(attribute_value).compact else attribute_value end end def convert_links(html) if html html.gsub(%r{?(['"]?)}) do link, postfix = text_link_map[$1], $2 link ? convert_link(link, postfix) : missing_link_url(postfix) end end end def convert_link(link, postfix) link.internal? ? convert_internal_link(link, postfix) : convert_external_link(link, postfix) end def convert_internal_link(link, postfix) link.obj ? decorate_link_url(link.internal_url, link, postfix) : missing_link_url(postfix) end def convert_external_link(link, postfix) decorate_link_url(link.external_url, link, postfix) end def decorate_link_url(url, link, postfix) "#{url}#{link.query_and_fragment}#{postfix} #{link.html_attribute_snippet}" end def missing_link_url(postfix) "#{CmsRoutingHelper::LINK_TO_UNREACHABLE}#{postfix}" end def text_link_map @_text_link_map ||= text_links.each_with_object({}) { |link, map| map[link.id] = link } 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? ? [] : Obj.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 module ClassMethods # Instantiate an {BasicObj Obj} instance from obj_data. # If a subclass of Obj with the same name as the property +_obj_class+ exists, # the instantiated Obj will be an instance of that subclass. def instantiate(obj_data) obj_class = obj_data.value_of('_obj_class') type_computer.compute_type(obj_class).new(obj_data) end end end end