locomotivecms/mounter

View on GitHub
lib/locomotive/mounter/writer/api/pages_writer.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module Locomotive
  module Mounter
    module Writer
      module Api

        # Push pages to a remote LocomotiveCMS engine.
        #
        # New pages are created and existing ones are partially updated.
        #
        # If the :force option is passed, the existing pages are fully updated (title, ...etc).
        # But in any cases, the content of the page will be destroyed, unless the layout of the page
        # changes.
        #
        class PagesWriter < Base

          MAX_ATTEMPTS = 5

          attr_accessor :new_pages
          attr_accessor :remote_translations

          def prepare
            super

            self.new_pages, self.remote_translations = [], {}

            # set the unique identifier to each local page
            self.get(:pages, nil, true).each do |attributes|
              page = self.pages.values.find do |_page|
                _page.safe_fullpath(false) == attributes['fullpath'].dasherize
              end

              self.remote_translations[attributes['fullpath']] = attributes['translated_in']

              page._id = attributes['id'] if page
            end

            # assign the parent_id and the content_type_id to all the pages
            self.pages.values.each do |page|
              next if page.index_or_404?

              page.parent_id = page.parent._id
            end
          end

          # Write all the pages to the remote destination
          def write
            self.each_locale do |locale|
              self.output_locale

              done, attempts = {}, 0

              # begin by the layouts (pages inside the layouts folder)
              _write_layouts(pages, done)

              while done.size < pages.length - 1 && attempts < MAX_ATTEMPTS
                _write(pages['index'], done, attempts > 0)

                # keep track of the attempts because we don't want to get an infinite loop.
                attempts += 1
              end

              write_page(pages['404'])

              if done.size < pages.length - 1
                self.log %{Warning: NOT all the pages were pushed.\n\tCheck that the pages inheritance was done right OR that you translated all your pages.\n}.colorize(color: :red)
              end
            end
          end

          protected

          def _write_layouts(pages, done)
            if pages['layouts']
              _write(pages['layouts'], done, false, false)

              layouts = pages.values.find_all { |page| page.fullpath =~ /^layouts\// }

              # make sure the templates without the extends tag are first in the list
              layouts.sort! do |a, b|
                (a.depth_and_position + (a.extends_template? ? 1000 : 0)) <=> (b.depth_and_position + (b.extends_template? ? 1000 : 0))
              end
              layouts.each do |page|
                _write(page, done, false, false)
              end
            end
          end

          def _write(page, done, already_done = false, with_children = true)
            if self.safely_translated?(page)
              write_page(page) unless already_done
            else
              self.output_resource_op page
              self.output_resource_op_status page, :not_translated
            end

            # mark it as done
            done[page.fullpath] = true

            # loop over its children
            if with_children
              (page.children || []).sort_by(&:depth_and_position).each do |child|
                template_fullpath = child.template_fullpath
                template_fullpath = page.fullpath if template_fullpath && template_fullpath == 'parent'

                if done[child.fullpath].nil? && (!template_fullpath || done[template_fullpath])
                  _write(child, done)
                end
              end
            end
          end

          def write_page(page)
            locale = Locomotive::Mounter.locale

            return unless page.translated_in?(locale)

            return if filtered?(page.fullpath, page.handle)

            self.output_resource_op page

            success = page.persisted? ? self.update_page(page) : self.create_page(page)

            self.output_resource_op_status page, success ? :success : :error

            self.flush_log_buffer
          end

          # Persist a page by calling the API. The returned _id
          # is then set to the page itself.
          #
          # @param [ Object ] page The page to create
          #
          # @return [ Boolean ] True if the call to the API succeeded
          #
          def create_page(page)
            if !page.index_or_404? && page.parent_id.nil?
              raise Mounter::WriterException.new("We are unable to find the parent page for #{page.fullpath}")
            end

            params = self.buffer_log { page_to_params(page) }

            # make a call to the API to create the page, no need to set
            # the locale since it first happens for the default locale.
            response = self.post :pages, params, nil, true

            if response
              page._id = response['id']
              self.new_pages << page._id
            end

            !response.nil?
          end

          # Update a page by calling the API.
          #
          # @param [ Object ] page The page to persist
          #
          # @return [ Boolean ] True if the call to the API succeeded
          #
          def update_page(page)
            locale  = Locomotive::Mounter.locale

            # All the attributes of the page or just some of them
            params = self.buffer_log do
              self.page_to_params(page, self.data? || !self.already_translated?(page))
            end

            # make a call to the API for the update
            response = self.put :pages, page._id, params, locale

            !response.nil?
          end

          # Shortcut to get pages.
          #
          # @return [ Hash ] The hash whose key is the fullpath and the value is the page itself
          #
          def pages
            self.mounting_point.pages
          end

          # Tell if the page passed in parameter has already been
          # translated on the remote engine for the locale passed
          # as the second parameter.
          #
          # @param [ Object ] page The page
          # @param [ String / Symbol ] locale The locale. Use the current locale by default
          #
          # @return [ Boolean] True if already translated.
          #
          def already_translated?(page, locale = nil)
            locale ||= Locomotive::Mounter.locale

            (@remote_translations[page.fullpath] || []).include?(locale.to_s)
          end

          # Tell if the page is correctly localized, meaning it is localized itself
          # as well as its parent.
          #
          # @param [ Object ] page The page
          #
          # @return [ Boolean] True if safely translated.
          #
          def safely_translated?(page)
            if page.parent.nil?
              page.translated_in?(Locomotive::Mounter.locale)
            else
              page.parent.translated_in?(Locomotive::Mounter.locale) &&
              page.translated_in?(Locomotive::Mounter.locale)
            end
          end

          # Return the parameters of a page sent by the API.
          # It includes the editable_elements if the data option is enabled or
          # if the page is a new one.
          #
          # @param [ Object ] page The page
          # @param [ Boolean ] safe If true the to_safe_params is called, otherwise to_params is applied.
          #
          # @return [ Hash ] The parameters of the page
          #
          def page_to_params(page, safe = false)
            (safe ? page.to_safe_params : page.to_params).tap do |params|
              # raw template
              params[:raw_template] = self.replace_content_assets!(params[:raw_template])

              if self.data? || self.new_pages.include?(page._id)
                params[:editable_elements] = (page.editable_elements || []).map(&:to_params)
              else
                params.delete(:editable_elements)
              end

              # editable elements
              (params[:editable_elements] || []).each do |element|
                if element[:content] =~ /^\/samples\//
                  element[:source] = self.path_to_file(element.delete(:content))
                elsif element[:content] =~ %r(^http://)
                  element[:source_url] = element.delete(:content)
                else
                  # string / text elements
                  element[:content] = self.replace_content_assets!(element[:content])
                end
              end
            end
          end

        end

      end
    end
  end
end