module Scrivito # The CMS widget class # @api public class BasicWidget extend AttributeContent::ClassMethods include AttributeContent def self.type_computer @_type_computer ||= TypeComputer.new(Scrivito::BasicWidget, ::Widget) end def self.reset_type_computer! @_type_computer = nil end # # @api public # This method can be overridden in subclasses to control to which pages or # widgets the given widget class can be added. This method can be used # to ensure that only a special type of widget can be added to a specific # container. An example for this is a +TabGroupWidget+ that should # contain widgets of the +TabWidget+ type only, and, vice versa, a +TabWidget+ # should only be contained in a +TabGroupWidget+. A value of +NilClass+ means that # no additional restrictions are applied. # # This method only further restricts the list of valid classes defined # by means of {AttributeContent#valid_widget_classes_for}. # # @return [NilClass, Array] # # @example # class TabGroupWidget < Widget # def valid_widget_classes_for(field_name) # [TabWidget] # end # end # # class TabWidget < Widget # def self.valid_container_classes # [TabGroupWidget] # end # end # def self.valid_container_classes end def self.valid_inside_container?(container_class) computed_classes = assert_classes(valid_container_classes, '.valid_container_classes') computed_classes.nil? || computed_classes.include?(container_class) end attr_accessor :container, :container_attribute_name attr_writer :obj, :id attr_reader :attributes_to_be_saved delegate :widget_from_pool, to: :obj # Create a new Widget. # The new Widget must be stored inside a container (i.e. an Obj or another Widget) # before it can be used. # # See {Scrivito::BasicObj.create Obj.create} for a detailed overview of how to set attributes. # @api public # @param [Hash] attributes # # @example Create a widget using a subclass # # you can create widgets by explicitly providing the attribute `_obj_class` # new_widget = Widget.new(_obj_class: "MyWidget") # # but it is better to simply use the constructor of a subclass # new_widget = MyWidget.new # # @example Create a widget and store it inside an Obj # # create the widget # new_widget = Widget.new(_obj_class: "MyWidget") # # store it inside an obj # my_obj(my_widget_field: [new_widget]) # def initialize(attributes = {}) @attributes_to_be_saved = self.class.prepare_attributes_for_instantiation(attributes) @attribute_cache = {} end # # Creates a new {Scrivito::BasicWidget Widget}. # # @api public # # It also considers the defaults set via # {Scrivito::AttributeContent::ClassMethods#default_for Widget.default_for}. # # @param [Hash] attributes for the new widget # @param [Hash] context in which the object creating should happen # @option context [Scrivito::User] :scrivito_user current visitor # @return [Widget] the newly created {Scrivito::BasicWidget Widget} # # @see Scrivito::BasicWidget.new # @see Scrivito::AttributeContent::ClassMethods#default_for # def self.new(attributes = {}, context = {}) if obj_class = extract_obj_class_from_attributes(attributes) obj_class.new(attributes, context) else super(build_attributes_with_defaults(attributes, context)) end end def revision obj.revision end # Update the attributes of this Widget # # See {Scrivito::BasicObj.create Obj.create} for a detailed overview of how to set attributes # @api public # @param [Hash] attributes def update(attributes) obj.update(_widget_pool: { self => attributes }) reload end # Destroys the {Scrivito::BasicWidget Widget} in the current {Workspace} # @api public def destroy new_widget_list = container[container_attribute_name] - [self] container.update(container_attribute_name => new_widget_list) end # # Create a copy of a {Scrivito::BasicWidget Widget}. # # @api public # # The copy will have all attributes of the original widget including its widgets. # Its attributes can be accessed only after it has been stored in a widget field of an # {Scrivito::BasicObj Obj}, since initially the copy is not stored in any widget field. # # @example Duplicate the first widget in field +my_widget+ # obj.update(my_widgets: obj.my_widgets.push(obj.my_widgets.first.copy)) # def copy attributes = {} attribute_definitions.each do |attribute_definition| attribute_name = attribute_definition.name attribute_value = read_attribute(attribute_name) attribute_value = attribute_value.map(&:copy) if attribute_definition.widgetlist? attributes[attribute_name] = attribute_value end self.class.new(attributes) end def copy_for_restore(referenced_ids) attributes = {} attribute_definitions.each do |attribute_definition| attribute_name = attribute_definition.name attribute_value = read_attribute(attribute_name) if attribute_definition.widgetlist? attribute_value = attribute_value .reject { |widget| referenced_ids.include?(widget.id) } .map { |widget| widget.copy_for_restore(referenced_ids) } end attributes[attribute_name] = attribute_value end attributes['_id'] = id self.class.new(attributes) end def clone raise "The method `clone' was removed. Please use `copy' instead" end def id if @id @id else raise_not_persisted_error end end def persisted? @attributes_to_be_saved.nil? end def ==(other) other.respond_to?(:obj) && obj == other.obj && other.respond_to?(:id) && id == other.id end def eql?(other) self == other end # # Reverts all changes made to the +Widget+ in the current workspace. # # @api public # # @note This method does not support +Widget+s, which are +new+. # Please use {Scrivito::BasicWidget#destroy Widget#destroy} to destroy them. # @note This method does not support +Widget+s, which are +deleted+. # Please use {Scrivito::BasicObj#revert Obj#revert} to restore them. # # @raise [ScrivitoError] If the current workspace is +published+. # @raise [ScrivitoError] If the +Widget+ is +new+. # @raise [ScrivitoError] If the +Widget+ is +deleted+. # def revert workspace.assert_revertable case modification when Modification::UNMODIFIED # do nothing when Modification::EDITED previous_obj_content = CmsRestApi.get("revisions/#{workspace.base_revision_id}/objs/#{obj.id}") previous_widget_content = previous_obj_content["_widget_pool"]["#{id}"] previous_widget_content.delete_if do |attribute_name, _| type_of_attribute(attribute_name) == 'widgetlist' end CmsRestApi.put("workspaces/#{workspace.id}/objs/#{obj.id}", { obj: {_widget_pool: {id => previous_widget_content}} }) else raise ScrivitoError, "cannot revert changes, since widget is #{modification}." end end def in_revision(revision) obj_in_revision = obj.in_revision(revision) obj_in_revision && obj_in_revision.widget_from_pool(id) end def new?(revision=Workspace.current.base_revision) return false unless revision obj.new?(revision) || cms_data_for_revision(revision).nil? end def deleted?(revision=Workspace.current.base_revision) return false unless revision obj.deleted?(revision) end def modification(revision=Workspace.current.base_revision) return Modification::UNMODIFIED unless revision if deleted?(revision) Modification::DELETED elsif new?(revision) Modification::NEW else if data_from_cms == cms_data_for_revision(revision) Modification::UNMODIFIED else Modification::EDITED end end end def hash if @obj && @id (id + obj.id).hash else super end end # returns the entity ({Scrivito::BasicObj} or {Scrivito::BasicWidget}) that references this widget # @api public def container @container || container_and_attribute_name.first end # returns the name of the widget attribute that references this widget # @api public def container_attribute_name @container_attribute_name || container_and_attribute_name.second end # returns the name of the widget field that references this widget # @api public # @deprecated def container_field_name Scrivito::Deprecation.warn('Scrivito::BasicWidget#container_field_name is deprecated'\ ' and will be removed in a future version.'\ ' Please use Scrivito::BasicWidget#container_attribute_name instead.') container_attribute_name end def container_attribute_index container[container_attribute_name].index(self) end def has_attribute?(key) key.to_s == '_obj_class' || super end def inspect if @id if @obj "<#{self.class} id=\"#{id}\" obj.id=\"#{obj.id}\">" else "<#{self.class} id=\"#{id}\">" end else "<#{self.class}>" end end def obj if @obj @obj else raise_not_persisted_error end end def forget_unsaved_attributes @attributes_to_be_saved = nil reload end def reload obj.reload update_proc = -> { obj.widget_data_from_pool(id) } update_data(update_proc) end # @api public # This method determines the description to be shown in the widget tooltips. By default, # it uses the value of the +Widget.description_for_editor+ class method. # # This method can be overridden to customize the description displayed to the editor. # # @return [String] description for the editor def description_for_editor self.class.description_for_editor end def data_from_cms if persisted? super else raise_not_persisted_error end end private def workspace if revision.workspace revision.workspace else raise ScrivitoError, "No workspace set for the obj of this widget" end end def raise_not_persisted_error raise ScrivitoError.new('Can not access a new widget before it has been saved') end def cms_data_for_revision(revision) obj.widget_data_for_revision(id, revision) end def container_and_attribute_name @container_and_attribute_name ||= obj.find_container_and_attribute_name_for_widget(id) end def has_system_attribute?(attribute_name) attribute_name == '_obj_class' end alias_method :has_public_system_attribute?, :has_system_attribute? def type_of_system_attribute(attribute_name) 'string' if attribute_name == 'obj_class' end def value_of_system_attribute(attribute_name) data_from_cms.value_of(attribute_name) end end end