SpontaneousCMS/spontaneous

View on GitHub
lib/spontaneous/model/core/publishing.rb

Summary

Maintainability
B
6 hrs
Test Coverage
# encoding: UTF-8


module Spontaneous::Model::Core
  module Publishing
    extend Spontaneous::Concern

    module ClassMethods

      def revision_table(revision=nil)
        mapper.revision_table(revision)
      end

      def revision_table?(table_name)
        mapper.revision_table?(table_name)
      end

      def revision_exists?(revision)
        Spontaneous::Publishing::Revision.exists?(self, revision)
        # database.tables.include?(revision_table(revision).to_sym)
      end

      # Returns a list of all tables in the database that represent a content
      # revision.
      def revision_tables
        Spontaneous::Publishing::Revision.tables(self)
        # database.tables.select { |t| revision_table?(t) }.sort
      end

      def revision
        mapper.current_revision
      end

      def with_revision(revision=nil, &block)
        mapper.revision(revision, &block)
      end

      def scope(revision = nil, visible = false, &block)
        mapper.scope(revision, visible, &block)
      end

      # A version of scope that forces the creation of a new scope
      # thus allow us to bypass the cache within a particular block
      def scope!(revision = nil, visible = false, &block)
        mapper.scope!(revision, visible, &block)
      end

      def with_editable(&block)
        mapper.editable(&block)
      end

      def with_editable!(&block)
        mapper.editable!(&block)
      end

      def with_published(site, &block)
        scope(site.published_revision, true, &block)
      end

      def with_published!(site, &block)
        scope!(site.published_revision, true, &block)
      end

      def database
        Spontaneous.database
      end

      ##
      # Publish a revision
      #
      # If content is a non-empty list of Content ids then this will only publish changes to those
      # rows (taking the rest of the content from the previous revision)
      # Pass in a block if you want to tie some bit of post processing to the success or failure
      # of the publish step (used in the site render stage for example)
      def publish(revision, content=nil, &block)
        Spontaneous::Publishing::Revision.patch(self, revision, content, &block)
      end

      def publish_all(revision, &block)
        publish(revision, nil, &block)
      end

      def cleanup_revisions(current_revision, keep_revisions)
        Spontaneous::Publishing::Revision.cleanup(self, current_revision, keep_revisions)
      end

      # def delete_revision_table(revision)
      #   return if revision.nil?
      #   database.drop_table?(revision_table(revision))
      # end

      def delete_revision(revision)
        Spontaneous::Publishing::Revision.delete(self, revision)
      end

      def delete_all_revisions!
        Spontaneous::Publishing::Revision.delete_all(self)
      end

      # def drop_table(table)
      #   database.drop_table?(table)
      # end

      def history_dataset(revision = nil)
        Spontaneous::Publishing::Revision.history_dataset(self, revision)
      end

      def archive_dataset(revision = nil)
        Spontaneous::Publishing::Revision.archive_dataset(self, revision)
      end
    end # ClassMethods

    # InstanceMethods

    def after_create
      super
      page.modified!(page?) if page
    end

    def after_update
      super
      page.modified!(page?) if page
    end

    def modified!(caller_is_page)
      unless caller_is_page
        model.where(id: self.id).update(modified_at: Sequel.datetime_class.now)
      end
    end

    def with_revision(revision, &block)
      self.class.with_revision(revision, &block)
    end

    def scope(revision, visible, &block)
      self.class.scope(revision, visible, &block)
    end

    def with_editable(&block)
      self.class.with_editable(&block)
    end

    def never_published?
      first_published_at.nil?
    end

    def before_publish(revision); end
    def after_publish(revision);  end

    def sync_to_revision(revision, origin=false)
      # 'publish' is a lock to make sure the duplication doesn't cross
      # page boundaries unless that's necessary (such as in the case
      # of a page addition)
      publish = origin || !page?
      first_publish = false

      with_revision(revision) do
        published_copy = content_model.get(id)
        if published_copy
          if publish
            pieces_to_delete = published_copy.content_ids - with_editable { content_ids }
            pieces_to_delete.each do |content_id|
              if c = content_model.get(content_id)
                c.destroy(false) rescue ::Sequel::NoExistingObject
              end
            end
          end
        else # missing content (so force a publish)
          content_model.insert({id: id, type_sid: attributes[:type_sid]})
          publish = true
          first_publish = true
        end

        if publish
          before_publish(revision)
          with_editable do
            pieces.each do |entry|
              entry.sync_to_revision(revision, false)
            end
          end

          if page?
            sync_children_to_revision(revision)
          end

          content_model.where(id: id).update(attributes)

          published_values = {}
          # ancestors can have un-published changes to their paths so we can't just directly publish the current path.
          # Instead we re-calculate our path using the published version of the ancestor's path & our (potentially) updated slug.
          if page?
            published = self.class.get(id)
            published_values[:path] = published.calculate_path_with_slug(slug)
          end

          # need to calculate the correct visibility for published items. I can't just take this from the editable
          # content because up-tree visibility changes might not have been published. This kinda mess is why individual
          # page publishing is a pain.
          # However, this only applies if the item's visibility is dependent on some up-tree state. So
          # if hidden_origin is empty (which means we have a separately calculated visibility) we want
          # to take visibility from our own value.

          published_values[:hidden] = recalculated_hidden unless hidden_origin.blank?

          unless published_values.empty?
            content_model.where(id: id).update(published_values)
          end
        end
      end
    end

    def sync_children_to_revision(revision)
      published_children = with_revision(revision) { content_model.filter(parent_id: self.id) }
      published_children.each do |child_page|
        editable = with_editable { content_model.select(:id, :box_position).get(child_page.id) }
        deleted  = editable.nil?
        moved    = child_page.box_position != editable[:box_position]
        with_revision(revision) do
          if deleted
            child_page.destroy
          elsif moved
            child_page.model.dataset.unfiltered.filter(id: child_page.id).update(box_position: editable[:box_position])
          end
        end
      end
    end

    # Finds an entry in the parent page and duplicates it to the parent
    # of the newly published page. Positions are not exact as other child pages might not have
    # been published.
    # def insert_entry_for_new_page(revision)
    #   return if parent_id.nil?
    #   _id = self.id
    #   detect_entry = proc { |content_id| content_id == _id }

    #   parent_content_ids = with_editable {
    #     content_model[self.parent_id].content_ids
    #   }
    #   entry = parent_content_ids.find(&detect_entry)
    #   index = parent_content_ids.index(&detect_entry)
    #   published_parent = content_model.get(parent_id)

    #   # store = published_parent.entry_store || []

    #   # unless store.find(&detect_entry)
    #     # store.insert(index, entry).compact!
    #     # published_parent.entry_store = store
    #   # end

    #   published_parent.save
    # end
  end
end